Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / netwerk / cookie / CookieService.cpp
blobd96fb5832988c0106932009f0f3d80d72a359038
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);
1984 #if 0
1985 } else {
1987 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
1988 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
1989 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
1990 * been disabled, unless we can evangelize these sites.
1992 // get path from aHostURI
1993 nsAutoCString pathFromURI;
1994 if (NS_FAILED(aHostURI->GetPathQueryRef(pathFromURI)) ||
1995 !StringBeginsWith(pathFromURI, aCookieData.path())) {
1996 return false;
1998 #endif
2001 if (!CookieCommons::CheckPathSize(aCookieData)) {
2002 AutoTArray<nsString, 2> params = {
2003 NS_ConvertUTF8toUTF16(aCookieData.name())};
2005 nsString size;
2006 size.AppendInt(kMaxBytesPerPath);
2007 params.AppendElement(size);
2009 CookieLogging::LogMessageToConsole(
2010 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
2011 "CookiePathOversize"_ns, params);
2012 return false;
2015 return !aCookieData.path().Contains('\t');
2018 // CheckPrefixes
2020 // Reject cookies whose name starts with the magic prefixes from
2021 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
2022 // if they do not meet the criteria required by the prefix.
2024 // Must not be called until after CheckDomain() and CheckPath() have
2025 // regularized and validated the CookieStruct values!
2026 bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
2027 bool aSecureRequest) {
2028 static const char kSecure[] = "__Secure-";
2029 static const char kHost[] = "__Host-";
2030 static const int kSecureLen = sizeof(kSecure) - 1;
2031 static const int kHostLen = sizeof(kHost) - 1;
2033 bool isSecure = strncmp(aCookieData.name().get(), kSecure, kSecureLen) == 0;
2034 bool isHost = strncmp(aCookieData.name().get(), kHost, kHostLen) == 0;
2036 if (!isSecure && !isHost) {
2037 // not one of the magic prefixes: carry on
2038 return true;
2041 if (!aSecureRequest || !aCookieData.isSecure()) {
2042 // the magic prefixes may only be used from a secure request and
2043 // the secure attribute must be set on the cookie
2044 return false;
2047 if (isHost) {
2048 // The host prefix requires that the path is "/" and that the cookie
2049 // had no domain attribute. CheckDomain() and CheckPath() MUST be run
2050 // first to make sure invalid attributes are rejected and to regularlize
2051 // them. In particular all explicit domain attributes result in a host
2052 // that starts with a dot, and if the host doesn't start with a dot it
2053 // correctly matches the true host.
2054 if (aCookieData.host()[0] == '.' ||
2055 !aCookieData.path().EqualsLiteral("/")) {
2056 return false;
2060 return true;
2063 bool CookieService::GetExpiry(CookieStruct& aCookieData,
2064 const nsACString& aExpires,
2065 const nsACString& aMaxage, int64_t aCurrentTime,
2066 bool aFromHttp) {
2067 // maxageCap is in seconds.
2068 // Disabled for HTTP cookies.
2069 int64_t maxageCap =
2070 aFromHttp ? 0 : StaticPrefs::privacy_documentCookies_maxage();
2072 /* Determine when the cookie should expire. This is done by taking the
2073 * difference between the server time and the time the server wants the cookie
2074 * to expire, and adding that difference to the client time. This localizes
2075 * the client time regardless of whether or not the TZ environment variable
2076 * was set on the client.
2078 * Note: We need to consider accounting for network lag here, per RFC.
2080 // check for max-age attribute first; this overrides expires attribute
2081 if (!aMaxage.IsEmpty()) {
2082 // obtain numeric value of maxageAttribute
2083 int64_t maxage;
2084 int32_t numInts = PR_sscanf(aMaxage.BeginReading(), "%lld", &maxage);
2086 // default to session cookie if the conversion failed
2087 if (numInts != 1) {
2088 return true;
2091 // if this addition overflows, expiryTime will be less than currentTime
2092 // and the cookie will be expired - that's okay.
2093 if (maxageCap) {
2094 aCookieData.expiry() = aCurrentTime + std::min(maxage, maxageCap);
2095 } else {
2096 aCookieData.expiry() = aCurrentTime + maxage;
2099 // check for expires attribute
2100 } else if (!aExpires.IsEmpty()) {
2101 PRTime expires;
2103 // parse expiry time
2104 if (PR_ParseTimeString(aExpires.BeginReading(), true, &expires) !=
2105 PR_SUCCESS) {
2106 return true;
2109 // If set-cookie used absolute time to set expiration, and it can't use
2110 // client time to set expiration.
2111 // Because if current time be set in the future, but the cookie expire
2112 // time be set less than current time and more than server time.
2113 // The cookie item have to be used to the expired cookie.
2114 if (maxageCap) {
2115 aCookieData.expiry() = std::min(expires / int64_t(PR_USEC_PER_SEC),
2116 aCurrentTime + maxageCap);
2117 } else {
2118 aCookieData.expiry() = expires / int64_t(PR_USEC_PER_SEC);
2121 // default to session cookie if no attributes found. Here we don't need to
2122 // enforce the maxage cap, because session cookies are short-lived by
2123 // definition.
2124 } else {
2125 return true;
2128 return false;
2131 /******************************************************************************
2132 * CookieService impl:
2133 * private cookielist management functions
2134 ******************************************************************************/
2136 // find whether a given cookie has been previously set. this is provided by the
2137 // nsICookieManager interface.
2138 NS_IMETHODIMP
2139 CookieService::CookieExists(const nsACString& aHost, const nsACString& aPath,
2140 const nsACString& aName,
2141 JS::Handle<JS::Value> aOriginAttributes,
2142 JSContext* aCx, bool* aFoundCookie) {
2143 NS_ENSURE_ARG_POINTER(aCx);
2144 NS_ENSURE_ARG_POINTER(aFoundCookie);
2146 OriginAttributes attrs;
2147 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2148 return NS_ERROR_INVALID_ARG;
2150 return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie);
2153 NS_IMETHODIMP_(nsresult)
2154 CookieService::CookieExistsNative(const nsACString& aHost,
2155 const nsACString& aPath,
2156 const nsACString& aName,
2157 OriginAttributes* aOriginAttributes,
2158 bool* aFoundCookie) {
2159 nsCOMPtr<nsICookie> cookie;
2160 nsresult rv = GetCookieNative(aHost, aPath, aName, aOriginAttributes,
2161 getter_AddRefs(cookie));
2162 NS_ENSURE_SUCCESS(rv, rv);
2164 *aFoundCookie = cookie != nullptr;
2166 return NS_OK;
2169 NS_IMETHODIMP_(nsresult)
2170 CookieService::GetCookieNative(const nsACString& aHost, const nsACString& aPath,
2171 const nsACString& aName,
2172 OriginAttributes* aOriginAttributes,
2173 nsICookie** aCookie) {
2174 NS_ENSURE_ARG_POINTER(aOriginAttributes);
2175 NS_ENSURE_ARG_POINTER(aCookie);
2177 if (!IsInitialized()) {
2178 return NS_ERROR_NOT_AVAILABLE;
2181 nsAutoCString baseDomain;
2182 nsresult rv =
2183 CookieCommons::GetBaseDomainFromHost(mTLDService, aHost, baseDomain);
2184 NS_ENSURE_SUCCESS(rv, rv);
2186 CookieListIter iter{};
2187 CookieStorage* storage = PickStorage(*aOriginAttributes);
2188 bool foundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost,
2189 aName, aPath, iter);
2191 if (foundCookie) {
2192 RefPtr<Cookie> cookie = iter.Cookie();
2193 NS_ENSURE_TRUE(cookie, NS_ERROR_NULL_POINTER);
2195 cookie.forget(aCookie);
2198 return NS_OK;
2201 // count the number of cookies stored by a particular host. this is provided by
2202 // the nsICookieManager interface.
2203 NS_IMETHODIMP
2204 CookieService::CountCookiesFromHost(const nsACString& aHost,
2205 uint32_t* aCountFromHost) {
2206 // first, normalize the hostname, and fail if it contains illegal characters.
2207 nsAutoCString host(aHost);
2208 nsresult rv = NormalizeHost(host);
2209 NS_ENSURE_SUCCESS(rv, rv);
2211 nsAutoCString baseDomain;
2212 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2213 NS_ENSURE_SUCCESS(rv, rv);
2215 if (!IsInitialized()) {
2216 return NS_ERROR_NOT_AVAILABLE;
2219 mPersistentStorage->EnsureInitialized();
2221 *aCountFromHost = mPersistentStorage->CountCookiesFromHost(baseDomain, 0);
2223 return NS_OK;
2226 // get an enumerator of cookies stored by a particular host. this is provided by
2227 // the nsICookieManager interface.
2228 NS_IMETHODIMP
2229 CookieService::GetCookiesFromHost(const nsACString& aHost,
2230 JS::Handle<JS::Value> aOriginAttributes,
2231 JSContext* aCx,
2232 nsTArray<RefPtr<nsICookie>>& aResult) {
2233 // first, normalize the hostname, and fail if it contains illegal characters.
2234 nsAutoCString host(aHost);
2235 nsresult rv = NormalizeHost(host);
2236 NS_ENSURE_SUCCESS(rv, rv);
2238 nsAutoCString baseDomain;
2239 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2240 NS_ENSURE_SUCCESS(rv, rv);
2242 OriginAttributes attrs;
2243 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2244 return NS_ERROR_INVALID_ARG;
2247 if (!IsInitialized()) {
2248 return NS_ERROR_NOT_AVAILABLE;
2251 CookieStorage* storage = PickStorage(attrs);
2253 const nsTArray<RefPtr<Cookie>>* cookies =
2254 storage->GetCookiesFromHost(baseDomain, attrs);
2256 if (cookies) {
2257 aResult.SetCapacity(cookies->Length());
2258 for (Cookie* cookie : *cookies) {
2259 aResult.AppendElement(cookie);
2263 return NS_OK;
2266 NS_IMETHODIMP
2267 CookieService::GetCookiesWithOriginAttributes(
2268 const nsAString& aPattern, const nsACString& aHost,
2269 nsTArray<RefPtr<nsICookie>>& aResult) {
2270 OriginAttributesPattern pattern;
2271 if (!pattern.Init(aPattern)) {
2272 return NS_ERROR_INVALID_ARG;
2275 nsAutoCString host(aHost);
2276 nsresult rv = NormalizeHost(host);
2277 NS_ENSURE_SUCCESS(rv, rv);
2279 nsAutoCString baseDomain;
2280 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2281 NS_ENSURE_SUCCESS(rv, rv);
2283 return GetCookiesWithOriginAttributes(pattern, baseDomain, aResult);
2286 nsresult CookieService::GetCookiesWithOriginAttributes(
2287 const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain,
2288 nsTArray<RefPtr<nsICookie>>& aResult) {
2289 CookieStorage* storage = PickStorage(aPattern);
2290 storage->GetCookiesWithOriginAttributes(aPattern, aBaseDomain, aResult);
2292 return NS_OK;
2295 NS_IMETHODIMP
2296 CookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
2297 const nsACString& aHost) {
2298 MOZ_ASSERT(XRE_IsParentProcess());
2300 OriginAttributesPattern pattern;
2301 if (!pattern.Init(aPattern)) {
2302 return NS_ERROR_INVALID_ARG;
2305 nsAutoCString host(aHost);
2306 nsresult rv = NormalizeHost(host);
2307 NS_ENSURE_SUCCESS(rv, rv);
2309 nsAutoCString baseDomain;
2310 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2311 NS_ENSURE_SUCCESS(rv, rv);
2313 return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
2316 nsresult CookieService::RemoveCookiesWithOriginAttributes(
2317 const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain) {
2318 if (!IsInitialized()) {
2319 return NS_ERROR_NOT_AVAILABLE;
2322 CookieStorage* storage = PickStorage(aPattern);
2323 storage->RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
2325 return NS_OK;
2328 NS_IMETHODIMP
2329 CookieService::RemoveCookiesFromExactHost(const nsACString& aHost,
2330 const nsAString& aPattern) {
2331 MOZ_ASSERT(XRE_IsParentProcess());
2333 OriginAttributesPattern pattern;
2334 if (!pattern.Init(aPattern)) {
2335 return NS_ERROR_INVALID_ARG;
2338 return RemoveCookiesFromExactHost(aHost, pattern);
2341 nsresult CookieService::RemoveCookiesFromExactHost(
2342 const nsACString& aHost, const OriginAttributesPattern& aPattern) {
2343 nsAutoCString host(aHost);
2344 nsresult rv = NormalizeHost(host);
2345 NS_ENSURE_SUCCESS(rv, rv);
2347 nsAutoCString baseDomain;
2348 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2349 NS_ENSURE_SUCCESS(rv, rv);
2351 if (!IsInitialized()) {
2352 return NS_ERROR_NOT_AVAILABLE;
2355 CookieStorage* storage = PickStorage(aPattern);
2356 storage->RemoveCookiesFromExactHost(aHost, baseDomain, aPattern);
2358 return NS_OK;
2361 namespace {
2363 class RemoveAllSinceRunnable : public Runnable {
2364 public:
2365 using CookieArray = nsTArray<RefPtr<nsICookie>>;
2366 RemoveAllSinceRunnable(Promise* aPromise, CookieService* aSelf,
2367 CookieArray&& aCookieArray, int64_t aSinceWhen)
2368 : Runnable("RemoveAllSinceRunnable"),
2369 mPromise(aPromise),
2370 mSelf(aSelf),
2371 mList(std::move(aCookieArray)),
2372 mIndex(0),
2373 mSinceWhen(aSinceWhen) {}
2375 NS_IMETHODIMP Run() override {
2376 RemoveSome();
2378 if (mIndex < mList.Length()) {
2379 return NS_DispatchToCurrentThread(this);
2381 mPromise->MaybeResolveWithUndefined();
2383 return NS_OK;
2386 private:
2387 void RemoveSome() {
2388 for (CookieArray::size_type iter = 0;
2389 iter < kYieldPeriod && mIndex < mList.Length(); ++mIndex, ++iter) {
2390 auto* cookie = static_cast<Cookie*>(mList[mIndex].get());
2391 if (cookie->CreationTime() > mSinceWhen &&
2392 NS_FAILED(mSelf->Remove(cookie->Host(), cookie->OriginAttributesRef(),
2393 cookie->Name(), cookie->Path()))) {
2394 continue;
2399 private:
2400 RefPtr<Promise> mPromise;
2401 RefPtr<CookieService> mSelf;
2402 CookieArray mList;
2403 CookieArray::size_type mIndex;
2404 int64_t mSinceWhen;
2405 static const CookieArray::size_type kYieldPeriod = 10;
2408 } // namespace
2410 NS_IMETHODIMP
2411 CookieService::RemoveAllSince(int64_t aSinceWhen, JSContext* aCx,
2412 Promise** aRetVal) {
2413 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
2414 if (NS_WARN_IF(!globalObject)) {
2415 return NS_ERROR_UNEXPECTED;
2418 ErrorResult result;
2419 RefPtr<Promise> promise = Promise::Create(globalObject, result);
2420 if (NS_WARN_IF(result.Failed())) {
2421 return result.StealNSResult();
2424 mPersistentStorage->EnsureInitialized();
2426 nsTArray<RefPtr<nsICookie>> cookieList;
2428 // We delete only non-private cookies.
2429 mPersistentStorage->GetAll(cookieList);
2431 RefPtr<RemoveAllSinceRunnable> runMe = new RemoveAllSinceRunnable(
2432 promise, this, std::move(cookieList), aSinceWhen);
2434 promise.forget(aRetVal);
2436 return runMe->Run();
2439 namespace {
2441 class CompareCookiesCreationTime {
2442 public:
2443 static bool Equals(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2444 return static_cast<const Cookie*>(aCookie1)->CreationTime() ==
2445 static_cast<const Cookie*>(aCookie2)->CreationTime();
2448 static bool LessThan(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2449 return static_cast<const Cookie*>(aCookie1)->CreationTime() <
2450 static_cast<const Cookie*>(aCookie2)->CreationTime();
2454 } // namespace
2456 NS_IMETHODIMP
2457 CookieService::GetCookiesSince(int64_t aSinceWhen,
2458 nsTArray<RefPtr<nsICookie>>& aResult) {
2459 if (!IsInitialized()) {
2460 return NS_OK;
2463 mPersistentStorage->EnsureInitialized();
2465 // We expose only non-private cookies.
2466 nsTArray<RefPtr<nsICookie>> cookieList;
2467 mPersistentStorage->GetAll(cookieList);
2469 for (RefPtr<nsICookie>& cookie : cookieList) {
2470 if (static_cast<Cookie*>(cookie.get())->CreationTime() >= aSinceWhen) {
2471 aResult.AppendElement(cookie);
2475 aResult.Sort(CompareCookiesCreationTime());
2476 return NS_OK;
2479 size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2480 size_t n = aMallocSizeOf(this);
2482 if (mPersistentStorage) {
2483 n += mPersistentStorage->SizeOfIncludingThis(aMallocSizeOf);
2485 if (mPrivateStorage) {
2486 n += mPrivateStorage->SizeOfIncludingThis(aMallocSizeOf);
2489 return n;
2492 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
2494 NS_IMETHODIMP
2495 CookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
2496 nsISupports* aData, bool /*aAnonymize*/) {
2497 MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
2498 SizeOfIncludingThis(CookieServiceMallocSizeOf),
2499 "Memory used by the cookie service.");
2501 return NS_OK;
2504 bool CookieService::IsInitialized() const {
2505 if (!mPersistentStorage) {
2506 NS_WARNING("No CookieStorage! Profile already close?");
2507 return false;
2510 MOZ_ASSERT(mPrivateStorage);
2511 return true;
2514 CookieStorage* CookieService::PickStorage(const OriginAttributes& aAttrs) {
2515 MOZ_ASSERT(IsInitialized());
2517 if (aAttrs.mPrivateBrowsingId > 0) {
2518 return mPrivateStorage;
2521 mPersistentStorage->EnsureInitialized();
2522 return mPersistentStorage;
2525 CookieStorage* CookieService::PickStorage(
2526 const OriginAttributesPattern& aAttrs) {
2527 MOZ_ASSERT(IsInitialized());
2529 if (aAttrs.mPrivateBrowsingId.WasPassed() &&
2530 aAttrs.mPrivateBrowsingId.Value() > 0) {
2531 return mPrivateStorage;
2534 mPersistentStorage->EnsureInitialized();
2535 return mPersistentStorage;
2538 bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain,
2539 const OriginAttributes& aAttrs,
2540 nsIURI* aHostURI, bool aFromHttp,
2541 const nsTArray<CookieStruct>& aCookies,
2542 BrowsingContext* aBrowsingContext) {
2543 if (!IsInitialized()) {
2544 // If we are probably shutting down, we can ignore this cookie.
2545 return true;
2548 CookieStorage* storage = PickStorage(aAttrs);
2549 int64_t currentTimeInUsec = PR_Now();
2551 for (const CookieStruct& cookieData : aCookies) {
2552 if (!CookieCommons::CheckPathSize(cookieData)) {
2553 return false;
2556 // reject cookie if it's over the size limit, per RFC2109
2557 if (!CookieCommons::CheckNameAndValueSize(cookieData)) {
2558 return false;
2561 RecordUnicodeTelemetry(cookieData);
2563 if (!CookieCommons::CheckName(cookieData)) {
2564 return false;
2567 if (!CookieCommons::CheckValue(cookieData)) {
2568 return false;
2571 // create a new Cookie and copy attributes
2572 RefPtr<Cookie> cookie = Cookie::Create(cookieData, aAttrs);
2573 if (!cookie) {
2574 continue;
2577 cookie->SetLastAccessed(currentTimeInUsec);
2578 cookie->SetCreationTime(
2579 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
2581 storage->AddCookie(nullptr, aBaseDomain, aAttrs, cookie, currentTimeInUsec,
2582 aHostURI, ""_ns, aFromHttp, aBrowsingContext);
2585 return true;
2588 } // namespace net
2589 } // namespace mozilla