diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /netwerk/cookie/test | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/cookie/test')
50 files changed, 3455 insertions, 0 deletions
diff --git a/netwerk/cookie/test/browser/browser.toml b/netwerk/cookie/test/browser/browser.toml new file mode 100644 index 0000000000..05a302ddbd --- /dev/null +++ b/netwerk/cookie/test/browser/browser.toml @@ -0,0 +1,42 @@ +[DEFAULT] + +support-files = [ + "file_empty.html", + "file_empty.js", + "head.js" +] + +["browser_broadcastChannel.js"] + +["browser_cookie_insecure_overwrites_secure.js"] + +["browser_cookie_purge_sync.js"] + +["browser_cookies.js"] +support-files = ["server.sjs"] + +["browser_cookies_ipv6.js"] + +["browser_domCache.js"] + +["browser_indexedDB.js"] + +["browser_originattributes.js"] + +["browser_oversize.js"] +support-files = ["oversize.sjs"] + +["browser_partitionedConsole.js"] +support-files = ["partitioned.sjs"] + +["browser_partitioned_telemetry.js"] +support-files = ["partitioned.sjs"] + +["browser_sameSiteConsole.js"] +support-files = ["sameSite.sjs"] + +["browser_serviceWorker.js"] + +["browser_sharedWorker.js"] + +["browser_storage.js"] diff --git a/netwerk/cookie/test/browser/browser_broadcastChannel.js b/netwerk/cookie/test/browser/browser_broadcastChannel.js new file mode 100644 index 0000000000..ee561e6f0c --- /dev/null +++ b/netwerk/cookie/test/browser/browser_broadcastChannel.js @@ -0,0 +1,80 @@ +// BroadcastChannel is not considered part of CookieJar. It's not allowed to +// communicate with other windows with different cookie jar settings. +"use strict"; + +CookiePolicyHelper.runTest("BroadcastChannel", { + cookieJarAccessAllowed: async w => { + new w.BroadcastChannel("hello"); + ok(true, "BroadcastChannel be used"); + }, + + cookieJarAccessDenied: async w => { + try { + new w.BroadcastChannel("hello"); + ok(false, "BroadcastChannel cannot be used!"); + } catch (e) { + ok(true, "BroadcastChannel cannot be used!"); + is(e.name, "SecurityError", "We want a security error message."); + } + }, +}); + +CookiePolicyHelper.runTest("BroadcastChannel in workers", { + cookieJarAccessAllowed: async w => { + function nonBlockingCode() { + new BroadcastChannel("hello"); + postMessage(true); + } + + let blob = new w.Blob([ + nonBlockingCode.toString() + "; nonBlockingCode();", + ]); + ok(blob, "Blob has been created"); + + let blobURL = w.URL.createObjectURL(blob); + ok(blobURL, "Blob URL has been created"); + + let worker = new w.Worker(blobURL); + ok(worker, "Worker has been created"); + + await new w.Promise((resolve, reject) => { + worker.onmessage = function (e) { + if (e) { + resolve(); + } else { + reject(); + } + }; + }); + }, + + cookieJarAccessDenied: async w => { + function blockingCode() { + try { + new BroadcastChannel("hello"); + postMessage(false); + } catch (e) { + postMessage(e.name == "SecurityError"); + } + } + + let blob = new w.Blob([blockingCode.toString() + "; blockingCode();"]); + ok(blob, "Blob has been created"); + + let blobURL = w.URL.createObjectURL(blob); + ok(blobURL, "Blob URL has been created"); + + let worker = new w.Worker(blobURL); + ok(worker, "Worker has been created"); + + await new w.Promise((resolve, reject) => { + worker.onmessage = function (e) { + if (e) { + resolve(); + } else { + reject(); + } + }; + }); + }, +}); diff --git a/netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js b/netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js new file mode 100644 index 0000000000..7461e5cc16 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_cookie_insecure_overwrites_secure.js @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +const urlPath = "/browser/netwerk/cookie/test/browser/file_empty.html"; +const baseDomain = "example.com"; + +// eslint doesn't like http +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const URL_INSECURE_COM = "http://" + baseDomain + urlPath; +const URL_SECURE_COM = "https://" + baseDomain + urlPath; + +// common cookie strings +const COOKIE_BASIC = "foo=one"; +const COOKIE_OTHER = "foo=two"; +const COOKIE_THIRD = "foo=three"; +const COOKIE_FORTH = "foo=four"; + +function securify(cookie) { + return cookie + "; Secure"; +} + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("dom.security.https_first"); + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + Services.prefs.clearUserPref( + "network.cookieJarSettings.unblocked_for_testing" + ); + Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault"); + Services.prefs.clearUserPref("network.cookie.sameSite.noneRequiresSecure"); + Services.prefs.clearUserPref("network.cookie.sameSite.schemeful"); + info("Cleaning up the test"); +}); + +async function setup() { + // HTTPS-First would interfere with this test. + Services.prefs.setBoolPref("dom.security.https_first", false); + + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + + Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false); + Services.prefs.setBoolPref( + "network.cookie.sameSite.noneRequiresSecure", + false + ); + Services.prefs.setBoolPref("network.cookie.sameSite.schemeful", true); + Services.cookies.removeAll(); +} +add_task(setup); + +// note: +// 1. The URL scheme will not matter for insecure cookies, since +// cookies are not "schemeful" in this sense. +// So an insecure cookie set anywhere will be visible on http and https sites +// Secure cookies are different, they will only be visible from https sites +// and will prevent cookie setting of the same name on insecure sites. +// +// 2. The different processes (tabs) shouldn't matter since +// cookie adds/changes are distributed to other processes on a need-to-know +// basis. + +add_task(async function test_insecure_cant_overwrite_secure_via_doc() { + // insecure + const tab1 = BrowserTestUtils.addTab(gBrowser, URL_INSECURE_COM); + const browser = gBrowser.getBrowserForTab(tab1); + await BrowserTestUtils.browserLoaded(browser); + + // secure + const tab2 = BrowserTestUtils.addTab(gBrowser, URL_SECURE_COM); + const browser2 = gBrowser.getBrowserForTab(tab2); + await BrowserTestUtils.browserLoaded(browser2); + + // init with insecure cookie on insecure origin child process + await SpecialPowers.spawn( + browser, + [COOKIE_BASIC, COOKIE_BASIC], + (cookie, expected) => { + content.document.cookie = cookie; + is(content.document.cookie, expected); + } + ); + + // insecure cookie visible on secure origin process (sanity check) + await SpecialPowers.spawn(browser2, [COOKIE_BASIC], expected => { + is(content.document.cookie, expected); + }); + + // overwrite insecure cookie on secure origin with secure cookie (sanity check) + await SpecialPowers.spawn( + browser2, + [securify(COOKIE_OTHER), COOKIE_OTHER], + (cookie, expected) => { + content.document.cookie = cookie; + is(content.document.cookie, expected); + } + ); + + // insecure cookie will NOT overwrite the secure one on insecure origin + // and cookie.document appears blank + await SpecialPowers.spawn(browser, [COOKIE_THIRD, ""], (cookie, expected) => { + content.document.cookie = cookie; // quiet failure here + is(content.document.cookie, expected); + }); + + // insecure cookie will overwrite secure cookie on secure origin + // a bit weird, but this is normal + await SpecialPowers.spawn( + browser2, + [COOKIE_FORTH, COOKIE_FORTH], + (cookie, expected) => { + content.document.cookie = cookie; + is(content.document.cookie, expected); + } + ); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); + Services.cookies.removeAll(); +}); diff --git a/netwerk/cookie/test/browser/browser_cookie_purge_sync.js b/netwerk/cookie/test/browser/browser_cookie_purge_sync.js new file mode 100644 index 0000000000..186797d058 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_cookie_purge_sync.js @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This test checks that cookie purge broadcast correctly +// updates content process memory as triggered by: +// 1. stale-cookie update from content process +// 2. delete cookie by host from parent process +// 3. clear all cookies from parent process + +const URL_EXAMPLE = "https://example.com"; +const COOKIE_NAMEVALUE = "name=value"; +const COOKIE_NAMEVALUE_2 = COOKIE_NAMEVALUE + "2"; +const COOKIE_STRING = COOKIE_NAMEVALUE + "; Secure; SameSite=None"; +const COOKIE_STRING_2 = COOKIE_NAMEVALUE_2 + "; Secure; SameSite=None"; +const MAX_AGE_OLD = 2; // seconds +const MAX_AGE_NEW = 100; // 100 sec + +registerCleanupFunction(() => { + info("Cleaning up the test setup"); + Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault"); + Services.cookies.removeAll(); +}); + +add_setup(async function () { + info("Setting up the test"); + Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false); +}); + +function waitForNotificationPromise(notification, expected) { + return new Promise(resolve => { + function observer(subject, topic, data) { + is(content.document.cookie, expected); + Services.obs.removeObserver(observer, notification); + resolve(); + } + Services.obs.addObserver(observer, notification); + }); +} + +add_task(async function test_purge_sync_batch_and_deleted() { + const tab1 = BrowserTestUtils.addTab(gBrowser, URL_EXAMPLE); + const browser = gBrowser.getBrowserForTab(tab1); + await BrowserTestUtils.browserLoaded(browser); + + const tab2 = BrowserTestUtils.addTab(gBrowser, URL_EXAMPLE); + const browser2 = gBrowser.getBrowserForTab(tab2); + await BrowserTestUtils.browserLoaded(browser2); + + let firstCookieAdded = SpecialPowers.spawn( + browser2, + ["content-added-cookie", COOKIE_NAMEVALUE], + waitForNotificationPromise + ); + await TestUtils.waitForTick(); // waiting helps --verify + + // set old cookie in tab 1 and check it in tab 2 + await SpecialPowers.spawn( + browser, + [COOKIE_STRING, MAX_AGE_OLD], + (cookie, max_age) => { + content.document.cookie = cookie + ";Max-Age=" + max_age; + } + ); + await firstCookieAdded; + + // wait until the first cookie expires + await SpecialPowers.spawn(browser2, [], () => { + return ContentTaskUtils.waitForCondition( + () => content.document.cookie == "", + "cookie did not expire in time", + 200 + ).catch(msg => { + is(false, "Cookie did not expire in time"); + }); + }); + + // BATCH_DELETED/BatchDeleted pathway + let batchDeletedPromise = SpecialPowers.spawn( + browser, + ["content-batch-deleted-cookies", COOKIE_NAMEVALUE_2], + waitForNotificationPromise + ); + await TestUtils.waitForTick(); // waiting helps --verify + await SpecialPowers.spawn( + browser, + [COOKIE_STRING_2, MAX_AGE_NEW], + (cookie, max_age) => { + content.document.cookie = cookie + ";Max-Age=" + max_age; + } + ); + await batchDeletedPromise; + + // COOKIE_DELETED/RemoveCookie pathway + let cookieRemovedPromise = SpecialPowers.spawn( + browser, + ["content-removed-cookie", ""], + waitForNotificationPromise + ); + let cookieRemovedPromise2 = SpecialPowers.spawn( + browser2, + ["content-removed-cookie", ""], + waitForNotificationPromise + ); + await TestUtils.waitForTick(); + Services.cookies.removeCookiesFromExactHost( + "example.com", + JSON.stringify({}) + ); + await cookieRemovedPromise; + await cookieRemovedPromise2; + + // cleanup prep + let anotherCookieAdded = SpecialPowers.spawn( + browser2, + ["content-added-cookie", COOKIE_NAMEVALUE], + waitForNotificationPromise + ); + await TestUtils.waitForTick(); // waiting helps --verify + await SpecialPowers.spawn( + browser, + [COOKIE_STRING, MAX_AGE_NEW], + (cookie, max_age) => { + content.document.cookie = cookie + ";Max-Age=" + max_age; + } + ); + await anotherCookieAdded; + + // ALL_COOKIES_CLEARED/RemoveAll pathway + let cleanup = SpecialPowers.spawn( + browser2, + ["content-removed-all-cookies", ""], + waitForNotificationPromise + ); + await TestUtils.waitForTick(); + Services.cookies.removeAll(); + await cleanup; + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); diff --git a/netwerk/cookie/test/browser/browser_cookies.js b/netwerk/cookie/test/browser/browser_cookies.js new file mode 100644 index 0000000000..8a0d8332bf --- /dev/null +++ b/netwerk/cookie/test/browser/browser_cookies.js @@ -0,0 +1,53 @@ +"use strict"; + +CookiePolicyHelper.runTest("document.cookies", { + cookieJarAccessAllowed: async _ => { + let hasCookie = !!content.document.cookie.length; + + await content + .fetch("server.sjs") + .then(r => r.text()) + .then(text => { + is( + text, + hasCookie ? "cookie-present" : "cookie-not-present", + "document.cookie is consistent with fetch requests" + ); + }); + + content.document.cookie = "name=value"; + ok(content.document.cookie.includes("name=value"), "Some cookies for me"); + ok(content.document.cookie.includes("foopy=1"), "Some cookies for me"); + + await content + .fetch("server.sjs") + .then(r => r.text()) + .then(text => { + is(text, "cookie-present", "We should have cookies"); + }); + + ok(!!content.document.cookie.length, "Some Cookies for me"); + }, + + cookieJarAccessDenied: async _ => { + is(content.document.cookie, "", "No cookies for me"); + content.document.cookie = "name=value"; + is(content.document.cookie, "", "No cookies for me"); + + await content + .fetch("server.sjs") + .then(r => r.text()) + .then(text => { + is(text, "cookie-not-present", "We should not have cookies"); + }); + // Let's do it twice. + await content + .fetch("server.sjs") + .then(r => r.text()) + .then(text => { + is(text, "cookie-not-present", "We should not have cookies"); + }); + + is(content.document.cookie, "", "Still no cookies for me"); + }, +}); diff --git a/netwerk/cookie/test/browser/browser_cookies_ipv6.js b/netwerk/cookie/test/browser/browser_cookies_ipv6.js new file mode 100644 index 0000000000..088a76f4e8 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_cookies_ipv6.js @@ -0,0 +1,57 @@ +"use strict"; + +let { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +let gHttpServer = null; +let ip = "[::1]"; + +function contentHandler(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/html", false); + let body = ` + <!DOCTYPE HTML> + <html> + <head> + <meta charset='utf-8'> + <title>Cookie ipv6 Test</title> + </head> + <body> + </body> + </html>`; + response.bodyOutputStream.write(body, body.length); +} + +add_task(async _ => { + if (!gHttpServer) { + gHttpServer = new HttpServer(); + gHttpServer.registerPathHandler("/content", contentHandler); + gHttpServer._start(-1, ip); + } + + registerCleanupFunction(() => { + gHttpServer.stop(() => { + gHttpServer = null; + }); + }); + + let serverPort = gHttpServer.identity.primaryPort; + let testURL = `http://${ip}:${serverPort}/content`; + + // Let's open our tab. + const tab = BrowserTestUtils.addTab(gBrowser, testURL); + gBrowser.selectedTab = tab; + + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Test if we can set and get document.cookie successfully. + await SpecialPowers.spawn(browser, [], () => { + content.document.cookie = "foo=bar"; + is(content.document.cookie, "foo=bar"); + }); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); +}); diff --git a/netwerk/cookie/test/browser/browser_domCache.js b/netwerk/cookie/test/browser/browser_domCache.js new file mode 100644 index 0000000000..5f1aa84d83 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_domCache.js @@ -0,0 +1,25 @@ +"use strict"; + +CookiePolicyHelper.runTest("DOM Cache", { + cookieJarAccessAllowed: async w => { + await w.caches.open("wow").then( + _ => { + ok(true, "DOM Cache can be used!"); + }, + _ => { + ok(false, "DOM Cache can be used!"); + } + ); + }, + + cookieJarAccessDenied: async w => { + await w.caches.open("wow").then( + _ => { + ok(false, "DOM Cache cannot be used!"); + }, + _ => { + ok(true, "DOM Cache cannot be used!"); + } + ); + }, +}); diff --git a/netwerk/cookie/test/browser/browser_indexedDB.js b/netwerk/cookie/test/browser/browser_indexedDB.js new file mode 100644 index 0000000000..7f417077eb --- /dev/null +++ b/netwerk/cookie/test/browser/browser_indexedDB.js @@ -0,0 +1,84 @@ +"use strict"; + +CookiePolicyHelper.runTest("IndexedDB", { + cookieJarAccessAllowed: async w => { + w.indexedDB.open("test", "1"); + ok(true, "IDB should be allowed"); + }, + + cookieJarAccessDenied: async w => { + try { + w.indexedDB.open("test", "1"); + ok(false, "IDB should be blocked"); + } catch (e) { + ok(true, "IDB should be blocked"); + is(e.name, "SecurityError", "We want a security error message."); + } + }, +}); + +CookiePolicyHelper.runTest("IndexedDB in workers", { + cookieJarAccessAllowed: async w => { + function nonBlockCode() { + indexedDB.open("test", "1"); + postMessage(true); + } + + let blob = new w.Blob([nonBlockCode.toString() + "; nonBlockCode();"]); + ok(blob, "Blob has been created"); + + let blobURL = w.URL.createObjectURL(blob); + ok(blobURL, "Blob URL has been created"); + + let worker = new w.Worker(blobURL); + ok(worker, "Worker has been created"); + + await new w.Promise((resolve, reject) => { + worker.onmessage = function (e) { + if (e.data) { + resolve(); + } else { + reject(); + } + }; + + worker.onerror = function (e) { + reject(); + }; + }); + }, + + cookieJarAccessDenied: async w => { + function blockCode() { + try { + indexedDB.open("test", "1"); + postMessage(false); + } catch (e) { + postMessage(e.name == "SecurityError"); + } + } + + let blob = new w.Blob([blockCode.toString() + "; blockCode();"]); + ok(blob, "Blob has been created"); + + let blobURL = w.URL.createObjectURL(blob); + ok(blobURL, "Blob URL has been created"); + + let worker = new w.Worker(blobURL); + ok(worker, "Worker has been created"); + + await new w.Promise((resolve, reject) => { + worker.onmessage = function (e) { + if (e.data) { + resolve(); + } else { + reject(); + } + }; + + worker.onerror = function (e) { + reject(); + }; + }); + }, +}); diff --git a/netwerk/cookie/test/browser/browser_originattributes.js b/netwerk/cookie/test/browser/browser_originattributes.js new file mode 100644 index 0000000000..fab7e67b2e --- /dev/null +++ b/netwerk/cookie/test/browser/browser_originattributes.js @@ -0,0 +1,121 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const USER_CONTEXTS = ["default", "personal", "work"]; + +const COOKIE_NAMES = ["cookie0", "cookie1", "cookie2"]; + +const TEST_URL = + "http://example.com/browser/netwerk/cookie/test/browser/file_empty.html"; + +// opens `uri' in a new tab with the provided userContextId and focuses it. +// returns the newly opened tab +async function openTabInUserContext(uri, userContextId) { + // open the tab in the correct userContextId + let tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + + // select tab and make sure its browser is focused + gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + + let browser = gBrowser.getBrowserForTab(tab); + // wait for tab load + await BrowserTestUtils.browserLoaded(browser); + + return { tab, browser }; +} + +add_setup(async function () { + // make sure userContext is enabled. + await new Promise(resolve => { + SpecialPowers.pushPrefEnv( + { set: [["privacy.userContext.enabled", true]] }, + resolve + ); + }); +}); + +add_task(async function test() { + // load the page in 3 different contexts and set a cookie + // which should only be visible in that context + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // open our tab in the given user context + let { tab, browser } = await openTabInUserContext(TEST_URL, userContextId); + + await SpecialPowers.spawn( + browser, + [{ names: COOKIE_NAMES, value: USER_CONTEXTS[userContextId] }], + function (opts) { + for (let name of opts.names) { + content.document.cookie = name + "=" + opts.value; + } + } + ); + + // remove the tab + gBrowser.removeTab(tab); + } + + let expectedValues = USER_CONTEXTS.slice(0); + await checkCookies(expectedValues, "before removal"); + + // remove cookies that belongs to user context id #1 + Services.cookies.removeCookiesWithOriginAttributes( + JSON.stringify({ userContextId: 1 }) + ); + + expectedValues[1] = undefined; + await checkCookies(expectedValues, "after removal"); +}); + +async function checkCookies(expectedValues, time) { + for (let userContextId of Object.keys(expectedValues)) { + let cookiesFromTitle = await getCookiesFromJS(userContextId); + let cookiesFromManager = getCookiesFromManager(userContextId); + + let expectedValue = expectedValues[userContextId]; + for (let name of COOKIE_NAMES) { + is( + cookiesFromTitle[name], + expectedValue, + `User context ${userContextId}: ${name} should be correct from title ${time}` + ); + is( + cookiesFromManager[name], + expectedValue, + `User context ${userContextId}: ${name} should be correct from manager ${time}` + ); + } + } +} + +function getCookiesFromManager(userContextId) { + let cookies = {}; + let allCookies = Services.cookies.getCookiesWithOriginAttributes( + JSON.stringify({ userContextId }) + ); + for (let cookie of allCookies) { + cookies[cookie.name] = cookie.value; + } + return cookies; +} + +async function getCookiesFromJS(userContextId) { + let { tab, browser } = await openTabInUserContext(TEST_URL, userContextId); + + // get the cookies + let cookieString = await SpecialPowers.spawn(browser, [], function () { + return content.document.cookie; + }); + + // check each item in the title and validate it meets expectatations + let cookies = {}; + for (let cookie of cookieString.split(";")) { + let [name, value] = cookie.trim().split("="); + cookies[name] = value; + } + + gBrowser.removeTab(tab); + return cookies; +} diff --git a/netwerk/cookie/test/browser/browser_oversize.js b/netwerk/cookie/test/browser/browser_oversize.js new file mode 100644 index 0000000000..f6e1f8a70b --- /dev/null +++ b/netwerk/cookie/test/browser/browser_oversize.js @@ -0,0 +1,96 @@ +"use strict"; + +const OVERSIZE_DOMAIN = "http://example.com/"; +const OVERSIZE_PATH = "browser/netwerk/cookie/test/browser/"; +const OVERSIZE_TOP_PAGE = OVERSIZE_DOMAIN + OVERSIZE_PATH + "oversize.sjs"; + +add_task(async _ => { + const expected = []; + + const consoleListener = { + observe(what) { + if (!(what instanceof Ci.nsIConsoleMessage)) { + return; + } + + info("Console Listener: " + what); + for (let i = expected.length - 1; i >= 0; --i) { + const e = expected[i]; + + if (what.message.includes(e.match)) { + ok(true, "Message received: " + e.match); + expected.splice(i, 1); + e.resolve(); + } + } + }, + }; + + Services.console.registerListener(consoleListener); + + registerCleanupFunction(() => + Services.console.unregisterListener(consoleListener) + ); + + const netPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “a” is invalid because its size is too big. Max size is 4096 B.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “b” is invalid because its path size is too big. Max size is 1024 B.", + }); + }), + ]; + + // Let's open our tab. + const tab = BrowserTestUtils.addTab(gBrowser, OVERSIZE_TOP_PAGE); + gBrowser.selectedTab = tab; + + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Let's wait for the first set of console events. + await Promise.all(netPromises); + + // the DOM list of events. + const domPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “d” is invalid because its size is too big. Max size is 4096 B.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “e” is invalid because its path size is too big. Max size is 1024 B.", + }); + }), + ]; + + // Let's use document.cookie + SpecialPowers.spawn(browser, [], () => { + const maxBytesPerCookie = 4096; + const maxBytesPerCookiePath = 1024; + content.document.cookie = "d=" + Array(maxBytesPerCookie + 1).join("x"); + content.document.cookie = + "e=f; path=/" + Array(maxBytesPerCookiePath + 1).join("x"); + }); + + // Let's wait for the dom events. + await Promise.all(domPromises); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); +}); diff --git a/netwerk/cookie/test/browser/browser_partitionedConsole.js b/netwerk/cookie/test/browser/browser_partitionedConsole.js new file mode 100644 index 0000000000..ec834bfbcf --- /dev/null +++ b/netwerk/cookie/test/browser/browser_partitionedConsole.js @@ -0,0 +1,201 @@ +"use strict"; + +const DOMAIN = "https://example.com/"; +const PATH = "browser/netwerk/cookie/test/browser/"; +const TOP_PAGE = DOMAIN + PATH + "file_empty.html"; + +// Run the test with CHIPS disabled, expecting a warning message +add_task(async _ => { + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", false]], + }); + + const expected = []; + + const consoleListener = { + observe(what) { + if (!(what instanceof Ci.nsIConsoleMessage)) { + return; + } + + info("Console Listener: " + what); + for (let i = expected.length - 1; i >= 0; --i) { + const e = expected[i]; + + if (what.message.includes(e.match)) { + ok(true, "Message received: " + e.match); + expected.splice(i, 1); + e.resolve(); + } + } + }, + }; + + Services.console.registerListener(consoleListener); + + registerCleanupFunction(() => + Services.console.unregisterListener(consoleListener) + ); + + const netPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “a” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.", + }); + }), + ]; + + // Let's open our tab. + const tab = BrowserTestUtils.addTab(gBrowser, TOP_PAGE); + gBrowser.selectedTab = tab; + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Set cookies with cross-site HTTP + await SpecialPowers.spawn(browser, [], async function () { + await content.fetch( + "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs", + { credentials: "include" } + ); + }); + + // Let's wait for the first set of console events. + await Promise.all(netPromises); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); +}); + +// Run the test with CHIPS enabled, expecting a different warning message +add_task(async _ => { + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", true]], + }); + + const expected = []; + + const consoleListener = { + observe(what) { + if (!(what instanceof Ci.nsIConsoleMessage)) { + return; + } + + info("Console Listener: " + what); + for (let i = expected.length - 1; i >= 0; --i) { + const e = expected[i]; + + if (what.message.includes(e.match)) { + ok(true, "Message received: " + e.match); + expected.splice(i, 1); + e.resolve(); + } + } + }, + }; + + Services.console.registerListener(consoleListener); + + registerCleanupFunction(() => + Services.console.unregisterListener(consoleListener) + ); + + const netPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “a” has been rejected because it is foreign and does not have the “Partitioned“ attribute.", + }); + }), + ]; + + // Let's open our tab. + const tab = BrowserTestUtils.addTab(gBrowser, TOP_PAGE); + gBrowser.selectedTab = tab; + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Set cookies with cross-site HTTP + await SpecialPowers.spawn(browser, [], async function () { + await content.fetch( + "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs", + { credentials: "include" } + ); + }); + + // Let's wait for the first set of console events. + await Promise.all(netPromises); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); +}); + +// Run the test with CHIPS enabled, ensuring the partitioned cookies require +// secure context. +add_task(async function partitionedAttrRequiresSecure() { + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", true]], + }); + + // Clear all cookies before testing. + Services.cookies.removeAll(); + + const expected = []; + + const consoleListener = { + observe(what) { + if (!(what instanceof Ci.nsIConsoleMessage)) { + return; + } + + info("Console Listener: " + what); + for (let i = expected.length - 1; i >= 0; --i) { + const e = expected[i]; + + if (what.message.includes(e.match)) { + ok(true, "Message received: " + e.match); + expected.splice(i, 1); + e.resolve(); + } + } + }, + }; + + Services.console.registerListener(consoleListener); + + registerCleanupFunction(() => + Services.console.unregisterListener(consoleListener) + ); + + const netPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “c” has been rejected because it has the “Partitioned” attribute but is missing the “secure” attribute.", + }); + }), + ]; + + // Let's open our tab. + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TOP_PAGE); + + // Set cookies with cross-site HTTP + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + await content.fetch( + "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs?nosecure", + { credentials: "include" } + ); + }); + + // Let's wait for the first set of console events. + await Promise.all(netPromises); + + // Ensure no cookie is set. + is(Services.cookies.cookies.length, 0, "No cookie is set."); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); +}); diff --git a/netwerk/cookie/test/browser/browser_partitioned_telemetry.js b/netwerk/cookie/test/browser/browser_partitioned_telemetry.js new file mode 100644 index 0000000000..f89bcdd189 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_partitioned_telemetry.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const TEST_URL = + "https://example.com/browser/netwerk/cookie/test/browser/file_empty.html"; + +async function validateTelemetryValues( + { setCookies, setForeigns, setPartitioneds, setForeignPartitioneds }, + message +) { + await Services.fog.testFlushAllChildren(); + let setCookieTelemetry = Glean.networking.setCookie.testGetValue(); + is( + setCookieTelemetry ?? undefined, + setCookies, + message + " - all set cookies" + ); + let foreignTelemetry = Glean.networking.setCookieForeign.testGetValue(); + is( + foreignTelemetry?.numerator, + setForeigns, + message + " - foreign set cookies" + ); + is( + foreignTelemetry?.denominator, + setCookies, + message + " - foreign set cookies denominator" + ); + let partitonedTelemetry = + Glean.networking.setCookiePartitioned.testGetValue(); + is( + partitonedTelemetry?.numerator, + setPartitioneds, + message + " - partitioned set cookies" + ); + is( + partitonedTelemetry?.denominator, + setCookies, + message + " - partitioned set cookies denominator" + ); + let foreignPartitonedTelemetry = + Glean.networking.setCookieForeignPartitioned.testGetValue(); + is( + foreignPartitonedTelemetry?.numerator, + setForeignPartitioneds, + message + " - foreign partitioned set cookies" + ); + is( + foreignPartitonedTelemetry?.denominator, + setCookies, + message + " - foreign partitioned set cookies denominator" + ); +} + +add_task(async () => { + await Services.fog.testFlushAllChildren(); + Services.fog.testResetFOG(); + await validateTelemetryValues({}, "initially empty"); + + // open a browser window for the test + let tab = BrowserTestUtils.addTab(gBrowser, TEST_URL); + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Set cookies with Javascript + await SpecialPowers.spawn(browser, [], function () { + content.document.cookie = "a=1; Partitioned; SameSite=None; Secure"; + content.document.cookie = "b=2; SameSite=None; Secure"; + }); + await validateTelemetryValues( + { + setCookies: 2, + setForeigns: 0, + setPartitioneds: 1, + setForeignPartitioneds: 0, + }, + "javascript cookie" + ); + + // Set cookies with HTTP + await SpecialPowers.spawn(browser, [], async function () { + await content.fetch("partitioned.sjs"); + }); + await validateTelemetryValues( + { + setCookies: 4, + setForeigns: 0, + setPartitioneds: 2, + setForeignPartitioneds: 0, + }, + "same site fetch" + ); + + // Set cookies with cross-site HTTP + await SpecialPowers.spawn(browser, [], async function () { + await content.fetch( + "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs", + { credentials: "include" } + ); + }); + await validateTelemetryValues( + { + setCookies: 6, + setForeigns: 2, + setPartitioneds: 3, + setForeignPartitioneds: 1, + }, + "foreign fetch" + ); + + // Set cookies with cross-site HTTP redirect + await SpecialPowers.spawn(browser, [], async function () { + await content.fetch( + encodeURI( + "https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs?redirect=https://example.com/browser/netwerk/cookie/test/browser/partitioned.sjs?nocookie" + ), + { credentials: "include" } + ); + }); + + await validateTelemetryValues( + { + setCookies: 8, + setForeigns: 4, + setPartitioneds: 4, + setForeignPartitioneds: 2, + }, + "foreign fetch redirect" + ); + + // remove the tab + gBrowser.removeTab(tab); +}); diff --git a/netwerk/cookie/test/browser/browser_sameSiteConsole.js b/netwerk/cookie/test/browser/browser_sameSiteConsole.js new file mode 100644 index 0000000000..84527296b2 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_sameSiteConsole.js @@ -0,0 +1,133 @@ +"use strict"; + +const SAMESITE_DOMAIN = "http://example.com/"; +const SAMESITE_PATH = "browser/netwerk/cookie/test/browser/"; +const SAMESITE_TOP_PAGE = SAMESITE_DOMAIN + SAMESITE_PATH + "sameSite.sjs"; + +add_task(async _ => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["network.cookie.sameSite.laxByDefault", true], + ["network.cookie.sameSite.noneRequiresSecure", true], + ], + }); + + const expected = []; + + const consoleListener = { + observe(what) { + if (!(what instanceof Ci.nsIConsoleMessage)) { + return; + } + + info("Console Listener: " + what); + for (let i = expected.length - 1; i >= 0; --i) { + const e = expected[i]; + + if (what.message.includes(e.match)) { + ok(true, "Message received: " + e.match); + expected.splice(i, 1); + e.resolve(); + } + } + }, + }; + + Services.console.registerListener(consoleListener); + + registerCleanupFunction(() => + Services.console.unregisterListener(consoleListener) + ); + + const netPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “a” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “b” rejected because it has the “SameSite=None” attribute but is missing the “secure” attribute.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Invalid “SameSite“ value for cookie “c”. The supported values are: “Lax“, “Strict“, “None“.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “c” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.", + }); + }), + ]; + + // Let's open our tab. + const tab = BrowserTestUtils.addTab(gBrowser, SAMESITE_TOP_PAGE); + gBrowser.selectedTab = tab; + + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Let's wait for the first set of console events. + await Promise.all(netPromises); + + // the DOM list of events. + const domPromises = [ + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “d” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “e” rejected because it has the “SameSite=None” attribute but is missing the “secure” attribute.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Invalid “SameSite“ value for cookie “f”. The supported values are: “Lax“, “Strict“, “None“.", + }); + }), + + new Promise(resolve => { + expected.push({ + resolve, + match: + "Cookie “f” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.", + }); + }), + ]; + + // Let's use document.cookie + SpecialPowers.spawn(browser, [], () => { + content.document.cookie = "d=4"; + content.document.cookie = "e=5; sameSite=none"; + content.document.cookie = "f=6; sameSite=batmat"; + }); + + // Let's wait for the dom events. + await Promise.all(domPromises); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); +}); diff --git a/netwerk/cookie/test/browser/browser_serviceWorker.js b/netwerk/cookie/test/browser/browser_serviceWorker.js new file mode 100644 index 0000000000..2a5c963535 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_serviceWorker.js @@ -0,0 +1,45 @@ +"use strict"; + +CookiePolicyHelper.runTest("ServiceWorker", { + prefs: [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.ipc.processCount", 1], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + + cookieJarAccessAllowed: async w => { + await w.navigator.serviceWorker + .register("file_empty.js") + .then( + reg => { + ok(true, "ServiceWorker can be used!"); + return reg; + }, + _ => { + ok(false, "ServiceWorker cannot be used! " + _); + } + ) + .then( + reg => reg.unregister(), + _ => { + ok(false, "unregister failed"); + } + ) + .catch(e => ok(false, "Promise rejected: " + e)); + }, + + cookieJarAccessDenied: async w => { + await w.navigator.serviceWorker + .register("file_empty.js") + .then( + _ => { + ok(false, "ServiceWorker cannot be used!"); + }, + _ => { + ok(true, "ServiceWorker cannot be used!"); + } + ) + .catch(e => ok(false, "Promise rejected: " + e)); + }, +}); diff --git a/netwerk/cookie/test/browser/browser_sharedWorker.js b/netwerk/cookie/test/browser/browser_sharedWorker.js new file mode 100644 index 0000000000..88a8b3f0e7 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_sharedWorker.js @@ -0,0 +1,18 @@ +"use strict"; + +CookiePolicyHelper.runTest("SharedWorker", { + cookieJarAccessAllowed: async w => { + new w.SharedWorker("a.js", "foo"); + ok(true, "SharedWorker is allowed"); + }, + + cookieJarAccessDenied: async w => { + try { + new w.SharedWorker("a.js", "foo"); + ok(false, "SharedWorker cannot be used!"); + } catch (e) { + ok(true, "SharedWorker cannot be used!"); + is(e.name, "SecurityError", "We want a security error message."); + } + }, +}); diff --git a/netwerk/cookie/test/browser/browser_storage.js b/netwerk/cookie/test/browser/browser_storage.js new file mode 100644 index 0000000000..1e37b1a367 --- /dev/null +++ b/netwerk/cookie/test/browser/browser_storage.js @@ -0,0 +1,43 @@ +"use strict"; + +CookiePolicyHelper.runTest("SessionStorage", { + cookieJarAccessAllowed: async w => { + try { + w.sessionStorage.foo = 42; + ok(true, "SessionStorage works"); + } catch (e) { + ok(false, "SessionStorage works"); + } + }, + + cookieJarAccessDenied: async w => { + try { + w.sessionStorage.foo = 42; + ok(false, "SessionStorage doesn't work"); + } catch (e) { + ok(true, "SessionStorage doesn't work"); + is(e.name, "SecurityError", "We want a security error message."); + } + }, +}); + +CookiePolicyHelper.runTest("LocalStorage", { + cookieJarAccessAllowed: async w => { + try { + w.localStorage.foo = 42; + ok(true, "LocalStorage works"); + } catch (e) { + ok(false, "LocalStorage works"); + } + }, + + cookieJarAccessDenied: async w => { + try { + w.localStorage.foo = 42; + ok(false, "LocalStorage doesn't work"); + } catch (e) { + ok(true, "LocalStorage doesn't work"); + is(e.name, "SecurityError", "We want a security error message."); + } + }, +}); diff --git a/netwerk/cookie/test/browser/file_empty.html b/netwerk/cookie/test/browser/file_empty.html new file mode 100644 index 0000000000..78b64149c4 --- /dev/null +++ b/netwerk/cookie/test/browser/file_empty.html @@ -0,0 +1,2 @@ +<html><body> +</body></html> diff --git a/netwerk/cookie/test/browser/file_empty.js b/netwerk/cookie/test/browser/file_empty.js new file mode 100644 index 0000000000..3053583c76 --- /dev/null +++ b/netwerk/cookie/test/browser/file_empty.js @@ -0,0 +1 @@ +/* nothing here */ diff --git a/netwerk/cookie/test/browser/head.js b/netwerk/cookie/test/browser/head.js new file mode 100644 index 0000000000..609ad683db --- /dev/null +++ b/netwerk/cookie/test/browser/head.js @@ -0,0 +1,201 @@ +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +const BEHAVIOR_ACCEPT = Ci.nsICookieService.BEHAVIOR_ACCEPT; +const BEHAVIOR_REJECT = Ci.nsICookieService.BEHAVIOR_REJECT; + +const PERM_DEFAULT = Ci.nsICookiePermission.ACCESS_DEFAULT; +const PERM_ALLOW = Ci.nsICookiePermission.ACCESS_ALLOW; +const PERM_DENY = Ci.nsICookiePermission.ACCESS_DENY; + +const TEST_DOMAIN = "https://example.com/"; +const TEST_PATH = "browser/netwerk/cookie/test/browser/"; +const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "file_empty.html"; + +// Helper to eval() provided cookieJarAccessAllowed and cookieJarAccessDenied +// toString()ed optionally async function in freshly created tabs with +// BEHAVIOR_ACCEPT and BEHAVIOR_REJECT configured, respectively, in a number of +// permutations. This includes verifying that changing the permission while the +// page is open still results in the state of the permission when the +// document/global was created still applying. Code will execute in the +// ContentTask.spawn frame-script context, use content to access the underlying +// page. +this.CookiePolicyHelper = { + runTest(testName, config) { + // Testing allowed to blocked by cookie behavior + this._createTest( + testName, + config.cookieJarAccessAllowed, + config.cookieJarAccessDenied, + config.prefs, + { + fromBehavior: BEHAVIOR_ACCEPT, + toBehavior: BEHAVIOR_REJECT, + fromPermission: PERM_DEFAULT, + toPermission: PERM_DEFAULT, + } + ); + + // Testing blocked to allowed by cookie behavior + this._createTest( + testName, + config.cookieJarAccessDenied, + config.cookieJarAccessAllowed, + config.prefs, + { + fromBehavior: BEHAVIOR_REJECT, + toBehavior: BEHAVIOR_ACCEPT, + fromPermission: PERM_DEFAULT, + toPermission: PERM_DEFAULT, + } + ); + + // Testing allowed to blocked by cookie permission + this._createTest( + testName, + config.cookieJarAccessAllowed, + config.cookieJarAccessDenied, + config.prefs, + { + fromBehavior: BEHAVIOR_REJECT, + toBehavior: BEHAVIOR_REJECT, + fromPermission: PERM_ALLOW, + toPermission: PERM_DEFAULT, + } + ); + + // Testing blocked to allowed by cookie permission + this._createTest( + testName, + config.cookieJarAccessDenied, + config.cookieJarAccessAllowed, + config.prefs, + { + fromBehavior: BEHAVIOR_ACCEPT, + toBehavior: BEHAVIOR_ACCEPT, + fromPermission: PERM_DENY, + toPermission: PERM_DEFAULT, + } + ); + }, + + _createTest(testName, goodCb, badCb, prefs, config) { + add_task(async _ => { + info("Starting " + testName + ": " + config.toSource()); + + await SpecialPowers.flushPrefEnv(); + + if (prefs) { + await SpecialPowers.pushPrefEnv({ set: prefs }); + } + + // Let's set the first cookie pref. + PermissionTestUtils.add(TEST_DOMAIN, "cookie", config.fromPermission); + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior", config.fromBehavior]], + }); + + // Let's open a tab and load content. + let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Let's create an iframe. + await SpecialPowers.spawn( + browser, + [{ url: TEST_TOP_PAGE }], + async obj => { + return new content.Promise(resolve => { + let ifr = content.document.createElement("iframe"); + ifr.setAttribute("id", "iframe"); + ifr.src = obj.url; + ifr.onload = () => resolve(); + content.document.body.appendChild(ifr); + }); + } + ); + + // Let's exec the "good" callback. + info( + "Executing the test after setting the cookie behavior to " + + config.fromBehavior + + " and permission to " + + config.fromPermission + ); + await SpecialPowers.spawn( + browser, + [{ callback: goodCb.toString() }], + async obj => { + let runnableStr = `(() => {return (${obj.callback});})();`; + let runnable = eval(runnableStr); // eslint-disable-line no-eval + await runnable(content); + + let ifr = content.document.getElementById("iframe"); + await runnable(ifr.contentWindow); + } + ); + + // Now, let's change the cookie settings + PermissionTestUtils.add(TEST_DOMAIN, "cookie", config.toPermission); + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior", config.toBehavior]], + }); + + // We still want the good callback to succeed. + info( + "Executing the test after setting the cookie behavior to " + + config.toBehavior + + " and permission to " + + config.toPermission + ); + await SpecialPowers.spawn( + browser, + [{ callback: goodCb.toString() }], + async obj => { + let runnableStr = `(() => {return (${obj.callback});})();`; + let runnable = eval(runnableStr); // eslint-disable-line no-eval + await runnable(content); + + let ifr = content.document.getElementById("iframe"); + await runnable(ifr.contentWindow); + } + ); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); + + // Let's open a new tab and load content again. + tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tab; + + browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Let's exec the "bad" callback. + info("Executing the test in a new tab"); + await SpecialPowers.spawn( + browser, + [{ callback: badCb.toString() }], + async obj => { + let runnableStr = `(() => {return (${obj.callback});})();`; + let runnable = eval(runnableStr); // eslint-disable-line no-eval + await runnable(content); + } + ); + + // Let's close the tab. + BrowserTestUtils.removeTab(tab); + + // Cleanup. + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_ALL, + resolve + ); + }); + }); + }, +}; diff --git a/netwerk/cookie/test/browser/oversize.sjs b/netwerk/cookie/test/browser/oversize.sjs new file mode 100644 index 0000000000..dfe2f31645 --- /dev/null +++ b/netwerk/cookie/test/browser/oversize.sjs @@ -0,0 +1,17 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + + const maxBytesPerCookie = 4096; + const maxBytesPerCookiePath = 1024; + + aResponse.setHeader( + "Set-Cookie", + "a=" + Array(maxBytesPerCookie + 1).join("x"), + true + ); + aResponse.setHeader( + "Set-Cookie", + "b=c; path=/" + Array(maxBytesPerCookiePath + 1).join("x"), + true + ); +} diff --git a/netwerk/cookie/test/browser/partitioned.sjs b/netwerk/cookie/test/browser/partitioned.sjs new file mode 100644 index 0000000000..5649b88f2f --- /dev/null +++ b/netwerk/cookie/test/browser/partitioned.sjs @@ -0,0 +1,32 @@ +function handleRequest(aRequest, aResponse) { + if (aRequest.hasHeader("Origin")) { + let origin = aRequest.getHeader("Origin"); + aResponse.setHeader("Access-Control-Allow-Origin", origin); + aResponse.setHeader("Access-Control-Allow-Credentials", "true"); + } + + var params = new URLSearchParams(aRequest.queryString); + if (params.has("redirect")) { + aResponse.setHeader("Location", params.get("redirect")); + aResponse.setStatusLine(aRequest.httpVersion, 302); + } else { + aResponse.setStatusLine(aRequest.httpVersion, 200); + } + + if (params.has("nocookie")) { + return; + } + + if (params.has("nosecure")) { + aResponse.setHeader("Set-Cookie", "c=3; Partitioned;", true); + + return; + } + + aResponse.setHeader("Set-Cookie", "a=1; SameSite=None; Secure", true); + aResponse.setHeader( + "Set-Cookie", + "b=2; Partitioned; SameSite=None; Secure", + true + ); +} diff --git a/netwerk/cookie/test/browser/sameSite.sjs b/netwerk/cookie/test/browser/sameSite.sjs new file mode 100644 index 0000000000..a19624d2cb --- /dev/null +++ b/netwerk/cookie/test/browser/sameSite.sjs @@ -0,0 +1,7 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + + aResponse.setHeader("Set-Cookie", "a=1", true); + aResponse.setHeader("Set-Cookie", "b=2; sameSite=none", true); + aResponse.setHeader("Set-Cookie", "c=3; sameSite=batman", true); +} diff --git a/netwerk/cookie/test/browser/server.sjs b/netwerk/cookie/test/browser/server.sjs new file mode 100644 index 0000000000..86835914bb --- /dev/null +++ b/netwerk/cookie/test/browser/server.sjs @@ -0,0 +1,9 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + if (aRequest.hasHeader("Cookie")) { + aResponse.write("cookie-present"); + } else { + aResponse.setHeader("Set-Cookie", "foopy=1"); + aResponse.write("cookie-not-present"); + } +} diff --git a/netwerk/cookie/test/mochitest/cookie.sjs b/netwerk/cookie/test/mochitest/cookie.sjs new file mode 100644 index 0000000000..75d4e638b4 --- /dev/null +++ b/netwerk/cookie/test/mochitest/cookie.sjs @@ -0,0 +1,166 @@ +function handleRequest(aRequest, aResponse) { + let parts = aRequest.queryString.split("&"); + if (parts.includes("window")) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + aResponse.setHeader("Content-Type", "text/html"); + aResponse.setHeader("Clear-Site-Data", '"cache", "cookies", "storage"'); + aResponse.write("<body><h1>Welcome</h1></body>"); + return; + } + + if (parts.includes("fetch")) { + setState( + "data", + JSON.stringify({ type: "fetch", hasCookie: aRequest.hasHeader("Cookie") }) + ); + aResponse.write("Hello world!"); + return; + } + + if (parts.includes("xhr")) { + setState( + "data", + JSON.stringify({ type: "xhr", hasCookie: aRequest.hasHeader("Cookie") }) + ); + aResponse.write("Hello world!"); + return; + } + + if (parts.includes("image")) { + setState( + "data", + JSON.stringify({ type: "image", hasCookie: aRequest.hasHeader("Cookie") }) + ); + + // A 1x1 PNG image. + // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain) + const IMAGE = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" + + "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=" + ); + + aResponse.setHeader("Content-Type", "image/png", false); + aResponse.write(IMAGE); + return; + } + + if (parts.includes("script")) { + setState( + "data", + JSON.stringify({ + type: "script", + hasCookie: aRequest.hasHeader("Cookie"), + }) + ); + + aResponse.setHeader("Content-Type", "text/javascript", false); + aResponse.write("window.scriptLoaded();"); + return; + } + + if (parts.includes("worker")) { + setState( + "data", + JSON.stringify({ + type: "worker", + hasCookie: aRequest.hasHeader("Cookie"), + }) + ); + + function w() { + onmessage = e => { + if (e.data == "subworker") { + importScripts("cookie.sjs?subworker&" + Math.random()); + postMessage(42); + return; + } + + if (e.data == "fetch") { + fetch("cookie.sjs?fetch&" + Math.random()) + .then(r => r.text()) + .then(_ => postMessage(42)); + return; + } + + if (e.data == "xhr") { + let xhr = new XMLHttpRequest(); + xhr.open("GET", "cookie.sjs?xhr&" + Math.random()); + xhr.send(); + xhr.onload = _ => postMessage(42); + } + }; + postMessage(42); + } + + aResponse.setHeader("Content-Type", "text/javascript", false); + aResponse.write(w.toString() + "; w();"); + return; + } + + if (parts.includes("subworker")) { + setState( + "data", + JSON.stringify({ + type: "subworker", + hasCookie: aRequest.hasHeader("Cookie"), + }) + ); + aResponse.setHeader("Content-Type", "text/javascript", false); + aResponse.write("42"); + return; + } + + if (parts.includes("sharedworker")) { + setState( + "data", + JSON.stringify({ + type: "sharedworker", + hasCookie: aRequest.hasHeader("Cookie"), + }) + ); + + // This function is exported as a string. + /* eslint-disable no-undef */ + function w() { + onconnect = e => { + e.ports[0].onmessage = evt => { + if (evt.data == "subworker") { + importScripts("cookie.sjs?subworker&" + Math.random()); + e.ports[0].postMessage(42); + return; + } + + if (evt.data == "fetch") { + fetch("cookie.sjs?fetch&" + Math.random()) + .then(r => r.text()) + .then(_ => e.ports[0].postMessage(42)); + return; + } + + if (evt.data == "xhr") { + let xhr = new XMLHttpRequest(); + xhr.open("GET", "cookie.sjs?xhr&" + Math.random()); + xhr.send(); + xhr.onload = _ => e.ports[0].postMessage(42); + } + }; + e.ports[0].postMessage(42); + }; + } + /* eslint-enable no-undef */ + + aResponse.setHeader("Content-Type", "text/javascript", false); + aResponse.write(w.toString() + "; w();"); + return; + } + + if (parts.includes("last")) { + let data = getState("data"); + setState("data", ""); + aResponse.write(data); + return; + } + + aResponse.setStatusLine(aRequest.httpVersion, 400); + aResponse.write("Invalid request"); +} diff --git a/netwerk/cookie/test/mochitest/cookiesHelper.js b/netwerk/cookie/test/mochitest/cookiesHelper.js new file mode 100644 index 0000000000..cbff91f2f2 --- /dev/null +++ b/netwerk/cookie/test/mochitest/cookiesHelper.js @@ -0,0 +1,63 @@ +const ALLOWED = 0; +const BLOCKED = 1; + +async function cleanupData() { + await new Promise(resolve => { + const chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + addMessageListener("go", __ => { + Services.clearData.deleteData( + Services.clearData.CLEAR_COOKIES | + Services.clearData.CLEAR_ALL_CACHES | + Services.clearData.CLEAR_DOM_STORAGES, + ___ => { + sendAsyncMessage("done"); + } + ); + }); + }); + + chromeScript.addMessageListener("done", _ => { + chromeScript.destroy(); + resolve(); + }); + + chromeScript.sendAsyncMessage("go"); + }); +} + +async function checkLastRequest(type, state) { + let json = await fetch("cookie.sjs?last&" + Math.random()).then(r => + r.json() + ); + is(json.type, type, "Type: " + type); + is(json.hasCookie, state == ALLOWED, "Fetch has cookies"); +} + +async function runTests(currentTest) { + await cleanupData(); + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior", 2]], + }); + let windowBlocked = window.open("cookie.sjs?window&" + Math.random()); + await new Promise(resolve => { + windowBlocked.onload = resolve; + }); + await currentTest(windowBlocked, BLOCKED); + windowBlocked.close(); + + await cleanupData(); + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior", 1]], + }); + let windowAllowed = window.open("cookie.sjs?window&" + Math.random()); + await new Promise(resolve => { + windowAllowed.onload = resolve; + }); + await currentTest(windowAllowed, ALLOWED); + windowAllowed.close(); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); diff --git a/netwerk/cookie/test/mochitest/empty.html b/netwerk/cookie/test/mochitest/empty.html new file mode 100644 index 0000000000..cd161cc52d --- /dev/null +++ b/netwerk/cookie/test/mochitest/empty.html @@ -0,0 +1 @@ +<h1>Nothing here</h1> diff --git a/netwerk/cookie/test/mochitest/mochitest.toml b/netwerk/cookie/test/mochitest/mochitest.toml new file mode 100644 index 0000000000..fdf26a7e94 --- /dev/null +++ b/netwerk/cookie/test/mochitest/mochitest.toml @@ -0,0 +1,27 @@ +[DEFAULT] +scheme = "https" +support-files = [ + "cookie.sjs", + "cookiesHelper.js", +] + +["test_document_cookie.html"] + +["test_document_cookie_notification.html"] + +["test_fetch.html"] + +["test_image.html"] + +["test_metaTag.html"] + +["test_script.html"] + +["test_sharedWorker.html"] + +["test_worker.html"] + +["test_xhr.html"] + +["test_xmlDocument.html"] +support-files = ["empty.html"] diff --git a/netwerk/cookie/test/mochitest/test_document_cookie.html b/netwerk/cookie/test/mochitest/test_document_cookie.html new file mode 100644 index 0000000000..86e7c7f661 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_document_cookie.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for document.cookie when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + is(w.document.cookie.length, 0, "No cookie to start!"); + w.document.cookie = "name=value"; + is(w.document.cookie.includes("name=value"), state == ALLOWED, "Some cookies for me"); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_document_cookie_notification.html b/netwerk/cookie/test/mochitest/test_document_cookie_notification.html new file mode 100644 index 0000000000..b84b6ed045 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_document_cookie_notification.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for document.cookie setter + notification</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript"> + +function Listener() { + SpecialPowers.addObserver(this, "document-set-cookie"); +} + +Listener.prototype = { + observe(aSubject, aTopic, aData) { + is(aTopic, "document-set-cookie", "Notification received"); + ok(aData.startsWith("a="), "Right cookie received"); + + SpecialPowers.removeObserver(this, "document-set-cookie"); + SimpleTest.finish(); + } +} + +const cl = new Listener(); +document.cookie = "a=" + Math.random(); +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_fetch.html b/netwerk/cookie/test/mochitest/test_fetch.html new file mode 100644 index 0000000000..315d0d7624 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_fetch.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for cookies + fetch when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + w.document.cookie = "name=value"; + await w.fetch("cookie.sjs?fetch&" + Math.random()).then(r => r.text()); + await checkLastRequest("fetch", state); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_image.html b/netwerk/cookie/test/mochitest/test_image.html new file mode 100644 index 0000000000..4a49d64169 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_image.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for cookies and image loading when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + w.document.cookie = "name=value"; + + let image = new w.Image(); + image.src = "cookie.sjs?image&" + Math.random(); + w.document.body.appendChild(image); + await new w.Promise(resolve => { image.onload = resolve; }); + await checkLastRequest("image", state); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_metaTag.html b/netwerk/cookie/test/mochitest/test_metaTag.html new file mode 100644 index 0000000000..48d360d5b2 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_metaTag.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for meta tag</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script type="application/javascript"> + +document.addEventListener("DOMContentLoaded", _ => { + try { + document.write('<meta content=a http-equiv="set-cookie">'); + } catch (e) {} + + ok(true, "No crash!"); + SimpleTest.finish(); +}); + +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_script.html b/netwerk/cookie/test/mochitest/test_script.html new file mode 100644 index 0000000000..9f4b9f846d --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_script.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for cookies + script loading when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + w.document.cookie = "name=value"; + + let p = new w.Promise(resolve => { w.scriptLoaded = resolve; }); + let script = document.createElement("script"); + script.src = "cookie.sjs?script&" + Math.random(); + w.document.body.appendChild(script); + await p; + await checkLastRequest("script", state); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_sharedWorker.html b/netwerk/cookie/test/mochitest/test_sharedWorker.html new file mode 100644 index 0000000000..c29bf86a88 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_sharedWorker.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for cookies + SharedWorker loading when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + w.document.cookie = "name=value"; + + if (state == BLOCKED) { + try { + new w.SharedWorker("cookie.sjs?sharedworker&" + Math.random()); + ok(false, "SharedWorker should not be allowed!"); + } catch (ex) { + ok(true, "SharedWorker should not be allowed!"); + } + return; + } + + let p = new w.SharedWorker("cookie.sjs?sharedworker&" + Math.random()); + await new w.Promise(resolve => { p.port.onmessage = resolve; }); + await checkLastRequest("sharedworker", state); + + await new w.Promise(resolve => { + p.port.postMessage("subworker"); + p.port.onmessage = resolve; + }); + await checkLastRequest("subworker", state); + + await new w.Promise(resolve => { + p.port.postMessage("fetch"); + p.port.onmessage = resolve; + }); + await checkLastRequest("fetch", state); + + await new w.Promise(resolve => { + p.port.postMessage("xhr"); + p.port.onmessage = resolve; + }); + await checkLastRequest("xhr", state); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_worker.html b/netwerk/cookie/test/mochitest/test_worker.html new file mode 100644 index 0000000000..37ab222bce --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_worker.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for cookies + worker loading when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + w.document.cookie = "name=value"; + + let p = new w.Worker("cookie.sjs?worker&" + Math.random()); + await new w.Promise(resolve => { p.onmessage = resolve; }); + await checkLastRequest("worker", state); + + await new w.Promise(resolve => { p.postMessage("subworker"); p.onmessage = resolve; }); + await checkLastRequest("subworker", state); + + await new w.Promise(resolve => { p.postMessage("fetch"); p.onmessage = resolve; }); + await checkLastRequest("fetch", state); + + await new w.Promise(resolve => { p.postMessage("xhr"); p.onmessage = resolve; }); + await checkLastRequest("xhr", state); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_xhr.html b/netwerk/cookie/test/mochitest/test_xhr.html new file mode 100644 index 0000000000..d00b5690f8 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_xhr.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for cookies + XHR when the policy changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="cookiesHelper.js"></script> +</head> +<body> +<script type="application/javascript"> + +runTests(async (w, state) => { + w.document.cookie = "name=value"; + await new w.Promise(resolve => { + let xhr = new w.XMLHttpRequest(); + xhr.open("GET", "cookie.sjs?xhr&" + Math.random()); + xhr.send(); + xhr.onload = resolve; + }); + await checkLastRequest("xhr", state); +}); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/mochitest/test_xmlDocument.html b/netwerk/cookie/test/mochitest/test_xmlDocument.html new file mode 100644 index 0000000000..91417c98c4 --- /dev/null +++ b/netwerk/cookie/test/mochitest/test_xmlDocument.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Document constructor</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script type="application/javascript"> + +let w; + +SpecialPowers.pushPrefEnv({set: [ + ["dom.storage_access.enabled", true], + ["dom.storage_access.prompt.testing", true], + ["dom.storage_access.prompt.testing.allow", true], + ["dom.testing.sync-content-blocking-notifications", true], + ["network.cookie.cookieBehavior", 0], +]}).then(_ => { + return new Promise(resolve => { + w = window.open("empty.html"); + w.onload = resolve; + }); +}).then(_ => { + const doc = new w.Document(); + return doc.requestStorageAccess().catch(__ => {}); +}).then(___ => { + w.close(); + ok(true, "No crash!"); + SimpleTest.finish(); +}); + +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js b/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js new file mode 100644 index 0000000000..94f01b778e --- /dev/null +++ b/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js @@ -0,0 +1,105 @@ +"use strict"; + +add_task(async () => { + const HOST = "www.bbc.co.uk"; + Assert.equal( + Services.eTLD.getBaseDomainFromHost(HOST), + "bbc.co.uk", + "Sanity check: HOST is an eTLD + 1 with subdomain" + ); + + const tests = [ + { + // Correct baseDomain: eTLD + 1. + baseDomain: "bbc.co.uk", + name: "originally_bbc_co_uk", + }, + { + // Incorrect baseDomain: Part of public suffix list. + baseDomain: "uk", + name: "originally_uk", + }, + { + // Incorrect baseDomain: Part of public suffix list. + baseDomain: "co.uk", + name: "originally_co_uk", + }, + { + // Incorrect baseDomain: eTLD + 2. + baseDomain: "www.bbc.co.uk", + name: "originally_www_bbc_co_uk", + }, + ]; + + do_get_profile(); + + let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + dbFile.append("cookies.sqlite"); + let conn = Services.storage.openDatabase(dbFile); + + conn.schemaVersion = 10; + conn.executeSimpleSQL("DROP TABLE IF EXISTS moz_cookies"); + conn.executeSimpleSQL( + "CREATE TABLE moz_cookies (" + + "id INTEGER PRIMARY KEY, " + + "baseDomain TEXT, " + + "originAttributes TEXT NOT NULL DEFAULT '', " + + "name TEXT, " + + "value TEXT, " + + "host TEXT, " + + "path TEXT, " + + "expiry INTEGER, " + + "lastAccessed INTEGER, " + + "creationTime INTEGER, " + + "isSecure INTEGER, " + + "isHttpOnly INTEGER, " + + "inBrowserElement INTEGER DEFAULT 0, " + + "sameSite INTEGER DEFAULT 0, " + + "rawSameSite INTEGER DEFAULT 0, " + + "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" + + ")" + ); + + function addCookie(baseDomain, host, name) { + conn.executeSimpleSQL( + "INSERT INTO moz_cookies(" + + "baseDomain, host, name, value, path, expiry, " + + "lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (" + + `'${baseDomain}', '${host}', '${name}', 'thevalue', '/', ` + + (Date.now() + 3600000) + + "," + + Date.now() + + "," + + Date.now() + + ", 1, 1)" + ); + } + + // Prepare the database. + for (let { baseDomain, name } of tests) { + addCookie(baseDomain, HOST, name); + } + // Domain cookies are not supported for IP addresses. + addCookie("127.0.0.1", ".127.0.0.1", "invalid_host"); + conn.close(); + + let cs = Services.cookies; + + // Count excludes the invalid_host cookie. + Assert.equal(cs.cookies.length, tests.length, "Expected number of cookies"); + + // Check whether the database has the expected value, + // despite the incorrect baseDomain. + for (let { name } of tests) { + Assert.ok( + cs.cookieExists(HOST, "/", name, {}), + "Should find cookie with name: " + name + ); + } + + Assert.equal( + cs.cookieExists("127.0.0.1", "/", "invalid_host", {}), + false, + "Should ignore database row with invalid host name" + ); +}); diff --git a/netwerk/cookie/test/unit/test_bug1155169.js b/netwerk/cookie/test/unit/test_bug1155169.js new file mode 100644 index 0000000000..2bf8bd768d --- /dev/null +++ b/netwerk/cookie/test/unit/test_bug1155169.js @@ -0,0 +1,96 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +const URI = Services.io.newURI("http://example.org/"); + +const { COOKIE_CHANGED, COOKIE_ADDED } = Ci.nsICookieNotification; + +function run_test() { + // Allow all cookies. + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + + // Clear cookies. + Services.cookies.removeAll(); + + // Add a new cookie. + setCookie("foo=bar", { + type: COOKIE_ADDED, + isSession: true, + isSecure: false, + isHttpOnly: false, + }); + + // Update cookie with isHttpOnly=true. + setCookie("foo=bar; HttpOnly", { + type: COOKIE_CHANGED, + isSession: true, + isSecure: false, + isHttpOnly: true, + }); + + // Update cookie with isSecure=true. + setCookie("foo=bar; Secure", { + type: COOKIE_CHANGED, + isSession: true, + isSecure: true, + isHttpOnly: false, + }); + + // Update cookie with isSession=false. + let expiry = new Date(); + expiry.setUTCFullYear(expiry.getUTCFullYear() + 2); + setCookie(`foo=bar; Expires=${expiry.toGMTString()}`, { + type: COOKIE_CHANGED, + isSession: false, + isSecure: false, + isHttpOnly: false, + }); + + // Reset cookie. + setCookie("foo=bar", { + type: COOKIE_CHANGED, + isSession: true, + isSecure: false, + isHttpOnly: false, + }); +} + +function setCookie(value, expected) { + function setCookieInternal(valueInternal, expectedInternal = null) { + function observer(subject) { + if (!expectedInternal) { + do_throw("no notification expected"); + return; + } + + let notification = subject.QueryInterface(Ci.nsICookieNotification); + + // Check we saw the right notification. + Assert.equal(notification.action, expectedInternal.type); + + // Check cookie details. + let cookie = notification.cookie.QueryInterface(Ci.nsICookie); + Assert.equal(cookie.isSession, expectedInternal.isSession); + Assert.equal(cookie.isSecure, expectedInternal.isSecure); + Assert.equal(cookie.isHttpOnly, expectedInternal.isHttpOnly); + } + + Services.obs.addObserver(observer, "cookie-changed"); + + let channel = NetUtil.newChannel({ + uri: URI, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + + Services.cookies.setCookieStringFromHttp(URI, valueInternal, channel); + Services.obs.removeObserver(observer, "cookie-changed"); + } + + // Check that updating/inserting the cookie works. + setCookieInternal(value, expected); + + // Check that we ignore identical cookies. + setCookieInternal(value); +} diff --git a/netwerk/cookie/test/unit/test_bug1321912.js b/netwerk/cookie/test/unit/test_bug1321912.js new file mode 100644 index 0000000000..fd24f15bbf --- /dev/null +++ b/netwerk/cookie/test/unit/test_bug1321912.js @@ -0,0 +1,99 @@ +do_get_profile(); +const dirSvc = Services.dirsvc; + +let dbFile = dirSvc.get("ProfD", Ci.nsIFile); +dbFile.append("cookies.sqlite"); + +let storage = Services.storage; +let properties = Cc["@mozilla.org/hash-property-bag;1"].createInstance( + Ci.nsIWritablePropertyBag +); +properties.setProperty("shared", true); +let conn = storage.openDatabase(dbFile); + +// Write the schema v7 to the database. +conn.schemaVersion = 7; +conn.executeSimpleSQL( + "CREATE TABLE moz_cookies (" + + "id INTEGER PRIMARY KEY, " + + "baseDomain TEXT, " + + "originAttributes TEXT NOT NULL DEFAULT '', " + + "name TEXT, " + + "value TEXT, " + + "host TEXT, " + + "path TEXT, " + + "expiry INTEGER, " + + "lastAccessed INTEGER, " + + "creationTime INTEGER, " + + "isSecure INTEGER, " + + "isHttpOnly INTEGER, " + + "appId INTEGER DEFAULT 0, " + + "inBrowserElement INTEGER DEFAULT 0, " + + "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" + + ")" +); +conn.executeSimpleSQL( + "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " + + "originAttributes)" +); + +conn.executeSimpleSQL("PRAGMA synchronous = OFF"); +conn.executeSimpleSQL("PRAGMA journal_mode = WAL"); +conn.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16"); + +let now = Date.now(); +conn.executeSimpleSQL( + "INSERT INTO moz_cookies(" + + "baseDomain, host, name, value, path, expiry, " + + "lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (" + + "'foo.com', '.foo.com', 'foo', 'bar=baz', '/', " + + now + + ", " + + now + + ", " + + now + + ", 1, 1)" +); + +// Now start the cookie service, and then check the fields in the table. +// Get sessionCookies to wait for the initialization in cookie thread +Services.cookies.sessionCookies; + +Assert.equal(conn.schemaVersion, 13); +let stmt = conn.createStatement( + "SELECT sql FROM sqlite_master " + + "WHERE type = 'table' AND " + + " name = 'moz_cookies'" +); +try { + Assert.ok(stmt.executeStep()); + let sql = stmt.getString(0); + Assert.equal(sql.indexOf("appId"), -1); +} finally { + stmt.finalize(); +} + +stmt = conn.createStatement( + "SELECT * FROM moz_cookies " + + "WHERE host = '.foo.com' AND " + + " name = 'foo' AND " + + " value = 'bar=baz' AND " + + " path = '/' AND " + + " expiry = " + + now + + " AND " + + " lastAccessed = " + + now + + " AND " + + " creationTime = " + + now + + " AND " + + " isSecure = 1 AND " + + " isHttpOnly = 1" +); +try { + Assert.ok(stmt.executeStep()); +} finally { + stmt.finalize(); +} +conn.close(); diff --git a/netwerk/cookie/test/unit/test_bug643051.js b/netwerk/cookie/test/unit/test_bug643051.js new file mode 100644 index 0000000000..35b37a5889 --- /dev/null +++ b/netwerk/cookie/test/unit/test_bug643051.js @@ -0,0 +1,43 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); +const { CookieXPCShellUtils } = ChromeUtils.importESModule( + "resource://testing-common/CookieXPCShellUtils.sys.mjs" +); + +CookieXPCShellUtils.init(this); +CookieXPCShellUtils.createServer({ hosts: ["example.net"] }); + +add_task(async () => { + Services.prefs.setBoolPref("dom.security.https_first", false); + + // Allow all cookies. + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + + let uri = NetUtil.newURI("http://example.org/"); + let channel = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + + let set = "foo=bar\nbaz=foo"; + let expected = "foo=bar; baz=foo"; + Services.cookies.setCookieStringFromHttp(uri, set, channel); + + let actual = Services.cookies.getCookieStringFromHttp(uri, channel); + Assert.equal(actual, expected); + + await CookieXPCShellUtils.setCookieToDocument("http://example.net/", set); + actual = await CookieXPCShellUtils.getCookieStringFromDocument( + "http://example.net/" + ); + + expected = "foo=bar"; + Assert.equal(actual, expected); + Services.prefs.clearUserPref("dom.security.https_first"); +}); diff --git a/netwerk/cookie/test/unit/test_eviction.js b/netwerk/cookie/test/unit/test_eviction.js new file mode 100644 index 0000000000..8c0073a107 --- /dev/null +++ b/netwerk/cookie/test/unit/test_eviction.js @@ -0,0 +1,199 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +const BASE_HOST = "example.org"; + +const { CookieXPCShellUtils } = ChromeUtils.importESModule( + "resource://testing-common/CookieXPCShellUtils.sys.mjs" +); + +CookieXPCShellUtils.init(this); +CookieXPCShellUtils.createServer({ hosts: ["example.org"] }); + +add_task(async function test_basic_eviction() { + do_get_profile(); + + Services.prefs.setIntPref("network.cookie.staleThreshold", 0); + Services.prefs.setIntPref("network.cookie.quotaPerHost", 2); + Services.prefs.setIntPref("network.cookie.maxPerHost", 5); + + // We don't want to have CookieJarSettings blocking this test. + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + + const BASE_URI = Services.io.newURI("http://" + BASE_HOST); + const FOO_PATH = Services.io.newURI("http://" + BASE_HOST + "/foo/"); + const BAR_PATH = Services.io.newURI("http://" + BASE_HOST + "/bar/"); + + await setCookie("session_foo_path_1", null, "/foo", null, FOO_PATH); + await setCookie("session_foo_path_2", null, "/foo", null, FOO_PATH); + await setCookie("session_foo_path_3", null, "/foo", null, FOO_PATH); + await setCookie("session_foo_path_4", null, "/foo", null, FOO_PATH); + await setCookie("session_foo_path_5", null, "/foo", null, FOO_PATH); + verifyCookies( + [ + "session_foo_path_1", + "session_foo_path_2", + "session_foo_path_3", + "session_foo_path_4", + "session_foo_path_5", + ], + BASE_URI + ); + + // Check if cookies are evicted by creation time. + await setCookie("session_foo_path_6", null, "/foo", null, FOO_PATH); + verifyCookies( + ["session_foo_path_4", "session_foo_path_5", "session_foo_path_6"], + BASE_URI + ); + + await setCookie("session_bar_path_1", null, "/bar", null, BAR_PATH); + await setCookie("session_bar_path_2", null, "/bar", null, BAR_PATH); + + verifyCookies( + [ + "session_foo_path_4", + "session_foo_path_5", + "session_foo_path_6", + "session_bar_path_1", + "session_bar_path_2", + ], + BASE_URI + ); + + // Check if cookies are evicted by last accessed time. + await CookieXPCShellUtils.getCookieStringFromDocument(FOO_PATH.spec); + + await setCookie("session_foo_path_7", null, "/foo", null, FOO_PATH); + verifyCookies( + ["session_foo_path_5", "session_foo_path_6", "session_foo_path_7"], + BASE_URI + ); + + const EXPIRED_TIME = 3; + + await setCookie( + "non_session_expired_foo_path_1", + null, + "/foo", + EXPIRED_TIME, + FOO_PATH + ); + await setCookie( + "non_session_expired_foo_path_2", + null, + "/foo", + EXPIRED_TIME, + FOO_PATH + ); + verifyCookies( + [ + "session_foo_path_5", + "session_foo_path_6", + "session_foo_path_7", + "non_session_expired_foo_path_1", + "non_session_expired_foo_path_2", + ], + BASE_URI + ); + + // Check if expired cookies are evicted first. + await new Promise(resolve => do_timeout(EXPIRED_TIME * 1000, resolve)); + await setCookie("session_foo_path_8", null, "/foo", null, FOO_PATH); + verifyCookies( + ["session_foo_path_6", "session_foo_path_7", "session_foo_path_8"], + BASE_URI + ); + + Services.cookies.removeAll(); +}); + +// Verify that the given cookie names exist, and are ordered from least to most recently accessed +function verifyCookies(names, uri) { + Assert.equal(Services.cookies.countCookiesFromHost(uri.host), names.length); + let actual_cookies = []; + for (let cookie of Services.cookies.getCookiesFromHost(uri.host, {})) { + actual_cookies.push(cookie); + } + if (names.length != actual_cookies.length) { + let left = names.filter(function (n) { + return ( + actual_cookies.findIndex(function (c) { + return c.name == n; + }) == -1 + ); + }); + let right = actual_cookies + .filter(function (c) { + return ( + names.findIndex(function (n) { + return c.name == n; + }) == -1 + ); + }) + .map(function (c) { + return c.name; + }); + if (left.length) { + info("unexpected cookies: " + left); + } + if (right.length) { + info("expected cookies: " + right); + } + } + Assert.equal(names.length, actual_cookies.length); + actual_cookies.sort(function (a, b) { + if (a.lastAccessed < b.lastAccessed) { + return -1; + } + if (a.lastAccessed > b.lastAccessed) { + return 1; + } + return 0; + }); + for (var i = 0; i < names.length; i++) { + Assert.equal(names[i], actual_cookies[i].name); + Assert.equal(names[i].startsWith("session"), actual_cookies[i].isSession); + } +} + +var lastValue = 0; +function setCookie(name, domain, path, maxAge, url) { + let value = name + "=" + ++lastValue; + var s = "setting cookie " + value; + if (domain) { + value += "; Domain=" + domain; + s += " (d=" + domain + ")"; + } + if (path) { + value += "; Path=" + path; + s += " (p=" + path + ")"; + } + if (maxAge) { + value += "; Max-Age=" + maxAge; + s += " (non-session)"; + } else { + s += " (session)"; + } + s += " for " + url.spec; + info(s); + + let channel = NetUtil.newChannel({ + uri: url, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + + Services.cookies.setCookieStringFromHttp(url, value, channel); + + return new Promise(function (resolve) { + // Windows XP has low precision timestamps that cause our cookie eviction + // algorithm to produce different results from other platforms. We work around + // this by ensuring that there's a clear gap between each cookie update. + do_timeout(10, resolve); + }); +} diff --git a/netwerk/cookie/test/unit/test_getCookieSince.js b/netwerk/cookie/test/unit/test_getCookieSince.js new file mode 100644 index 0000000000..e58624b6a1 --- /dev/null +++ b/netwerk/cookie/test/unit/test_getCookieSince.js @@ -0,0 +1,72 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function setCookie(name, url) { + let value = `${name}=${Math.random()}; Path=/; Max-Age=1000; sameSite=none; Secure`; + info(`Setting cookie ${value} for ${url.spec}`); + + let channel = NetUtil.newChannel({ + uri: url, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + + Services.cookies.setCookieStringFromHttp(url, value, channel); +} + +async function sleep() { + await new Promise(resolve => do_timeout(1000, resolve)); +} + +function checkSorting(cookies) { + for (let i = 1; i < cookies.length; ++i) { + Assert.greater( + cookies[i].creationTime, + cookies[i - 1].creationTime, + "Cookie " + cookies[i].name + ); + } +} + +add_task(async function () { + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + + await setCookie("A", Services.io.newURI("https://example.com/A/")); + await sleep(); + + await setCookie("B", Services.io.newURI("https://foo.bar/B/")); + await sleep(); + + await setCookie("C", Services.io.newURI("https://example.org/C/")); + await sleep(); + + await setCookie("D", Services.io.newURI("https://example.com/D/")); + await sleep(); + + Assert.equal(Services.cookies.cookies.length, 4, "Cookie check"); + + const cookies = Services.cookies.getCookiesSince(0); + Assert.equal(cookies.length, 4, "We retrieve all the 4 cookies"); + checkSorting(cookies); + + let someCookies = Services.cookies.getCookiesSince( + cookies[0].creationTime + 1 + ); + Assert.equal(someCookies.length, 3, "We retrieve some cookies"); + checkSorting(someCookies); + + someCookies = Services.cookies.getCookiesSince(cookies[1].creationTime + 1); + Assert.equal(someCookies.length, 2, "We retrieve some cookies"); + checkSorting(someCookies); + + someCookies = Services.cookies.getCookiesSince(cookies[2].creationTime + 1); + Assert.equal(someCookies.length, 1, "We retrieve some cookies"); + checkSorting(someCookies); + + someCookies = Services.cookies.getCookiesSince(cookies[3].creationTime + 1); + Assert.equal(someCookies.length, 0, "We retrieve some cookies"); +}); diff --git a/netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js b/netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js new file mode 100644 index 0000000000..088a909709 --- /dev/null +++ b/netwerk/cookie/test/unit/test_migrateCookieLifetimePref.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + Tests that + - the migration code runs, + - the sanitize on shutdown prefs for profiles with the network.cookie.lifetimePolicy enabled are set to true, + - the previous settings for clearOnShutdown prefs will not be applied due to sanitizeOnShutdown being disabled + - the network.cookie.lifetimePolicy is disabled afterwards. +*/ +add_task(async function migrateSanitizationPrefsClearCleaningPrefs() { + // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY are not available anymore + // 2 = ACCEPT_SESSION + Services.prefs.setIntPref("network.cookie.lifetimePolicy", 2); + Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", false); + Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", false); + Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", false); + Services.prefs.setBoolPref("privacy.clearOnShutdown.offlineApps", false); + Services.prefs.setBoolPref("privacy.clearOnShutdown.downloads", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true); + + // The migration code is called in cookieService::Init + Services.cookies; + + // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY are not available anymore + // 0 = ACCEPT_NORMALLY + Assert.equal( + Services.prefs.getIntPref("network.cookie.lifetimePolicy", 0), + 0, + "Cookie lifetime policy is off" + ); + + Assert.ok( + Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown"), + "Sanitize on shutdown is set" + ); + + Assert.ok( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"), + "Clearing cookies on shutdown is selected" + ); + + Assert.ok( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"), + "Clearing cache on shutdown is still selected" + ); + + Assert.ok( + Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"), + "Clearing offline apps on shutdown is selected" + ); + + Assert.ok( + !Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads"), + "Clearing downloads on shutdown is not set anymore" + ); + Assert.ok( + !Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions"), + "Clearing active logins on shutdown is not set anymore" + ); + + Services.prefs.resetPrefs(); + + delete Services.cookies; +}); diff --git a/netwerk/cookie/test/unit/test_parser_0001.js b/netwerk/cookie/test/unit/test_parser_0001.js new file mode 100644 index 0000000000..acc2e919ef --- /dev/null +++ b/netwerk/cookie/test/unit/test_parser_0001.js @@ -0,0 +1,32 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function inChildProcess() { + return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function run_test() { + // Allow all cookies if the pref service is available in this process. + if (!inChildProcess()) { + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + } + + let uri = NetUtil.newURI("http://example.org/"); + let channel = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + + let set = "foo=bar"; + Services.cookies.setCookieStringFromHttp(uri, set, channel); + + let expected = "foo=bar"; + let actual = Services.cookies.getCookieStringFromHttp(uri, channel); + Assert.equal(actual, expected); +} diff --git a/netwerk/cookie/test/unit/test_parser_0019.js b/netwerk/cookie/test/unit/test_parser_0019.js new file mode 100644 index 0000000000..7ba0d4ef79 --- /dev/null +++ b/netwerk/cookie/test/unit/test_parser_0019.js @@ -0,0 +1,32 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function inChildProcess() { + return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function run_test() { + // Allow all cookies if the pref service is available in this process. + if (!inChildProcess()) { + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + } + + let uri = NetUtil.newURI("http://example.org/"); + let channel = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + + let set = "foo=b;max-age=3600, c=d;path=/"; + Services.cookies.setCookieStringFromHttp(uri, set, channel); + + let expected = "foo=b"; + let actual = Services.cookies.getCookieStringFromHttp(uri, channel); + Assert.equal(actual, expected); +} diff --git a/netwerk/cookie/test/unit/test_rawSameSite.js b/netwerk/cookie/test/unit/test_rawSameSite.js new file mode 100644 index 0000000000..dc739ef852 --- /dev/null +++ b/netwerk/cookie/test/unit/test_rawSameSite.js @@ -0,0 +1,125 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function inChildProcess() { + return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +add_task(async _ => { + do_get_profile(); + + let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + dbFile.append("cookies.sqlite"); + + let storage = Services.storage; + let properties = Cc["@mozilla.org/hash-property-bag;1"].createInstance( + Ci.nsIWritablePropertyBag + ); + properties.setProperty("shared", true); + let conn = storage.openDatabase(dbFile); + + conn.schemaVersion = 9; + conn.executeSimpleSQL("DROP TABLE IF EXISTS moz_cookies"); + conn.executeSimpleSQL( + "CREATE TABLE moz_cookies (" + + "id INTEGER PRIMARY KEY, " + + "baseDomain TEXT, " + + "originAttributes TEXT NOT NULL DEFAULT '', " + + "name TEXT, " + + "value TEXT, " + + "host TEXT, " + + "path TEXT, " + + "expiry INTEGER, " + + "lastAccessed INTEGER, " + + "creationTime INTEGER, " + + "isSecure INTEGER, " + + "isHttpOnly INTEGER, " + + "inBrowserElement INTEGER DEFAULT 0, " + + "sameSite INTEGER DEFAULT 0, " + + "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" + + ")" + ); + conn.close(); + + // Allow all cookies if the pref service is available in this process. + if (!inChildProcess()) { + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", true); + Services.prefs.setBoolPref( + "network.cookie.sameSite.noneRequiresSecure", + true + ); + } + + let uri = NetUtil.newURI("http://example.org/"); + + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + let channel = NetUtil.newChannel({ + uri, + loadingPrincipal: principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + let tests = [ + { + cookie: "foo=b;max-age=3600, c=d;path=/; sameSite=strict", + sameSite: 2, + rawSameSite: 2, + }, + { + cookie: "foo=b;max-age=3600, c=d;path=/; sameSite=lax", + sameSite: 1, + rawSameSite: 1, + }, + { cookie: "foo=b;max-age=3600, c=d;path=/", sameSite: 1, rawSameSite: 0 }, + ]; + + for (let i = 0; i < tests.length; ++i) { + let test = tests[i]; + + let promise = new Promise(resolve => { + function observer(subject, topic, data) { + Services.obs.removeObserver(observer, "cookie-saved-on-disk"); + resolve(); + } + + Services.obs.addObserver(observer, "cookie-saved-on-disk"); + }); + + Services.cookies.setCookieStringFromHttp(uri, test.cookie, channel); + + await promise; + + conn = storage.openDatabase(dbFile); + Assert.equal(conn.schemaVersion, 13); + + let stmt = conn.createStatement( + "SELECT sameSite, rawSameSite FROM moz_cookies" + ); + + let success = stmt.executeStep(); + Assert.ok(success); + + let sameSite = stmt.getInt32(0); + let rawSameSite = stmt.getInt32(1); + stmt.finalize(); + + Assert.equal(sameSite, test.sameSite); + Assert.equal(rawSameSite, test.rawSameSite); + + Services.cookies.removeAll(); + + stmt.finalize(); + conn.close(); + } +}); diff --git a/netwerk/cookie/test/unit/test_schemeMap.js b/netwerk/cookie/test/unit/test_schemeMap.js new file mode 100644 index 0000000000..041c24033a --- /dev/null +++ b/netwerk/cookie/test/unit/test_schemeMap.js @@ -0,0 +1,216 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function inChildProcess() { + return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +const { CookieXPCShellUtils } = ChromeUtils.importESModule( + "resource://testing-common/CookieXPCShellUtils.sys.mjs" +); + +let CookieXPCShellUtilsInitialized = false; +function maybeInitializeCookieXPCShellUtils() { + if (!CookieXPCShellUtilsInitialized) { + CookieXPCShellUtilsInitialized = true; + CookieXPCShellUtils.init(this); + + CookieXPCShellUtils.createServer({ hosts: ["example.org"] }); + } +} + +// Don't pick up default permissions from profile. +Services.prefs.setCharPref("permissions.manager.defaultsUrl", ""); + +add_task(async _ => { + do_get_profile(); + + // Allow all cookies if the pref service is available in this process. + if (!inChildProcess()) { + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + } + + info("Let's set a cookie from HTTP example.org"); + + let uri = NetUtil.newURI("http://example.org/"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + let channel = NetUtil.newChannel({ + uri, + loadingPrincipal: principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + Services.cookies.setCookieStringFromHttp(uri, "a=b; sameSite=lax", channel); + + let cookies = Services.cookies.getCookiesFromHost("example.org", {}); + Assert.equal(cookies.length, 1, "We expect 1 cookie only"); + + Assert.equal(cookies[0].schemeMap, Ci.nsICookie.SCHEME_HTTP, "HTTP Scheme"); + + info("Let's set a cookie from HTTPS example.org"); + + uri = NetUtil.newURI("https://example.org/"); + principal = Services.scriptSecurityManager.createContentPrincipal(uri, {}); + channel = NetUtil.newChannel({ + uri, + loadingPrincipal: principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + Services.cookies.setCookieStringFromHttp(uri, "a=b; sameSite=lax", channel); + + cookies = Services.cookies.getCookiesFromHost("example.org", {}); + Assert.equal(cookies.length, 1, "We expect 1 cookie only"); + + Assert.equal( + cookies[0].schemeMap, + Ci.nsICookie.SCHEME_HTTP | Ci.nsICookie.SCHEME_HTTPS, + "HTTP + HTTPS Schemes" + ); + + Services.cookies.removeAll(); +}); + +[true, false].forEach(schemefulComparison => { + add_task(async () => { + do_get_profile(); + Services.prefs.setBoolPref("dom.security.https_first", false); + + maybeInitializeCookieXPCShellUtils(); + + // Allow all cookies if the pref service is available in this process. + if (!inChildProcess()) { + Services.prefs.setBoolPref( + "network.cookie.sameSite.schemeful", + schemefulComparison + ); + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + } + + info( + `Testing schemefulSameSite=${schemefulComparison}. Let's set a cookie from HTTPS example.org` + ); + + let https_uri = NetUtil.newURI("https://example.org/"); + let https_principal = Services.scriptSecurityManager.createContentPrincipal( + https_uri, + {} + ); + let same_site_channel = NetUtil.newChannel({ + uri: https_uri, + loadingPrincipal: https_principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + Services.cookies.setCookieStringFromHttp( + https_uri, + "a=b; sameSite=lax", + same_site_channel + ); + + let cookies = Services.cookies.getCookieStringFromHttp( + https_uri, + same_site_channel + ); + Assert.equal(cookies, "a=b", "Cookies match"); + + let http_uri = NetUtil.newURI("http://example.org/"); + let http_principal = Services.scriptSecurityManager.createContentPrincipal( + http_uri, + {} + ); + let cross_site_channel = NetUtil.newChannel({ + uri: https_uri, + loadingPrincipal: http_principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + cookies = Services.cookies.getCookieStringFromHttp( + http_uri, + cross_site_channel + ); + if (schemefulComparison) { + Assert.equal(cookies, "", "No http(s) cookie for different scheme!"); + } else { + Assert.equal(cookies, "a=b", "http(s) Cookie even for differentscheme!"); + } + + // SameSite cookies are included via document.domain + cookies = await CookieXPCShellUtils.getCookieStringFromDocument( + http_uri.spec + ); + Assert.equal(cookies, "a=b", "document.cookie even for different scheme!"); + + Services.cookies.removeAll(); + Services.prefs.clearUserPref("dom.security.https_first"); + }); +}); + +add_task(async _ => { + do_get_profile(); + Services.prefs.setBoolPref("dom.security.https_first", false); + + // Allow all cookies if the pref service is available in this process. + if (!inChildProcess()) { + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + } + + info("Let's set a cookie without scheme"); + Services.cookies.add( + "example.org", + "/", + "a", + "b", + false, + false, + false, + Math.floor(Date.now() / 1000 + 1000), + {}, + Ci.nsICookie.SAMESITE_LAX, + Ci.nsICookie.SCHEME_UNSET + ); + + let cookies = Services.cookies.getCookiesFromHost("example.org", {}); + Assert.equal(cookies.length, 1, "We expect 1 cookie only"); + Assert.equal(cookies[0].schemeMap, Ci.nsICookie.SCHEME_UNSET, "Unset scheme"); + + ["https", "http"].forEach(scheme => { + let uri = NetUtil.newURI(scheme + "://example.org/"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + let channel = NetUtil.newChannel({ + uri, + loadingPrincipal: principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + cookies = Services.cookies.getCookieStringFromHttp(uri, channel); + Assert.equal(cookies, "a=b", "Cookie for unset scheme"); + }); + + Services.cookies.removeAll(); + Services.prefs.clearUserPref("dom.security.https_first"); +}); diff --git a/netwerk/cookie/test/unit/test_timestamp_fixup.js b/netwerk/cookie/test/unit/test_timestamp_fixup.js new file mode 100644 index 0000000000..a6e9642ad7 --- /dev/null +++ b/netwerk/cookie/test/unit/test_timestamp_fixup.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const USEC_PER_SEC = 1000 * 1000; +const ONE_DAY = 60 * 60 * 24 * USEC_PER_SEC; +const ONE_YEAR = ONE_DAY * 365; +const LAST_ACCESSED_DIFF = 10 * ONE_YEAR; +const CREATION_DIFF = 100 * ONE_YEAR; + +function initDB(conn, now) { + // Write the schema v7 to the database. + conn.schemaVersion = 7; + conn.executeSimpleSQL( + "CREATE TABLE moz_cookies (" + + "id INTEGER PRIMARY KEY, " + + "baseDomain TEXT, " + + "originAttributes TEXT NOT NULL DEFAULT '', " + + "name TEXT, " + + "value TEXT, " + + "host TEXT, " + + "path TEXT, " + + "expiry INTEGER, " + + "lastAccessed INTEGER, " + + "creationTime INTEGER, " + + "isSecure INTEGER, " + + "isHttpOnly INTEGER, " + + "appId INTEGER DEFAULT 0, " + + "inBrowserElement INTEGER DEFAULT 0, " + + "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" + + ")" + ); + conn.executeSimpleSQL( + "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " + + "originAttributes)" + ); + + conn.executeSimpleSQL("PRAGMA synchronous = OFF"); + conn.executeSimpleSQL("PRAGMA journal_mode = WAL"); + conn.executeSimpleSQL("PRAGMA wal_autocheckpoint = 16"); + + conn.executeSimpleSQL( + `INSERT INTO moz_cookies(baseDomain, host, name, value, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) + VALUES ('foo.com', '.foo.com', 'foo', 'bar=baz', '/', + ${now + ONE_DAY}, ${now + LAST_ACCESSED_DIFF} , ${ + now + CREATION_DIFF + } , 1, 1)` + ); +} + +add_task(async function test_timestamp_fixup() { + let now = Date.now() * 1000; // date in microseconds + Services.prefs.setBoolPref("network.cookie.fixup_on_db_load", true); + do_get_profile(); + let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + dbFile.append("cookies.sqlite"); + let conn = Services.storage.openDatabase(dbFile); + initDB(conn, now); + + if (AppConstants.platform != "android") { + Services.fog.initializeFOG(); + } + Services.fog.testResetFOG(); + + // Now start the cookie service, and then check the fields in the table. + // Get sessionCookies to wait for the initialization in cookie thread + Assert.lessOrEqual( + Math.floor(Services.cookies.cookies[0].creationTime / 1000), + now + ); + Assert.equal(conn.schemaVersion, 13); + + Assert.equal( + await Glean.networking.cookieTimestampFixedCount.creationTime.testGetValue(), + 1, + "One fixup of creation time" + ); + Assert.equal( + await Glean.networking.cookieTimestampFixedCount.lastAccessed.testGetValue(), + 1, + "One fixup of lastAccessed" + ); + { + let { values } = + await Glean.networking.cookieCreationFixupDiff.testGetValue(); + info(JSON.stringify(values)); + let keys = Object.keys(values).splice(-2, 2); + Assert.equal(keys.length, 2, "There should be two entries in telemetry"); + Assert.equal(values[keys[0]], 1, "First entry should have value 1"); + Assert.equal(values[keys[1]], 0, "Second entry should have value 0"); + const creationDiffInSeconds = CREATION_DIFF / USEC_PER_SEC; + Assert.lessOrEqual( + parseInt(keys[0]), + creationDiffInSeconds, + "The bucket should be smaller than time diff" + ); + Assert.lessOrEqual( + creationDiffInSeconds, + parseInt(keys[1]), + "The next bucket should be larger than time diff" + ); + } + + { + let { values } = + await Glean.networking.cookieAccessFixupDiff.testGetValue(); + info(JSON.stringify(values)); + let keys = Object.keys(values).splice(-2, 2); + Assert.equal(keys.length, 2, "There should be two entries in telemetry"); + Assert.equal(values[keys[0]], 1, "First entry should have value 1"); + Assert.equal(values[keys[1]], 0, "Second entry should have value 0"); + info(now); + const lastAccessedDiffInSeconds = LAST_ACCESSED_DIFF / USEC_PER_SEC; + Assert.lessOrEqual( + parseInt(keys[0]), + lastAccessedDiffInSeconds, + "The bucket should be smaller than time diff" + ); + Assert.lessOrEqual( + lastAccessedDiffInSeconds, + parseInt(keys[1]), + "The next bucket should be larger than time diff" + ); + } + + conn.close(); +}); diff --git a/netwerk/cookie/test/unit/xpcshell.toml b/netwerk/cookie/test/unit/xpcshell.toml new file mode 100644 index 0000000000..694d3cb847 --- /dev/null +++ b/netwerk/cookie/test/unit/xpcshell.toml @@ -0,0 +1,26 @@ +[DEFAULT] +head = "" + +["test_baseDomain_publicsuffix.js"] + +["test_bug643051.js"] + +["test_bug1155169.js"] + +["test_bug1321912.js"] + +["test_eviction.js"] + +["test_getCookieSince.js"] + +["test_migrateCookieLifetimePref.js"] + +["test_parser_0001.js"] + +["test_parser_0019.js"] + +["test_rawSameSite.js"] + +["test_schemeMap.js"] + +["test_timestamp_fixup.js"] |