Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* 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/. */
const { NetUtil } = ChromeUtils.importESModule(
"resource://gre/modules/NetUtil.sys.mjs"
);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("network.cookie.cookieBehavior");
Services.prefs.clearUserPref(
"network.cookieJarSettings.unblocked_for_testing"
);
Services.prefs.clearUserPref("network.cookie.CHIPS.enabled");
Services.prefs.clearUserPref("network.cookie.chips.partitionLimitEnabled");
Services.prefs.clearUserPref(
"network.cookie.chips.partitionLimitByteCapacity"
);
Services.prefs.clearUserPref("network.cookie.chips.partitionLimitDryRun");
Services.cookies.removeAll();
});
// enable chips and chips partition limit
add_setup(async () => {
Services.prefs.setIntPref("network.cookie.cookieBehavior", 5);
Services.prefs.setBoolPref(
"network.cookieJarSettings.unblocked_for_testing",
true
);
Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", true);
Services.prefs.setBoolPref(
"network.cookie.chips.partitionLimitEnabled",
true
);
Services.prefs.setBoolPref(
"network.cookie.chips.partitionLimitDryRun",
false
);
// FOG needs a profile directory to put its data in.
do_get_profile();
// FOG needs to be initialized in order for data to flow.
Services.fog.initializeFOG();
});
function headerify(cookie, index, partitioned) {
let maxAge = 9000 + index; // use index so cookies can be ordered by age
// there is no way to test insecure purging first with a partitioned cookie without `Secure`
// these cookies would be immediately rejected
let mostHeaders = `; Max-Age=${maxAge}; SameSite=None; Secure`;
let temp = cookie.concat(mostHeaders);
if (partitioned) {
temp = temp.concat("; Partitioned");
}
return temp;
}
async function checkReportedOverflow(expected) {
let reported =
await Glean.networking.cookieChipsPartitionLimitOverflow.testGetValue();
if (expected == 0) {
Assert.equal(reported, null);
return;
}
// verify the telemetry by summing, but the individual reports are not aggregated
Assert.equal(reported.sum, expected);
}
function channelMaybePartitioned(uri, partition) {
let channel = NetUtil.newChannel({
uri,
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
if (partition) {
let cookieJarSettings = Cc[
"@mozilla.org/cookieJarSettings;1"
].createInstance(Ci.nsICookieJarSettings);
cookieJarSettings.initWithURI(uri, false);
channel.loadInfo.cookieJarSettings = cookieJarSettings;
}
return channel;
}
const BYTE_LIMIT = 10240;
const BYTE_LIMIT_WITH_BUFFER = BYTE_LIMIT * 1.2; // 12288
const BYTES_PER_COOKIE = 100;
const COOKIES_TO_SET_COUNT = 122;
const COOKIES_TO_SET_BYTES = COOKIES_TO_SET_COUNT * BYTES_PER_COOKIE; // 12200
// set many cookies at 100 Bytes each
// partition maximum is 10KiB or 10240B (only for partitioned cookies)
// so after this function is called any partitioned cookie (100B) will exceed
function setManyCookies(uri, channel, partitioned) {
let cookieString = "";
let cookieNames = [];
for (let i = 0; i < COOKIES_TO_SET_COUNT; i++) {
let name = "c" + i.toString();
let value =
i + "_".repeat(BYTES_PER_COOKIE - i.toString().length - name.length);
let cookie = name + "=" + value;
cookieNames.push(name);
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, i, partitioned),
channel
);
// prep the expected value
cookieString += cookie;
if (i < COOKIES_TO_SET_COUNT - 1) {
cookieString += "; ";
}
}
return { cookieString, cookieNames };
}
// unpartitioned cookies should not be purged
add_task(async function test_chips_limit_parent_http_unpartitioned() {
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, false);
let expected = setManyCookies(uri, channel, false);
expected.cookieNames.push("exceeded");
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0);
// unpartitioned cookies, no limit here
let cookie = "exceeded".concat("=").concat("x".repeat(240));
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, false), // use count for uniqueness
channel
);
await checkReportedOverflow(0);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});
// parent http partition cookies exceeding capacity should purge in FIFO manner
add_task(async function test_chips_limit_parent_http_partitioned() {
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, true);
let expected = setManyCookies(uri, channel, true);
expected.cookieNames.push("exceeded");
// with the buffer quite a few cookies will be purged
for (let i = 0; i < 23; i++) {
expected.cookieNames.shift();
}
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0); // no reporting until over the hard cap
// adding 248 Bytes has excess of 208Bytes (3 cookies will be purged (FIFO))
let cookie = "exceeded".concat("=").concat("x".repeat(240));
let cookieNameValueLen = cookie.length - 1;
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
channel
);
let expectedOverflow =
COOKIES_TO_SET_BYTES + cookieNameValueLen - BYTE_LIMIT_WITH_BUFFER;
await checkReportedOverflow(expectedOverflow);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});
// partition limit should still work for cookie overwrites
add_task(async function test_chips_limit_overwrites_can_purge() {
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, true);
let expected = setManyCookies(uri, channel, true);
for (let i = 0; i < 22; i++) {
expected.cookieNames.shift();
}
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0);
// cookie which already exists also triggers purge
// 244 (new cookie) - 100 (existing cookie) -> 144 newly added bytes
// So we are in excess by 104 bytes, means 2 cookies need purging (FIFO)
let cookie = "c101".concat("=").concat("x".repeat(240)); // 244
let cookieNameValueLen = cookie.length - 1;
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
channel
);
let expectedOverflow =
COOKIES_TO_SET_BYTES +
cookieNameValueLen -
BYTES_PER_COOKIE -
BYTE_LIMIT_WITH_BUFFER;
await checkReportedOverflow(expectedOverflow);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});
// dry run mode should not purge, but still report excess via telemetry
add_task(async function test_chips_limit_dry_run_no_purge() {
Services.prefs.setBoolPref("network.cookie.chips.partitionLimitDryRun", true);
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, true);
let expected = setManyCookies(uri, channel, true);
expected.cookieNames.push("exceeded");
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0);
// adding 248 Bytes has excess of 208Bytes (3 cookies will be purged (FIFO))
let cookie = "exceeded".concat("=").concat("x".repeat(240));
let cookieNameValueLen = cookie.length - 1;
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
channel
);
expected.cookieString += "; ".concat(cookie);
let expectedOverflow =
COOKIES_TO_SET_BYTES + cookieNameValueLen - BYTE_LIMIT_WITH_BUFFER;
await checkReportedOverflow(expectedOverflow);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Assert.equal(second, expected.cookieString);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});
add_task(async function test_chips_limit_chips_off() {
Services.prefs.setBoolPref(
"network.cookie.chips.partitionLimitDryRun",
false
);
Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", false);
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, true);
let expected = setManyCookies(uri, channel, true);
expected.cookieNames.push("exceeded");
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0);
// shouldn't trigger purge when CHIPS disabled
let cookie = "exceeded".concat("=").concat("x".repeat(240));
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
channel
);
expected.cookieString += "; ".concat(cookie);
await checkReportedOverflow(0);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Assert.equal(second, expected.cookieString);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});
add_task(async function test_chips_limit_chips_limit_off() {
Services.prefs.setBoolPref(
"network.cookie.chips.partitionLimitDryRun",
false
);
Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", true);
Services.prefs.setBoolPref(
"network.cookie.chips.partitionLimitEnabled",
false
);
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, true);
let expected = setManyCookies(uri, channel, true);
expected.cookieNames.push("exceeded");
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0);
// shouldn't trigger purge when CHIPS limit disabled
let cookie = "exceeded".concat("=").concat("x".repeat(240));
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
channel
);
expected.cookieString += "; ".concat(cookie);
await checkReportedOverflow(0);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Assert.equal(second, expected.cookieString);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});
// non-chips-partitioned cookies do not trigger the limit
add_task(async function test_chips_limit_non_chips_partitioned() {
// channel is partitioned, not cookie header -> non-chips-partition cookies
let baseDomain = "example.org";
let uri = NetUtil.newURI("https://" + baseDomain + "/");
let channel = channelMaybePartitioned(uri, baseDomain, true);
let expected = setManyCookies(uri, channel, false);
expected.cookieNames.push("exceeded");
// pre-condition: check that all got added as expected
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
Assert.equal(actual, expected.cookieString);
await checkReportedOverflow(0);
// non-chips-partitioned cookies, should also have no limit
let cookie = "exceeded".concat("=").concat("x".repeat(240));
Services.cookies.setCookieStringFromHttp(
uri,
headerify(cookie, COOKIES_TO_SET_COUNT, false), // use count for uniqueness
channel
);
await checkReportedOverflow(0);
// extract cookie names from string and compare to expected values
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
let cookies = second.split("; ");
for (let i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
}
Assert.deepEqual(cookies, expected.cookieNames);
Assert.equal(cookies.length, expected.cookieNames.length);
Services.cookies.removeAll();
Services.fog.testResetFOG();
});