Bug 1876289 [wpt PR 44165] - Unskip loaf-source-location-redirect and deflake, a...
[gecko.git] / netwerk / cookie / CookieService.cpp
blob5ed7d8b2805e1f13e68a275515418464a2716e21
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"
32 #include "nsIURL.h"
33 #include "nsIURI.h"
34 #include "nsIWebProgressListener.h"
35 #include "nsNetUtil.h"
36 #include "prprf.h"
37 #include "ThirdPartyUtil.h"
39 using namespace mozilla::dom;
41 namespace {
43 uint32_t MakeCookieBehavior(uint32_t aCookieBehavior) {
44 bool isFirstPartyIsolated = OriginAttributes::IsFirstPartyEnabled();
46 if (isFirstPartyIsolated &&
47 aCookieBehavior ==
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) {
62 return;
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",
72 false);
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
82 // static
83 uint32_t nsICookieManager::GetCookieBehavior(bool aIsPrivate) {
84 if (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
87 // mode when
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());
112 namespace mozilla {
113 namespace net {
115 /******************************************************************************
116 * CookieService impl:
117 * useful types & constants
118 ******************************************************************************/
120 static StaticRefPtr<CookieService> gCookieService;
122 constexpr auto CONSOLE_CHIPS_CATEGORY = "cookiesCHIPS"_ns;
123 constexpr auto CONSOLE_SAMESITE_CATEGORY = "cookieSameSite"_ns;
124 constexpr auto CONSOLE_OVERSIZE_CATEGORY = "cookiesOversize"_ns;
125 constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
126 constexpr auto SAMESITE_MDN_URL =
127 "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/"
128 u"SameSite"_ns;
130 namespace {
132 void ComposeCookieString(nsTArray<Cookie*>& aCookieList,
133 nsACString& aCookieString) {
134 for (Cookie* cookie : aCookieList) {
135 // check if we have anything to write
136 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
137 // if we've already added a cookie to the return list, append a "; " so
138 // that subsequent cookies are delimited in the final list.
139 if (!aCookieString.IsEmpty()) {
140 aCookieString.AppendLiteral("; ");
143 if (!cookie->Name().IsEmpty()) {
144 // we have a name and value - write both
145 aCookieString += cookie->Name() + "="_ns + cookie->Value();
146 } else {
147 // just write value
148 aCookieString += cookie->Value();
154 // Return false if the cookie should be ignored for the current channel.
155 bool ProcessSameSiteCookieForForeignRequest(nsIChannel* aChannel,
156 Cookie* aCookie,
157 bool aIsSafeTopLevelNav,
158 bool aHadCrossSiteRedirects,
159 bool aLaxByDefault) {
160 // If it's a cross-site request and the cookie is same site only (strict)
161 // don't send it.
162 if (aCookie->SameSite() == nsICookie::SAMESITE_STRICT) {
163 return false;
166 // Explicit SameSite=None cookies are always processed. When laxByDefault
167 // is OFF then so are default cookies.
168 if (aCookie->SameSite() == nsICookie::SAMESITE_NONE ||
169 (!aLaxByDefault && aCookie->IsDefaultSameSite())) {
170 return true;
173 // Lax-by-default cookies are processed even with an intermediate
174 // cross-site redirect (they are treated like aIsSameSiteForeign = false).
175 if (aLaxByDefault && aCookie->IsDefaultSameSite() && aHadCrossSiteRedirects &&
176 StaticPrefs::
177 network_cookie_sameSite_laxByDefault_allowBoomerangRedirect()) {
178 return true;
181 int64_t currentTimeInUsec = PR_Now();
183 // 2 minutes of tolerance for 'SameSite=Lax by default' for cookies set
184 // without a SameSite value when used for unsafe http methods.
185 if (aLaxByDefault && aCookie->IsDefaultSameSite() &&
186 StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 &&
187 currentTimeInUsec - aCookie->CreationTime() <=
188 (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() *
189 PR_USEC_PER_SEC) &&
190 !NS_IsSafeMethodNav(aChannel)) {
191 return true;
194 MOZ_ASSERT((aLaxByDefault && aCookie->IsDefaultSameSite()) ||
195 aCookie->SameSite() == nsICookie::SAMESITE_LAX);
196 // We only have SameSite=Lax or lax-by-default cookies at this point. These
197 // are processed only if it's a top-level navigation
198 return aIsSafeTopLevelNav;
201 } // namespace
203 /******************************************************************************
204 * CookieService impl:
205 * singleton instance ctor/dtor methods
206 ******************************************************************************/
208 already_AddRefed<nsICookieService> CookieService::GetXPCOMSingleton() {
209 if (IsNeckoChild()) {
210 return CookieServiceChild::GetSingleton();
213 return GetSingleton();
216 already_AddRefed<CookieService> CookieService::GetSingleton() {
217 NS_ASSERTION(!IsNeckoChild(), "not a parent process");
219 if (gCookieService) {
220 return do_AddRef(gCookieService);
223 // Create a new singleton CookieService.
224 // We AddRef only once since XPCOM has rules about the ordering of module
225 // teardowns - by the time our module destructor is called, it's too late to
226 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
227 // cycles have already been completed and would result in serious leaks.
228 // See bug 209571.
229 // TODO: Verify what is the earliest point in time during shutdown where
230 // we can deny the creation of the CookieService as a whole.
231 gCookieService = new CookieService();
232 if (gCookieService) {
233 if (NS_SUCCEEDED(gCookieService->Init())) {
234 ClearOnShutdown(&gCookieService);
235 } else {
236 gCookieService = nullptr;
240 return do_AddRef(gCookieService);
243 /******************************************************************************
244 * CookieService impl:
245 * public methods
246 ******************************************************************************/
248 NS_IMPL_ISUPPORTS(CookieService, nsICookieService, nsICookieManager,
249 nsIObserver, nsISupportsWeakReference, nsIMemoryReporter)
251 CookieService::CookieService() = default;
253 nsresult CookieService::Init() {
254 nsresult rv;
255 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
256 NS_ENSURE_SUCCESS(rv, rv);
258 mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
259 NS_ENSURE_SUCCESS(rv, rv);
261 mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
262 NS_ENSURE_SUCCESS(rv, rv);
264 // Init our default, and possibly private CookieStorages.
265 InitCookieStorages();
267 // Migrate network.cookie.lifetimePolicy pref to sanitizeOnShutdown prefs
268 MigrateCookieLifetimePrefs();
270 RegisterWeakMemoryReporter(this);
272 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
273 NS_ENSURE_STATE(os);
274 os->AddObserver(this, "profile-before-change", true);
275 os->AddObserver(this, "profile-do-change", true);
276 os->AddObserver(this, "last-pb-context-exited", true);
278 return NS_OK;
281 void CookieService::InitCookieStorages() {
282 NS_ASSERTION(!mPersistentStorage, "already have a default CookieStorage");
283 NS_ASSERTION(!mPrivateStorage, "already have a private CookieStorage");
285 // Create two new CookieStorages. If we are in or beyond our observed
286 // shutdown phase, just be non-persistent.
287 if (MOZ_UNLIKELY(StaticPrefs::network_cookie_noPersistentStorage() ||
288 AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) {
289 mPersistentStorage = CookiePrivateStorage::Create();
290 } else {
291 mPersistentStorage = CookiePersistentStorage::Create();
294 mPrivateStorage = CookiePrivateStorage::Create();
297 void CookieService::CloseCookieStorages() {
298 // return if we already closed
299 if (!mPersistentStorage) {
300 return;
303 // Let's nullify both storages before calling Close().
304 RefPtr<CookieStorage> privateStorage;
305 privateStorage.swap(mPrivateStorage);
307 RefPtr<CookieStorage> persistentStorage;
308 persistentStorage.swap(mPersistentStorage);
310 privateStorage->Close();
311 persistentStorage->Close();
314 CookieService::~CookieService() {
315 CloseCookieStorages();
317 UnregisterWeakMemoryReporter(this);
319 gCookieService = nullptr;
322 NS_IMETHODIMP
323 CookieService::Observe(nsISupports* /*aSubject*/, const char* aTopic,
324 const char16_t* /*aData*/) {
325 // check the topic
326 if (!strcmp(aTopic, "profile-before-change")) {
327 // The profile is about to change,
328 // or is going away because the application is shutting down.
330 // Close the default DB connection and null out our CookieStorages before
331 // changing.
332 CloseCookieStorages();
334 } else if (!strcmp(aTopic, "profile-do-change")) {
335 NS_ASSERTION(!mPersistentStorage, "shouldn't have a default CookieStorage");
336 NS_ASSERTION(!mPrivateStorage, "shouldn't have a private CookieStorage");
338 // the profile has already changed; init the db from the new location.
339 // if we are in the private browsing state, however, we do not want to read
340 // data into it - we should instead put it into the default state, so it's
341 // ready for us if and when we switch back to it.
342 InitCookieStorages();
344 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
345 // Flush all the cookies stored by private browsing contexts
346 OriginAttributesPattern pattern;
347 pattern.mPrivateBrowsingId.Construct(1);
348 RemoveCookiesWithOriginAttributes(pattern, ""_ns);
349 mPrivateStorage = CookiePrivateStorage::Create();
352 return NS_OK;
355 NS_IMETHODIMP
356 CookieService::GetCookieBehavior(bool aIsPrivate, uint32_t* aCookieBehavior) {
357 NS_ENSURE_ARG_POINTER(aCookieBehavior);
358 *aCookieBehavior = nsICookieManager::GetCookieBehavior(aIsPrivate);
359 return NS_OK;
362 NS_IMETHODIMP
363 CookieService::GetCookieStringFromDocument(Document* aDocument,
364 nsACString& aCookie) {
365 NS_ENSURE_ARG(aDocument);
367 nsresult rv;
369 aCookie.Truncate();
371 if (!IsInitialized()) {
372 return NS_OK;
375 nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
377 if (!CookieCommons::IsSchemeSupported(principal)) {
378 return NS_OK;
381 CookieStorage* storage = PickStorage(principal->OriginAttributesRef());
383 nsAutoCString baseDomain;
384 rv = CookieCommons::GetBaseDomain(principal, baseDomain);
385 if (NS_WARN_IF(NS_FAILED(rv))) {
386 return NS_OK;
389 nsAutoCString hostFromURI;
390 rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
391 if (NS_WARN_IF(NS_FAILED(rv))) {
392 return NS_OK;
395 nsAutoCString pathFromURI;
396 rv = principal->GetFilePath(pathFromURI);
397 if (NS_WARN_IF(NS_FAILED(rv))) {
398 return NS_OK;
401 int64_t currentTimeInUsec = PR_Now();
402 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
404 const nsTArray<RefPtr<Cookie>>* cookies =
405 storage->GetCookiesFromHost(baseDomain, principal->OriginAttributesRef());
406 if (!cookies) {
407 return NS_OK;
410 // check if the nsIPrincipal is using an https secure protocol.
411 // if it isn't, then we can't send a secure cookie over the connection.
412 bool potentiallyTurstworthy = principal->GetIsOriginPotentiallyTrustworthy();
414 bool thirdParty = true;
415 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
416 // in gtests we don't have a window, let's consider those requests as 3rd
417 // party.
418 if (innerWindow) {
419 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
421 if (thirdPartyUtil) {
422 Unused << thirdPartyUtil->IsThirdPartyWindow(
423 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
427 bool stale = false;
428 nsTArray<Cookie*> cookieList;
430 // iterate the cookies!
431 for (Cookie* cookie : *cookies) {
432 // check the host, since the base domain lookup is conservative.
433 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
434 continue;
437 // if the cookie is httpOnly and it's not going directly to the HTTP
438 // connection, don't send it
439 if (cookie->IsHttpOnly()) {
440 continue;
443 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
444 cookie, aDocument)) {
445 continue;
448 // if the cookie is secure and the host scheme isn't, we can't send it
449 if (cookie->IsSecure() && !potentiallyTurstworthy) {
450 continue;
453 // if the nsIURI path doesn't match the cookie path, don't send it back
454 if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
455 continue;
458 // check if the cookie has expired
459 if (cookie->Expiry() <= currentTime) {
460 continue;
463 // all checks passed - add to list and check if lastAccessed stamp needs
464 // updating
465 cookieList.AppendElement(cookie);
466 if (cookie->IsStale()) {
467 stale = true;
471 if (cookieList.IsEmpty()) {
472 return NS_OK;
475 // update lastAccessed timestamps. we only do this if the timestamp is stale
476 // by a certain amount, to avoid thrashing the db during pageload.
477 if (stale) {
478 storage->StaleCookies(cookieList, currentTimeInUsec);
481 // return cookies in order of path length; longest to shortest.
482 // this is required per RFC2109. if cookies match in length,
483 // then sort by creation time (see bug 236772).
484 cookieList.Sort(CompareCookiesForSending());
485 ComposeCookieString(cookieList, aCookie);
487 return NS_OK;
490 NS_IMETHODIMP
491 CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel,
492 nsACString& aCookieString) {
493 NS_ENSURE_ARG(aHostURI);
494 NS_ENSURE_ARG(aChannel);
496 aCookieString.Truncate();
498 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
499 return NS_OK;
502 uint32_t rejectedReason = 0;
503 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
504 aChannel, false, aHostURI, nullptr, &rejectedReason);
506 OriginAttributes attrs;
507 StoragePrincipalHelper::GetOriginAttributes(
508 aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
510 bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
511 bool hadCrossSiteRedirects = false;
512 bool isSameSiteForeign = CookieCommons::IsSameSiteForeign(
513 aChannel, aHostURI, &hadCrossSiteRedirects);
515 AutoTArray<Cookie*, 8> foundCookieList;
516 GetCookiesForURI(
517 aHostURI, aChannel, result.contains(ThirdPartyAnalysis::IsForeign),
518 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
519 result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
520 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
521 rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
522 hadCrossSiteRedirects, true, false, attrs, foundCookieList);
524 ComposeCookieString(foundCookieList, aCookieString);
526 if (!aCookieString.IsEmpty()) {
527 COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
529 return NS_OK;
532 NS_IMETHODIMP
533 CookieService::SetCookieStringFromDocument(Document* aDocument,
534 const nsACString& aCookieString) {
535 NS_ENSURE_ARG(aDocument);
537 if (!IsInitialized()) {
538 return NS_OK;
541 nsCOMPtr<nsIURI> documentURI;
542 nsAutoCString baseDomain;
543 OriginAttributes attrs;
545 int64_t currentTimeInUsec = PR_Now();
547 // This function is executed in this context, I don't need to keep objects
548 // alive.
549 auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
550 const OriginAttributes& aAttrs) {
551 CookieStorage* storage = PickStorage(aAttrs);
552 return !!storage->CountCookiesFromHost(aBaseDomain,
553 aAttrs.mPrivateBrowsingId);
556 RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
557 aDocument, aCookieString, currentTimeInUsec, mTLDService, mThirdPartyUtil,
558 hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
559 if (!cookie) {
560 return NS_OK;
563 bool thirdParty = true;
564 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
565 // in gtests we don't have a window, let's consider those requests as 3rd
566 // party.
567 if (innerWindow) {
568 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
570 if (thirdPartyUtil) {
571 Unused << thirdPartyUtil->IsThirdPartyWindow(
572 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
576 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
577 cookie, aDocument)) {
578 return NS_OK;
581 nsCOMPtr<nsIConsoleReportCollector> crc =
582 do_QueryInterface(aDocument->GetChannel());
584 // add the cookie to the list. AddCookie() takes care of logging.
585 PickStorage(attrs)->AddCookie(crc, baseDomain, attrs, cookie,
586 currentTimeInUsec, documentURI, aCookieString,
587 false, aDocument->GetBrowsingContext());
588 return NS_OK;
591 NS_IMETHODIMP
592 CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
593 const nsACString& aCookieHeader,
594 nsIChannel* aChannel) {
595 NS_ENSURE_ARG(aHostURI);
596 NS_ENSURE_ARG(aChannel);
598 if (!IsInitialized()) {
599 return NS_OK;
602 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
603 return NS_OK;
606 uint32_t rejectedReason = 0;
607 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
608 aChannel, false, aHostURI, nullptr, &rejectedReason);
610 OriginAttributes attrs;
611 StoragePrincipalHelper::GetOriginAttributes(
612 aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
614 // get the base domain for the host URI.
615 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
616 // file:// URI's (i.e. with an empty host) are allowed, but any other
617 // scheme must have a non-empty host. A trailing dot in the host
618 // is acceptable.
619 bool requireHostMatch;
620 nsAutoCString baseDomain;
621 nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
622 requireHostMatch);
623 if (NS_FAILED(rv)) {
624 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
625 "couldn't get base domain from URI");
626 return NS_OK;
629 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
630 CookieCommons::GetCookieJarSettings(aChannel);
632 nsAutoCString hostFromURI;
633 nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
635 nsAutoCString baseDomainFromURI;
636 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, hostFromURI,
637 baseDomainFromURI);
638 NS_ENSURE_SUCCESS(rv, NS_OK);
640 CookieStorage* storage = PickStorage(attrs);
642 // check default prefs
643 uint32_t priorCookieCount = storage->CountCookiesFromHost(
644 baseDomainFromURI, attrs.mPrivateBrowsingId);
646 nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
648 CookieStatus cookieStatus = CheckPrefs(
649 crc, cookieJarSettings, aHostURI,
650 result.contains(ThirdPartyAnalysis::IsForeign),
651 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
652 result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
653 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
654 aCookieHeader, priorCookieCount, attrs, &rejectedReason);
656 MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
658 // fire a notification if third party or if cookie was rejected
659 // (but not if there was an error)
660 switch (cookieStatus) {
661 case STATUS_REJECTED:
662 CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
663 OPERATION_WRITE);
664 return NS_OK; // Stop here
665 case STATUS_REJECTED_WITH_ERROR:
666 CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
667 OPERATION_WRITE);
668 return NS_OK;
669 case STATUS_ACCEPTED: // Fallthrough
670 case STATUS_ACCEPT_SESSION:
671 NotifyAccepted(aChannel);
672 break;
673 default:
674 break;
677 bool addonAllowsLoad = false;
678 nsCOMPtr<nsIURI> channelURI;
679 NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
680 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
681 addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
682 ->AddonAllowsLoad(channelURI);
684 bool isForeignAndNotAddon = false;
685 if (!addonAllowsLoad) {
686 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
687 &isForeignAndNotAddon);
690 bool mustBePartitioned =
691 isForeignAndNotAddon &&
692 cookieJarSettings->GetCookieBehavior() ==
693 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
694 !result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
696 nsCString cookieHeader(aCookieHeader);
698 bool moreCookieToRead = true;
700 // process each cookie in the header
701 while (moreCookieToRead) {
702 CookieStruct cookieData;
703 bool canSetCookie = false;
705 moreCookieToRead =
706 CanSetCookie(aHostURI, baseDomain, cookieData, requireHostMatch,
707 cookieStatus, cookieHeader, true, isForeignAndNotAddon,
708 mustBePartitioned, crc, canSetCookie);
710 if (!canSetCookie) {
711 continue;
714 // check permissions from site permission list.
715 if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
716 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
717 "cookie rejected by permission manager");
718 CookieCommons::NotifyRejected(
719 aHostURI, aChannel,
720 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
721 OPERATION_WRITE);
722 CookieLogging::LogMessageToConsole(
723 crc, aHostURI, nsIScriptError::warningFlag,
724 CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
725 AutoTArray<nsString, 1>{
726 NS_ConvertUTF8toUTF16(cookieData.name()),
728 continue;
731 // create a new Cookie
732 RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
733 MOZ_ASSERT(cookie);
735 int64_t currentTimeInUsec = PR_Now();
736 cookie->SetLastAccessed(currentTimeInUsec);
737 cookie->SetCreationTime(
738 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
740 RefPtr<BrowsingContext> bc = loadInfo->GetBrowsingContext();
742 // add the cookie to the list. AddCookie() takes care of logging.
743 storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec,
744 aHostURI, aCookieHeader, true, bc);
747 return NS_OK;
750 void CookieService::NotifyAccepted(nsIChannel* aChannel) {
751 ContentBlockingNotifier::OnDecision(
752 aChannel, ContentBlockingNotifier::BlockingDecision::eAllow, 0);
755 /******************************************************************************
756 * CookieService:
757 * public transaction helper impl
758 ******************************************************************************/
760 NS_IMETHODIMP
761 CookieService::RunInTransaction(nsICookieTransactionCallback* aCallback) {
762 NS_ENSURE_ARG(aCallback);
764 if (!IsInitialized()) {
765 return NS_ERROR_NOT_AVAILABLE;
768 mPersistentStorage->EnsureInitialized();
769 return mPersistentStorage->RunInTransaction(aCallback);
772 /******************************************************************************
773 * nsICookieManager impl:
774 * nsICookieManager
775 ******************************************************************************/
777 NS_IMETHODIMP
778 CookieService::RemoveAll() {
779 if (!IsInitialized()) {
780 return NS_ERROR_NOT_AVAILABLE;
783 mPersistentStorage->EnsureInitialized();
784 mPersistentStorage->RemoveAll();
785 return NS_OK;
788 NS_IMETHODIMP
789 CookieService::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
790 if (!IsInitialized()) {
791 return NS_ERROR_NOT_AVAILABLE;
794 mPersistentStorage->EnsureInitialized();
796 // We expose only non-private cookies.
797 mPersistentStorage->GetCookies(aCookies);
799 return NS_OK;
802 NS_IMETHODIMP
803 CookieService::GetSessionCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
804 if (!IsInitialized()) {
805 return NS_ERROR_NOT_AVAILABLE;
808 mPersistentStorage->EnsureInitialized();
810 // We expose only non-private cookies.
811 mPersistentStorage->GetSessionCookies(aCookies);
813 return NS_OK;
816 NS_IMETHODIMP
817 CookieService::Add(const nsACString& aHost, const nsACString& aPath,
818 const nsACString& aName, const nsACString& aValue,
819 bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
820 int64_t aExpiry, JS::Handle<JS::Value> aOriginAttributes,
821 int32_t aSameSite, nsICookie::schemeType aSchemeMap,
822 JSContext* aCx) {
823 OriginAttributes attrs;
825 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
826 return NS_ERROR_INVALID_ARG;
829 return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
830 aIsSession, aExpiry, &attrs, aSameSite, aSchemeMap);
833 NS_IMETHODIMP_(nsresult)
834 CookieService::AddNative(const nsACString& aHost, const nsACString& aPath,
835 const nsACString& aName, const nsACString& aValue,
836 bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
837 int64_t aExpiry, OriginAttributes* aOriginAttributes,
838 int32_t aSameSite, nsICookie::schemeType aSchemeMap) {
839 if (NS_WARN_IF(!aOriginAttributes)) {
840 return NS_ERROR_FAILURE;
843 if (!IsInitialized()) {
844 return NS_ERROR_NOT_AVAILABLE;
847 // first, normalize the hostname, and fail if it contains illegal characters.
848 nsAutoCString host(aHost);
849 nsresult rv = NormalizeHost(host);
850 NS_ENSURE_SUCCESS(rv, rv);
852 // get the base domain for the host URI.
853 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
854 nsAutoCString baseDomain;
855 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
856 NS_ENSURE_SUCCESS(rv, rv);
858 int64_t currentTimeInUsec = PR_Now();
859 CookieKey key = CookieKey(baseDomain, *aOriginAttributes);
861 CookieStruct cookieData(nsCString(aName), nsCString(aValue), nsCString(aHost),
862 nsCString(aPath), aExpiry, currentTimeInUsec,
863 Cookie::GenerateUniqueCreationTime(currentTimeInUsec),
864 aIsHttpOnly, aIsSession, aIsSecure, false, aSameSite,
865 aSameSite, aSchemeMap);
867 RefPtr<Cookie> cookie = Cookie::Create(cookieData, key.mOriginAttributes);
868 MOZ_ASSERT(cookie);
870 CookieStorage* storage = PickStorage(*aOriginAttributes);
871 storage->AddCookie(nullptr, baseDomain, *aOriginAttributes, cookie,
872 currentTimeInUsec, nullptr, VoidCString(), true, nullptr);
873 return NS_OK;
876 nsresult CookieService::Remove(const nsACString& aHost,
877 const OriginAttributes& aAttrs,
878 const nsACString& aName,
879 const nsACString& aPath) {
880 // first, normalize the hostname, and fail if it contains illegal characters.
881 nsAutoCString host(aHost);
882 nsresult rv = NormalizeHost(host);
883 NS_ENSURE_SUCCESS(rv, rv);
885 nsAutoCString baseDomain;
886 if (!host.IsEmpty()) {
887 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
888 NS_ENSURE_SUCCESS(rv, rv);
891 if (!IsInitialized()) {
892 return NS_ERROR_NOT_AVAILABLE;
895 CookieStorage* storage = PickStorage(aAttrs);
896 storage->RemoveCookie(baseDomain, aAttrs, host, PromiseFlatCString(aName),
897 PromiseFlatCString(aPath));
899 return NS_OK;
902 NS_IMETHODIMP
903 CookieService::Remove(const nsACString& aHost, const nsACString& aName,
904 const nsACString& aPath,
905 JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx) {
906 OriginAttributes attrs;
908 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
909 return NS_ERROR_INVALID_ARG;
912 return RemoveNative(aHost, aName, aPath, &attrs);
915 NS_IMETHODIMP_(nsresult)
916 CookieService::RemoveNative(const nsACString& aHost, const nsACString& aName,
917 const nsACString& aPath,
918 OriginAttributes* aOriginAttributes) {
919 if (NS_WARN_IF(!aOriginAttributes)) {
920 return NS_ERROR_FAILURE;
923 nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath);
924 if (NS_WARN_IF(NS_FAILED(rv))) {
925 return rv;
928 return NS_OK;
931 void CookieService::GetCookiesForURI(
932 nsIURI* aHostURI, nsIChannel* aChannel, bool aIsForeign,
933 bool aIsThirdPartyTrackingResource,
934 bool aIsThirdPartySocialTrackingResource,
935 bool aStorageAccessPermissionGranted, uint32_t aRejectedReason,
936 bool aIsSafeTopLevelNav, bool aIsSameSiteForeign,
937 bool aHadCrossSiteRedirects, bool aHttpBound,
938 bool aAllowSecureCookiesToInsecureOrigin,
939 const OriginAttributes& aOriginAttrs, nsTArray<Cookie*>& aCookieList) {
940 NS_ASSERTION(aHostURI, "null host!");
942 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
943 return;
946 if (!IsInitialized()) {
947 return;
950 CookieStorage* storage = PickStorage(aOriginAttrs);
952 // get the base domain, host, and path from the URI.
953 // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
954 // file:// URI's (i.e. with an empty host) are allowed, but any other
955 // scheme must have a non-empty host. A trailing dot in the host
956 // is acceptable.
957 bool requireHostMatch;
958 nsAutoCString baseDomain;
959 nsAutoCString hostFromURI;
960 nsAutoCString pathFromURI;
961 nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
962 requireHostMatch);
963 if (NS_SUCCEEDED(rv)) {
964 rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
966 if (NS_SUCCEEDED(rv)) {
967 rv = aHostURI->GetFilePath(pathFromURI);
969 if (NS_FAILED(rv)) {
970 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(),
971 "invalid host/path from URI");
972 return;
975 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
976 CookieCommons::GetCookieJarSettings(aChannel);
978 nsAutoCString normalizedHostFromURI(hostFromURI);
979 rv = NormalizeHost(normalizedHostFromURI);
980 NS_ENSURE_SUCCESS_VOID(rv);
982 nsAutoCString baseDomainFromURI;
983 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, normalizedHostFromURI,
984 baseDomainFromURI);
985 NS_ENSURE_SUCCESS_VOID(rv);
987 // check default prefs
988 uint32_t rejectedReason = aRejectedReason;
989 uint32_t priorCookieCount = storage->CountCookiesFromHost(
990 baseDomainFromURI, aOriginAttrs.mPrivateBrowsingId);
992 nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
993 CookieStatus cookieStatus = CheckPrefs(
994 crc, cookieJarSettings, aHostURI, aIsForeign,
995 aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource,
996 aStorageAccessPermissionGranted, VoidCString(), priorCookieCount,
997 aOriginAttrs, &rejectedReason);
999 MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
1001 // for GetCookie(), we only fire acceptance/rejection notifications
1002 // (but not if there was an error)
1003 switch (cookieStatus) {
1004 case STATUS_REJECTED:
1005 // If we don't have any cookies from this host, fail silently.
1006 if (priorCookieCount) {
1007 CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
1008 OPERATION_READ);
1010 return;
1011 default:
1012 break;
1015 // Note: The following permissions logic is mirrored in
1016 // extensions::MatchPattern::MatchesCookie.
1017 // If it changes, please update that function, or file a bug for someone
1018 // else to do so.
1020 // check if aHostURI is using an https secure protocol.
1021 // if it isn't, then we can't send a secure cookie over the connection.
1022 // if SchemeIs fails, assume an insecure connection, to be on the safe side
1023 bool potentiallyTurstworthy =
1024 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
1026 int64_t currentTimeInUsec = PR_Now();
1027 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
1028 bool stale = false;
1030 const nsTArray<RefPtr<Cookie>>* cookies =
1031 storage->GetCookiesFromHost(baseDomain, aOriginAttrs);
1032 if (!cookies) {
1033 return;
1036 bool laxByDefault =
1037 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1038 !nsContentUtils::IsURIInPrefList(
1039 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1041 // iterate the cookies!
1042 for (Cookie* cookie : *cookies) {
1043 // check the host, since the base domain lookup is conservative.
1044 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
1045 continue;
1048 // if the cookie is secure and the host scheme isn't, we avoid sending
1049 // cookie if possible. But for process synchronization purposes, we may want
1050 // the content process to know about the cookie (without it's value). In
1051 // which case we will wipe the value before sending
1052 if (cookie->IsSecure() && !potentiallyTurstworthy &&
1053 !aAllowSecureCookiesToInsecureOrigin) {
1054 continue;
1057 // if the cookie is httpOnly and it's not going directly to the HTTP
1058 // connection, don't send it
1059 if (cookie->IsHttpOnly() && !aHttpBound) {
1060 continue;
1063 // if the nsIURI path doesn't match the cookie path, don't send it back
1064 if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
1065 continue;
1068 // check if the cookie has expired
1069 if (cookie->Expiry() <= currentTime) {
1070 continue;
1073 if (aHttpBound && aIsSameSiteForeign) {
1074 bool blockCookie = !ProcessSameSiteCookieForForeignRequest(
1075 aChannel, cookie, aIsSafeTopLevelNav, aHadCrossSiteRedirects,
1076 laxByDefault);
1078 if (blockCookie) {
1079 if (aHadCrossSiteRedirects) {
1080 CookieLogging::LogMessageToConsole(
1081 crc, aHostURI, nsIScriptError::warningFlag,
1082 CONSOLE_REJECTION_CATEGORY, "CookieBlockedCrossSiteRedirect"_ns,
1083 AutoTArray<nsString, 1>{
1084 NS_ConvertUTF8toUTF16(cookie->Name()),
1087 continue;
1091 // all checks passed - add to list and check if lastAccessed stamp needs
1092 // updating
1093 aCookieList.AppendElement(cookie);
1094 if (cookie->IsStale()) {
1095 stale = true;
1099 if (aCookieList.IsEmpty()) {
1100 return;
1103 // Send a notification about the acceptance of the cookies now that we found
1104 // some.
1105 NotifyAccepted(aChannel);
1107 // update lastAccessed timestamps. we only do this if the timestamp is stale
1108 // by a certain amount, to avoid thrashing the db during pageload.
1109 if (stale) {
1110 storage->StaleCookies(aCookieList, currentTimeInUsec);
1113 // return cookies in order of path length; longest to shortest.
1114 // this is required per RFC2109. if cookies match in length,
1115 // then sort by creation time (see bug 236772).
1116 aCookieList.Sort(CompareCookiesForSending());
1119 static bool ContainsUnicodeChars(const nsCString& str) {
1120 const auto* start = str.BeginReading();
1121 const auto* end = str.EndReading();
1123 return std::find_if(start, end, [](unsigned char c) { return c >= 0x80; }) !=
1124 end;
1127 static void RecordUnicodeTelemetry(const CookieStruct& cookieData) {
1128 auto label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::none;
1129 if (ContainsUnicodeChars(cookieData.name())) {
1130 label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeName;
1131 } else if (ContainsUnicodeChars(cookieData.value())) {
1132 label = Telemetry::LABELS_NETWORK_COOKIE_UNICODE_BYTE::unicodeValue;
1134 Telemetry::AccumulateCategorical(label);
1137 static void RecordPartitionedTelemetry(const CookieStruct& aCookieData,
1138 bool aIsForeign) {
1139 mozilla::glean::networking::set_cookie.Add(1);
1140 if (aCookieData.isPartitioned()) {
1141 mozilla::glean::networking::set_cookie_partitioned.AddToNumerator(1);
1143 if (aIsForeign) {
1144 mozilla::glean::networking::set_cookie_foreign.AddToNumerator(1);
1146 if (aIsForeign && aCookieData.isPartitioned()) {
1147 mozilla::glean::networking::set_cookie_foreign_partitioned.AddToNumerator(
1152 // processes a single cookie, and returns true if there are more cookies
1153 // to be processed
1154 bool CookieService::CanSetCookie(
1155 nsIURI* aHostURI, const nsACString& aBaseDomain, CookieStruct& aCookieData,
1156 bool aRequireHostMatch, CookieStatus aStatus, nsCString& aCookieHeader,
1157 bool aFromHttp, bool aIsForeignAndNotAddon, bool aPartitionedOnly,
1158 nsIConsoleReportCollector* aCRC, bool& aSetCookie) {
1159 MOZ_ASSERT(aHostURI);
1161 aSetCookie = false;
1163 // init expiryTime such that session cookies won't prematurely expire
1164 aCookieData.expiry() = INT64_MAX;
1166 aCookieData.schemeMap() = CookieCommons::URIToSchemeType(aHostURI);
1168 // aCookieHeader is an in/out param to point to the next cookie, if
1169 // there is one. Save the present value for logging purposes
1170 nsCString savedCookieHeader(aCookieHeader);
1172 // newCookie says whether there are multiple cookies in the header;
1173 // so we can handle them separately.
1174 nsAutoCString expires;
1175 nsAutoCString maxage;
1176 bool acceptedByParser = false;
1177 bool newCookie = ParseAttributes(aCRC, aHostURI, aCookieHeader, aCookieData,
1178 expires, maxage, acceptedByParser);
1179 if (!acceptedByParser) {
1180 return newCookie;
1183 // Collect telemetry on how often secure cookies are set from non-secure
1184 // origins, and vice-versa.
1186 // 0 = nonsecure and "http:"
1187 // 1 = nonsecure and "https:"
1188 // 2 = secure and "http:"
1189 // 3 = secure and "https:"
1190 bool potentiallyTurstworthy =
1191 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
1193 int64_t currentTimeInUsec = PR_Now();
1195 // calculate expiry time of cookie.
1196 aCookieData.isSession() =
1197 GetExpiry(aCookieData, expires, maxage,
1198 currentTimeInUsec / PR_USEC_PER_SEC, aFromHttp);
1199 if (aStatus == STATUS_ACCEPT_SESSION) {
1200 // force lifetime to session. note that the expiration time, if set above,
1201 // will still apply.
1202 aCookieData.isSession() = true;
1205 // reject cookie if it's over the size limit, per RFC2109
1206 if (!CookieCommons::CheckNameAndValueSize(aCookieData)) {
1207 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1208 "cookie too big (> 4kb)");
1210 AutoTArray<nsString, 2> params = {
1211 NS_ConvertUTF8toUTF16(aCookieData.name())};
1213 nsString size;
1214 size.AppendInt(kMaxBytesPerCookie);
1215 params.AppendElement(size);
1217 CookieLogging::LogMessageToConsole(
1218 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
1219 "CookieOversize"_ns, params);
1220 return newCookie;
1223 RecordUnicodeTelemetry(aCookieData);
1225 // We count SetCookie operations in the parent process only for HTTP set
1226 // cookies to prevent double counting.
1227 if (XRE_IsParentProcess() || !aFromHttp) {
1228 RecordPartitionedTelemetry(aCookieData, aIsForeignAndNotAddon);
1231 if (!CookieCommons::CheckName(aCookieData)) {
1232 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1233 "invalid name character");
1234 CookieLogging::LogMessageToConsole(
1235 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1236 "CookieRejectedInvalidCharName"_ns,
1237 AutoTArray<nsString, 1>{
1238 NS_ConvertUTF8toUTF16(aCookieData.name()),
1240 return newCookie;
1243 // domain & path checks
1244 if (!CheckDomain(aCookieData, aHostURI, aBaseDomain, aRequireHostMatch)) {
1245 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1246 "failed the domain tests");
1247 CookieLogging::LogMessageToConsole(
1248 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1249 "CookieRejectedInvalidDomain"_ns,
1250 AutoTArray<nsString, 1>{
1251 NS_ConvertUTF8toUTF16(aCookieData.name()),
1253 return newCookie;
1256 if (!CheckPath(aCookieData, aCRC, aHostURI)) {
1257 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1258 "failed the path tests");
1259 return newCookie;
1262 if (!CheckHiddenPrefix(aCookieData)) {
1263 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1264 "failed the CheckHiddenPrefix tests");
1265 CookieLogging::LogMessageToConsole(
1266 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1267 "CookieRejectedInvalidPrefix"_ns,
1268 AutoTArray<nsString, 1>{
1269 NS_ConvertUTF8toUTF16(aCookieData.name()),
1271 return newCookie;
1274 // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
1275 if (!CheckPrefixes(aCookieData, potentiallyTurstworthy)) {
1276 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1277 "failed the prefix tests");
1278 CookieLogging::LogMessageToConsole(
1279 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1280 "CookieRejectedInvalidPrefix"_ns,
1281 AutoTArray<nsString, 1>{
1282 NS_ConvertUTF8toUTF16(aCookieData.name()),
1284 return newCookie;
1287 if (!CookieCommons::CheckValue(aCookieData)) {
1288 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1289 "invalid value character");
1290 CookieLogging::LogMessageToConsole(
1291 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1292 "CookieRejectedInvalidCharValue"_ns,
1293 AutoTArray<nsString, 1>{
1294 NS_ConvertUTF8toUTF16(aCookieData.name()),
1296 return newCookie;
1299 // if the new cookie is httponly, make sure we're not coming from script
1300 if (!aFromHttp && aCookieData.isHttpOnly()) {
1301 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1302 "cookie is httponly; coming from script");
1303 CookieLogging::LogMessageToConsole(
1304 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1305 "CookieRejectedHttpOnlyButFromScript"_ns,
1306 AutoTArray<nsString, 1>{
1307 NS_ConvertUTF8toUTF16(aCookieData.name()),
1309 return newCookie;
1312 // If the new cookie is non-https and wants to set secure flag,
1313 // browser have to ignore this new cookie.
1314 // (draft-ietf-httpbis-cookie-alone section 3.1)
1315 if (aCookieData.isSecure() && !potentiallyTurstworthy) {
1316 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
1317 "non-https cookie can't set secure flag");
1318 CookieLogging::LogMessageToConsole(
1319 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1320 "CookieRejectedSecureButNonHttps"_ns,
1321 AutoTArray<nsString, 1>{
1322 NS_ConvertUTF8toUTF16(aCookieData.name()),
1324 return newCookie;
1327 // If the new cookie is same-site but in a cross site context,
1328 // browser must ignore the cookie.
1329 bool laxByDefault =
1330 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1331 !nsContentUtils::IsURIInPrefList(
1332 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1333 auto effectiveSameSite =
1334 laxByDefault ? aCookieData.sameSite() : aCookieData.rawSameSite();
1335 if ((effectiveSameSite != nsICookie::SAMESITE_NONE) &&
1336 aIsForeignAndNotAddon) {
1337 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1338 "failed the samesite tests");
1340 CookieLogging::LogMessageToConsole(
1341 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
1342 "CookieRejectedForNonSameSiteness"_ns,
1343 AutoTArray<nsString, 1>{
1344 NS_ConvertUTF8toUTF16(aCookieData.name()),
1346 return newCookie;
1349 // If the cookie does not have the partitioned attribute,
1350 // but is foreign we should give the developer a message.
1351 // If CHIPS isn't required yet, we will warn the console
1352 // that we have upcoming changes. Otherwise we give a rejection message.
1353 if (aPartitionedOnly && !aCookieData.isPartitioned() &&
1354 aIsForeignAndNotAddon) {
1355 if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
1356 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1357 "foreign cookies must be partitioned");
1358 CookieLogging::LogMessageToConsole(
1359 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
1360 "CookieForeignNoPartitionedError"_ns,
1361 AutoTArray<nsString, 1>{
1362 NS_ConvertUTF8toUTF16(aCookieData.name()),
1364 return newCookie;
1366 CookieLogging::LogMessageToConsole(
1367 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
1368 "CookieForeignNoPartitionedWarning"_ns,
1369 AutoTArray<nsString, 1>{
1370 NS_ConvertUTF8toUTF16(aCookieData.name()),
1374 aSetCookie = true;
1375 return newCookie;
1378 /******************************************************************************
1379 * CookieService impl:
1380 * private cookie header parsing functions
1381 ******************************************************************************/
1383 // clang-format off
1384 // The following comment block elucidates the function of ParseAttributes.
1385 /******************************************************************************
1386 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1387 ** please note: this BNF deviates from both specifications, and reflects this
1388 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1390 ** Differences from RFC2109/2616 and explanations:
1391 1. implied *LWS
1392 The grammar described by this specification is word-based. Except
1393 where noted otherwise, linear white space (<LWS>) can be included
1394 between any two adjacent words (token or quoted-string), and
1395 between adjacent words and separators, without changing the
1396 interpretation of a field.
1397 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1399 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1400 common use inside values.
1402 3. tokens and values have looser restrictions on allowed characters than
1403 spec. This is also due to certain characters being in common use inside
1404 values. We allow only '=' to separate token/value pairs, and ';' to
1405 terminate tokens or values. <LWS> is allowed within tokens and values
1406 (see bug 206022).
1408 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1409 reject control chars or non-ASCII chars. This is erring on the loose
1410 side, since there's probably no good reason to enforce this strictness.
1412 5. Attribute "HttpOnly", not covered in the RFCs, is supported
1413 (see bug 178993).
1415 ** Begin BNF:
1416 token = 1*<any allowed-chars except separators>
1417 value = 1*<any allowed-chars except value-sep>
1418 separators = ";" | "="
1419 value-sep = ";"
1420 cookie-sep = CR | LF
1421 allowed-chars = <any OCTET except NUL or cookie-sep>
1422 OCTET = <any 8-bit sequence of data>
1423 LWS = SP | HT
1424 NUL = <US-ASCII NUL, null control character (0)>
1425 CR = <US-ASCII CR, carriage return (13)>
1426 LF = <US-ASCII LF, linefeed (10)>
1427 SP = <US-ASCII SP, space (32)>
1428 HT = <US-ASCII HT, horizontal-tab (9)>
1430 set-cookie = "Set-Cookie:" cookies
1431 cookies = cookie *( cookie-sep cookie )
1432 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
1433 NAME = token ; cookie name
1434 VALUE = value ; cookie value
1435 cookie-av = token ["=" value]
1437 valid values for cookie-av (checked post-parsing) are:
1438 cookie-av = "Path" "=" value
1439 | "Domain" "=" value
1440 | "Expires" "=" value
1441 | "Max-Age" "=" value
1442 | "Comment" "=" value
1443 | "Version" "=" value
1444 | "Secure"
1445 | "HttpOnly"
1447 ******************************************************************************/
1448 // clang-format on
1450 // helper functions for GetTokenValue
1451 static inline bool isnull(char c) { return c == 0; }
1452 static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
1453 static inline bool isterminator(char c) { return c == '\n' || c == '\r'; }
1454 static inline bool isvalueseparator(char c) {
1455 return isterminator(c) || c == ';';
1457 static inline bool istokenseparator(char c) {
1458 return isvalueseparator(c) || c == '=';
1461 // Parse a single token/value pair.
1462 // Returns true if a cookie terminator is found, so caller can parse new cookie.
1463 bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
1464 nsACString::const_char_iterator& aEndIter,
1465 nsDependentCSubstring& aTokenString,
1466 nsDependentCSubstring& aTokenValue,
1467 bool& aEqualsFound) {
1468 nsACString::const_char_iterator start;
1469 nsACString::const_char_iterator lastSpace;
1470 // initialize value string to clear garbage
1471 aTokenValue.Rebind(aIter, aIter);
1473 // find <token>, including any <LWS> between the end-of-token and the
1474 // token separator. we'll remove trailing <LWS> next
1475 while (aIter != aEndIter && iswhitespace(*aIter)) {
1476 ++aIter;
1478 start = aIter;
1479 while (aIter != aEndIter && !isnull(*aIter) && !istokenseparator(*aIter)) {
1480 ++aIter;
1483 // remove trailing <LWS>; first check we're not at the beginning
1484 lastSpace = aIter;
1485 if (lastSpace != start) {
1486 while (--lastSpace != start && iswhitespace(*lastSpace)) {
1488 ++lastSpace;
1490 aTokenString.Rebind(start, lastSpace);
1492 aEqualsFound = (*aIter == '=');
1493 if (aEqualsFound) {
1494 // find <value>
1495 while (++aIter != aEndIter && iswhitespace(*aIter)) {
1498 start = aIter;
1500 // process <token>
1501 // just look for ';' to terminate ('=' allowed)
1502 while (aIter != aEndIter && !isnull(*aIter) && !isvalueseparator(*aIter)) {
1503 ++aIter;
1506 // remove trailing <LWS>; first check we're not at the beginning
1507 if (aIter != start) {
1508 lastSpace = aIter;
1509 while (--lastSpace != start && iswhitespace(*lastSpace)) {
1512 aTokenValue.Rebind(start, ++lastSpace);
1516 // aIter is on ';', or terminator, or EOS
1517 if (aIter != aEndIter) {
1518 // if on terminator, increment past & return true to process new cookie
1519 if (isterminator(*aIter)) {
1520 ++aIter;
1521 return true;
1523 // fall-through: aIter is on ';', increment and return false
1524 ++aIter;
1526 return false;
1529 static inline void SetSameSiteAttributeDefault(CookieStruct& aCookieData) {
1530 // Set cookie with SameSite attribute that is treated as Default
1531 // and doesn't requires changing the DB schema.
1532 aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
1533 aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
1536 static inline void SetSameSiteAttribute(CookieStruct& aCookieData,
1537 int32_t aValue) {
1538 aCookieData.sameSite() = aValue;
1539 aCookieData.rawSameSite() = aValue;
1542 // Parses attributes from cookie header. expires/max-age attributes aren't
1543 // folded into the cookie struct here, because we don't know which one to use
1544 // until we've parsed the header.
1545 bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
1546 nsIURI* aHostURI, nsCString& aCookieHeader,
1547 CookieStruct& aCookieData,
1548 nsACString& aExpires, nsACString& aMaxage,
1549 bool& aAcceptedByParser) {
1550 aAcceptedByParser = false;
1552 static const char kPath[] = "path";
1553 static const char kDomain[] = "domain";
1554 static const char kExpires[] = "expires";
1555 static const char kMaxage[] = "max-age";
1556 static const char kSecure[] = "secure";
1557 static const char kHttpOnly[] = "httponly";
1558 static const char kSameSite[] = "samesite";
1559 static const char kSameSiteLax[] = "lax";
1560 static const char kSameSiteNone[] = "none";
1561 static const char kSameSiteStrict[] = "strict";
1562 static const char kPartitioned[] = "partitioned";
1564 nsACString::const_char_iterator cookieStart;
1565 aCookieHeader.BeginReading(cookieStart);
1567 nsACString::const_char_iterator cookieEnd;
1568 aCookieHeader.EndReading(cookieEnd);
1570 aCookieData.isSecure() = false;
1571 aCookieData.isHttpOnly() = false;
1573 SetSameSiteAttributeDefault(aCookieData);
1575 nsDependentCSubstring tokenString(cookieStart, cookieStart);
1576 nsDependentCSubstring tokenValue(cookieStart, cookieStart);
1577 bool newCookie;
1578 bool equalsFound;
1580 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1581 // if we find multiple cookies, return for processing
1582 // note: if there's no '=', we assume token is <VALUE>. this is required by
1583 // some sites (see bug 169091).
1584 // XXX fix the parser to parse according to <VALUE> grammar for this case
1585 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
1586 equalsFound);
1587 if (equalsFound) {
1588 aCookieData.name() = tokenString;
1589 aCookieData.value() = tokenValue;
1590 } else {
1591 aCookieData.value() = tokenString;
1594 // extract remaining attributes
1595 while (cookieStart != cookieEnd && !newCookie) {
1596 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
1597 equalsFound);
1599 // decide which attribute we have, and copy the string
1600 if (tokenString.LowerCaseEqualsLiteral(kPath)) {
1601 aCookieData.path() = tokenValue;
1603 } else if (tokenString.LowerCaseEqualsLiteral(kDomain)) {
1604 aCookieData.host() = tokenValue;
1606 } else if (tokenString.LowerCaseEqualsLiteral(kExpires)) {
1607 aExpires = tokenValue;
1609 } else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) {
1610 aMaxage = tokenValue;
1612 // ignore any tokenValue for isSecure; just set the boolean
1613 } else if (tokenString.LowerCaseEqualsLiteral(kSecure)) {
1614 aCookieData.isSecure() = true;
1616 // ignore any tokenValue for isPartitioned; just set the boolean
1617 } else if (tokenString.LowerCaseEqualsLiteral(kPartitioned)) {
1618 aCookieData.isPartitioned() = true;
1620 // ignore any tokenValue for isHttpOnly (see bug 178993);
1621 // just set the boolean
1622 } else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) {
1623 aCookieData.isHttpOnly() = true;
1625 } else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
1626 if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
1627 SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_LAX);
1628 } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
1629 SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_STRICT);
1630 } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteNone)) {
1631 SetSameSiteAttribute(aCookieData, nsICookie::SAMESITE_NONE);
1632 } else {
1633 // Reset to Default if unknown token value (see Bug 1682450)
1634 SetSameSiteAttributeDefault(aCookieData);
1635 CookieLogging::LogMessageToConsole(
1636 aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1637 "CookieSameSiteValueInvalid2"_ns,
1638 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1643 // re-assign aCookieHeader, in case we need to process another cookie
1644 aCookieHeader.Assign(Substring(cookieStart, cookieEnd));
1646 // If same-site is explicitly set to 'none' but this is not a secure context,
1647 // let's abort the parsing.
1648 if (!aCookieData.isSecure() &&
1649 aCookieData.sameSite() == nsICookie::SAMESITE_NONE) {
1650 if (StaticPrefs::network_cookie_sameSite_noneRequiresSecure()) {
1651 CookieLogging::LogMessageToConsole(
1652 aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_SAMESITE_CATEGORY,
1653 "CookieRejectedNonRequiresSecure2"_ns,
1654 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1655 return newCookie;
1658 // Still warn about the missing Secure attribute when not enforcing.
1659 CookieLogging::LogMessageToConsole(
1660 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
1661 "CookieRejectedNonRequiresSecureForBeta3"_ns,
1662 AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
1663 SAMESITE_MDN_URL});
1666 if (aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE &&
1667 aCookieData.sameSite() == nsICookie::SAMESITE_LAX) {
1668 bool laxByDefault =
1669 StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1670 !nsContentUtils::IsURIInPrefList(
1671 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1672 if (laxByDefault) {
1673 CookieLogging::LogMessageToConsole(
1674 aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1675 "CookieLaxForced2"_ns,
1676 AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1677 } else {
1678 CookieLogging::LogMessageToConsole(
1679 aCRC, aHostURI, nsIScriptError::warningFlag,
1680 CONSOLE_SAMESITE_CATEGORY, "CookieLaxForcedForBeta2"_ns,
1681 AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
1682 SAMESITE_MDN_URL});
1686 // Cookie accepted.
1687 aAcceptedByParser = true;
1689 MOZ_ASSERT(Cookie::ValidateSameSite(aCookieData));
1690 return newCookie;
1693 /******************************************************************************
1694 * CookieService impl:
1695 * private domain & permission compliance enforcement functions
1696 ******************************************************************************/
1698 // Normalizes the given hostname, component by component. ASCII/ACE
1699 // components are lower-cased, and UTF-8 components are normalized per
1700 // RFC 3454 and converted to ACE.
1701 nsresult CookieService::NormalizeHost(nsCString& aHost) {
1702 if (!IsAscii(aHost)) {
1703 nsAutoCString host;
1704 nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
1705 if (NS_FAILED(rv)) {
1706 return rv;
1709 aHost = host;
1712 ToLowerCase(aHost);
1713 return NS_OK;
1716 // returns true if 'a' is equal to or a subdomain of 'b',
1717 // assuming no leading dots are present.
1718 static inline bool IsSubdomainOf(const nsACString& a, const nsACString& b) {
1719 if (a == b) {
1720 return true;
1722 if (a.Length() > b.Length()) {
1723 return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
1725 return false;
1728 CookieStatus CookieService::CheckPrefs(
1729 nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings,
1730 nsIURI* aHostURI, bool aIsForeign, bool aIsThirdPartyTrackingResource,
1731 bool aIsThirdPartySocialTrackingResource,
1732 bool aStorageAccessPermissionGranted, const nsACString& aCookieHeader,
1733 const int aNumOfCookies, const OriginAttributes& aOriginAttrs,
1734 uint32_t* aRejectedReason) {
1735 nsresult rv;
1737 MOZ_ASSERT(aRejectedReason);
1739 *aRejectedReason = 0;
1741 // don't let unsupported scheme sites get/set cookies (could be a security
1742 // issue)
1743 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
1744 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1745 "non http/https sites cannot read cookies");
1746 return STATUS_REJECTED_WITH_ERROR;
1749 nsCOMPtr<nsIPrincipal> principal =
1750 BasePrincipal::CreateContentPrincipal(aHostURI, aOriginAttrs);
1752 if (!principal) {
1753 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1754 "non-content principals cannot get/set cookies");
1755 return STATUS_REJECTED_WITH_ERROR;
1758 // check the permission list first; if we find an entry, it overrides
1759 // default prefs. see bug 184059.
1760 uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
1761 rv = aCookieJarSettings->CookiePermission(principal, &cookiePermission);
1762 if (NS_SUCCEEDED(rv)) {
1763 switch (cookiePermission) {
1764 case nsICookiePermission::ACCESS_DENY:
1765 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1766 "cookies are blocked for this site");
1767 CookieLogging::LogMessageToConsole(
1768 aCRC, aHostURI, nsIScriptError::warningFlag,
1769 CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
1770 AutoTArray<nsString, 1>{
1771 NS_ConvertUTF8toUTF16(aCookieHeader),
1774 *aRejectedReason =
1775 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION;
1776 return STATUS_REJECTED;
1778 case nsICookiePermission::ACCESS_ALLOW:
1779 return STATUS_ACCEPTED;
1780 default:
1781 break;
1785 // No cookies allowed if this request comes from a resource in a 3rd party
1786 // context, when anti-tracking protection is enabled and when we don't have
1787 // access to the first-party cookie jar.
1788 if (aIsForeign && aIsThirdPartyTrackingResource &&
1789 !aStorageAccessPermissionGranted &&
1790 aCookieJarSettings->GetRejectThirdPartyContexts()) {
1791 uint32_t rejectReason =
1792 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
1793 if (StoragePartitioningEnabled(rejectReason, aCookieJarSettings)) {
1794 MOZ_ASSERT(!aOriginAttrs.mPartitionKey.IsEmpty(),
1795 "We must have a StoragePrincipal here!");
1796 return STATUS_ACCEPTED;
1799 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1800 "cookies are disabled in trackers");
1801 if (aIsThirdPartySocialTrackingResource) {
1802 *aRejectedReason =
1803 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
1804 } else {
1805 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
1807 return STATUS_REJECTED;
1810 // check default prefs.
1811 // Check aStorageAccessPermissionGranted when checking aCookieBehavior
1812 // so that we take things such as the content blocking allow list into
1813 // account.
1814 if (aCookieJarSettings->GetCookieBehavior() ==
1815 nsICookieService::BEHAVIOR_REJECT &&
1816 !aStorageAccessPermissionGranted) {
1817 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1818 "cookies are disabled");
1819 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL;
1820 return STATUS_REJECTED;
1823 // check if cookie is foreign
1824 if (aIsForeign) {
1825 if (aCookieJarSettings->GetCookieBehavior() ==
1826 nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
1827 !aStorageAccessPermissionGranted) {
1828 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1829 "context is third party");
1830 CookieLogging::LogMessageToConsole(
1831 aCRC, aHostURI, nsIScriptError::warningFlag,
1832 CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
1833 AutoTArray<nsString, 1>{
1834 NS_ConvertUTF8toUTF16(aCookieHeader),
1836 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1837 return STATUS_REJECTED;
1840 if (aCookieJarSettings->GetLimitForeignContexts() &&
1841 !aStorageAccessPermissionGranted && aNumOfCookies == 0) {
1842 COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1843 "context is third party");
1844 CookieLogging::LogMessageToConsole(
1845 aCRC, aHostURI, nsIScriptError::warningFlag,
1846 CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
1847 AutoTArray<nsString, 1>{
1848 NS_ConvertUTF8toUTF16(aCookieHeader),
1850 *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1851 return STATUS_REJECTED;
1854 if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
1855 return STATUS_ACCEPT_SESSION;
1858 if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly()) {
1859 if (!aHostURI->SchemeIs("https")) {
1860 return STATUS_ACCEPT_SESSION;
1865 // if nothing has complained, accept cookie
1866 return STATUS_ACCEPTED;
1869 // processes domain attribute, and returns true if host has permission to set
1870 // for this domain.
1871 bool CookieService::CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
1872 const nsACString& aBaseDomain,
1873 bool aRequireHostMatch) {
1874 // Note: The logic in this function is mirrored in
1875 // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
1876 // If it changes, please update that function, or file a bug for someone
1877 // else to do so.
1879 // get host from aHostURI
1880 nsAutoCString hostFromURI;
1881 nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
1883 // if a domain is given, check the host has permission
1884 if (!aCookieData.host().IsEmpty()) {
1885 // Tolerate leading '.' characters, but not if it's otherwise an empty host.
1886 if (aCookieData.host().Length() > 1 && aCookieData.host().First() == '.') {
1887 aCookieData.host().Cut(0, 1);
1890 // switch to lowercase now, to avoid case-insensitive compares everywhere
1891 ToLowerCase(aCookieData.host());
1893 // check whether the host is either an IP address, an alias such as
1894 // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
1895 // cases, require an exact string match for the domain, and leave the cookie
1896 // as a non-domain one. bug 105917 originally noted the requirement to deal
1897 // with IP addresses.
1898 if (aRequireHostMatch) {
1899 return hostFromURI.Equals(aCookieData.host());
1902 // ensure the proposed domain is derived from the base domain; and also
1903 // that the host domain is derived from the proposed domain (per RFC2109).
1904 if (IsSubdomainOf(aCookieData.host(), aBaseDomain) &&
1905 IsSubdomainOf(hostFromURI, aCookieData.host())) {
1906 // prepend a dot to indicate a domain cookie
1907 aCookieData.host().InsertLiteral(".", 0);
1908 return true;
1912 * note: RFC2109 section 4.3.2 requires that we check the following:
1913 * that the portion of host not in domain does not contain a dot.
1914 * this prevents hosts of the form x.y.co.nz from setting cookies in the
1915 * entire .co.nz domain. however, it's only a only a partial solution and
1916 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1918 return false;
1921 // no domain specified, use hostFromURI
1922 aCookieData.host() = hostFromURI;
1923 return true;
1926 // static
1927 bool CookieService::CheckHiddenPrefix(CookieStruct& aCookieData) {
1928 // If a cookie is nameless, then its value must not start with
1929 // `__Host-` or `__Secure-`
1930 if (!aCookieData.name().IsEmpty()) {
1931 return true;
1934 if (StringBeginsWith(aCookieData.value(), "__Host-"_ns)) {
1935 return false;
1938 if (StringBeginsWith(aCookieData.value(), "__Secure-"_ns)) {
1939 return false;
1942 return true;
1945 namespace {
1946 nsAutoCString GetPathFromURI(nsIURI* aHostURI) {
1947 // strip down everything after the last slash to get the path,
1948 // ignoring slashes in the query string part.
1949 // if we can QI to nsIURL, that'll take care of the query string portion.
1950 // otherwise, it's not an nsIURL and can't have a query string, so just find
1951 // the last slash.
1952 nsAutoCString path;
1953 nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
1954 if (hostURL) {
1955 hostURL->GetDirectory(path);
1956 } else {
1957 aHostURI->GetPathQueryRef(path);
1958 int32_t slash = path.RFindChar('/');
1959 if (slash != kNotFound) {
1960 path.Truncate(slash + 1);
1964 // strip the right-most %x2F ("/") if the path doesn't contain only 1 '/'.
1965 int32_t lastSlash = path.RFindChar('/');
1966 int32_t firstSlash = path.FindChar('/');
1967 if (lastSlash != firstSlash && lastSlash != kNotFound &&
1968 lastSlash == static_cast<int32_t>(path.Length() - 1)) {
1969 path.Truncate(lastSlash);
1972 return path;
1975 } // namespace
1977 bool CookieService::CheckPath(CookieStruct& aCookieData,
1978 nsIConsoleReportCollector* aCRC,
1979 nsIURI* aHostURI) {
1980 // if a path is given, check the host has permission
1981 if (aCookieData.path().IsEmpty() || aCookieData.path().First() != '/') {
1982 aCookieData.path() = GetPathFromURI(aHostURI);
1985 if (!CookieCommons::CheckPathSize(aCookieData)) {
1986 AutoTArray<nsString, 2> params = {
1987 NS_ConvertUTF8toUTF16(aCookieData.name())};
1989 nsString size;
1990 size.AppendInt(kMaxBytesPerPath);
1991 params.AppendElement(size);
1993 CookieLogging::LogMessageToConsole(
1994 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
1995 "CookiePathOversize"_ns, params);
1996 return false;
1999 return !aCookieData.path().Contains('\t');
2002 // CheckPrefixes
2004 // Reject cookies whose name starts with the magic prefixes from
2005 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
2006 // if they do not meet the criteria required by the prefix.
2008 // Must not be called until after CheckDomain() and CheckPath() have
2009 // regularized and validated the CookieStruct values!
2010 bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
2011 bool aSecureRequest) {
2012 static const char kSecure[] = "__Secure-";
2013 static const char kHost[] = "__Host-";
2014 static const int kSecureLen = sizeof(kSecure) - 1;
2015 static const int kHostLen = sizeof(kHost) - 1;
2017 bool isSecure = strncmp(aCookieData.name().get(), kSecure, kSecureLen) == 0;
2018 bool isHost = strncmp(aCookieData.name().get(), kHost, kHostLen) == 0;
2020 if (!isSecure && !isHost) {
2021 // not one of the magic prefixes: carry on
2022 return true;
2025 if (!aSecureRequest || !aCookieData.isSecure()) {
2026 // the magic prefixes may only be used from a secure request and
2027 // the secure attribute must be set on the cookie
2028 return false;
2031 if (isHost) {
2032 // The host prefix requires that the path is "/" and that the cookie
2033 // had no domain attribute. CheckDomain() and CheckPath() MUST be run
2034 // first to make sure invalid attributes are rejected and to regularlize
2035 // them. In particular all explicit domain attributes result in a host
2036 // that starts with a dot, and if the host doesn't start with a dot it
2037 // correctly matches the true host.
2038 if (aCookieData.host()[0] == '.' ||
2039 !aCookieData.path().EqualsLiteral("/")) {
2040 return false;
2044 return true;
2047 bool CookieService::GetExpiry(CookieStruct& aCookieData,
2048 const nsACString& aExpires,
2049 const nsACString& aMaxage, int64_t aCurrentTime,
2050 bool aFromHttp) {
2051 // maxageCap is in seconds.
2052 // Disabled for HTTP cookies.
2053 int64_t maxageCap =
2054 aFromHttp ? 0 : StaticPrefs::privacy_documentCookies_maxage();
2056 /* Determine when the cookie should expire. This is done by taking the
2057 * difference between the server time and the time the server wants the cookie
2058 * to expire, and adding that difference to the client time. This localizes
2059 * the client time regardless of whether or not the TZ environment variable
2060 * was set on the client.
2062 * Note: We need to consider accounting for network lag here, per RFC.
2064 // check for max-age attribute first; this overrides expires attribute
2065 if (!aMaxage.IsEmpty()) {
2066 // obtain numeric value of maxageAttribute
2067 int64_t maxage;
2068 int32_t numInts = PR_sscanf(aMaxage.BeginReading(), "%lld", &maxage);
2070 // default to session cookie if the conversion failed
2071 if (numInts != 1) {
2072 return true;
2075 // if this addition overflows, expiryTime will be less than currentTime
2076 // and the cookie will be expired - that's okay.
2077 if (maxageCap) {
2078 aCookieData.expiry() = aCurrentTime + std::min(maxage, maxageCap);
2079 } else {
2080 aCookieData.expiry() = aCurrentTime + maxage;
2083 // check for expires attribute
2084 } else if (!aExpires.IsEmpty()) {
2085 PRTime expires;
2087 // parse expiry time
2088 if (PR_ParseTimeString(aExpires.BeginReading(), true, &expires) !=
2089 PR_SUCCESS) {
2090 return true;
2093 // If set-cookie used absolute time to set expiration, and it can't use
2094 // client time to set expiration.
2095 // Because if current time be set in the future, but the cookie expire
2096 // time be set less than current time and more than server time.
2097 // The cookie item have to be used to the expired cookie.
2098 if (maxageCap) {
2099 aCookieData.expiry() = std::min(expires / int64_t(PR_USEC_PER_SEC),
2100 aCurrentTime + maxageCap);
2101 } else {
2102 aCookieData.expiry() = expires / int64_t(PR_USEC_PER_SEC);
2105 // default to session cookie if no attributes found. Here we don't need to
2106 // enforce the maxage cap, because session cookies are short-lived by
2107 // definition.
2108 } else {
2109 return true;
2112 return false;
2115 /******************************************************************************
2116 * CookieService impl:
2117 * private cookielist management functions
2118 ******************************************************************************/
2120 // find whether a given cookie has been previously set. this is provided by the
2121 // nsICookieManager interface.
2122 NS_IMETHODIMP
2123 CookieService::CookieExists(const nsACString& aHost, const nsACString& aPath,
2124 const nsACString& aName,
2125 JS::Handle<JS::Value> aOriginAttributes,
2126 JSContext* aCx, bool* aFoundCookie) {
2127 NS_ENSURE_ARG_POINTER(aCx);
2128 NS_ENSURE_ARG_POINTER(aFoundCookie);
2130 OriginAttributes attrs;
2131 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2132 return NS_ERROR_INVALID_ARG;
2134 return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie);
2137 NS_IMETHODIMP_(nsresult)
2138 CookieService::CookieExistsNative(const nsACString& aHost,
2139 const nsACString& aPath,
2140 const nsACString& aName,
2141 OriginAttributes* aOriginAttributes,
2142 bool* aFoundCookie) {
2143 nsCOMPtr<nsICookie> cookie;
2144 nsresult rv = GetCookieNative(aHost, aPath, aName, aOriginAttributes,
2145 getter_AddRefs(cookie));
2146 NS_ENSURE_SUCCESS(rv, rv);
2148 *aFoundCookie = cookie != nullptr;
2150 return NS_OK;
2153 NS_IMETHODIMP_(nsresult)
2154 CookieService::GetCookieNative(const nsACString& aHost, const nsACString& aPath,
2155 const nsACString& aName,
2156 OriginAttributes* aOriginAttributes,
2157 nsICookie** aCookie) {
2158 NS_ENSURE_ARG_POINTER(aOriginAttributes);
2159 NS_ENSURE_ARG_POINTER(aCookie);
2161 if (!IsInitialized()) {
2162 return NS_ERROR_NOT_AVAILABLE;
2165 nsAutoCString baseDomain;
2166 nsresult rv =
2167 CookieCommons::GetBaseDomainFromHost(mTLDService, aHost, baseDomain);
2168 NS_ENSURE_SUCCESS(rv, rv);
2170 CookieListIter iter{};
2171 CookieStorage* storage = PickStorage(*aOriginAttributes);
2172 bool foundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost,
2173 aName, aPath, iter);
2175 if (foundCookie) {
2176 RefPtr<Cookie> cookie = iter.Cookie();
2177 NS_ENSURE_TRUE(cookie, NS_ERROR_NULL_POINTER);
2179 cookie.forget(aCookie);
2182 return NS_OK;
2185 // count the number of cookies stored by a particular host. this is provided by
2186 // the nsICookieManager interface.
2187 NS_IMETHODIMP
2188 CookieService::CountCookiesFromHost(const nsACString& aHost,
2189 uint32_t* aCountFromHost) {
2190 // first, normalize the hostname, and fail if it contains illegal characters.
2191 nsAutoCString host(aHost);
2192 nsresult rv = NormalizeHost(host);
2193 NS_ENSURE_SUCCESS(rv, rv);
2195 nsAutoCString baseDomain;
2196 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2197 NS_ENSURE_SUCCESS(rv, rv);
2199 if (!IsInitialized()) {
2200 return NS_ERROR_NOT_AVAILABLE;
2203 mPersistentStorage->EnsureInitialized();
2205 *aCountFromHost = mPersistentStorage->CountCookiesFromHost(baseDomain, 0);
2207 return NS_OK;
2210 // get an enumerator of cookies stored by a particular host. this is provided by
2211 // the nsICookieManager interface.
2212 NS_IMETHODIMP
2213 CookieService::GetCookiesFromHost(const nsACString& aHost,
2214 JS::Handle<JS::Value> aOriginAttributes,
2215 JSContext* aCx,
2216 nsTArray<RefPtr<nsICookie>>& aResult) {
2217 // first, normalize the hostname, and fail if it contains illegal characters.
2218 nsAutoCString host(aHost);
2219 nsresult rv = NormalizeHost(host);
2220 NS_ENSURE_SUCCESS(rv, rv);
2222 nsAutoCString baseDomain;
2223 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2224 NS_ENSURE_SUCCESS(rv, rv);
2226 OriginAttributes attrs;
2227 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2228 return NS_ERROR_INVALID_ARG;
2231 if (!IsInitialized()) {
2232 return NS_ERROR_NOT_AVAILABLE;
2235 CookieStorage* storage = PickStorage(attrs);
2237 const nsTArray<RefPtr<Cookie>>* cookies =
2238 storage->GetCookiesFromHost(baseDomain, attrs);
2240 if (cookies) {
2241 aResult.SetCapacity(cookies->Length());
2242 for (Cookie* cookie : *cookies) {
2243 aResult.AppendElement(cookie);
2247 return NS_OK;
2250 NS_IMETHODIMP
2251 CookieService::GetCookiesWithOriginAttributes(
2252 const nsAString& aPattern, const nsACString& aHost,
2253 nsTArray<RefPtr<nsICookie>>& aResult) {
2254 OriginAttributesPattern pattern;
2255 if (!pattern.Init(aPattern)) {
2256 return NS_ERROR_INVALID_ARG;
2259 nsAutoCString host(aHost);
2260 nsresult rv = NormalizeHost(host);
2261 NS_ENSURE_SUCCESS(rv, rv);
2263 nsAutoCString baseDomain;
2264 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2265 NS_ENSURE_SUCCESS(rv, rv);
2267 return GetCookiesWithOriginAttributes(pattern, baseDomain, aResult);
2270 nsresult CookieService::GetCookiesWithOriginAttributes(
2271 const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain,
2272 nsTArray<RefPtr<nsICookie>>& aResult) {
2273 CookieStorage* storage = PickStorage(aPattern);
2274 storage->GetCookiesWithOriginAttributes(aPattern, aBaseDomain, aResult);
2276 return NS_OK;
2279 NS_IMETHODIMP
2280 CookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
2281 const nsACString& aHost) {
2282 MOZ_ASSERT(XRE_IsParentProcess());
2284 OriginAttributesPattern pattern;
2285 if (!pattern.Init(aPattern)) {
2286 return NS_ERROR_INVALID_ARG;
2289 nsAutoCString host(aHost);
2290 nsresult rv = NormalizeHost(host);
2291 NS_ENSURE_SUCCESS(rv, rv);
2293 nsAutoCString baseDomain;
2294 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2295 NS_ENSURE_SUCCESS(rv, rv);
2297 return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
2300 nsresult CookieService::RemoveCookiesWithOriginAttributes(
2301 const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain) {
2302 if (!IsInitialized()) {
2303 return NS_ERROR_NOT_AVAILABLE;
2306 CookieStorage* storage = PickStorage(aPattern);
2307 storage->RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
2309 return NS_OK;
2312 NS_IMETHODIMP
2313 CookieService::RemoveCookiesFromExactHost(const nsACString& aHost,
2314 const nsAString& aPattern) {
2315 MOZ_ASSERT(XRE_IsParentProcess());
2317 OriginAttributesPattern pattern;
2318 if (!pattern.Init(aPattern)) {
2319 return NS_ERROR_INVALID_ARG;
2322 return RemoveCookiesFromExactHost(aHost, pattern);
2325 nsresult CookieService::RemoveCookiesFromExactHost(
2326 const nsACString& aHost, const OriginAttributesPattern& aPattern) {
2327 nsAutoCString host(aHost);
2328 nsresult rv = NormalizeHost(host);
2329 NS_ENSURE_SUCCESS(rv, rv);
2331 nsAutoCString baseDomain;
2332 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2333 NS_ENSURE_SUCCESS(rv, rv);
2335 if (!IsInitialized()) {
2336 return NS_ERROR_NOT_AVAILABLE;
2339 CookieStorage* storage = PickStorage(aPattern);
2340 storage->RemoveCookiesFromExactHost(aHost, baseDomain, aPattern);
2342 return NS_OK;
2345 namespace {
2347 class RemoveAllSinceRunnable : public Runnable {
2348 public:
2349 using CookieArray = nsTArray<RefPtr<nsICookie>>;
2350 RemoveAllSinceRunnable(Promise* aPromise, CookieService* aSelf,
2351 CookieArray&& aCookieArray, int64_t aSinceWhen)
2352 : Runnable("RemoveAllSinceRunnable"),
2353 mPromise(aPromise),
2354 mSelf(aSelf),
2355 mList(std::move(aCookieArray)),
2356 mIndex(0),
2357 mSinceWhen(aSinceWhen) {}
2359 NS_IMETHODIMP Run() override {
2360 RemoveSome();
2362 if (mIndex < mList.Length()) {
2363 return NS_DispatchToCurrentThread(this);
2365 mPromise->MaybeResolveWithUndefined();
2367 return NS_OK;
2370 private:
2371 void RemoveSome() {
2372 for (CookieArray::size_type iter = 0;
2373 iter < kYieldPeriod && mIndex < mList.Length(); ++mIndex, ++iter) {
2374 auto* cookie = static_cast<Cookie*>(mList[mIndex].get());
2375 if (cookie->CreationTime() > mSinceWhen &&
2376 NS_FAILED(mSelf->Remove(cookie->Host(), cookie->OriginAttributesRef(),
2377 cookie->Name(), cookie->Path()))) {
2378 continue;
2383 private:
2384 RefPtr<Promise> mPromise;
2385 RefPtr<CookieService> mSelf;
2386 CookieArray mList;
2387 CookieArray::size_type mIndex;
2388 int64_t mSinceWhen;
2389 static const CookieArray::size_type kYieldPeriod = 10;
2392 } // namespace
2394 NS_IMETHODIMP
2395 CookieService::RemoveAllSince(int64_t aSinceWhen, JSContext* aCx,
2396 Promise** aRetVal) {
2397 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
2398 if (NS_WARN_IF(!globalObject)) {
2399 return NS_ERROR_UNEXPECTED;
2402 ErrorResult result;
2403 RefPtr<Promise> promise = Promise::Create(globalObject, result);
2404 if (NS_WARN_IF(result.Failed())) {
2405 return result.StealNSResult();
2408 mPersistentStorage->EnsureInitialized();
2410 nsTArray<RefPtr<nsICookie>> cookieList;
2412 // We delete only non-private cookies.
2413 mPersistentStorage->GetAll(cookieList);
2415 RefPtr<RemoveAllSinceRunnable> runMe = new RemoveAllSinceRunnable(
2416 promise, this, std::move(cookieList), aSinceWhen);
2418 promise.forget(aRetVal);
2420 return runMe->Run();
2423 namespace {
2425 class CompareCookiesCreationTime {
2426 public:
2427 static bool Equals(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2428 return static_cast<const Cookie*>(aCookie1)->CreationTime() ==
2429 static_cast<const Cookie*>(aCookie2)->CreationTime();
2432 static bool LessThan(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2433 return static_cast<const Cookie*>(aCookie1)->CreationTime() <
2434 static_cast<const Cookie*>(aCookie2)->CreationTime();
2438 } // namespace
2440 NS_IMETHODIMP
2441 CookieService::GetCookiesSince(int64_t aSinceWhen,
2442 nsTArray<RefPtr<nsICookie>>& aResult) {
2443 if (!IsInitialized()) {
2444 return NS_OK;
2447 mPersistentStorage->EnsureInitialized();
2449 // We expose only non-private cookies.
2450 nsTArray<RefPtr<nsICookie>> cookieList;
2451 mPersistentStorage->GetAll(cookieList);
2453 for (RefPtr<nsICookie>& cookie : cookieList) {
2454 if (static_cast<Cookie*>(cookie.get())->CreationTime() >= aSinceWhen) {
2455 aResult.AppendElement(cookie);
2459 aResult.Sort(CompareCookiesCreationTime());
2460 return NS_OK;
2463 size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2464 size_t n = aMallocSizeOf(this);
2466 if (mPersistentStorage) {
2467 n += mPersistentStorage->SizeOfIncludingThis(aMallocSizeOf);
2469 if (mPrivateStorage) {
2470 n += mPrivateStorage->SizeOfIncludingThis(aMallocSizeOf);
2473 return n;
2476 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
2478 NS_IMETHODIMP
2479 CookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
2480 nsISupports* aData, bool /*aAnonymize*/) {
2481 MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
2482 SizeOfIncludingThis(CookieServiceMallocSizeOf),
2483 "Memory used by the cookie service.");
2485 return NS_OK;
2488 bool CookieService::IsInitialized() const {
2489 if (!mPersistentStorage) {
2490 NS_WARNING("No CookieStorage! Profile already close?");
2491 return false;
2494 MOZ_ASSERT(mPrivateStorage);
2495 return true;
2498 CookieStorage* CookieService::PickStorage(const OriginAttributes& aAttrs) {
2499 MOZ_ASSERT(IsInitialized());
2501 if (aAttrs.mPrivateBrowsingId > 0) {
2502 return mPrivateStorage;
2505 mPersistentStorage->EnsureInitialized();
2506 return mPersistentStorage;
2509 CookieStorage* CookieService::PickStorage(
2510 const OriginAttributesPattern& aAttrs) {
2511 MOZ_ASSERT(IsInitialized());
2513 if (aAttrs.mPrivateBrowsingId.WasPassed() &&
2514 aAttrs.mPrivateBrowsingId.Value() > 0) {
2515 return mPrivateStorage;
2518 mPersistentStorage->EnsureInitialized();
2519 return mPersistentStorage;
2522 bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain,
2523 const OriginAttributes& aAttrs,
2524 nsIURI* aHostURI, bool aFromHttp,
2525 const nsTArray<CookieStruct>& aCookies,
2526 BrowsingContext* aBrowsingContext) {
2527 if (!IsInitialized()) {
2528 // If we are probably shutting down, we can ignore this cookie.
2529 return true;
2532 CookieStorage* storage = PickStorage(aAttrs);
2533 int64_t currentTimeInUsec = PR_Now();
2535 for (const CookieStruct& cookieData : aCookies) {
2536 if (!CookieCommons::CheckPathSize(cookieData)) {
2537 return false;
2540 // reject cookie if it's over the size limit, per RFC2109
2541 if (!CookieCommons::CheckNameAndValueSize(cookieData)) {
2542 return false;
2545 RecordUnicodeTelemetry(cookieData);
2547 if (!CookieCommons::CheckName(cookieData)) {
2548 return false;
2551 if (!CookieCommons::CheckValue(cookieData)) {
2552 return false;
2555 // create a new Cookie and copy attributes
2556 RefPtr<Cookie> cookie = Cookie::Create(cookieData, aAttrs);
2557 if (!cookie) {
2558 continue;
2561 cookie->SetLastAccessed(currentTimeInUsec);
2562 cookie->SetCreationTime(
2563 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
2565 storage->AddCookie(nullptr, aBaseDomain, aAttrs, cookie, currentTimeInUsec,
2566 aHostURI, ""_ns, aFromHttp, aBrowsingContext);
2569 return true;
2572 } // namespace net
2573 } // namespace mozilla