diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/cookie/CookieCommons.cpp | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/netwerk/cookie/CookieCommons.cpp b/netwerk/cookie/CookieCommons.cpp new file mode 100644 index 0000000000..52b61c4e95 --- /dev/null +++ b/netwerk/cookie/CookieCommons.cpp @@ -0,0 +1,735 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Cookie.h" +#include "CookieCommons.h" +#include "CookieLogging.h" +#include "CookieService.h" +#include "mozilla/ConsoleReportCollector.h" +#include "mozilla/ContentBlockingNotifier.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/nsMixedContentBlocker.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozIThirdPartyUtil.h" +#include "nsContentUtils.h" +#include "nsICookiePermission.h" +#include "nsICookieService.h" +#include "nsIEffectiveTLDService.h" +#include "nsIRedirectHistoryEntry.h" +#include "nsIWebProgressListener.h" +#include "nsNetUtil.h" +#include "nsScriptSecurityManager.h" +#include "ThirdPartyUtil.h" + +namespace mozilla { + +using dom::Document; + +namespace net { + +// static +bool CookieCommons::DomainMatches(Cookie* aCookie, const nsACString& aHost) { + // first, check for an exact host or domain cookie match, e.g. "google.com" + // or ".google.com"; second a subdomain match, e.g. + // host = "mail.google.com", cookie domain = ".google.com". + return aCookie->RawHost() == aHost || + (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host())); +} + +// static +bool CookieCommons::PathMatches(Cookie* aCookie, const nsACString& aPath) { + nsCString cookiePath(aCookie->GetFilePath()); + + // if our cookie path is empty we can't really perform our prefix check, and + // also we can't check the last character of the cookie path, so we would + // never return a successful match. + if (cookiePath.IsEmpty()) { + return false; + } + + // if the cookie path and the request path are identical, they match. + if (cookiePath.Equals(aPath)) { + return true; + } + + // if the cookie path is a prefix of the request path, and the last character + // of the cookie path is %x2F ("/"), they match. + bool isPrefix = StringBeginsWith(aPath, cookiePath); + if (isPrefix && cookiePath.Last() == '/') { + return true; + } + + // if the cookie path is a prefix of the request path, and the first character + // of the request path that is not included in the cookie path is a %x2F ("/") + // character, they match. + uint32_t cookiePathLen = cookiePath.Length(); + return isPrefix && aPath[cookiePathLen] == '/'; +} + +// Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be +// "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing +// dot may be present. If aHostURI is an IP address, an alias such as +// 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will +// be the exact host, and aRequireHostMatch will be true to indicate that +// substring matches should not be performed. +nsresult CookieCommons::GetBaseDomain(nsIEffectiveTLDService* aTLDService, + nsIURI* aHostURI, nsACString& aBaseDomain, + bool& aRequireHostMatch) { + // get the base domain. this will fail if the host contains a leading dot, + // more than one trailing dot, or is otherwise malformed. + nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain); + aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS || + rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS; + if (aRequireHostMatch) { + // aHostURI is either an IP address, an alias such as 'localhost', an eTLD + // such as 'co.uk', or the empty string. use the host as a key in such + // cases. + rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, aBaseDomain); + } + NS_ENSURE_SUCCESS(rv, rv); + + // aHost (and thus aBaseDomain) may be the string '.'. If so, fail. + if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') { + return NS_ERROR_INVALID_ARG; + } + + // block any URIs without a host that aren't file:// URIs. + if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) { + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +nsresult CookieCommons::GetBaseDomain(nsIPrincipal* aPrincipal, + nsACString& aBaseDomain) { + MOZ_ASSERT(aPrincipal); + + // for historical reasons we use ascii host for file:// URLs. + if (aPrincipal->SchemeIs("file")) { + return nsContentUtils::GetHostOrIPv6WithBrackets(aPrincipal, aBaseDomain); + } + + nsresult rv = aPrincipal->GetBaseDomain(aBaseDomain); + if (NS_FAILED(rv)) { + return rv; + } + + nsContentUtils::MaybeFixIPv6Host(aBaseDomain); + return NS_OK; +} + +// Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be +// "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it +// is assumed that aHost is already normalized, and it may contain a leading dot +// (indicating that it represents a domain). A trailing dot may be present. +// If aHost is an IP address, an alias such as 'localhost', an eTLD such as +// 'co.uk', or the empty string, aBaseDomain will be the exact host, and a +// leading dot will be treated as an error. +nsresult CookieCommons::GetBaseDomainFromHost( + nsIEffectiveTLDService* aTLDService, const nsACString& aHost, + nsCString& aBaseDomain) { + // aHost must not be the string '.'. + if (aHost.Length() == 1 && aHost.Last() == '.') { + return NS_ERROR_INVALID_ARG; + } + + // aHost may contain a leading dot; if so, strip it now. + bool domain = !aHost.IsEmpty() && aHost.First() == '.'; + + // get the base domain. this will fail if the host contains a leading dot, + // more than one trailing dot, or is otherwise malformed. + nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, + aBaseDomain); + if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || + rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + // aHost is either an IP address, an alias such as 'localhost', an eTLD + // such as 'co.uk', or the empty string. use the host as a key in such + // cases; however, we reject any such hosts with a leading dot, since it + // doesn't make sense for them to be domain cookies. + if (domain) { + return NS_ERROR_INVALID_ARG; + } + + aBaseDomain = aHost; + return NS_OK; + } + return rv; +} + +namespace { + +void NotifyRejectionToObservers(nsIURI* aHostURI, CookieOperation aOperation) { + if (aOperation == OPERATION_WRITE) { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->NotifyObservers(aHostURI, "cookie-rejected", nullptr); + } + } else { + MOZ_ASSERT(aOperation == OPERATION_READ); + } +} + +} // namespace + +// Notify observers that a cookie was rejected due to the users' prefs. +void CookieCommons::NotifyRejected(nsIURI* aHostURI, nsIChannel* aChannel, + uint32_t aRejectedReason, + CookieOperation aOperation) { + NotifyRejectionToObservers(aHostURI, aOperation); + + ContentBlockingNotifier::OnDecision( + aChannel, ContentBlockingNotifier::BlockingDecision::eBlock, + aRejectedReason); +} + +bool CookieCommons::CheckPathSize(const CookieStruct& aCookieData) { + return aCookieData.path().Length() <= kMaxBytesPerPath; +} + +bool CookieCommons::CheckNameAndValueSize(const CookieStruct& aCookieData) { + // reject cookie if it's over the size limit, per RFC2109 + return (aCookieData.name().Length() + aCookieData.value().Length()) <= + kMaxBytesPerCookie; +} + +bool CookieCommons::CheckName(const CookieStruct& aCookieData) { + const char illegalNameCharacters[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x00}; + + const auto* start = aCookieData.name().BeginReading(); + const auto* end = aCookieData.name().EndReading(); + + auto charFilter = [&](unsigned char c) { + if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) { + return true; + } + return std::find(std::begin(illegalNameCharacters), + std::end(illegalNameCharacters), + c) != std::end(illegalNameCharacters); + }; + + return std::find_if(start, end, charFilter) == end; +} + +bool CookieCommons::CheckValue(const CookieStruct& aCookieData) { + // reject cookie if value contains an RFC 6265 disallowed character - see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423 + // NOTE: this is not the full set of characters disallowed by 6265 - notably + // 0x09, 0x20, 0x22, 0x2C, and 0x5C are missing from this list. + const char illegalCharacters[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x7F, 0x00}; + + const auto* start = aCookieData.value().BeginReading(); + const auto* end = aCookieData.value().EndReading(); + + auto charFilter = [&](unsigned char c) { + if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) { + return true; + } + return std::find(std::begin(illegalCharacters), std::end(illegalCharacters), + c) != std::end(illegalCharacters); + }; + + return std::find_if(start, end, charFilter) == end; +} + +// static +bool CookieCommons::CheckCookiePermission(nsIChannel* aChannel, + CookieStruct& aCookieData) { + if (!aChannel) { + // No channel, let's assume this is a system-principal request. + return true; + } + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + nsresult rv = + loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return true; + } + + nsIScriptSecurityManager* ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + MOZ_ASSERT(ssm); + + nsCOMPtr<nsIPrincipal> channelPrincipal; + rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return CheckCookiePermission(channelPrincipal, cookieJarSettings, + aCookieData); +} + +// static +bool CookieCommons::CheckCookiePermission( + nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings, + CookieStruct& aCookieData) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCookieJarSettings); + + if (!aPrincipal->GetIsContentPrincipal()) { + return true; + } + + uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT; + nsresult rv = + aCookieJarSettings->CookiePermission(aPrincipal, &cookiePermission); + if (NS_WARN_IF(NS_FAILED(rv))) { + return true; + } + + if (cookiePermission == nsICookiePermission::ACCESS_ALLOW) { + return true; + } + + if (cookiePermission == nsICookiePermission::ACCESS_SESSION) { + aCookieData.isSession() = true; + return true; + } + + if (cookiePermission == nsICookiePermission::ACCESS_DENY) { + return false; + } + + return true; +} + +namespace { + +CookieStatus CookieStatusForWindow(nsPIDOMWindowInner* aWindow, + nsIURI* aDocumentURI) { + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aDocumentURI); + + ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + if (thirdPartyUtil) { + bool isThirdParty = true; + + nsresult rv = thirdPartyUtil->IsThirdPartyWindow( + aWindow->GetOuterWindow(), aDocumentURI, &isThirdParty); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Third-party window check failed."); + + if (NS_SUCCEEDED(rv) && !isThirdParty) { + return STATUS_ACCEPTED; + } + } + + if (StaticPrefs::network_cookie_thirdparty_sessionOnly()) { + return STATUS_ACCEPT_SESSION; + } + + if (StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly() && + !nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aDocumentURI)) { + return STATUS_ACCEPT_SESSION; + } + + return STATUS_ACCEPTED; +} + +} // namespace + +// static +already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument( + Document* aDocument, const nsACString& aCookieString, + int64_t currentTimeInUsec, nsIEffectiveTLDService* aTLDService, + mozIThirdPartyUtil* aThirdPartyUtil, + std::function<bool(const nsACString&, const OriginAttributes&)>&& + aHasExistingCookiesLambda, + nsIURI** aDocumentURI, nsACString& aBaseDomain, OriginAttributes& aAttrs) { + nsCOMPtr<nsIPrincipal> storagePrincipal = + aDocument->EffectiveCookiePrincipal(); + MOZ_ASSERT(storagePrincipal); + + nsCOMPtr<nsIURI> principalURI; + auto* basePrincipal = BasePrincipal::Cast(aDocument->NodePrincipal()); + basePrincipal->GetURI(getter_AddRefs(principalURI)); + if (NS_WARN_IF(!principalURI)) { + // Document's principal is not a content or null (may be system), so + // can't set cookies + return nullptr; + } + + if (!CookieCommons::IsSchemeSupported(principalURI)) { + return nullptr; + } + + nsAutoCString baseDomain; + bool requireHostMatch = false; + nsresult rv = CookieCommons::GetBaseDomain(aTLDService, principalURI, + baseDomain, requireHostMatch); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow(); + if (NS_WARN_IF(!innerWindow)) { + return nullptr; + } + + // Check if limit-foreign is required. + uint32_t dummyRejectedReason = 0; + if (aDocument->CookieJarSettings()->GetLimitForeignContexts() && + !aHasExistingCookiesLambda(baseDomain, + storagePrincipal->OriginAttributesRef()) && + !ShouldAllowAccessFor(innerWindow, principalURI, &dummyRejectedReason)) { + return nullptr; + } + + bool isForeignAndNotAddon = false; + if (!BasePrincipal::Cast(aDocument->NodePrincipal())->AddonPolicy()) { + rv = aThirdPartyUtil->IsThirdPartyWindow( + innerWindow->GetOuterWindow(), principalURI, &isForeignAndNotAddon); + if (NS_WARN_IF(NS_FAILED(rv))) { + isForeignAndNotAddon = true; + } + } + + // If we are here, we have been already accepted by the anti-tracking. + // We just need to check if we have to be in session-only mode. + CookieStatus cookieStatus = CookieStatusForWindow(innerWindow, principalURI); + MOZ_ASSERT(cookieStatus == STATUS_ACCEPTED || + cookieStatus == STATUS_ACCEPT_SESSION); + + // Console report takes care of the correct reporting at the exit of this + // method. + RefPtr<ConsoleReportCollector> crc = new ConsoleReportCollector(); + auto scopeExit = MakeScopeExit([&] { crc->FlushConsoleReports(aDocument); }); + + nsCString cookieString(aCookieString); + + CookieStruct cookieData; + MOZ_ASSERT(cookieData.creationTime() == 0, "Must be initialized to 0"); + bool canSetCookie = false; + CookieService::CanSetCookie(principalURI, baseDomain, cookieData, + requireHostMatch, cookieStatus, cookieString, + false, isForeignAndNotAddon, crc, canSetCookie); + + if (!canSetCookie) { + return nullptr; + } + + // check permissions from site permission list. + if (!CookieCommons::CheckCookiePermission(aDocument->NodePrincipal(), + aDocument->CookieJarSettings(), + cookieData)) { + NotifyRejectionToObservers(principalURI, OPERATION_WRITE); + ContentBlockingNotifier::OnDecision( + innerWindow, ContentBlockingNotifier::BlockingDecision::eBlock, + nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION); + return nullptr; + } + + RefPtr<Cookie> cookie = + Cookie::Create(cookieData, storagePrincipal->OriginAttributesRef()); + MOZ_ASSERT(cookie); + + cookie->SetLastAccessed(currentTimeInUsec); + cookie->SetCreationTime( + Cookie::GenerateUniqueCreationTime(currentTimeInUsec)); + + aBaseDomain = baseDomain; + aAttrs = storagePrincipal->OriginAttributesRef(); + principalURI.forget(aDocumentURI); + + return cookie.forget(); +} + +// static +already_AddRefed<nsICookieJarSettings> CookieCommons::GetCookieJarSettings( + nsIChannel* aChannel) { + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting(aChannel); + if (aChannel) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + nsresult rv = + loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + cookieJarSettings = + CookieJarSettings::GetBlockingAll(shouldResistFingerprinting); + } + } else { + cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + + MOZ_ASSERT(cookieJarSettings); + return cookieJarSettings.forget(); +} + +// static +bool CookieCommons::ShouldIncludeCrossSiteCookieForDocument(Cookie* aCookie) { + MOZ_ASSERT(aCookie); + + int32_t sameSiteAttr = 0; + aCookie->GetSameSite(&sameSiteAttr); + + return sameSiteAttr == nsICookie::SAMESITE_NONE; +} + +bool CookieCommons::IsSafeTopLevelNav(nsIChannel* aChannel) { + if (!aChannel) { + return false; + } + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo(); + if ((loadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT && + loadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) && + !interceptionInfo) { + return false; + } + + if (interceptionInfo && + interceptionInfo->GetExtContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT && + interceptionInfo->GetExtContentPolicyType() != + ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD && + interceptionInfo->GetExtContentPolicyType() != + ExtContentPolicy::TYPE_INVALID) { + return false; + } + + return NS_IsSafeMethodNav(aChannel); +} + +// This function determines if two schemes are equal in the context of +// "Schemeful SameSite cookies". +// +// Two schemes are considered equal: +// - if the "network.cookie.sameSite.schemeful" pref is set to false. +// OR +// - if one of the schemes is not http or https. +// OR +// - if both schemes are equal AND both are either http or https. +bool IsSameSiteSchemeEqual(const nsACString& aFirstScheme, + const nsACString& aSecondScheme) { + if (!StaticPrefs::network_cookie_sameSite_schemeful()) { + return true; + } + + auto isSchemeHttpOrHttps = [](const nsACString& scheme) -> bool { + return scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https"); + }; + + if (!isSchemeHttpOrHttps(aFirstScheme) || + !isSchemeHttpOrHttps(aSecondScheme)) { + return true; + } + + return aFirstScheme.Equals(aSecondScheme); +} + +bool CookieCommons::IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI, + bool* aHadCrossSiteRedirects) { + *aHadCrossSiteRedirects = false; + + if (!aChannel) { + return false; + } + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + // Do not treat loads triggered by web extensions as foreign + nsCOMPtr<nsIURI> channelURI; + NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); + + nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo(); + + RefPtr<BasePrincipal> triggeringPrincipal; + ExtContentPolicy contentPolicyType; + if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) { + triggeringPrincipal = + BasePrincipal::Cast(interceptionInfo->TriggeringPrincipal()); + contentPolicyType = interceptionInfo->GetExtContentPolicyType(); + } else { + triggeringPrincipal = BasePrincipal::Cast(loadInfo->TriggeringPrincipal()); + contentPolicyType = loadInfo->GetExternalContentPolicyType(); + + if (triggeringPrincipal->AddonPolicy() && + triggeringPrincipal->AddonAllowsLoad(channelURI)) { + return false; + } + } + const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& redirectChain( + interceptionInfo && interceptionInfo->TriggeringPrincipal() + ? interceptionInfo->RedirectChain() + : loadInfo->RedirectChain()); + + nsAutoCString hostScheme, otherScheme; + aHostURI->GetScheme(hostScheme); + + bool isForeign = true; + nsresult rv; + if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT || + contentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) { + // for loads of TYPE_DOCUMENT we query the hostURI from the + // triggeringPrincipal which returns the URI of the document that caused the + // navigation. + rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign); + + triggeringPrincipal->GetScheme(otherScheme); + } else { + // If the load is caused by FetchEvent.request or NavigationPreload request, + // check the original InterceptedHttpChannel is a third-party channel or + // not. + if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) { + isForeign = interceptionInfo->FromThirdParty(); + if (isForeign) { + return true; + } + } + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + if (!thirdPartyUtil) { + return true; + } + rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); + + channelURI->GetScheme(otherScheme); + } + // if we are dealing with a cross origin request, we can return here + // because we already know the request is 'foreign'. + if (NS_FAILED(rv) || isForeign) { + return true; + } + + if (!IsSameSiteSchemeEqual(otherScheme, hostScheme)) { + // If the two schemes are not of the same http(s) scheme then we + // consider the request as foreign. + return true; + } + + // for loads of TYPE_SUBDOCUMENT we have to perform an additional test, + // because a cross-origin iframe might perform a navigation to a same-origin + // iframe which would send same-site cookies. Hence, if the iframe navigation + // was triggered by a cross-origin triggeringPrincipal, we treat the load as + // foreign. + if (contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) { + rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign); + if (NS_FAILED(rv) || isForeign) { + return true; + } + } + + // for the purpose of same-site cookies we have to treat any cross-origin + // redirects as foreign. E.g. cross-site to same-site redirect is a problem + // with regards to CSRF. + + nsCOMPtr<nsIPrincipal> redirectPrincipal; + for (nsIRedirectHistoryEntry* entry : redirectChain) { + entry->GetPrincipal(getter_AddRefs(redirectPrincipal)); + if (redirectPrincipal) { + rv = redirectPrincipal->IsThirdPartyChannel(aChannel, &isForeign); + // if at any point we encounter a cross-origin redirect we can return. + if (NS_FAILED(rv) || isForeign) { + *aHadCrossSiteRedirects = isForeign; + return true; + } + + nsAutoCString redirectScheme; + redirectPrincipal->GetScheme(redirectScheme); + if (!IsSameSiteSchemeEqual(redirectScheme, hostScheme)) { + // If the two schemes are not of the same http(s) scheme then we + // consider the request as foreign. + *aHadCrossSiteRedirects = true; + return true; + } + } + } + return isForeign; +} + +// static +nsICookie::schemeType CookieCommons::URIToSchemeType(nsIURI* aURI) { + MOZ_ASSERT(aURI); + + nsAutoCString scheme; + nsresult rv = aURI->GetScheme(scheme); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nsICookie::SCHEME_UNSET; + } + + return SchemeToSchemeType(scheme); +} + +// static +nsICookie::schemeType CookieCommons::PrincipalToSchemeType( + nsIPrincipal* aPrincipal) { + MOZ_ASSERT(aPrincipal); + + nsAutoCString scheme; + nsresult rv = aPrincipal->GetScheme(scheme); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nsICookie::SCHEME_UNSET; + } + + return SchemeToSchemeType(scheme); +} + +// static +nsICookie::schemeType CookieCommons::SchemeToSchemeType( + const nsACString& aScheme) { + MOZ_ASSERT(IsSchemeSupported(aScheme)); + + if (aScheme.Equals("https")) { + return nsICookie::SCHEME_HTTPS; + } + + if (aScheme.Equals("http")) { + return nsICookie::SCHEME_HTTP; + } + + if (aScheme.Equals("file")) { + return nsICookie::SCHEME_FILE; + } + + MOZ_CRASH("Unsupported scheme type"); +} + +// static +bool CookieCommons::IsSchemeSupported(nsIPrincipal* aPrincipal) { + MOZ_ASSERT(aPrincipal); + + nsAutoCString scheme; + nsresult rv = aPrincipal->GetScheme(scheme); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return IsSchemeSupported(scheme); +} + +// static +bool CookieCommons::IsSchemeSupported(nsIURI* aURI) { + MOZ_ASSERT(aURI); + + nsAutoCString scheme; + nsresult rv = aURI->GetScheme(scheme); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return IsSchemeSupported(scheme); +} + +// static +bool CookieCommons::IsSchemeSupported(const nsACString& aScheme) { + return aScheme.Equals("https") || aScheme.Equals("http") || + aScheme.Equals("file"); +} + +} // namespace net +} // namespace mozilla |