Bug 1517993 - An unexpected error occured with browser.cookies.set() with domain and url key for ip address. r=robwu,rpl,mixedpuppy
authormyeongjun <myeongjun.ko@gmail.com>
Thu, 16 May 2019 19:13:16 +0000
changeset 474291 2f69f765ef513a61ad39b4fc2b294135cf514e98
parent 474290 00a761b7fce83bfe255ac76a65641cd83c5c7915
child 474292 aee7bbc03bff773a8ce895d0bd58174f2f8aafa1
push id113144
push usershindli@mozilla.com
push dateFri, 17 May 2019 16:44:55 +0000
treeherdermozilla-inbound@f4c4b796f845 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobwu, rpl, mixedpuppy
bugs1517993
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1517993 - An unexpected error occured with browser.cookies.set() with domain and url key for ip address. r=robwu,rpl,mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D29933
toolkit/components/extensions/parent/ext-cookies.js
toolkit/components/extensions/test/mochitest/test_ext_cookies.html
--- a/toolkit/components/extensions/parent/ext-cookies.js
+++ b/toolkit/components/extensions/parent/ext-cookies.js
@@ -10,16 +10,24 @@ var {
 } = ExtensionUtils;
 
 const SAME_SITE_STATUSES = [
   "no_restriction", // Index 0 = Ci.nsICookie2.SAMESITE_UNSET
   "lax",            // Index 1 = Ci.nsICookie2.SAMESITE_LAX
   "strict",         // Index 2 = Ci.nsICookie2.SAMESITE_STRICT
 ];
 
