Bug 1807268 - Re-enable verifyShowClipboardSuggestionsToggleTest UI test r=jajohnson
[gecko.git] / netwerk / cookie / CookieService.cpp
blob2f1efe18d181cfe12fc2409cc411e346a9380ced
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/glean/GleanMetrics.h"
17 #include "mozilla/net/CookieJarSettings.h"
18 #include "mozilla/net/CookiePersistentStorage.h"
19 #include "mozilla/net/CookiePrivateStorage.h"
20 #include "mozilla/net/CookieService.h"
21 #include "mozilla/net/CookieServiceChild.h"
22 #include "mozilla/net/HttpBaseChannel.h"
23 #include "mozilla/net/NeckoCommon.h"
24 #include "mozilla/StaticPrefs_network.h"
25 #include "mozilla/StoragePrincipalHelper.h"
26 #include "mozilla/Telemetry.h"
27 #include "mozIThirdPartyUtil.h"
28 #include "nsCRT.h"
29 #include "nsICookiePermission.h"
30 #include "nsIConsoleReportCollector.h"
31 #include "nsIEffectiveTLDService.h"
32 #include "nsIIDNService.h"
33 #include "nsIScriptError.h"
34 #include "nsIURL.h"
35 #include "nsIURI.h"
36 #include "nsIWebProgressListener.h"
37 #include "nsNetUtil.h"
38 #include "prprf.h"
39 #include "ThirdPartyUtil.h"
41 using namespace mozilla::dom;
43 namespace {
45 uint32_t MakeCookieBehavior(uint32_t aCookieBehavior) {
46 bool isFirstPartyIsolated = OriginAttributes::IsFirstPartyEnabled();
48 if (isFirstPartyIsolated &&
49 aCookieBehavior ==
50 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
51 return nsICookieService::BEHAVIOR_REJECT_TRACKER;
53 return aCookieBehavior;
57 Enables sanitizeOnShutdown cleaning prefs and disables the
58 network.cookie.lifetimePolicy
60 void MigrateCookieLifetimePrefs() {
61 // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY
62 // are not available anymore 2 = ACCEPT_SESSION
63 if (mozilla::Preferences::GetInt("network.cookie.lifetimePolicy") != 2) {
64 return;
66 if (!mozilla::Preferences::GetBool("privacy.sanitize.sanitizeOnShutdown")) {
67 mozilla::Preferences::SetBool("privacy.sanitize.sanitizeOnShutdown", true);
68 // To avoid clearing categories that the user did not intend to clear
69 mozilla::Preferences::SetBool("privacy.clearOnShutdown.history", false);
70 mozilla::Preferences::SetBool("privacy.clearOnShutdown.formdata", false);
71 mozilla::Preferences::SetBool("privacy.clearOnShutdown.downloads", false);
72 mozilla::Preferences::SetBool("privacy.clearOnShutdown.sessions", false);
73 mozilla::Preferences::SetBool("privacy.clearOnShutdown.siteSettings",
74 false);
76 mozilla::Preferences::SetBool("privacy.clearOnShutdown.cookies", true);
77 mozilla::Preferences::SetBool("privacy.clearOnShutdown.cache", true);
78 mozilla::Preferences::SetBool("privacy.clearOnShutdown.offlineApps", true);
79 mozilla::Preferences::ClearUser("network.cookie.lifetimePolicy");
82 } // anonymous namespace
84 // static
85 uint32_t nsICookieManager::GetCookieBehavior(bool aIsPrivate) {
86 if (aIsPrivate) {
87 // To sync the cookieBehavior pref between regular and private mode in ETP
88 // custom mode, we will return the regular cookieBehavior pref for private
89 // mode when
90 // 1. The regular cookieBehavior pref has a non-default value.
91 // 2. And the private cookieBehavior pref has a default value.
92 // Also, this can cover the migration case where the user has a non-default
93 // cookieBehavior before the private cookieBehavior was introduced. The
94 // getter here will directly return the regular cookieBehavior, so that the
95 // cookieBehavior for private mode is consistent.
96 if (mozilla::Preferences::HasUserValue(
97 "network.cookie.cookieBehavior.pbmode")) {
98 return MakeCookieBehavior(
99 mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
102 if (mozilla::Preferences::HasUserValue("network.cookie.cookieBehavior")) {
103 return MakeCookieBehavior(
104 mozilla::StaticPrefs::network_cookie_cookieBehavior());
107 return MakeCookieBehavior(
108 mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
110 return MakeCookieBehavior(
111 mozilla::StaticPrefs::network_cookie_cookieBehavior());
114 namespace mozilla {
115 namespace net {
117 /******************************************************************************
118 * CookieService impl:
119 * useful types & constants
120 ******************************************************************************/
122 static StaticRefPtr<CookieService> gCookieService;
124 constexpr auto CONSOLE_CHIPS_CATEGORY = "cookiesCHIPS"_ns;
125 constexpr auto CONSOLE_SAMESITE_CATEGORY = "cookieSameSite"_ns;
126 constexpr auto CONSOLE_OVERSIZE_CATEGORY = "cookiesOversize"_ns;
127 constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
128 constexpr auto SAMESITE_MDN_URL =
129 "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/"
130 u"SameSite"_ns;
132 namespace {
134 void ComposeCookieString(nsTArray<Cookie*>& aCookieList,
135 nsACString& aCookieString) {
136 for (Cookie* cookie : aCookieList) {
137 // check if we have anything to write
138 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
139 // if we've already added a cookie to the return list, append a "; " so
140 // that subsequent cookies are delimited in the final list.
141 if (!aCookieString.IsEmpty()) {
142 aCookieString.AppendLiteral("; ");
145 if (!cookie->Name().IsEmpty()) {
146 // we have a name and value - write both
147 aCookieString += cookie->Name() + "="_ns + cookie->Value();
148 } else {
149 // just write value
150 aCookieString += cookie->Value();
156 // Return false if the cookie should be ignored for the current channel.
157 bool ProcessSameSiteCookieForForeignRequest(nsIChannel* aChannel,
158 Cookie* aCookie,
159 bool aIsSafeTopLevelNav,
160 bool aHadCrossSiteRedirects,
161 bool aLaxByDefault) {
162 // If it's a cross-site request and the cookie is same site only (strict)
163 // don't send it.
164 if (aCookie->SameSite() == nsICookie::SAMESITE_STRICT) {
165 return false;
168 // Explicit SameSite=None cookies are always processed. When laxByDefault
169 // is OFF then so are default cookies.
170 if (aCookie->SameSite() == nsICookie::SAMESITE_NONE ||
171 (!aLaxByDefault && aCookie->IsDefaultSameSite())) {
172 return true;
175 // Lax-by-default cookies are processed even with an intermediate
176 // cross-site redirect (they are treated like aIsSameSiteForeign = false).
177 if (aLaxByDefault && aCookie->IsDefaultSameSite() && aHadCrossSiteRedirects &&
178 StaticPrefs::
179 network_cookie_sameSite_laxByDefault_allowBoomerangRedirect()) {
180 return true;
183 int64_t currentTimeInUsec = PR_Now();
185 // 2 minutes of tolerance for 'SameSite=Lax by default' for cookies set
186 // without a SameSite value when used for unsafe http methods.
187 if (aLaxByDefault && aCookie->IsDefaultSameSite() &&
188 StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 &&
189 currentTimeInUsec - aCookie->CreationTime() <=
190 (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() *
191 PR_USEC_PER_SEC) &&
192 !NS_IsSafeMethodNav(aChannel)) {
193 return true;
196 MOZ_ASSERT((aLaxByDefault && aCookie->IsDefaultSameSite()) ||
197 aCookie->SameSite() == nsICookie::SAMESITE_LAX);
198 // We only have SameSite=Lax or lax-by-default cookies at this point. These
199 // are processed only if it's a top-level navigation
200 return aIsSafeTopLevelNav;
203 } // namespace
205 /******************************************************************************
206 * CookieService impl:
207 * singleton instance ctor/dtor methods
208 ******************************************************************************/
210 already_AddRefed<nsICookieService> CookieService::GetXPCOMSingleton() {
211 if (IsNeckoChild()) {
212 return CookieServiceChild::GetSingleton();
215 return GetSingleton();
218 already_AddRefed<CookieService> CookieService::GetSingleton() {
219 NS_ASSERTION(!IsNeckoChild(), "not a parent process");
221 if (gCookieService) {
222 return do_AddRef(gCookieService);
225 // Create a new singleton CookieService.
226 // We AddRef only once since XPCOM has rules about the ordering of module
227 // teardowns - by the time our module destructor is called, it's too late to
228 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
229 // cycles have already been completed and would result in serious leaks.
230 // See bug 209571.
231 // TODO: Verify what is the earliest point in time during shutdown where
232 // we can deny the creation of the CookieService as a whole.
233 gCookieService = new CookieService();
234 if (gCookieService) {
235 if (NS_SUCCEEDED(gCookieService->Init())) {
236 ClearOnShutdown(&gCookieService);
237 } else {
238 gCookieService = nullptr;
242 return do_AddRef(gCookieService);
245 /******************************************************************************
246 * CookieService impl:
247 * public methods
248 ******************************************************************************/
250 NS_IMPL_ISUPPORTS(CookieService, nsICookieService, nsICookieManager,
251 nsIObserver, nsISupportsWeakReference, nsIMemoryReporter)
253 CookieService::CookieService() = default;
255 nsresult CookieService::Init() {
256 nsresult rv;
257 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
258 NS_ENSURE_SUCCESS(rv, rv);
260 mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
261 NS_ENSURE_SUCCESS(rv, rv);
263 mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
264 NS_ENSURE_SUCCESS(rv, rv);
266 // Init our default, and possibly private CookieStorages.
267 InitCookieStorages();
269 // Migrate network.cookie.lifetimePolicy pref to sanitizeOnShutdown prefs
270 MigrateCookieLifetimePrefs();
272 RegisterWeakMemoryReporter(this);
274 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
275 NS_ENSURE_STATE(os);
276 os->AddObserver(this, "profile-before-change", true);
277 os->AddObserver(this, "profile-do-change", true);
278 os->AddObserver(this, "last-pb-context-exited", true);
280 return NS_OK;
283 void CookieService::InitCookieStorages() {
284 NS_ASSERTION(!mPersistentStorage, "already have a default CookieStorage");
285 NS_ASSERTION(!mPrivateStorage, "already have a private CookieStorage");
287 // Create two new CookieStorages. If we are in or beyond our observed
288 // shutdown phase, just be non-persistent.
289 if (MOZ_UNLIKELY(StaticPrefs::network_cookie_noPersistentStorage() ||
290 AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) {
291 mPersistentStorage = CookiePrivateStorage::Create();
292 } else {
293 mPersistentStorage = CookiePersistentStorage::Create();
296 mPrivateStorage = CookiePrivateStorage::Create();
299 void CookieService::CloseCookieStorages() {
300 // return if we already closed
301 if (!mPersistentStorage) {
302 return;
305 // Let's nullify both storages before calling Close().
306 RefPtr<CookieStorage> privateStorage;
307 privateStorage.swap(mPrivateStorage);
309 RefPtr<CookieStorage> persistentStorage;
310 persistentStorage.swap(mPersistentStorage);
312 privateStorage->Close();
313 persistentStorage->Close();
316 CookieService::~CookieService() {
317 CloseCookieStorages();
319 UnregisterWeakMemoryReporter(this);
321 gCookieService = nullptr;
324 NS_IMETHODIMP
325 CookieService::Observe(nsISupports* /*aSubject*/, const char* aTopic,
326 const char16_t* /*aData*/) {
327 // check the topic
328 if (!strcmp(aTopic, "profile-before-change")) {
329 // The profile is about to change,
330 // or is going away because the application is shutting down.
332 // Close the default DB connection and null out our CookieStorages before
333 // changing.
334 CloseCookieStorages();
336 } else if (!strcmp(aTopic, "profile-do-change")) {
337 NS_ASSERTION(!mPersistentStorage, "shouldn't have a default CookieStorage");
338 NS_ASSERTION(!mPrivateStorage, "shouldn't have a private CookieStorage");
340 // the profile has already changed; init the db from the new location.
341 // if we are in the private browsing state, however, we do not want to read
342 // data into it - we should instead put it into the default state, so it's
343 // ready for us if and when we switch back to it.
344 InitCookieStorages();
346 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
347 // Flush all the cookies stored by private browsing contexts
348 OriginAttributesPattern pattern;
349 pattern.mPrivateBrowsingId.Construct(1);
350 RemoveCookiesWithOriginAttributes(pattern, ""_ns);
351 mPrivateStorage = CookiePrivateStorage::Create();
354 return NS_OK;
357 NS_IMETHODIMP
358 CookieService::GetCookieBehavior(bool aIsPrivate, uint32_t* aCookieBehavior) {
359 NS_ENSURE_ARG_POINTER(aCookieBehavior);
360 *aCookieBehavior = nsICookieManager::GetCookieBehavior(aIsPrivate);
361 return NS_OK;
364 NS_IMETHODIMP
365 CookieService::GetCookieStringFromDocument(Document* aDocument,
366 nsACString& aCookie) {
367 NS_ENSURE_ARG(aDocument);
369 nsresult rv;
371 aCookie.Truncate();
373 if (!IsInitialized()) {
374 return NS_OK;
377 nsCOMPtr<nsIPrincipal> cookiePrincipal =
378 aDocument->EffectiveCookiePrincipal();
380 // TODO (Bug 1874174): A document could access both unpartitioned and
381 // partitioned cookie jars. We will need to prepare partitioned and
382 // unpartitioned principals for access both cookie jars.
383 nsTArray<nsCOMPtr<nsIPrincipal>> principals;
384 principals.AppendElement(cookiePrincipal);
386 bool thirdParty = true;
387 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
388 // in gtests we don't have a window, let's consider those requests as 3rd
389 // party.
390 if (innerWindow) {
391 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
393 if (thirdPartyUtil) {
394 Unused << thirdPartyUtil->IsThirdPartyWindow(
395 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
399 nsTArray<Cookie*> cookieList;
401 for (auto& principal : principals) {
402 if (!CookieCommons::IsSchemeSupported(principal)) {
403 return NS_OK;
406 CookieStorage* storage = PickStorage(principal->OriginAttributesRef());
408 nsAutoCString baseDomain;
409 rv = CookieCommons::GetBaseDomain(principal, baseDomain);
410 if (NS_WARN_IF(NS_FAILED(rv))) {
411 return NS_OK;
414 nsAutoCString hostFromURI;
415 rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
416 if (NS_WARN_IF(NS_FAILED(rv))) {
417 return NS_OK;
420 nsAutoCString pathFromURI;
421 rv = principal->GetFilePath(pathFromURI);
422 if (NS_WARN_IF(NS_FAILED(rv))) {
423 return NS_OK;
426 int64_t currentTimeInUsec = PR_Now();
427 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
429 const nsTArray<RefPtr<Cookie>>* cookies = storage->GetCookiesFromHost(
430 baseDomain, principal->OriginAttributesRef());
431 if (!cookies) {
432 continue;
435 // check if the nsIPrincipal is using an https secure protocol.
436 // if it isn't, then we can't send a secure cookie over the connection.
437 bool potentiallyTrustworthy =
438 principal->GetIsOriginPotentiallyTrustworthy();
440 bool stale = false;
442 // iterate the cookies!
443 for (Cookie* cookie : *cookies) {
444 // check the host, since the base domain lookup is conservative.
445 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
446 continue;
449 // if the cookie is httpOnly and it's not going directly to the HTTP
450 // connection, don't send it
451 if (cookie->IsHttpOnly()) {
452 continue;
455 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
456 cookie, aDocument)) {
457 continue;
460 // if the cookie is secure and the host scheme isn't, we can't send it
461 if (cookie->IsSecure() && !potentiallyTrustworthy) {
462 continue;
465 // if the nsIURI path doesn't match the cookie path, don't send it back
466 if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
467 continue;
470 // check if the cookie has expired
471 if (cookie->Expiry() <= currentTime) {
472 continue;
475 // all checks passed - add to list and check if lastAccessed stamp needs
476 // updating
477 cookieList.AppendElement(cookie);
478 if (cookie->IsStale()) {
479 stale = true;
483 if (cookieList.IsEmpty()) {
484 continue;
487 // update lastAccessed timestamps. we only do this if the timestamp is stale
488 // by a certain amount, to avoid thrashing the db during pageload.
489 if (stale) {
490 storage->StaleCookies(cookieList, currentTimeInUsec);
494 if (cookieList.IsEmpty()) {
495 return NS_OK;
498 // return cookies in order of path length; longest to shortest.
499 // this is required per RFC2109. if cookies match in length,
500 // then sort by creation time (see bug 236772).
501 cookieList.Sort(CompareCookiesForSending());
502 ComposeCookieString(cookieList, aCookie);
504 return NS_OK;
507 NS_IMETHODIMP
508 CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel,
509 nsACString& aCookieString) {
510 NS_ENSURE_ARG(aHostURI);
511 NS_ENSURE_ARG(aChannel);
513 aCookieString.Truncate();
515 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
516 return NS_OK;
519 uint32_t rejectedReason = 0;
520 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
521 aChannel, false, aHostURI, nullptr, &rejectedReason);
523 OriginAttributes attrs;
524 StoragePrincipalHelper::GetOriginAttributes(
525 aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
527 bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
528 bool hadCrossSiteRedirects = false;
529 bool isSameSiteForeign = CookieCommons::IsSameSiteForeign(
530 aChannel, aHostURI, &hadCrossSiteRedirects);
532 // TODO (Bug 1874174): A channel could load both unpartitioned and partitioned
533 // cookie jars together. We will need to get cookies from both unpartitioned
534 // and partitioned cookie jars according to storage access.
535 nsTArray<OriginAttributes> originAttributesList;
536 originAttributesList.AppendElement(attrs);
538 AutoTArray<Cookie*, 8> foundCookieList;
539 GetCookiesForURI(
540 aHostURI, aChannel, result.contains(ThirdPartyAnalysis::IsForeign),
541 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
542 result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
543 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
544 rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
545 hadCrossSiteRedirects, true, false, originAttributesList,
546 foundCookieList);
548 ComposeCookieString(foundCookieList, aCookieString);
550 if (!aCookieString.IsEmpty()) {
551 COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
553 return NS_OK;
556 NS_IMETHODIMP
557 CookieService::SetCookieStringFromDocument(Document* aDocument,
558 const nsACString& aCookieString) {
559 NS_ENSURE_ARG(aDocument);
561 if (!IsInitialized()) {
562 return NS_OK;
565 nsCOMPtr<nsIURI> documentURI;
566 nsAutoCString baseDomain;
567 OriginAttributes attrs;
569 int64_t currentTimeInUsec = PR_Now();
571 // This function is executed in this context, I don't need to keep objects
572 // alive.
573 auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
574 const OriginAttributes& aAttrs) {
575 CookieStorage* storage = PickStorage(aAttrs);
576 return !!storage->CountCookiesFromHost(aBaseDomain,
577 aAttrs.mPrivateBrowsingId);
580 RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
581 aDocument, aCookieString, currentTimeInUsec, mTLDService, mThirdPartyUtil,
582 hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
583 if (!cookie) {
584 return NS_OK;
587 bool thirdParty = true;
588 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
589 // in gtests we don't have a window, let's consider those requests as 3rd
590 // party.
591 if (innerWindow) {
592 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
594 if (thirdPartyUtil) {
595 Unused << thirdPartyUtil->IsThirdPartyWindow(
596 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
600 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
601 cookie, aDocument)) {
602 return NS_OK;
605 nsCOMPtr<nsIConsoleReportCollector> crc =
606 do_QueryInterface(aDocument->GetChannel());
608 // add the cookie to the list. AddCookie() takes care of logging.
609 PickStorage(attrs)->AddCookie(crc, baseDomain, attrs, cookie,
610 currentTimeInUsec, documentURI, aCookieString,
611 false, aDocument->GetBrowsingContext());
612 return NS_OK;
615 NS_IMETHODIMP
616 CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
617 const nsACString& aCookieHeader,
618 nsIChannel* aChannel) {
619 NS_ENSURE_ARG(aHostURI);
620 NS_ENSURE_ARG(aChannel);
622 if (!IsInitialized()) {
623 return NS_OK;
626 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
627 return NS_OK;
630 uint32_t rejectedReason = 0;
631 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
632 aChannel, false, aHostURI, nullptr, &rejectedReason);
634 OriginAttributes attrs;
635 StoragePrincipalHelper::GetOriginAttributes(
636 aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
638 // get the base domain for the host URI.
639 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
640 // file:// URI's (i.e. with an empty host) are allowed, but any other
641 // scheme must have a non-empty host. A trailing dot in the host
642 // is acceptable.
643 bool requireHostMatch;
644 nsAutoCString baseDomain;
645 nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
646 requireHostMatch);
647 if (NS_FAILED(rv)) {
648 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
649 "couldn't get base domain from URI");
650 return NS_OK;
653 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
654 CookieCommons::GetCookieJarSettings(aChannel);
656 nsAutoCString hostFromURI;
657 nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
659 nsAutoCString baseDomainFromURI;
660 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, hostFromURI,
661 baseDomainFromURI);
662 NS_ENSURE_SUCCESS(rv, NS_OK);
664 CookieStorage* storage = PickStorage(attrs);
666 // check default prefs
667 uint32_t priorCookieCount = storage->CountCookiesFromHost(
668 baseDomainFromURI, attrs.mPrivateBrowsingId);
670 nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
672 CookieStatus cookieStatus = CheckPrefs(
673 crc, cookieJarSettings, aHostURI,
674 result.contains(ThirdPartyAnalysis::IsForeign),
675 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
676 result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
677 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
678 aCookieHeader, priorCookieCount, attrs, &rejectedReason);
680 MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
682 // fire a notification if third party or if cookie was rejected
683 // (but not if there was an error)
684 switch (cookieStatus) {
685 case STATUS_REJECTED:
686 CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
687 OPERATION_WRITE);
688 return NS_OK; // Stop here
689 case STATUS_REJECTED_WITH_ERROR:
690 CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
691 OPERATION_WRITE);
692 return NS_OK;
693 case STATUS_ACCEPTED: // Fallthrough
694 case STATUS_ACCEPT_SESSION:
695 NotifyAccepted(aChannel);
696 break;
697 default:
698 break;
701 bool addonAllowsLoad = false;
702 nsCOMPtr<nsIURI> channelURI;
703 NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
704 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
705 addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
706 ->AddonAllowsLoad(channelURI);
708 bool isForeignAndNotAddon = false;
709 if (!addonAllowsLoad) {
710 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
711 &isForeignAndNotAddon);
714 bool mustBePartitioned =
715 isForeignAndNotAddon &&
716 cookieJarSettings->GetCookieBehavior() ==
717 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
718 !result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
720 nsCString cookieHeader(aCookieHeader);
722 bool moreCookieToRead = true;
724 // process each cookie in the header
725 while (moreCookieToRead) {
726 CookieStruct cookieData;
727 bool canSetCookie = false;
729 moreCookieToRead =
730 CanSetCookie(aHostURI, baseDomain, cookieData, requireHostMatch,
731 cookieStatus, cookieHeader, true, isForeignAndNotAddon,
732 mustBePartitioned, crc, canSetCookie);
734 if (!canSetCookie) {
735 continue;
738 // check permissions from site permission list.
739 if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
740 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
741 "cookie rejected by permission manager");
742 CookieCommons::NotifyRejected(
743 aHostURI, aChannel,
744 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
745 OPERATION_WRITE);
746 CookieLogging::LogMessageToConsole(
747 crc, aHostURI, nsIScriptError::warningFlag,
748 CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
749 AutoTArray<nsString, 1>{
750 NS_ConvertUTF8toUTF16(cookieData.name()),
752 continue;
755 // create a new Cookie
756 RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
757 MOZ_ASSERT(cookie);
759 int64_t currentTimeInUsec = PR_Now();
760 cookie->SetLastAccessed(currentTimeInUsec);
761 cookie->SetCreationTime(
762 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
764 RefPtr<BrowsingContext> bc = loadInfo->GetBrowsingContext();
766 // add the cookie to the list. AddCookie() takes care of logging.
767 storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec,
768 aHostURI, aCookieHeader, true, bc);
771 return NS_OK;
774 void CookieService::NotifyAccepted(nsIChannel* aChannel) {
775 ContentBlockingNotifier::OnDecision(
776 aChannel, ContentBlockingNotifier::BlockingDecision::eAllow, 0);
779 /******************************************************************************
780 * CookieService:
781 * public transaction helper impl
782 ******************************************************************************/
784 NS_IMETHODIMP
785 CookieService::RunInTransaction(nsICookieTransactionCallback* aCallback) {
786 NS_ENSURE_ARG(aCallback);
788 if (!IsInitialized()) {
789 return NS_ERROR_NOT_AVAILABLE;
792 mPersistentStorage->EnsureInitialized();
793 return mPersistentStorage->RunInTransaction(aCallback);
796 /******************************************************************************
797 * nsICookieManager impl:
798 * nsICookieManager
799 ******************************************************************************/
801 NS_IMETHODIMP
802 CookieService::RemoveAll() {
803 if (!IsInitialized()) {
804 return NS_ERROR_NOT_AVAILABLE;
807 mPersistentStorage->EnsureInitialized();
808 mPersistentStorage->RemoveAll();
809 return NS_OK;
812 NS_IMETHODIMP
813 CookieService::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
814 if (!IsInitialized()) {
815 return NS_ERROR_NOT_AVAILABLE;
818 mPersistentStorage->EnsureInitialized();
820 // We expose only non-private cookies.
821 mPersistentStorage->GetCookies(aCookies);
823 return NS_OK;
826 NS_IMETHODIMP
827 CookieService::GetSessionCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
828 if (!IsInitialized()) {
829 return NS_ERROR_NOT_AVAILABLE;
832 mPersistentStorage->EnsureInitialized();
834 // We expose only non-private cookies.
835 mPersistentStorage->GetSessionCookies(aCookies);
837 return NS_OK;
840 NS_IMETHODIMP
841 CookieService::Add(const nsACString& aHost, const nsACString& aPath,
842 const nsACString& aName, const nsACString& aValue,
843 bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
844 int64_t aExpiry, JS::Handle<JS::Value> aOriginAttributes,
845 int32_t aSameSite, nsICookie::schemeType aSchemeMap,
846 JSContext* aCx) {
847 OriginAttributes attrs;
849 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
850 return NS_ERROR_INVALID_ARG;
853 return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
854 aIsSession, aExpiry, &attrs, aSameSite, aSchemeMap);
857 NS_IMETHODIMP_(nsresult)
858 CookieService::AddNative(const nsACString& aHost, const nsACString& aPath,
859 const nsACString& aName, const nsACString& aValue,
860 bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
861 int64_t aExpiry, OriginAttributes* aOriginAttributes,
862 int32_t aSameSite, nsICookie::schemeType aSchemeMap) {
863 if (NS_WARN_IF(!aOriginAttributes)) {
864 return NS_ERROR_FAILURE;
867 if (!IsInitialized()) {
868 return NS_ERROR_NOT_AVAILABLE;
871 // first, normalize the hostname, and fail if it contains illegal characters.
872 nsAutoCString host(aHost);
873 nsresult rv = NormalizeHost(host);
874 NS_ENSURE_SUCCESS(rv, rv);
876 // get the base domain for the host URI.
877 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
878 nsAutoCString baseDomain;
879 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
880 NS_ENSURE_SUCCESS(rv, rv);
882 int64_t currentTimeInUsec = PR_Now();
883 CookieKey key = CookieKey(baseDomain, *aOriginAttributes);
885 CookieStruct cookieData(nsCString(aName), nsCString(aValue), nsCString(aHost),
886 nsCString(aPath), aExpiry, currentTimeInUsec,
887 Cookie::GenerateUniqueCreationTime(currentTimeInUsec),
888 aIsHttpOnly, aIsSession, aIsSecure, false, aSameSite,
889 aSameSite, aSchemeMap);
891 RefPtr<Cookie> cookie = Cookie::Create(cookieData, key.mOriginAttributes);
892 MOZ_ASSERT(cookie);
894 CookieStorage* storage = PickStorage(*aOriginAttributes);
895 storage->AddCookie(nullptr, baseDomain, *aOriginAttributes, cookie,
896 currentTimeInUsec, nullptr, VoidCString(), true, nullptr);
897 return NS_OK;
900 nsresult CookieService::Remove(const nsACString& aHost,
901 const OriginAttributes& aAttrs,
902 const nsACString& aName,
903 const nsACString& aPath) {
904 // first, normalize the hostname, and fail if it contains illegal characters.
905 nsAutoCString host(aHost);
906 nsresult rv = NormalizeHost(host);
907 NS_ENSURE_SUCCESS(rv, rv);
909 nsAutoCString baseDomain;
910 if (!host.IsEmpty()) {
911 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
912 NS_ENSURE_SUCCESS(rv, rv);
915 if (!IsInitialized()) {
916 return NS_ERROR_NOT_AVAILABLE;
919 CookieStorage* storage = PickStorage(aAttrs);
920 storage->RemoveCookie(baseDomain, aAttrs, host, PromiseFlatCString(aName),
921 PromiseFlatCString(aPath));
923 return NS_OK;
926 NS_IMETHODIMP
927 CookieService::Remove(const nsACString& aHost, const nsACString& aName,
928 const nsACString& aPath,
929 JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx) {
930 OriginAttributes attrs;
932 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
933 return NS_ERROR_INVALID_ARG;
936 return RemoveNative(aHost, aName, aPath, &attrs);
939 NS_IMETHODIMP_(nsresult)
940 CookieService::RemoveNative(const nsACString& aHost, const nsACString& aName,
941 const nsACString& aPath,
942 OriginAttributes* aOriginAttributes) {
943 if (NS_WARN_IF(!aOriginAttributes)) {
944 return NS_ERROR_FAILURE;
947 nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath);
948 if (NS_WARN_IF(NS_FAILED(rv))) {
949 return rv;
952 return NS_OK;
955 void CookieService::GetCookiesForURI(
956 nsIURI* aHostURI, nsIChannel* aChannel, bool aIsForeign,
957 bool aIsThirdPartyTrackingResource,
958 bool aIsThirdPartySocialTrackingResource,
959 bool aStorageAccessPermissionGranted, uint32_t aRejectedReason,
960 bool aIsSafeTopLevelNav, bool aIsSameSiteForeign,
961 bool aHadCrossSiteRedirects, bool aHttpBound,
962 bool aAllowSecureCookiesToInsecureOrigin,
963 const nsTArray<OriginAttributes>& aOriginAttrsList,
964 nsTArray<Cookie*>& aCookieList) {
965 NS_ASSERTION(aHostURI, "null host!");
967 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
968 return;
971 if (!IsInitialized()) {
972 return;
975 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
976 CookieCommons::GetCookieJarSettings(aChannel);
978 nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
980 for (const auto& attrs : aOriginAttrsList) {
981 CookieStorage* storage = PickStorage(attrs);
983 // get the base domain, host, and path from the URI.
984 // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
985 // file:// URI's (i.e. with an empty host) are allowed, but any other
986 // scheme must have a non-empty host. A trailing dot in the host
987 // is acceptable.
988 bool requireHostMatch;
989 nsAutoCString baseDomain;
990 nsAutoCString hostFromURI;
991 nsAutoCString pathFromURI;
992 nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI,
993 baseDomain, requireHostMatch);
994 if (NS_SUCCEEDED(rv)) {
995 rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
997 if (NS_SUCCEEDED(rv)) {
998 rv = aHostURI->GetFilePath(pathFromURI);
1000 if (NS_FAILED(rv)) {
1001 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(),
1002 "invalid host/path from URI");
1003 return;
1006 nsAutoCString normalizedHostFromURI(hostFromURI);
1007 rv = NormalizeHost(normalizedHostFromURI);
1008 NS_ENSURE_SUCCESS_VOID(rv);
1010 nsAutoCString baseDomainFromURI;
1011 rv = CookieCommons::GetBaseDomainFromHost(
1012 mTLDService, normalizedHostFromURI, baseDomainFromURI);
1013 NS_ENSURE_SUCCESS_VOID(rv);
1015 // check default prefs
1016 uint32_t rejectedReason = aRejectedReason;
1017 uint32_t priorCookieCount = storage->CountCookiesFromHost(
1018 baseDomainFromURI, attrs.mPrivateBrowsingId);
1020 CookieStatus cookieStatus = CheckPrefs(
1021 crc, cookieJarSettings, aHostURI, aIsForeign,
1022 aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource,
1023 aStorageAccessPermissionGranted, VoidCString(), priorCookieCount, attrs,
1024 &rejectedReason);
1026 MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
1028 // for GetCookie(), we only fire acceptance/rejection notifications
1029 // (but not if there was an error)
1030 switch (cookieStatus) {
1031 case STATUS_REJECTED:
1032 // If we don't have any cookies from this host, fail silently.
1033 if (priorCookieCount) {
1034 CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
1035 OPERATION_READ);
1037 return;
1038 default:
1039 break;
1042 // Note: The following permissions logic is mirrored in
1043 // extensions::MatchPattern::MatchesCookie.
1044 // If it changes, please update that function, or file a bug for someone
1045 // else to do so.
1047 // check if aHostURI is using an https secure protocol.
1048 // if it isn't, then we can't send a secure cookie over the connection.
1049 // if SchemeIs fails, assume an insecure connection, to be on the safe side
1050 bool potentiallyTrustworthy =
1051 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
1053 int64_t currentTimeInUsec = PR_Now();
1054 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
1055 bool stale = false;
1057 const nsTArray<RefPtr<Cookie>>* cookies =
1058 storage->GetCookiesFromHost(baseDomain, attrs);
1059 if (!cookies) {
1060 continue;
1063 bool laxByDefault =
1064 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1065 !nsContentUtils::IsURIInPrefList(
1066 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1068 // iterate the cookies!
1069 for (Cookie* cookie : *cookies) {
1070 // check the host, since the base domain lookup is conservative.
1071 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
1072 continue;
1075 // if the cookie is secure and the host scheme isn't, we avoid sending
1076 // cookie if possible. But for process synchronization purposes, we may
1077 // want the content process to know about the cookie (without it's value).
1078 // In which case we will wipe the value before sending
1079 if (cookie->IsSecure() && !potentiallyTrustworthy &&
1080 !aAllowSecureCookiesToInsecureOrigin) {
1081 continue;
1084 // if the cookie is httpOnly and it's not going directly to the HTTP
1085 // connection, don't send it
1086 if (cookie->IsHttpOnly() && !aHttpBound) {
1087 continue;
1090 // if the nsIURI path doesn't match the cookie path, don't send it back
1091 if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
1092 continue;
1095 // check if the cookie has expired
1096 if (cookie->Expiry() <= currentTime) {
1097 continue;
1100 if (aHttpBound && aIsSameSiteForeign) {
1101 bool blockCookie = !ProcessSameSiteCookieForForeignRequest(
1102 aChannel, cookie, aIsSafeTopLevelNav, aHadCrossSiteRedirects,
1103 laxByDefault);
1105 if (blockCookie) {
1106 if (aHadCrossSiteRedirects) {
1107 CookieLogging::LogMessageToConsole(
1108 crc, aHostURI, nsIScriptError::warningFlag,
1109 CONSOLE_REJECTION_CATEGORY, "CookieBlockedCrossSiteRedirect"_ns,
1110 AutoTArray<nsString, 1>{
1111 NS_ConvertUTF8toUTF16(cookie->Name()),
1114 continue;
1118 // all checks passed - add to list and check if lastAccessed stamp needs
1119 // updating
1120 aCookieList.AppendElement(cookie);
1121 if (cookie->IsStale()) {
1122 stale = true;
1126 if (aCookieList.IsEmpty()) {
1127 continue;
1130 // update lastAccessed timestamps. we only do this if the timestamp is stale
1131 // by a certain amount, to avoid thrashing the db during pageload.
1132 if (stale) {
1133 storage->StaleCookies(aCookieList, currentTimeInUsec);
1137 if (aCookieList.IsEmpty()) {
1138 return;
1141 // Send a notification about the acceptance of the cookies now that we found
1142 // some.
1143 NotifyAccepted(aChannel);
1145 // return cookies in order of path length; longest to shortest.
1146 // this is required per RFC2109. if cookies match in length,
1147 // then sort by creation time (see bug 236772).
1148 aCookieList.Sort(CompareCookiesForSending());
1151 static bool ContainsUnicodeChars(const nsCString& str) {
1152 const auto* start = str.BeginReading();
1153 const auto* end = str.EndReading();
1155 return std::find_if(start, end, [](unsigned char c) { return c >= 0x80; }) !=
1156 end;
1159 static void RecordUnicodeTelemetry(const CookieStruct& cookieData) {
1160 auto label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::none;
1161 if (ContainsUnicodeChars(cookieData.name())) {
1162 label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeName;
1163 } else if (ContainsUnicodeChars(cookieData.value())) {
1164 label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeValue;
1166 Telemetry::AccumulateCategorical(label);
1169 static void RecordPartitionedTelemetry(const CookieStruct& aCookieData,
1170 bool aIsForeign) {
1171 mozilla::glean::networking::set_cookie.Add(1);
1172 if (aCookieData.isPartitioned()) {
1173 mozilla::glean::networking::set_cookie_partitioned.AddToNumerator(1);
1175 if (aIsForeign) {
1176 mozilla::glean::networking::set_cookie_foreign.AddToNumerator(1);
1178 if (aIsForeign && aCookieData.isPartitioned()) {
1179 mozilla::glean::networking::set_cookie_foreign_partitioned.AddToNumerator(
1184 bool HasSecurePrefix(const nsCString& aString) {
1185 static const char kSecure[] = "__Secure-";
1186 static constexpr uint32_t kSecureLen = sizeof(kSecure) - 1;
1187 return nsCRT::strncasecmp(aString.get(), kSecure, kSecureLen) == 0;
1190 bool HasHostPrefix(const nsCString& aString) {
1191 static const char kHost[] = "__Host-";
1192 static constexpr uint32_t kHostLen = sizeof(kHost) - 1;
1193 return nsCRT::strncasecmp(aString.get(), kHost, kHostLen) == 0;
1196 // processes a single cookie, and returns true if there are more cookies
1197 // to be processed
1198 bool CookieService::CanSetCookie(
1199 nsIURI* aHostURI, const nsACString& aBaseDomain, CookieStruct& aCookieData,
1200 bool aRequireHostMatch, CookieStatus aStatus, nsCString& aCookieHeader,
1201 bool aFromHttp, bool aIsForeignAndNotAddon, bool aPartitionedOnly,
1202 nsIConsoleReportCollector* aCRC, bool& aSetCookie) {
1203 MOZ_ASSERT(aHostURI);
1205 aSetCookie = false;
1207 // init expiryTime such that session cookies won't prematurely expire
1208 aCookieData.expiry() = INT64_MAX;
1210 aCookieData.schemeMap() = CookieCommons::URIToSchemeType(aHostURI);
1212 // aCookieHeader is an in/out param to point to the next cookie, if
1213 // there is one. Save the present value for logging purposes
1214 nsCString savedCookieHeader(aCookieHeader);
1216 // newCookie says whether there are multiple cookies in the header;
1217 // so we can handle them separately.
1218 nsAutoCString expires;
1219 nsAutoCString maxage;
1220 bool acceptedByParser = false;
1221 bool newCookie = ParseAttributes(aCRC, aHostURI, aCookieHeader, aCookieData,
1222 expires, maxage, acceptedByParser);
1223 if (!acceptedByParser) {
1224 return newCookie;
1227 // Collect telemetry on how often secure cookies are set from non-secure
1228 // origins, and vice-versa.
1230 // 0 = nonsecure and "http:"
1231 // 1 = nonsecure and "https:"
1232 // 2 = secure and "http:"
1233 // 3 = secure and "https:"
1234 bool potentiallyTrustworthy =
1235 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
1237 int64_t currentTimeInUsec = PR_Now();
1239 // calculate expiry time of cookie.
1240 aCookieData.isSession() =
1241 GetExpiry(aCookieData, expires, maxage,
1242 currentTimeInUsec / PR_USEC_PER_SEC, aFromHttp);
1243 if (aStatus == STATUS_ACCEPT_SESSION) {
1244 // force lifetime to session. note that the expiration time, if set above,
1245 // will still apply.
1246 aCookieData.isSession() = true;
1249 // reject cookie if it's over the size limit, per RFC2109
1250 if (!CookieCommons::CheckNameAndValueSize(aCookieData)) {
1251 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1252 "cookie too big (> 4kb)");
1254 AutoTArray<nsString, 2> params = {
1255 NS_ConvertUTF8toUTF16(aCookieData.name())};
1257 nsString size;
1258 size.AppendInt(kMaxBytesPerCookie);
1259 params.AppendElement(size);
1261 CookieLogging::LogMessageToConsole(
1262 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
1263 "CookieOversize"_ns, params);
1264 return newCookie;
1267 RecordUnicodeTelemetry(aCookieData);
1269 // We count SetCookie operations in the parent process only for HTTP set
1270 // cookies to prevent double counting.
1271 if (XRE_IsParentProcess() || !aFromHttp) {
1272 RecordPartitionedTelemetry(aCookieData, aIsForeignAndNotAddon);
1275 if (!CookieCommons::CheckName(aCookieData)) {
1276 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1277 "invalid name character");
1278 CookieLogging::LogMessageToConsole(
1279 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1280 "CookieRejectedInvalidCharName"_ns,
1281 AutoTArray<nsString, 1>{
1282 NS_ConvertUTF8toUTF16(aCookieData.name()),
1284 return newCookie;
1287 // domain & path checks
1288 if (!CheckDomain(aCookieData, aHostURI, aBaseDomain, aRequireHostMatch)) {
1289 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1290 "failed the domain tests");
1291 CookieLogging::LogMessageToConsole(
1292 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1293 "CookieRejectedInvalidDomain"_ns,
1294 AutoTArray<nsString, 1>{
1295 NS_ConvertUTF8toUTF16(aCookieData.name()),
1297 return newCookie;
1300 if (!CheckPath(aCookieData, aCRC, aHostURI)) {
1301 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1302 "failed the path tests");
1303 return newCookie;
1306 // If a cookie is nameless, then its value must not start with
1307 // `__Host-` or `__Secure-`
1308 if (aCookieData.name().IsEmpty() && (HasSecurePrefix(aCookieData.value()) ||
1309 HasHostPrefix(aCookieData.value()))) {
1310 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1311 "failed hidden prefix tests");
1312 CookieLogging::LogMessageToConsole(
1313 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1314 "CookieRejectedInvalidPrefix"_ns,
1315 AutoTArray<nsString, 1>{
1316 NS_ConvertUTF8toUTF16(aCookieData.name()),
1318 return newCookie;
1321 // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
1322 if (!CheckPrefixes(aCookieData, potentiallyTrustworthy)) {
1323 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1324 "failed the prefix tests");
1325 CookieLogging::LogMessageToConsole(
1326 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1327 "CookieRejectedInvalidPrefix"_ns,
1328 AutoTArray<nsString, 1>{
1329 NS_ConvertUTF8toUTF16(aCookieData.name()),
1331 return newCookie;
1334 if (!CookieCommons::CheckValue(aCookieData)) {
1335 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1336 "invalid value character");
1337 CookieLogging::LogMessageToConsole(
1338 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1339 "CookieRejectedInvalidCharValue"_ns,
1340 AutoTArray<nsString, 1>{
1341 NS_ConvertUTF8toUTF16(aCookieData.name()),
1343 return newCookie;
1346 // if the new cookie is httponly, make sure we're not coming from script
1347 if (!aFromHttp && aCookieData.isHttpOnly()) {
1348 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1349 "cookie is httponly; coming from script");
1350 CookieLogging::LogMessageToConsole(
1351 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1352 "CookieRejectedHttpOnlyButFromScript"_ns,
1353 AutoTArray<nsString, 1>{
1354 NS_ConvertUTF8toUTF16(aCookieData.name()),
1356 return newCookie;
1359 // If the new cookie is non-https and wants to set secure flag,
1360 // browser have to ignore this new cookie.
1361 // (draft-ietf-httpbis-cookie-alone section 3.1)
1362 if (aCookieData.isSecure() && !potentiallyTrustworthy) {
1363 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
1364 "non-https cookie can't set secure flag");
1365 CookieLogging::LogMessageToConsole(
1366 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1367 "CookieRejectedSecureButNonHttps"_ns,
1368 AutoTArray<nsString, 1>{
1369 NS_ConvertUTF8toUTF16(aCookieData.name()),
1371 return newCookie;
1374 // If the new cookie is same-site but in a cross site context,
1375 // browser must ignore the cookie.
1376 bool laxByDefault =
1377 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1378 !nsContentUtils::IsURIInPrefList(
1379 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1380 auto effectiveSameSite =
1381 laxByDefault ? aCookieData.sameSite() : aCookieData.rawSameSite();
1382 if ((effectiveSameSite != nsICookie::SAMESITE_NONE) &&
1383 aIsForeignAndNotAddon) {
1384 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1385 "failed the samesite tests");
1387 CookieLogging::LogMessageToConsole(
1388 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
1389 "CookieRejectedForNonSameSiteness"_ns,
1390 AutoTArray<nsString, 1>{
1391 NS_ConvertUTF8toUTF16(aCookieData.name()),
1393 return newCookie;
1396 // If the cookie does not have the partitioned attribute,
1397 // but is foreign we should give the developer a message.
1398 // If CHIPS isn't required yet, we will warn the console
1399 // that we have upcoming changes. Otherwise we give a rejection message.
1400 if (aPartitionedOnly && !aCookieData.isPartitioned() &&
1401 aIsForeignAndNotAddon) {
1402 if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
1403 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1404 "foreign cookies must be partitioned");
1405 CookieLogging::LogMessageToConsole(
1406 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
1407 "CookieForeignNoPartitionedError"_ns,
1408 AutoTArray<nsString, 1>{
1409 NS_ConvertUTF8toUTF16(aCookieData.name()),
1411 return newCookie;
1413 CookieLogging::LogMessageToConsole(
1414 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
1415 "CookieForeignNoPartitionedWarning"_ns,
1416 AutoTArray<nsString, 1>{
1417 NS_ConvertUTF8toUTF16(aCookieData.name()),
1421 aSetCookie = true;
1422 return newCookie;
1425 /******************************************************************************
1426 * CookieService impl:
1427 * private cookie header parsing functions
1428 ******************************************************************************/
1430 // clang-format off
1431 // The following comment block elucidates the function of ParseAttributes.
1432 /******************************************************************************
1433 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1434 ** please note: this BNF deviates from both specifications, and reflects this
1435 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1437 ** Differences from RFC2109/2616 and explanations:
1438 1. implied *LWS
1439 The grammar described by this specification is word-based. Except
1440 where noted otherwise, linear white space (<LWS>) can be included
1441 between any two adjacent words (token or quoted-string), and
1442 between adjacent words and separators, without changing the
1443 interpretation of a field.
1444 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1446 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1447 common use inside values.
1449 3. tokens and values have looser restrictions on allowed characters than
1450 spec. This is also due to certain characters being in common use inside
1451 values. We allow only '=' to separate token/value pairs, and ';' to
1452 terminate tokens or values. <LWS> is allowed within tokens and values
1453 (see bug 206022).
1455 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1456 reject control chars or non-ASCII chars. This is erring on the loose
1457 side, since there's probably no good reason to enforce this strictness.
1459 5. Attribute "HttpOnly", not covered in the RFCs, is supported
1460 (see bug 178993).
1462 ** Begin BNF:
1463 token = 1*<any allowed-chars except separators>
1464 value = 1*<any allowed-chars except value-sep>
1465 separators = ";" | "="
1466 value-sep = ";"
1467 cookie-sep = CR | LF
1468 allowed-chars = <any OCTET except NUL or cookie-sep>
1469 OCTET = <any 8-bit sequence of data>
1470 LWS = SP | HT
1471 NUL = <US-ASCII NUL, null control character (0)>
1472 CR = <US-ASCII CR, carriage return (13)>
1473 LF = <US-ASCII LF, linefeed (10)>
1474 SP = <US-ASCII SP, space (32)>
1475 HT = <US-ASCII HT, horizontal-tab (9)>
1477 set-cookie = "Set-Cookie:" cookies
1478 cookies = cookie *( cookie-sep cookie )
1479 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
1480 NAME = token ; cookie name
1481 VALUE = value ; cookie value
1482 cookie-av = token ["=" value]
1484 valid values for cookie-av (checked post-parsing) are:
1485 cookie-av = "Path" "=" value
1486 | "Domain" "=" value
1487 | "Expires" "=" value
1488 | "Max-Age" "=" value
1489 | "Comment" "=" value
1490 | "Version" "=" value
1491 | "Secure"
1492 | "HttpOnly"
1494 ******************************************************************************/
1495 // clang-format on
1497 // helper functions for GetTokenValue
1498 static inline bool isnull(char c) { return c == 0; }
1499 static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
1500 static inline bool isterminator(char c) { return c == '\n' || c == '\r'; }
1501 static inline bool isvalueseparator(char c) {
1502 return isterminator(c) || c == ';';
1504 static inline bool istokenseparator(char c) {
1505 return isvalueseparator(c) || c == '=';
1508 // Parse a single token/value pair.
1509 // Returns true if a cookie terminator is found, so caller can parse new cookie.
1510 bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
1511 nsACString::const_char_iterator& aEndIter,
1512 nsDependentCSubstring& aTokenString,
1513 nsDependentCSubstring& aTokenValue,
1514 bool& aEqualsFound) {
1515 nsACString::const_char_iterator start;
1516 nsACString::const_char_iterator lastSpace;
1517 // initialize value string to clear garbage
1518 aTokenValue.Rebind(aIter, aIter);
1520 // find <token>, including any <LWS> between the end-of-token and the
1521 // token separator. we'll remove trailing <LWS> next
1522 while (aIter != aEndIter && iswhitespace(*aIter)) {
1523 ++aIter;
1525 start = aIter;
1526 while (aIter != aEndIter && !isnull(*aIter) && !istokenseparator(*aIter)) {
1527 ++aIter;
1530 // remove trailing <LWS>; first check we're not at the beginning
1531 lastSpace = aIter;
1532 if (lastSpace != start) {
1533 while (--lastSpace != start && iswhitespace(*lastSpace)) {
1535 ++lastSpace;
1537 aTokenString.Rebind(start, lastSpace);
1539 aEqualsFound = (*aIter == '=');
1540 if (aEqualsFound) {
1541 // find <value>
1542 while (++aIter != aEndIter && iswhitespace(*aIter)) {
1545 start = aIter;
1547 // process <token>
1548 // just look for ';' to terminate ('=' allowed)
1549 while (aIter != aEndIter && !isnull(*aIter) && !isvalueseparator(*aIter)) {
1550 ++aIter;
1553 // remove trailing <LWS>; first check we're not at the beginning
1554 if (aIter != start) {
1555 lastSpace = aIter;
1556 while (--lastSpace != start && iswhitespace(*lastSpace)) {
1559 aTokenValue.Rebind(start, ++lastSpace);
1563 // aIter is on ';', or terminator, or EOS
1564 if (aIter != aEndIter) {
1565 // if on terminator, increment past & return true to process new cookie
1566 if (isterminator(*aIter)) {
1567 ++aIter;
1568 return true;
1570 // fall-through: aIter is on ';', increment and return false
1571 ++aIter;
1573 return false;
1576 static inline void SetSameSiteAttributeDefault(CookieStruct& aCookieData) {
1577 // Set cookie with SameSite attribute that is treated as Default
1578 // and doesn't requires changing the DB schema.
1579 aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
1580 aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
1583 static inline void SetSameSiteAttribute(CookieStruct& aCookieData,
1584 int32_t aValue) {
1585 aCookieData.sameSite() = aValue;
1586 aCookieData.rawSameSite() = aValue;
1589 // Parses attributes from cookie header. expires/max-age attributes aren't
1590 // folded into the cookie struct here, because we don't know which one to use
1591 // until we've parsed the header.
1592 bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
1593 nsIURI* aHostURI, nsCString& aCookieHeader,
1594 CookieStruct& aCookieData,
1595 nsACString& aExpires, nsACString& aMaxage,
1596 bool& aAcceptedByParser) {
1597 aAcceptedByParser = false;
1599 static const char kPath[] = "path";
1600 static const char kDomain[] = "domain";
1601 static const char kExpires[] = "expires";
1602 static const char kMaxage[] = "max-age";
1603 static const char kSecure[] = "secure";
1604 static const char kHttpOnly[] = "httponly";
1605 static const char kSameSite[] = "samesite";
1606 static const char kSameSiteLax[] = "lax";
1607 static const char kSameSiteNone[] = "none";
1608 static const char kSameSiteStrict[] = "strict";
1609 static const char kPartitioned[] = "partitioned";
1611 nsACString::const_char_iterator cookieStart;
1612 aCookieHeader.BeginReading(cookieStart);
1614 nsACString::const_char_iterator cookieEnd;
1615 aCookieHeader.EndReading(cookieEnd);
1617 aCookieData.isSecure() = false;
1618 aCookieData.isHttpOnly() = false;
1620 SetSameSiteAttributeDefault(aCookieData);
1622 nsDependentCSubstring tokenString(cookieStart, cookieStart);
1623 nsDependentCSubstring tokenValue(cookieStart, cookieStart);
1624 bool newCookie;
1625 bool equalsFound;
1627 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1628 // if we find multiple cookies, return for processing
1629 // note: if there's no '=', we assume token is <VALUE>. this is required by
1630 // some sites (see bug 169091).
1631 // XXX fix the parser to parse according to <VALUE> grammar for this case
1632 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
1633 equalsFound);
1634 if (equalsFound) {
1635 aCookieData.name() = tokenString;
1636 aCookieData.value() = tokenValue;
1637 } else {
1638 aCookieData.value() = tokenString;
1641 // extract remaining attributes
1642 while (cookieStart != cookieEnd && !newCookie) {
1643 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
1644 equalsFound);
1646 // decide which attribute we have, and copy the string
1647 if (tokenString.LowerCaseEqualsLiteral(kPath)) {
1648 aCookieData.path() = tokenValue;
1650 } else if (tokenString.LowerCaseEqualsLiteral(kDomain)) {
1651 aCookieData.host() = tokenValue;
1653 } else if (tokenString.LowerCaseEqualsLiteral(kExpires)) {
1654 aExpires = tokenValue;
1656 } else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) {
1657 aMaxage = tokenValue;
1659 // ignore any tokenValue for isSecure; just set the boolean
1660 } else if (tokenString.LowerCaseEqualsLiteral(kSecure)) {
1661 aCookieData.isSecure() = true;
1663 // ignore any tokenValue for isPartitioned; just set the boolean
1664 } else if (tokenString.LowerCaseEqualsLiteral(kPartitioned)) {
1665 aCookieData.isPartitioned() = true;
1667 // ignore any tokenValue for isHttpOnly (see bug 178993);
1668 // just set the boolean
1669 } else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) {
1670 aCookieData.isHttpOnly() = true;
1672 } else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
1673 if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
1674 SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_LAX);
1675 } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
1676 SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_STRICT);
1677 } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteNone)) {
1678 SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_NONE);
1679 } else {
1680 // Reset to Default if unknown token value (see Bug 1682450)
1681 SetSameSiteAttributeDefault(aCookieData);
1682 CookieLogging::LogMessageToConsole(
1683 aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1684 "CookieSameSiteValueInvalid2"_ns,
1685 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1690 // re-assign aCookieHeader, in case we need to process another cookie
1691 aCookieHeader.Assign(Substring(cookieStart, cookieEnd));
1693 // If same-site is explicitly set to 'none' but this is not a secure context,
1694 // let's abort the parsing.
1695 if (!aCookieData.isSecure() &&
1696 aCookieData.sameSite() == nsICookie::SAMESITE_NONE) {
1697 if (StaticPrefs::network_cookie_sameSite_noneRequiresSecure()) {
1698 CookieLogging::LogMessageToConsole(
1699 aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_SAMESITE_CATEGORY,
1700 "CookieRejectedNonRequiresSecure2"_ns,
1701 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1702 return newCookie;
1705 // Still warn about the missing Secure attribute when not enforcing.
1706 CookieLogging::LogMessageToConsole(
1707 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
1708 "CookieRejectedNonRequiresSecureForBeta3"_ns,
1709 AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
1710 SAMESITE_MDN_URL});
1713 // Ensure the partitioned cookie is set with the secure attribute.
1714 if (aCookieData.isPartitioned() && !aCookieData.isSecure()) {
1715 CookieLogging::LogMessageToConsole(
1716 aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_REJECTION_CATEGORY,
1717 "CookieRejectedPartitionedRequiresSecure"_ns,
1718 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1720 // We only drop the cookie if CHIPS is enabled.
1721 if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
1722 return newCookie;
1726 if (aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE &&
1727 aCookieData.sameSite() == nsICookie::SAMESITE_LAX) {
1728 bool laxByDefault =
1729 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1730 !nsContentUtils::IsURIInPrefList(
1731 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1732 if (laxByDefault) {
1733 CookieLogging::LogMessageToConsole(
1734 aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1735 "CookieLaxForced2"_ns,
1736 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1737 } else {
1738 CookieLogging::LogMessageToConsole(
1739 aCRC, aHostURI, nsIScriptError::warningFlag,
1740 CONSOLE_SAMESITE_CATEGORY, "CookieLaxForcedForBeta2"_ns,
1741 AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
1742 SAMESITE_MDN_URL});
1746 // Cookie accepted.
1747 aAcceptedByParser = true;
1749 MOZ_ASSERT(Cookie::ValidateSameSite(aCookieData));
1750 return newCookie;
1753 /******************************************************************************
1754 * CookieService impl:
1755 * private domain & permission compliance enforcement functions
1756 ******************************************************************************/
1758 // Normalizes the given hostname, component by component. ASCII/ACE
1759 // components are lower-cased, and UTF-8 components are normalized per
1760 // RFC 3454 and converted to ACE.
1761 nsresult CookieService::NormalizeHost(nsCString& aHost) {
1762 if (!IsAscii(aHost)) {
1763 nsAutoCString host;
1764 nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
1765 if (NS_FAILED(rv)) {
1766 return rv;
1769 aHost = host;
1772 ToLowerCase(aHost);
1773 return NS_OK;
1776 // returns true if 'a' is equal to or a subdomain of 'b',
1777 // assuming no leading dots are present.
1778 static inline bool IsSubdomainOf(const nsACString& a, const nsACString& b) {
1779 if (a == b) {
1780 return true;
1782 if (a.Length() > b.Length()) {
1783 return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
1785 return false;
1788 CookieStatus CookieService::CheckPrefs(
1789 nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings,
1790 nsIURI* aHostURI, bool aIsForeign, bool aIsThirdPartyTrackingResource,
1791 bool aIsThirdPartySocialTrackingResource,
1792 bool aStorageAccessPermissionGranted, const nsACString& aCookieHeader,
1793 const int aNumOfCookies, const OriginAttributes& aOriginAttrs,
1794 uint32_t* aRejectedReason) {
1795 nsresult rv;
1797 MOZ_ASSERT(aRejectedReason);
1799 *aRejectedReason = 0;
1801 // don't let unsupported scheme sites get/set cookies (could be a security
1802 // issue)
1803 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
1804 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1805 "non http/https sites cannot read cookies");
1806 return STATUS_REJECTED_WITH_ERROR;
1809 nsCOMPtr<nsIPrincipal> principal =
1810 BasePrincipal::CreateContentPrincipal(aHostURI, aOriginAttrs);
1812 if (!principal) {
1813 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1814 "non-content principals cannot get/set cookies");
1815 return STATUS_REJECTED_WITH_ERROR;
1818 // check the permission list first; if we find an entry, it overrides
1819 // default prefs. see bug 184059.
1820 uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
1821 rv = aCookieJarSettings->CookiePermission(principal, &cookiePermission);
1822 if (NS_SUCCEEDED(rv)) {
1823 switch (cookiePermission) {
1824 case nsICookiePermission::ACCESS_DENY:
1825 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1826 "cookies are blocked for this site");
1827 CookieLogging::LogMessageToConsole(
1828 aCRC, aHostURI, nsIScriptError::warningFlag,
1829 CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
1830 AutoTArray<nsString, 1>{
1831 NS_ConvertUTF8toUTF16(aCookieHeader),
1834 *aRejectedReason =
1835 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION;
1836 return STATUS_REJECTED;
1838 case nsICookiePermission::ACCESS_ALLOW:
1839 return STATUS_ACCEPTED;
1840 default:
1841 break;
1845 // No cookies allowed if this request comes from a resource in a 3rd party
1846 // context, when anti-tracking protection is enabled and when we don't have
1847 // access to the first-party cookie jar.
1848 if (aIsForeign && aIsThirdPartyTrackingResource &&
1849 !aStorageAccessPermissionGranted &&
1850 aCookieJarSettings->GetRejectThirdPartyContexts()) {
1851 uint32_t rejectReason =
1852 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
1853 if (StoragePartitioningEnabled(rejectReason, aCookieJarSettings)) {
1854 MOZ_ASSERT(!aOriginAttrs.mPartitionKey.IsEmpty(),
1855 "We must have a StoragePrincipal here!");
1856 return STATUS_ACCEPTED;
1859 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1860 "cookies are disabled in trackers");
1861 if (aIsThirdPartySocialTrackingResource) {
1862 *aRejectedReason =
1863 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
1864 } else {
1865 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
1867 return STATUS_REJECTED;
1870 // check default prefs.
1871 // Check aStorageAccessPermissionGranted when checking aCookieBehavior
1872 // so that we take things such as the content blocking allow list into
1873 // account.
1874 if (aCookieJarSettings->GetCookieBehavior() ==
1875 nsICookieService::BEHAVIOR_REJECT &&
1876 !aStorageAccessPermissionGranted) {
1877 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1878 "cookies are disabled");
1879 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL;
1880 return STATUS_REJECTED;
1883 // check if cookie is foreign
1884 if (aIsForeign) {
1885 if (aCookieJarSettings->GetCookieBehavior() ==
1886 nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
1887 !aStorageAccessPermissionGranted) {
1888 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1889 "context is third party");
1890 CookieLogging::LogMessageToConsole(
1891 aCRC, aHostURI, nsIScriptError::warningFlag,
1892 CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
1893 AutoTArray<nsString, 1>{
1894 NS_ConvertUTF8toUTF16(aCookieHeader),
1896 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1897 return STATUS_REJECTED;
1900 if (aCookieJarSettings->GetLimitForeignContexts() &&
1901 !aStorageAccessPermissionGranted && aNumOfCookies == 0) {
1902 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1903 "context is third party");
1904 CookieLogging::LogMessageToConsole(
1905 aCRC, aHostURI, nsIScriptError::warningFlag,
1906 CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
1907 AutoTArray<nsString, 1>{
1908 NS_ConvertUTF8toUTF16(aCookieHeader),
1910 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1911 return STATUS_REJECTED;
1914 if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
1915 return STATUS_ACCEPT_SESSION;
1918 if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly()) {
1919 if (!aHostURI->SchemeIs("https")) {
1920 return STATUS_ACCEPT_SESSION;
1925 // if nothing has complained, accept cookie
1926 return STATUS_ACCEPTED;
1929 // processes domain attribute, and returns true if host has permission to set
1930 // for this domain.
1931 bool CookieService::CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
1932 const nsACString& aBaseDomain,
1933 bool aRequireHostMatch) {
1934 // Note: The logic in this function is mirrored in
1935 // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
1936 // If it changes, please update that function, or file a bug for someone
1937 // else to do so.
1939 // get host from aHostURI
1940 nsAutoCString hostFromURI;
1941 nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
1943 // if a domain is given, check the host has permission
1944 if (!aCookieData.host().IsEmpty()) {
1945 // Tolerate leading '.' characters, but not if it's otherwise an empty host.
1946 if (aCookieData.host().Length() > 1 && aCookieData.host().First() == '.') {
1947 aCookieData.host().Cut(0, 1);
1950 // switch to lowercase now, to avoid case-insensitive compares everywhere
1951 ToLowerCase(aCookieData.host());
1953 // check whether the host is either an IP address, an alias such as
1954 // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
1955 // cases, require an exact string match for the domain, and leave the cookie
1956 // as a non-domain one. bug 105917 originally noted the requirement to deal
1957 // with IP addresses.
1958 if (aRequireHostMatch) {
1959 return hostFromURI.Equals(aCookieData.host());
1962 // ensure the proposed domain is derived from the base domain; and also
1963 // that the host domain is derived from the proposed domain (per RFC2109).
1964 if (IsSubdomainOf(aCookieData.host(), aBaseDomain) &&
1965 IsSubdomainOf(hostFromURI, aCookieData.host())) {
1966 // prepend a dot to indicate a domain cookie
1967 aCookieData.host().InsertLiteral(".", 0);
1968 return true;
1972 * note: RFC2109 section 4.3.2 requires that we check the following:
1973 * that the portion of host not in domain does not contain a dot.
1974 * this prevents hosts of the form x.y.co.nz from setting cookies in the
1975 * entire .co.nz domain. however, it's only a only a partial solution and
1976 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1978 return false;
1981 // no domain specified, use hostFromURI
1982 aCookieData.host() = hostFromURI;
1983 return true;
1986 namespace {
1987 nsAutoCString GetPathFromURI(nsIURI* aHostURI) {
1988 // strip down everything after the last slash to get the path,
1989 // ignoring slashes in the query string part.
1990 // if we can QI to nsIURL, that'll take care of the query string portion.
1991 // otherwise, it's not an nsIURL and can't have a query string, so just find
1992 // the last slash.
1993 nsAutoCString path;
1994 nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
1995 if (hostURL) {
1996 hostURL->GetDirectory(path);
1997 } else {
1998 aHostURI->GetPathQueryRef(path);
1999 int32_t slash = path.RFindChar('/');
2000 if (slash != kNotFound) {
2001 path.Truncate(slash + 1);
2005 // strip the right-most %x2F ("/") if the path doesn't contain only 1 '/'.
2006 int32_t lastSlash = path.RFindChar('/');
2007 int32_t firstSlash = path.FindChar('/');
2008 if (lastSlash != firstSlash && lastSlash != kNotFound &&
2009 lastSlash == static_cast<int32_t>(path.Length() - 1)) {
2010 path.Truncate(lastSlash);
2013 return path;
2016 } // namespace
2018 bool CookieService::CheckPath(CookieStruct& aCookieData,
2019 nsIConsoleReportCollector* aCRC,
2020 nsIURI* aHostURI) {
2021 // if a path is given, check the host has permission
2022 if (aCookieData.path().IsEmpty() || aCookieData.path().First() != '/') {
2023 aCookieData.path() = GetPathFromURI(aHostURI);
2026 if (!CookieCommons::CheckPathSize(aCookieData)) {
2027 AutoTArray<nsString, 2> params = {
2028 NS_ConvertUTF8toUTF16(aCookieData.name())};
2030 nsString size;
2031 size.AppendInt(kMaxBytesPerPath);
2032 params.AppendElement(size);
2034 CookieLogging::LogMessageToConsole(
2035 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
2036 "CookiePathOversize"_ns, params);
2037 return false;
2040 return !aCookieData.path().Contains('\t');
2043 // CheckPrefixes
2045 // Reject cookies whose name starts with the magic prefixes from
2046 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
2047 // if they do not meet the criteria required by the prefix.
2049 // Must not be called until after CheckDomain() and CheckPath() have
2050 // regularized and validated the CookieStruct values!
2051 bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
2052 bool aSecureRequest) {
2053 bool isSecure = HasSecurePrefix(aCookieData.name());
2054 bool isHost = HasHostPrefix(aCookieData.name());
2056 if (!isSecure && !isHost) {
2057 // not one of the magic prefixes: carry on
2058 return true;
2061 if (!aSecureRequest || !aCookieData.isSecure()) {
2062 // the magic prefixes may only be used from a secure request and
2063 // the secure attribute must be set on the cookie
2064 return false;
2067 if (isHost) {
2068 // The host prefix requires that the path is "/" and that the cookie
2069 // had no domain attribute. CheckDomain() and CheckPath() MUST be run
2070 // first to make sure invalid attributes are rejected and to regularlize
2071 // them. In particular all explicit domain attributes result in a host
2072 // that starts with a dot, and if the host doesn't start with a dot it
2073 // correctly matches the true host.
2074 if (aCookieData.host()[0] == '.' ||
2075 !aCookieData.path().EqualsLiteral("/")) {
2076 return false;
2080 return true;
2083 bool CookieService::GetExpiry(CookieStruct& aCookieData,
2084 const nsACString& aExpires,
2085 const nsACString& aMaxage, int64_t aCurrentTime,
2086 bool aFromHttp) {
2087 // maxageCap is in seconds.
2088 // Disabled for HTTP cookies.
2089 int64_t maxageCap =
2090 aFromHttp ? 0 : StaticPrefs::privacy_documentCookies_maxage();
2092 /* Determine when the cookie should expire. This is done by taking the
2093 * difference between the server time and the time the server wants the cookie
2094 * to expire, and adding that difference to the client time. This localizes
2095 * the client time regardless of whether or not the TZ environment variable
2096 * was set on the client.
2098 * Note: We need to consider accounting for network lag here, per RFC.
2100 // check for max-age attribute first; this overrides expires attribute
2101 if (!aMaxage.IsEmpty()) {
2102 // obtain numeric value of maxageAttribute
2103 int64_t maxage;
2104 int32_t numInts = PR_sscanf(aMaxage.BeginReading(), "%lld", &maxage);
2106 // default to session cookie if the conversion failed
2107 if (numInts != 1) {
2108 return true;
2111 // if this addition overflows, expiryTime will be less than currentTime
2112 // and the cookie will be expired - that's okay.
2113 if (maxageCap) {
2114 aCookieData.expiry() = aCurrentTime + std::min(maxage, maxageCap);
2115 } else {
2116 aCookieData.expiry() = aCurrentTime + maxage;
2119 // check for expires attribute
2120 } else if (!aExpires.IsEmpty()) {
2121 PRTime expires;
2123 // parse expiry time
2124 if (PR_ParseTimeString(aExpires.BeginReading(), true, &expires) !=
2125 PR_SUCCESS) {
2126 return true;
2129 // If set-cookie used absolute time to set expiration, and it can't use
2130 // client time to set expiration.
2131 // Because if current time be set in the future, but the cookie expire
2132 // time be set less than current time and more than server time.
2133 // The cookie item have to be used to the expired cookie.
2134 if (maxageCap) {
2135 aCookieData.expiry() = std::min(expires / int64_t(PR_USEC_PER_SEC),
2136 aCurrentTime + maxageCap);
2137 } else {
2138 aCookieData.expiry() = expires / int64_t(PR_USEC_PER_SEC);
2141 // default to session cookie if no attributes found. Here we don't need to
2142 // enforce the maxage cap, because session cookies are short-lived by
2143 // definition.
2144 } else {
2145 return true;
2148 return false;
2151 /******************************************************************************
2152 * CookieService impl:
2153 * private cookielist management functions
2154 ******************************************************************************/
2156 // find whether a given cookie has been previously set. this is provided by the
2157 // nsICookieManager interface.
2158 NS_IMETHODIMP
2159 CookieService::CookieExists(const nsACString& aHost, const nsACString& aPath,
2160 const nsACString& aName,
2161 JS::Handle<JS::Value> aOriginAttributes,
2162 JSContext* aCx, bool* aFoundCookie) {
2163 NS_ENSURE_ARG_POINTER(aCx);
2164 NS_ENSURE_ARG_POINTER(aFoundCookie);
2166 OriginAttributes attrs;
2167 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2168 return NS_ERROR_INVALID_ARG;
2170 return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie);
2173 NS_IMETHODIMP_(nsresult)
2174 CookieService::CookieExistsNative(const nsACString& aHost,
2175 const nsACString& aPath,
2176 const nsACString& aName,
2177 OriginAttributes* aOriginAttributes,
2178 bool* aFoundCookie) {
2179 nsCOMPtr<nsICookie> cookie;
2180 nsresult rv = GetCookieNative(aHost, aPath, aName, aOriginAttributes,
2181 getter_AddRefs(cookie));
2182 NS_ENSURE_SUCCESS(rv, rv);
2184 *aFoundCookie = cookie != nullptr;
2186 return NS_OK;
2189 NS_IMETHODIMP_(nsresult)
2190 CookieService::GetCookieNative(const nsACString& aHost, const nsACString& aPath,
2191 const nsACString& aName,
2192 OriginAttributes* aOriginAttributes,
2193 nsICookie** aCookie) {
2194 NS_ENSURE_ARG_POINTER(aOriginAttributes);
2195 NS_ENSURE_ARG_POINTER(aCookie);
2197 if (!IsInitialized()) {
2198 return NS_ERROR_NOT_AVAILABLE;
2201 nsAutoCString baseDomain;
2202 nsresult rv =
2203 CookieCommons::GetBaseDomainFromHost(mTLDService, aHost, baseDomain);
2204 NS_ENSURE_SUCCESS(rv, rv);
2206 CookieListIter iter{};
2207 CookieStorage* storage = PickStorage(*aOriginAttributes);
2208 bool foundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost,
2209 aName, aPath, iter);
2211 if (foundCookie) {
2212 RefPtr<Cookie> cookie = iter.Cookie();
2213 NS_ENSURE_TRUE(cookie, NS_ERROR_NULL_POINTER);
2215 cookie.forget(aCookie);
2218 return NS_OK;
2221 // count the number of cookies stored by a particular host. this is provided by
2222 // the nsICookieManager interface.
2223 NS_IMETHODIMP
2224 CookieService::CountCookiesFromHost(const nsACString& aHost,
2225 uint32_t* aCountFromHost) {
2226 // first, normalize the hostname, and fail if it contains illegal characters.
2227 nsAutoCString host(aHost);
2228 nsresult rv = NormalizeHost(host);
2229 NS_ENSURE_SUCCESS(rv, rv);
2231 nsAutoCString baseDomain;
2232 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2233 NS_ENSURE_SUCCESS(rv, rv);
2235 if (!IsInitialized()) {
2236 return NS_ERROR_NOT_AVAILABLE;
2239 mPersistentStorage->EnsureInitialized();
2241 *aCountFromHost = mPersistentStorage->CountCookiesFromHost(baseDomain, 0);
2243 return NS_OK;
2246 // get an enumerator of cookies stored by a particular host. this is provided by
2247 // the nsICookieManager interface.
2248 NS_IMETHODIMP
2249 CookieService::GetCookiesFromHost(const nsACString& aHost,
2250 JS::Handle<JS::Value> aOriginAttributes,
2251 JSContext* aCx,
2252 nsTArray<RefPtr<nsICookie>>& aResult) {
2253 // first, normalize the hostname, and fail if it contains illegal characters.
2254 nsAutoCString host(aHost);
2255 nsresult rv = NormalizeHost(host);
2256 NS_ENSURE_SUCCESS(rv, rv);
2258 nsAutoCString baseDomain;
2259 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2260 NS_ENSURE_SUCCESS(rv, rv);
2262 OriginAttributes attrs;
2263 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2264 return NS_ERROR_INVALID_ARG;
2267 if (!IsInitialized()) {
2268 return NS_ERROR_NOT_AVAILABLE;
2271 CookieStorage* storage = PickStorage(attrs);
2273 const nsTArray<RefPtr<Cookie>>* cookies =
2274 storage->GetCookiesFromHost(baseDomain, attrs);
2276 if (cookies) {
2277 aResult.SetCapacity(cookies->Length());
2278 for (Cookie* cookie : *cookies) {
2279 aResult.AppendElement(cookie);
2283 return NS_OK;
2286 NS_IMETHODIMP
2287 CookieService::GetCookiesWithOriginAttributes(
2288 const nsAString& aPattern, const nsACString& aHost,
2289 nsTArray<RefPtr<nsICookie>>& aResult) {
2290 OriginAttributesPattern pattern;
2291 if (!pattern.Init(aPattern)) {
2292 return NS_ERROR_INVALID_ARG;
2295 nsAutoCString host(aHost);
2296 nsresult rv = NormalizeHost(host);
2297 NS_ENSURE_SUCCESS(rv, rv);
2299 nsAutoCString baseDomain;
2300 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2301 NS_ENSURE_SUCCESS(rv, rv);
2303 return GetCookiesWithOriginAttributes(pattern, baseDomain, aResult);
2306 nsresult CookieService::GetCookiesWithOriginAttributes(
2307 const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain,
2308 nsTArray<RefPtr<nsICookie>>& aResult) {
2309 CookieStorage* storage = PickStorage(aPattern);
2310 storage->GetCookiesWithOriginAttributes(aPattern, aBaseDomain, aResult);
2312 return NS_OK;
2315 NS_IMETHODIMP
2316 CookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
2317 const nsACString& aHost) {
2318 MOZ_ASSERT(XRE_IsParentProcess());
2320 OriginAttributesPattern pattern;
2321 if (!pattern.Init(aPattern)) {
2322 return NS_ERROR_INVALID_ARG;
2325 nsAutoCString host(aHost);
2326 nsresult rv = NormalizeHost(host);
2327 NS_ENSURE_SUCCESS(rv, rv);
2329 nsAutoCString baseDomain;
2330 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2331 NS_ENSURE_SUCCESS(rv, rv);
2333 return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
2336 nsresult CookieService::RemoveCookiesWithOriginAttributes(
2337 const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain) {
2338 if (!IsInitialized()) {
2339 return NS_ERROR_NOT_AVAILABLE;
2342 CookieStorage* storage = PickStorage(aPattern);
2343 storage->RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
2345 return NS_OK;
2348 NS_IMETHODIMP
2349 CookieService::RemoveCookiesFromExactHost(const nsACString& aHost,
2350 const nsAString& aPattern) {
2351 MOZ_ASSERT(XRE_IsParentProcess());
2353 OriginAttributesPattern pattern;
2354 if (!pattern.Init(aPattern)) {
2355 return NS_ERROR_INVALID_ARG;
2358 return RemoveCookiesFromExactHost(aHost, pattern);
2361 nsresult CookieService::RemoveCookiesFromExactHost(
2362 const nsACString& aHost, const OriginAttributesPattern& aPattern) {
2363 nsAutoCString host(aHost);
2364 nsresult rv = NormalizeHost(host);
2365 NS_ENSURE_SUCCESS(rv, rv);
2367 nsAutoCString baseDomain;
2368 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2369 NS_ENSURE_SUCCESS(rv, rv);
2371 if (!IsInitialized()) {
2372 return NS_ERROR_NOT_AVAILABLE;
2375 CookieStorage* storage = PickStorage(aPattern);
2376 storage->RemoveCookiesFromExactHost(aHost, baseDomain, aPattern);
2378 return NS_OK;
2381 namespace {
2383 class RemoveAllSinceRunnable : public Runnable {
2384 public:
2385 using CookieArray = nsTArray<RefPtr<nsICookie>>;
2386 RemoveAllSinceRunnable(Promise* aPromise, CookieService* aSelf,
2387 CookieArray&& aCookieArray, int64_t aSinceWhen)
2388 : Runnable("RemoveAllSinceRunnable"),
2389 mPromise(aPromise),
2390 mSelf(aSelf),
2391 mList(std::move(aCookieArray)),
2392 mIndex(0),
2393 mSinceWhen(aSinceWhen) {}
2395 NS_IMETHODIMP Run() override {
2396 RemoveSome();
2398 if (mIndex < mList.Length()) {
2399 return NS_DispatchToCurrentThread(this);
2401 mPromise->MaybeResolveWithUndefined();
2403 return NS_OK;
2406 private:
2407 void RemoveSome() {
2408 for (CookieArray::size_type iter = 0;
2409 iter < kYieldPeriod && mIndex < mList.Length(); ++mIndex, ++iter) {
2410 auto* cookie = static_cast<Cookie*>(mList[mIndex].get());
2411 if (cookie->CreationTime() > mSinceWhen &&
2412 NS_FAILED(mSelf->Remove(cookie->Host(), cookie->OriginAttributesRef(),
2413 cookie->Name(), cookie->Path()))) {
2414 continue;
2419 private:
2420 RefPtr<Promise> mPromise;
2421 RefPtr<CookieService> mSelf;
2422 CookieArray mList;
2423 CookieArray::size_type mIndex;
2424 int64_t mSinceWhen;
2425 static const CookieArray::size_type kYieldPeriod = 10;
2428 } // namespace
2430 NS_IMETHODIMP
2431 CookieService::RemoveAllSince(int64_t aSinceWhen, JSContext* aCx,
2432 Promise** aRetVal) {
2433 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
2434 if (NS_WARN_IF(!globalObject)) {
2435 return NS_ERROR_UNEXPECTED;
2438 ErrorResult result;
2439 RefPtr<Promise> promise = Promise::Create(globalObject, result);
2440 if (NS_WARN_IF(result.Failed())) {
2441 return result.StealNSResult();
2444 mPersistentStorage->EnsureInitialized();
2446 nsTArray<RefPtr<nsICookie>> cookieList;
2448 // We delete only non-private cookies.
2449 mPersistentStorage->GetAll(cookieList);
2451 RefPtr<RemoveAllSinceRunnable> runMe = new RemoveAllSinceRunnable(
2452 promise, this, std::move(cookieList), aSinceWhen);
2454 promise.forget(aRetVal);
2456 return runMe->Run();
2459 namespace {
2461 class CompareCookiesCreationTime {
2462 public:
2463 static bool Equals(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2464 return static_cast<const Cookie*>(aCookie1)->CreationTime() ==
2465 static_cast<const Cookie*>(aCookie2)->CreationTime();
2468 static bool LessThan(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2469 return static_cast<const Cookie*>(aCookie1)->CreationTime() <
2470 static_cast<const Cookie*>(aCookie2)->CreationTime();
2474 } // namespace
2476 NS_IMETHODIMP
2477 CookieService::GetCookiesSince(int64_t aSinceWhen,
2478 nsTArray<RefPtr<nsICookie>>& aResult) {
2479 if (!IsInitialized()) {
2480 return NS_OK;
2483 mPersistentStorage->EnsureInitialized();
2485 // We expose only non-private cookies.
2486 nsTArray<RefPtr<nsICookie>> cookieList;
2487 mPersistentStorage->GetAll(cookieList);
2489 for (RefPtr<nsICookie>& cookie : cookieList) {
2490 if (static_cast<Cookie*>(cookie.get())->CreationTime() >= aSinceWhen) {
2491 aResult.AppendElement(cookie);
2495 aResult.Sort(CompareCookiesCreationTime());
2496 return NS_OK;
2499 size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2500 size_t n = aMallocSizeOf(this);
2502 if (mPersistentStorage) {
2503 n += mPersistentStorage->SizeOfIncludingThis(aMallocSizeOf);
2505 if (mPrivateStorage) {
2506 n += mPrivateStorage->SizeOfIncludingThis(aMallocSizeOf);
2509 return n;
2512 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
2514 NS_IMETHODIMP
2515 CookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
2516 nsISupports* aData, bool /*aAnonymize*/) {
2517 MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
2518 SizeOfIncludingThis(CookieServiceMallocSizeOf),
2519 "Memory used by the cookie service.");
2521 return NS_OK;
2524 bool CookieService::IsInitialized() const {
2525 if (!mPersistentStorage) {
2526 NS_WARNING("No CookieStorage! Profile already close?");
2527 return false;
2530 MOZ_ASSERT(mPrivateStorage);
2531 return true;
2534 CookieStorage* CookieService::PickStorage(const OriginAttributes& aAttrs) {
2535 MOZ_ASSERT(IsInitialized());
2537 if (aAttrs.mPrivateBrowsingId > 0) {
2538 return mPrivateStorage;
2541 mPersistentStorage->EnsureInitialized();
2542 return mPersistentStorage;
2545 CookieStorage* CookieService::PickStorage(
2546 const OriginAttributesPattern& aAttrs) {
2547 MOZ_ASSERT(IsInitialized());
2549 if (aAttrs.mPrivateBrowsingId.WasPassed() &&
2550 aAttrs.mPrivateBrowsingId.Value() > 0) {
2551 return mPrivateStorage;
2554 mPersistentStorage->EnsureInitialized();
2555 return mPersistentStorage;
2558 bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain,
2559 const OriginAttributes& aAttrs,
2560 nsIURI* aHostURI, bool aFromHttp,
2561 const nsTArray<CookieStruct>& aCookies,
2562 BrowsingContext* aBrowsingContext) {
2563 if (!IsInitialized()) {
2564 // If we are probably shutting down, we can ignore this cookie.
2565 return true;
2568 CookieStorage* storage = PickStorage(aAttrs);
2569 int64_t currentTimeInUsec = PR_Now();
2571 for (const CookieStruct& cookieData : aCookies) {
2572 if (!CookieCommons::CheckPathSize(cookieData)) {
2573 return false;
2576 // reject cookie if it's over the size limit, per RFC2109
2577 if (!CookieCommons::CheckNameAndValueSize(cookieData)) {
2578 return false;
2581 RecordUnicodeTelemetry(cookieData);
2583 if (!CookieCommons::CheckName(cookieData)) {
2584 return false;
2587 if (!CookieCommons::CheckValue(cookieData)) {
2588 return false;
2591 // create a new Cookie and copy attributes
2592 RefPtr<Cookie> cookie = Cookie::Create(cookieData, aAttrs);
2593 if (!cookie) {
2594 continue;
2597 cookie->SetLastAccessed(currentTimeInUsec);
2598 cookie->SetCreationTime(
2599 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
2601 storage->AddCookie(nullptr, aBaseDomain, aAttrs, cookie, currentTimeInUsec,
2602 aHostURI, ""_ns, aFromHttp, aBrowsingContext);
2605 return true;
2608 } // namespace net
2609 } // namespace mozilla