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/. */
6 
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/nsMixedContentBlocker.h"
12 #include "mozilla/dom/Promise.h"
13 #include "mozilla/net/CookiePersistentStorage.h"
14 #include "mozilla/net/CookiePrivateStorage.h"
15 #include "mozilla/net/CookieService.h"
16 #include "mozilla/net/CookieServiceChild.h"
17 #include "mozilla/net/HttpBaseChannel.h"
18 #include "mozilla/net/NeckoCommon.h"
19 #include "mozilla/StaticPrefs_network.h"
20 #include "mozilla/StoragePrincipalHelper.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozIThirdPartyUtil.h"
23 #include "nsIConsoleReportCollector.h"
24 #include "nsIEffectiveTLDService.h"
25 #include "nsIIDNService.h"
26 #include "nsIScriptError.h"
27 #include "nsIURL.h"
28 #include "nsIURI.h"
29 #include "nsIWebProgressListener.h"
30 #include "nsNetUtil.h"
31 #include "prprf.h"
32 
33 using namespace mozilla::dom;
34 
35 namespace {
36 
MakeCookieBehavior(uint32_t aCookieBehavior)37 uint32_t MakeCookieBehavior(uint32_t aCookieBehavior) {
38   bool isFirstPartyIsolated = OriginAttributes::IsFirstPartyEnabled();
39 
40   if (isFirstPartyIsolated &&
41       aCookieBehavior ==
42           nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
43     return nsICookieService::BEHAVIOR_REJECT_TRACKER;
44   }
45   return aCookieBehavior;
46 }
47 
48 }  // anonymous namespace
49 
50 // static
GetCookieBehavior(bool aIsPrivate)51 uint32_t nsICookieManager::GetCookieBehavior(bool aIsPrivate) {
52   if (aIsPrivate) {
53     // To sync the cookieBehavior pref between regular and private mode in ETP
54     // custom mode, we will return the regular cookieBehavior pref for private
55     // mode when
56     //   1. The regular cookieBehavior pref has a non-default value.
57     //   2. And the private cookieBehavior pref has a default value.
58     // Also, this can cover the migration case where the user has a non-default
59     // cookieBehavior before the private cookieBehavior was introduced. The
60     // getter here will directly return the regular cookieBehavior, so that the
61     // cookieBehavior for private mode is consistent.
62     if (mozilla::Preferences::HasUserValue(
63             "network.cookie.cookieBehavior.pbmode")) {
64       return MakeCookieBehavior(
65           mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
66     }
67 
68     if (mozilla::Preferences::HasUserValue("network.cookie.cookieBehavior")) {
69       return MakeCookieBehavior(
70           mozilla::StaticPrefs::network_cookie_cookieBehavior());
71     }
72 
73     return MakeCookieBehavior(
74         mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode());
75   }
76   return MakeCookieBehavior(
77       mozilla::StaticPrefs::network_cookie_cookieBehavior());
78 }
79 
80 namespace mozilla {
81 namespace net {
82 
83 /******************************************************************************
84  * CookieService impl:
85  * useful types & constants
86  ******************************************************************************/
87 
88 static StaticRefPtr<CookieService> gCookieService;
89 
90 constexpr auto CONSOLE_SAMESITE_CATEGORY = "cookieSameSite"_ns;
91 constexpr auto CONSOLE_OVERSIZE_CATEGORY = "cookiesOversize"_ns;
92 constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
93 constexpr auto SAMESITE_MDN_URL =
94     "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/"
95     u"SameSite"_ns;
96 
97 namespace {
98 
ComposeCookieString(nsTArray<Cookie * > & aCookieList,nsACString & aCookieString)99 void ComposeCookieString(nsTArray<Cookie*>& aCookieList,
100                          nsACString& aCookieString) {
101   for (Cookie* cookie : aCookieList) {
102     // check if we have anything to write
103     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
104       // if we've already added a cookie to the return list, append a "; " so
105       // that subsequent cookies are delimited in the final list.
106       if (!aCookieString.IsEmpty()) {
107         aCookieString.AppendLiteral("; ");
108       }
109 
110       if (!cookie->Name().IsEmpty()) {
111         // we have a name and value - write both
112         aCookieString += cookie->Name() + "="_ns + cookie->Value();
113       } else {
114         // just write value
115         aCookieString += cookie->Value();
116       }
117     }
118   }
119 }
120 
121 // Return false if the cookie should be ignored for the current channel.
ProcessSameSiteCookieForForeignRequest(nsIChannel * aChannel,Cookie * aCookie,bool aIsSafeTopLevelNav,bool aLaxByDefault)122 bool ProcessSameSiteCookieForForeignRequest(nsIChannel* aChannel,
123                                             Cookie* aCookie,
124                                             bool aIsSafeTopLevelNav,
125                                             bool aLaxByDefault) {
126   int32_t sameSiteAttr = 0;
127   aCookie->GetSameSite(&sameSiteAttr);
128 
129   // it if's a cross origin request and the cookie is same site only (strict)
130   // don't send it
131   if (sameSiteAttr == nsICookie::SAMESITE_STRICT) {
132     return false;
133   }
134 
135   int64_t currentTimeInUsec = PR_Now();
136 
137   // 2 minutes of tolerance for 'SameSite=Lax by default' for cookies set
138   // without a SameSite value when used for unsafe http methods.
139   if (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 &&
140       aLaxByDefault && sameSiteAttr == nsICookie::SAMESITE_LAX &&
141       aCookie->RawSameSite() == nsICookie::SAMESITE_NONE &&
142       currentTimeInUsec - aCookie->CreationTime() <=
143           (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() *
144            PR_USEC_PER_SEC) &&
145       !NS_IsSafeMethodNav(aChannel)) {
146     return true;
147   }
148 
149   // if it's a cross origin request, the cookie is same site lax, but it's not a
150   // top-level navigation, don't send it
151   return sameSiteAttr != nsICookie::SAMESITE_LAX || aIsSafeTopLevelNav;
152 }
153 
154 }  // namespace
155 
156 /******************************************************************************
157  * CookieService impl:
158  * singleton instance ctor/dtor methods
159  ******************************************************************************/
160 
GetXPCOMSingleton()161 already_AddRefed<nsICookieService> CookieService::GetXPCOMSingleton() {
162   if (IsNeckoChild()) {
163     return CookieServiceChild::GetSingleton();
164   }
165 
166   return GetSingleton();
167 }
168 
GetSingleton()169 already_AddRefed<CookieService> CookieService::GetSingleton() {
170   NS_ASSERTION(!IsNeckoChild(), "not a parent process");
171 
172   if (gCookieService) {
173     return do_AddRef(gCookieService);
174   }
175 
176   // Create a new singleton CookieService.
177   // We AddRef only once since XPCOM has rules about the ordering of module
178   // teardowns - by the time our module destructor is called, it's too late to
179   // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
180   // cycles have already been completed and would result in serious leaks.
181   // See bug 209571.
182   gCookieService = new CookieService();
183   if (gCookieService) {
184     if (NS_SUCCEEDED(gCookieService->Init())) {
185       ClearOnShutdown(&gCookieService);
186     } else {
187       gCookieService = nullptr;
188     }
189   }
190 
191   return do_AddRef(gCookieService);
192 }
193 
194 /******************************************************************************
195  * CookieService impl:
196  * public methods
197  ******************************************************************************/
198 
199 NS_IMPL_ISUPPORTS(CookieService, nsICookieService, nsICookieManager,
200                   nsIObserver, nsISupportsWeakReference, nsIMemoryReporter)
201 
202 CookieService::CookieService() = default;
203 
Init()204 nsresult CookieService::Init() {
205   nsresult rv;
206   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
207   NS_ENSURE_SUCCESS(rv, rv);
208 
209   mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
210   NS_ENSURE_SUCCESS(rv, rv);
211 
212   mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
213   NS_ENSURE_SUCCESS(rv, rv);
214 
215   // Init our default, and possibly private CookieStorages.
216   InitCookieStorages();
217 
218   RegisterWeakMemoryReporter(this);
219 
220   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
221   NS_ENSURE_STATE(os);
222   os->AddObserver(this, "profile-before-change", true);
223   os->AddObserver(this, "profile-do-change", true);
224   os->AddObserver(this, "last-pb-context-exited", true);
225 
226   return NS_OK;
227 }
228 
InitCookieStorages()229 void CookieService::InitCookieStorages() {
230   NS_ASSERTION(!mPersistentStorage, "already have a default CookieStorage");
231   NS_ASSERTION(!mPrivateStorage, "already have a private CookieStorage");
232 
233   // Create two new CookieStorages.
234   mPersistentStorage = CookiePersistentStorage::Create();
235   mPrivateStorage = CookiePrivateStorage::Create();
236 
237   mPersistentStorage->Activate();
238 }
239 
CloseCookieStorages()240 void CookieService::CloseCookieStorages() {
241   // return if we already closed
242   if (!mPersistentStorage) {
243     return;
244   }
245 
246   // Let's nullify both storages before calling Close().
247   RefPtr<CookiePrivateStorage> privateStorage;
248   privateStorage.swap(mPrivateStorage);
249 
250   RefPtr<CookiePersistentStorage> persistentStorage;
251   persistentStorage.swap(mPersistentStorage);
252 
253   privateStorage->Close();
254   persistentStorage->Close();
255 }
256 
~CookieService()257 CookieService::~CookieService() {
258   CloseCookieStorages();
259 
260   UnregisterWeakMemoryReporter(this);
261 
262   gCookieService = nullptr;
263 }
264 
265 NS_IMETHODIMP
Observe(nsISupports *,const char * aTopic,const char16_t *)266 CookieService::Observe(nsISupports* /*aSubject*/, const char* aTopic,
267                        const char16_t* /*aData*/) {
268   // check the topic
269   if (!strcmp(aTopic, "profile-before-change")) {
270     // The profile is about to change,
271     // or is going away because the application is shutting down.
272 
273     // Close the default DB connection and null out our CookieStorages before
274     // changing.
275     CloseCookieStorages();
276 
277   } else if (!strcmp(aTopic, "profile-do-change")) {
278     NS_ASSERTION(!mPersistentStorage, "shouldn't have a default CookieStorage");
279     NS_ASSERTION(!mPrivateStorage, "shouldn't have a private CookieStorage");
280 
281     // the profile has already changed; init the db from the new location.
282     // if we are in the private browsing state, however, we do not want to read
283     // data into it - we should instead put it into the default state, so it's
284     // ready for us if and when we switch back to it.
285     InitCookieStorages();
286 
287   } else if (!strcmp(aTopic, "last-pb-context-exited")) {
288     // Flush all the cookies stored by private browsing contexts
289     OriginAttributesPattern pattern;
290     pattern.mPrivateBrowsingId.Construct(1);
291     RemoveCookiesWithOriginAttributes(pattern, ""_ns);
292     mPrivateStorage = CookiePrivateStorage::Create();
293   }
294 
295   return NS_OK;
296 }
297 
298 NS_IMETHODIMP
GetCookieBehavior(bool aIsPrivate,uint32_t * aCookieBehavior)299 CookieService::GetCookieBehavior(bool aIsPrivate, uint32_t* aCookieBehavior) {
300   NS_ENSURE_ARG_POINTER(aCookieBehavior);
301   *aCookieBehavior = nsICookieManager::GetCookieBehavior(aIsPrivate);
302   return NS_OK;
303 }
304 
305 NS_IMETHODIMP
GetCookieStringFromDocument(Document * aDocument,nsACString & aCookie)306 CookieService::GetCookieStringFromDocument(Document* aDocument,
307                                            nsACString& aCookie) {
308   NS_ENSURE_ARG(aDocument);
309 
310   nsresult rv;
311 
312   aCookie.Truncate();
313 
314   if (!IsInitialized()) {
315     return NS_OK;
316   }
317 
318   nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveStoragePrincipal();
319 
320   if (!CookieCommons::IsSchemeSupported(principal)) {
321     return NS_OK;
322   }
323 
324   nsICookie::schemeType schemeType =
325       CookieCommons::PrincipalToSchemeType(principal);
326 
327   CookieStorage* storage = PickStorage(principal->OriginAttributesRef());
328 
329   nsAutoCString baseDomain;
330   rv = CookieCommons::GetBaseDomain(principal, baseDomain);
331   if (NS_WARN_IF(NS_FAILED(rv))) {
332     return NS_OK;
333   }
334 
335   nsAutoCString hostFromURI;
336   rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
337   if (NS_WARN_IF(NS_FAILED(rv))) {
338     return NS_OK;
339   }
340 
341   nsAutoCString pathFromURI;
342   rv = principal->GetFilePath(pathFromURI);
343   if (NS_WARN_IF(NS_FAILED(rv))) {
344     return NS_OK;
345   }
346 
347   int64_t currentTimeInUsec = PR_Now();
348   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
349 
350   const nsTArray<RefPtr<Cookie>>* cookies =
351       storage->GetCookiesFromHost(baseDomain, principal->OriginAttributesRef());
352   if (!cookies) {
353     return NS_OK;
354   }
355 
356   // check if the nsIPrincipal is using an https secure protocol.
357   // if it isn't, then we can't send a secure cookie over the connection.
358   bool potentiallyTurstworthy = principal->GetIsOriginPotentiallyTrustworthy();
359 
360   bool thirdParty = true;
361   nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
362   // in gtests we don't have a window, let's consider those requests as 3rd
363   // party.
364   if (innerWindow) {
365     thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow,
366                                                              nullptr, nullptr);
367   }
368 
369   bool stale = false;
370   nsTArray<Cookie*> cookieList;
371 
372   // iterate the cookies!
373   for (Cookie* cookie : *cookies) {
374     // check the host, since the base domain lookup is conservative.
375     if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
376       continue;
377     }
378 
379     // if the cookie is httpOnly and it's not going directly to the HTTP
380     // connection, don't send it
381     if (cookie->IsHttpOnly()) {
382       continue;
383     }
384 
385     if (thirdParty &&
386         !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie)) {
387       continue;
388     }
389 
390     // if the cookie is secure and the host scheme isn't, we can't send it
391     if (cookie->IsSecure() && !potentiallyTurstworthy) {
392       continue;
393     }
394 
395     if (!CookieCommons::MaybeCompareScheme(cookie, schemeType)) {
396       continue;
397     }
398 
399     // if the nsIURI path doesn't match the cookie path, don't send it back
400     if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
401       continue;
402     }
403 
404     // check if the cookie has expired
405     if (cookie->Expiry() <= currentTime) {
406       continue;
407     }
408 
409     // all checks passed - add to list and check if lastAccessed stamp needs
410     // updating
411     cookieList.AppendElement(cookie);
412     if (cookie->IsStale()) {
413       stale = true;
414     }
415   }
416 
417   if (cookieList.IsEmpty()) {
418     return NS_OK;
419   }
420 
421   // update lastAccessed timestamps. we only do this if the timestamp is stale
422   // by a certain amount, to avoid thrashing the db during pageload.
423   if (stale) {
424     storage->StaleCookies(cookieList, currentTimeInUsec);
425   }
426 
427   // return cookies in order of path length; longest to shortest.
428   // this is required per RFC2109.  if cookies match in length,
429   // then sort by creation time (see bug 236772).
430   cookieList.Sort(CompareCookiesForSending());
431   ComposeCookieString(cookieList, aCookie);
432 
433   return NS_OK;
434 }
435 
436 NS_IMETHODIMP
GetCookieStringFromHttp(nsIURI * aHostURI,nsIChannel * aChannel,nsACString & aCookieString)437 CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel,
438                                        nsACString& aCookieString) {
439   NS_ENSURE_ARG(aHostURI);
440   NS_ENSURE_ARG(aChannel);
441 
442   aCookieString.Truncate();
443 
444   if (!CookieCommons::IsSchemeSupported(aHostURI)) {
445     return NS_OK;
446   }
447 
448   uint32_t rejectedReason = 0;
449   ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
450       aChannel, false, aHostURI, nullptr, &rejectedReason);
451 
452   OriginAttributes attrs;
453   StoragePrincipalHelper::GetOriginAttributes(
454       aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
455 
456   bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
457   bool isSameSiteForeign = CookieCommons::IsSameSiteForeign(aChannel, aHostURI);
458 
459   AutoTArray<Cookie*, 8> foundCookieList;
460   GetCookiesForURI(
461       aHostURI, aChannel, result.contains(ThirdPartyAnalysis::IsForeign),
462       result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
463       result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
464       result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
465       rejectedReason, isSafeTopLevelNav, isSameSiteForeign, true, attrs,
466       foundCookieList);
467 
468   ComposeCookieString(foundCookieList, aCookieString);
469 
470   if (!aCookieString.IsEmpty()) {
471     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
472   }
473   return NS_OK;
474 }
475 
476 NS_IMETHODIMP
SetCookieStringFromDocument(Document * aDocument,const nsACString & aCookieString)477 CookieService::SetCookieStringFromDocument(Document* aDocument,
478                                            const nsACString& aCookieString) {
479   NS_ENSURE_ARG(aDocument);
480 
481   if (!IsInitialized()) {
482     return NS_OK;
483   }
484 
485   nsCOMPtr<nsIURI> documentURI;
486   nsAutoCString baseDomain;
487   OriginAttributes attrs;
488 
489   int64_t currentTimeInUsec = PR_Now();
490 
491   // This function is executed in this context, I don't need to keep objects
492   // alive.
493   auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
494                                       const OriginAttributes& aAttrs) {
495     CookieStorage* storage = PickStorage(aAttrs);
496     return !!storage->CountCookiesFromHost(aBaseDomain,
497                                            aAttrs.mPrivateBrowsingId);
498   };
499 
500   RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
501       aDocument, aCookieString, currentTimeInUsec, mTLDService, mThirdPartyUtil,
502       hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
503   if (!cookie) {
504     return NS_OK;
505   }
506 
507   bool thirdParty = true;
508   nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
509   // in gtests we don't have a window, let's consider those requests as 3rd
510   // party.
511   if (innerWindow) {
512     thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow,
513                                                              nullptr, nullptr);
514   }
515 
516   if (thirdParty &&
517       !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie)) {
518     return NS_OK;
519   }
520 
521   nsCOMPtr<nsIConsoleReportCollector> crc =
522       do_QueryInterface(aDocument->GetChannel());
523 
524   // add the cookie to the list. AddCookie() takes care of logging.
525   PickStorage(attrs)->AddCookie(crc, baseDomain, attrs, cookie,
526                                 currentTimeInUsec, documentURI, aCookieString,
527                                 false);
528   return NS_OK;
529 }
530 
531 NS_IMETHODIMP
SetCookieStringFromHttp(nsIURI * aHostURI,const nsACString & aCookieHeader,nsIChannel * aChannel)532 CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
533                                        const nsACString& aCookieHeader,
534                                        nsIChannel* aChannel) {
535   NS_ENSURE_ARG(aHostURI);
536   NS_ENSURE_ARG(aChannel);
537 
538   if (!IsInitialized()) {
539     return NS_OK;
540   }
541 
542   if (!CookieCommons::IsSchemeSupported(aHostURI)) {
543     return NS_OK;
544   }
545 
546   uint32_t rejectedReason = 0;
547   ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
548       aChannel, false, aHostURI, nullptr, &rejectedReason);
549 
550   OriginAttributes attrs;
551   StoragePrincipalHelper::GetOriginAttributes(
552       aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal);
553 
554   // get the base domain for the host URI.
555   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
556   // file:// URI's (i.e. with an empty host) are allowed, but any other
557   // scheme must have a non-empty host. A trailing dot in the host
558   // is acceptable.
559   bool requireHostMatch;
560   nsAutoCString baseDomain;
561   nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
562                                              requireHostMatch);
563   if (NS_FAILED(rv)) {
564     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
565                       "couldn't get base domain from URI");
566     return NS_OK;
567   }
568 
569   nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
570       CookieCommons::GetCookieJarSettings(aChannel);
571 
572   nsAutoCString hostFromURI;
573   nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
574 
575   nsAutoCString baseDomainFromURI;
576   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, hostFromURI,
577                                             baseDomainFromURI);
578   NS_ENSURE_SUCCESS(rv, NS_OK);
579 
580   CookieStorage* storage = PickStorage(attrs);
581 
582   // check default prefs
583   uint32_t priorCookieCount = storage->CountCookiesFromHost(
584       baseDomainFromURI, attrs.mPrivateBrowsingId);
585 
586   nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
587 
588   CookieStatus cookieStatus = CheckPrefs(
589       crc, cookieJarSettings, aHostURI,
590       result.contains(ThirdPartyAnalysis::IsForeign),
591       result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
592       result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
593       result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
594       aCookieHeader, priorCookieCount, attrs, &rejectedReason);
595 
596   MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
597 
598   // fire a notification if third party or if cookie was rejected
599   // (but not if there was an error)
600   switch (cookieStatus) {
601     case STATUS_REJECTED:
602       CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
603                                     OPERATION_WRITE);
604       return NS_OK;  // Stop here
605     case STATUS_REJECTED_WITH_ERROR:
606       CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
607                                     OPERATION_WRITE);
608       return NS_OK;
609     case STATUS_ACCEPTED:  // Fallthrough
610     case STATUS_ACCEPT_SESSION:
611       NotifyAccepted(aChannel);
612       break;
613     default:
614       break;
615   }
616 
617   bool addonAllowsLoad = false;
618   nsCOMPtr<nsIURI> channelURI;
619   NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
620   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
621   addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
622                         ->AddonAllowsLoad(channelURI);
623 
624   bool isForeignAndNotAddon = false;
625   if (!addonAllowsLoad) {
626     mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
627                                          &isForeignAndNotAddon);
628   }
629 
630   nsCString cookieHeader(aCookieHeader);
631 
632   bool moreCookieToRead = true;
633 
634   // process each cookie in the header
635   while (moreCookieToRead) {
636     CookieStruct cookieData;
637     bool canSetCookie = false;
638 
639     moreCookieToRead = CanSetCookie(
640         aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
641         cookieHeader, true, isForeignAndNotAddon, crc, canSetCookie);
642 
643     if (!canSetCookie) {
644       continue;
645     }
646 
647     // check permissions from site permission list.
648     if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
649       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
650                         "cookie rejected by permission manager");
651       CookieCommons::NotifyRejected(
652           aHostURI, aChannel,
653           nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
654           OPERATION_WRITE);
655       CookieLogging::LogMessageToConsole(
656           crc, aHostURI, nsIScriptError::warningFlag,
657           CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
658           AutoTArray<nsString, 1>{
659               NS_ConvertUTF8toUTF16(cookieData.name()),
660           });
661       continue;
662     }
663 
664     // create a new Cookie
665     RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
666     MOZ_ASSERT(cookie);
667 
668     int64_t currentTimeInUsec = PR_Now();
669     cookie->SetLastAccessed(currentTimeInUsec);
670     cookie->SetCreationTime(
671         Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
672 
673     // add the cookie to the list. AddCookie() takes care of logging.
674     storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec,
675                        aHostURI, aCookieHeader, true);
676   }
677 
678   return NS_OK;
679 }
680 
NotifyAccepted(nsIChannel * aChannel)681 void CookieService::NotifyAccepted(nsIChannel* aChannel) {
682   ContentBlockingNotifier::OnDecision(
683       aChannel, ContentBlockingNotifier::BlockingDecision::eAllow, 0);
684 }
685 
686 /******************************************************************************
687  * CookieService:
688  * public transaction helper impl
689  ******************************************************************************/
690 
691 NS_IMETHODIMP
RunInTransaction(nsICookieTransactionCallback * aCallback)692 CookieService::RunInTransaction(nsICookieTransactionCallback* aCallback) {
693   NS_ENSURE_ARG(aCallback);
694 
695   if (!IsInitialized()) {
696     return NS_ERROR_NOT_AVAILABLE;
697   }
698 
699   mPersistentStorage->EnsureReadComplete();
700   return mPersistentStorage->RunInTransaction(aCallback);
701 }
702 
703 /******************************************************************************
704  * nsICookieManager impl:
705  * nsICookieManager
706  ******************************************************************************/
707 
708 NS_IMETHODIMP
RemoveAll()709 CookieService::RemoveAll() {
710   if (!IsInitialized()) {
711     return NS_ERROR_NOT_AVAILABLE;
712   }
713 
714   mPersistentStorage->EnsureReadComplete();
715   mPersistentStorage->RemoveAll();
716   return NS_OK;
717 }
718 
719 NS_IMETHODIMP
GetCookies(nsTArray<RefPtr<nsICookie>> & aCookies)720 CookieService::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
721   if (!IsInitialized()) {
722     return NS_ERROR_NOT_AVAILABLE;
723   }
724 
725   mPersistentStorage->EnsureReadComplete();
726 
727   // We expose only non-private cookies.
728   mPersistentStorage->GetCookies(aCookies);
729 
730   return NS_OK;
731 }
732 
733 NS_IMETHODIMP
GetSessionCookies(nsTArray<RefPtr<nsICookie>> & aCookies)734 CookieService::GetSessionCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
735   if (!IsInitialized()) {
736     return NS_ERROR_NOT_AVAILABLE;
737   }
738 
739   mPersistentStorage->EnsureReadComplete();
740 
741   // We expose only non-private cookies.
742   mPersistentStorage->GetCookies(aCookies);
743 
744   return NS_OK;
745 }
746 
747 NS_IMETHODIMP
Add(const nsACString & aHost,const nsACString & aPath,const nsACString & aName,const nsACString & aValue,bool aIsSecure,bool aIsHttpOnly,bool aIsSession,int64_t aExpiry,JS::HandleValue aOriginAttributes,int32_t aSameSite,nsICookie::schemeType aSchemeMap,JSContext * aCx)748 CookieService::Add(const nsACString& aHost, const nsACString& aPath,
749                    const nsACString& aName, const nsACString& aValue,
750                    bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
751                    int64_t aExpiry, JS::HandleValue aOriginAttributes,
752                    int32_t aSameSite, nsICookie::schemeType aSchemeMap,
753                    JSContext* aCx) {
754   OriginAttributes attrs;
755 
756   if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
757     return NS_ERROR_INVALID_ARG;
758   }
759 
760   return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
761                    aIsSession, aExpiry, &attrs, aSameSite, aSchemeMap);
762 }
763 
NS_IMETHODIMP_(nsresult)764 NS_IMETHODIMP_(nsresult)
765 CookieService::AddNative(const nsACString& aHost, const nsACString& aPath,
766                          const nsACString& aName, const nsACString& aValue,
767                          bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
768                          int64_t aExpiry, OriginAttributes* aOriginAttributes,
769                          int32_t aSameSite, nsICookie::schemeType aSchemeMap) {
770   if (NS_WARN_IF(!aOriginAttributes)) {
771     return NS_ERROR_FAILURE;
772   }
773 
774   if (!IsInitialized()) {
775     return NS_ERROR_NOT_AVAILABLE;
776   }
777 
778   // first, normalize the hostname, and fail if it contains illegal characters.
779   nsAutoCString host(aHost);
780   nsresult rv = NormalizeHost(host);
781   NS_ENSURE_SUCCESS(rv, rv);
782 
783   // get the base domain for the host URI.
784   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
785   nsAutoCString baseDomain;
786   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
787   NS_ENSURE_SUCCESS(rv, rv);
788 
789   int64_t currentTimeInUsec = PR_Now();
790   CookieKey key = CookieKey(baseDomain, *aOriginAttributes);
791 
792   CookieStruct cookieData(nsCString(aName), nsCString(aValue), nsCString(aHost),
793                           nsCString(aPath), aExpiry, currentTimeInUsec,
794                           Cookie::GenerateUniqueCreationTime(currentTimeInUsec),
795                           aIsHttpOnly, aIsSession, aIsSecure, aSameSite,
796                           aSameSite, aSchemeMap);
797 
798   RefPtr<Cookie> cookie = Cookie::Create(cookieData, key.mOriginAttributes);
799   MOZ_ASSERT(cookie);
800 
801   CookieStorage* storage = PickStorage(*aOriginAttributes);
802   storage->AddCookie(nullptr, baseDomain, *aOriginAttributes, cookie,
803                      currentTimeInUsec, nullptr, VoidCString(), true);
804   return NS_OK;
805 }
806 
Remove(const nsACString & aHost,const OriginAttributes & aAttrs,const nsACString & aName,const nsACString & aPath)807 nsresult CookieService::Remove(const nsACString& aHost,
808                                const OriginAttributes& aAttrs,
809                                const nsACString& aName,
810                                const nsACString& aPath) {
811   // first, normalize the hostname, and fail if it contains illegal characters.
812   nsAutoCString host(aHost);
813   nsresult rv = NormalizeHost(host);
814   NS_ENSURE_SUCCESS(rv, rv);
815 
816   nsAutoCString baseDomain;
817   if (!host.IsEmpty()) {
818     rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
819     NS_ENSURE_SUCCESS(rv, rv);
820   }
821 
822   if (!IsInitialized()) {
823     return NS_ERROR_NOT_AVAILABLE;
824   }
825 
826   CookieStorage* storage = PickStorage(aAttrs);
827   storage->RemoveCookie(baseDomain, aAttrs, host, PromiseFlatCString(aName),
828                         PromiseFlatCString(aPath));
829 
830   return NS_OK;
831 }
832 
833 NS_IMETHODIMP
Remove(const nsACString & aHost,const nsACString & aName,const nsACString & aPath,JS::HandleValue aOriginAttributes,JSContext * aCx)834 CookieService::Remove(const nsACString& aHost, const nsACString& aName,
835                       const nsACString& aPath,
836                       JS::HandleValue aOriginAttributes, JSContext* aCx) {
837   OriginAttributes attrs;
838 
839   if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
840     return NS_ERROR_INVALID_ARG;
841   }
842 
843   return RemoveNative(aHost, aName, aPath, &attrs);
844 }
845 
NS_IMETHODIMP_(nsresult)846 NS_IMETHODIMP_(nsresult)
847 CookieService::RemoveNative(const nsACString& aHost, const nsACString& aName,
848                             const nsACString& aPath,
849                             OriginAttributes* aOriginAttributes) {
850   if (NS_WARN_IF(!aOriginAttributes)) {
851     return NS_ERROR_FAILURE;
852   }
853 
854   nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath);
855   if (NS_WARN_IF(NS_FAILED(rv))) {
856     return rv;
857   }
858 
859   return NS_OK;
860 }
861 
GetCookiesForURI(nsIURI * aHostURI,nsIChannel * aChannel,bool aIsForeign,bool aIsThirdPartyTrackingResource,bool aIsThirdPartySocialTrackingResource,bool aStorageAccessPermissionGranted,uint32_t aRejectedReason,bool aIsSafeTopLevelNav,bool aIsSameSiteForeign,bool aHttpBound,const OriginAttributes & aOriginAttrs,nsTArray<Cookie * > & aCookieList)862 void CookieService::GetCookiesForURI(
863     nsIURI* aHostURI, nsIChannel* aChannel, bool aIsForeign,
864     bool aIsThirdPartyTrackingResource,
865     bool aIsThirdPartySocialTrackingResource,
866     bool aStorageAccessPermissionGranted, uint32_t aRejectedReason,
867     bool aIsSafeTopLevelNav, bool aIsSameSiteForeign, bool aHttpBound,
868     const OriginAttributes& aOriginAttrs, nsTArray<Cookie*>& aCookieList) {
869   NS_ASSERTION(aHostURI, "null host!");
870 
871   if (!CookieCommons::IsSchemeSupported(aHostURI)) {
872     return;
873   }
874 
875   if (!IsInitialized()) {
876     return;
877   }
878 
879   CookieStorage* storage = PickStorage(aOriginAttrs);
880 
881   // get the base domain, host, and path from the URI.
882   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
883   // file:// URI's (i.e. with an empty host) are allowed, but any other
884   // scheme must have a non-empty host. A trailing dot in the host
885   // is acceptable.
886   bool requireHostMatch;
887   nsAutoCString baseDomain;
888   nsAutoCString hostFromURI;
889   nsAutoCString pathFromURI;
890   nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
891                                              requireHostMatch);
892   if (NS_SUCCEEDED(rv)) {
893     rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
894   }
895   if (NS_SUCCEEDED(rv)) {
896     rv = aHostURI->GetFilePath(pathFromURI);
897   }
898   if (NS_FAILED(rv)) {
899     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(),
900                       "invalid host/path from URI");
901     return;
902   }
903 
904   nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
905       CookieCommons::GetCookieJarSettings(aChannel);
906 
907   nsAutoCString normalizedHostFromURI(hostFromURI);
908   rv = NormalizeHost(normalizedHostFromURI);
909   NS_ENSURE_SUCCESS_VOID(rv);
910 
911   nsAutoCString baseDomainFromURI;
912   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, normalizedHostFromURI,
913                                             baseDomainFromURI);
914   NS_ENSURE_SUCCESS_VOID(rv);
915 
916   nsICookie::schemeType schemeType = CookieCommons::URIToSchemeType(aHostURI);
917 
918   // check default prefs
919   uint32_t rejectedReason = aRejectedReason;
920   uint32_t priorCookieCount = storage->CountCookiesFromHost(
921       baseDomainFromURI, aOriginAttrs.mPrivateBrowsingId);
922 
923   nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
924   CookieStatus cookieStatus = CheckPrefs(
925       crc, cookieJarSettings, aHostURI, aIsForeign,
926       aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource,
927       aStorageAccessPermissionGranted, VoidCString(), priorCookieCount,
928       aOriginAttrs, &rejectedReason);
929 
930   MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED);
931 
932   // for GetCookie(), we only fire acceptance/rejection notifications
933   // (but not if there was an error)
934   switch (cookieStatus) {
935     case STATUS_REJECTED:
936       // If we don't have any cookies from this host, fail silently.
937       if (priorCookieCount) {
938         CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason,
939                                       OPERATION_READ);
940       }
941       return;
942     default:
943       break;
944   }
945 
946   // Note: The following permissions logic is mirrored in
947   // extensions::MatchPattern::MatchesCookie.
948   // If it changes, please update that function, or file a bug for someone
949   // else to do so.
950 
951   // check if aHostURI is using an https secure protocol.
952   // if it isn't, then we can't send a secure cookie over the connection.
953   // if SchemeIs fails, assume an insecure connection, to be on the safe side
954   bool potentiallyTurstworthy =
955       nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
956 
957   int64_t currentTimeInUsec = PR_Now();
958   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
959   bool stale = false;
960 
961   const nsTArray<RefPtr<Cookie>>* cookies =
962       storage->GetCookiesFromHost(baseDomain, aOriginAttrs);
963   if (!cookies) {
964     return;
965   }
966 
967   bool laxByDefault =
968       StaticPrefs::network_cookie_sameSite_laxByDefault() &&
969       !nsContentUtils::IsURIInPrefList(
970           aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
971 
972   // iterate the cookies!
973   for (Cookie* cookie : *cookies) {
974     // check the host, since the base domain lookup is conservative.
975     if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
976       continue;
977     }
978 
979     // if the cookie is secure and the host scheme isn't, we can't send it
980     if (cookie->IsSecure() && !potentiallyTurstworthy) {
981       continue;
982     }
983 
984     // The scheme doesn't match.
985     if (!CookieCommons::MaybeCompareSchemeWithLogging(crc, aHostURI, cookie,
986                                                       schemeType)) {
987       continue;
988     }
989 
990     if (aHttpBound && aIsSameSiteForeign &&
991         !ProcessSameSiteCookieForForeignRequest(
992             aChannel, cookie, aIsSafeTopLevelNav, laxByDefault)) {
993       continue;
994     }
995 
996     // if the cookie is httpOnly and it's not going directly to the HTTP
997     // connection, don't send it
998     if (cookie->IsHttpOnly() && !aHttpBound) {
999       continue;
1000     }
1001 
1002     // if the nsIURI path doesn't match the cookie path, don't send it back
1003     if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
1004       continue;
1005     }
1006 
1007     // check if the cookie has expired
1008     if (cookie->Expiry() <= currentTime) {
1009       continue;
1010     }
1011 
1012     // all checks passed - add to list and check if lastAccessed stamp needs
1013     // updating
1014     aCookieList.AppendElement(cookie);
1015     if (cookie->IsStale()) {
1016       stale = true;
1017     }
1018   }
1019 
1020   if (aCookieList.IsEmpty()) {
1021     return;
1022   }
1023 
1024   // Send a notification about the acceptance of the cookies now that we found
1025   // some.
1026   NotifyAccepted(aChannel);
1027 
1028   // update lastAccessed timestamps. we only do this if the timestamp is stale
1029   // by a certain amount, to avoid thrashing the db during pageload.
1030   if (stale) {
1031     storage->StaleCookies(aCookieList, currentTimeInUsec);
1032   }
1033 
1034   // return cookies in order of path length; longest to shortest.
1035   // this is required per RFC2109.  if cookies match in length,
1036   // then sort by creation time (see bug 236772).
1037   aCookieList.Sort(CompareCookiesForSending());
1038 }
1039 
1040 // processes a single cookie, and returns true if there are more cookies
1041 // to be processed
CanSetCookie(nsIURI * aHostURI,const nsACString & aBaseDomain,CookieStruct & aCookieData,bool aRequireHostMatch,CookieStatus aStatus,nsCString & aCookieHeader,bool aFromHttp,bool aIsForeignAndNotAddon,nsIConsoleReportCollector * aCRC,bool & aSetCookie)1042 bool CookieService::CanSetCookie(
1043     nsIURI* aHostURI, const nsACString& aBaseDomain, CookieStruct& aCookieData,
1044     bool aRequireHostMatch, CookieStatus aStatus, nsCString& aCookieHeader,
1045     bool aFromHttp, bool aIsForeignAndNotAddon, nsIConsoleReportCollector* aCRC,
1046     bool& aSetCookie) {
1047   MOZ_ASSERT(aHostURI);
1048 
1049   aSetCookie = false;
1050 
1051   // init expiryTime such that session cookies won't prematurely expire
1052   aCookieData.expiry() = INT64_MAX;
1053 
1054   aCookieData.schemeMap() = CookieCommons::URIToSchemeType(aHostURI);
1055 
1056   // aCookieHeader is an in/out param to point to the next cookie, if
1057   // there is one. Save the present value for logging purposes
1058   nsCString savedCookieHeader(aCookieHeader);
1059 
1060   // newCookie says whether there are multiple cookies in the header;
1061   // so we can handle them separately.
1062   nsAutoCString expires;
1063   nsAutoCString maxage;
1064   bool acceptedByParser = false;
1065   bool newCookie = ParseAttributes(aCRC, aHostURI, aCookieHeader, aCookieData,
1066                                    expires, maxage, acceptedByParser);
1067   if (!acceptedByParser) {
1068     return newCookie;
1069   }
1070 
1071   // Collect telemetry on how often secure cookies are set from non-secure
1072   // origins, and vice-versa.
1073   //
1074   // 0 = nonsecure and "http:"
1075   // 1 = nonsecure and "https:"
1076   // 2 = secure and "http:"
1077   // 3 = secure and "https:"
1078   bool potentiallyTurstworthy =
1079       nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
1080 
1081   int64_t currentTimeInUsec = PR_Now();
1082 
1083   // calculate expiry time of cookie.
1084   aCookieData.isSession() =
1085       GetExpiry(aCookieData, expires, maxage,
1086                 currentTimeInUsec / PR_USEC_PER_SEC, aFromHttp);
1087   if (aStatus == STATUS_ACCEPT_SESSION) {
1088     // force lifetime to session. note that the expiration time, if set above,
1089     // will still apply.
1090     aCookieData.isSession() = true;
1091   }
1092 
1093   // reject cookie if it's over the size limit, per RFC2109
1094   if (!CookieCommons::CheckNameAndValueSize(aCookieData)) {
1095     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1096                       "cookie too big (> 4kb)");
1097 
1098     AutoTArray<nsString, 2> params = {
1099         NS_ConvertUTF8toUTF16(aCookieData.name())};
1100 
1101     nsString size;
1102     size.AppendInt(kMaxBytesPerCookie);
1103     params.AppendElement(size);
1104 
1105     CookieLogging::LogMessageToConsole(
1106         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
1107         "CookieOversize"_ns, params);
1108     return newCookie;
1109   }
1110 
1111   if (!CookieCommons::CheckName(aCookieData)) {
1112     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1113                       "invalid name character");
1114     CookieLogging::LogMessageToConsole(
1115         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1116         "CookieRejectedInvalidCharName"_ns,
1117         AutoTArray<nsString, 1>{
1118             NS_ConvertUTF8toUTF16(aCookieData.name()),
1119         });
1120     return newCookie;
1121   }
1122 
1123   // domain & path checks
1124   if (!CheckDomain(aCookieData, aHostURI, aBaseDomain, aRequireHostMatch)) {
1125     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1126                       "failed the domain tests");
1127     CookieLogging::LogMessageToConsole(
1128         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1129         "CookieRejectedInvalidDomain"_ns,
1130         AutoTArray<nsString, 1>{
1131             NS_ConvertUTF8toUTF16(aCookieData.name()),
1132         });
1133     return newCookie;
1134   }
1135 
1136   if (!CheckPath(aCookieData, aCRC, aHostURI)) {
1137     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1138                       "failed the path tests");
1139     return newCookie;
1140   }
1141 
1142   // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
1143   if (!CheckPrefixes(aCookieData, potentiallyTurstworthy)) {
1144     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1145                       "failed the prefix tests");
1146     CookieLogging::LogMessageToConsole(
1147         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1148         "CookieRejectedInvalidPrefix"_ns,
1149         AutoTArray<nsString, 1>{
1150             NS_ConvertUTF8toUTF16(aCookieData.name()),
1151         });
1152     return newCookie;
1153   }
1154 
1155   if (aFromHttp && !CookieCommons::CheckHttpValue(aCookieData)) {
1156     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1157                       "invalid value character");
1158     CookieLogging::LogMessageToConsole(
1159         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1160         "CookieRejectedInvalidCharValue"_ns,
1161         AutoTArray<nsString, 1>{
1162             NS_ConvertUTF8toUTF16(aCookieData.name()),
1163         });
1164     return newCookie;
1165   }
1166 
1167   // if the new cookie is httponly, make sure we're not coming from script
1168   if (!aFromHttp && aCookieData.isHttpOnly()) {
1169     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1170                       "cookie is httponly; coming from script");
1171     CookieLogging::LogMessageToConsole(
1172         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1173         "CookieRejectedHttpOnlyButFromScript"_ns,
1174         AutoTArray<nsString, 1>{
1175             NS_ConvertUTF8toUTF16(aCookieData.name()),
1176         });
1177     return newCookie;
1178   }
1179 
1180   // If the new cookie is non-https and wants to set secure flag,
1181   // browser have to ignore this new cookie.
1182   // (draft-ietf-httpbis-cookie-alone section 3.1)
1183   if (aCookieData.isSecure() && !potentiallyTurstworthy) {
1184     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
1185                       "non-https cookie can't set secure flag");
1186     CookieLogging::LogMessageToConsole(
1187         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
1188         "CookieRejectedSecureButNonHttps"_ns,
1189         AutoTArray<nsString, 1>{
1190             NS_ConvertUTF8toUTF16(aCookieData.name()),
1191         });
1192     return newCookie;
1193   }
1194 
1195   // If the new cookie is same-site but in a cross site context,
1196   // browser must ignore the cookie.
1197   if ((aCookieData.sameSite() != nsICookie::SAMESITE_NONE) &&
1198       aIsForeignAndNotAddon) {
1199     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
1200                       "failed the samesite tests");
1201 
1202     CookieLogging::LogMessageToConsole(
1203         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
1204         "CookieRejectedForNonSameSiteness"_ns,
1205         AutoTArray<nsString, 1>{
1206             NS_ConvertUTF8toUTF16(aCookieData.name()),
1207         });
1208     return newCookie;
1209   }
1210 
1211   aSetCookie = true;
1212   return newCookie;
1213 }
1214 
1215 /******************************************************************************
1216  * CookieService impl:
1217  * private cookie header parsing functions
1218  ******************************************************************************/
1219 
1220 // clang-format off
1221 // The following comment block elucidates the function of ParseAttributes.
1222 /******************************************************************************
1223  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1224  ** please note: this BNF deviates from both specifications, and reflects this
1225  ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1226 
1227  ** Differences from RFC2109/2616 and explanations:
1228     1. implied *LWS
1229          The grammar described by this specification is word-based. Except
1230          where noted otherwise, linear white space (<LWS>) can be included
1231          between any two adjacent words (token or quoted-string), and
1232          between adjacent words and separators, without changing the
1233          interpretation of a field.
1234        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1235 
1236     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1237        common use inside values.
1238 
1239     3. tokens and values have looser restrictions on allowed characters than
1240        spec. This is also due to certain characters being in common use inside
1241        values. We allow only '=' to separate token/value pairs, and ';' to
1242        terminate tokens or values. <LWS> is allowed within tokens and values
1243        (see bug 206022).
1244 
1245     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1246        reject control chars or non-ASCII chars. This is erring on the loose
1247        side, since there's probably no good reason to enforce this strictness.
1248 
1249     5. Attribute "HttpOnly", not covered in the RFCs, is supported
1250        (see bug 178993).
1251 
1252  ** Begin BNF:
1253     token         = 1*<any allowed-chars except separators>
1254     value         = 1*<any allowed-chars except value-sep>
1255     separators    = ";" | "="
1256     value-sep     = ";"
1257     cookie-sep    = CR | LF
1258     allowed-chars = <any OCTET except NUL or cookie-sep>
1259     OCTET         = <any 8-bit sequence of data>
1260     LWS           = SP | HT
1261     NUL           = <US-ASCII NUL, null control character (0)>
1262     CR            = <US-ASCII CR, carriage return (13)>
1263     LF            = <US-ASCII LF, linefeed (10)>
1264     SP            = <US-ASCII SP, space (32)>
1265     HT            = <US-ASCII HT, horizontal-tab (9)>
1266 
1267     set-cookie    = "Set-Cookie:" cookies
1268     cookies       = cookie *( cookie-sep cookie )
1269     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
1270     NAME          = token                                ; cookie name
1271     VALUE         = value                                ; cookie value
1272     cookie-av     = token ["=" value]
1273 
1274     valid values for cookie-av (checked post-parsing) are:
1275     cookie-av     = "Path"    "=" value
1276                   | "Domain"  "=" value
1277                   | "Expires" "=" value
1278                   | "Max-Age" "=" value
1279                   | "Comment" "=" value
1280                   | "Version" "=" value
1281                   | "Secure"
1282                   | "HttpOnly"
1283 
1284 ******************************************************************************/
1285 // clang-format on
1286 
1287 // helper functions for GetTokenValue
isnull(char c)1288 static inline bool isnull(char c) { return c == 0; }
iswhitespace(char c)1289 static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
isterminator(char c)1290 static inline bool isterminator(char c) { return c == '\n' || c == '\r'; }
isvalueseparator(char c)1291 static inline bool isvalueseparator(char c) {
1292   return isterminator(c) || c == ';';
1293 }
istokenseparator(char c)1294 static inline bool istokenseparator(char c) {
1295   return isvalueseparator(c) || c == '=';
1296 }
1297 
1298 // Parse a single token/value pair.
1299 // Returns true if a cookie terminator is found, so caller can parse new cookie.
GetTokenValue(nsACString::const_char_iterator & aIter,nsACString::const_char_iterator & aEndIter,nsDependentCSubstring & aTokenString,nsDependentCSubstring & aTokenValue,bool & aEqualsFound)1300 bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
1301                                   nsACString::const_char_iterator& aEndIter,
1302                                   nsDependentCSubstring& aTokenString,
1303                                   nsDependentCSubstring& aTokenValue,
1304                                   bool& aEqualsFound) {
1305   nsACString::const_char_iterator start;
1306   nsACString::const_char_iterator lastSpace;
1307   // initialize value string to clear garbage
1308   aTokenValue.Rebind(aIter, aIter);
1309 
1310   // find <token>, including any <LWS> between the end-of-token and the
1311   // token separator. we'll remove trailing <LWS> next
1312   while (aIter != aEndIter && iswhitespace(*aIter)) {
1313     ++aIter;
1314   }
1315   start = aIter;
1316   while (aIter != aEndIter && !isnull(*aIter) && !istokenseparator(*aIter)) {
1317     ++aIter;
1318   }
1319 
1320   // remove trailing <LWS>; first check we're not at the beginning
1321   lastSpace = aIter;
1322   if (lastSpace != start) {
1323     while (--lastSpace != start && iswhitespace(*lastSpace)) {
1324     }
1325     ++lastSpace;
1326   }
1327   aTokenString.Rebind(start, lastSpace);
1328 
1329   aEqualsFound = (*aIter == '=');
1330   if (aEqualsFound) {
1331     // find <value>
1332     while (++aIter != aEndIter && iswhitespace(*aIter)) {
1333     }
1334 
1335     start = aIter;
1336 
1337     // process <token>
1338     // just look for ';' to terminate ('=' allowed)
1339     while (aIter != aEndIter && !isnull(*aIter) && !isvalueseparator(*aIter)) {
1340       ++aIter;
1341     }
1342 
1343     // remove trailing <LWS>; first check we're not at the beginning
1344     if (aIter != start) {
1345       lastSpace = aIter;
1346       while (--lastSpace != start && iswhitespace(*lastSpace)) {
1347       }
1348 
1349       aTokenValue.Rebind(start, ++lastSpace);
1350     }
1351   }
1352 
1353   // aIter is on ';', or terminator, or EOS
1354   if (aIter != aEndIter) {
1355     // if on terminator, increment past & return true to process new cookie
1356     if (isterminator(*aIter)) {
1357       ++aIter;
1358       return true;
1359     }
1360     // fall-through: aIter is on ';', increment and return false
1361     ++aIter;
1362   }
1363   return false;
1364 }
1365 
SetSameSiteDefaultAttribute(CookieStruct & aCookieData,bool laxByDefault)1366 static inline void SetSameSiteDefaultAttribute(CookieStruct& aCookieData,
1367                                                bool laxByDefault) {
1368   aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
1369   if (laxByDefault) {
1370     aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
1371   } else {
1372     aCookieData.sameSite() = nsICookie::SAMESITE_NONE;
1373   }
1374 }
1375 
1376 // Parses attributes from cookie header. expires/max-age attributes aren't
1377 // folded into the cookie struct here, because we don't know which one to use
1378 // until we've parsed the header.
ParseAttributes(nsIConsoleReportCollector * aCRC,nsIURI * aHostURI,nsCString & aCookieHeader,CookieStruct & aCookieData,nsACString & aExpires,nsACString & aMaxage,bool & aAcceptedByParser)1379 bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
1380                                     nsIURI* aHostURI, nsCString& aCookieHeader,
1381                                     CookieStruct& aCookieData,
1382                                     nsACString& aExpires, nsACString& aMaxage,
1383                                     bool& aAcceptedByParser) {
1384   aAcceptedByParser = false;
1385 
1386   static const char kPath[] = "path";
1387   static const char kDomain[] = "domain";
1388   static const char kExpires[] = "expires";
1389   static const char kMaxage[] = "max-age";
1390   static const char kSecure[] = "secure";
1391   static const char kHttpOnly[] = "httponly";
1392   static const char kSameSite[] = "samesite";
1393   static const char kSameSiteLax[] = "lax";
1394   static const char kSameSiteNone[] = "none";
1395   static const char kSameSiteStrict[] = "strict";
1396 
1397   nsACString::const_char_iterator cookieStart;
1398   aCookieHeader.BeginReading(cookieStart);
1399 
1400   nsACString::const_char_iterator cookieEnd;
1401   aCookieHeader.EndReading(cookieEnd);
1402 
1403   aCookieData.isSecure() = false;
1404   aCookieData.isHttpOnly() = false;
1405 
1406   bool laxByDefault =
1407       StaticPrefs::network_cookie_sameSite_laxByDefault() &&
1408       !nsContentUtils::IsURIInPrefList(
1409           aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
1410   SetSameSiteDefaultAttribute(aCookieData, laxByDefault);
1411 
1412   nsDependentCSubstring tokenString(cookieStart, cookieStart);
1413   nsDependentCSubstring tokenValue(cookieStart, cookieStart);
1414   bool newCookie;
1415   bool equalsFound;
1416 
1417   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1418   // if we find multiple cookies, return for processing
1419   // note: if there's no '=', we assume token is <VALUE>. this is required by
1420   //       some sites (see bug 169091).
1421   // XXX fix the parser to parse according to <VALUE> grammar for this case
1422   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
1423                             equalsFound);
1424   if (equalsFound) {
1425     aCookieData.name() = tokenString;
1426     aCookieData.value() = tokenValue;
1427   } else {
1428     aCookieData.value() = tokenString;
1429   }
1430 
1431   bool sameSiteSet = false;
1432 
1433   // extract remaining attributes
1434   while (cookieStart != cookieEnd && !newCookie) {
1435     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
1436                               equalsFound);
1437 
1438     // decide which attribute we have, and copy the string
1439     if (tokenString.LowerCaseEqualsLiteral(kPath)) {
1440       aCookieData.path() = tokenValue;
1441 
1442     } else if (tokenString.LowerCaseEqualsLiteral(kDomain)) {
1443       aCookieData.host() = tokenValue;
1444 
1445     } else if (tokenString.LowerCaseEqualsLiteral(kExpires)) {
1446       aExpires = tokenValue;
1447 
1448     } else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) {
1449       aMaxage = tokenValue;
1450 
1451       // ignore any tokenValue for isSecure; just set the boolean
1452     } else if (tokenString.LowerCaseEqualsLiteral(kSecure)) {
1453       aCookieData.isSecure() = true;
1454 
1455       // ignore any tokenValue for isHttpOnly (see bug 178993);
1456       // just set the boolean
1457     } else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) {
1458       aCookieData.isHttpOnly() = true;
1459 
1460     } else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
1461       if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
1462         aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
1463         aCookieData.rawSameSite() = nsICookie::SAMESITE_LAX;
1464         sameSiteSet = true;
1465       } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
1466         aCookieData.sameSite() = nsICookie::SAMESITE_STRICT;
1467         aCookieData.rawSameSite() = nsICookie::SAMESITE_STRICT;
1468         sameSiteSet = true;
1469       } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteNone)) {
1470         aCookieData.sameSite() = nsICookie::SAMESITE_NONE;
1471         aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
1472         sameSiteSet = true;
1473       } else {
1474         // Reset to defaults if unknown token value (see Bug 1682450)
1475         SetSameSiteDefaultAttribute(aCookieData, laxByDefault);
1476         sameSiteSet = false;
1477         CookieLogging::LogMessageToConsole(
1478             aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1479             "CookieSameSiteValueInvalid2"_ns,
1480             AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1481       }
1482     }
1483   }
1484 
1485   Telemetry::Accumulate(Telemetry::COOKIE_SAMESITE_SET_VS_UNSET,
1486                         sameSiteSet ? 1 : 0);
1487 
1488   // re-assign aCookieHeader, in case we need to process another cookie
1489   aCookieHeader.Assign(Substring(cookieStart, cookieEnd));
1490 
1491   // If same-site is set to 'none' but this is not a secure context, let's abort
1492   // the parsing.
1493   if (!aCookieData.isSecure() &&
1494       aCookieData.sameSite() == nsICookie::SAMESITE_NONE) {
1495     if (laxByDefault &&
1496         StaticPrefs::network_cookie_sameSite_noneRequiresSecure()) {
1497       CookieLogging::LogMessageToConsole(
1498           aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1499           "CookieRejectedNonRequiresSecure2"_ns,
1500           AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1501       return newCookie;
1502     }
1503 
1504     // if SameSite=Lax by default is disabled, we want to warn the user.
1505     CookieLogging::LogMessageToConsole(
1506         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
1507         "CookieRejectedNonRequiresSecureForBeta2"_ns,
1508         AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
1509                                 SAMESITE_MDN_URL});
1510   }
1511 
1512   if (aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE &&
1513       aCookieData.sameSite() == nsICookie::SAMESITE_LAX) {
1514     if (laxByDefault) {
1515       CookieLogging::LogMessageToConsole(
1516           aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
1517           "CookieLaxForced2"_ns,
1518           AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
1519     } else {
1520       CookieLogging::LogMessageToConsole(
1521           aCRC, aHostURI, nsIScriptError::warningFlag,
1522           CONSOLE_SAMESITE_CATEGORY, "CookieLaxForcedForBeta2"_ns,
1523           AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
1524                                   SAMESITE_MDN_URL});
1525     }
1526   }
1527 
1528   // Cookie accepted.
1529   aAcceptedByParser = true;
1530 
1531   MOZ_ASSERT(Cookie::ValidateRawSame(aCookieData));
1532   return newCookie;
1533 }
1534 
1535 /******************************************************************************
1536  * CookieService impl:
1537  * private domain & permission compliance enforcement functions
1538  ******************************************************************************/
1539 
1540 // Normalizes the given hostname, component by component. ASCII/ACE
1541 // components are lower-cased, and UTF-8 components are normalized per
1542 // RFC 3454 and converted to ACE.
NormalizeHost(nsCString & aHost)1543 nsresult CookieService::NormalizeHost(nsCString& aHost) {
1544   if (!IsAscii(aHost)) {
1545     nsAutoCString host;
1546     nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
1547     if (NS_FAILED(rv)) {
1548       return rv;
1549     }
1550 
1551     aHost = host;
1552   }
1553 
1554   ToLowerCase(aHost);
1555   return NS_OK;
1556 }
1557 
1558 // returns true if 'a' is equal to or a subdomain of 'b',
1559 // assuming no leading dots are present.
IsSubdomainOf(const nsACString & a,const nsACString & b)1560 static inline bool IsSubdomainOf(const nsACString& a, const nsACString& b) {
1561   if (a == b) {
1562     return true;
1563   }
1564   if (a.Length() > b.Length()) {
1565     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
1566   }
1567   return false;
1568 }
1569 
CheckPrefs(nsIConsoleReportCollector * aCRC,nsICookieJarSettings * aCookieJarSettings,nsIURI * aHostURI,bool aIsForeign,bool aIsThirdPartyTrackingResource,bool aIsThirdPartySocialTrackingResource,bool aStorageAccessPermissionGranted,const nsACString & aCookieHeader,const int aNumOfCookies,const OriginAttributes & aOriginAttrs,uint32_t * aRejectedReason)1570 CookieStatus CookieService::CheckPrefs(
1571     nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings,
1572     nsIURI* aHostURI, bool aIsForeign, bool aIsThirdPartyTrackingResource,
1573     bool aIsThirdPartySocialTrackingResource,
1574     bool aStorageAccessPermissionGranted, const nsACString& aCookieHeader,
1575     const int aNumOfCookies, const OriginAttributes& aOriginAttrs,
1576     uint32_t* aRejectedReason) {
1577   nsresult rv;
1578 
1579   MOZ_ASSERT(aRejectedReason);
1580 
1581   *aRejectedReason = 0;
1582 
1583   // don't let unsupported scheme sites get/set cookies (could be a security
1584   // issue)
1585   if (!CookieCommons::IsSchemeSupported(aHostURI)) {
1586     COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1587                       "non http/https sites cannot read cookies");
1588     return STATUS_REJECTED_WITH_ERROR;
1589   }
1590 
1591   nsCOMPtr<nsIPrincipal> principal =
1592       BasePrincipal::CreateContentPrincipal(aHostURI, aOriginAttrs);
1593 
1594   if (!principal) {
1595     COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1596                       "non-content principals cannot get/set cookies");
1597     return STATUS_REJECTED_WITH_ERROR;
1598   }
1599 
1600   // check the permission list first; if we find an entry, it overrides
1601   // default prefs. see bug 184059.
1602   uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
1603   rv = aCookieJarSettings->CookiePermission(principal, &cookiePermission);
1604   if (NS_SUCCEEDED(rv)) {
1605     switch (cookiePermission) {
1606       case nsICookiePermission::ACCESS_DENY:
1607         COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1608                           "cookies are blocked for this site");
1609         CookieLogging::LogMessageToConsole(
1610             aCRC, aHostURI, nsIScriptError::warningFlag,
1611             CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
1612             AutoTArray<nsString, 1>{
1613                 NS_ConvertUTF8toUTF16(aCookieHeader),
1614             });
1615 
1616         *aRejectedReason =
1617             nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION;
1618         return STATUS_REJECTED;
1619 
1620       case nsICookiePermission::ACCESS_ALLOW:
1621         return STATUS_ACCEPTED;
1622       default:
1623         break;
1624     }
1625   }
1626 
1627   // No cookies allowed if this request comes from a resource in a 3rd party
1628   // context, when anti-tracking protection is enabled and when we don't have
1629   // access to the first-party cookie jar.
1630   if (aIsForeign && aIsThirdPartyTrackingResource &&
1631       !aStorageAccessPermissionGranted &&
1632       aCookieJarSettings->GetRejectThirdPartyContexts()) {
1633     bool rejectThirdPartyWithExceptions =
1634         CookieJarSettings::IsRejectThirdPartyWithExceptions(
1635             aCookieJarSettings->GetCookieBehavior());
1636 
1637     uint32_t rejectReason =
1638         rejectThirdPartyWithExceptions
1639             ? nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
1640             : nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
1641     if (StoragePartitioningEnabled(rejectReason, aCookieJarSettings)) {
1642       MOZ_ASSERT(!aOriginAttrs.mPartitionKey.IsEmpty(),
1643                  "We must have a StoragePrincipal here!");
1644       return STATUS_ACCEPTED;
1645     }
1646 
1647     COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1648                       "cookies are disabled in trackers");
1649     if (aIsThirdPartySocialTrackingResource) {
1650       *aRejectedReason =
1651           nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
1652     } else if (rejectThirdPartyWithExceptions) {
1653       *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1654     } else {
1655       *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
1656     }
1657     return STATUS_REJECTED;
1658   }
1659 
1660   // check default prefs.
1661   // Check aStorageAccessPermissionGranted when checking aCookieBehavior
1662   // so that we take things such as the content blocking allow list into
1663   // account.
1664   if (aCookieJarSettings->GetCookieBehavior() ==
1665           nsICookieService::BEHAVIOR_REJECT &&
1666       !aStorageAccessPermissionGranted) {
1667     COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1668                       "cookies are disabled");
1669     *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL;
1670     return STATUS_REJECTED;
1671   }
1672 
1673   // check if cookie is foreign
1674   if (aIsForeign) {
1675     if (aCookieJarSettings->GetCookieBehavior() ==
1676             nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
1677         !aStorageAccessPermissionGranted) {
1678       COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1679                         "context is third party");
1680       CookieLogging::LogMessageToConsole(
1681           aCRC, aHostURI, nsIScriptError::warningFlag,
1682           CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
1683           AutoTArray<nsString, 1>{
1684               NS_ConvertUTF8toUTF16(aCookieHeader),
1685           });
1686       *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1687       return STATUS_REJECTED;
1688     }
1689 
1690     if (aCookieJarSettings->GetLimitForeignContexts() &&
1691         !aStorageAccessPermissionGranted && aNumOfCookies == 0) {
1692       COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
1693                         "context is third party");
1694       CookieLogging::LogMessageToConsole(
1695           aCRC, aHostURI, nsIScriptError::warningFlag,
1696           CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
1697           AutoTArray<nsString, 1>{
1698               NS_ConvertUTF8toUTF16(aCookieHeader),
1699           });
1700       *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
1701       return STATUS_REJECTED;
1702     }
1703 
1704     if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
1705       return STATUS_ACCEPT_SESSION;
1706     }
1707 
1708     if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly()) {
1709       if (!aHostURI->SchemeIs("https")) {
1710         return STATUS_ACCEPT_SESSION;
1711       }
1712     }
1713   }
1714 
1715   // if nothing has complained, accept cookie
1716   return STATUS_ACCEPTED;
1717 }
1718 
1719 // processes domain attribute, and returns true if host has permission to set
1720 // for this domain.
CheckDomain(CookieStruct & aCookieData,nsIURI * aHostURI,const nsACString & aBaseDomain,bool aRequireHostMatch)1721 bool CookieService::CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
1722                                 const nsACString& aBaseDomain,
1723                                 bool aRequireHostMatch) {
1724   // Note: The logic in this function is mirrored in
1725   // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
1726   // If it changes, please update that function, or file a bug for someone
1727   // else to do so.
1728 
1729   // get host from aHostURI
1730   nsAutoCString hostFromURI;
1731   nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
1732 
1733   // if a domain is given, check the host has permission
1734   if (!aCookieData.host().IsEmpty()) {
1735     // Tolerate leading '.' characters, but not if it's otherwise an empty host.
1736     if (aCookieData.host().Length() > 1 && aCookieData.host().First() == '.') {
1737       aCookieData.host().Cut(0, 1);
1738     }
1739 
1740     // switch to lowercase now, to avoid case-insensitive compares everywhere
1741     ToLowerCase(aCookieData.host());
1742 
1743     // check whether the host is either an IP address, an alias such as
1744     // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
1745     // cases, require an exact string match for the domain, and leave the cookie
1746     // as a non-domain one. bug 105917 originally noted the requirement to deal
1747     // with IP addresses.
1748     if (aRequireHostMatch) {
1749       return hostFromURI.Equals(aCookieData.host());
1750     }
1751 
1752     // ensure the proposed domain is derived from the base domain; and also
1753     // that the host domain is derived from the proposed domain (per RFC2109).
1754     if (IsSubdomainOf(aCookieData.host(), aBaseDomain) &&
1755         IsSubdomainOf(hostFromURI, aCookieData.host())) {
1756       // prepend a dot to indicate a domain cookie
1757       aCookieData.host().InsertLiteral(".", 0);
1758       return true;
1759     }
1760 
1761     /*
1762      * note: RFC2109 section 4.3.2 requires that we check the following:
1763      * that the portion of host not in domain does not contain a dot.
1764      * this prevents hosts of the form x.y.co.nz from setting cookies in the
1765      * entire .co.nz domain. however, it's only a only a partial solution and
1766      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1767      */
1768     return false;
1769   }
1770 
1771   // no domain specified, use hostFromURI
1772   aCookieData.host() = hostFromURI;
1773   return true;
1774 }
1775 
1776 namespace {
GetPathFromURI(nsIURI * aHostURI)1777 nsAutoCString GetPathFromURI(nsIURI* aHostURI) {
1778   // strip down everything after the last slash to get the path,
1779   // ignoring slashes in the query string part.
1780   // if we can QI to nsIURL, that'll take care of the query string portion.
1781   // otherwise, it's not an nsIURL and can't have a query string, so just find
1782   // the last slash.
1783   nsAutoCString path;
1784   nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
1785   if (hostURL) {
1786     hostURL->GetDirectory(path);
1787   } else {
1788     aHostURI->GetPathQueryRef(path);
1789     int32_t slash = path.RFindChar('/');
1790     if (slash != kNotFound) {
1791       path.Truncate(slash + 1);
1792     }
1793   }
1794 
1795   // strip the right-most %x2F ("/") if the path doesn't contain only 1 '/'.
1796   int32_t lastSlash = path.RFindChar('/');
1797   int32_t firstSlash = path.FindChar('/');
1798   if (lastSlash != firstSlash && lastSlash != kNotFound &&
1799       lastSlash == static_cast<int32_t>(path.Length() - 1)) {
1800     path.Truncate(lastSlash);
1801   }
1802 
1803   return path;
1804 }
1805 
1806 }  // namespace
1807 
CheckPath(CookieStruct & aCookieData,nsIConsoleReportCollector * aCRC,nsIURI * aHostURI)1808 bool CookieService::CheckPath(CookieStruct& aCookieData,
1809                               nsIConsoleReportCollector* aCRC,
1810                               nsIURI* aHostURI) {
1811   // if a path is given, check the host has permission
1812   if (aCookieData.path().IsEmpty() || aCookieData.path().First() != '/') {
1813     aCookieData.path() = GetPathFromURI(aHostURI);
1814 
1815 #if 0
1816   } else {
1817     /**
1818      * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
1819      * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
1820      * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
1821      * been disabled, unless we can evangelize these sites.
1822      */
1823     // get path from aHostURI
1824     nsAutoCString pathFromURI;
1825     if (NS_FAILED(aHostURI->GetPathQueryRef(pathFromURI)) ||
1826         !StringBeginsWith(pathFromURI, aCookieData.path())) {
1827       return false;
1828     }
1829 #endif
1830   }
1831 
1832   if (!CookieCommons::CheckPathSize(aCookieData)) {
1833     AutoTArray<nsString, 2> params = {
1834         NS_ConvertUTF8toUTF16(aCookieData.name())};
1835 
1836     nsString size;
1837     size.AppendInt(kMaxBytesPerPath);
1838     params.AppendElement(size);
1839 
1840     CookieLogging::LogMessageToConsole(
1841         aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
1842         "CookiePathOversize"_ns, params);
1843     return false;
1844   }
1845 
1846   return !aCookieData.path().Contains('\t');
1847 }
1848 
1849 // CheckPrefixes
1850 //
1851 // Reject cookies whose name starts with the magic prefixes from
1852 // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
1853 // if they do not meet the criteria required by the prefix.
1854 //
1855 // Must not be called until after CheckDomain() and CheckPath() have
1856 // regularized and validated the CookieStruct values!
CheckPrefixes(CookieStruct & aCookieData,bool aSecureRequest)1857 bool CookieService::CheckPrefixes(CookieStruct& aCookieData,
1858                                   bool aSecureRequest) {
1859   static const char kSecure[] = "__Secure-";
1860   static const char kHost[] = "__Host-";
1861   static const int kSecureLen = sizeof(kSecure) - 1;
1862   static const int kHostLen = sizeof(kHost) - 1;
1863 
1864   bool isSecure = strncmp(aCookieData.name().get(), kSecure, kSecureLen) == 0;
1865   bool isHost = strncmp(aCookieData.name().get(), kHost, kHostLen) == 0;
1866 
1867   if (!isSecure && !isHost) {
1868     // not one of the magic prefixes: carry on
1869     return true;
1870   }
1871 
1872   if (!aSecureRequest || !aCookieData.isSecure()) {
1873     // the magic prefixes may only be used from a secure request and
1874     // the secure attribute must be set on the cookie
1875     return false;
1876   }
1877 
1878   if (isHost) {
1879     // The host prefix requires that the path is "/" and that the cookie
1880     // had no domain attribute. CheckDomain() and CheckPath() MUST be run
1881     // first to make sure invalid attributes are rejected and to regularlize
1882     // them. In particular all explicit domain attributes result in a host
1883     // that starts with a dot, and if the host doesn't start with a dot it
1884     // correctly matches the true host.
1885     if (aCookieData.host()[0] == '.' ||
1886         !aCookieData.path().EqualsLiteral("/")) {
1887       return false;
1888     }
1889   }
1890 
1891   return true;
1892 }
1893 
GetExpiry(CookieStruct & aCookieData,const nsACString & aExpires,const nsACString & aMaxage,int64_t aCurrentTime,bool aFromHttp)1894 bool CookieService::GetExpiry(CookieStruct& aCookieData,
1895                               const nsACString& aExpires,
1896                               const nsACString& aMaxage, int64_t aCurrentTime,
1897                               bool aFromHttp) {
1898   // maxageCap is in seconds.
1899   // Disabled for HTTP cookies.
1900   int64_t maxageCap =
1901       aFromHttp ? 0 : StaticPrefs::privacy_documentCookies_maxage();
1902 
1903   /* Determine when the cookie should expire. This is done by taking the
1904    * difference between the server time and the time the server wants the cookie
1905    * to expire, and adding that difference to the client time. This localizes
1906    * the client time regardless of whether or not the TZ environment variable
1907    * was set on the client.
1908    *
1909    * Note: We need to consider accounting for network lag here, per RFC.
1910    */
1911   // check for max-age attribute first; this overrides expires attribute
1912   if (!aMaxage.IsEmpty()) {
1913     // obtain numeric value of maxageAttribute
1914     int64_t maxage;
1915     int32_t numInts = PR_sscanf(aMaxage.BeginReading(), "%lld", &maxage);
1916 
1917     // default to session cookie if the conversion failed
1918     if (numInts != 1) {
1919       return true;
1920     }
1921 
1922     // if this addition overflows, expiryTime will be less than currentTime
1923     // and the cookie will be expired - that's okay.
1924     if (maxageCap) {
1925       aCookieData.expiry() = aCurrentTime + std::min(maxage, maxageCap);
1926     } else {
1927       aCookieData.expiry() = aCurrentTime + maxage;
1928     }
1929 
1930     // check for expires attribute
1931   } else if (!aExpires.IsEmpty()) {
1932     PRTime expires;
1933 
1934     // parse expiry time
1935     if (PR_ParseTimeString(aExpires.BeginReading(), true, &expires) !=
1936         PR_SUCCESS) {
1937       return true;
1938     }
1939 
1940     // If set-cookie used absolute time to set expiration, and it can't use
1941     // client time to set expiration.
1942     // Because if current time be set in the future, but the cookie expire
1943     // time be set less than current time and more than server time.
1944     // The cookie item have to be used to the expired cookie.
1945     if (maxageCap) {
1946       aCookieData.expiry() = std::min(expires / int64_t(PR_USEC_PER_SEC),
1947                                       aCurrentTime + maxageCap);
1948     } else {
1949       aCookieData.expiry() = expires / int64_t(PR_USEC_PER_SEC);
1950     }
1951 
1952     // default to session cookie if no attributes found.  Here we don't need to
1953     // enforce the maxage cap, because session cookies are short-lived by
1954     // definition.
1955   } else {
1956     return true;
1957   }
1958 
1959   return false;
1960 }
1961 
1962 /******************************************************************************
1963  * CookieService impl:
1964  * private cookielist management functions
1965  ******************************************************************************/
1966 
1967 // find whether a given cookie has been previously set. this is provided by the
1968 // nsICookieManager interface.
1969 NS_IMETHODIMP
CookieExists(const nsACString & aHost,const nsACString & aPath,const nsACString & aName,JS::HandleValue aOriginAttributes,JSContext * aCx,bool * aFoundCookie)1970 CookieService::CookieExists(const nsACString& aHost, const nsACString& aPath,
1971                             const nsACString& aName,
1972                             JS::HandleValue aOriginAttributes, JSContext* aCx,
1973                             bool* aFoundCookie) {
1974   NS_ENSURE_ARG_POINTER(aCx);
1975   NS_ENSURE_ARG_POINTER(aFoundCookie);
1976 
1977   OriginAttributes attrs;
1978   if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
1979     return NS_ERROR_INVALID_ARG;
1980   }
1981   return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie);
1982 }
1983 
NS_IMETHODIMP_(nsresult)1984 NS_IMETHODIMP_(nsresult)
1985 CookieService::CookieExistsNative(const nsACString& aHost,
1986                                   const nsACString& aPath,
1987                                   const nsACString& aName,
1988                                   OriginAttributes* aOriginAttributes,
1989                                   bool* aFoundCookie) {
1990   NS_ENSURE_ARG_POINTER(aOriginAttributes);
1991   NS_ENSURE_ARG_POINTER(aFoundCookie);
1992 
1993   if (!IsInitialized()) {
1994     return NS_ERROR_NOT_AVAILABLE;
1995   }
1996 
1997   nsAutoCString baseDomain;
1998   nsresult rv =
1999       CookieCommons::GetBaseDomainFromHost(mTLDService, aHost, baseDomain);
2000   NS_ENSURE_SUCCESS(rv, rv);
2001 
2002   CookieListIter iter{};
2003   CookieStorage* storage = PickStorage(*aOriginAttributes);
2004   *aFoundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost,
2005                                       aName, aPath, iter);
2006   return NS_OK;
2007 }
2008 
2009 // count the number of cookies stored by a particular host. this is provided by
2010 // the nsICookieManager interface.
2011 NS_IMETHODIMP
CountCookiesFromHost(const nsACString & aHost,uint32_t * aCountFromHost)2012 CookieService::CountCookiesFromHost(const nsACString& aHost,
2013                                     uint32_t* aCountFromHost) {
2014   // first, normalize the hostname, and fail if it contains illegal characters.
2015   nsAutoCString host(aHost);
2016   nsresult rv = NormalizeHost(host);
2017   NS_ENSURE_SUCCESS(rv, rv);
2018 
2019   nsAutoCString baseDomain;
2020   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2021   NS_ENSURE_SUCCESS(rv, rv);
2022 
2023   if (!IsInitialized()) {
2024     return NS_ERROR_NOT_AVAILABLE;
2025   }
2026 
2027   mPersistentStorage->EnsureReadComplete();
2028 
2029   *aCountFromHost = mPersistentStorage->CountCookiesFromHost(baseDomain, 0);
2030 
2031   return NS_OK;
2032 }
2033 
2034 // get an enumerator of cookies stored by a particular host. this is provided by
2035 // the nsICookieManager interface.
2036 NS_IMETHODIMP
GetCookiesFromHost(const nsACString & aHost,JS::HandleValue aOriginAttributes,JSContext * aCx,nsTArray<RefPtr<nsICookie>> & aResult)2037 CookieService::GetCookiesFromHost(const nsACString& aHost,
2038                                   JS::HandleValue aOriginAttributes,
2039                                   JSContext* aCx,
2040                                   nsTArray<RefPtr<nsICookie>>& aResult) {
2041   // first, normalize the hostname, and fail if it contains illegal characters.
2042   nsAutoCString host(aHost);
2043   nsresult rv = NormalizeHost(host);
2044   NS_ENSURE_SUCCESS(rv, rv);
2045 
2046   nsAutoCString baseDomain;
2047   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2048   NS_ENSURE_SUCCESS(rv, rv);
2049 
2050   OriginAttributes attrs;
2051   if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
2052     return NS_ERROR_INVALID_ARG;
2053   }
2054 
2055   if (!IsInitialized()) {
2056     return NS_ERROR_NOT_AVAILABLE;
2057   }
2058 
2059   CookieStorage* storage = PickStorage(attrs);
2060 
2061   const nsTArray<RefPtr<Cookie>>* cookies =
2062       storage->GetCookiesFromHost(baseDomain, attrs);
2063 
2064   if (cookies) {
2065     aResult.SetCapacity(cookies->Length());
2066     for (Cookie* cookie : *cookies) {
2067       aResult.AppendElement(cookie);
2068     }
2069   }
2070 
2071   return NS_OK;
2072 }
2073 
2074 NS_IMETHODIMP
GetCookiesWithOriginAttributes(const nsAString & aPattern,const nsACString & aHost,nsTArray<RefPtr<nsICookie>> & aResult)2075 CookieService::GetCookiesWithOriginAttributes(
2076     const nsAString& aPattern, const nsACString& aHost,
2077     nsTArray<RefPtr<nsICookie>>& aResult) {
2078   OriginAttributesPattern pattern;
2079   if (!pattern.Init(aPattern)) {
2080     return NS_ERROR_INVALID_ARG;
2081   }
2082 
2083   nsAutoCString host(aHost);
2084   nsresult rv = NormalizeHost(host);
2085   NS_ENSURE_SUCCESS(rv, rv);
2086 
2087   nsAutoCString baseDomain;
2088   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2089   NS_ENSURE_SUCCESS(rv, rv);
2090 
2091   return GetCookiesWithOriginAttributes(pattern, baseDomain, aResult);
2092 }
2093 
GetCookiesWithOriginAttributes(const OriginAttributesPattern & aPattern,const nsCString & aBaseDomain,nsTArray<RefPtr<nsICookie>> & aResult)2094 nsresult CookieService::GetCookiesWithOriginAttributes(
2095     const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain,
2096     nsTArray<RefPtr<nsICookie>>& aResult) {
2097   CookieStorage* storage = PickStorage(aPattern);
2098   storage->GetCookiesWithOriginAttributes(aPattern, aBaseDomain, aResult);
2099 
2100   return NS_OK;
2101 }
2102 
2103 NS_IMETHODIMP
RemoveCookiesWithOriginAttributes(const nsAString & aPattern,const nsACString & aHost)2104 CookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
2105                                                  const nsACString& aHost) {
2106   MOZ_ASSERT(XRE_IsParentProcess());
2107 
2108   OriginAttributesPattern pattern;
2109   if (!pattern.Init(aPattern)) {
2110     return NS_ERROR_INVALID_ARG;
2111   }
2112 
2113   nsAutoCString host(aHost);
2114   nsresult rv = NormalizeHost(host);
2115   NS_ENSURE_SUCCESS(rv, rv);
2116 
2117   nsAutoCString baseDomain;
2118   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2119   NS_ENSURE_SUCCESS(rv, rv);
2120 
2121   return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
2122 }
2123 
RemoveCookiesWithOriginAttributes(const OriginAttributesPattern & aPattern,const nsCString & aBaseDomain)2124 nsresult CookieService::RemoveCookiesWithOriginAttributes(
2125     const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain) {
2126   if (!IsInitialized()) {
2127     return NS_ERROR_NOT_AVAILABLE;
2128   }
2129 
2130   CookieStorage* storage = PickStorage(aPattern);
2131   storage->RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
2132 
2133   return NS_OK;
2134 }
2135 
2136 NS_IMETHODIMP
RemoveCookiesFromExactHost(const nsACString & aHost,const nsAString & aPattern)2137 CookieService::RemoveCookiesFromExactHost(const nsACString& aHost,
2138                                           const nsAString& aPattern) {
2139   MOZ_ASSERT(XRE_IsParentProcess());
2140 
2141   OriginAttributesPattern pattern;
2142   if (!pattern.Init(aPattern)) {
2143     return NS_ERROR_INVALID_ARG;
2144   }
2145 
2146   return RemoveCookiesFromExactHost(aHost, pattern);
2147 }
2148 
RemoveCookiesFromExactHost(const nsACString & aHost,const OriginAttributesPattern & aPattern)2149 nsresult CookieService::RemoveCookiesFromExactHost(
2150     const nsACString& aHost, const OriginAttributesPattern& aPattern) {
2151   nsAutoCString host(aHost);
2152   nsresult rv = NormalizeHost(host);
2153   NS_ENSURE_SUCCESS(rv, rv);
2154 
2155   nsAutoCString baseDomain;
2156   rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
2157   NS_ENSURE_SUCCESS(rv, rv);
2158 
2159   if (!IsInitialized()) {
2160     return NS_ERROR_NOT_AVAILABLE;
2161   }
2162 
2163   CookieStorage* storage = PickStorage(aPattern);
2164   storage->RemoveCookiesFromExactHost(aHost, baseDomain, aPattern);
2165 
2166   return NS_OK;
2167 }
2168 
2169 namespace {
2170 
2171 class RemoveAllSinceRunnable : public Runnable {
2172  public:
2173   using CookieArray = nsTArray<RefPtr<nsICookie>>;
RemoveAllSinceRunnable(Promise * aPromise,CookieService * aSelf,CookieArray && aCookieArray,int64_t aSinceWhen)2174   RemoveAllSinceRunnable(Promise* aPromise, CookieService* aSelf,
2175                          CookieArray&& aCookieArray, int64_t aSinceWhen)
2176       : Runnable("RemoveAllSinceRunnable"),
2177         mPromise(aPromise),
2178         mSelf(aSelf),
2179         mList(std::move(aCookieArray)),
2180         mIndex(0),
2181         mSinceWhen(aSinceWhen) {}
2182 
Run()2183   NS_IMETHODIMP Run() override {
2184     RemoveSome();
2185 
2186     if (mIndex < mList.Length()) {
2187       return NS_DispatchToCurrentThread(this);
2188     }
2189     mPromise->MaybeResolveWithUndefined();
2190 
2191     return NS_OK;
2192   }
2193 
2194  private:
RemoveSome()2195   void RemoveSome() {
2196     for (CookieArray::size_type iter = 0;
2197          iter < kYieldPeriod && mIndex < mList.Length(); ++mIndex, ++iter) {
2198       auto* cookie = static_cast<Cookie*>(mList[mIndex].get());
2199       if (cookie->CreationTime() > mSinceWhen &&
2200           NS_FAILED(mSelf->Remove(cookie->Host(), cookie->OriginAttributesRef(),
2201                                   cookie->Name(), cookie->Path()))) {
2202         continue;
2203       }
2204     }
2205   }
2206 
2207  private:
2208   RefPtr<Promise> mPromise;
2209   RefPtr<CookieService> mSelf;
2210   CookieArray mList;
2211   CookieArray::size_type mIndex;
2212   int64_t mSinceWhen;
2213   static const CookieArray::size_type kYieldPeriod = 10;
2214 };
2215 
2216 }  // namespace
2217 
2218 NS_IMETHODIMP
RemoveAllSince(int64_t aSinceWhen,JSContext * aCx,Promise ** aRetVal)2219 CookieService::RemoveAllSince(int64_t aSinceWhen, JSContext* aCx,
2220                               Promise** aRetVal) {
2221   nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
2222   if (NS_WARN_IF(!globalObject)) {
2223     return NS_ERROR_UNEXPECTED;
2224   }
2225 
2226   ErrorResult result;
2227   RefPtr<Promise> promise = Promise::Create(globalObject, result);
2228   if (NS_WARN_IF(result.Failed())) {
2229     return result.StealNSResult();
2230   }
2231 
2232   mPersistentStorage->EnsureReadComplete();
2233 
2234   nsTArray<RefPtr<nsICookie>> cookieList;
2235 
2236   // We delete only non-private cookies.
2237   mPersistentStorage->GetAll(cookieList);
2238 
2239   RefPtr<RemoveAllSinceRunnable> runMe = new RemoveAllSinceRunnable(
2240       promise, this, std::move(cookieList), aSinceWhen);
2241 
2242   promise.forget(aRetVal);
2243 
2244   return runMe->Run();
2245 }
2246 
2247 namespace {
2248 
2249 class CompareCookiesCreationTime {
2250  public:
Equals(const nsICookie * aCookie1,const nsICookie * aCookie2)2251   static bool Equals(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2252     return static_cast<const Cookie*>(aCookie1)->CreationTime() ==
2253            static_cast<const Cookie*>(aCookie2)->CreationTime();
2254   }
2255 
LessThan(const nsICookie * aCookie1,const nsICookie * aCookie2)2256   static bool LessThan(const nsICookie* aCookie1, const nsICookie* aCookie2) {
2257     return static_cast<const Cookie*>(aCookie1)->CreationTime() <
2258            static_cast<const Cookie*>(aCookie2)->CreationTime();
2259   }
2260 };
2261 
2262 }  // namespace
2263 
2264 NS_IMETHODIMP
GetCookiesSince(int64_t aSinceWhen,nsTArray<RefPtr<nsICookie>> & aResult)2265 CookieService::GetCookiesSince(int64_t aSinceWhen,
2266                                nsTArray<RefPtr<nsICookie>>& aResult) {
2267   if (!IsInitialized()) {
2268     return NS_OK;
2269   }
2270 
2271   mPersistentStorage->EnsureReadComplete();
2272 
2273   // We expose only non-private cookies.
2274   nsTArray<RefPtr<nsICookie>> cookieList;
2275   mPersistentStorage->GetAll(cookieList);
2276 
2277   for (RefPtr<nsICookie>& cookie : cookieList) {
2278     if (static_cast<Cookie*>(cookie.get())->CreationTime() >= aSinceWhen) {
2279       aResult.AppendElement(cookie);
2280     }
2281   }
2282 
2283   aResult.Sort(CompareCookiesCreationTime());
2284   return NS_OK;
2285 }
2286 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const2287 size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2288   size_t n = aMallocSizeOf(this);
2289 
2290   if (mPersistentStorage) {
2291     n += mPersistentStorage->SizeOfIncludingThis(aMallocSizeOf);
2292   }
2293   if (mPrivateStorage) {
2294     n += mPrivateStorage->SizeOfIncludingThis(aMallocSizeOf);
2295   }
2296 
2297   return n;
2298 }
2299 
MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)2300 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
2301 
2302 NS_IMETHODIMP
2303 CookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
2304                               nsISupports* aData, bool /*aAnonymize*/) {
2305   MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
2306                      SizeOfIncludingThis(CookieServiceMallocSizeOf),
2307                      "Memory used by the cookie service.");
2308 
2309   return NS_OK;
2310 }
2311 
IsInitialized() const2312 bool CookieService::IsInitialized() const {
2313   if (!mPersistentStorage) {
2314     NS_WARNING("No CookieStorage! Profile already close?");
2315     return false;
2316   }
2317 
2318   MOZ_ASSERT(mPrivateStorage);
2319   return true;
2320 }
2321 
PickStorage(const OriginAttributes & aAttrs)2322 CookieStorage* CookieService::PickStorage(const OriginAttributes& aAttrs) {
2323   MOZ_ASSERT(IsInitialized());
2324 
2325   if (aAttrs.mPrivateBrowsingId > 0) {
2326     return mPrivateStorage;
2327   }
2328 
2329   mPersistentStorage->EnsureReadComplete();
2330   return mPersistentStorage;
2331 }
2332 
PickStorage(const OriginAttributesPattern & aAttrs)2333 CookieStorage* CookieService::PickStorage(
2334     const OriginAttributesPattern& aAttrs) {
2335   MOZ_ASSERT(IsInitialized());
2336 
2337   if (aAttrs.mPrivateBrowsingId.WasPassed() &&
2338       aAttrs.mPrivateBrowsingId.Value() > 0) {
2339     return mPrivateStorage;
2340   }
2341 
2342   mPersistentStorage->EnsureReadComplete();
2343   return mPersistentStorage;
2344 }
2345 
SetCookiesFromIPC(const nsACString & aBaseDomain,const OriginAttributes & aAttrs,nsIURI * aHostURI,bool aFromHttp,const nsTArray<CookieStruct> & aCookies)2346 bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain,
2347                                       const OriginAttributes& aAttrs,
2348                                       nsIURI* aHostURI, bool aFromHttp,
2349                                       const nsTArray<CookieStruct>& aCookies) {
2350   if (!IsInitialized()) {
2351     // If we are probably shutting down, we can ignore this cookie.
2352     return true;
2353   }
2354 
2355   CookieStorage* storage = PickStorage(aAttrs);
2356   int64_t currentTimeInUsec = PR_Now();
2357 
2358   for (const CookieStruct& cookieData : aCookies) {
2359     if (!CookieCommons::CheckPathSize(cookieData)) {
2360       return false;
2361     }
2362 
2363     // reject cookie if it's over the size limit, per RFC2109
2364     if (!CookieCommons::CheckNameAndValueSize(cookieData)) {
2365       return false;
2366     }
2367 
2368     if (!CookieCommons::CheckName(cookieData)) {
2369       return false;
2370     }
2371 
2372     if (aFromHttp && !CookieCommons::CheckHttpValue(cookieData)) {
2373       return false;
2374     }
2375 
2376     // create a new Cookie and copy attributes
2377     RefPtr<Cookie> cookie = Cookie::Create(cookieData, aAttrs);
2378     if (!cookie) {
2379       continue;
2380     }
2381 
2382     cookie->SetLastAccessed(currentTimeInUsec);
2383     cookie->SetCreationTime(
2384         Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
2385 
2386     storage->AddCookie(nullptr, aBaseDomain, aAttrs, cookie, currentTimeInUsec,
2387                        aHostURI, ""_ns, aFromHttp);
2388   }
2389 
2390   return true;
2391 }
2392 
2393 }  // namespace net
2394 }  // namespace mozilla
2395