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:
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,