Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / netwerk / cookie / CookieServiceChild.cpp
blobb5c91f5872844a264f2a40164947d78b65027f5e
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "Cookie.h"
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "CookieService.h"
10 #include "mozilla/net/CookieServiceChild.h"
11 #include "mozilla/net/HttpChannelChild.h"
12 #include "mozilla/net/NeckoChannelParams.h"
13 #include "mozilla/LoadInfo.h"
14 #include "mozilla/BasePrincipal.h"
15 #include "mozilla/ClearOnShutdown.h"
16 #include "mozilla/dom/ContentChild.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/ipc/URIUtils.h"
19 #include "mozilla/net/NeckoChild.h"
20 #include "mozilla/StaticPrefs_network.h"
21 #include "mozilla/StoragePrincipalHelper.h"
22 #include "nsNetCID.h"
23 #include "nsNetUtil.h"
24 #include "nsICookieJarSettings.h"
25 #include "nsIChannel.h"
26 #include "nsIClassifiedChannel.h"
27 #include "nsIHttpChannel.h"
28 #include "nsIEffectiveTLDService.h"
29 #include "nsIURI.h"
30 #include "nsIPrefBranch.h"
31 #include "nsIWebProgressListener.h"
32 #include "nsQueryObject.h"
33 #include "nsServiceManagerUtils.h"
34 #include "mozilla/Telemetry.h"
35 #include "mozilla/TimeStamp.h"
36 #include "ThirdPartyUtil.h"
37 #include "nsIConsoleReportCollector.h"
38 #include "mozilla/dom/WindowGlobalChild.h"
40 using namespace mozilla::ipc;
42 namespace mozilla {
43 namespace net {
45 static StaticRefPtr<CookieServiceChild> gCookieChildService;
47 already_AddRefed<CookieServiceChild> CookieServiceChild::GetSingleton() {
48 if (!gCookieChildService) {
49 gCookieChildService = new CookieServiceChild();
50 gCookieChildService->Init();
51 ClearOnShutdown(&gCookieChildService);
54 return do_AddRef(gCookieChildService);
57 NS_IMPL_ISUPPORTS(CookieServiceChild, nsICookieService,
58 nsISupportsWeakReference)
60 CookieServiceChild::CookieServiceChild() { NeckoChild::InitNeckoChild(); }
62 CookieServiceChild::~CookieServiceChild() { gCookieChildService = nullptr; }
64 void CookieServiceChild::Init() {
65 auto* cc = static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
66 if (cc->IsShuttingDown()) {
67 return;
70 // This corresponds to Release() in DeallocPCookieService.
71 NS_ADDREF_THIS();
73 // Create a child PCookieService actor. Don't do this in the constructor
74 // since it could release 'this' on failure
75 gNeckoChild->SendPCookieServiceConstructor(this);
77 mThirdPartyUtil = ThirdPartyUtil::GetInstance();
78 NS_ASSERTION(mThirdPartyUtil, "couldn't get ThirdPartyUtil service");
80 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
81 NS_ASSERTION(mTLDService, "couldn't get TLDService");
84 void CookieServiceChild::TrackCookieLoad(nsIChannel* aChannel) {
85 if (!CanSend()) {
86 return;
89 uint32_t rejectedReason = 0;
90 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
91 aChannel, true, nullptr, RequireThirdPartyCheck, &rejectedReason);
93 nsCOMPtr<nsIURI> uri;
94 aChannel->GetURI(getter_AddRefs(uri));
95 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
97 OriginAttributes attrs = loadInfo->GetOriginAttributes();
98 StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
99 aChannel, attrs);
101 bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
102 bool hadCrossSiteRedirects = false;
103 bool isSameSiteForeign =
104 CookieCommons::IsSameSiteForeign(aChannel, uri, &hadCrossSiteRedirects);
105 SendPrepareCookieList(
106 uri, result.contains(ThirdPartyAnalysis::IsForeign),
107 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
108 result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
109 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
110 rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
111 hadCrossSiteRedirects, attrs);
114 IPCResult CookieServiceChild::RecvRemoveAll() {
115 mCookiesMap.Clear();
116 return IPC_OK();
119 IPCResult CookieServiceChild::RecvRemoveCookie(const CookieStruct& aCookie,
120 const OriginAttributes& aAttrs) {
121 nsCString baseDomain;
122 CookieCommons::GetBaseDomainFromHost(mTLDService, aCookie.host(), baseDomain);
123 CookieKey key(baseDomain, aAttrs);
124 CookiesList* cookiesList = nullptr;
125 mCookiesMap.Get(key, &cookiesList);
127 if (!cookiesList) {
128 return IPC_OK();
131 for (uint32_t i = 0; i < cookiesList->Length(); i++) {
132 Cookie* cookie = cookiesList->ElementAt(i);
133 if (cookie->Name().Equals(aCookie.name()) &&
134 cookie->Host().Equals(aCookie.host()) &&
135 cookie->Path().Equals(aCookie.path())) {
136 cookiesList->RemoveElementAt(i);
137 break;
141 return IPC_OK();
144 IPCResult CookieServiceChild::RecvAddCookie(const CookieStruct& aCookie,
145 const OriginAttributes& aAttrs) {
146 RefPtr<Cookie> cookie = Cookie::Create(aCookie, aAttrs);
147 RecordDocumentCookie(cookie, aAttrs);
149 // signal test code to check their cookie list
150 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
151 if (obsService) {
152 obsService->NotifyObservers(nullptr, "cookie-content-filter-test", nullptr);
155 return IPC_OK();
158 IPCResult CookieServiceChild::RecvRemoveBatchDeletedCookies(
159 nsTArray<CookieStruct>&& aCookiesList,
160 nsTArray<OriginAttributes>&& aAttrsList) {
161 MOZ_ASSERT(aCookiesList.Length() == aAttrsList.Length());
162 for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
163 CookieStruct cookieStruct = aCookiesList.ElementAt(i);
164 RecvRemoveCookie(cookieStruct, aAttrsList.ElementAt(i));
166 return IPC_OK();
169 IPCResult CookieServiceChild::RecvTrackCookiesLoad(
170 nsTArray<CookieStruct>&& aCookiesList, const OriginAttributes& aAttrs) {
171 for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
172 RefPtr<Cookie> cookie = Cookie::Create(aCookiesList[i], aAttrs);
173 cookie->SetIsHttpOnly(false);
174 RecordDocumentCookie(cookie, aAttrs);
177 return IPC_OK();
180 uint32_t CookieServiceChild::CountCookiesFromHashTable(
181 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttrs) {
182 CookiesList* cookiesList = nullptr;
184 nsCString baseDomain;
185 CookieKey key(aBaseDomain, aOriginAttrs);
186 mCookiesMap.Get(key, &cookiesList);
188 return cookiesList ? cookiesList->Length() : 0;
191 /* static */ bool CookieServiceChild::RequireThirdPartyCheck(
192 nsILoadInfo* aLoadInfo) {
193 if (!aLoadInfo) {
194 return false;
197 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
198 nsresult rv =
199 aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
200 if (NS_WARN_IF(NS_FAILED(rv))) {
201 return false;
204 uint32_t cookieBehavior = cookieJarSettings->GetCookieBehavior();
205 return cookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
206 cookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN ||
207 cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
208 cookieBehavior ==
209 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN ||
210 StaticPrefs::network_cookie_thirdparty_sessionOnly() ||
211 StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly();
214 void CookieServiceChild::RecordDocumentCookie(Cookie* aCookie,
215 const OriginAttributes& aAttrs) {
216 nsAutoCString baseDomain;
217 CookieCommons::GetBaseDomainFromHost(mTLDService, aCookie->Host(),
218 baseDomain);
220 CookieKey key(baseDomain, aAttrs);
221 CookiesList* cookiesList = nullptr;
222 mCookiesMap.Get(key, &cookiesList);
224 if (!cookiesList) {
225 cookiesList = mCookiesMap.GetOrInsertNew(key);
227 for (uint32_t i = 0; i < cookiesList->Length(); i++) {
228 Cookie* cookie = cookiesList->ElementAt(i);
229 if (cookie->Name().Equals(aCookie->Name()) &&
230 cookie->Host().Equals(aCookie->Host()) &&
231 cookie->Path().Equals(aCookie->Path())) {
232 if (cookie->Value().Equals(aCookie->Value()) &&
233 cookie->Expiry() == aCookie->Expiry() &&
234 cookie->IsSecure() == aCookie->IsSecure() &&
235 cookie->SameSite() == aCookie->SameSite() &&
236 cookie->RawSameSite() == aCookie->RawSameSite() &&
237 cookie->IsSession() == aCookie->IsSession() &&
238 cookie->IsHttpOnly() == aCookie->IsHttpOnly()) {
239 cookie->SetLastAccessed(aCookie->LastAccessed());
240 return;
242 cookiesList->RemoveElementAt(i);
243 break;
247 int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
248 if (aCookie->Expiry() <= currentTime) {
249 return;
252 cookiesList->AppendElement(aCookie);
255 NS_IMETHODIMP
256 CookieServiceChild::GetCookieStringFromDocument(dom::Document* aDocument,
257 nsACString& aCookieString) {
258 NS_ENSURE_ARG(aDocument);
260 aCookieString.Truncate();
262 nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
264 if (!CookieCommons::IsSchemeSupported(principal)) {
265 return NS_OK;
268 nsAutoCString baseDomain;
269 nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain);
270 if (NS_WARN_IF(NS_FAILED(rv))) {
271 return NS_OK;
274 CookieKey key(baseDomain, principal->OriginAttributesRef());
275 CookiesList* cookiesList = nullptr;
276 mCookiesMap.Get(key, &cookiesList);
278 if (!cookiesList) {
279 return NS_OK;
282 nsAutoCString hostFromURI;
283 rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
284 if (NS_WARN_IF(NS_FAILED(rv))) {
285 return NS_OK;
288 nsAutoCString pathFromURI;
289 principal->GetFilePath(pathFromURI);
291 bool thirdParty = true;
292 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
293 // in gtests we don't have a window, let's consider those requests as 3rd
294 // party.
295 if (innerWindow) {
296 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
298 if (thirdPartyUtil) {
299 Unused << thirdPartyUtil->IsThirdPartyWindow(
300 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
304 bool isPotentiallyTrustworthy =
305 principal->GetIsOriginPotentiallyTrustworthy();
306 int64_t currentTimeInUsec = PR_Now();
307 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
309 cookiesList->Sort(CompareCookiesForSending());
310 for (uint32_t i = 0; i < cookiesList->Length(); i++) {
311 Cookie* cookie = cookiesList->ElementAt(i);
312 // check the host, since the base domain lookup is conservative.
313 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
314 continue;
317 // We don't show HttpOnly cookies in content processes.
318 if (cookie->IsHttpOnly()) {
319 continue;
322 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
323 cookie, aDocument)) {
324 continue;
327 // do not display the cookie if it is secure and the host scheme isn't
328 if (cookie->IsSecure() && !isPotentiallyTrustworthy) {
329 continue;
332 // if the nsIURI path doesn't match the cookie path, don't send it back
333 if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
334 continue;
337 // check if the cookie has expired
338 if (cookie->Expiry() <= currentTime) {
339 continue;
342 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
343 if (!aCookieString.IsEmpty()) {
344 aCookieString.AppendLiteral("; ");
346 if (!cookie->Name().IsEmpty()) {
347 aCookieString.Append(cookie->Name().get());
348 aCookieString.AppendLiteral("=");
349 aCookieString.Append(cookie->Value().get());
350 } else {
351 aCookieString.Append(cookie->Value().get());
356 return NS_OK;
359 NS_IMETHODIMP
360 CookieServiceChild::GetCookieStringFromHttp(nsIURI* /*aHostURI*/,
361 nsIChannel* /*aChannel*/,
362 nsACString& /*aCookieString*/) {
363 return NS_ERROR_NOT_IMPLEMENTED;
366 NS_IMETHODIMP
367 CookieServiceChild::SetCookieStringFromDocument(
368 dom::Document* aDocument, const nsACString& aCookieString) {
369 NS_ENSURE_ARG(aDocument);
371 nsCOMPtr<nsIURI> documentURI;
372 nsAutoCString baseDomain;
373 OriginAttributes attrs;
375 // This function is executed in this context, I don't need to keep objects
376 // alive.
377 auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
378 const OriginAttributes& aAttrs) {
379 return !!CountCookiesFromHashTable(aBaseDomain, aAttrs);
382 RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
383 aDocument, aCookieString, PR_Now(), mTLDService, mThirdPartyUtil,
384 hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
385 if (!cookie) {
386 return NS_OK;
389 bool thirdParty = true;
390 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
391 // in gtests we don't have a window, let's consider those requests as 3rd
392 // party.
393 if (innerWindow) {
394 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
396 if (thirdPartyUtil) {
397 Unused << thirdPartyUtil->IsThirdPartyWindow(
398 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
402 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
403 cookie, aDocument)) {
404 return NS_OK;
407 CookieKey key(baseDomain, attrs);
408 CookiesList* cookies = mCookiesMap.Get(key);
410 if (cookies) {
411 // We need to see if the cookie we're setting would overwrite an httponly
412 // or a secure one. This would not affect anything we send over the net
413 // (those come from the parent, which already checks this),
414 // but script could see an inconsistent view of things.
416 nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
417 bool isPotentiallyTrustworthy =
418 principal->GetIsOriginPotentiallyTrustworthy();
420 for (uint32_t i = 0; i < cookies->Length(); ++i) {
421 RefPtr<Cookie> existingCookie = cookies->ElementAt(i);
422 if (existingCookie->Name().Equals(cookie->Name()) &&
423 existingCookie->Host().Equals(cookie->Host()) &&
424 existingCookie->Path().Equals(cookie->Path())) {
425 // Can't overwrite an httponly cookie from a script context.
426 if (existingCookie->IsHttpOnly()) {
427 return NS_OK;
430 // prevent insecure cookie from overwriting a secure one in insecure
431 // context.
432 if (existingCookie->IsSecure() && !isPotentiallyTrustworthy) {
433 return NS_OK;
439 RecordDocumentCookie(cookie, attrs);
441 if (CanSend()) {
442 nsTArray<CookieStruct> cookiesToSend;
443 cookiesToSend.AppendElement(cookie->ToIPC());
445 // Asynchronously call the parent.
446 dom::WindowGlobalChild* windowGlobalChild =
447 aDocument->GetWindowGlobalChild();
449 // If there is no WindowGlobalChild fall back to PCookieService SetCookies.
450 if (NS_WARN_IF(!windowGlobalChild)) {
451 SendSetCookies(baseDomain, attrs, documentURI, false, cookiesToSend);
452 return NS_OK;
454 windowGlobalChild->SendSetCookies(baseDomain, attrs, documentURI, false,
455 cookiesToSend);
458 return NS_OK;
461 NS_IMETHODIMP
462 CookieServiceChild::SetCookieStringFromHttp(nsIURI* aHostURI,
463 const nsACString& aCookieString,
464 nsIChannel* aChannel) {
465 NS_ENSURE_ARG(aHostURI);
466 NS_ENSURE_ARG(aChannel);
468 if (!CookieCommons::IsSchemeSupported(aHostURI)) {
469 return NS_OK;
472 // Fast past: don't bother sending IPC messages about nullprincipal'd
473 // documents.
474 nsAutoCString scheme;
475 aHostURI->GetScheme(scheme);
476 if (scheme.EqualsLiteral("moz-nullprincipal")) {
477 return NS_OK;
480 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
482 uint32_t rejectedReason = 0;
483 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
484 aChannel, false, aHostURI, RequireThirdPartyCheck, &rejectedReason);
486 nsCString cookieString(aCookieString);
488 OriginAttributes attrs = loadInfo->GetOriginAttributes();
489 StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
490 aChannel, attrs);
492 bool requireHostMatch;
493 nsCString baseDomain;
494 CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
495 requireHostMatch);
497 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
498 CookieCommons::GetCookieJarSettings(aChannel);
500 nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
502 CookieStatus cookieStatus = CookieService::CheckPrefs(
503 crc, cookieJarSettings, aHostURI,
504 result.contains(ThirdPartyAnalysis::IsForeign),
505 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
506 result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
507 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
508 aCookieString, CountCookiesFromHashTable(baseDomain, attrs), attrs,
509 &rejectedReason);
511 if (cookieStatus != STATUS_ACCEPTED &&
512 cookieStatus != STATUS_ACCEPT_SESSION) {
513 return NS_OK;
516 CookieKey key(baseDomain, attrs);
518 nsTArray<CookieStruct> cookiesToSend;
520 int64_t currentTimeInUsec = PR_Now();
522 bool addonAllowsLoad = false;
523 nsCOMPtr<nsIURI> finalChannelURI;
524 NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalChannelURI));
525 addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
526 ->AddonAllowsLoad(finalChannelURI);
528 bool isForeignAndNotAddon = false;
529 if (!addonAllowsLoad) {
530 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
531 &isForeignAndNotAddon);
534 bool mustBePartitioned =
535 isForeignAndNotAddon &&
536 cookieJarSettings->GetCookieBehavior() ==
537 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
538 !result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
540 bool moreCookies;
541 do {
542 CookieStruct cookieData;
543 bool canSetCookie = false;
544 moreCookies = CookieService::CanSetCookie(
545 aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
546 cookieString, true, isForeignAndNotAddon, mustBePartitioned, crc,
547 canSetCookie);
548 if (!canSetCookie) {
549 continue;
552 // check permissions from site permission list.
553 if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
554 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieString,
555 "cookie rejected by permission manager");
556 constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
557 CookieLogging::LogMessageToConsole(
558 crc, aHostURI, nsIScriptError::warningFlag,
559 CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
560 AutoTArray<nsString, 1>{
561 NS_ConvertUTF8toUTF16(cookieData.name()),
563 CookieCommons::NotifyRejected(
564 aHostURI, aChannel,
565 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
566 OPERATION_WRITE);
567 continue;
570 RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
571 MOZ_ASSERT(cookie);
573 cookie->SetLastAccessed(currentTimeInUsec);
574 cookie->SetCreationTime(
575 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
577 RecordDocumentCookie(cookie, attrs);
578 cookiesToSend.AppendElement(cookieData);
579 } while (moreCookies);
581 // Asynchronously call the parent.
582 if (CanSend() && !cookiesToSend.IsEmpty()) {
583 RefPtr<HttpChannelChild> httpChannelChild = do_QueryObject(aChannel);
584 MOZ_ASSERT(httpChannelChild);
585 httpChannelChild->SendSetCookies(baseDomain, attrs, aHostURI, true,
586 cookiesToSend);
589 return NS_OK;
592 NS_IMETHODIMP
593 CookieServiceChild::RunInTransaction(
594 nsICookieTransactionCallback* /*aCallback*/) {
595 return NS_ERROR_NOT_IMPLEMENTED;
598 } // namespace net
599 } // namespace mozilla