Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / netwerk / cookie / CookieCommons.cpp
blob3708f23daa1809838738a3453f6b1761ecf3de29
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/ConsoleReportCollector.h"
11 #include "mozilla/ContentBlockingNotifier.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/StaticPrefs_network.h"
14 #include "mozilla/StorageAccess.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/nsMixedContentBlocker.h"
17 #include "mozilla/net/CookieJarSettings.h"
18 #include "mozIThirdPartyUtil.h"
19 #include "nsContentUtils.h"
20 #include "nsICookiePermission.h"
21 #include "nsICookieService.h"
22 #include "nsIEffectiveTLDService.h"
23 #include "nsIRedirectHistoryEntry.h"
24 #include "nsIWebProgressListener.h"
25 #include "nsNetUtil.h"
26 #include "nsScriptSecurityManager.h"
27 #include "ThirdPartyUtil.h"
29 namespace mozilla {
31 using dom::Document;
33 namespace net {
35 // static
36 bool CookieCommons::DomainMatches(Cookie* aCookie, const nsACString& aHost) {
37 // first, check for an exact host or domain cookie match, e.g. "google.com"
38 // or ".google.com"; second a subdomain match, e.g.
39 // host = "mail.google.com", cookie domain = ".google.com".
40 return aCookie->RawHost() == aHost ||
41 (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
44 // static
45 bool CookieCommons::PathMatches(Cookie* aCookie, const nsACString& aPath) {
46 nsCString cookiePath(aCookie->GetFilePath());
48 // if our cookie path is empty we can't really perform our prefix check, and
49 // also we can't check the last character of the cookie path, so we would
50 // never return a successful match.
51 if (cookiePath.IsEmpty()) {
52 return false;
55 // if the cookie path and the request path are identical, they match.
56 if (cookiePath.Equals(aPath)) {
57 return true;
60 // if the cookie path is a prefix of the request path, and the last character
61 // of the cookie path is %x2F ("/"), they match.
62 bool isPrefix = StringBeginsWith(aPath, cookiePath);
63 if (isPrefix && cookiePath.Last() == '/') {
64 return true;
67 // if the cookie path is a prefix of the request path, and the first character
68 // of the request path that is not included in the cookie path is a %x2F ("/")
69 // character, they match.
70 uint32_t cookiePathLen = cookiePath.Length();
71 return isPrefix && aPath[cookiePathLen] == '/';
74 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
75 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
76 // dot may be present. If aHostURI is an IP address, an alias such as
77 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
78 // be the exact host, and aRequireHostMatch will be true to indicate that
79 // substring matches should not be performed.
80 nsresult CookieCommons::GetBaseDomain(nsIEffectiveTLDService* aTLDService,
81 nsIURI* aHostURI, nsACString& aBaseDomain,
82 bool& aRequireHostMatch) {
83 // get the base domain. this will fail if the host contains a leading dot,
84 // more than one trailing dot, or is otherwise malformed.
85 nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
86 aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
87 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
88 if (aRequireHostMatch) {
89 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
90 // such as 'co.uk', or the empty string. use the host as a key in such
91 // cases.
92 rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, aBaseDomain);
94 NS_ENSURE_SUCCESS(rv, rv);
96 // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
97 if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') {
98 return NS_ERROR_INVALID_ARG;
101 // block any URIs without a host that aren't file:// URIs.
102 if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) {
103 return NS_ERROR_INVALID_ARG;
106 return NS_OK;
109 nsresult CookieCommons::GetBaseDomain(nsIPrincipal* aPrincipal,
110 nsACString& aBaseDomain) {
111 MOZ_ASSERT(aPrincipal);
113 // for historical reasons we use ascii host for file:// URLs.
114 if (aPrincipal->SchemeIs("file")) {
115 return nsContentUtils::GetHostOrIPv6WithBrackets(aPrincipal, aBaseDomain);
118 nsresult rv = aPrincipal->GetBaseDomain(aBaseDomain);
119 if (NS_FAILED(rv)) {
120 return rv;
123 nsContentUtils::MaybeFixIPv6Host(aBaseDomain);
124 return NS_OK;
127 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
128 // "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it
129 // is assumed that aHost is already normalized, and it may contain a leading dot
130 // (indicating that it represents a domain). A trailing dot may be present.
131 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
132 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
133 // leading dot will be treated as an error.
134 nsresult CookieCommons::GetBaseDomainFromHost(
135 nsIEffectiveTLDService* aTLDService, const nsACString& aHost,
136 nsCString& aBaseDomain) {
137 // aHost must not be the string '.'.
138 if (aHost.Length() == 1 && aHost.Last() == '.') {
139 return NS_ERROR_INVALID_ARG;
142 // aHost may contain a leading dot; if so, strip it now.
143 bool domain = !aHost.IsEmpty() && aHost.First() == '.';
145 // get the base domain. this will fail if the host contains a leading dot,
146 // more than one trailing dot, or is otherwise malformed.
147 nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0,
148 aBaseDomain);
149 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
150 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
151 // aHost is either an IP address, an alias such as 'localhost', an eTLD
152 // such as 'co.uk', or the empty string. use the host as a key in such
153 // cases; however, we reject any such hosts with a leading dot, since it
154 // doesn't make sense for them to be domain cookies.
155 if (domain) {
156 return NS_ERROR_INVALID_ARG;
159 aBaseDomain = aHost;
160 return NS_OK;
162 return rv;
165 namespace {
167 void NotifyRejectionToObservers(nsIURI* aHostURI, CookieOperation aOperation) {
168 if (aOperation == OPERATION_WRITE) {
169 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
170 if (os) {
171 os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
173 } else {
174 MOZ_ASSERT(aOperation == OPERATION_READ);
178 } // namespace
180 // Notify observers that a cookie was rejected due to the users' prefs.
181 void CookieCommons::NotifyRejected(nsIURI* aHostURI, nsIChannel* aChannel,
182 uint32_t aRejectedReason,
183 CookieOperation aOperation) {
184 NotifyRejectionToObservers(aHostURI, aOperation);
186 ContentBlockingNotifier::OnDecision(
187 aChannel, ContentBlockingNotifier::BlockingDecision::eBlock,
188 aRejectedReason);
191 bool CookieCommons::CheckPathSize(const CookieStruct& aCookieData) {
192 return aCookieData.path().Length() <= kMaxBytesPerPath;
195 bool CookieCommons::CheckNameAndValueSize(const CookieStruct& aCookieData) {
196 // reject cookie if it's over the size limit, per RFC2109
197 return (aCookieData.name().Length() + aCookieData.value().Length()) <=
198 kMaxBytesPerCookie;
201 bool CookieCommons::CheckName(const CookieStruct& aCookieData) {
202 const char illegalNameCharacters[] = {
203 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
204 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
205 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x3D, 0x7F, 0x00};
207 const auto* start = aCookieData.name().BeginReading();
208 const auto* end = aCookieData.name().EndReading();
210 auto charFilter = [&](unsigned char c) {
211 if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) {
212 return true;
214 return std::find(std::begin(illegalNameCharacters),
215 std::end(illegalNameCharacters),
216 c) != std::end(illegalNameCharacters);
219 return std::find_if(start, end, charFilter) == end;
222 bool CookieCommons::CheckValue(const CookieStruct& aCookieData) {
223 // reject cookie if value contains an RFC 6265 disallowed character - see
224 // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
225 // NOTE: this is not the full set of characters disallowed by 6265 - notably
226 // 0x09, 0x20, 0x22, 0x2C, and 0x5C are missing from this list.
227 const char illegalCharacters[] = {
228 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C,
229 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
230 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x7F, 0x00};
232 const auto* start = aCookieData.value().BeginReading();
233 const auto* end = aCookieData.value().EndReading();
235 auto charFilter = [&](unsigned char c) {
236 if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) {
237 return true;
239 return std::find(std::begin(illegalCharacters), std::end(illegalCharacters),
240 c) != std::end(illegalCharacters);
243 return std::find_if(start, end, charFilter) == end;
246 // static
247 bool CookieCommons::CheckCookiePermission(nsIChannel* aChannel,
248 CookieStruct& aCookieData) {
249 if (!aChannel) {
250 // No channel, let's assume this is a system-principal request.
251 return true;
254 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
255 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
256 nsresult rv =
257 loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
258 if (NS_WARN_IF(NS_FAILED(rv))) {
259 return true;
262 nsIScriptSecurityManager* ssm =
263 nsScriptSecurityManager::GetScriptSecurityManager();
264 MOZ_ASSERT(ssm);
266 nsCOMPtr<nsIPrincipal> channelPrincipal;
267 rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(channelPrincipal));
268 if (NS_WARN_IF(NS_FAILED(rv))) {
269 return false;
272 return CheckCookiePermission(channelPrincipal, cookieJarSettings,
273 aCookieData);
276 // static
277 bool CookieCommons::CheckCookiePermission(
278 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
279 CookieStruct& aCookieData) {
280 MOZ_ASSERT(aPrincipal);
281 MOZ_ASSERT(aCookieJarSettings);
283 if (!aPrincipal->GetIsContentPrincipal()) {
284 return true;
287 uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
288 nsresult rv =
289 aCookieJarSettings->CookiePermission(aPrincipal, &cookiePermission);
290 if (NS_WARN_IF(NS_FAILED(rv))) {
291 return true;
294 if (cookiePermission == nsICookiePermission::ACCESS_ALLOW) {
295 return true;
298 if (cookiePermission == nsICookiePermission::ACCESS_SESSION) {
299 aCookieData.isSession() = true;
300 return true;
303 if (cookiePermission == nsICookiePermission::ACCESS_DENY) {
304 return false;
307 return true;
310 namespace {
312 CookieStatus CookieStatusForWindow(nsPIDOMWindowInner* aWindow,
313 nsIURI* aDocumentURI) {
314 MOZ_ASSERT(aWindow);
315 MOZ_ASSERT(aDocumentURI);
317 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
318 if (thirdPartyUtil) {
319 bool isThirdParty = true;
321 nsresult rv = thirdPartyUtil->IsThirdPartyWindow(
322 aWindow->GetOuterWindow(), aDocumentURI, &isThirdParty);
323 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Third-party window check failed.");
325 if (NS_SUCCEEDED(rv) && !isThirdParty) {
326 return STATUS_ACCEPTED;
330 if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) {
331 return STATUS_ACCEPT_SESSION;
334 if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly() &&
335 !nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aDocumentURI)) {
336 return STATUS_ACCEPT_SESSION;
339 return STATUS_ACCEPTED;
342 } // namespace
344 // static
345 already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument(
346 Document* aDocument, const nsACString& aCookieString,
347 int64_t currentTimeInUsec, nsIEffectiveTLDService* aTLDService,
348 mozIThirdPartyUtil* aThirdPartyUtil,
349 std::function<bool(const nsACString&, const OriginAttributes&)>&&
350 aHasExistingCookiesLambda,
351 nsIURI** aDocumentURI, nsACString& aBaseDomain, OriginAttributes& aAttrs) {
352 nsCOMPtr<nsIPrincipal> storagePrincipal =
353 aDocument->EffectiveCookiePrincipal();
354 MOZ_ASSERT(storagePrincipal);
356 nsCOMPtr<nsIURI> principalURI;
357 auto* basePrincipal = BasePrincipal::Cast(aDocument->NodePrincipal());
358 basePrincipal->GetURI(getter_AddRefs(principalURI));
359 if (NS_WARN_IF(!principalURI)) {
360 // Document's principal is not a content or null (may be system), so
361 // can't set cookies
362 return nullptr;
365 if (!CookieCommons::IsSchemeSupported(principalURI)) {
366 return nullptr;
369 nsAutoCString baseDomain;
370 bool requireHostMatch = false;
371 nsresult rv = CookieCommons::GetBaseDomain(aTLDService, principalURI,
372 baseDomain, requireHostMatch);
373 if (NS_WARN_IF(NS_FAILED(rv))) {
374 return nullptr;
377 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
378 if (NS_WARN_IF(!innerWindow)) {
379 return nullptr;
382 // Check if limit-foreign is required.
383 uint32_t dummyRejectedReason = 0;
384 if (aDocument->CookieJarSettings()->GetLimitForeignContexts() &&
385 !aHasExistingCookiesLambda(baseDomain,
386 storagePrincipal->OriginAttributesRef()) &&
387 !ShouldAllowAccessFor(innerWindow, principalURI, &dummyRejectedReason)) {
388 return nullptr;
391 bool isForeignAndNotAddon = false;
392 if (!BasePrincipal::Cast(aDocument->NodePrincipal())->AddonPolicy()) {
393 rv = aThirdPartyUtil->IsThirdPartyWindow(
394 innerWindow->GetOuterWindow(), principalURI, &isForeignAndNotAddon);
395 if (NS_WARN_IF(NS_FAILED(rv))) {
396 isForeignAndNotAddon = true;
400 bool mustBePartitioned =
401 isForeignAndNotAddon &&
402 aDocument->CookieJarSettings()->GetCookieBehavior() ==
403 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
404 !aDocument->UsingStorageAccess();
406 // If we are here, we have been already accepted by the anti-tracking.
407 // We just need to check if we have to be in session-only mode.
408 CookieStatus cookieStatus = CookieStatusForWindow(innerWindow, principalURI);
409 MOZ_ASSERT(cookieStatus == STATUS_ACCEPTED ||
410 cookieStatus == STATUS_ACCEPT_SESSION);
412 // Console report takes care of the correct reporting at the exit of this
413 // method.
414 RefPtr<ConsoleReportCollector> crc = new ConsoleReportCollector();
415 auto scopeExit = MakeScopeExit([&] { crc->FlushConsoleReports(aDocument); });
417 nsCString cookieString(aCookieString);
419 CookieStruct cookieData;
420 MOZ_ASSERT(cookieData.creationTime() == 0, "Must be initialized to 0");
421 bool canSetCookie = false;
422 CookieService::CanSetCookie(principalURI, baseDomain, cookieData,
423 requireHostMatch, cookieStatus, cookieString,
424 false, isForeignAndNotAddon, mustBePartitioned,
425 crc, canSetCookie);
427 if (!canSetCookie) {
428 return nullptr;
431 // check permissions from site permission list.
432 if (!CookieCommons::CheckCookiePermission(aDocument->NodePrincipal(),
433 aDocument->CookieJarSettings(),
434 cookieData)) {
435 NotifyRejectionToObservers(principalURI, OPERATION_WRITE);
436 ContentBlockingNotifier::OnDecision(
437 innerWindow, ContentBlockingNotifier::BlockingDecision::eBlock,
438 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION);
439 return nullptr;
442 RefPtr<Cookie> cookie =
443 Cookie::Create(cookieData, storagePrincipal->OriginAttributesRef());
444 MOZ_ASSERT(cookie);
446 cookie->SetLastAccessed(currentTimeInUsec);
447 cookie->SetCreationTime(
448 Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
450 aBaseDomain = baseDomain;
451 aAttrs = storagePrincipal->OriginAttributesRef();
452 principalURI.forget(aDocumentURI);
454 return cookie.forget();
457 // static
458 already_AddRefed<nsICookieJarSettings> CookieCommons::GetCookieJarSettings(
459 nsIChannel* aChannel) {
460 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
461 bool shouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
462 aChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
463 if (aChannel) {
464 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
465 nsresult rv =
466 loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
467 if (NS_WARN_IF(NS_FAILED(rv))) {
468 cookieJarSettings =
469 CookieJarSettings::GetBlockingAll(shouldResistFingerprinting);
471 } else {
472 cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular,
473 shouldResistFingerprinting);
476 MOZ_ASSERT(cookieJarSettings);
477 return cookieJarSettings.forget();
480 // static
481 bool CookieCommons::ShouldIncludeCrossSiteCookieForDocument(
482 Cookie* aCookie, dom::Document* aDocument) {
483 MOZ_ASSERT(aCookie);
484 MOZ_ASSERT(aDocument);
486 int32_t sameSiteAttr = 0;
487 aCookie->GetSameSite(&sameSiteAttr);
489 if (aDocument->CookieJarSettings()->GetPartitionForeign() &&
490 StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
491 return false;
494 return sameSiteAttr == nsICookie::SAMESITE_NONE;
497 bool CookieCommons::IsSafeTopLevelNav(nsIChannel* aChannel) {
498 if (!aChannel) {
499 return false;
501 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
502 nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo();
503 if ((loadInfo->GetExternalContentPolicyType() !=
504 ExtContentPolicy::TYPE_DOCUMENT &&
505 loadInfo->GetExternalContentPolicyType() !=
506 ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) &&
507 !interceptionInfo) {
508 return false;
511 if (interceptionInfo &&
512 interceptionInfo->GetExtContentPolicyType() !=
513 ExtContentPolicy::TYPE_DOCUMENT &&
514 interceptionInfo->GetExtContentPolicyType() !=
515 ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD &&
516 interceptionInfo->GetExtContentPolicyType() !=
517 ExtContentPolicy::TYPE_INVALID) {
518 return false;
521 return NS_IsSafeMethodNav(aChannel);
524 // This function determines if two schemes are equal in the context of
525 // "Schemeful SameSite cookies".
527 // Two schemes are considered equal:
528 // - if the "network.cookie.sameSite.schemeful" pref is set to false.
529 // OR
530 // - if one of the schemes is not http or https.
531 // OR
532 // - if both schemes are equal AND both are either http or https.
533 bool IsSameSiteSchemeEqual(const nsACString& aFirstScheme,
534 const nsACString& aSecondScheme) {
535 if (!StaticPrefs::network_cookie_sameSite_schemeful()) {
536 return true;
539 auto isSchemeHttpOrHttps = [](const nsACString& scheme) -> bool {
540 return scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https");
543 if (!isSchemeHttpOrHttps(aFirstScheme) ||
544 !isSchemeHttpOrHttps(aSecondScheme)) {
545 return true;
548 return aFirstScheme.Equals(aSecondScheme);
551 bool CookieCommons::IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI,
552 bool* aHadCrossSiteRedirects) {
553 *aHadCrossSiteRedirects = false;
555 if (!aChannel) {
556 return false;
558 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
559 // Do not treat loads triggered by web extensions as foreign
560 nsCOMPtr<nsIURI> channelURI;
561 NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
563 nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo();
565 RefPtr<BasePrincipal> triggeringPrincipal;
566 ExtContentPolicy contentPolicyType;
567 if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) {
568 triggeringPrincipal =
569 BasePrincipal::Cast(interceptionInfo->TriggeringPrincipal());
570 contentPolicyType = interceptionInfo->GetExtContentPolicyType();
571 } else {
572 triggeringPrincipal = BasePrincipal::Cast(loadInfo->TriggeringPrincipal());
573 contentPolicyType = loadInfo->GetExternalContentPolicyType();
575 if (triggeringPrincipal->AddonPolicy() &&
576 triggeringPrincipal->AddonAllowsLoad(channelURI)) {
577 return false;
580 const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& redirectChain(
581 interceptionInfo && interceptionInfo->TriggeringPrincipal()
582 ? interceptionInfo->RedirectChain()
583 : loadInfo->RedirectChain());
585 nsAutoCString hostScheme, otherScheme;
586 aHostURI->GetScheme(hostScheme);
588 bool isForeign = true;
589 nsresult rv;
590 if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
591 contentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
592 // for loads of TYPE_DOCUMENT we query the hostURI from the
593 // triggeringPrincipal which returns the URI of the document that caused the
594 // navigation.
595 rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
597 triggeringPrincipal->GetScheme(otherScheme);
598 } else {
599 // If the load is caused by FetchEvent.request or NavigationPreload request,
600 // check the original InterceptedHttpChannel is a third-party channel or
601 // not.
602 if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) {
603 isForeign = interceptionInfo->FromThirdParty();
604 if (isForeign) {
605 return true;
608 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
609 do_GetService(THIRDPARTYUTIL_CONTRACTID);
610 if (!thirdPartyUtil) {
611 return true;
613 rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
615 channelURI->GetScheme(otherScheme);
617 // if we are dealing with a cross origin request, we can return here
618 // because we already know the request is 'foreign'.
619 if (NS_FAILED(rv) || isForeign) {
620 return true;
623 if (!IsSameSiteSchemeEqual(otherScheme, hostScheme)) {
624 // If the two schemes are not of the same http(s) scheme then we
625 // consider the request as foreign.
626 return true;
629 // for loads of TYPE_SUBDOCUMENT we have to perform an additional test,
630 // because a cross-origin iframe might perform a navigation to a same-origin
631 // iframe which would send same-site cookies. Hence, if the iframe navigation
632 // was triggered by a cross-origin triggeringPrincipal, we treat the load as
633 // foreign.
634 if (contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
635 rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
636 if (NS_FAILED(rv) || isForeign) {
637 return true;
641 // for the purpose of same-site cookies we have to treat any cross-origin
642 // redirects as foreign. E.g. cross-site to same-site redirect is a problem
643 // with regards to CSRF.
645 nsCOMPtr<nsIPrincipal> redirectPrincipal;
646 for (nsIRedirectHistoryEntry* entry : redirectChain) {
647 entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
648 if (redirectPrincipal) {
649 rv = redirectPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
650 // if at any point we encounter a cross-origin redirect we can return.
651 if (NS_FAILED(rv) || isForeign) {
652 *aHadCrossSiteRedirects = isForeign;
653 return true;
656 nsAutoCString redirectScheme;
657 redirectPrincipal->GetScheme(redirectScheme);
658 if (!IsSameSiteSchemeEqual(redirectScheme, hostScheme)) {
659 // If the two schemes are not of the same http(s) scheme then we
660 // consider the request as foreign.
661 *aHadCrossSiteRedirects = true;
662 return true;
666 return isForeign;
669 // static
670 nsICookie::schemeType CookieCommons::URIToSchemeType(nsIURI* aURI) {
671 MOZ_ASSERT(aURI);
673 nsAutoCString scheme;
674 nsresult rv = aURI->GetScheme(scheme);
675 if (NS_WARN_IF(NS_FAILED(rv))) {
676 return nsICookie::SCHEME_UNSET;
679 return SchemeToSchemeType(scheme);
682 // static
683 nsICookie::schemeType CookieCommons::PrincipalToSchemeType(
684 nsIPrincipal* aPrincipal) {
685 MOZ_ASSERT(aPrincipal);
687 nsAutoCString scheme;
688 nsresult rv = aPrincipal->GetScheme(scheme);
689 if (NS_WARN_IF(NS_FAILED(rv))) {
690 return nsICookie::SCHEME_UNSET;
693 return SchemeToSchemeType(scheme);
696 // static
697 nsICookie::schemeType CookieCommons::SchemeToSchemeType(
698 const nsACString& aScheme) {
699 MOZ_ASSERT(IsSchemeSupported(aScheme));
701 if (aScheme.Equals("https")) {
702 return nsICookie::SCHEME_HTTPS;
705 if (aScheme.Equals("http")) {
706 return nsICookie::SCHEME_HTTP;
709 if (aScheme.Equals("file")) {
710 return nsICookie::SCHEME_FILE;
713 MOZ_CRASH("Unsupported scheme type");
716 // static
717 bool CookieCommons::IsSchemeSupported(nsIPrincipal* aPrincipal) {
718 MOZ_ASSERT(aPrincipal);
720 nsAutoCString scheme;
721 nsresult rv = aPrincipal->GetScheme(scheme);
722 if (NS_WARN_IF(NS_FAILED(rv))) {
723 return false;
726 return IsSchemeSupported(scheme);
729 // static
730 bool CookieCommons::IsSchemeSupported(nsIURI* aURI) {
731 MOZ_ASSERT(aURI);
733 nsAutoCString scheme;
734 nsresult rv = aURI->GetScheme(scheme);
735 if (NS_WARN_IF(NS_FAILED(rv))) {
736 return false;
739 return IsSchemeSupported(scheme);
742 // static
743 bool CookieCommons::IsSchemeSupported(const nsACString& aScheme) {
744 return aScheme.Equals("https") || aScheme.Equals("http") ||
745 aScheme.Equals("file");
748 } // namespace net
749 } // namespace mozilla