1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "mozilla/AppShutdown.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/ContentBlockingNotifier.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/nsMixedContentBlocker.h"
15 #include "mozilla/dom/Promise.h"
16 #include "mozilla/net/CookieJarSettings.h"
17 #include "mozilla/net/CookiePersistentStorage.h"
18 #include "mozilla/net/CookiePrivateStorage.h"
19 #include "mozilla/net/CookieService.h"
20 #include "mozilla/net/CookieServiceChild.h"
21 #include "mozilla/net/HttpBaseChannel.h"
22 #include "mozilla/net/NeckoCommon.h"
23 #include "mozilla/StaticPrefs_network.h"
24 #include "mozilla/StoragePrincipalHelper.h"
25 #include "mozilla/Telemetry.h"
26 #include "mozIThirdPartyUtil.h"
27 #include "nsICookiePermission.h"
28 #include "nsIConsoleReportCollector.h"
29 #include "nsIEffectiveTLDService.h"
30 #include "nsIIDNService.h"
31 #include "nsIScriptError.h"
34 #include "nsIWebProgressListener.h"
35 #include "nsNetUtil.h"
37 #include "ThirdPartyUtil.h"
39 using namespace mozilla::dom
;
43 uint32_t MakeCookieBehavior(uint32_t aCookieBehavior
) {
44 bool isFirstPartyIsolated
= OriginAttributes::IsFirstPartyEnabled();
46 if (isFirstPartyIsolated
&&
48 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
) {
49 return nsICookieService::BEHAVIOR_REJECT_TRACKER
;
51 return aCookieBehavior
;
55 Enables sanitizeOnShutdown cleaning prefs and disables the
56 network.cookie.lifetimePolicy
58 void MigrateCookieLifetimePrefs() {
59 // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY
60 // are not available anymore 2 = ACCEPT_SESSION
61 if (mozilla::Preferences::GetInt("network.cookie.lifetimePolicy") != 2) {
64 if (!mozilla::Preferences::GetBool("privacy.sanitize.sanitizeOnShutdown")) {
65 mozilla::Preferences::SetBool("privacy.sanitize.sanitizeOnShutdown", true);
66 // To avoid clearing categories that the user did not intend to clear
67 mozilla::Preferences::SetBool("privacy.clearOnShutdown.history", false);
68 mozilla::Preferences::SetBool("privacy.clearOnShutdown.formdata", false);
69 mozilla::Preferences::SetBool("privacy.clearOnShutdown.downloads", false);
70 mozilla::Preferences::SetBool("privacy.clearOnShutdown.sessions", false);
71 mozilla::Preferences::SetBool("privacy.clearOnShutdown.siteSettings",
74 mozilla::Preferences::SetBool("privacy.clearOnShutdown.cookies", true);
75 mozilla::Preferences::SetBool("privacy.clearOnShutdown.cache", true);
76 mozilla::Preferences::SetBool("privacy.clearOnShutdown.offlineApps", true);
77 mozilla::Preferences::ClearUser("network.cookie.lifetimePolicy");
80 } // anonymous namespace
83 uint32_t nsICookieManager::GetCookieBehavior(bool aIsPrivate
) {
85 // To sync the cookieBehavior pref between regular and private mode in ETP
86 // custom mode, we will return the regular cookieBehavior pref for private
88 // 1. The regular cookieBehavior pref has a non-default value.
89 // 2. And the private cookieBehavior pref has a default value.
90 // Also, this can cover the migration case where the user has a non-default
91 // cookieBehavior before the private cookieBehavior was introduced. The
92 // getter here will directly return the regular cookieBehavior, so that the
93 // cookieBehavior for private mode is consistent.
94 if (mozilla::Preferences::HasUserValue(
95 "network.cookie.cookieBehavior.pbmode")) {
96 return MakeCookieBehavior(
97 mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
100 if (mozilla::Preferences::HasUserValue("network.cookie.cookieBehavior")) {
101 return MakeCookieBehavior(
102 mozilla::StaticPrefs::network_cookie_cookieBehavior());
105 return MakeCookieBehavior(
106 mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
108 return MakeCookieBehavior(
109 mozilla::StaticPrefs::network_cookie_cookieBehavior());
115 /******************************************************************************
116 * CookieService impl:
117 * useful types & constants
118 ******************************************************************************/
120 static StaticRefPtr
<CookieService
> gCookieService
;
122 constexpr auto CONSOLE_SAMESITE_CATEGORY
= "cookieSameSite"_ns
;
123 constexpr auto CONSOLE_OVERSIZE_CATEGORY
= "cookiesOversize"_ns
;
124 constexpr auto CONSOLE_REJECTION_CATEGORY
= "cookiesRejection"_ns
;
125 constexpr auto SAMESITE_MDN_URL
=
126 "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/"
131 void ComposeCookieString(nsTArray
<Cookie
*>& aCookieList
,
132 nsACString
& aCookieString
) {
133 for (Cookie
* cookie
: aCookieList
) {
134 // check if we have anything to write
135 if (!cookie
->Name().IsEmpty() || !cookie
->Value().IsEmpty()) {
136 // if we've already added a cookie to the return list, append a "; " so
137 // that subsequent cookies are delimited in the final list.
138 if (!aCookieString
.IsEmpty()) {
139 aCookieString
.AppendLiteral("; ");
142 if (!cookie
->Name().IsEmpty()) {
143 // we have a name and value - write both
144 aCookieString
+= cookie
->Name() + "="_ns
+ cookie
->Value();
147 aCookieString
+= cookie
->Value();
153 // Return false if the cookie should be ignored for the current channel.
154 bool ProcessSameSiteCookieForForeignRequest(nsIChannel
* aChannel
,
156 bool aIsSafeTopLevelNav
,
157 bool aHadCrossSiteRedirects
,
158 bool aLaxByDefault
) {
159 // If it's a cross-site request and the cookie is same site only (strict)
161 if (aCookie
->SameSite() == nsICookie::SAMESITE_STRICT
) {
165 // Explicit SameSite=None cookies are always processed. When laxByDefault
166 // is OFF then so are default cookies.
167 if (aCookie
->SameSite() == nsICookie::SAMESITE_NONE
||
168 (!aLaxByDefault
&& aCookie
->IsDefaultSameSite())) {
172 // Lax-by-default cookies are processed even with an intermediate
173 // cross-site redirect (they are treated like aIsSameSiteForeign = false).
174 if (aLaxByDefault
&& aCookie
->IsDefaultSameSite() && aHadCrossSiteRedirects
&&
176 network_cookie_sameSite_laxByDefault_allowBoomerangRedirect()) {
180 int64_t currentTimeInUsec
= PR_Now();
182 // 2 minutes of tolerance for 'SameSite=Lax by default' for cookies set
183 // without a SameSite value when used for unsafe http methods.
184 if (aLaxByDefault
&& aCookie
->IsDefaultSameSite() &&
185 StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 &&
186 currentTimeInUsec
- aCookie
->CreationTime() <=
187 (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() *
189 !NS_IsSafeMethodNav(aChannel
)) {
193 MOZ_ASSERT((aLaxByDefault
&& aCookie
->IsDefaultSameSite()) ||
194 aCookie
->SameSite() == nsICookie::SAMESITE_LAX
);
195 // We only have SameSite=Lax or lax-by-default cookies at this point. These
196 // are processed only if it's a top-level navigation
197 return aIsSafeTopLevelNav
;
202 /******************************************************************************
203 * CookieService impl:
204 * singleton instance ctor/dtor methods
205 ******************************************************************************/
207 already_AddRefed
<nsICookieService
> CookieService::GetXPCOMSingleton() {
208 if (IsNeckoChild()) {
209 return CookieServiceChild::GetSingleton();
212 return GetSingleton();
215 already_AddRefed
<CookieService
> CookieService::GetSingleton() {
216 NS_ASSERTION(!IsNeckoChild(), "not a parent process");
218 if (gCookieService
) {
219 return do_AddRef(gCookieService
);
222 // Create a new singleton CookieService.
223 // We AddRef only once since XPCOM has rules about the ordering of module
224 // teardowns - by the time our module destructor is called, it's too late to
225 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
226 // cycles have already been completed and would result in serious leaks.
228 // TODO: Verify what is the earliest point in time during shutdown where
229 // we can deny the creation of the CookieService as a whole.
230 gCookieService
= new CookieService();
231 if (gCookieService
) {
232 if (NS_SUCCEEDED(gCookieService
->Init())) {
233 ClearOnShutdown(&gCookieService
);
235 gCookieService
= nullptr;
239 return do_AddRef(gCookieService
);
242 /******************************************************************************
243 * CookieService impl:
245 ******************************************************************************/
247 NS_IMPL_ISUPPORTS(CookieService
, nsICookieService
, nsICookieManager
,
248 nsIObserver
, nsISupportsWeakReference
, nsIMemoryReporter
)
250 CookieService::CookieService() = default;
252 nsresult
CookieService::Init() {
254 mTLDService
= do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
, &rv
);
255 NS_ENSURE_SUCCESS(rv
, rv
);
257 mIDNService
= do_GetService(NS_IDNSERVICE_CONTRACTID
, &rv
);
258 NS_ENSURE_SUCCESS(rv
, rv
);
260 mThirdPartyUtil
= do_GetService(THIRDPARTYUTIL_CONTRACTID
);
261 NS_ENSURE_SUCCESS(rv
, rv
);
263 // Init our default, and possibly private CookieStorages.
264 InitCookieStorages();
266 // Migrate network.cookie.lifetimePolicy pref to sanitizeOnShutdown prefs
267 MigrateCookieLifetimePrefs();
269 RegisterWeakMemoryReporter(this);
271 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
273 os
->AddObserver(this, "profile-before-change", true);
274 os
->AddObserver(this, "profile-do-change", true);
275 os
->AddObserver(this, "last-pb-context-exited", true);
280 void CookieService::InitCookieStorages() {
281 NS_ASSERTION(!mPersistentStorage
, "already have a default CookieStorage");
282 NS_ASSERTION(!mPrivateStorage
, "already have a private CookieStorage");
284 // Create two new CookieStorages. If we are in or beyond our observed
285 // shutdown phase, just be non-persistent.
286 if (MOZ_UNLIKELY(StaticPrefs::network_cookie_noPersistentStorage() ||
287 AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown
))) {
288 mPersistentStorage
= CookiePrivateStorage::Create();
290 mPersistentStorage
= CookiePersistentStorage::Create();
293 mPrivateStorage
= CookiePrivateStorage::Create();
296 void CookieService::CloseCookieStorages() {
297 // return if we already closed
298 if (!mPersistentStorage
) {
302 // Let's nullify both storages before calling Close().
303 RefPtr
<CookieStorage
> privateStorage
;
304 privateStorage
.swap(mPrivateStorage
);
306 RefPtr
<CookieStorage
> persistentStorage
;
307 persistentStorage
.swap(mPersistentStorage
);
309 privateStorage
->Close();
310 persistentStorage
->Close();
313 CookieService::~CookieService() {
314 CloseCookieStorages();
316 UnregisterWeakMemoryReporter(this);
318 gCookieService
= nullptr;
322 CookieService::Observe(nsISupports
* /*aSubject*/, const char* aTopic
,
323 const char16_t
* /*aData*/) {
325 if (!strcmp(aTopic
, "profile-before-change")) {
326 // The profile is about to change,
327 // or is going away because the application is shutting down.
329 // Close the default DB connection and null out our CookieStorages before
331 CloseCookieStorages();
333 } else if (!strcmp(aTopic
, "profile-do-change")) {
334 NS_ASSERTION(!mPersistentStorage
, "shouldn't have a default CookieStorage");
335 NS_ASSERTION(!mPrivateStorage
, "shouldn't have a private CookieStorage");
337 // the profile has already changed; init the db from the new location.
338 // if we are in the private browsing state, however, we do not want to read
339 // data into it - we should instead put it into the default state, so it's
340 // ready for us if and when we switch back to it.
341 InitCookieStorages();
343 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
344 // Flush all the cookies stored by private browsing contexts
345 OriginAttributesPattern pattern
;
346 pattern
.mPrivateBrowsingId
.Construct(1);
347 RemoveCookiesWithOriginAttributes(pattern
, ""_ns
);
348 mPrivateStorage
= CookiePrivateStorage::Create();
355 CookieService::GetCookieBehavior(bool aIsPrivate
, uint32_t* aCookieBehavior
) {
356 NS_ENSURE_ARG_POINTER(aCookieBehavior
);
357 *aCookieBehavior
= nsICookieManager::GetCookieBehavior(aIsPrivate
);
362 CookieService::GetCookieStringFromDocument(Document
* aDocument
,
363 nsACString
& aCookie
) {
364 NS_ENSURE_ARG(aDocument
);
370 if (!IsInitialized()) {
374 nsCOMPtr
<nsIPrincipal
> principal
= aDocument
->EffectiveCookiePrincipal();
376 if (!CookieCommons::IsSchemeSupported(principal
)) {
380 CookieStorage
* storage
= PickStorage(principal
->OriginAttributesRef());
382 nsAutoCString baseDomain
;
383 rv
= CookieCommons::GetBaseDomain(principal
, baseDomain
);
384 if (NS_WARN_IF(NS_FAILED(rv
))) {
388 nsAutoCString hostFromURI
;
389 rv
= nsContentUtils::GetHostOrIPv6WithBrackets(principal
, hostFromURI
);
390 if (NS_WARN_IF(NS_FAILED(rv
))) {
394 nsAutoCString pathFromURI
;
395 rv
= principal
->GetFilePath(pathFromURI
);
396 if (NS_WARN_IF(NS_FAILED(rv
))) {
400 int64_t currentTimeInUsec
= PR_Now();
401 int64_t currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
403 const nsTArray
<RefPtr
<Cookie
>>* cookies
=
404 storage
->GetCookiesFromHost(baseDomain
, principal
->OriginAttributesRef());
409 // check if the nsIPrincipal is using an https secure protocol.
410 // if it isn't, then we can't send a secure cookie over the connection.
411 bool potentiallyTurstworthy
= principal
->GetIsOriginPotentiallyTrustworthy();
413 bool thirdParty
= true;
414 nsPIDOMWindowInner
* innerWindow
= aDocument
->GetInnerWindow();
415 // in gtests we don't have a window, let's consider those requests as 3rd
418 ThirdPartyUtil
* thirdPartyUtil
= ThirdPartyUtil::GetInstance();
420 if (thirdPartyUtil
) {
421 Unused
<< thirdPartyUtil
->IsThirdPartyWindow(
422 innerWindow
->GetOuterWindow(), nullptr, &thirdParty
);
427 nsTArray
<Cookie
*> cookieList
;
429 // iterate the cookies!
430 for (Cookie
* cookie
: *cookies
) {
431 // check the host, since the base domain lookup is conservative.
432 if (!CookieCommons::DomainMatches(cookie
, hostFromURI
)) {
436 // if the cookie is httpOnly and it's not going directly to the HTTP
437 // connection, don't send it
438 if (cookie
->IsHttpOnly()) {
443 !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie
)) {
447 // if the cookie is secure and the host scheme isn't, we can't send it
448 if (cookie
->IsSecure() && !potentiallyTurstworthy
) {
452 // if the nsIURI path doesn't match the cookie path, don't send it back
453 if (!CookieCommons::PathMatches(cookie
, pathFromURI
)) {
457 // check if the cookie has expired
458 if (cookie
->Expiry() <= currentTime
) {
462 // all checks passed - add to list and check if lastAccessed stamp needs
464 cookieList
.AppendElement(cookie
);
465 if (cookie
->IsStale()) {
470 if (cookieList
.IsEmpty()) {
474 // update lastAccessed timestamps. we only do this if the timestamp is stale
475 // by a certain amount, to avoid thrashing the db during pageload.
477 storage
->StaleCookies(cookieList
, currentTimeInUsec
);
480 // return cookies in order of path length; longest to shortest.
481 // this is required per RFC2109. if cookies match in length,
482 // then sort by creation time (see bug 236772).
483 cookieList
.Sort(CompareCookiesForSending());
484 ComposeCookieString(cookieList
, aCookie
);
490 CookieService::GetCookieStringFromHttp(nsIURI
* aHostURI
, nsIChannel
* aChannel
,
491 nsACString
& aCookieString
) {
492 NS_ENSURE_ARG(aHostURI
);
493 NS_ENSURE_ARG(aChannel
);
495 aCookieString
.Truncate();
497 if (!CookieCommons::IsSchemeSupported(aHostURI
)) {
501 uint32_t rejectedReason
= 0;
502 ThirdPartyAnalysisResult result
= mThirdPartyUtil
->AnalyzeChannel(
503 aChannel
, false, aHostURI
, nullptr, &rejectedReason
);
505 OriginAttributes attrs
;
506 StoragePrincipalHelper::GetOriginAttributes(
507 aChannel
, attrs
, StoragePrincipalHelper::eStorageAccessPrincipal
);
509 bool isSafeTopLevelNav
= CookieCommons::IsSafeTopLevelNav(aChannel
);
510 bool hadCrossSiteRedirects
= false;
511 bool isSameSiteForeign
= CookieCommons::IsSameSiteForeign(
512 aChannel
, aHostURI
, &hadCrossSiteRedirects
);
514 AutoTArray
<Cookie
*, 8> foundCookieList
;
516 aHostURI
, aChannel
, result
.contains(ThirdPartyAnalysis::IsForeign
),
517 result
.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource
),
518 result
.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource
),
519 result
.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted
),
520 rejectedReason
, isSafeTopLevelNav
, isSameSiteForeign
,
521 hadCrossSiteRedirects
, true, false, attrs
, foundCookieList
);
523 ComposeCookieString(foundCookieList
, aCookieString
);
525 if (!aCookieString
.IsEmpty()) {
526 COOKIE_LOGSUCCESS(GET_COOKIE
, aHostURI
, aCookieString
, nullptr, false);
532 CookieService::SetCookieStringFromDocument(Document
* aDocument
,
533 const nsACString
& aCookieString
) {
534 NS_ENSURE_ARG(aDocument
);
536 if (!IsInitialized()) {
540 nsCOMPtr
<nsIURI
> documentURI
;
541 nsAutoCString baseDomain
;
542 OriginAttributes attrs
;
544 int64_t currentTimeInUsec
= PR_Now();
546 // This function is executed in this context, I don't need to keep objects
548 auto hasExistingCookiesLambda
= [&](const nsACString
& aBaseDomain
,
549 const OriginAttributes
& aAttrs
) {
550 CookieStorage
* storage
= PickStorage(aAttrs
);
551 return !!storage
->CountCookiesFromHost(aBaseDomain
,
552 aAttrs
.mPrivateBrowsingId
);
555 RefPtr
<Cookie
> cookie
= CookieCommons::CreateCookieFromDocument(
556 aDocument
, aCookieString
, currentTimeInUsec
, mTLDService
, mThirdPartyUtil
,
557 hasExistingCookiesLambda
, getter_AddRefs(documentURI
), baseDomain
, attrs
);
562 bool thirdParty
= true;
563 nsPIDOMWindowInner
* innerWindow
= aDocument
->GetInnerWindow();
564 // in gtests we don't have a window, let's consider those requests as 3rd
567 ThirdPartyUtil
* thirdPartyUtil
= ThirdPartyUtil::GetInstance();
569 if (thirdPartyUtil
) {
570 Unused
<< thirdPartyUtil
->IsThirdPartyWindow(
571 innerWindow
->GetOuterWindow(), nullptr, &thirdParty
);
576 !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie
)) {
580 nsCOMPtr
<nsIConsoleReportCollector
> crc
=
581 do_QueryInterface(aDocument
->GetChannel());
583 // add the cookie to the list. AddCookie() takes care of logging.
584 PickStorage(attrs
)->AddCookie(crc
, baseDomain
, attrs
, cookie
,
585 currentTimeInUsec
, documentURI
, aCookieString
,
591 CookieService::SetCookieStringFromHttp(nsIURI
* aHostURI
,
592 const nsACString
& aCookieHeader
,
593 nsIChannel
* aChannel
) {
594 NS_ENSURE_ARG(aHostURI
);
595 NS_ENSURE_ARG(aChannel
);
597 if (!IsInitialized()) {
601 if (!CookieCommons::IsSchemeSupported(aHostURI
)) {
605 uint32_t rejectedReason
= 0;
606 ThirdPartyAnalysisResult result
= mThirdPartyUtil
->AnalyzeChannel(
607 aChannel
, false, aHostURI
, nullptr, &rejectedReason
);
609 OriginAttributes attrs
;
610 StoragePrincipalHelper::GetOriginAttributes(
611 aChannel
, attrs
, StoragePrincipalHelper::eStorageAccessPrincipal
);
613 // get the base domain for the host URI.
614 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
615 // file:// URI's (i.e. with an empty host) are allowed, but any other
616 // scheme must have a non-empty host. A trailing dot in the host
618 bool requireHostMatch
;
619 nsAutoCString baseDomain
;
620 nsresult rv
= CookieCommons::GetBaseDomain(mTLDService
, aHostURI
, baseDomain
,
623 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
624 "couldn't get base domain from URI");
628 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
=
629 CookieCommons::GetCookieJarSettings(aChannel
);
631 nsAutoCString hostFromURI
;
632 nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI
, hostFromURI
);
634 nsAutoCString baseDomainFromURI
;
635 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, hostFromURI
,
637 NS_ENSURE_SUCCESS(rv
, NS_OK
);
639 CookieStorage
* storage
= PickStorage(attrs
);
641 // check default prefs
642 uint32_t priorCookieCount
= storage
->CountCookiesFromHost(
643 baseDomainFromURI
, attrs
.mPrivateBrowsingId
);
645 nsCOMPtr
<nsIConsoleReportCollector
> crc
= do_QueryInterface(aChannel
);
647 CookieStatus cookieStatus
= CheckPrefs(
648 crc
, cookieJarSettings
, aHostURI
,
649 result
.contains(ThirdPartyAnalysis::IsForeign
),
650 result
.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource
),
651 result
.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource
),
652 result
.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted
),
653 aCookieHeader
, priorCookieCount
, attrs
, &rejectedReason
);
655 MOZ_ASSERT_IF(rejectedReason
, cookieStatus
== STATUS_REJECTED
);
657 // fire a notification if third party or if cookie was rejected
658 // (but not if there was an error)
659 switch (cookieStatus
) {
660 case STATUS_REJECTED
:
661 CookieCommons::NotifyRejected(aHostURI
, aChannel
, rejectedReason
,
663 return NS_OK
; // Stop here
664 case STATUS_REJECTED_WITH_ERROR
:
665 CookieCommons::NotifyRejected(aHostURI
, aChannel
, rejectedReason
,
668 case STATUS_ACCEPTED
: // Fallthrough
669 case STATUS_ACCEPT_SESSION
:
670 NotifyAccepted(aChannel
);
676 bool addonAllowsLoad
= false;
677 nsCOMPtr
<nsIURI
> channelURI
;
678 NS_GetFinalChannelURI(aChannel
, getter_AddRefs(channelURI
));
679 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
680 addonAllowsLoad
= BasePrincipal::Cast(loadInfo
->TriggeringPrincipal())
681 ->AddonAllowsLoad(channelURI
);
683 bool isForeignAndNotAddon
= false;
684 if (!addonAllowsLoad
) {
685 mThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aHostURI
,
686 &isForeignAndNotAddon
);
689 nsCString
cookieHeader(aCookieHeader
);
691 bool moreCookieToRead
= true;
693 // process each cookie in the header
694 while (moreCookieToRead
) {
695 CookieStruct cookieData
;
696 bool canSetCookie
= false;
698 moreCookieToRead
= CanSetCookie(
699 aHostURI
, baseDomain
, cookieData
, requireHostMatch
, cookieStatus
,
700 cookieHeader
, true, isForeignAndNotAddon
, crc
, canSetCookie
);
706 // check permissions from site permission list.
707 if (!CookieCommons::CheckCookiePermission(aChannel
, cookieData
)) {
708 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
709 "cookie rejected by permission manager");
710 CookieCommons::NotifyRejected(
712 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
,
714 CookieLogging::LogMessageToConsole(
715 crc
, aHostURI
, nsIScriptError::warningFlag
,
716 CONSOLE_REJECTION_CATEGORY
, "CookieRejectedByPermissionManager"_ns
,
717 AutoTArray
<nsString
, 1>{
718 NS_ConvertUTF8toUTF16(cookieData
.name()),
723 // create a new Cookie
724 RefPtr
<Cookie
> cookie
= Cookie::Create(cookieData
, attrs
);
727 int64_t currentTimeInUsec
= PR_Now();
728 cookie
->SetLastAccessed(currentTimeInUsec
);
729 cookie
->SetCreationTime(
730 Cookie::GenerateUniqueCreationTime(currentTimeInUsec
));
732 // add the cookie to the list. AddCookie() takes care of logging.
733 storage
->AddCookie(crc
, baseDomain
, attrs
, cookie
, currentTimeInUsec
,
734 aHostURI
, aCookieHeader
, true);
740 void CookieService::NotifyAccepted(nsIChannel
* aChannel
) {
741 ContentBlockingNotifier::OnDecision(
742 aChannel
, ContentBlockingNotifier::BlockingDecision::eAllow
, 0);
745 /******************************************************************************
747 * public transaction helper impl
748 ******************************************************************************/
751 CookieService::RunInTransaction(nsICookieTransactionCallback
* aCallback
) {
752 NS_ENSURE_ARG(aCallback
);
754 if (!IsInitialized()) {
755 return NS_ERROR_NOT_AVAILABLE
;
758 mPersistentStorage
->EnsureInitialized();
759 return mPersistentStorage
->RunInTransaction(aCallback
);
762 /******************************************************************************
763 * nsICookieManager impl:
765 ******************************************************************************/
768 CookieService::RemoveAll() {
769 if (!IsInitialized()) {
770 return NS_ERROR_NOT_AVAILABLE
;
773 mPersistentStorage
->EnsureInitialized();
774 mPersistentStorage
->RemoveAll();
779 CookieService::GetCookies(nsTArray
<RefPtr
<nsICookie
>>& aCookies
) {
780 if (!IsInitialized()) {
781 return NS_ERROR_NOT_AVAILABLE
;
784 mPersistentStorage
->EnsureInitialized();
786 // We expose only non-private cookies.
787 mPersistentStorage
->GetCookies(aCookies
);
793 CookieService::GetSessionCookies(nsTArray
<RefPtr
<nsICookie
>>& aCookies
) {
794 if (!IsInitialized()) {
795 return NS_ERROR_NOT_AVAILABLE
;
798 mPersistentStorage
->EnsureInitialized();
800 // We expose only non-private cookies.
801 mPersistentStorage
->GetSessionCookies(aCookies
);
807 CookieService::Add(const nsACString
& aHost
, const nsACString
& aPath
,
808 const nsACString
& aName
, const nsACString
& aValue
,
809 bool aIsSecure
, bool aIsHttpOnly
, bool aIsSession
,
810 int64_t aExpiry
, JS::Handle
<JS::Value
> aOriginAttributes
,
811 int32_t aSameSite
, nsICookie::schemeType aSchemeMap
,
813 OriginAttributes attrs
;
815 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
816 return NS_ERROR_INVALID_ARG
;
819 return AddNative(aHost
, aPath
, aName
, aValue
, aIsSecure
, aIsHttpOnly
,
820 aIsSession
, aExpiry
, &attrs
, aSameSite
, aSchemeMap
);
823 NS_IMETHODIMP_(nsresult
)
824 CookieService::AddNative(const nsACString
& aHost
, const nsACString
& aPath
,
825 const nsACString
& aName
, const nsACString
& aValue
,
826 bool aIsSecure
, bool aIsHttpOnly
, bool aIsSession
,
827 int64_t aExpiry
, OriginAttributes
* aOriginAttributes
,
828 int32_t aSameSite
, nsICookie::schemeType aSchemeMap
) {
829 if (NS_WARN_IF(!aOriginAttributes
)) {
830 return NS_ERROR_FAILURE
;
833 if (!IsInitialized()) {
834 return NS_ERROR_NOT_AVAILABLE
;
837 // first, normalize the hostname, and fail if it contains illegal characters.
838 nsAutoCString
host(aHost
);
839 nsresult rv
= NormalizeHost(host
);
840 NS_ENSURE_SUCCESS(rv
, rv
);
842 // get the base domain for the host URI.
843 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
844 nsAutoCString baseDomain
;
845 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
846 NS_ENSURE_SUCCESS(rv
, rv
);
848 int64_t currentTimeInUsec
= PR_Now();
849 CookieKey key
= CookieKey(baseDomain
, *aOriginAttributes
);
851 CookieStruct
cookieData(nsCString(aName
), nsCString(aValue
), nsCString(aHost
),
852 nsCString(aPath
), aExpiry
, currentTimeInUsec
,
853 Cookie::GenerateUniqueCreationTime(currentTimeInUsec
),
854 aIsHttpOnly
, aIsSession
, aIsSecure
, aSameSite
,
855 aSameSite
, aSchemeMap
);
857 RefPtr
<Cookie
> cookie
= Cookie::Create(cookieData
, key
.mOriginAttributes
);
860 CookieStorage
* storage
= PickStorage(*aOriginAttributes
);
861 storage
->AddCookie(nullptr, baseDomain
, *aOriginAttributes
, cookie
,
862 currentTimeInUsec
, nullptr, VoidCString(), true);
866 nsresult
CookieService::Remove(const nsACString
& aHost
,
867 const OriginAttributes
& aAttrs
,
868 const nsACString
& aName
,
869 const nsACString
& aPath
) {
870 // first, normalize the hostname, and fail if it contains illegal characters.
871 nsAutoCString
host(aHost
);
872 nsresult rv
= NormalizeHost(host
);
873 NS_ENSURE_SUCCESS(rv
, rv
);
875 nsAutoCString baseDomain
;
876 if (!host
.IsEmpty()) {
877 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
878 NS_ENSURE_SUCCESS(rv
, rv
);
881 if (!IsInitialized()) {
882 return NS_ERROR_NOT_AVAILABLE
;
885 CookieStorage
* storage
= PickStorage(aAttrs
);
886 storage
->RemoveCookie(baseDomain
, aAttrs
, host
, PromiseFlatCString(aName
),
887 PromiseFlatCString(aPath
));
893 CookieService::Remove(const nsACString
& aHost
, const nsACString
& aName
,
894 const nsACString
& aPath
,
895 JS::Handle
<JS::Value
> aOriginAttributes
, JSContext
* aCx
) {
896 OriginAttributes attrs
;
898 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
899 return NS_ERROR_INVALID_ARG
;
902 return RemoveNative(aHost
, aName
, aPath
, &attrs
);
905 NS_IMETHODIMP_(nsresult
)
906 CookieService::RemoveNative(const nsACString
& aHost
, const nsACString
& aName
,
907 const nsACString
& aPath
,
908 OriginAttributes
* aOriginAttributes
) {
909 if (NS_WARN_IF(!aOriginAttributes
)) {
910 return NS_ERROR_FAILURE
;
913 nsresult rv
= Remove(aHost
, *aOriginAttributes
, aName
, aPath
);
914 if (NS_WARN_IF(NS_FAILED(rv
))) {
921 void CookieService::GetCookiesForURI(
922 nsIURI
* aHostURI
, nsIChannel
* aChannel
, bool aIsForeign
,
923 bool aIsThirdPartyTrackingResource
,
924 bool aIsThirdPartySocialTrackingResource
,
925 bool aStorageAccessPermissionGranted
, uint32_t aRejectedReason
,
926 bool aIsSafeTopLevelNav
, bool aIsSameSiteForeign
,
927 bool aHadCrossSiteRedirects
, bool aHttpBound
,
928 bool aAllowSecureCookiesToInsecureOrigin
,
929 const OriginAttributes
& aOriginAttrs
, nsTArray
<Cookie
*>& aCookieList
) {
930 NS_ASSERTION(aHostURI
, "null host!");
932 if (!CookieCommons::IsSchemeSupported(aHostURI
)) {
936 if (!IsInitialized()) {
940 CookieStorage
* storage
= PickStorage(aOriginAttrs
);
942 // get the base domain, host, and path from the URI.
943 // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
944 // file:// URI's (i.e. with an empty host) are allowed, but any other
945 // scheme must have a non-empty host. A trailing dot in the host
947 bool requireHostMatch
;
948 nsAutoCString baseDomain
;
949 nsAutoCString hostFromURI
;
950 nsAutoCString pathFromURI
;
951 nsresult rv
= CookieCommons::GetBaseDomain(mTLDService
, aHostURI
, baseDomain
,
953 if (NS_SUCCEEDED(rv
)) {
954 rv
= nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI
, hostFromURI
);
956 if (NS_SUCCEEDED(rv
)) {
957 rv
= aHostURI
->GetFilePath(pathFromURI
);
960 COOKIE_LOGFAILURE(GET_COOKIE
, aHostURI
, VoidCString(),
961 "invalid host/path from URI");
965 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
=
966 CookieCommons::GetCookieJarSettings(aChannel
);
968 nsAutoCString
normalizedHostFromURI(hostFromURI
);
969 rv
= NormalizeHost(normalizedHostFromURI
);
970 NS_ENSURE_SUCCESS_VOID(rv
);
972 nsAutoCString baseDomainFromURI
;
973 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, normalizedHostFromURI
,
975 NS_ENSURE_SUCCESS_VOID(rv
);
977 // check default prefs
978 uint32_t rejectedReason
= aRejectedReason
;
979 uint32_t priorCookieCount
= storage
->CountCookiesFromHost(
980 baseDomainFromURI
, aOriginAttrs
.mPrivateBrowsingId
);
982 nsCOMPtr
<nsIConsoleReportCollector
> crc
= do_QueryInterface(aChannel
);
983 CookieStatus cookieStatus
= CheckPrefs(
984 crc
, cookieJarSettings
, aHostURI
, aIsForeign
,
985 aIsThirdPartyTrackingResource
, aIsThirdPartySocialTrackingResource
,
986 aStorageAccessPermissionGranted
, VoidCString(), priorCookieCount
,
987 aOriginAttrs
, &rejectedReason
);
989 MOZ_ASSERT_IF(rejectedReason
, cookieStatus
== STATUS_REJECTED
);
991 // for GetCookie(), we only fire acceptance/rejection notifications
992 // (but not if there was an error)
993 switch (cookieStatus
) {
994 case STATUS_REJECTED
:
995 // If we don't have any cookies from this host, fail silently.
996 if (priorCookieCount
) {
997 CookieCommons::NotifyRejected(aHostURI
, aChannel
, rejectedReason
,
1005 // Note: The following permissions logic is mirrored in
1006 // extensions::MatchPattern::MatchesCookie.
1007 // If it changes, please update that function, or file a bug for someone
1010 // check if aHostURI is using an https secure protocol.
1011 // if it isn't, then we can't send a secure cookie over the connection.
1012 // if SchemeIs fails, assume an insecure connection, to be on the safe side
1013 bool potentiallyTurstworthy
=
1014 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI
);
1016 int64_t currentTimeInUsec
= PR_Now();
1017 int64_t currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
1020 const nsTArray
<RefPtr
<Cookie
>>* cookies
=
1021 storage
->GetCookiesFromHost(baseDomain
, aOriginAttrs
);
1027 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1028 !nsContentUtils::IsURIInPrefList(
1029 aHostURI
, "network.cookie.sameSite.laxByDefault.disabledHosts");
1031 // iterate the cookies!
1032 for (Cookie
* cookie
: *cookies
) {
1033 // check the host, since the base domain lookup is conservative.
1034 if (!CookieCommons::DomainMatches(cookie
, hostFromURI
)) {
1038 // if the cookie is secure and the host scheme isn't, we avoid sending
1039 // cookie if possible. But for process synchronization purposes, we may want
1040 // the content process to know about the cookie (without it's value). In
1041 // which case we will wipe the value before sending
1042 if (cookie
->IsSecure() && !potentiallyTurstworthy
&&
1043 !aAllowSecureCookiesToInsecureOrigin
) {
1047 // if the cookie is httpOnly and it's not going directly to the HTTP
1048 // connection, don't send it
1049 if (cookie
->IsHttpOnly() && !aHttpBound
) {
1053 // if the nsIURI path doesn't match the cookie path, don't send it back
1054 if (!CookieCommons::PathMatches(cookie
, pathFromURI
)) {
1058 // check if the cookie has expired
1059 if (cookie
->Expiry() <= currentTime
) {
1063 if (aHttpBound
&& aIsSameSiteForeign
) {
1064 bool blockCookie
= !ProcessSameSiteCookieForForeignRequest(
1065 aChannel
, cookie
, aIsSafeTopLevelNav
, aHadCrossSiteRedirects
,
1069 if (aHadCrossSiteRedirects
) {
1070 CookieLogging::LogMessageToConsole(
1071 crc
, aHostURI
, nsIScriptError::warningFlag
,
1072 CONSOLE_REJECTION_CATEGORY
, "CookieBlockedCrossSiteRedirect"_ns
,
1073 AutoTArray
<nsString
, 1>{
1074 NS_ConvertUTF8toUTF16(cookie
->Name()),
1081 // all checks passed - add to list and check if lastAccessed stamp needs
1083 aCookieList
.AppendElement(cookie
);
1084 if (cookie
->IsStale()) {
1089 if (aCookieList
.IsEmpty()) {
1093 // Send a notification about the acceptance of the cookies now that we found
1095 NotifyAccepted(aChannel
);
1097 // update lastAccessed timestamps. we only do this if the timestamp is stale
1098 // by a certain amount, to avoid thrashing the db during pageload.
1100 storage
->StaleCookies(aCookieList
, currentTimeInUsec
);
1103 // return cookies in order of path length; longest to shortest.
1104 // this is required per RFC2109. if cookies match in length,
1105 // then sort by creation time (see bug 236772).
1106 aCookieList
.Sort(CompareCookiesForSending());
1109 static bool ContainsUnicodeChars(const nsCString
& str
) {
1110 const auto* start
= str
.BeginReading();
1111 const auto* end
= str
.EndReading();
1113 return std::find_if(start
, end
, [](unsigned char c
) { return c
>= 0x80; }) !=
1117 static void RecordUnicodeTelemetry(const CookieStruct
& cookieData
) {
1118 auto label
= Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::none
;
1119 if (ContainsUnicodeChars(cookieData
.name())) {
1120 label
= Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeName
;
1121 } else if (ContainsUnicodeChars(cookieData
.value())) {
1122 label
= Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeValue
;
1124 Telemetry::AccumulateCategorical(label
);
1127 // processes a single cookie, and returns true if there are more cookies
1129 bool CookieService::CanSetCookie(
1130 nsIURI
* aHostURI
, const nsACString
& aBaseDomain
, CookieStruct
& aCookieData
,
1131 bool aRequireHostMatch
, CookieStatus aStatus
, nsCString
& aCookieHeader
,
1132 bool aFromHttp
, bool aIsForeignAndNotAddon
, nsIConsoleReportCollector
* aCRC
,
1134 MOZ_ASSERT(aHostURI
);
1138 // init expiryTime such that session cookies won't prematurely expire
1139 aCookieData
.expiry() = INT64_MAX
;
1141 aCookieData
.schemeMap() = CookieCommons::URIToSchemeType(aHostURI
);
1143 // aCookieHeader is an in/out param to point to the next cookie, if
1144 // there is one. Save the present value for logging purposes
1145 nsCString
savedCookieHeader(aCookieHeader
);
1147 // newCookie says whether there are multiple cookies in the header;
1148 // so we can handle them separately.
1149 nsAutoCString expires
;
1150 nsAutoCString maxage
;
1151 bool acceptedByParser
= false;
1152 bool newCookie
= ParseAttributes(aCRC
, aHostURI
, aCookieHeader
, aCookieData
,
1153 expires
, maxage
, acceptedByParser
);
1154 if (!acceptedByParser
) {
1158 // Collect telemetry on how often secure cookies are set from non-secure
1159 // origins, and vice-versa.
1161 // 0 = nonsecure and "http:"
1162 // 1 = nonsecure and "https:"
1163 // 2 = secure and "http:"
1164 // 3 = secure and "https:"
1165 bool potentiallyTurstworthy
=
1166 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI
);
1168 int64_t currentTimeInUsec
= PR_Now();
1170 // calculate expiry time of cookie.
1171 aCookieData
.isSession() =
1172 GetExpiry(aCookieData
, expires
, maxage
,
1173 currentTimeInUsec
/ PR_USEC_PER_SEC
, aFromHttp
);
1174 if (aStatus
== STATUS_ACCEPT_SESSION
) {
1175 // force lifetime to session. note that the expiration time, if set above,
1176 // will still apply.
1177 aCookieData
.isSession() = true;
1180 // reject cookie if it's over the size limit, per RFC2109
1181 if (!CookieCommons::CheckNameAndValueSize(aCookieData
)) {
1182 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1183 "cookie too big (> 4kb)");
1185 AutoTArray
<nsString
, 2> params
= {
1186 NS_ConvertUTF8toUTF16(aCookieData
.name())};
1189 size
.AppendInt(kMaxBytesPerCookie
);
1190 params
.AppendElement(size
);
1192 CookieLogging::LogMessageToConsole(
1193 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_OVERSIZE_CATEGORY
,
1194 "CookieOversize"_ns
, params
);
1198 RecordUnicodeTelemetry(aCookieData
);
1200 if (!CookieCommons::CheckName(aCookieData
)) {
1201 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1202 "invalid name character");
1203 CookieLogging::LogMessageToConsole(
1204 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1205 "CookieRejectedInvalidCharName"_ns
,
1206 AutoTArray
<nsString
, 1>{
1207 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1212 // domain & path checks
1213 if (!CheckDomain(aCookieData
, aHostURI
, aBaseDomain
, aRequireHostMatch
)) {
1214 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1215 "failed the domain tests");
1216 CookieLogging::LogMessageToConsole(
1217 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1218 "CookieRejectedInvalidDomain"_ns
,
1219 AutoTArray
<nsString
, 1>{
1220 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1225 if (!CheckPath(aCookieData
, aCRC
, aHostURI
)) {
1226 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1227 "failed the path tests");
1231 if (!CheckHiddenPrefix(aCookieData
)) {
1232 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1233 "failed the CheckHiddenPrefix tests");
1234 CookieLogging::LogMessageToConsole(
1235 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1236 "CookieRejectedInvalidPrefix"_ns
,
1237 AutoTArray
<nsString
, 1>{
1238 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1243 // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
1244 if (!CheckPrefixes(aCookieData
, potentiallyTurstworthy
)) {
1245 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1246 "failed the prefix tests");
1247 CookieLogging::LogMessageToConsole(
1248 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1249 "CookieRejectedInvalidPrefix"_ns
,
1250 AutoTArray
<nsString
, 1>{
1251 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1256 if (!CookieCommons::CheckValue(aCookieData
)) {
1257 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1258 "invalid value character");
1259 CookieLogging::LogMessageToConsole(
1260 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1261 "CookieRejectedInvalidCharValue"_ns
,
1262 AutoTArray
<nsString
, 1>{
1263 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1268 // if the new cookie is httponly, make sure we're not coming from script
1269 if (!aFromHttp
&& aCookieData
.isHttpOnly()) {
1270 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1271 "cookie is httponly; coming from script");
1272 CookieLogging::LogMessageToConsole(
1273 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1274 "CookieRejectedHttpOnlyButFromScript"_ns
,
1275 AutoTArray
<nsString
, 1>{
1276 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1281 // If the new cookie is non-https and wants to set secure flag,
1282 // browser have to ignore this new cookie.
1283 // (draft-ietf-httpbis-cookie-alone section 3.1)
1284 if (aCookieData
.isSecure() && !potentiallyTurstworthy
) {
1285 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
1286 "non-https cookie can't set secure flag");
1287 CookieLogging::LogMessageToConsole(
1288 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
1289 "CookieRejectedSecureButNonHttps"_ns
,
1290 AutoTArray
<nsString
, 1>{
1291 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1296 // If the new cookie is same-site but in a cross site context,
1297 // browser must ignore the cookie.
1299 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1300 !nsContentUtils::IsURIInPrefList(
1301 aHostURI
, "network.cookie.sameSite.laxByDefault.disabledHosts");
1302 auto effectiveSameSite
=
1303 laxByDefault
? aCookieData
.sameSite() : aCookieData
.rawSameSite();
1304 if ((effectiveSameSite
!= nsICookie::SAMESITE_NONE
) &&
1305 aIsForeignAndNotAddon
) {
1306 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
1307 "failed the samesite tests");
1309 CookieLogging::LogMessageToConsole(
1310 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_SAMESITE_CATEGORY
,
1311 "CookieRejectedForNonSameSiteness"_ns
,
1312 AutoTArray
<nsString
, 1>{
1313 NS_ConvertUTF8toUTF16(aCookieData
.name()),
1322 /******************************************************************************
1323 * CookieService impl:
1324 * private cookie header parsing functions
1325 ******************************************************************************/
1328 // The following comment block elucidates the function of ParseAttributes.
1329 /******************************************************************************
1330 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1331 ** please note: this BNF deviates from both specifications, and reflects this
1332 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1334 ** Differences from RFC2109/2616 and explanations:
1336 The grammar described by this specification is word-based. Except
1337 where noted otherwise, linear white space (<LWS>) can be included
1338 between any two adjacent words (token or quoted-string), and
1339 between adjacent words and separators, without changing the
1340 interpretation of a field.
1341 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1343 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1344 common use inside values.
1346 3. tokens and values have looser restrictions on allowed characters than
1347 spec. This is also due to certain characters being in common use inside
1348 values. We allow only '=' to separate token/value pairs, and ';' to
1349 terminate tokens or values. <LWS> is allowed within tokens and values
1352 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1353 reject control chars or non-ASCII chars. This is erring on the loose
1354 side, since there's probably no good reason to enforce this strictness.
1356 5. Attribute "HttpOnly", not covered in the RFCs, is supported
1360 token = 1*<any allowed-chars except separators>
1361 value = 1*<any allowed-chars except value-sep>
1362 separators = ";" | "="
1364 cookie-sep = CR | LF
1365 allowed-chars = <any OCTET except NUL or cookie-sep>
1366 OCTET = <any 8-bit sequence of data>
1368 NUL = <US-ASCII NUL, null control character (0)>
1369 CR = <US-ASCII CR, carriage return (13)>
1370 LF = <US-ASCII LF, linefeed (10)>
1371 SP = <US-ASCII SP, space (32)>
1372 HT = <US-ASCII HT, horizontal-tab (9)>
1374 set-cookie = "Set-Cookie:" cookies
1375 cookies = cookie *( cookie-sep cookie )
1376 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
1377 NAME = token ; cookie name
1378 VALUE = value ; cookie value
1379 cookie-av = token ["=" value]
1381 valid values for cookie-av (checked post-parsing) are:
1382 cookie-av = "Path" "=" value
1383 | "Domain" "=" value
1384 | "Expires" "=" value
1385 | "Max-Age" "=" value
1386 | "Comment" "=" value
1387 | "Version" "=" value
1391 ******************************************************************************/
1394 // helper functions for GetTokenValue
1395 static inline bool isnull(char c
) { return c
== 0; }
1396 static inline bool iswhitespace(char c
) { return c
== ' ' || c
== '\t'; }
1397 static inline bool isterminator(char c
) { return c
== '\n' || c
== '\r'; }
1398 static inline bool isvalueseparator(char c
) {
1399 return isterminator(c
) || c
== ';';
1401 static inline bool istokenseparator(char c
) {
1402 return isvalueseparator(c
) || c
== '=';
1405 // Parse a single token/value pair.
1406 // Returns true if a cookie terminator is found, so caller can parse new cookie.
1407 bool CookieService::GetTokenValue(nsACString::const_char_iterator
& aIter
,
1408 nsACString::const_char_iterator
& aEndIter
,
1409 nsDependentCSubstring
& aTokenString
,
1410 nsDependentCSubstring
& aTokenValue
,
1411 bool& aEqualsFound
) {
1412 nsACString::const_char_iterator start
;
1413 nsACString::const_char_iterator lastSpace
;
1414 // initialize value string to clear garbage
1415 aTokenValue
.Rebind(aIter
, aIter
);
1417 // find <token>, including any <LWS> between the end-of-token and the
1418 // token separator. we'll remove trailing <LWS> next
1419 while (aIter
!= aEndIter
&& iswhitespace(*aIter
)) {
1423 while (aIter
!= aEndIter
&& !isnull(*aIter
) && !istokenseparator(*aIter
)) {
1427 // remove trailing <LWS>; first check we're not at the beginning
1429 if (lastSpace
!= start
) {
1430 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
)) {
1434 aTokenString
.Rebind(start
, lastSpace
);
1436 aEqualsFound
= (*aIter
== '=');
1439 while (++aIter
!= aEndIter
&& iswhitespace(*aIter
)) {
1445 // just look for ';' to terminate ('=' allowed)
1446 while (aIter
!= aEndIter
&& !isnull(*aIter
) && !isvalueseparator(*aIter
)) {
1450 // remove trailing <LWS>; first check we're not at the beginning
1451 if (aIter
!= start
) {
1453 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
)) {
1456 aTokenValue
.Rebind(start
, ++lastSpace
);
1460 // aIter is on ';', or terminator, or EOS
1461 if (aIter
!= aEndIter
) {
1462 // if on terminator, increment past & return true to process new cookie
1463 if (isterminator(*aIter
)) {
1467 // fall-through: aIter is on ';', increment and return false
1473 static inline void SetSameSiteAttributeDefault(CookieStruct
& aCookieData
) {
1474 // Set cookie with SameSite attribute that is treated as Default
1475 // and doesn't requires changing the DB schema.
1476 aCookieData
.sameSite() = nsICookie::SAMESITE_LAX
;
1477 aCookieData
.rawSameSite() = nsICookie::SAMESITE_NONE
;
1480 static inline void SetSameSiteAttribute(CookieStruct
& aCookieData
,
1482 aCookieData
.sameSite() = aValue
;
1483 aCookieData
.rawSameSite() = aValue
;
1486 // Parses attributes from cookie header. expires/max-age attributes aren't
1487 // folded into the cookie struct here, because we don't know which one to use
1488 // until we've parsed the header.
1489 bool CookieService::ParseAttributes(nsIConsoleReportCollector
* aCRC
,
1490 nsIURI
* aHostURI
, nsCString
& aCookieHeader
,
1491 CookieStruct
& aCookieData
,
1492 nsACString
& aExpires
, nsACString
& aMaxage
,
1493 bool& aAcceptedByParser
) {
1494 aAcceptedByParser
= false;
1496 static const char kPath
[] = "path";
1497 static const char kDomain
[] = "domain";
1498 static const char kExpires
[] = "expires";
1499 static const char kMaxage
[] = "max-age";
1500 static const char kSecure
[] = "secure";
1501 static const char kHttpOnly
[] = "httponly";
1502 static const char kSameSite
[] = "samesite";
1503 static const char kSameSiteLax
[] = "lax";
1504 static const char kSameSiteNone
[] = "none";
1505 static const char kSameSiteStrict
[] = "strict";
1507 nsACString::const_char_iterator cookieStart
;
1508 aCookieHeader
.BeginReading(cookieStart
);
1510 nsACString::const_char_iterator cookieEnd
;
1511 aCookieHeader
.EndReading(cookieEnd
);
1513 aCookieData
.isSecure() = false;
1514 aCookieData
.isHttpOnly() = false;
1516 SetSameSiteAttributeDefault(aCookieData
);
1518 nsDependentCSubstring
tokenString(cookieStart
, cookieStart
);
1519 nsDependentCSubstring
tokenValue(cookieStart
, cookieStart
);
1523 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1524 // if we find multiple cookies, return for processing
1525 // note: if there's no '=', we assume token is <VALUE>. this is required by
1526 // some sites (see bug 169091).
1527 // XXX fix the parser to parse according to <VALUE> grammar for this case
1528 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
,
1531 aCookieData
.name() = tokenString
;
1532 aCookieData
.value() = tokenValue
;
1534 aCookieData
.value() = tokenString
;
1537 // extract remaining attributes
1538 while (cookieStart
!= cookieEnd
&& !newCookie
) {
1539 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
,
1542 // decide which attribute we have, and copy the string
1543 if (tokenString
.LowerCaseEqualsLiteral(kPath
)) {
1544 aCookieData
.path() = tokenValue
;
1546 } else if (tokenString
.LowerCaseEqualsLiteral(kDomain
)) {
1547 aCookieData
.host() = tokenValue
;
1549 } else if (tokenString
.LowerCaseEqualsLiteral(kExpires
)) {
1550 aExpires
= tokenValue
;
1552 } else if (tokenString
.LowerCaseEqualsLiteral(kMaxage
)) {
1553 aMaxage
= tokenValue
;
1555 // ignore any tokenValue for isSecure; just set the boolean
1556 } else if (tokenString
.LowerCaseEqualsLiteral(kSecure
)) {
1557 aCookieData
.isSecure() = true;
1559 // ignore any tokenValue for isHttpOnly (see bug 178993);
1560 // just set the boolean
1561 } else if (tokenString
.LowerCaseEqualsLiteral(kHttpOnly
)) {
1562 aCookieData
.isHttpOnly() = true;
1564 } else if (tokenString
.LowerCaseEqualsLiteral(kSameSite
)) {
1565 if (tokenValue
.LowerCaseEqualsLiteral(kSameSiteLax
)) {
1566 SetSameSiteAttribute(aCookieData
, nsICookie::SAMESITE_LAX
);
1567 } else if (tokenValue
.LowerCaseEqualsLiteral(kSameSiteStrict
)) {
1568 SetSameSiteAttribute(aCookieData
, nsICookie::SAMESITE_STRICT
);
1569 } else if (tokenValue
.LowerCaseEqualsLiteral(kSameSiteNone
)) {
1570 SetSameSiteAttribute(aCookieData
, nsICookie::SAMESITE_NONE
);
1572 // Reset to Default if unknown token value (see Bug 1682450)
1573 SetSameSiteAttributeDefault(aCookieData
);
1574 CookieLogging::LogMessageToConsole(
1575 aCRC
, aHostURI
, nsIScriptError::infoFlag
, CONSOLE_SAMESITE_CATEGORY
,
1576 "CookieSameSiteValueInvalid2"_ns
,
1577 AutoTArray
<nsString
, 1>{NS_ConvertUTF8toUTF16(aCookieData
.name())});
1582 // re-assign aCookieHeader, in case we need to process another cookie
1583 aCookieHeader
.Assign(Substring(cookieStart
, cookieEnd
));
1585 // If same-site is explicitly set to 'none' but this is not a secure context,
1586 // let's abort the parsing.
1587 if (!aCookieData
.isSecure() &&
1588 aCookieData
.sameSite() == nsICookie::SAMESITE_NONE
) {
1589 if (StaticPrefs::network_cookie_sameSite_noneRequiresSecure()) {
1590 CookieLogging::LogMessageToConsole(
1591 aCRC
, aHostURI
, nsIScriptError::errorFlag
, CONSOLE_SAMESITE_CATEGORY
,
1592 "CookieRejectedNonRequiresSecure2"_ns
,
1593 AutoTArray
<nsString
, 1>{NS_ConvertUTF8toUTF16(aCookieData
.name())});
1597 // Still warn about the missing Secure attribute when not enforcing.
1598 CookieLogging::LogMessageToConsole(
1599 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_SAMESITE_CATEGORY
,
1600 "CookieRejectedNonRequiresSecureForBeta3"_ns
,
1601 AutoTArray
<nsString
, 2>{NS_ConvertUTF8toUTF16(aCookieData
.name()),
1605 if (aCookieData
.rawSameSite() == nsICookie::SAMESITE_NONE
&&
1606 aCookieData
.sameSite() == nsICookie::SAMESITE_LAX
) {
1608 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1609 !nsContentUtils::IsURIInPrefList(
1610 aHostURI
, "network.cookie.sameSite.laxByDefault.disabledHosts");
1612 CookieLogging::LogMessageToConsole(
1613 aCRC
, aHostURI
, nsIScriptError::infoFlag
, CONSOLE_SAMESITE_CATEGORY
,
1614 "CookieLaxForced2"_ns
,
1615 AutoTArray
<nsString
, 1>{NS_ConvertUTF8toUTF16(aCookieData
.name())});
1617 CookieLogging::LogMessageToConsole(
1618 aCRC
, aHostURI
, nsIScriptError::warningFlag
,
1619 CONSOLE_SAMESITE_CATEGORY
, "CookieLaxForcedForBeta2"_ns
,
1620 AutoTArray
<nsString
, 2>{NS_ConvertUTF8toUTF16(aCookieData
.name()),
1626 aAcceptedByParser
= true;
1628 MOZ_ASSERT(Cookie::ValidateSameSite(aCookieData
));
1632 /******************************************************************************
1633 * CookieService impl:
1634 * private domain & permission compliance enforcement functions
1635 ******************************************************************************/
1637 // Normalizes the given hostname, component by component. ASCII/ACE
1638 // components are lower-cased, and UTF-8 components are normalized per
1639 // RFC 3454 and converted to ACE.
1640 nsresult
CookieService::NormalizeHost(nsCString
& aHost
) {
1641 if (!IsAscii(aHost
)) {
1643 nsresult rv
= mIDNService
->ConvertUTF8toACE(aHost
, host
);
1644 if (NS_FAILED(rv
)) {
1655 // returns true if 'a' is equal to or a subdomain of 'b',
1656 // assuming no leading dots are present.
1657 static inline bool IsSubdomainOf(const nsACString
& a
, const nsACString
& b
) {
1661 if (a
.Length() > b
.Length()) {
1662 return a
[a
.Length() - b
.Length() - 1] == '.' && StringEndsWith(a
, b
);
1667 CookieStatus
CookieService::CheckPrefs(
1668 nsIConsoleReportCollector
* aCRC
, nsICookieJarSettings
* aCookieJarSettings
,
1669 nsIURI
* aHostURI
, bool aIsForeign
, bool aIsThirdPartyTrackingResource
,
1670 bool aIsThirdPartySocialTrackingResource
,
1671 bool aStorageAccessPermissionGranted
, const nsACString
& aCookieHeader
,
1672 const int aNumOfCookies
, const OriginAttributes
& aOriginAttrs
,
1673 uint32_t* aRejectedReason
) {
1676 MOZ_ASSERT(aRejectedReason
);
1678 *aRejectedReason
= 0;
1680 // don't let unsupported scheme sites get/set cookies (could be a security
1682 if (!CookieCommons::IsSchemeSupported(aHostURI
)) {
1683 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1684 "non http/https sites cannot read cookies");
1685 return STATUS_REJECTED_WITH_ERROR
;
1688 nsCOMPtr
<nsIPrincipal
> principal
=
1689 BasePrincipal::CreateContentPrincipal(aHostURI
, aOriginAttrs
);
1692 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1693 "non-content principals cannot get/set cookies");
1694 return STATUS_REJECTED_WITH_ERROR
;
1697 // check the permission list first; if we find an entry, it overrides
1698 // default prefs. see bug 184059.
1699 uint32_t cookiePermission
= nsICookiePermission::ACCESS_DEFAULT
;
1700 rv
= aCookieJarSettings
->CookiePermission(principal
, &cookiePermission
);
1701 if (NS_SUCCEEDED(rv
)) {
1702 switch (cookiePermission
) {
1703 case nsICookiePermission::ACCESS_DENY
:
1704 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1705 "cookies are blocked for this site");
1706 CookieLogging::LogMessageToConsole(
1707 aCRC
, aHostURI
, nsIScriptError::warningFlag
,
1708 CONSOLE_REJECTION_CATEGORY
, "CookieRejectedByPermissionManager"_ns
,
1709 AutoTArray
<nsString
, 1>{
1710 NS_ConvertUTF8toUTF16(aCookieHeader
),
1714 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
;
1715 return STATUS_REJECTED
;
1717 case nsICookiePermission::ACCESS_ALLOW
:
1718 return STATUS_ACCEPTED
;
1724 // No cookies allowed if this request comes from a resource in a 3rd party
1725 // context, when anti-tracking protection is enabled and when we don't have
1726 // access to the first-party cookie jar.
1727 if (aIsForeign
&& aIsThirdPartyTrackingResource
&&
1728 !aStorageAccessPermissionGranted
&&
1729 aCookieJarSettings
->GetRejectThirdPartyContexts()) {
1730 bool rejectThirdPartyWithExceptions
=
1731 CookieJarSettings::IsRejectThirdPartyWithExceptions(
1732 aCookieJarSettings
->GetCookieBehavior());
1734 uint32_t rejectReason
=
1735 rejectThirdPartyWithExceptions
1736 ? nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
1737 : nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
;
1738 if (StoragePartitioningEnabled(rejectReason
, aCookieJarSettings
)) {
1739 MOZ_ASSERT(!aOriginAttrs
.mPartitionKey
.IsEmpty(),
1740 "We must have a StoragePrincipal here!");
1741 return STATUS_ACCEPTED
;
1744 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1745 "cookies are disabled in trackers");
1746 if (aIsThirdPartySocialTrackingResource
) {
1748 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
;
1749 } else if (rejectThirdPartyWithExceptions
) {
1750 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
;
1752 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
;
1754 return STATUS_REJECTED
;
1757 // check default prefs.
1758 // Check aStorageAccessPermissionGranted when checking aCookieBehavior
1759 // so that we take things such as the content blocking allow list into
1761 if (aCookieJarSettings
->GetCookieBehavior() ==
1762 nsICookieService::BEHAVIOR_REJECT
&&
1763 !aStorageAccessPermissionGranted
) {
1764 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1765 "cookies are disabled");
1766 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
;
1767 return STATUS_REJECTED
;
1770 // check if cookie is foreign
1772 if (aCookieJarSettings
->GetCookieBehavior() ==
1773 nsICookieService::BEHAVIOR_REJECT_FOREIGN
&&
1774 !aStorageAccessPermissionGranted
) {
1775 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1776 "context is third party");
1777 CookieLogging::LogMessageToConsole(
1778 aCRC
, aHostURI
, nsIScriptError::warningFlag
,
1779 CONSOLE_REJECTION_CATEGORY
, "CookieRejectedThirdParty"_ns
,
1780 AutoTArray
<nsString
, 1>{
1781 NS_ConvertUTF8toUTF16(aCookieHeader
),
1783 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
;
1784 return STATUS_REJECTED
;
1787 if (aCookieJarSettings
->GetLimitForeignContexts() &&
1788 !aStorageAccessPermissionGranted
&& aNumOfCookies
== 0) {
1789 COOKIE_LOGFAILURE(!aCookieHeader
.IsVoid(), aHostURI
, aCookieHeader
,
1790 "context is third party");
1791 CookieLogging::LogMessageToConsole(
1792 aCRC
, aHostURI
, nsIScriptError::warningFlag
,
1793 CONSOLE_REJECTION_CATEGORY
, "CookieRejectedThirdParty"_ns
,
1794 AutoTArray
<nsString
, 1>{
1795 NS_ConvertUTF8toUTF16(aCookieHeader
),
1797 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
;
1798 return STATUS_REJECTED
;
1801 if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
1802 return STATUS_ACCEPT_SESSION
;
1805 if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly()) {
1806 if (!aHostURI
->SchemeIs("https")) {
1807 return STATUS_ACCEPT_SESSION
;
1812 // if nothing has complained, accept cookie
1813 return STATUS_ACCEPTED
;
1816 // processes domain attribute, and returns true if host has permission to set
1818 bool CookieService::CheckDomain(CookieStruct
& aCookieData
, nsIURI
* aHostURI
,
1819 const nsACString
& aBaseDomain
,
1820 bool aRequireHostMatch
) {
1821 // Note: The logic in this function is mirrored in
1822 // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
1823 // If it changes, please update that function, or file a bug for someone
1826 // get host from aHostURI
1827 nsAutoCString hostFromURI
;
1828 nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI
, hostFromURI
);
1830 // if a domain is given, check the host has permission
1831 if (!aCookieData
.host().IsEmpty()) {
1832 // Tolerate leading '.' characters, but not if it's otherwise an empty host.
1833 if (aCookieData
.host().Length() > 1 && aCookieData
.host().First() == '.') {
1834 aCookieData
.host().Cut(0, 1);
1837 // switch to lowercase now, to avoid case-insensitive compares everywhere
1838 ToLowerCase(aCookieData
.host());
1840 // check whether the host is either an IP address, an alias such as
1841 // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
1842 // cases, require an exact string match for the domain, and leave the cookie
1843 // as a non-domain one. bug 105917 originally noted the requirement to deal
1844 // with IP addresses.
1845 if (aRequireHostMatch
) {
1846 return hostFromURI
.Equals(aCookieData
.host());
1849 // ensure the proposed domain is derived from the base domain; and also
1850 // that the host domain is derived from the proposed domain (per RFC2109).
1851 if (IsSubdomainOf(aCookieData
.host(), aBaseDomain
) &&
1852 IsSubdomainOf(hostFromURI
, aCookieData
.host())) {
1853 // prepend a dot to indicate a domain cookie
1854 aCookieData
.host().InsertLiteral(".", 0);
1859 * note: RFC2109 section 4.3.2 requires that we check the following:
1860 * that the portion of host not in domain does not contain a dot.
1861 * this prevents hosts of the form x.y.co.nz from setting cookies in the
1862 * entire .co.nz domain. however, it's only a only a partial solution and
1863 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1868 // no domain specified, use hostFromURI
1869 aCookieData
.host() = hostFromURI
;
1874 bool CookieService::CheckHiddenPrefix(CookieStruct
& aCookieData
) {
1875 // If a cookie is nameless, then its value must not start with
1876 // `__Host-` or `__Secure-`
1877 if (!aCookieData
.name().IsEmpty()) {
1881 if (StringBeginsWith(aCookieData
.value(), "__Host-"_ns
)) {
1885 if (StringBeginsWith(aCookieData
.value(), "__Secure-"_ns
)) {
1893 nsAutoCString
GetPathFromURI(nsIURI
* aHostURI
) {
1894 // strip down everything after the last slash to get the path,
1895 // ignoring slashes in the query string part.
1896 // if we can QI to nsIURL, that'll take care of the query string portion.
1897 // otherwise, it's not an nsIURL and can't have a query string, so just find
1900 nsCOMPtr
<nsIURL
> hostURL
= do_QueryInterface(aHostURI
);
1902 hostURL
->GetDirectory(path
);
1904 aHostURI
->GetPathQueryRef(path
);
1905 int32_t slash
= path
.RFindChar('/');
1906 if (slash
!= kNotFound
) {
1907 path
.Truncate(slash
+ 1);
1911 // strip the right-most %x2F ("/") if the path doesn't contain only 1 '/'.
1912 int32_t lastSlash
= path
.RFindChar('/');
1913 int32_t firstSlash
= path
.FindChar('/');
1914 if (lastSlash
!= firstSlash
&& lastSlash
!= kNotFound
&&
1915 lastSlash
== static_cast<int32_t>(path
.Length() - 1)) {
1916 path
.Truncate(lastSlash
);
1924 bool CookieService::CheckPath(CookieStruct
& aCookieData
,
1925 nsIConsoleReportCollector
* aCRC
,
1927 // if a path is given, check the host has permission
1928 if (aCookieData
.path().IsEmpty() || aCookieData
.path().First() != '/') {
1929 aCookieData
.path() = GetPathFromURI(aHostURI
);
1934 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
1935 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
1936 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
1937 * been disabled, unless we can evangelize these sites.
1939 // get path from aHostURI
1940 nsAutoCString pathFromURI
;
1941 if (NS_FAILED(aHostURI
->GetPathQueryRef(pathFromURI
)) ||
1942 !StringBeginsWith(pathFromURI
, aCookieData
.path())) {
1948 if (!CookieCommons::CheckPathSize(aCookieData
)) {
1949 AutoTArray
<nsString
, 2> params
= {
1950 NS_ConvertUTF8toUTF16(aCookieData
.name())};
1953 size
.AppendInt(kMaxBytesPerPath
);
1954 params
.AppendElement(size
);
1956 CookieLogging::LogMessageToConsole(
1957 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_OVERSIZE_CATEGORY
,
1958 "CookiePathOversize"_ns
, params
);
1962 return !aCookieData
.path().Contains('\t');
1967 // Reject cookies whose name starts with the magic prefixes from
1968 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
1969 // if they do not meet the criteria required by the prefix.
1971 // Must not be called until after CheckDomain() and CheckPath() have
1972 // regularized and validated the CookieStruct values!
1973 bool CookieService::CheckPrefixes(CookieStruct
& aCookieData
,
1974 bool aSecureRequest
) {
1975 static const char kSecure
[] = "__Secure-";
1976 static const char kHost
[] = "__Host-";
1977 static const int kSecureLen
= sizeof(kSecure
) - 1;
1978 static const int kHostLen
= sizeof(kHost
) - 1;
1980 bool isSecure
= strncmp(aCookieData
.name().get(), kSecure
, kSecureLen
) == 0;
1981 bool isHost
= strncmp(aCookieData
.name().get(), kHost
, kHostLen
) == 0;
1983 if (!isSecure
&& !isHost
) {
1984 // not one of the magic prefixes: carry on
1988 if (!aSecureRequest
|| !aCookieData
.isSecure()) {
1989 // the magic prefixes may only be used from a secure request and
1990 // the secure attribute must be set on the cookie
1995 // The host prefix requires that the path is "/" and that the cookie
1996 // had no domain attribute. CheckDomain() and CheckPath() MUST be run
1997 // first to make sure invalid attributes are rejected and to regularlize
1998 // them. In particular all explicit domain attributes result in a host
1999 // that starts with a dot, and if the host doesn't start with a dot it
2000 // correctly matches the true host.
2001 if (aCookieData
.host()[0] == '.' ||
2002 !aCookieData
.path().EqualsLiteral("/")) {
2010 bool CookieService::GetExpiry(CookieStruct
& aCookieData
,
2011 const nsACString
& aExpires
,
2012 const nsACString
& aMaxage
, int64_t aCurrentTime
,
2014 // maxageCap is in seconds.
2015 // Disabled for HTTP cookies.
2017 aFromHttp
? 0 : StaticPrefs::privacy_documentCookies_maxage();
2019 /* Determine when the cookie should expire. This is done by taking the
2020 * difference between the server time and the time the server wants the cookie
2021 * to expire, and adding that difference to the client time. This localizes
2022 * the client time regardless of whether or not the TZ environment variable
2023 * was set on the client.
2025 * Note: We need to consider accounting for network lag here, per RFC.
2027 // check for max-age attribute first; this overrides expires attribute
2028 if (!aMaxage
.IsEmpty()) {
2029 // obtain numeric value of maxageAttribute
2031 int32_t numInts
= PR_sscanf(aMaxage
.BeginReading(), "%lld", &maxage
);
2033 // default to session cookie if the conversion failed
2038 // if this addition overflows, expiryTime will be less than currentTime
2039 // and the cookie will be expired - that's okay.
2041 aCookieData
.expiry() = aCurrentTime
+ std::min(maxage
, maxageCap
);
2043 aCookieData
.expiry() = aCurrentTime
+ maxage
;
2046 // check for expires attribute
2047 } else if (!aExpires
.IsEmpty()) {
2050 // parse expiry time
2051 if (PR_ParseTimeString(aExpires
.BeginReading(), true, &expires
) !=
2056 // If set-cookie used absolute time to set expiration, and it can't use
2057 // client time to set expiration.
2058 // Because if current time be set in the future, but the cookie expire
2059 // time be set less than current time and more than server time.
2060 // The cookie item have to be used to the expired cookie.
2062 aCookieData
.expiry() = std::min(expires
/ int64_t(PR_USEC_PER_SEC
),
2063 aCurrentTime
+ maxageCap
);
2065 aCookieData
.expiry() = expires
/ int64_t(PR_USEC_PER_SEC
);
2068 // default to session cookie if no attributes found. Here we don't need to
2069 // enforce the maxage cap, because session cookies are short-lived by
2078 /******************************************************************************
2079 * CookieService impl:
2080 * private cookielist management functions
2081 ******************************************************************************/
2083 // find whether a given cookie has been previously set. this is provided by the
2084 // nsICookieManager interface.
2086 CookieService::CookieExists(const nsACString
& aHost
, const nsACString
& aPath
,
2087 const nsACString
& aName
,
2088 JS::Handle
<JS::Value
> aOriginAttributes
,
2089 JSContext
* aCx
, bool* aFoundCookie
) {
2090 NS_ENSURE_ARG_POINTER(aCx
);
2091 NS_ENSURE_ARG_POINTER(aFoundCookie
);
2093 OriginAttributes attrs
;
2094 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
2095 return NS_ERROR_INVALID_ARG
;
2097 return CookieExistsNative(aHost
, aPath
, aName
, &attrs
, aFoundCookie
);
2100 NS_IMETHODIMP_(nsresult
)
2101 CookieService::CookieExistsNative(const nsACString
& aHost
,
2102 const nsACString
& aPath
,
2103 const nsACString
& aName
,
2104 OriginAttributes
* aOriginAttributes
,
2105 bool* aFoundCookie
) {
2106 nsCOMPtr
<nsICookie
> cookie
;
2107 nsresult rv
= GetCookieNative(aHost
, aPath
, aName
, aOriginAttributes
,
2108 getter_AddRefs(cookie
));
2109 NS_ENSURE_SUCCESS(rv
, rv
);
2111 *aFoundCookie
= cookie
!= nullptr;
2116 NS_IMETHODIMP_(nsresult
)
2117 CookieService::GetCookieNative(const nsACString
& aHost
, const nsACString
& aPath
,
2118 const nsACString
& aName
,
2119 OriginAttributes
* aOriginAttributes
,
2120 nsICookie
** aCookie
) {
2121 NS_ENSURE_ARG_POINTER(aOriginAttributes
);
2122 NS_ENSURE_ARG_POINTER(aCookie
);
2124 if (!IsInitialized()) {
2125 return NS_ERROR_NOT_AVAILABLE
;
2128 nsAutoCString baseDomain
;
2130 CookieCommons::GetBaseDomainFromHost(mTLDService
, aHost
, baseDomain
);
2131 NS_ENSURE_SUCCESS(rv
, rv
);
2133 CookieListIter iter
{};
2134 CookieStorage
* storage
= PickStorage(*aOriginAttributes
);
2135 bool foundCookie
= storage
->FindCookie(baseDomain
, *aOriginAttributes
, aHost
,
2136 aName
, aPath
, iter
);
2139 RefPtr
<Cookie
> cookie
= iter
.Cookie();
2140 NS_ENSURE_TRUE(cookie
, NS_ERROR_NULL_POINTER
);
2142 cookie
.forget(aCookie
);
2148 // count the number of cookies stored by a particular host. this is provided by
2149 // the nsICookieManager interface.
2151 CookieService::CountCookiesFromHost(const nsACString
& aHost
,
2152 uint32_t* aCountFromHost
) {
2153 // first, normalize the hostname, and fail if it contains illegal characters.
2154 nsAutoCString
host(aHost
);
2155 nsresult rv
= NormalizeHost(host
);
2156 NS_ENSURE_SUCCESS(rv
, rv
);
2158 nsAutoCString baseDomain
;
2159 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2160 NS_ENSURE_SUCCESS(rv
, rv
);
2162 if (!IsInitialized()) {
2163 return NS_ERROR_NOT_AVAILABLE
;
2166 mPersistentStorage
->EnsureInitialized();
2168 *aCountFromHost
= mPersistentStorage
->CountCookiesFromHost(baseDomain
, 0);
2173 // get an enumerator of cookies stored by a particular host. this is provided by
2174 // the nsICookieManager interface.
2176 CookieService::GetCookiesFromHost(const nsACString
& aHost
,
2177 JS::Handle
<JS::Value
> aOriginAttributes
,
2179 nsTArray
<RefPtr
<nsICookie
>>& aResult
) {
2180 // first, normalize the hostname, and fail if it contains illegal characters.
2181 nsAutoCString
host(aHost
);
2182 nsresult rv
= NormalizeHost(host
);
2183 NS_ENSURE_SUCCESS(rv
, rv
);
2185 nsAutoCString baseDomain
;
2186 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2187 NS_ENSURE_SUCCESS(rv
, rv
);
2189 OriginAttributes attrs
;
2190 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
2191 return NS_ERROR_INVALID_ARG
;
2194 if (!IsInitialized()) {
2195 return NS_ERROR_NOT_AVAILABLE
;
2198 CookieStorage
* storage
= PickStorage(attrs
);
2200 const nsTArray
<RefPtr
<Cookie
>>* cookies
=
2201 storage
->GetCookiesFromHost(baseDomain
, attrs
);
2204 aResult
.SetCapacity(cookies
->Length());
2205 for (Cookie
* cookie
: *cookies
) {
2206 aResult
.AppendElement(cookie
);
2214 CookieService::GetCookiesWithOriginAttributes(
2215 const nsAString
& aPattern
, const nsACString
& aHost
,
2216 nsTArray
<RefPtr
<nsICookie
>>& aResult
) {
2217 OriginAttributesPattern pattern
;
2218 if (!pattern
.Init(aPattern
)) {
2219 return NS_ERROR_INVALID_ARG
;
2222 nsAutoCString
host(aHost
);
2223 nsresult rv
= NormalizeHost(host
);
2224 NS_ENSURE_SUCCESS(rv
, rv
);
2226 nsAutoCString baseDomain
;
2227 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2228 NS_ENSURE_SUCCESS(rv
, rv
);
2230 return GetCookiesWithOriginAttributes(pattern
, baseDomain
, aResult
);
2233 nsresult
CookieService::GetCookiesWithOriginAttributes(
2234 const OriginAttributesPattern
& aPattern
, const nsCString
& aBaseDomain
,
2235 nsTArray
<RefPtr
<nsICookie
>>& aResult
) {
2236 CookieStorage
* storage
= PickStorage(aPattern
);
2237 storage
->GetCookiesWithOriginAttributes(aPattern
, aBaseDomain
, aResult
);
2243 CookieService::RemoveCookiesWithOriginAttributes(const nsAString
& aPattern
,
2244 const nsACString
& aHost
) {
2245 MOZ_ASSERT(XRE_IsParentProcess());
2247 OriginAttributesPattern pattern
;
2248 if (!pattern
.Init(aPattern
)) {
2249 return NS_ERROR_INVALID_ARG
;
2252 nsAutoCString
host(aHost
);
2253 nsresult rv
= NormalizeHost(host
);
2254 NS_ENSURE_SUCCESS(rv
, rv
);
2256 nsAutoCString baseDomain
;
2257 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2258 NS_ENSURE_SUCCESS(rv
, rv
);
2260 return RemoveCookiesWithOriginAttributes(pattern
, baseDomain
);
2263 nsresult
CookieService::RemoveCookiesWithOriginAttributes(
2264 const OriginAttributesPattern
& aPattern
, const nsCString
& aBaseDomain
) {
2265 if (!IsInitialized()) {
2266 return NS_ERROR_NOT_AVAILABLE
;
2269 CookieStorage
* storage
= PickStorage(aPattern
);
2270 storage
->RemoveCookiesWithOriginAttributes(aPattern
, aBaseDomain
);
2276 CookieService::RemoveCookiesFromExactHost(const nsACString
& aHost
,
2277 const nsAString
& aPattern
) {
2278 MOZ_ASSERT(XRE_IsParentProcess());
2280 OriginAttributesPattern pattern
;
2281 if (!pattern
.Init(aPattern
)) {
2282 return NS_ERROR_INVALID_ARG
;
2285 return RemoveCookiesFromExactHost(aHost
, pattern
);
2288 nsresult
CookieService::RemoveCookiesFromExactHost(
2289 const nsACString
& aHost
, const OriginAttributesPattern
& aPattern
) {
2290 nsAutoCString
host(aHost
);
2291 nsresult rv
= NormalizeHost(host
);
2292 NS_ENSURE_SUCCESS(rv
, rv
);
2294 nsAutoCString baseDomain
;
2295 rv
= CookieCommons::GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2296 NS_ENSURE_SUCCESS(rv
, rv
);
2298 if (!IsInitialized()) {
2299 return NS_ERROR_NOT_AVAILABLE
;
2302 CookieStorage
* storage
= PickStorage(aPattern
);
2303 storage
->RemoveCookiesFromExactHost(aHost
, baseDomain
, aPattern
);
2310 class RemoveAllSinceRunnable
: public Runnable
{
2312 using CookieArray
= nsTArray
<RefPtr
<nsICookie
>>;
2313 RemoveAllSinceRunnable(Promise
* aPromise
, CookieService
* aSelf
,
2314 CookieArray
&& aCookieArray
, int64_t aSinceWhen
)
2315 : Runnable("RemoveAllSinceRunnable"),
2318 mList(std::move(aCookieArray
)),
2320 mSinceWhen(aSinceWhen
) {}
2322 NS_IMETHODIMP
Run() override
{
2325 if (mIndex
< mList
.Length()) {
2326 return NS_DispatchToCurrentThread(this);
2328 mPromise
->MaybeResolveWithUndefined();
2335 for (CookieArray::size_type iter
= 0;
2336 iter
< kYieldPeriod
&& mIndex
< mList
.Length(); ++mIndex
, ++iter
) {
2337 auto* cookie
= static_cast<Cookie
*>(mList
[mIndex
].get());
2338 if (cookie
->CreationTime() > mSinceWhen
&&
2339 NS_FAILED(mSelf
->Remove(cookie
->Host(), cookie
->OriginAttributesRef(),
2340 cookie
->Name(), cookie
->Path()))) {
2347 RefPtr
<Promise
> mPromise
;
2348 RefPtr
<CookieService
> mSelf
;
2350 CookieArray::size_type mIndex
;
2352 static const CookieArray::size_type kYieldPeriod
= 10;
2358 CookieService::RemoveAllSince(int64_t aSinceWhen
, JSContext
* aCx
,
2359 Promise
** aRetVal
) {
2360 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
2361 if (NS_WARN_IF(!globalObject
)) {
2362 return NS_ERROR_UNEXPECTED
;
2366 RefPtr
<Promise
> promise
= Promise::Create(globalObject
, result
);
2367 if (NS_WARN_IF(result
.Failed())) {
2368 return result
.StealNSResult();
2371 mPersistentStorage
->EnsureInitialized();
2373 nsTArray
<RefPtr
<nsICookie
>> cookieList
;
2375 // We delete only non-private cookies.
2376 mPersistentStorage
->GetAll(cookieList
);
2378 RefPtr
<RemoveAllSinceRunnable
> runMe
= new RemoveAllSinceRunnable(
2379 promise
, this, std::move(cookieList
), aSinceWhen
);
2381 promise
.forget(aRetVal
);
2383 return runMe
->Run();
2388 class CompareCookiesCreationTime
{
2390 static bool Equals(const nsICookie
* aCookie1
, const nsICookie
* aCookie2
) {
2391 return static_cast<const Cookie
*>(aCookie1
)->CreationTime() ==
2392 static_cast<const Cookie
*>(aCookie2
)->CreationTime();
2395 static bool LessThan(const nsICookie
* aCookie1
, const nsICookie
* aCookie2
) {
2396 return static_cast<const Cookie
*>(aCookie1
)->CreationTime() <
2397 static_cast<const Cookie
*>(aCookie2
)->CreationTime();
2404 CookieService::GetCookiesSince(int64_t aSinceWhen
,
2405 nsTArray
<RefPtr
<nsICookie
>>& aResult
) {
2406 if (!IsInitialized()) {
2410 mPersistentStorage
->EnsureInitialized();
2412 // We expose only non-private cookies.
2413 nsTArray
<RefPtr
<nsICookie
>> cookieList
;
2414 mPersistentStorage
->GetAll(cookieList
);
2416 for (RefPtr
<nsICookie
>& cookie
: cookieList
) {
2417 if (static_cast<Cookie
*>(cookie
.get())->CreationTime() >= aSinceWhen
) {
2418 aResult
.AppendElement(cookie
);
2422 aResult
.Sort(CompareCookiesCreationTime());
2426 size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
2427 size_t n
= aMallocSizeOf(this);
2429 if (mPersistentStorage
) {
2430 n
+= mPersistentStorage
->SizeOfIncludingThis(aMallocSizeOf
);
2432 if (mPrivateStorage
) {
2433 n
+= mPrivateStorage
->SizeOfIncludingThis(aMallocSizeOf
);
2439 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf
)
2442 CookieService::CollectReports(nsIHandleReportCallback
* aHandleReport
,
2443 nsISupports
* aData
, bool /*aAnonymize*/) {
2444 MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP
, UNITS_BYTES
,
2445 SizeOfIncludingThis(CookieServiceMallocSizeOf
),
2446 "Memory used by the cookie service.");
2451 bool CookieService::IsInitialized() const {
2452 if (!mPersistentStorage
) {
2453 NS_WARNING("No CookieStorage! Profile already close?");
2457 MOZ_ASSERT(mPrivateStorage
);
2461 CookieStorage
* CookieService::PickStorage(const OriginAttributes
& aAttrs
) {
2462 MOZ_ASSERT(IsInitialized());
2464 if (aAttrs
.mPrivateBrowsingId
> 0) {
2465 return mPrivateStorage
;
2468 mPersistentStorage
->EnsureInitialized();
2469 return mPersistentStorage
;
2472 CookieStorage
* CookieService::PickStorage(
2473 const OriginAttributesPattern
& aAttrs
) {
2474 MOZ_ASSERT(IsInitialized());
2476 if (aAttrs
.mPrivateBrowsingId
.WasPassed() &&
2477 aAttrs
.mPrivateBrowsingId
.Value() > 0) {
2478 return mPrivateStorage
;
2481 mPersistentStorage
->EnsureInitialized();
2482 return mPersistentStorage
;
2485 bool CookieService::SetCookiesFromIPC(const nsACString
& aBaseDomain
,
2486 const OriginAttributes
& aAttrs
,
2487 nsIURI
* aHostURI
, bool aFromHttp
,
2488 const nsTArray
<CookieStruct
>& aCookies
) {
2489 if (!IsInitialized()) {
2490 // If we are probably shutting down, we can ignore this cookie.
2494 CookieStorage
* storage
= PickStorage(aAttrs
);
2495 int64_t currentTimeInUsec
= PR_Now();
2497 for (const CookieStruct
& cookieData
: aCookies
) {
2498 if (!CookieCommons::CheckPathSize(cookieData
)) {
2502 // reject cookie if it's over the size limit, per RFC2109
2503 if (!CookieCommons::CheckNameAndValueSize(cookieData
)) {
2507 RecordUnicodeTelemetry(cookieData
);
2509 if (!CookieCommons::CheckName(cookieData
)) {
2513 if (!CookieCommons::CheckValue(cookieData
)) {
2517 // create a new Cookie and copy attributes
2518 RefPtr
<Cookie
> cookie
= Cookie::Create(cookieData
, aAttrs
);
2523 cookie
->SetLastAccessed(currentTimeInUsec
);
2524 cookie
->SetCreationTime(
2525 Cookie::GenerateUniqueCreationTime(currentTimeInUsec
));
2527 storage
->AddCookie(nullptr, aBaseDomain
, aAttrs
, cookie
, currentTimeInUsec
,
2528 aHostURI
, ""_ns
, aFromHttp
);
2535 } // namespace mozilla