tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit bef781bbd7a225c428c2444d7d02e9f6eb327e94
parent 7db4be187b8b76d1894cc14c1a98e70ed5ee0f2d
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date:   Sun, 14 Dec 2025 16:56:33 +0000

Bug 2005748 - MaxAge attribute for CookieStore API r=webidl,cookie-reviewers,smaug,bvandersloot

Differential Revision: https://phabricator.services.mozilla.com/D276206

Diffstat:
Mdom/cookiestore/CookieStore.cpp | 51++++++++++++++++++++++++++++++++++++++-------------
Mdom/webidl/CookieStore.webidl | 1+
Mnetwerk/cookie/CookieCommons.cpp | 12+++++++++++-
Mnetwerk/cookie/CookieCommons.h | 8++++++--
Mnetwerk/cookie/CookieParser.cpp | 7++-----
Dtesting/web-platform/meta/cookiestore/cookieStore_set_maxAge.https.any.js.ini | 14--------------
Mtesting/web-platform/tests/cookiestore/cookieStore_set_maxAge.https.any.js | 2+-
7 files changed, 59 insertions(+), 36 deletions(-)

diff --git a/dom/cookiestore/CookieStore.cpp b/dom/cookiestore/CookieStore.cpp @@ -35,15 +35,6 @@ namespace mozilla::dom { namespace { -int64_t ComputeExpiry(const CookieInit& aOptions) { - if (aOptions.mExpires.IsNull()) { // Session cookie - return INT64_MAX; - } - - return CookieCommons::MaybeCapExpiry(PR_Now() / PR_USEC_PER_MSEC, - aOptions.mExpires.Value()); -} - int32_t SameSiteToConst(const CookieSameSite& aSameSite) { switch (aSameSite) { case CookieSameSite::Strict: @@ -185,6 +176,36 @@ bool ValidateCookieDomain(nsIPrincipal* aPrincipal, const nsAString& aName, return true; } +bool ValidateExpiresAndMaxAge(const Nullable<double>& aExpires, + const Nullable<int64_t>& aMaxAge, + int64_t& aComputedExpiry, Promise* aPromise) { + MOZ_ASSERT(aPromise); + + if (aExpires.IsNull() && aMaxAge.IsNull()) { + // Session cookie + aComputedExpiry = INT64_MAX; + return true; + } + + if (!aExpires.IsNull() && !aMaxAge.IsNull()) { + aPromise->MaybeRejectWithTypeError( + "Cookie expires and maxAge attributes cannot both be set"); + return false; + } + + int64_t creationTimeInMSec = PR_Now() / PR_USEC_PER_MSEC; + + if (!aExpires.IsNull()) { + aComputedExpiry = + CookieCommons::MaybeCapExpiry(creationTimeInMSec, aExpires.Value()); + } else { + aComputedExpiry = + CookieCommons::MaybeCapMaxAge(creationTimeInMSec, aMaxAge.Value()); + } + + return true; +} + bool ValidateCookiePath(nsIURI* aURI, const nsAString& aPath, nsAString& aRetPath, Promise* aPromise) { MOZ_ASSERT(aURI); @@ -461,6 +482,12 @@ already_AddRefed<Promise> CookieStore::Set(const CookieInit& aOptions, return; } + int64_t expiry; + if (!ValidateExpiresAndMaxAge(aOptions.mExpires, aOptions.mMaxAge, + expiry, promise)) { + return; + } + nsString path; if (!ValidateCookiePath(cookieURI, aOptions.mPath, path, promise)) { return; @@ -506,10 +533,8 @@ already_AddRefed<Promise> CookieStore::Set(const CookieInit& aOptions, mozilla::WrapNotNull(cookieURI.get()), cookiePrincipal->OriginAttributesRef(), thirdPartyContext, partitionForeign, usingStorageAccess, isOn3PCBExceptionList, - name, value, - // If expires is not set, it's a session cookie. - aOptions.mExpires.IsNull(), ComputeExpiry(aOptions), domain, - path, SameSiteToConst(aOptions.mSameSite), + name, value, /* session cookie: */ expiry == INT64_MAX, expiry, + domain, path, SameSiteToConst(aOptions.mSameSite), aOptions.mPartitioned, operationID); if (NS_WARN_IF(!ipcPromise)) { promise->MaybeResolveWithUndefined(); diff --git a/dom/webidl/CookieStore.webidl b/dom/webidl/CookieStore.webidl @@ -59,6 +59,7 @@ dictionary CookieInit { USVString path = "/"; CookieSameSite sameSite = "strict"; boolean partitioned = false; + long long? maxAge = null; }; dictionary CookieStoreDeleteOptions { diff --git a/netwerk/cookie/CookieCommons.cpp b/netwerk/cookie/CookieCommons.cpp @@ -1000,7 +1000,7 @@ void CookieCommons::GetServerDateHeader(nsIChannel* aChannel, // static int64_t CookieCommons::MaybeCapExpiry(int64_t aCurrentTimeInMSec, int64_t aExpiryInMSec) { - int64_t maxageCap = StaticPrefs::network_cookie_maxageCap(); + const int64_t maxageCap = StaticPrefs::network_cookie_maxageCap(); if (maxageCap) { aExpiryInMSec = @@ -1010,6 +1010,16 @@ int64_t CookieCommons::MaybeCapExpiry(int64_t aCurrentTimeInMSec, return aExpiryInMSec; } +int64_t CookieCommons::MaybeCapMaxAge(int64_t aCurrentTimeInMSec, + int64_t aMaxAgeInSec) { + const int64_t maxageCap = StaticPrefs::network_cookie_maxageCap(); + CheckedInt<int64_t> value(aCurrentTimeInMSec); + + value += + (maxageCap ? std::min(aMaxAgeInSec, maxageCap) : aMaxAgeInSec) * 1000; + return value.isValid() ? value.value() : INT64_MAX; +} + // static bool CookieCommons::IsSubdomainOf(const nsACString& a, const nsACString& b) { if (a == b) { diff --git a/netwerk/cookie/CookieCommons.h b/netwerk/cookie/CookieCommons.h @@ -174,11 +174,15 @@ class CookieCommons final { mozilla::dom::Document* aDocument, nsIPrincipal** aCookiePrincipal, nsIPrincipal** aCookiePartitionedPrincipal); - // Return a reduced expiry attribute value if needed. Parameters are in - // milliseconds. + // Return a reduced expiry attribute value if needed. static int64_t MaybeCapExpiry(int64_t aCurrentTimeInMSec, int64_t aExpiryInMSec); + // Return a reduced expiry value starting from the max-age attribute and the + // current time. + static int64_t MaybeCapMaxAge(int64_t aCurrentTimeInMSec, + int64_t aMaxAgeInSec); + // returns true if 'a' is equal to or a subdomain of 'b', // assuming no leading dots are present. static bool IsSubdomainOf(const nsACString& a, const nsACString& b); diff --git a/netwerk/cookie/CookieParser.cpp b/netwerk/cookie/CookieParser.cpp @@ -499,7 +499,6 @@ bool CookieParser::GetExpiry(CookieStruct& aCookieData, const nsACString& aExpires, const nsACString& aMaxage, const nsACString& aDateHeader, bool aFromHttp) { - int64_t maxageCap = StaticPrefs::network_cookie_maxageCap(); int64_t creationTimeInMSec = aCookieData.creationTimeInUSec() / int64_t(PR_USEC_PER_MSEC); @@ -517,10 +516,8 @@ bool CookieParser::GetExpiry(CookieStruct& aCookieData, if (maxage == INT64_MIN) { aCookieData.expiryInMSec() = maxage; } else { - CheckedInt<int64_t> value(creationTimeInMSec); - value += (maxageCap ? std::min(maxage, maxageCap) : maxage) * 1000; - - aCookieData.expiryInMSec() = value.isValid() ? value.value() : INT64_MAX; + aCookieData.expiryInMSec() = + CookieCommons::MaybeCapMaxAge(creationTimeInMSec, maxage); } return false; diff --git a/testing/web-platform/meta/cookiestore/cookieStore_set_maxAge.https.any.js.ini b/testing/web-platform/meta/cookiestore/cookieStore_set_maxAge.https.any.js.ini @@ -1,14 +0,0 @@ -[cookieStore_set_maxAge.https.any.html] - [cookieStore.set with maxAge set to a negative value] - expected: FAIL - - [cookieStore.set fails with both maxAge and expires] - expected: FAIL - - -[cookieStore_set_maxAge.https.any.serviceworker.html] - [cookieStore.set with maxAge set to a negative value] - expected: FAIL - - [cookieStore.set fails with both maxAge and expires] - expected: FAIL diff --git a/testing/web-platform/tests/cookiestore/cookieStore_set_maxAge.https.any.js b/testing/web-platform/tests/cookiestore/cookieStore_set_maxAge.https.any.js @@ -31,7 +31,7 @@ cookie_test(async testCase => { const tomorrow = Date.now() + oneDay ; await promise_rejects_js(testCase, TypeError, - cookieStore.set('cookie-name', { + cookieStore.set({ name: 'cookie-name', value: 'cookie-value', expires: tomorrow,