diff --git a/caps/OriginAttributes.cpp b/caps/OriginAttributes.cpp index 46e458f0d9bd..ba63a7761b35 100644 --- a/caps/OriginAttributes.cpp +++ b/caps/OriginAttributes.cpp @@ -61,7 +61,7 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument, bool aForeignByAncestorContext, bool aIsFirstPartyEnabled, bool aForced, bool aUseSite, - nsString OriginAttributes::*aTarget, + nsString OriginAttributes::* aTarget, OriginAttributes& aOriginAttributes) { nsresult rv; @@ -514,4 +514,23 @@ bool OriginAttributes::ParsePartitionKey(const nsAString& aPartitionKey, return fieldIndex > 1; } +/* static */ +bool OriginAttributes::ExtractSiteFromPartitionKey( + const nsAString& aPartitionKey, nsAString& aOutSite) { + nsAutoString scheme, host; + int32_t port; + bool unused; + if (!ParsePartitionKey(aPartitionKey, scheme, host, port, unused)) { + return false; + } + + if (port == -1) { + aOutSite.Assign(scheme + u"://"_ns + host); + } else { + aOutSite.Assign(scheme + u"://"_ns + host + u":"_ns); + aOutSite.AppendInt(port); + } + return true; +} + } // namespace mozilla diff --git a/caps/OriginAttributes.h b/caps/OriginAttributes.h index 21b641771f7e..1794c6e0bea0 100644 --- a/caps/OriginAttributes.h +++ b/caps/OriginAttributes.h @@ -141,6 +141,11 @@ class OriginAttributes : public dom::OriginAttributesDictionary { nsAString& outScheme, nsAString& outBaseDomain, int32_t& outPort, bool& outForeignByAncestorContext); + + // Parse a partitionKey and extract the site from it. Returns false if the + // partitionKey cannot be parsed because the format is invalid. + static bool ExtractSiteFromPartitionKey(const nsAString& aPartitionKey, + nsAString& aOutSite); }; class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary { diff --git a/netwerk/cookie/CookieService.cpp b/netwerk/cookie/CookieService.cpp index 32ef35a6d161..f18890626e33 100644 --- a/netwerk/cookie/CookieService.cpp +++ b/netwerk/cookie/CookieService.cpp @@ -7,6 +7,7 @@ #include "CookieCommons.h" #include "CookieLogging.h" #include "CookieParser.h" +#include "CookieService.h" #include "mozilla/AppShutdown.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Components.h" @@ -16,6 +17,7 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/nsMixedContentBlocker.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/Promise-inl.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/net/CookiePersistentStorage.h" #include "mozilla/net/CookiePrivateStorage.h" @@ -259,6 +261,10 @@ nsresult CookieService::Init() { os->AddObserver(this, "profile-do-change", true); os->AddObserver(this, "last-pb-context-exited", true); + RunOnShutdown([self = RefPtr{this}] { + self->mThirdPartyCookieBlockingExceptions.Shutdown(); + }); + return NS_OK; } @@ -1723,5 +1729,77 @@ void CookieService::AddCookieFromDocument( aThirdParty, aDocument->GetBrowsingContext()); } +/* static */ +void CookieService::Update3PCBExceptionInfo(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr loadInfo = aChannel->LoadInfo(); + RefPtr csSingleton = CookieService::GetSingleton(); + + // If the channel is a top-level loading, we start initiating the exception + // list service. + if (loadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT) { + Unused + << csSingleton->mThirdPartyCookieBlockingExceptions.EnsureInitialized(); + return; + } + + // If the channel is triggered by a system principal, we don't need to do + // anything because the channel is for loading system resources. + if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) { + return; + } + + // Suspend the channel here. We will resume it after we check the exception + // list. + aChannel->Suspend(); + + // It would be better to do this check with other checks that also suspend + // the channel, such as the URLClassifier. + csSingleton->mThirdPartyCookieBlockingExceptions.EnsureInitialized()->Then( + GetMainThreadSerialEventTarget(), __func__, + [channel = nsCOMPtr{aChannel}, csSingleton, loadInfo]( + const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) { + // We check the 3PCB exception list here. We will check both the + // wildcard exception and the specific exception. If any of them is in + // the exception list, we will set the channel's isOn3PCBExceptionList + // to true. + bool isInExceptionList = + csSingleton->mThirdPartyCookieBlockingExceptions + .CheckExceptionForChannel(channel); + + Unused << loadInfo->SetIsOn3PCBExceptionList(isInExceptionList); + + channel->Resume(); + return NS_OK; + }); +} + +NS_IMETHODIMP +CookieService::AddThirdPartyCookieBlockingExceptions( + const nsTArray>& aExceptions) { + for (const auto& ex : aExceptions) { + nsAutoCString exception; + MOZ_ALWAYS_SUCCEEDS(ex->Serialize(exception)); + mThirdPartyCookieBlockingExceptions.Insert(exception); + } + + return NS_OK; +} + +NS_IMETHODIMP +CookieService::RemoveThirdPartyCookieBlockingExceptions( + const nsTArray>& aExceptions) { + for (const auto& ex : aExceptions) { + nsAutoCString exception; + MOZ_ALWAYS_SUCCEEDS(ex->Serialize(exception)); + mThirdPartyCookieBlockingExceptions.Remove(exception); + } + + return NS_OK; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/cookie/CookieService.h b/netwerk/cookie/CookieService.h index 49eeb3d2a847..e787809d1a15 100644 --- a/netwerk/cookie/CookieService.h +++ b/netwerk/cookie/CookieService.h @@ -13,10 +13,12 @@ #include "Cookie.h" #include "CookieCommons.h" +#include "ThirdPartyCookieBlockingExceptions.h" #include "nsString.h" #include "nsIMemoryReporter.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/MozPromise.h" class nsIConsoleReportCollector; class nsICookieJarSettings; @@ -58,6 +60,12 @@ class CookieService final : public nsICookieService, static already_AddRefed GetXPCOMSingleton(); nsresult Init(); + static void Update3PCBExceptionInfo(nsIChannel* aChannel); + + ThirdPartyCookieBlockingExceptions& ThirdPartyCookieBlockingExceptionsRef() { + return mThirdPartyCookieBlockingExceptions; + } + /** * Start watching the observer service for messages indicating that an app has * been uninstalled. When an app is uninstalled, we get the cookie service @@ -126,6 +134,8 @@ class CookieService final : public nsICookieService, nsCOMPtr mThirdPartyUtil; nsCOMPtr mTLDService; + ThirdPartyCookieBlockingExceptions mThirdPartyCookieBlockingExceptions; + // we have two separate Cookie Storages: one for normal browsing and one for // private browsing. RefPtr mPersistentStorage; diff --git a/netwerk/cookie/ThirdPartyCookieBlockingExceptionListService.sys.mjs b/netwerk/cookie/ThirdPartyCookieBlockingExceptionListService.sys.mjs index 093ec9436d89..eb78179f4218 100644 --- a/netwerk/cookie/ThirdPartyCookieBlockingExceptionListService.sys.mjs +++ b/netwerk/cookie/ThirdPartyCookieBlockingExceptionListService.sys.mjs @@ -57,10 +57,10 @@ export class ThirdPartyCookieBlockingExceptionListService { #handleExceptionChange(created = [], deleted = []) { if (created.length) { - // TODO: Calling CookieService API to remove exception sites. + Services.cookies.addThirdPartyCookieBlockingExceptions(created); } if (deleted.length) { - // TODO: Calling CookieService API to add exception sites. + Services.cookies.removeThirdPartyCookieBlockingExceptions(deleted); } } diff --git a/netwerk/cookie/ThirdPartyCookieBlockingExceptions.cpp b/netwerk/cookie/ThirdPartyCookieBlockingExceptions.cpp new file mode 100644 index 000000000000..795c9372750a --- /dev/null +++ b/netwerk/cookie/ThirdPartyCookieBlockingExceptions.cpp @@ -0,0 +1,208 @@ +/* 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 "ThirdPartyCookieBlockingExceptions.h" + +#include "mozilla/Components.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozilla/dom/WindowGlobalParent.h" + +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +RefPtr +ThirdPartyCookieBlockingExceptions::EnsureInitialized() { + if (mInitPromise) { + return mInitPromise; + } + + // Get the remote third-party cookie blocking exception list service instance. + nsresult rv; + m3PCBExceptionService = do_GetService( + NS_NSITHIRDPARTYCOOKIEBLOCKINGEXCEPTIONLISTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, + GenericNonExclusivePromise::CreateAndReject(rv, __func__)); + + RefPtr initPromise; + rv = m3PCBExceptionService->Init(getter_AddRefs(initPromise)); + NS_ENSURE_SUCCESS(rv, + GenericNonExclusivePromise::CreateAndReject(rv, __func__)); + + // Bail out earlier if we don't have a init promise. + if (!initPromise) { + return GenericNonExclusivePromise::CreateAndReject(rv, __func__); + } + + mInitPromise = new GenericNonExclusivePromise::Private(__func__); + + initPromise->AddCallbacksWithCycleCollectedArgs( + [&self = *this](JSContext*, JS::Handle, + mozilla::ErrorResult&) { + self.mInitPromise->Resolve(true, __func__); + }, + [&self = *this](JSContext*, JS::Handle, + mozilla::ErrorResult& error) { + nsresult rv = error.StealNSResult(); + self.mInitPromise->Reject(rv, __func__); + return; + }); + + return mInitPromise; +} + +void ThirdPartyCookieBlockingExceptions::Shutdown() { + if (m3PCBExceptionService) { + Unused << m3PCBExceptionService->Shutdown(); + m3PCBExceptionService = nullptr; + } + + // Reject the init promise during the shutdown. + if (mInitPromise) { + mInitPromise->Reject(NS_ERROR_ABORT, __func__); + mInitPromise = nullptr; + } +} + +void ThirdPartyCookieBlockingExceptions::Insert(const nsACString& aException) { + m3PCBExceptionsSet.Insert(aException); +} + +void ThirdPartyCookieBlockingExceptions::Remove(const nsACString& aException) { + m3PCBExceptionsSet.Remove(aException); +} + +bool ThirdPartyCookieBlockingExceptions::CheckWildcardException( + const nsACString& aThirdPartySite) { + nsAutoCString key; + Create3PCBExceptionKey("*"_ns, aThirdPartySite, key); + + return m3PCBExceptionsSet.Contains(key); +} + +bool ThirdPartyCookieBlockingExceptions::CheckException( + const nsACString& aFirstPartySite, const nsACString& aThirdPartySite) { + nsAutoCString key; + Create3PCBExceptionKey(aFirstPartySite, aThirdPartySite, key); + + return m3PCBExceptionsSet.Contains(key); +} + +bool ThirdPartyCookieBlockingExceptions::CheckExceptionForURIs( + nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI) { + MOZ_ASSERT(XRE_IsParentProcess()); + NS_ENSURE_TRUE(aFirstPartyURI, false); + NS_ENSURE_TRUE(aThirdPartyURI, false); + + RefPtr eTLDService = + nsEffectiveTLDService::GetInstance(); + NS_ENSURE_TRUE(eTLDService, false); + + nsAutoCString thirdPartySite; + nsresult rv = eTLDService->GetSite(aThirdPartyURI, thirdPartySite); + NS_ENSURE_SUCCESS(rv, false); + + bool isInExceptionList = CheckWildcardException(thirdPartySite); + + if (isInExceptionList) { + return true; + } + + nsAutoCString firstPartySite; + rv = eTLDService->GetSite(aFirstPartyURI, firstPartySite); + NS_ENSURE_SUCCESS(rv, false); + + return CheckException(firstPartySite, thirdPartySite); +} + +bool ThirdPartyCookieBlockingExceptions::CheckExceptionForChannel( + nsIChannel* aChannel) { + MOZ_ASSERT(XRE_IsParentProcess()); + NS_ENSURE_TRUE(aChannel, false); + + RefPtr eTLDService = + nsEffectiveTLDService::GetInstance(); + NS_ENSURE_TRUE(eTLDService, false); + + nsCOMPtr uri; + nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString thirdPartySite; + rv = eTLDService->GetSite(uri, thirdPartySite); + NS_ENSURE_SUCCESS(rv, false); + + bool isInExceptionList = CheckWildcardException(thirdPartySite); + + if (isInExceptionList) { + return true; + } + + nsCOMPtr loadInfo = aChannel->LoadInfo(); + + RefPtr bc; + loadInfo->GetBrowsingContext(getter_AddRefs(bc)); + if (!bc) { + bc = loadInfo->GetWorkerAssociatedBrowsingContext(); + } + + nsAutoCString firstPartySite; + + // If the channel is not associated with a browsing context, we will try to + // get the first party site from the partition key. + if (!bc) { + nsCOMPtr cjs; + nsresult rv = loadInfo->GetCookieJarSettings(getter_AddRefs(cjs)); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoString partitionKey; + rv = cjs->GetPartitionKey(partitionKey); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoString site; + if (!OriginAttributes::ExtractSiteFromPartitionKey(partitionKey, site)) { + return false; + } + + firstPartySite.Assign(NS_ConvertUTF16toUTF8(site)); + } else { + RefPtr topWGP = + bc->Top()->Canonical()->GetCurrentWindowGlobal(); + if (!topWGP) { + return false; + } + + nsCOMPtr topPrincipal = topWGP->DocumentPrincipal(); + + // If the top window is an about page, we don't need to do anything. This + // could happen when fetching system resources, such as pocket's images + if (topPrincipal->SchemeIs("about")) { + return false; + } + + nsCOMPtr topURI = topPrincipal->GetURI(); + + nsAutoCString site; + nsresult rv = eTLDService->GetSite(topURI, firstPartySite); + NS_ENSURE_SUCCESS(rv, false); + } + + return CheckException(firstPartySite, thirdPartySite); +} + +void ThirdPartyCookieBlockingExceptions::GetExceptions( + nsTArray& aExceptions) { + aExceptions.Clear(); + + for (const auto& host : m3PCBExceptionsSet) { + aExceptions.AppendElement(host); + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/cookie/ThirdPartyCookieBlockingExceptions.h b/netwerk/cookie/ThirdPartyCookieBlockingExceptions.h new file mode 100644 index 000000000000..2230dbbfdba5 --- /dev/null +++ b/netwerk/cookie/ThirdPartyCookieBlockingExceptions.h @@ -0,0 +1,74 @@ +/* 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/. */ + +#ifndef mozilla_net_ThirdPartyCookieBlockingExceptions_h +#define mozilla_net_ThirdPartyCookieBlockingExceptions_h + +#include "mozilla/MozPromise.h" +#include "nsEffectiveTLDService.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashSet.h" +#include "nsIThirdPartyCookieBlockingExceptionListService.h" + +class nsIEffectiveTLDService; +class nsIURI; +class nsIChannel; + +namespace mozilla { +namespace net { + +class ThirdPartyCookieBlockingExceptions final { + public: + // Lazily initializes the foreign cookie blocking exception list. The function + // returns the promise that resolves when the list is initialized. + RefPtr EnsureInitialized(); + + // Check if the given top-level and third-party URIs are in the exception + // list. + bool CheckExceptionForURIs(nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI); + + // Check if the given channel is in the exception list. + bool CheckExceptionForChannel(nsIChannel* aChannel); + + // Check if the given third-party site is in the wildcard exception list. + // The wildcard exception list is used to allow third-party cookies under + // every top-level site. + bool CheckWildcardException(const nsACString& aThirdPartySite); + + // Check if the given first-party and third-party sites are in the exception + // list. + bool CheckException(const nsACString& aFirstPartySite, + const nsACString& aThirdPartySite); + + void Insert(const nsACString& aException); + void Remove(const nsACString& aException); + + void GetExceptions(nsTArray& aExceptions); + + void Shutdown(); + + private: + nsCOMPtr + m3PCBExceptionService; + + // A helper function to create a key for the exception list. + static void Create3PCBExceptionKey(const nsACString& aFirstPartySite, + const nsACString& aThirdPartySite, + nsACString& aKey) { + aKey.Truncate(); + aKey.Append(aFirstPartySite); + aKey.AppendLiteral(","); + aKey.Append(aThirdPartySite); + } + + // The promise that resolves when the 3PCB exception service is initialized. + RefPtr mInitPromise; + nsTHashSet m3PCBExceptionsSet; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ThirdPartyCookieBlockingExceptions_h diff --git a/netwerk/cookie/moz.build b/netwerk/cookie/moz.build index 3313d48b51b0..5c1a6acf2dab 100644 --- a/netwerk/cookie/moz.build +++ b/netwerk/cookie/moz.build @@ -34,6 +34,7 @@ EXPORTS.mozilla.net = [ "CookieServiceChild.h", "CookieServiceParent.h", "CookieStorage.h", + "ThirdPartyCookieBlockingExceptions.h", ] UNIFIED_SOURCES += [ "Cookie.cpp", @@ -48,6 +49,7 @@ UNIFIED_SOURCES += [ "CookieServiceChild.cpp", "CookieServiceParent.cpp", "CookieStorage.cpp", + "ThirdPartyCookieBlockingExceptions.cpp", ] XPCSHELL_TESTS_MANIFESTS += [ "test/unit/xpcshell.toml", diff --git a/netwerk/cookie/nsICookieManager.idl b/netwerk/cookie/nsICookieManager.idl index a747167be088..ce3d9fa97650 100644 --- a/netwerk/cookie/nsICookieManager.idl +++ b/netwerk/cookie/nsICookieManager.idl @@ -5,6 +5,7 @@ #include "nsISupports.idl" #include "nsICookie.idl" +#include "nsIThirdPartyCookieBlockingExceptionListService.idl" %{ C++ namespace mozilla { @@ -295,4 +296,19 @@ interface nsICookieManager : nsISupports * Retrieves all the cookies that were created on or after aSinceWhen, order * by creation time */ Array getCookiesSince(in int64_t aSinceWhen); + + + /** + * Adds a list of exceptions to the third party cookie blocking exception + * list. + */ + void addThirdPartyCookieBlockingExceptions( + in Array aExcpetions); + + /** + * Removes a list of exceptions from the third party cookie blocking + * exception list. + */ + void removeThirdPartyCookieBlockingExceptions( + in Array aExceptions); }; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 9ca7c15edb1f..6c6a7f7c98d9 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -72,6 +72,7 @@ #include "nsContentUtils.h" #include "nsContentSecurityManager.h" #include "nsIClassOfService.h" +#include "CookieService.h" #include "nsIPrincipal.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" @@ -7176,6 +7177,10 @@ nsresult nsHttpChannel::BeginConnect() { MaybeStartDNSPrefetch(); + // Update whether the channel is on the third-party cookie blocking exception + // list. + CookieService::Update3PCBExceptionInfo(this); + rv = CallOrWaitForResume( [](nsHttpChannel* self) { return self->PrepareToConnect(); }); if (NS_FAILED(rv)) {