Bug 536650 - Cookie values not saved for pages loaded from file://; part 2: deal with empty hosts in the
authorDan Witte <dwitte@mozilla.com>
Tue, 12 Jan 2010 10:29:20 -0800
changeset 37108 21362ffb3b0490c8da773bfcf0cbafa892e4a0a0
parent 37107 e63c419af6673cdb7617be014b0da4b6c1890772
child 37109 b036d913b0bd369eee5c6c2eb77d4e273c0d295d
push idunknown
push userunknown
push dateunknown
bugs536650
milestone1.9.3a1pre
Bug 536650 - Cookie values not saved for pages loaded from file://; part 2: deal with empty hosts in the cookieservice . r=sdwilsh, sr=bz
extensions/cookie/test/unit/test_bug526789.js
netwerk/cookie/public/nsICookieManager.idl
netwerk/cookie/public/nsICookieManager2.idl
netwerk/cookie/public/nsICookieService.idl
netwerk/cookie/src/nsCookieService.cpp
netwerk/cookie/src/nsCookieService.h
--- a/extensions/cookie/test/unit/test_bug526789.js
+++ b/extensions/cookie/test/unit/test_bug526789.js
@@ -1,12 +1,14 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
 function do_check_throws(f, result, stack)
 {
   if (!stack)
     stack = Components.stack.caller;
 
   try {
     f();
   } catch (exc) {
@@ -15,81 +17,212 @@ function do_check_throws(f, result, stac
     do_throw("expected result " + result + ", caught " + exc, stack);
   }
   do_throw("expected result " + result + ", none thrown", stack);
 }
 
 function run_test() {
   var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
   var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
-  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var expiry = (Date.now() + 1000) * 1000;
 
   cm.removeAll();
 
-  // test that an empty host results in a no-op
-  var uri = ios.newURI("http://baz.com/", null, null);
-  var emptyuri = ios.newURI("http:///", null, null);
-  var doturi = ios.newURI("http://./", null, null);
+  // test that variants of 'baz.com' get normalized appropriately, but that
+  // malformed hosts are rejected
+  cm.add("baz.com", "/", "foo", "bar", false, false, true, expiry);
+  do_check_eq(cm.countCookiesFromHost("baz.com"), 1);
+  do_check_eq(cm.countCookiesFromHost("BAZ.com"), 1);
+  do_check_eq(cm.countCookiesFromHost(".baz.com"), 1);
+  do_check_eq(cm.countCookiesFromHost("baz.com."), 1);
+  do_check_eq(cm.countCookiesFromHost(".baz.com."), 1);
+  do_check_throws(function() {
+    cm.countCookiesFromHost("baz.com..");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.countCookiesFromHost("baz..com");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.countCookiesFromHost("..baz.com");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  cm.remove("BAZ.com.", "foo", "/", false);
+  do_check_eq(cm.countCookiesFromHost("baz.com"), 0);
+
+  // test that domain cookies are illegal for IP addresses, aliases such as
+  // 'localhost', and eTLD's such as 'co.uk'
+  cm.add("192.168.0.1", "/", "foo", "bar", false, false, true, expiry);
+  do_check_eq(cm.countCookiesFromHost("192.168.0.1"), 1);
+  do_check_eq(cm.countCookiesFromHost("192.168.0.1."), 1);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".192.168.0.1");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".192.168.0.1.");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  cm.add("localhost", "/", "foo", "bar", false, false, true, expiry);
+  do_check_eq(cm.countCookiesFromHost("localhost"), 1);
+  do_check_eq(cm.countCookiesFromHost("localhost."), 1);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".localhost");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".localhost.");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  cm.add("co.uk", "/", "foo", "bar", false, false, true, expiry);
+  do_check_eq(cm.countCookiesFromHost("co.uk"), 1);
+  do_check_eq(cm.countCookiesFromHost("co.uk."), 1);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".co.uk");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".co.uk.");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  cm.removeAll();
+
+  // test that setting an empty or '.' http:// host results in a no-op
+  var uri = NetUtil.newURI("http://baz.com/");
+  var emptyuri = NetUtil.newURI("http:///");
+  var doturi = NetUtil.newURI("http://./");
+  do_check_eq(uri.asciiHost, "baz.com");
   do_check_eq(emptyuri.asciiHost, "");
   do_check_eq(doturi.asciiHost, ".");
   cs.setCookieString(emptyuri, null, "foo2=bar", null);
   do_check_eq(getCookieCount(), 0);
   cs.setCookieString(doturi, null, "foo3=bar", null);
   do_check_eq(getCookieCount(), 0);
   cs.setCookieString(uri, null, "foo=bar", null);
   do_check_eq(getCookieCount(), 1);
 
   do_check_eq(cs.getCookieString(uri, null), "foo=bar");
   do_check_eq(cs.getCookieString(emptyuri, null), null);
   do_check_eq(cs.getCookieString(doturi, null), null);
 
