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/. */
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"
23 #include "nsNetUtil.h"
24 #include "nsICookieJarSettings.h"
25 #include "nsIChannel.h"
26 #include "nsIClassifiedChannel.h"
27 #include "nsIHttpChannel.h"
28 #include "nsIEffectiveTLDService.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
;
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()) {
70 // This corresponds to Release() in DeallocPCookieService.
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
) {
89 uint32_t rejectedReason
= 0;
90 ThirdPartyAnalysisResult result
= mThirdPartyUtil
->AnalyzeChannel(
91 aChannel
, true, nullptr, RequireThirdPartyCheck
, &rejectedReason
);
94 aChannel
->GetURI(getter_AddRefs(uri
));
95 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
97 OriginAttributes attrs
= loadInfo
->GetOriginAttributes();
98 StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
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() {
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
);
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
);
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();
152 obsService
->NotifyObservers(nullptr, "cookie-content-filter-test", nullptr);
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
));
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
);
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
) {
197 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
;
199 aLoadInfo
->GetCookieJarSettings(getter_AddRefs(cookieJarSettings
));
200 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
||
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(),
220 CookieKey
key(baseDomain
, aAttrs
);
221 CookiesList
* cookiesList
= nullptr;
222 mCookiesMap
.Get(key
, &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());
242 cookiesList
->RemoveElementAt(i
);
247 int64_t currentTime
= PR_Now() / PR_USEC_PER_SEC
;
248 if (aCookie
->Expiry() <= currentTime
) {
252 cookiesList
->AppendElement(aCookie
);
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
)) {
268 nsAutoCString baseDomain
;
269 nsresult rv
= CookieCommons::GetBaseDomain(principal
, baseDomain
);
270 if (NS_WARN_IF(NS_FAILED(rv
))) {
274 CookieKey
key(baseDomain
, principal
->OriginAttributesRef());
275 CookiesList
* cookiesList
= nullptr;
276 mCookiesMap
.Get(key
, &cookiesList
);
282 nsAutoCString hostFromURI
;
283 rv
= nsContentUtils::GetHostOrIPv6WithBrackets(principal
, hostFromURI
);
284 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
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
)) {
317 // We don't show HttpOnly cookies in content processes.
318 if (cookie
->IsHttpOnly()) {
322 if (thirdParty
&& !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
323 cookie
, aDocument
)) {
327 // do not display the cookie if it is secure and the host scheme isn't
328 if (cookie
->IsSecure() && !isPotentiallyTrustworthy
) {
332 // if the nsIURI path doesn't match the cookie path, don't send it back
333 if (!CookieCommons::PathMatches(cookie
, pathFromURI
)) {
337 // check if the cookie has expired
338 if (cookie
->Expiry() <= currentTime
) {
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());
351 aCookieString
.Append(cookie
->Value().get());
360 CookieServiceChild::GetCookieStringFromHttp(nsIURI
* /*aHostURI*/,
361 nsIChannel
* /*aChannel*/,
362 nsACString
& /*aCookieString*/) {
363 return NS_ERROR_NOT_IMPLEMENTED
;
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
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
);
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
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
)) {
407 CookieKey
key(baseDomain
, attrs
);
408 CookiesList
* cookies
= mCookiesMap
.Get(key
);
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()) {
430 // prevent insecure cookie from overwriting a secure one in insecure
432 if (existingCookie
->IsSecure() && !isPotentiallyTrustworthy
) {
439 RecordDocumentCookie(cookie
, attrs
);
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
);
454 windowGlobalChild
->SendSetCookies(baseDomain
, attrs
, documentURI
, false,
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
)) {
472 // Fast past: don't bother sending IPC messages about nullprincipal'd
474 nsAutoCString scheme
;
475 aHostURI
->GetScheme(scheme
);
476 if (scheme
.EqualsLiteral("moz-nullprincipal")) {
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(
492 bool requireHostMatch
;
493 nsCString baseDomain
;
494 CookieCommons::GetBaseDomain(mTLDService
, aHostURI
, baseDomain
,
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
,
511 if (cookieStatus
!= STATUS_ACCEPTED
&&
512 cookieStatus
!= STATUS_ACCEPT_SESSION
) {
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
);
536 CookieStruct cookieData
;
537 bool canSetCookie
= false;
538 moreCookies
= CookieService::CanSetCookie(
539 aHostURI
, baseDomain
, cookieData
, requireHostMatch
, cookieStatus
,
540 cookieString
, true, isForeignAndNotAddon
, crc
, canSetCookie
);
545 // check permissions from site permission list.
546 if (!CookieCommons::CheckCookiePermission(aChannel
, cookieData
)) {
547 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieString
,
548 "cookie rejected by permission manager");
549 constexpr auto CONSOLE_REJECTION_CATEGORY
= "cookiesRejection"_ns
;
550 CookieLogging::LogMessageToConsole(
551 crc
, aHostURI
, nsIScriptError::warningFlag
,
552 CONSOLE_REJECTION_CATEGORY
, "CookieRejectedByPermissionManager"_ns
,
553 AutoTArray
<nsString
, 1>{
554 NS_ConvertUTF8toUTF16(cookieData
.name()),
556 CookieCommons::NotifyRejected(
558 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
,
563 RefPtr
<Cookie
> cookie
= Cookie::Create(cookieData
, attrs
);
566 cookie
->SetLastAccessed(currentTimeInUsec
);
567 cookie
->SetCreationTime(
568 Cookie::GenerateUniqueCreationTime(currentTimeInUsec
));
570 RecordDocumentCookie(cookie
, attrs
);
571 cookiesToSend
.AppendElement(cookieData
);
572 } while (moreCookies
);
574 // Asynchronously call the parent.
575 if (CanSend() && !cookiesToSend
.IsEmpty()) {
576 RefPtr
<HttpChannelChild
> httpChannelChild
= do_QueryObject(aChannel
);
577 MOZ_ASSERT(httpChannelChild
);
578 httpChannelChild
->SendSetCookies(baseDomain
, attrs
, aHostURI
, true,
586 CookieServiceChild::RunInTransaction(
587 nsICookieTransactionCallback
* /*aCallback*/) {
588 return NS_ERROR_NOT_IMPLEMENTED
;
592 } // namespace mozilla