| author | Valentin Gosu <valentin.gosu@gmail.com> |
| Tue, 28 Apr 2026 07:29:26 +0000 | |
| changeset 845401 | 01a837103137ea893c7583aba08d61a0a4345323 |
| parent 845400 | 9b08fb675c7eaa5a3d9226f995a49d644cfe5f88 |
| child 845402 | 02c52f1578d2da4f0c4e9d886e6e5e170c4c4d1f |
| push id | 44422 |
| push user | sstanca@mozilla.com |
| push date | Tue, 28 Apr 2026 21:44:21 +0000 |
| treeherder | mozilla-central@499b89bd5492 [default view] [failures only] |
| git commit | 991fe75b38e2aeafb9844fc995b265268687a730 |
| reviewers | baku, cookie-reviewers |
| bugs | 2026913 |
| milestone | 152.0a1 |
| first release with | nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/netwerk/cookie/CookiePersistentStorage.cpp +++ b/netwerk/cookie/CookiePersistentStorage.cpp @@ -1999,16 +1999,29 @@ CookiePersistentStorage::OpenDBResult Co host.get())); CookieDomainTuple* cleanupTuple = mCleanupArray.AppendElement(); cleanupTuple->key = CookieKey(baseDomain, attrs); cleanupTuple->originAttributes = attrs; cleanupTuple->cookie = Cookie::Create(*cookieStruct, attrs); continue; } + // When the valueless_cookie pref is enabled, discard legacy nameless + // cookies (name="") that were stored under the old behavior. + // Under the new behavior these cookies are unreachable since new cookies + // without '=' are stored with the token as name, not value. + if (StaticPrefs::network_cookie_valueless_cookie() && + cookieStruct->name().IsEmpty()) { + CookieDomainTuple* cleanupTuple = mCleanupArray.AppendElement(); + cleanupTuple->key = CookieKey(baseDomain, attrs); + cleanupTuple->originAttributes = attrs; + cleanupTuple->cookie = Cookie::Create(*cookieStruct, attrs); + continue; + } + // Create the Cookie on the background thread. CreateValidated fixes up // any timestamps in the DB that are far in the future. RefPtr<Cookie> cookie = Cookie::CreateValidated(*cookieStruct, attrs); // Clean up invalid first-party partitioned cookies without the // 'Partitioned' attribute. Use Cookie::Create to preserve the original DB // timestamps for the deletion query. if (CookieCommons::IsFirstPartyPartitionedCookieWithoutCHIPS(
new file mode 100644 --- /dev/null +++ b/netwerk/cookie/test/unit/test_valueless_cookie_migration.js @@ -0,0 +1,120 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + Tests that when network.cookie.valueless_cookie=true (the default), legacy + nameless cookies (name="", value!="") that were stored on disk under the old + behavior are discarded on load rather than surfacing with an unreachable key. +*/ + +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +const USEC_PER_MSEC = 1000; +const ONE_DAY_IN_USEC = 60 * 60 * 24 * USEC_PER_MSEC * 1000; + +function initDBWithNamelessCookie(conn, now) { + 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"); + + // A legacy nameless cookie: name="", value="nameless" (created by old + // behavior where `Set-Cookie: foo` produced name="" value="foo"). + conn.executeSimpleSQL( + `INSERT INTO moz_cookies(baseDomain, host, name, value, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) + VALUES ('example.com', 'example.com', '', 'nameless', '/', + ${now + ONE_DAY_IN_USEC}, ${now}, ${now - ONE_DAY_IN_USEC}, 0, 0)` + ); + + // A normal cookie with a name, which must always be retained. + conn.executeSimpleSQL( + `INSERT INTO moz_cookies(baseDomain, host, name, value, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) + VALUES ('example.com', 'example.com', 'regular', 'cookie', '/', + ${now + ONE_DAY_IN_USEC}, ${now}, ${now - ONE_DAY_IN_USEC}, 0, 0)` + ); +} + +add_task(async function test_nameless_cookie_discarded_when_pref_enabled() { + let now = Date.now() * USEC_PER_MSEC; + Services.prefs.setBoolPref("network.cookie.valueless_cookie", true); + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + do_get_profile(); + + let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + dbFile.append("cookies.sqlite"); + let conn = Services.storage.openDatabase(dbFile); + initDBWithNamelessCookie(conn, now); + conn.close(); + + // Arm the observer before triggering init so we don't miss the notification. + let dbReadPromise = TestUtils.topicObserved("cookie-db-read"); + + // Trigger cookie service initialization by accessing cookies. + let { cookies } = Services.cookies; + + let exampleCookies = cookies.filter(c => c.host === "example.com"); + Assert.equal( + exampleCookies.length, + 1, + "Only one cookie should be loaded (nameless one discarded)" + ); + Assert.equal( + exampleCookies[0].name, + "regular", + "The remaining cookie has a name" + ); + Assert.equal( + exampleCookies[0].value, + "cookie", + "The remaining cookie has the right value" + ); + + // Wait for InitDBConn to complete; RemoveCookieFromDB is dispatched inside it. + await dbReadPromise; + + // RemoveCookieFromDB uses an async statement, so poll until the row is gone. + await TestUtils.waitForCondition(() => { + let checkConn = Services.storage.openDatabase(dbFile); + try { + let stmt = checkConn.createStatement( + "SELECT COUNT(*) FROM moz_cookies WHERE name = '' AND host = 'example.com'" + ); + try { + stmt.executeStep(); + return stmt.getInt32(0) === 0; + } finally { + stmt.finalize(); + } + } finally { + checkConn.close(); + } + }, "Nameless cookie should be removed from the SQLite database"); +});