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