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