-  // test that an empty host to add() or remove() throws
-  var expiry = (Date.now() + 1000) * 1000;
+  do_check_eq(cm.countCookiesFromHost(""), 0);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.countCookiesFromHost("..");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  var e = cm.getCookiesFromHost("");
+  do_check_false(e.hasMoreElements());
+  do_check_throws(function() {
+    cm.getCookiesFromHost(".");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+  do_check_throws(function() {
+    cm.getCookiesFromHost("..");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  e = cm.getCookiesFromHost("baz.com");
+  do_check_true(e.hasMoreElements());
+  do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).name, "foo");
+  do_check_false(e.hasMoreElements());
+  e = cm.getCookiesFromHost("");
+  do_check_false(e.hasMoreElements());
+  do_check_throws(function() {
+    cm.getCookiesFromHost(".");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
   do_check_throws(function() {
-    cm.add("", "/", "foo2", "bar", false, false, true, expiry);
+    cm.getCookiesFromHost("..");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  cm.removeAll();
+
+  // test that an empty file:// host works
+  emptyuri = NetUtil.newURI("file:///");
+  do_check_eq(emptyuri.asciiHost, "");
+  do_check_eq(NetUtil.newURI("file://./").asciiHost, "");
+  do_check_eq(NetUtil.newURI("file://foo.bar/").asciiHost, "");
+  cs.setCookieString(emptyuri, null, "foo2=bar", null);
+  do_check_eq(getCookieCount(), 1);
+  cs.setCookieString(emptyuri, null, "foo3=bar; domain=", null);
+  do_check_eq(getCookieCount(), 2);
+  cs.setCookieString(emptyuri, null, "foo4=bar; domain=.", null);
+  do_check_eq(getCookieCount(), 3);
+  cs.setCookieString(emptyuri, null, "foo5=bar; domain=bar.com", null);
+  do_check_eq(getCookieCount(), 3);
+
+  do_check_eq(cs.getCookieString(emptyuri, null), "foo2=bar; foo3=bar; foo4=bar");
+
+  do_check_eq(cm.countCookiesFromHost("baz.com"), 0);
+  do_check_eq(cm.countCookiesFromHost(""), 3);
+  do_check_throws(function() {
+    cm.countCookiesFromHost(".");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  e = cm.getCookiesFromHost("baz.com");
+  do_check_false(e.hasMoreElements());
+  e = cm.getCookiesFromHost("");
+  do_check_true(e.hasMoreElements());
+  e.getNext();
+  do_check_true(e.hasMoreElements());
+  e.getNext();
+  do_check_true(e.hasMoreElements());
+  e.getNext();
+  do_check_false(e.hasMoreElements());
+  do_check_throws(function() {
+    cm.getCookiesFromHost(".");
+  }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+  cm.removeAll();
+
+  // test that an empty host to add() or remove() works,
+  // but a host of '.' doesn't
+  cm.add("", "/", "foo2", "bar", false, false, true, expiry);
   do_check_eq(getCookieCount(), 1);
   do_check_throws(function() {
     cm.add(".", "/", "foo3", "bar", false, false, true, expiry);
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
   do_check_eq(getCookieCount(), 1);
-  cm.add("test.com", "/", "foo", "bar", false, false, true, expiry);
-  do_check_eq(getCookieCount(), 2);
 
-  do_check_throws(function() {
-    cm.remove("", "foo2", "/", false);
-  }, Cr.NS_ERROR_ILLEGAL_VALUE);
-  do_check_eq(getCookieCount(), 2);
+  cm.remove("", "foo2", "/", false);
+  do_check_eq(getCookieCount(), 0);
   do_check_throws(function() {
     cm.remove(".", "foo3", "/", false);
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
-  do_check_eq(getCookieCount(), 2);
-  cm.remove("test.com", "foo", "/", false);
-  do_check_eq(getCookieCount(), 1);
 
-  do_check_eq(cm.countCookiesFromHost("baz.com"), 1);
-  do_check_eq(cm.countCookiesFromHost(""), 0);
-  do_check_eq(cm.countCookiesFromHost("."), 0);
-
-  var e = cm.getCookiesFromHost("baz.com");
-  do_check_true(e.hasMoreElements());
-  do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).name, "foo");
-  do_check_false(e.hasMoreElements());
-  e = cm.getCookiesFromHost("");
-  do_check_false(e.hasMoreElements());
-  e = cm.getCookiesFromHost(".");
-  do_check_false(e.hasMoreElements());
+  // test that the 'domain' attribute accepts a leading dot for IP addresses,
+  // aliases such as 'localhost', eTLD's such as 'co.uk', and the empty host;
+  // but that the resulting cookie is for the exact host only.
+  testDomainCookie("http://192.168.0.1/", "192.168.0.1");
+  testDomainCookie("http://localhost/", "localhost");
+  testDomainCookie("http://co.uk/", "co.uk");
+  testDomainCookie("file:///", "");
 
   cm.removeAll();
 }
 
 function getCookieCount() {
   var count = 0;
   var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
   var enumerator = cm.enumerator;
   while (enumerator.hasMoreElements()) {
     if (!(enumerator.getNext() instanceof Ci.nsICookie2))
       throw new Error("not a cookie");
     ++count;
   }
   return count;
 }
 
+function testDomainCookie(uriString, domain) {
+  var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+  var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+
+  cm.removeAll();
+
+  var uri = NetUtil.newURI(uriString);
+  cs.setCookieString(uri, null, "foo=bar; domain=" + domain, null);
+  var e = cm.getCookiesFromHost(domain);
+  do_check_true(e.hasMoreElements());
+  do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).host, domain);
+  cm.removeAll();
+
+  cs.setCookieString(uri, null, "foo=bar; domain=." + domain, null);
+  e = cm.getCookiesFromHost(domain);
+  do_check_true(e.hasMoreElements());
+  do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).host, domain);
+  cm.removeAll();
+}
+
--- a/netwerk/cookie/public/nsICookieManager.idl
+++ b/netwerk/cookie/public/nsICookieManager.idl
@@ -57,18 +57,27 @@ interface nsICookieManager : nsISupports
 
   /**
    * Called to enumerate through each cookie in the cookie list.
    * The objects enumerated over are of type nsICookie
    */
   readonly attribute nsISimpleEnumerator enumerator;
 
   /**
-   * Called to remove an individual cookie from the cookie list
+   * Called to remove an individual cookie from the cookie list, specified
+   * by host, name, and path. If the cookie cannot be found, no exception
+   * is thrown. Typically, the arguments to this method will be obtained
+   * directly from the desired nsICookie object.
    *
-   * @param aDomain The host or domain for which the cookie was set
+   * @param aHost The host or domain for which the cookie was set. @see
+   *              nsICookieManager2::add for a description of acceptable host
+   *              strings. If the target cookie is a domain cookie, a leading
+   *              dot must be present.
    * @param aName The name specified in the cookie
    * @param aPath The path for which the cookie was set
    * @param aBlocked Indicates if cookies from this host should be permanently blocked
    *
    */
-  void remove(in AUTF8String aDomain, in ACString aName, in AUTF8String aPath, in boolean aBlocked);
+  void remove(in AUTF8String aHost,
+              in ACString    aName,
+              in AUTF8String aPath,
+              in boolean     aBlocked);
 };
--- a/netwerk/cookie/public/nsICookieManager2.idl
+++ b/netwerk/cookie/public/nsICookieManager2.idl
@@ -39,27 +39,32 @@
 
 interface nsICookie2;
 interface nsIFile;
 
 /** 
  * Additions to the frozen nsICookieManager
  */
 
-[scriptable, uuid(d1e9e50f-b78b-4e3b-a474-f3cbca59b013)]
+[scriptable, uuid(94628d1d-8b31-4baa-b474-9c872c440f90)]
 interface nsICookieManager2 : nsICookieManager
 {
   /**
    * Add a cookie. nsICookieService is the normal way to do this. This
    * method is something of a backdoor.
    *
-   * @param aDomain
+   * @param aHost
    *        the host or domain for which the cookie is set. presence of a
    *        leading dot indicates a domain cookie; otherwise, the cookie
-   *        is treated as a non-domain cookie. see RFC2109.
+   *        is treated as a non-domain cookie (see RFC2109). The host string
+   *        will be normalized to ASCII or ACE; any trailing dot will be
+   *        stripped. To be a domain cookie, the host must have at least two
+   *        subdomain parts (e.g. '.foo.com', not '.com'), otherwise an
+   *        exception will be thrown. An empty string is acceptable
+   *        (e.g. file:// URI's).
    * @param aPath
    *        path within the domain for which the cookie is valid
    * @param aName
    *        cookie name
    * @param aValue
    *        cookie data
    * @param aIsSecure
    *        true if the cookie should only be sent over a secure connection.
@@ -69,17 +74,17 @@ interface nsICookieManager2 : nsICookieM
    * @param aIsSession
    *        true if the cookie should exist for the current session only.
    *        see aExpiry.
    * @param aExpiry
    *        expiration date, in seconds since midnight (00:00:00), January 1,
    *        1970 UTC. note that expiry time will also be honored for session cookies;
    *        in this way, the more restrictive of the two will take effect.
    */
-  void add(in AUTF8String aDomain,
+  void add(in AUTF8String aHost,
            in AUTF8String aPath,
            in ACString    aName,
            in ACString    aValue,
            in boolean     aIsSecure,
            in boolean     aIsHttpOnly,
            in boolean     aIsSession,
            in PRInt64     aExpiry);
 
@@ -96,40 +101,40 @@ interface nsICookieManager2 : nsICookieM
 
   /**
    * Count how many cookies exist within the base domain of 'aHost'.
    * Thus, for a host "weather.yahoo.com", the base domain would be "yahoo.com",
    * and any host or domain cookies for "yahoo.com" and its subdomains would be
    * counted.
    *
    * @param aHost
-   *        the host string to begin from, e.g. "google.com". this should consist
-   *        of only the host portion of a URI, and should not contain a leading
-   *        dot, a port, etc.
+   *        the host string to search for, e.g. "google.com". this should consist
+   *        of only the host portion of a URI. see @add for a description of
+   *        acceptable host strings.
    *
    * @return the number of cookies found.
    */
-  unsigned long countCookiesFromHost(in ACString aHost);
+  unsigned long countCookiesFromHost(in AUTF8String aHost);
 
   /**
    * Returns an enumerator of cookies that exist within the base domain of
    * 'aHost'. Thus, for a host "weather.yahoo.com", the base domain would be
    * "yahoo.com", and any host or domain cookies for "yahoo.com" and its
    * subdomains would be returned.
    *
    * @param aHost
-   *        the host string to begin from, e.g. "google.com". this should consist
-   *        of only the host portion of a URI, and should not contain a leading
-   *        dot, a port, etc.
+   *        the host string to search for, e.g. "google.com". this should consist
+   *        of only the host portion of a URI. see @add for a description of
+   *        acceptable host strings.
    *
    * @return an nsISimpleEnumerator of nsICookie2 objects.
    *
    * @see countCookiesFromHost
    */
-  nsISimpleEnumerator getCookiesFromHost(in ACString aHost);
+  nsISimpleEnumerator getCookiesFromHost(in AUTF8String aHost);
 
   /**
    * Import an old-style cookie file. Imported cookies will be added to the
    * existing database. If the database contains any cookies the same as those
    * being imported (i.e. domain, name, and path match), they will be replaced.
    *
    * @param aCookieFile the file to import, usually cookies.txt
    */
--- a/netwerk/cookie/public/nsICookieService.idl
+++ b/netwerk/cookie/public/nsICookieService.idl
@@ -84,16 +84,19 @@ interface nsIChannel;
 [scriptable, uuid(2aaa897a-293c-4d2b-a657-8c9b7136996d)]
 interface nsICookieService : nsISupports
 {
   /*
    * Get the complete cookie string associated with the URI.
    *
    * @param aURI
    *        the URI of the document for which cookies are being queried.
+   *        file:// URI's (i.e. with an empty host) are allowed, but any other
+   *        scheme must have a non-empty host. A trailing dot in the host
+   *        is acceptable, and will be stripped.
    * @param aChannel
    *        the channel used to load the document.  this parameter should not
    *        be null, otherwise the cookies will not be returned if third-party
    *        cookies have been disabled by the user. (the channel is used
    *        to determine the originating URI of the document; if it is not
    *        provided, the cookies will be assumed third-party.)
    *
    * @return the resulting cookie string
@@ -103,16 +106,19 @@ interface nsICookieService : nsISupports
   /*
    * Get the complete cookie string associated with the URI.
    *
    * This function is NOT redundant with getCookieString, as the result
    * will be different based on httponly (see bug 178993)
    *
    * @param aURI
    *        the URI of the document for which cookies are being queried.
+   *        file:// URI's (i.e. with an empty host) are allowed, but any other
+   *        scheme must have a non-empty host. A trailing dot in the host
+   *        is acceptable, and will be stripped.
    * @param aFirstURI
    *        the URI that the user originally typed in or clicked on to initiate
    *        the load of the document referenced by aURI.
    * @param aChannel
    *        the channel used to load the document.  this parameter should not
    *        be null, otherwise the cookies will not be returned if third-party
    *        cookies have been disabled by the user. (the channel is used
    *        to determine the originating URI of the document; if it is not
@@ -122,16 +128,19 @@ interface nsICookieService : nsISupports
    */
   string getCookieStringFromHttp(in nsIURI aURI, in nsIURI aFirstURI, in nsIChannel aChannel);
 
   /*
    * Set the cookie string associated with the URI.
    *
    * @param aURI
    *        the URI of the document for which cookies are being set.
+   *        file:// URI's (i.e. with an empty host) are allowed, but any other
+   *        scheme must have a non-empty host. A trailing dot in the host
+   *        is acceptable, and will be stripped.
    * @param aPrompt
    *        the prompt to use for all user-level cookie notifications.
    * @param aCookie
    *        the cookie string to set.
    * @param aChannel
    *        the channel used to load the document.  this parameter should not
    *        be null, otherwise the cookies will not be set if third-party
    *        cookies have been disabled by the user. (the channel is used
@@ -146,16 +155,19 @@ interface nsICookieService : nsISupports
   /*
    * Set the cookie string and expires associated with the URI.
    *
    * This function is NOT redundant with setCookieString, as the result
    * will be different based on httponly (see bug 178993)
    *
    * @param aURI
    *        the URI of the document for which cookies are being set.
+   *        file:// URI's (i.e. with an empty host) are allowed, but any other
+   *        scheme must have a non-empty host. A trailing dot in the host
+   *        is acceptable, and will be stripped.
    * @param aFirstURI
    *        the URI that the user originally typed in or clicked on to initiate
    *        the load of the document referenced by aURI.
    * @param aPrompt
    *        the prompt to use for all user-level cookie notifications.
    * @param aCookie
    *        the cookie string to set.
    * @param aServerTime
--- a/netwerk/cookie/src/nsCookieService.cpp
+++ b/netwerk/cookie/src/nsCookieService.cpp
@@ -49,16 +49,17 @@
 #include "nsICookiePermission.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIChannel.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsILineInputStream.h"
 #include "nsIEffectiveTLDService.h"
+#include "nsIIDNService.h"
 
 #include "nsTArray.h"
 #include "nsCOMArray.h"
 #include "nsIMutableArray.h"
 #include "nsArrayEnumerator.h"
 #include "nsEnumeratorUtils.h"
 #include "nsAutoPtr.h"
 #include "nsReadableUtils.h"
@@ -400,16 +401,19 @@ nsCookieService::Init()
   if (!mDBState->hostTable.Init()) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsresult rv;
   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // init our pref and observer
   nsCOMPtr<nsIPrefBranch2> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefBranch) {
     prefBranch->AddObserver(kPrefCookiesPermissions, this, PR_TRUE);
     prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, PR_TRUE);
     prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, PR_TRUE);
     prefBranch->AddObserver(kPrefCookiePurgeAge,     this, PR_TRUE);
     PrefChanged(prefBranch);
@@ -805,28 +809,31 @@ nsCookieService::SetCookieStringInternal
 {
   if (!aHostURI) {
     COOKIE_LOGFAILURE(SET_COOKIE, nsnull, aCookieHeader, "host URI is null");
     return NS_OK;
   }
 
   // get the base domain for the host URI.
   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
-  PRBool isIPAddress;
+  // file:// URI's (i.e. with an empty host) are allowed, but any other
+  // scheme must have a non-empty host. A trailing dot in the host
+  // is acceptable, and will be stripped.
+  PRBool requireHostMatch;
   nsCAutoString baseDomain;
-  nsresult rv = GetBaseDomain(aHostURI, baseDomain, isIPAddress);
+  nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
   if (NS_FAILED(rv)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 
                       "couldn't get base domain from URI");
     return NS_OK;
   }
 
   // check default prefs
   PRUint32 cookieStatus = CheckPrefs(aHostURI, aChannel, baseDomain,
-                                     isIPAddress, aCookieHeader);
+                                     requireHostMatch, aCookieHeader);
   // fire a notification if cookie was rejected (but not if there was an error)
   switch (cookieStatus) {
   case STATUS_REJECTED:
     NotifyRejected(aHostURI);
   case STATUS_REJECTED_WITH_ERROR:
     return NS_OK;
   }
 
@@ -845,17 +852,17 @@ nsCookieService::SetCookieStringInternal
   }
 
   // start a transaction on the storage db, to optimize insertions.
   // transaction will automically commit on completion
   mozStorageTransaction transaction(mDBState->dbConn, PR_TRUE);
  
   // switch to a nice string type now, and process each cookie in the header
   nsDependentCString cookieHeader(aCookieHeader);
-  while (SetCookieInternal(aHostURI, aChannel, baseDomain, isIPAddress,
+  while (SetCookieInternal(aHostURI, aChannel, baseDomain, requireHostMatch,
                            cookieHeader, serverTime, aFromHttp));
 
   return NS_OK;
 }
 
 // notify observers that a cookie was rejected due to the users' prefs.
 void
 nsCookieService::NotifyRejected(nsIURI *aHostURI)
@@ -967,35 +974,40 @@ nsCookieService::GetEnumerator(nsISimple
   nsGetEnumeratorData data(&cookieList, PR_Now() / PR_USEC_PER_SEC);
 
   mDBState->hostTable.EnumerateEntries(COMArrayCallback, &data);
 
   return NS_NewArrayEnumerator(aEnumerator, cookieList);
 }
 
 NS_IMETHODIMP
-nsCookieService::Add(const nsACString &aDomain,
+nsCookieService::Add(const nsACString &aHost,
                      const nsACString &aPath,
                      const nsACString &aName,
                      const nsACString &aValue,
                      PRBool            aIsSecure,
                      PRBool            aIsHttpOnly,
                      PRBool            aIsSession,
                      PRInt64           aExpiry)
 {
+  // first, normalize the hostname, and fail if it contains illegal characters.
+  nsCAutoString host(aHost);
+  nsresult rv = NormalizeHost(host);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // get the base domain for the host URI.
   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
   nsCAutoString baseDomain;
-  nsresult rv = GetBaseDomainFromHost(aDomain, baseDomain);
+  rv = GetBaseDomainFromHost(host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt64 currentTimeInUsec = PR_Now();
 
   nsRefPtr<nsCookie> cookie =
-    nsCookie::Create(aName, aValue, aDomain, aPath,
+    nsCookie::Create(aName, aValue, host, aPath,
                      aExpiry,
                      currentTimeInUsec,
                      currentTimeInUsec,
                      aIsSession,
                      aIsSecure,
                      aIsHttpOnly);
   if (!cookie) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -1006,41 +1018,44 @@ nsCookieService::Add(const nsACString &a
 }
 
 NS_IMETHODIMP
 nsCookieService::Remove(const nsACString &aHost,
                         const nsACString &aName,
                         const nsACString &aPath,
                         PRBool           aBlocked)
 {
+  // first, normalize the hostname, and fail if it contains illegal characters.
+  nsCAutoString host(aHost);
+  nsresult rv = NormalizeHost(host);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsCAutoString baseDomain;
-  nsresult rv = GetBaseDomainFromHost(aHost, baseDomain);
+  rv = GetBaseDomainFromHost(host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsListIter matchIter;
   if (FindCookie(baseDomain,
-                 PromiseFlatCString(aHost),
+                 host,
                  PromiseFlatCString(aName),
                  PromiseFlatCString(aPath),
                  matchIter,
                  PR_Now() / PR_USEC_PER_SEC)) {
     nsRefPtr<nsCookie> cookie = matchIter.Cookie();
     RemoveCookieFromList(matchIter);
     NotifyChanged(cookie, NS_LITERAL_STRING("deleted").get());
   }
 
   // check if we need to add the host to the permissions blacklist.
   if (aBlocked && mPermissionService) {
-    nsCAutoString host(NS_LITERAL_CSTRING("http://"));
-    
     // strip off the domain dot, if necessary
-    if (aHost.First() == '.')
-      host.Append(Substring(aHost, 1, aHost.Length() - 1));
-    else
-      host.Append(aHost);
+    if (!host.IsEmpty() && host.First() == '.')
+      host.Cut(0, 1);
+
+    host.Insert(NS_LITERAL_CSTRING("http://"), 0);
 
     nsCOMPtr<nsIURI> uri;
     NS_NewURI(getter_AddRefs(uri), host);
 
     if (uri)
       mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
   }
 
@@ -1318,33 +1333,37 @@ nsCookieService::GetCookieInternal(nsIUR
 
   if (!aHostURI) {
     COOKIE_LOGFAILURE(GET_COOKIE, nsnull, nsnull, "host URI is null");
     return;
   }
 
   // get the base domain, host, and path from the URI.
   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
-  PRBool isIPAddress;
+  // file:// URI's (i.e. with an empty host) are allowed, but any other
+  // scheme must have a non-empty host. A trailing dot in the host
+  // is acceptable, and will be stripped.
+  PRBool requireHostMatch;
   nsCAutoString baseDomain, hostFromURI, pathFromURI;
-  nsresult rv = GetBaseDomain(aHostURI, baseDomain, isIPAddress);
+  nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
   if (NS_SUCCEEDED(rv))
     rv = aHostURI->GetAsciiHost(hostFromURI);
   if (NS_SUCCEEDED(rv))
     rv = aHostURI->GetPath(pathFromURI);
-  // trim trailing dots
-  hostFromURI.Trim(".");
-  if (NS_FAILED(rv) || hostFromURI.IsEmpty()) {
+  // trim any trailing dot
+  if (!hostFromURI.IsEmpty() && hostFromURI.Last() == '.')
+    hostFromURI.Truncate(hostFromURI.Length() - 1);
+  if (NS_FAILED(rv)) {
     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nsnull, "invalid host/path from URI");
     return;
   }
 
   // check default prefs
   PRUint32 cookieStatus = CheckPrefs(aHostURI, aChannel, baseDomain,
-                                     isIPAddress, nsnull);
+                                     requireHostMatch, nsnull);
   // for GetCookie(), we don't fire rejection notifications.
   switch (cookieStatus) {
   case STATUS_REJECTED:
   case STATUS_REJECTED_WITH_ERROR:
     return;
   }
 
   // check if aHostURI is using an https secure protocol.
@@ -1476,17 +1495,17 @@ nsCookieService::GetCookieInternal(nsIUR
 }
 
 // processes a single cookie, and returns PR_TRUE if there are more cookies
 // to be processed
 PRBool
 nsCookieService::SetCookieInternal(nsIURI             *aHostURI,
                                    nsIChannel         *aChannel,
                                    const nsCString    &aBaseDomain,
-                                   PRBool              aIsIPAddress,
+                                   PRBool              aRequireHostMatch,
                                    nsDependentCString &aCookieHeader,
                                    PRInt64             aServerTime,
                                    PRBool              aFromHttp)
 {
   // create a stack-based nsCookieAttributes, to store all the
   // attributes parsed from the cookie
   nsCookieAttributes cookieAttributes;
 
@@ -1514,17 +1533,17 @@ nsCookieService::SetCookieInternal(nsIUR
   }
 
   if (cookieAttributes.name.FindChar('\t') != kNotFound) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
     return newCookie;
   }
 
   // domain & path checks
-  if (!CheckDomain(cookieAttributes, aHostURI, aBaseDomain, aIsIPAddress)) {
+  if (!CheckDomain(cookieAttributes, aHostURI, aBaseDomain, aRequireHostMatch)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
     return newCookie;
   }
   if (!CheckPath(cookieAttributes, aHostURI)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
     return newCookie;
   }
 
@@ -1915,107 +1934,160 @@ nsCookieService::ParseAttributes(nsDepen
   return newCookie;
 }
 
 /******************************************************************************
  * nsCookieService impl:
  * private domain & permission compliance enforcement functions
  ******************************************************************************/
 
+// Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
+// "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
+// dot may be present (and will be stripped). If aHostURI is an IP address,
+// an alias such as 'localhost', an eTLD such as 'co.uk', or the empty string,
+// aBaseDomain will be the exact host, and aRequireHostMatch will be true to
+// indicate that substring matches should not be performed.
 nsresult
 nsCookieService::GetBaseDomain(nsIURI    *aHostURI,
                                nsCString &aBaseDomain,
-                               PRBool    &aIsIPAddress)
+                               PRBool    &aRequireHostMatch)
 {
-  // get the base domain for the host URI.
-  // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
+  // get the base domain. this will fail if the host contains a leading dot,
+  // more than one trailing dot, or is otherwise malformed.
   nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
-  aIsIPAddress = rv == NS_ERROR_HOST_IS_IP_ADDRESS;
-  if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
-      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
-    // address is either an IP address or an alias such as 'localhost'.
-    // use the host as a key in such cases.
+  aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+                      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+  if (aRequireHostMatch) {
+    // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
+    // such as 'co.uk', or the empty string. use the host as a key in such
+    // cases.
     rv = aHostURI->GetAsciiHost(aBaseDomain);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // trim trailing dots
-  aBaseDomain.Trim(".");
-  if (aBaseDomain.IsEmpty())
-    return NS_ERROR_INVALID_ARG;
-
-  aIsIPAddress = PR_FALSE;
+  // aHost (and thus aBaseDomain) may contain a trailing dot; if so, trim it.
+  if (!aBaseDomain.IsEmpty() && aBaseDomain.Last() == '.')
+    aBaseDomain.Truncate(aBaseDomain.Length() - 1);
+
+  // block any URIs without a host that aren't file:// URIs.
+  if (aBaseDomain.IsEmpty()) {
+    PRBool isFileURI = PR_FALSE;
+    aHostURI->SchemeIs("file", &isFileURI);
+    if (!isFileURI)
+      return NS_ERROR_INVALID_ARG;
+  }
+
   return NS_OK;
 }
 
-nsresult 
+// Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
+// "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
+// that aHost is already normalized, and it may contain a leading dot
+// (indicating that it represents a domain). A trailing dot must not be present.
+// If aHost is an IP address, an alias such as 'localhost', an eTLD such as
+// 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
+// leading dot will be treated as an error.
+nsresult
 nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
                                        nsCString        &aBaseDomain)
 {
-  // trim leading and trailing dots
-  nsCAutoString host(aHost);
-  host.Trim(".");
-  if (host.IsEmpty())
+  // aHost must not contain a trailing dot, or be the string '.'.
+  if (!aHost.IsEmpty() && aHost.Last() == '.')
     return NS_ERROR_INVALID_ARG;
 
-  // get the base domain for the host.
-  // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
+  // aHost may contain a leading dot; if so, strip it now.
+  nsDependentCString host(aHost);
+  PRBool domain = !host.IsEmpty() && host.First() == '.';
+  if (domain)
+    host.Rebind(host.BeginReading() + 1, host.EndReading());
+
+  // get the base domain. this will fail if the host contains a leading dot,
+  // more than one trailing dot, or is otherwise malformed.
   nsresult rv = mTLDService->GetBaseDomainFromHost(host, 0, aBaseDomain);
   if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
-    // address is either an IP address or an alias such as 'localhost'.
-    // use the host as a key in such cases.
+    // aHost is either an IP address, an alias such as 'localhost', an eTLD
+    // such as 'co.uk', or the empty string. use the host as a key in such
+    // cases; however, we reject any such hosts with a leading dot, since it
+    // doesn't make sense for them to be domain cookies.
+    if (domain)
+      return NS_ERROR_INVALID_ARG;
+
     aBaseDomain = host;
     return NS_OK;
   }
   return rv;
 }
 
+// Normalizes the given hostname, component by component. ASCII/ACE
+// components are lower-cased, and UTF-8 components are normalized per
+// RFC 3454 and converted to ACE. Any trailing dot is stripped.
+nsresult
+nsCookieService::NormalizeHost(nsCString &aHost)
+{
+  if (!IsASCII(aHost)) {
+    nsCAutoString host;
+    nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
+    if (NS_FAILED(rv))
+      return rv;
+
+    aHost = host;
+  }
+
+  // Only strip the trailing dot if it wouldn't result in the empty string;
+  // in that case, treat it like a leading dot.
+  if (aHost.Length() > 1 && aHost.Last() == '.')
+    aHost.Truncate(aHost.Length() - 1);
+
+  ToLowerCase(aHost);
+  return NS_OK;
+}
+
 // returns PR_TRUE if 'a' is equal to or a subdomain of 'b',
 // assuming no leading or trailing dots are present.
 static inline PRBool IsSubdomainOf(const nsCString &a, const nsCString &b)
 {
   if (a == b)
     return PR_TRUE;
   if (a.Length() > b.Length())
     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
   return PR_FALSE;
 }
 
 PRBool
 nsCookieService::IsForeign(const nsCString &aBaseDomain,
-                           PRBool           aHostIsIPAddress,
+                           PRBool           aRequireHostMatch,
                            nsIURI          *aFirstURI)
 {
   nsCAutoString firstHost;
   if (NS_FAILED(aFirstURI->GetAsciiHost(firstHost))) {
     // assume foreign
     return PR_TRUE;
   }
-  // trim trailing dots
-  firstHost.Trim(".");
-
-  // if we're dealing with IP addresses, require an exact match. this
-  // eliminates any chance of IP address funkiness (e.g. the alias 127.1
-  // domain-matching 99.54.127.1). note that the base domain parameter will be
-  // equivalent to the host IP in this case.
-  if (aHostIsIPAddress)
+
+  // trim any trailing dot
+  if (!firstHost.IsEmpty() && firstHost.Last() == '.')
+    firstHost.Truncate(firstHost.Length() - 1);
+
+  // check whether the host is either an IP address, an alias such as
+  // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
+  // cases, require an exact string match for the domain. note that the base
+  // domain parameter will be equivalent to the host in this case.
+  if (aRequireHostMatch)
     return !firstHost.Equals(aBaseDomain);
 
   // ensure the originating domain is also derived from the host's base domain.
-  // note that if the host is an alias such as 'localhost', the base domain
-  // parameter will also be 'localhost', and this comparison will work.
   return !IsSubdomainOf(firstHost, aBaseDomain);
 }
 
 PRUint32
 nsCookieService::CheckPrefs(nsIURI          *aHostURI,
                             nsIChannel      *aChannel,
                             const nsCString &aBaseDomain,
-                            PRBool           aIsIPAddress,
+                            PRBool           aRequireHostMatch,
                             const char      *aCookieHeader)
 {
   nsresult rv;
 
   // don't let ftp sites get/set cookies (could be a security issue)
   PRBool ftp;
   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
@@ -2052,56 +2124,56 @@ nsCookieService::CheckPrefs(nsIURI      
       NS_WARNING("Foreign cookie blocking enabled, but nsICookiePermission unavailable! Rejecting cookie");
       COOKIE_LOGSTRING(PR_LOG_WARNING, ("CheckPrefs(): foreign blocking enabled, but nsICookiePermission unavailable! Rejecting cookie"));
       return STATUS_REJECTED;
     }
 
     nsCOMPtr<nsIURI> firstURI;
     rv = mPermissionService->GetOriginatingURI(aChannel, getter_AddRefs(firstURI));
 
-    if (NS_FAILED(rv) || IsForeign(aBaseDomain, aIsIPAddress, firstURI)) {
+    if (NS_FAILED(rv) || IsForeign(aBaseDomain, aRequireHostMatch, firstURI)) {
       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "originating server test failed");
       return STATUS_REJECTED;
     }
   }
 
   // if nothing has complained, accept cookie
   return STATUS_ACCEPTED;
 }
 
 // processes domain attribute, and returns PR_TRUE if host has permission to set for this domain.
 PRBool
 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
                              nsIURI             *aHostURI,
                              const nsCString    &aBaseDomain,
-                             PRBool              aIsIPAddress)
+                             PRBool              aRequireHostMatch)
 {
   // get host from aHostURI
   nsCAutoString hostFromURI;
   aHostURI->GetAsciiHost(hostFromURI);
 
-  // trim trailing dots
-  hostFromURI.Trim(".");
-  NS_ASSERTION(!hostFromURI.IsEmpty(), "empty host");
+  // trim any trailing dot
+  if (!hostFromURI.IsEmpty() && hostFromURI.Last() == '.')
+    hostFromURI.Truncate(hostFromURI.Length() - 1);
 
   // if a domain is given, check the host has permission
   if (!aCookieAttributes.host.IsEmpty()) {
     // Tolerate leading '.' characters.
     if (aCookieAttributes.host.First() == '.')
       aCookieAttributes.host.Cut(0, 1);
 
     // switch to lowercase now, to avoid case-insensitive compares everywhere
     ToLowerCase(aCookieAttributes.host);
 
-    // check whether the host is an IP address, and leave the cookie as
-    // a non-domain one. this will require an exact host match for the cookie,
-    // so we eliminate any chance of IP address funkiness (e.g. the alias 127.1
-    // domain-matching 99.54.127.1). bug 105917 originally noted the
-    // requirement to deal with IP addresses.
-    if (aIsIPAddress)
+    // check whether the host is either an IP address, an alias such as
+    // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
+    // cases, require an exact string match for the domain, and leave the cookie
+    // as a non-domain one. bug 105917 originally noted the requirement to deal
+    // with IP addresses.
+    if (aRequireHostMatch)
       return hostFromURI.Equals(aCookieAttributes.host);
 
     // ensure the proposed domain is derived from the base domain; and also
     // that the host domain is derived from the proposed domain (per RFC2109).
     if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
         IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
       // prepend a dot to indicate a domain cookie
       aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
@@ -2463,39 +2535,45 @@ nsCookieService::CountCookiesFromHostInt
 }
 
 // count the number of cookies stored by a particular host. this is provided by the
 // nsICookieManager2 interface.
 NS_IMETHODIMP
 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
                                       PRUint32         *aCountFromHost)
 {
+  // first, normalize the hostname, and fail if it contains illegal characters.
+  nsCAutoString host(aHost);
+  nsresult rv = NormalizeHost(host);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsCAutoString baseDomain;
-  nsresult rv = GetBaseDomainFromHost(aHost, baseDomain);
-  if (NS_FAILED(rv)) {
-    *aCountFromHost = 0;
-    return NS_OK;
-  }
+  rv = GetBaseDomainFromHost(host, baseDomain);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // we don't care about finding the oldest cookie here, so disable the search
   nsEnumerationData data(PR_Now() / PR_USEC_PER_SEC, LL_MININT);
   *aCountFromHost = CountCookiesFromHostInternal(baseDomain, data);
   return NS_OK;
 }
 
 // get an enumerator of cookies stored by a particular host. this is provided by the
 // nsICookieManager2 interface.
 NS_IMETHODIMP
 nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
                                     nsISimpleEnumerator **aEnumerator)
 {
+  // first, normalize the hostname, and fail if it contains illegal characters.
+  nsCAutoString host(aHost);
+  nsresult rv = NormalizeHost(host);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsCAutoString baseDomain;
-  nsresult rv = GetBaseDomainFromHost(aHost, baseDomain);
-  if (NS_FAILED(rv))
-    return NS_NewEmptyEnumerator(aEnumerator);
+  rv = GetBaseDomainFromHost(host, baseDomain);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
   PRInt64 currentTime = PR_Now() / PR_USEC_PER_SEC;
 
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
   if (!entry)
     return NS_NewEmptyEnumerator(aEnumerator);
 
--- a/netwerk/cookie/src/nsCookieService.h
+++ b/netwerk/cookie/src/nsCookieService.h
@@ -57,16 +57,17 @@
 #include "mozIStorageConnection.h"
 
 struct nsCookieAttributes;
 struct nsListIter;
 struct nsEnumerationData;
 
 class nsICookiePermission;
 class nsIEffectiveTLDService;
+class nsIIDNService;
 class nsIPrefBranch;
 class nsIObserverService;
 class nsIURI;
 class nsIChannel;
 
 // hash entry class
 class nsCookieEntry : public PLDHashEntryHdr
 {
@@ -163,44 +164,46 @@ class nsCookieService : public nsICookie
 
   protected:
     void                          PrefChanged(nsIPrefBranch *aPrefBranch);
     nsresult                      InitDB();
     nsresult                      TryInitDB(PRBool aDeleteExistingDB);
     nsresult                      CreateTable();
     void                          CloseDB();
     nsresult                      Read();
-    nsresult                      GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, PRBool &aIsIPAddress);
+    nsresult                      NormalizeHost(nsCString &aHost);
+    nsresult                      GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, PRBool &aRequireHostMatch);
     nsresult                      GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);
     void                          GetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, PRBool aHttpBound, char **aCookie);
     nsresult                      SetCookieStringInternal(nsIURI *aHostURI, nsIPrompt *aPrompt, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, PRBool aFromHttp);
