summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/AntiTrackingUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/antitracking/AntiTrackingUtils.cpp
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/antitracking/AntiTrackingUtils.cpp')
-rw-r--r--toolkit/components/antitracking/AntiTrackingUtils.cpp899
1 files changed, 899 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/AntiTrackingUtils.cpp b/toolkit/components/antitracking/AntiTrackingUtils.cpp
new file mode 100644
index 0000000000..d40cd71c48
--- /dev/null
+++ b/toolkit/components/antitracking/AntiTrackingUtils.cpp
@@ -0,0 +1,899 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "AntiTrackingUtils.h"
+
+#include "AntiTrackingLog.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/PermissionManager.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsEffectiveTLDService.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIChannel.h"
+#include "nsICookieService.h"
+#include "nsIHttpChannel.h"
+#include "nsIPermission.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsRFPService.h"
+#include "nsSandboxFlags.h"
+#include "nsScriptSecurityManager.h"
+#include "PartitioningExceptionList.h"
+
+#define ANTITRACKING_PERM_KEY "3rdPartyStorage"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/* static */ already_AddRefed<nsPIDOMWindowInner>
+AntiTrackingUtils::GetInnerWindow(BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer = aBrowsingContext->GetDOMWindow();
+ if (!outer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
+ return inner.forget();
+}
+
+/* static */ already_AddRefed<nsPIDOMWindowOuter>
+AntiTrackingUtils::GetTopWindow(nsPIDOMWindowInner* aWindow) {
+ Document* document = aWindow->GetExtantDoc();
+ if (!document) {
+ return nullptr;
+ }
+
+ nsIChannel* channel = document->GetChannel();
+ if (!channel) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> pwin =
+ aWindow->GetBrowsingContext()->Top()->GetDOMWindow();
+
+ if (!pwin) {
+ return nullptr;
+ }
+
+ return pwin.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIURI> uriBeingLoaded;
+ nsLoadFlags loadFlags = 0;
+ nsresult rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ // If the channel being loaded is a document channel, this call may be
+ // coming from an OnStopRequest notification, which might mean that our
+ // document may still be in the loading process, so we may need to pass in
+ // the uriBeingLoaded argument explicitly.
+ rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uriBeingLoaded));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ }
+ return uriBeingLoaded.forget();
+}
+
+// static
+void AntiTrackingUtils::CreateStoragePermissionKey(
+ const nsACString& aTrackingOrigin, nsACString& aPermissionKey) {
+ MOZ_ASSERT(aPermissionKey.IsEmpty());
+
+ static const nsLiteralCString prefix =
+ nsLiteralCString(ANTITRACKING_PERM_KEY "^");
+
+ aPermissionKey.SetCapacity(prefix.Length() + aTrackingOrigin.Length());
+ aPermissionKey.Append(prefix);
+ aPermissionKey.Append(aTrackingOrigin);
+}
+
+// static
+bool AntiTrackingUtils::CreateStoragePermissionKey(nsIPrincipal* aPrincipal,
+ nsACString& aKey) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ nsAutoCString origin;
+ nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ CreateStoragePermissionKey(origin, aKey);
+ return true;
+}
+
+// static
+bool AntiTrackingUtils::CreateStorageRequestPermissionKey(
+ nsIURI* aURI, nsACString& aPermissionKey) {
+ MOZ_ASSERT(aPermissionKey.IsEmpty());
+ RefPtr<nsEffectiveTLDService> eTLDService =
+ nsEffectiveTLDService::GetInstance();
+ if (!eTLDService) {
+ return false;
+ }
+ nsCString site;
+ nsresult rv = eTLDService->GetSite(aURI, site);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ static const nsLiteralCString prefix =
+ nsLiteralCString("AllowStorageAccessRequest^");
+ aPermissionKey.SetCapacity(prefix.Length() + site.Length());
+ aPermissionKey.Append(prefix);
+ aPermissionKey.Append(site);
+ return true;
+}
+
+// static
+bool AntiTrackingUtils::IsStorageAccessPermission(nsIPermission* aPermission,
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(aPermission);
+ MOZ_ASSERT(aPrincipal);
+
+ // The permission key may belong either to a tracking origin on the same
+ // origin as the granted origin, or on another origin as the granted origin
+ // (for example when a tracker in a third-party context uses window.open to
+ // open another origin where that second origin would be the granted origin.)
+ // But even in the second case, the type of the permission would still be
+ // formed by concatenating the granted origin to the end of the type name
+ // (see CreatePermissionKey). Therefore, we pass in the same argument to
+ // both tracking origin and granted origin here in order to compute the
+ // shorter permission key and will then do a prefix match on the type of the
+ // input permission to see if it is a storage access permission or not.
+ nsAutoCString permissionKey;
+ bool result = CreateStoragePermissionKey(aPrincipal, permissionKey);
+ if (NS_WARN_IF(!result)) {
+ return false;
+ }
+
+ nsAutoCString type;
+ nsresult rv = aPermission->GetType(type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return StringBeginsWith(type, permissionKey);
+}
+
+// static
+Maybe<size_t> AntiTrackingUtils::CountSitesAllowStorageAccess(
+ nsIPrincipal* aPrincipal) {
+ PermissionManager* permManager = PermissionManager::GetInstance();
+ if (NS_WARN_IF(!permManager)) {
+ return Nothing();
+ }
+
+ nsAutoCString prefix;
+ AntiTrackingUtils::CreateStoragePermissionKey(aPrincipal, prefix);
+
+ using Permissions = nsTArray<RefPtr<nsIPermission>>;
+ Permissions perms;
+ nsresult rv = permManager->GetAllWithTypePrefix(prefix, perms);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Nothing();
+ }
+
+ // Create a set of unique origins
+ using Origins = nsTArray<nsCString>;
+ Origins origins;
+
+ // Iterate over all permissions that have a prefix equal to the expected
+ // permission we are looking for. This includes two things we need to remove:
+ // duplicates and origin strings that have a prefix of aPrincipal's origin
+ // string, e.g. https://example.company when aPrincipal is
+ // https://example.com.
+ for (const auto& perm : perms) {
+ nsAutoCString type;
+ rv = perm->GetType(type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Nothing();
+ }
+ // Let's make sure that we're not looking at a permission for
+ // https://exampletracker.company when we mean to look for the
+ // permission for https://exampletracker.com!
+ if (type != prefix) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = perm->GetPrincipal(getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Nothing();
+ }
+
+ nsAutoCString origin;
+ rv = principal->GetOrigin(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Nothing();
+ }
+
+ ToLowerCase(origin);
+
+ // Append if it would not be a duplicate
+ if (origins.IndexOf(origin) == Origins::NoIndex) {
+ origins.AppendElement(origin);
+ }
+ }
+
+ return Some(origins.Length());
+}
+
+// static
+bool AntiTrackingUtils::CheckStoragePermission(nsIPrincipal* aPrincipal,
+ const nsAutoCString& aType,
+ bool aIsInPrivateBrowsing,
+ uint32_t* aRejectedReason,
+ uint32_t aBlockedReason) {
+ PermissionManager* permManager = PermissionManager::GetInstance();
+ if (NS_WARN_IF(!permManager)) {
+ LOG(("Failed to obtain the permission manager"));
+ return false;
+ }
+
+ uint32_t result = 0;
+ if (aIsInPrivateBrowsing) {
+ LOG_PRIN(("Querying the permissions for private modei looking for a "
+ "permission of type %s for %s",
+ aType.get(), _spec),
+ aPrincipal);
+ if (!permManager->PermissionAvailable(aPrincipal, aType)) {
+ LOG(
+ ("Permission isn't available for this principal in the current "
+ "process"));
+ return false;
+ }
+ nsTArray<RefPtr<nsIPermission>> permissions;
+ nsresult rv = permManager->GetAllForPrincipal(aPrincipal, permissions);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Failed to get the list of permissions"));
+ return false;
+ }
+
+ bool found = false;
+ for (const auto& permission : permissions) {
+ if (!permission) {
+ LOG(("Couldn't get the permission for unknown reasons"));
+ continue;
+ }
+
+ nsAutoCString permissionType;
+ if (NS_SUCCEEDED(permission->GetType(permissionType)) &&
+ permissionType != aType) {
+ LOG(("Non-matching permission type: %s", aType.get()));
+ continue;
+ }
+
+ uint32_t capability = 0;
+ if (NS_SUCCEEDED(permission->GetCapability(&capability)) &&
+ capability != nsIPermissionManager::ALLOW_ACTION) {
+ LOG(("Non-matching permission capability: %d", capability));
+ continue;
+ }
+
+ uint32_t expirationType = 0;
+ if (NS_SUCCEEDED(permission->GetExpireType(&expirationType)) &&
+ expirationType != nsIPermissionManager ::EXPIRE_SESSION) {
+ LOG(("Non-matching permission expiration type: %d", expirationType));
+ continue;
+ }
+
+ int64_t expirationTime = 0;
+ if (NS_SUCCEEDED(permission->GetExpireTime(&expirationTime)) &&
+ expirationTime != 0) {
+ LOG(("Non-matching permission expiration time: %" PRId64,
+ expirationTime));
+ continue;
+ }
+
+ LOG(("Found a matching permission"));
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ if (aRejectedReason) {
+ *aRejectedReason = aBlockedReason;
+ }
+ return false;
+ }
+ } else {
+ nsresult rv = permManager->TestPermissionWithoutDefaultsFromPrincipal(
+ aPrincipal, aType, &result);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Failed to test the permission"));
+ return false;
+ }
+
+ LOG_PRIN(
+ ("Testing permission type %s for %s resulted in %d (%s)", aType.get(),
+ _spec, int(result),
+ result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"),
+ aPrincipal);
+
+ if (result != nsIPermissionManager::ALLOW_ACTION) {
+ if (aRejectedReason) {
+ *aRejectedReason = aBlockedReason;
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+nsILoadInfo::StoragePermissionState
+AntiTrackingUtils::GetStoragePermissionStateInParent(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+
+ auto policyType = loadInfo->GetExternalContentPolicyType();
+
+ // The channel is for the document load of the top-level window. The top-level
+ // window should always has 'hasStoragePermission' flag as false. So, we can
+ // return here directly.
+ if (policyType == ExtContentPolicy::TYPE_DOCUMENT) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ nsresult rv =
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ int32_t cookieBehavior = cookieJarSettings->GetCookieBehavior();
+
+ // We only need to check the storage permission if the cookie behavior is
+ // BEHAVIOR_REJECT_TRACKER, BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN or
+ // BEHAVIOR_REJECT_FOREIGN with exceptions. Because ContentBlocking wouldn't
+ // update or check the storage permission if the cookie behavior is not
+ // belongs to these three.
+ if (!net::CookieJarSettings::IsRejectThirdPartyContexts(cookieBehavior)) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ RefPtr<BrowsingContext> bc;
+ rv = loadInfo->GetTargetBrowsingContext(getter_AddRefs(bc));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !bc) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ uint64_t targetWindowId = GetTopLevelAntiTrackingWindowId(bc);
+ nsCOMPtr<nsIPrincipal> targetPrincipal;
+
+ if (targetWindowId) {
+ RefPtr<WindowGlobalParent> wgp =
+ WindowGlobalParent::GetByInnerWindowId(targetWindowId);
+
+ if (NS_WARN_IF(!wgp)) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ targetPrincipal = wgp->DocumentPrincipal();
+ } else {
+ // We try to use the loading principal if there is no AntiTrackingWindowId.
+ targetPrincipal = loadInfo->GetLoadingPrincipal();
+ }
+
+ if (!targetPrincipal) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+
+ if (httpChannel) {
+ // We don't have a loading principal, let's see if this is a document
+ // channel which belongs to a top-level window.
+ bool isDocument = false;
+ rv = httpChannel->GetIsMainDocumentChannel(&isDocument);
+ if (NS_SUCCEEDED(rv) && isDocument) {
+ nsIScriptSecurityManager* ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ Unused << ssm->GetChannelResultPrincipal(
+ aChannel, getter_AddRefs(targetPrincipal));
+ }
+ }
+ }
+
+ // Let's use the triggering principal if we still have nothing on the hand.
+ if (!targetPrincipal) {
+ targetPrincipal = loadInfo->TriggeringPrincipal();
+ }
+
+ // Cannot get the target principal, bail out.
+ if (NS_WARN_IF(!targetPrincipal)) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ nsCOMPtr<nsIURI> trackingURI;
+ rv = aChannel->GetURI(getter_AddRefs(trackingURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ nsAutoCString trackingOrigin;
+ rv = nsContentUtils::GetASCIIOrigin(trackingURI, trackingOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ if (IsThirdPartyChannel(aChannel)) {
+ nsAutoCString targetOrigin;
+ if (NS_FAILED(targetPrincipal->GetAsciiOrigin(targetOrigin))) {
+ return nsILoadInfo::NoStoragePermission;
+ }
+
+ if (PartitioningExceptionList::Check(targetOrigin, trackingOrigin)) {
+ return nsILoadInfo::StoragePermissionAllowListed;
+ }
+ }
+
+ nsAutoCString type;
+ AntiTrackingUtils::CreateStoragePermissionKey(trackingOrigin, type);
+
+ uint32_t unusedReason = 0;
+
+ if (AntiTrackingUtils::CheckStoragePermission(targetPrincipal, type,
+ NS_UsePrivateBrowsing(aChannel),
+ &unusedReason, unusedReason)) {
+ return nsILoadInfo::HasStoragePermission;
+ }
+
+ return nsILoadInfo::NoStoragePermission;
+}
+
+uint64_t AntiTrackingUtils::GetTopLevelAntiTrackingWindowId(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ RefPtr<WindowContext> winContext =
+ aBrowsingContext->GetCurrentWindowContext();
+ if (!winContext || winContext->GetCookieBehavior().isNothing()) {
+ return 0;
+ }
+
+ // Do not check BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN her because when
+ // a third-party subresource is inside the main frame, we need to return the
+ // top-level window id to partition its cookies correctly.
+ uint32_t behavior = *winContext->GetCookieBehavior();
+ if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
+ aBrowsingContext->IsTop()) {
+ return 0;
+ }
+
+ return aBrowsingContext->Top()->GetCurrentInnerWindowId();
+}
+
+uint64_t AntiTrackingUtils::GetTopLevelStorageAreaWindowId(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ if (Document::StorageAccessSandboxed(aBrowsingContext->GetSandboxFlags())) {
+ return 0;
+ }
+
+ BrowsingContext* parentBC = aBrowsingContext->GetParent();
+ if (!parentBC) {
+ // No parent browsing context available!
+ return 0;
+ }
+
+ if (!parentBC->IsTop()) {
+ return 0;
+ }
+
+ return parentBC->GetCurrentInnerWindowId();
+}
+
+/* static */
+already_AddRefed<nsIPrincipal> AntiTrackingUtils::GetPrincipal(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ nsCOMPtr<nsIPrincipal> principal;
+
+ if (XRE_IsContentProcess()) {
+ // Passing an out-of-process browsing context in child processes to
+ // this API won't get any result, so just assert.
+ MOZ_ASSERT(aBrowsingContext->IsInProcess());
+
+ nsPIDOMWindowOuter* outer = aBrowsingContext->GetDOMWindow();
+ if (NS_WARN_IF(!outer)) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
+ if (NS_WARN_IF(!inner)) {
+ return nullptr;
+ }
+
+ principal = nsGlobalWindowInner::Cast(inner)->GetPrincipal();
+ } else {
+ WindowGlobalParent* wgp =
+ aBrowsingContext->Canonical()->GetCurrentWindowGlobal();
+ if (NS_WARN_IF(!wgp)) {
+ return nullptr;
+ }
+
+ principal = wgp->DocumentPrincipal();
+ }
+ return principal.forget();
+}
+
+/* static */
+bool AntiTrackingUtils::GetPrincipalAndTrackingOrigin(
+ BrowsingContext* aBrowsingContext, nsIPrincipal** aPrincipal,
+ nsACString& aTrackingOrigin) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ // Passing an out-of-process browsing context in child processes to
+ // this API won't get any result, so just assert.
+ MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext->IsInProcess());
+
+ // Let's take the principal and the origin of the tracker.
+ nsCOMPtr<nsIPrincipal> principal =
+ AntiTrackingUtils::GetPrincipal(aBrowsingContext);
+ if (NS_WARN_IF(!principal)) {
+ return false;
+ }
+
+ nsresult rv = principal->GetOriginNoSuffix(aTrackingOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ if (aPrincipal) {
+ principal.forget(aPrincipal);
+ }
+
+ return true;
+};
+
+/* static */
+uint32_t AntiTrackingUtils::GetCookieBehavior(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ RefPtr<dom::WindowContext> win = aBrowsingContext->GetCurrentWindowContext();
+ if (!win || win->GetCookieBehavior().isNothing()) {
+ return nsICookieService::BEHAVIOR_REJECT;
+ }
+
+ return *win->GetCookieBehavior();
+}
+
+/* static */
+already_AddRefed<WindowGlobalParent>
+AntiTrackingUtils::GetTopWindowExcludingExtensionAccessibleContentFrames(
+ CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURIBeingLoaded) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aBrowsingContext);
+
+ CanonicalBrowsingContext* bc = aBrowsingContext;
+ RefPtr<WindowGlobalParent> prev;
+ while (RefPtr<WindowGlobalParent> parent = bc->GetParentWindowContext()) {
+ CanonicalBrowsingContext* parentBC = parent->BrowsingContext();
+
+ nsIPrincipal* parentPrincipal = parent->DocumentPrincipal();
+ nsIURI* uri = prev ? prev->GetDocumentURI() : aURIBeingLoaded;
+
+ // If the new parent has permission to load the current page, we're
+ // at a moz-extension:// frame which has a host permission that allows
+ // it to load the document that we've loaded. In that case, stop at
+ // this frame and consider it the top-level frame.
+ if (uri &&
+ BasePrincipal::Cast(parentPrincipal)->AddonAllowsLoad(uri, true)) {
+ break;
+ }
+
+ bc = parentBC;
+ prev = parent;
+ }
+ if (!prev) {
+ prev = bc->GetCurrentWindowGlobal();
+ }
+ return prev.forget();
+}
+
+/* static */
+void AntiTrackingUtils::ComputeIsThirdPartyToTopWindow(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ // When a top-level load is opened by window.open, BrowsingContext from
+ // LoadInfo is its opener, which may make the third-party caculation code
+ // below returns an incorrect result. So we use TYPE_DOCUMENT to
+ // ensure a top-level load is not considered 3rd-party.
+ auto policyType = loadInfo->GetExternalContentPolicyType();
+ if (policyType == ExtContentPolicy::TYPE_DOCUMENT) {
+ loadInfo->SetIsThirdPartyContextToTopWindow(false);
+ return;
+ }
+
+ RefPtr<BrowsingContext> bc;
+ loadInfo->GetBrowsingContext(getter_AddRefs(bc));
+
+ nsCOMPtr<nsIURI> uri;
+ Unused << aChannel->GetURI(getter_AddRefs(uri));
+
+ // In some cases we don't have a browsingContext. For example, in xpcshell
+ // tests, channels that are used to download images and channels for loading
+ // worker script.
+ if (!bc) {
+ // If the flag was set before, we don't need to compute again. This could
+ // happen for the channels used to load worker scripts.
+ //
+ // Note that we cannot stop computing the flag in general even it has set
+ // before because sometimes we need to get the up-to-date flag, e.g.
+ // redirects.
+ if (static_cast<net::LoadInfo*>(loadInfo.get())
+ ->HasIsThirdPartyContextToTopWindowSet()) {
+ return;
+ }
+
+ // We turn to check the loading principal if there is no browsing context.
+ auto* loadingPrincipal =
+ BasePrincipal::Cast(loadInfo->GetLoadingPrincipal());
+
+ if (uri && loadingPrincipal) {
+ bool isThirdParty = true;
+ nsresult rv = loadingPrincipal->IsThirdPartyURI(uri, &isThirdParty);
+
+ if (NS_SUCCEEDED(rv)) {
+ loadInfo->SetIsThirdPartyContextToTopWindow(isThirdParty);
+ }
+ }
+ return;
+ }
+
+ RefPtr<WindowGlobalParent> topWindow =
+ GetTopWindowExcludingExtensionAccessibleContentFrames(bc->Canonical(),
+ uri);
+
+ if (NS_WARN_IF(!topWindow)) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> topWindowPrincipal = topWindow->DocumentPrincipal();
+ if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
+ auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
+ bool isThirdParty = true;
+
+ // For about:blank and about:srcdoc, we can't just compare uri to determine
+ // whether the page is third-party, so we use channel result principal
+ // instead. By doing this, an the resource inherits the principal from
+ // its parent is considered not a third-party.
+ if (NS_IsAboutBlank(uri) || NS_IsAboutSrcdoc(uri)) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (NS_WARN_IF(!ssm)) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv =
+ ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ basePrin->IsThirdPartyPrincipal(principal, &isThirdParty);
+ } else {
+ basePrin->IsThirdPartyURI(uri, &isThirdParty);
+ }
+
+ loadInfo->SetIsThirdPartyContextToTopWindow(isThirdParty);
+ }
+}
+
+/* static */
+bool AntiTrackingUtils::IsThirdPartyChannel(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ // We only care whether the channel is 3rd-party with respect to
+ // the top-level.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ return loadInfo->GetIsThirdPartyContextToTopWindow();
+}
+
+/* static */
+bool AntiTrackingUtils::IsThirdPartyWindow(nsPIDOMWindowInner* aWindow,
+ nsIURI* aURI) {
+ MOZ_ASSERT(aWindow);
+
+ // We assume that the window is foreign to the URI by default.
+ bool thirdParty = true;
+
+ // We will skip checking URIs for about:blank and about:srcdoc because they
+ // have no domain. So, comparing them will always fail.
+ if (aURI && !NS_IsAboutBlank(aURI) && !NS_IsAboutSrcdoc(aURI)) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin =
+ do_QueryInterface(aWindow);
+ if (!scriptObjPrin) {
+ return thirdParty;
+ }
+
+ nsCOMPtr<nsIPrincipal> prin = scriptObjPrin->GetPrincipal();
+ if (!prin) {
+ return thirdParty;
+ }
+
+ // Determine whether aURI is foreign with respect to the current principal.
+ nsresult rv = prin->IsThirdPartyURI(aURI, &thirdParty);
+ if (NS_FAILED(rv)) {
+ return thirdParty;
+ }
+
+ if (thirdParty) {
+ return thirdParty;
+ }
+ }
+
+ RefPtr<Document> doc = aWindow->GetDoc();
+ if (!doc) {
+ // If we can't get the document from the window, ex, about:blank, fallback
+ // to use IsThirdPartyWindow check that examine the whole hierarchy.
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ components::ThirdPartyUtil::Service();
+ Unused << thirdPartyUtil->IsThirdPartyWindow(aWindow->GetOuterWindow(),
+ nullptr, &thirdParty);
+ return thirdParty;
+ }
+
+ return IsThirdPartyDocument(doc);
+}
+
+/* static */
+bool AntiTrackingUtils::IsThirdPartyDocument(Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+ if (!aDocument->GetChannel()) {
+ // If we can't get the channel from the document, i.e. initial about:blank
+ // page, we use the browsingContext of the document to check if it's in the
+ // third-party context. If the browsing context is still not available, we
+ // will treat the window as third-party.
+ RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext();
+ return bc ? IsThirdPartyContext(bc) : true;
+ }
+
+ // We only care whether the channel is 3rd-party with respect to
+ // the top-level.
+ nsCOMPtr<nsILoadInfo> loadInfo = aDocument->GetChannel()->LoadInfo();
+ return loadInfo->GetIsThirdPartyContextToTopWindow();
+}
+
+/* static */
+bool AntiTrackingUtils::IsThirdPartyContext(BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+ MOZ_ASSERT(aBrowsingContext->IsInProcess());
+
+ if (aBrowsingContext->IsTopContent()) {
+ return false;
+ }
+
+ // If the top browsing context is not in the same process, it's cross-origin.
+ if (!aBrowsingContext->Top()->IsInProcess()) {
+ return true;
+ }
+
+ nsIDocShell* docShell = aBrowsingContext->GetDocShell();
+ if (!docShell) {
+ return true;
+ }
+ Document* doc = docShell->GetExtantDocument();
+ if (!doc) {
+ return true;
+ }
+ nsIPrincipal* principal = doc->NodePrincipal();
+
+ nsIDocShell* topDocShell = aBrowsingContext->Top()->GetDocShell();
+ if (!topDocShell) {
+ return true;
+ }
+ Document* topDoc = topDocShell->GetDocument();
+ if (!topDoc) {
+ return true;
+ }
+ nsIPrincipal* topPrincipal = topDoc->NodePrincipal();
+
+ auto* topBasePrin = BasePrincipal::Cast(topPrincipal);
+ bool isThirdParty = true;
+
+ topBasePrin->IsThirdPartyPrincipal(principal, &isThirdParty);
+
+ return isThirdParty;
+}
+
+/* static */
+nsCString AntiTrackingUtils::GrantedReasonToString(
+ ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason) {
+ switch (aReason) {
+ case ContentBlockingNotifier::eOpener:
+ return "opener"_ns;
+ case ContentBlockingNotifier::eOpenerAfterUserInteraction:
+ return "user interaction"_ns;
+ default:
+ return "stroage access API"_ns;
+ }
+}
+
+/* static */
+void AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ AntiTrackingUtils::ComputeIsThirdPartyToTopWindow(aChannel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ Unused << loadInfo->SetStoragePermission(
+ AntiTrackingUtils::GetStoragePermissionStateInParent(aChannel));
+
+ // We only update the IsOnContentBlockingAllowList flag and the partition key
+ // for the top-level http channel.
+ //
+ // The IsOnContentBlockingAllowList is only for http. For other types of
+ // channels, such as 'file:', there will be no interface to modify this. So,
+ // we only update it in http channels.
+ //
+ // The partition key is computed based on the site, so it's no point to set it
+ // for channels other than http channels.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel || loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ return;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+
+ // Update the IsOnContentBlockingAllowList flag in the CookieJarSettings
+ // if this is a top level loading. For sub-document loading, this flag
+ // would inherit from the parent.
+ net::CookieJarSettings::Cast(cookieJarSettings)
+ ->UpdateIsOnContentBlockingAllowList(aChannel);
+
+ // We only need to set FPD for top-level loads. FPD will automatically be
+ // propagated to non-top level loads via CookieJarSetting.
+ nsCOMPtr<nsIURI> uri;
+ Unused << aChannel->GetURI(getter_AddRefs(uri));
+ net::CookieJarSettings::Cast(cookieJarSettings)->SetPartitionKey(uri);
+
+ // Generate the fingerprinting randomization key for top-level loads. The key
+ // will automatically be propagated to sub loads.
+ auto RFPRandomKey =
+ nsRFPService::GenerateKey(uri, NS_UsePrivateBrowsing(aChannel));
+ if (RFPRandomKey) {
+ net::CookieJarSettings::Cast(cookieJarSettings)
+ ->SetFingerprintingRandomizationKey(RFPRandomKey.ref());
+ }
+}