summaryrefslogtreecommitdiffstats
path: root/netwerk/cookie/CookieService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cookie/CookieService.cpp')
-rw-r--r--netwerk/cookie/CookieService.cpp207
1 files changed, 146 insertions, 61 deletions
diff --git a/netwerk/cookie/CookieService.cpp b/netwerk/cookie/CookieService.cpp
index 0e90b8cbae..78698d44a0 100644
--- a/netwerk/cookie/CookieService.cpp
+++ b/netwerk/cookie/CookieService.cpp
@@ -373,15 +373,6 @@ CookieService::GetCookieStringFromDocument(Document* aDocument,
return NS_OK;
}
- nsCOMPtr<nsIPrincipal> cookiePrincipal =
- aDocument->EffectiveCookiePrincipal();
-
- // TODO (Bug 1874174): A document could access both unpartitioned and
- // partitioned cookie jars. We will need to prepare partitioned and
- // unpartitioned principals for access both cookie jars.
- nsTArray<nsCOMPtr<nsIPrincipal>> principals;
- principals.AppendElement(cookiePrincipal);
-
bool thirdParty = true;
nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
// in gtests we don't have a window, let's consider those requests as 3rd
@@ -395,6 +386,26 @@ CookieService::GetCookieStringFromDocument(Document* aDocument,
}
}
+ nsCOMPtr<nsIPrincipal> cookiePrincipal =
+ aDocument->EffectiveCookiePrincipal();
+
+ nsTArray<nsCOMPtr<nsIPrincipal>> principals;
+ principals.AppendElement(cookiePrincipal);
+
+ // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available
+ // (and therefore the partitioned principal), the unpartitioned cookie jar is
+ // only available in first-party or third-party with storageAccess contexts.
+ bool isCHIPS = StaticPrefs::network_cookie_cookieBehavior_optInPartitioning();
+ bool isUnpartitioned =
+ cookiePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty();
+ if (isCHIPS && isUnpartitioned) {
+ // Assert that we are only doing this if we are first-party or third-party
+ // with storageAccess.
+ MOZ_ASSERT(!thirdParty || aDocument->UsingStorageAccess());
+ // Add the partitioned principal to principals
+ principals.AppendElement(aDocument->PartitionedPrincipal());
+ }
+
nsTArray<Cookie*> cookieList;
for (auto& principal : principals) {
@@ -519,20 +530,40 @@ CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel,
ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
aChannel, false, aHostURI, nullptr, &rejectedReason);
- OriginAttributes attrs;
- StoragePrincipalHelper::GetOriginAttributes(
- aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
-
bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
bool hadCrossSiteRedirects = false;
bool isSameSiteForeign = CookieCommons::IsSameSiteForeign(
aChannel, aHostURI, &hadCrossSiteRedirects);
- // TODO (Bug 1874174): A channel could load both unpartitioned and partitioned
- // cookie jars together. We will need to get cookies from both unpartitioned
- // and partitioned cookie jars according to storage access.
+ OriginAttributes storageOriginAttributes;
+ StoragePrincipalHelper::GetOriginAttributes(
+ aChannel, storageOriginAttributes,
+ StoragePrincipalHelper::eStorageAccessPrincipal);
+
nsTArray<OriginAttributes> originAttributesList;
- originAttributesList.AppendElement(attrs);
+ originAttributesList.AppendElement(storageOriginAttributes);
+
+ // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available
+ // (and therefore the partitioned OriginAttributes), the unpartitioned cookie
+ // jar is only available in first-party or third-party with storageAccess
+ // contexts.
+ bool isCHIPS = StaticPrefs::network_cookie_cookieBehavior_optInPartitioning();
+ bool isUnpartitioned = storageOriginAttributes.mPartitionKey.IsEmpty();
+ if (isCHIPS && isUnpartitioned) {
+ // Assert that we are only doing this if we are first-party or third-party
+ // with storageAccess.
+ MOZ_ASSERT(
+ !result.contains(ThirdPartyAnalysis::IsForeign) ||
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted));
+ // Add the partitioned principal to principals
+ OriginAttributes partitionedOriginAttributes;
+ StoragePrincipalHelper::GetOriginAttributes(
+ aChannel, partitionedOriginAttributes,
+ StoragePrincipalHelper::ePartitionedPrincipal);
+ originAttributesList.AppendElement(partitionedOriginAttributes);
+ // Assert partitionedOAs have partitioneKey set.
+ MOZ_ASSERT(!partitionedOriginAttributes.mPartitionKey.IsEmpty());
+ }
AutoTArray<Cookie*, 8> foundCookieList;
GetCookiesForURI(
@@ -630,9 +661,10 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
aChannel, false, aHostURI, nullptr, &rejectedReason);
- OriginAttributes attrs;
+ OriginAttributes storagePrincipalOriginAttributes;
StoragePrincipalHelper::GetOriginAttributes(
- aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
+ aChannel, storagePrincipalOriginAttributes,
+ StoragePrincipalHelper::eStorageAccessPrincipal);
// get the base domain for the host URI.
// e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
@@ -660,11 +692,11 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
baseDomainFromURI);
NS_ENSURE_SUCCESS(rv, NS_OK);
- CookieStorage* storage = PickStorage(attrs);
+ CookieStorage* storage = PickStorage(storagePrincipalOriginAttributes);
// check default prefs
uint32_t priorCookieCount = storage->CountCookiesFromHost(
- baseDomainFromURI, attrs.mPrivateBrowsingId);
+ baseDomainFromURI, storagePrincipalOriginAttributes.mPrivateBrowsingId);
nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
@@ -674,7 +706,8 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
- aCookieHeader, priorCookieCount, attrs, &rejectedReason);
+ aCookieHeader, priorCookieCount, storagePrincipalOriginAttributes,
+ &rejectedReason);
MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
@@ -718,9 +751,24 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
nsCString cookieHeader(aCookieHeader);
- bool moreCookieToRead = true;
+ // CHIPS - The partitioned cookie jar is always available and it is always
+ // possible to store cookies in it using the "Partitioned" attribute.
+ // Prepare the partitioned principals OAs to enable possible partitioned
+ // cookie storing from first-party or with StorageAccess.
+ // Similar behavior to CookieServiceChild::SetCookieStringFromHttp().
+ OriginAttributes partitionedPrincipalOriginAttributes;
+ bool isPartitionedPrincipal =
+ !storagePrincipalOriginAttributes.mPartitionKey.IsEmpty();
+ bool isCHIPS = StaticPrefs::network_cookie_cookieBehavior_optInPartitioning();
+ // Only need to get OAs if we don't already use the partitioned principal.
+ if (isCHIPS && !isPartitionedPrincipal) {
+ StoragePrincipalHelper::GetOriginAttributes(
+ aChannel, partitionedPrincipalOriginAttributes,
+ StoragePrincipalHelper::ePartitionedPrincipal);
+ }
// process each cookie in the header
+ bool moreCookieToRead = true;
while (moreCookieToRead) {
CookieStruct cookieData;
bool canSetCookie = false;
@@ -751,8 +799,22 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
continue;
}
+ // CHIPS - If the partitioned attribute is set, store cookie in partitioned
+ // cookie jar independent of context. If the cookies are stored in the
+ // partitioned cookie jar anyway no special treatment of CHIPS cookies
+ // necessary.
+ bool needPartitioned =
+ isCHIPS && cookieData.isPartitioned() && !isPartitionedPrincipal;
+ OriginAttributes& cookieOriginAttributes =
+ needPartitioned ? partitionedPrincipalOriginAttributes
+ : storagePrincipalOriginAttributes;
+ // Assert that partitionedPrincipalOriginAttributes are initialized if used.
+ MOZ_ASSERT_IF(
+ needPartitioned,
+ !partitionedPrincipalOriginAttributes.mPartitionKey.IsEmpty());
+
// create a new Cookie
- RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
+ RefPtr<Cookie> cookie = Cookie::Create(cookieData, cookieOriginAttributes);
MOZ_ASSERT(cookie);
int64_t currentTimeInUsec = PR_Now();
@@ -763,8 +825,8 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
RefPtr<BrowsingContext> bc = loadInfo->GetBrowsingContext();
// add the cookie to the list. AddCookie() takes care of logging.
- storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec,
- aHostURI, aCookieHeader, true, bc);
+ storage->AddCookie(crc, baseDomain, cookieOriginAttributes, cookie,
+ currentTimeInUsec, aHostURI, aCookieHeader, true, bc);
}
return NS_OK;
@@ -1180,6 +1242,16 @@ static void RecordPartitionedTelemetry(const CookieStruct& aCookieData,
}
}
+static bool HasSecurePrefix(const nsACString& aString) {
+ return StringBeginsWith(aString, "__Secure-"_ns,
+ nsCaseInsensitiveCStringComparator);
+}
+
+static bool HasHostPrefix(const nsACString& aString) {
+ return StringBeginsWith(aString, "__Host-"_ns,
+ nsCaseInsensitiveCStringComparator);
+}
+
// processes a single cookie, and returns true if there are more cookies
// to be processed
bool CookieService::CanSetCookie(
@@ -1290,9 +1362,12 @@ bool CookieService::CanSetCookie(
return newCookie;
}
- if (!CheckHiddenPrefix(aCookieData)) {
+ // If a cookie is nameless, then its value must not start with
+ // `__Host-` or `__Secure-`
+ if (aCookieData.name().IsEmpty() && (HasSecurePrefix(aCookieData.value()) ||
+ HasHostPrefix(aCookieData.value()))) {
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
- "failed the CheckHiddenPrefix tests");
+ "failed hidden prefix tests");
CookieLogging::LogMessageToConsole(
aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
"CookieRejectedInvalidPrefix"_ns,
@@ -1449,10 +1524,9 @@ bool CookieService::CanSetCookie(
separators = ";" | "="
value-sep = ";"
cookie-sep = CR | LF
- allowed-chars = <any OCTET except NUL or cookie-sep>
+ allowed-chars = <any OCTET except cookie-sep>
OCTET = <any 8-bit sequence of data>
LWS = SP | HT
- NUL = <US-ASCII NUL, null control character (0)>
CR = <US-ASCII CR, carriage return (13)>
LF = <US-ASCII LF, linefeed (10)>
SP = <US-ASCII SP, space (32)>
@@ -1472,6 +1546,8 @@ bool CookieService::CanSetCookie(
| "Max-Age" "=" value
| "Comment" "=" value
| "Version" "=" value
+ | "Partitioned"
+ | "SameSite"
| "Secure"
| "HttpOnly"
@@ -1479,7 +1555,6 @@ bool CookieService::CanSetCookie(
// clang-format on
// helper functions for GetTokenValue
-static inline bool isnull(char c) { return c == 0; }
static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
static inline bool isterminator(char c) { return c == '\n' || c == '\r'; }
static inline bool isvalueseparator(char c) {
@@ -1507,7 +1582,7 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
++aIter;
}
start = aIter;
- while (aIter != aEndIter && !isnull(*aIter) && !istokenseparator(*aIter)) {
+ while (aIter != aEndIter && !istokenseparator(*aIter)) {
++aIter;
}
@@ -1530,7 +1605,7 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
// process <token>
// just look for ';' to terminate ('=' allowed)
- while (aIter != aEndIter && !isnull(*aIter) && !isvalueseparator(*aIter)) {
+ while (aIter != aEndIter && !isvalueseparator(*aIter)) {
++aIter;
}
@@ -1549,6 +1624,21 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
// if on terminator, increment past & return true to process new cookie
if (isterminator(*aIter)) {
++aIter;
+ while (aIter != aEndIter && isvalueseparator(*aIter)) {
+ ++aIter;
+ }
+ nsACString::const_char_iterator end = aIter - 1;
+ if (!isterminator(*end)) {
+ // The cookie isn't valid because we have multiple terminators or
+ // a terminator followed by a value separator. Add those invalid
+ // characters to the cookie string or value so it will be rejected.
+ if (aEqualsFound) {
+ aTokenString.Rebind(start, end);
+ } else {
+ aTokenValue.Rebind(start, end);
+ }
+ return false;
+ }
return true;
}
// fall-through: aIter is on ';', increment and return false
@@ -1570,6 +1660,17 @@ static inline void SetSameSiteAttribute(CookieStruct& aCookieData,
aCookieData.rawSameSite() = aValue;
}
+// Tests for control characters, defined by RFC 5234 to be %x00-1F / %x7F.
+// An exception is made for HTAB as the cookie spec treats that as whitespace.
+static bool ContainsControlChars(const nsACString& aString) {
+ const auto* start = aString.BeginReading();
+ const auto* end = aString.EndReading();
+
+ return std::find_if(start, end, [](unsigned char c) {
+ return (c <= 0x1F && c != 0x09) || c == 0x7F;
+ }) != end;
+}
+
// Parses attributes from cookie header. expires/max-age attributes aren't
// folded into the cookie struct here, because we don't know which one to use
// until we've parsed the header.
@@ -1627,6 +1728,14 @@ bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
equalsFound);
+ if (ContainsControlChars(tokenString) || ContainsControlChars(tokenValue)) {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_REJECTION_CATEGORY,
+ "CookieRejectedInvalidCharAttributes"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+ return newCookie;
+ }
+
// decide which attribute we have, and copy the string
if (tokenString.LowerCaseEqualsLiteral(kPath)) {
aCookieData.path() = tokenValue;
@@ -1967,25 +2076,6 @@ bool CookieService::CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
return true;
}
-// static
-bool CookieService::CheckHiddenPrefix(CookieStruct& aCookieData) {
- // If a cookie is nameless, then its value must not start with
- // `__Host-` or `__Secure-`
- if (!aCookieData.name().IsEmpty()) {
- return true;
- }
-
- if (StringBeginsWith(aCookieData.value(), "__Host-"_ns)) {
- return false;
- }
-
- if (StringBeginsWith(aCookieData.value(), "__Secure-"_ns)) {
- return false;
- }
-
- return true;
-}
-
namespace {
nsAutoCString GetPathFromURI(nsIURI* aHostURI) {
// strip down everything after the last slash to get the path,
@@ -2053,15 +2143,10 @@ bool CookieService::CheckPath(CookieStruct& aCookieData,
// regularized and validated the CookieStruct values!
bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
bool aSecureRequest) {
- static const char kSecure[] = "__Secure-";
- static const char kHost[] = "__Host-";
- static const int kSecureLen = sizeof(kSecure) - 1;
- static const int kHostLen = sizeof(kHost) - 1;
-
- bool isSecure = strncmp(aCookieData.name().get(), kSecure, kSecureLen) == 0;
- bool isHost = strncmp(aCookieData.name().get(), kHost, kHostLen) == 0;
+ bool hasSecurePrefix = HasSecurePrefix(aCookieData.name());
+ bool hasHostPrefix = HasHostPrefix(aCookieData.name());
- if (!isSecure && !isHost) {
+ if (!hasSecurePrefix && !hasHostPrefix) {
// not one of the magic prefixes: carry on
return true;
}
@@ -2072,7 +2157,7 @@ bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
return false;
}
- if (isHost) {
+ if (hasHostPrefix) {
// The host prefix requires that the path is "/" and that the cookie
// had no domain attribute. CheckDomain() and CheckPath() MUST be run
// first to make sure invalid attributes are rejected and to regularlize