-    PRBool                        SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString& aBaseDomain, PRBool aIsIPAddress, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp);
+    PRBool                        SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString& aBaseDomain, PRBool aRequireHostMatch, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp);
     void                          AddInternal(const nsCString& aBaseDomain, nsCookie *aCookie, PRInt64 aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
     void                          RemoveCookieFromList(const nsListIter &aIter);
     PRBool                        AddCookieToList(const nsCString& aBaseDomain, nsCookie *aCookie, PRBool aWriteToDB = PR_TRUE);
     void                          UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed);
     static PRBool                 GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, PRBool &aEqualsFound);
     static PRBool                 ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie);
-    PRBool                        IsForeign(const nsCString &aBaseDomain, PRBool aHostIsIPAddress, nsIURI *aFirstURI);
-    PRUint32                      CheckPrefs(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString &aBaseDomain, PRBool aIsIPAddress, const char *aCookieHeader);
-    PRBool                        CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, PRBool aIsIPAddress);
+    PRBool                        IsForeign(const nsCString &aBaseDomain, PRBool aRequireHostMatch, nsIURI *aFirstURI);
+    PRUint32                      CheckPrefs(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString &aBaseDomain, PRBool aRequireHostMatch, const char *aCookieHeader);
+    PRBool                        CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, PRBool aRequireHostMatch);
     static PRBool                 CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static PRBool                 GetExpiry(nsCookieAttributes &aCookie, PRInt64 aServerTime, PRInt64 aCurrentTime);
     void                          RemoveAllFromMemory();
     void                          PurgeCookies(PRInt64 aCurrentTimeInUsec);
     PRBool                        FindCookie(const nsCString& aBaseDomain, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter, PRInt64 aCurrentTime);
     PRUint32                      CountCookiesFromHostInternal(const nsCString &aBaseDomain, nsEnumerationData &aData);
     void                          NotifyRejected(nsIURI *aHostURI);
     void                          NotifyChanged(nsISupports *aSubject, const PRUnichar *aData);
 
   protected:
     // cached members.
     nsCOMPtr<nsIObserverService>     mObserverService;
     nsCOMPtr<nsICookiePermission>    mPermissionService;
     nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+    nsCOMPtr<nsIIDNService>          mIDNService;
 
     // we have two separate DB states: one for normal browsing and one for
     // private browsing, switching between them as appropriate. this state
     // encapsulates both the in-memory table and the on-disk DB.
     // note that the private states' dbConn should always be null - we never
     // want to be dealing with the on-disk DB when in private browsing.
     DBState                      *mDBState;
     DBState                       mDefaultDBState;