+const isIPv4 = (host) => {
+  let match = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(host);
+
+  if (match) {
+    return match[1] < 256 && match[2] < 256 && match[3] < 256 && match[4] < 256;
+  }
+  return false;
+};
 const isIPv6 = (host) => host.includes(":");
 const addBracketIfIPv6 = (host) => (isIPv6(host) && !host.startsWith("[")) ? `[${host}]` : host;
 const dropBracketIfIPv6 = (host) => (isIPv6(host) && host.startsWith("[") && host.endsWith("]")) ? host.slice(1, -1) : host;
 
 const convertCookie = ({cookie, isPrivate}) => {
   let result = {
     name: cookie.name,
     value: cookie.value,
@@ -95,16 +103,17 @@ const checkSetCookiePermissions = (exten
 
   // A leading "." is not expected, but is tolerated if it's not the only
   // character in the host. If there is one, start by stripping it off. We'll
   // add a new one on success.
   if (cookie.host.length > 1) {
     cookie.host = cookie.host.replace(/^\./, "");
   }
   cookie.host = cookie.host.toLowerCase();
+  cookie.host = dropBracketIfIPv6(cookie.host);
 
   if (cookie.host != uri.host) {
     // Not an exact match, so check for a valid subdomain.
     let baseDomain;
     try {
       baseDomain = Services.eTLD.getBaseDomain(uri);
     } catch (e) {
       if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
@@ -126,16 +135,22 @@ const checkSetCookiePermissions = (exten
         !isSubdomain(uri.host, cookie.host)) {
       return false;
     }
 
     // RFC2109 suggests that we may only add cookies for sub-domains 1-level
     // below us, but enforcing that would break the web, so we don't.
   }
 
+  // If the host is an IP address, avoid adding a leading ".".
+  // An IP address is not a domain name, and only supports host-only cookies.
+  if (isIPv6(cookie.host) || isIPv4(cookie.host)) {
+    return true;
+  }
+
   // An explicit domain was passed, so add a leading "." to make this a
   // domain cookie.
   cookie.host = "." + cookie.host;
 
   // We don't do any significant checking of path permissions. RFC2109
   // suggests we only allow sites to add cookies for sub-paths, similar to
   // same origin policy enforcement, but no-one implements this.
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_cookies.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies.html
@@ -29,16 +29,62 @@ add_task(async function test_cookies() {
     async function getDocumentCookie(tabId) {
       let results = await browser.tabs.executeScript(tabId, {
         code: "document.cookie",
       });
       browser.test.assertEq(1, results.length, "executeScript returns one result");
       return results[0];
     }
 
+    async function testIpCookie(ipAddress, setHostOnly) {
+      const IP_TEST_HOST = ipAddress;
+      const IP_TEST_URL = `http://${IP_TEST_HOST}/`;
+      const IP_THE_FUTURE = Date.now() + 5 * 60;
+      const IP_STORE_ID = "firefox-default";
+
+      let expectedCookie = {
+        name: "name1",
+        value: "value1",
+        domain: IP_TEST_HOST,
+        hostOnly: true,
+        path: "/",
+        secure: false,
+        httpOnly: false,
+        sameSite: "no_restriction",
+        session: false,
+        expirationDate: IP_THE_FUTURE,
+        storeId: IP_STORE_ID,
+        firstPartyDomain: "",
+      };
+
+      await browser.browsingData.removeCookies({});
+      let ip_cookie = await browser.cookies.set({
+        url: IP_TEST_URL,
+        domain: setHostOnly ? ipAddress : undefined,
+        name: "name1",
+        value: "value1",
+        expirationDate: IP_THE_FUTURE,
+      });
+      assertExpected(expectedCookie, ip_cookie);
+
+      let ip_cookies = await browser.cookies.getAll({name: "name1"});
+      browser.test.assertEq(1, ip_cookies.length, "ip cookie can be added");
+      assertExpected(expectedCookie, ip_cookies[0]);
+
+      ip_cookies = await browser.cookies.getAll({domain: IP_TEST_HOST, name: "name1"});
+      browser.test.assertEq(1, ip_cookies.length, "can get ip cookie by host");
+      assertExpected(expectedCookie, ip_cookies[0]);
+
+      let ip_details = await browser.cookies.remove({url: IP_TEST_URL, name: "name1"});
+      assertExpected({url: IP_TEST_URL, name: "name1", storeId: IP_STORE_ID, firstPartyDomain: ""}, ip_details);
+
+      ip_cookies = await browser.cookies.getAll({name: "name1"});
+      browser.test.assertEq(0, ip_cookies.length, "ip cookie can be removed");
+    }
+
     async function openPrivateWindowAndTab(TEST_URL) {
       // Add some random suffix to make sure that we select the right tab.
       const PRIVATE_TEST_URL = TEST_URL + "?random" + Math.random();
 
       let tabReadyPromise = new Promise((resolve) => {
         browser.webNavigation.onDOMContentLoaded.addListener(function listener({tabId}) {
           browser.webNavigation.onDOMContentLoaded.removeListener(listener);
           resolve(tabId);
@@ -62,53 +108,20 @@ add_task(async function test_cookies() {
     }
 
     function changePort(href, port) {
       let url = new URL(href);
       url.port = port;
       return url.href;
     }
 
-    const IPV6_TEST_HOST = "[2a03:4000:6:310e:216:3eff:fe53:99b]";
-    const IPV6_TEST_URL = `http://${IPV6_TEST_HOST}/`;
-    const IPV6_THE_FUTURE = Date.now() + 5 * 60;
-    const IPV6_STORE_ID = "firefox-default";
-
-    let ipv6_expected = {
-      name: "name1",
-      value: "value1",
-      domain: IPV6_TEST_HOST,
-      hostOnly: true,
-      path: "/",
-      secure: false,
-      httpOnly: false,
-      sameSite: "no_restriction",
-      session: false,
-      expirationDate: IPV6_THE_FUTURE,
-      storeId: IPV6_STORE_ID,
-      firstPartyDomain: "",
-    };
-
-    await browser.browsingData.removeCookies({});
-    let ipv6_cookie = await browser.cookies.set({url: IPV6_TEST_URL, name: "name1", value: "value1", expirationDate: IPV6_THE_FUTURE});
-    assertExpected(ipv6_expected, ipv6_cookie);
-
-    let ipv6_cookies = await browser.cookies.getAll({name: "name1"});
-    browser.test.assertEq(1, ipv6_cookies.length, "ipv6 cookie can be added");
-    assertExpected(ipv6_expected, ipv6_cookies[0]);
-
-    ipv6_cookies = await browser.cookies.getAll({domain: IPV6_TEST_HOST, name: "name1"});
-    browser.test.assertEq(1, ipv6_cookies.length, "can get ipv6 cookie by host");
-    assertExpected(ipv6_expected, ipv6_cookies[0]);
-
-    let ipv6_details = await browser.cookies.remove({url: IPV6_TEST_URL, name: "name1"});
-    assertExpected({url: IPV6_TEST_URL, name: "name1", storeId: IPV6_STORE_ID, firstPartyDomain: ""}, ipv6_details);
-
-    ipv6_cookies = await browser.cookies.getAll({name: "name1"});
-    browser.test.assertEq(0, ipv6_cookies.length, "ipv6 cookie can be removed");
+    await testIpCookie("[2a03:4000:6:310e:216:3eff:fe53:99b]", false);
+    await testIpCookie("[2a03:4000:6:310e:216:3eff:fe53:99b]", true);
+    await testIpCookie("192.168.1.1", false);
+    await testIpCookie("192.168.1.1", true);
 
     const TEST_URL = "http://example.org/";
     const TEST_SECURE_URL = "https://example.org/";
     const THE_FUTURE = Date.now() + 5 * 60;
     const TEST_PATH = "set_path";
     const TEST_URL_WITH_PATH = TEST_URL + TEST_PATH;
     const TEST_COOKIE_PATH = `/${TEST_PATH}`;
     const STORE_ID = "firefox-default";
@@ -331,17 +344,17 @@ add_task(async function test_cookies() {
 
     browser.test.notifyPass("cookies");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     incognitoOverride: "spanning",
     background,
     manifest: {
-      permissions: ["cookies", "*://example.org/", "*://[2a03:4000:6:310e:216:3eff:fe53:99b]/", "webNavigation", "browsingData"],
+      permissions: ["cookies", "*://example.org/", "*://[2a03:4000:6:310e:216:3eff:fe53:99b]/", "*://192.168.1.1/", "webNavigation", "browsingData"],
     },
   });
 
   await extension.startup();
   await extension.awaitFinish("cookies");
   await extension.unload();
 });