/* -*- 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 "AntiTrackingLog.h" #include "ContentBlockingAllowList.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/Document.h" #include "mozilla/BasePrincipal.h" #include "mozilla/PermissionManager.h" #include "mozilla/ScopeExit.h" #include "nsContentUtils.h" #include "nsGlobalWindowInner.h" #include "nsICookieJarSettings.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIPermission.h" #include "nsNetUtil.h" #include "nsString.h" using namespace mozilla; NS_IMPL_ISUPPORTS(ContentBlockingAllowList, nsIContentBlockingAllowList) NS_IMETHODIMP // Wrapper for the static ContentBlockingAllowList::ComputePrincipal method ContentBlockingAllowList::ComputeContentBlockingAllowListPrincipal( nsIPrincipal* aDocumentPrincipal, nsIPrincipal** aPrincipal) { NS_ENSURE_ARG_POINTER(aDocumentPrincipal); NS_ENSURE_ARG_POINTER(aPrincipal); nsCOMPtr principal; ContentBlockingAllowList::ComputePrincipal(aDocumentPrincipal, getter_AddRefs(principal)); NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); principal.forget(aPrincipal); return NS_OK; } /* static */ bool ContentBlockingAllowList::Check( nsIPrincipal* aTopWinPrincipal, bool aIsPrivateBrowsing) { bool isAllowed = false; nsresult rv = Check(aTopWinPrincipal, aIsPrivateBrowsing, isAllowed); if (NS_SUCCEEDED(rv) && isAllowed) { LOG( ("The top-level window is on the content blocking allow list, " "bail out early")); return true; } if (NS_FAILED(rv)) { LOG(("Checking the content blocking allow list for failed with %" PRIx32, static_cast(rv))); } return false; } /* static */ bool ContentBlockingAllowList::Check( nsICookieJarSettings* aCookieJarSettings) { if (!aCookieJarSettings) { LOG( ("Could not check the content blocking allow list because the cookie " "jar settings wasn't available")); return false; } return aCookieJarSettings->GetIsOnContentBlockingAllowList(); } /* static */ bool ContentBlockingAllowList::Check(nsPIDOMWindowInner* aWindow) { // TODO: this is a quick fix to ensure that we allow storage permission for // a chrome window. We should check if there is a better way to do this in // Bug 1626223. if (nsGlobalWindowInner::Cast(aWindow)->GetPrincipal() == nsContentUtils::GetSystemPrincipal()) { return true; } // We can check the IsOnContentBlockingAllowList flag in the document's // CookieJarSettings. Because this flag represents the fact that whether the // top-level document is on the content blocking allow list. And this flag was // propagated from the top-level as the CookieJarSettings inherits from the // parent. RefPtr doc = nsGlobalWindowInner::Cast(aWindow)->GetDocument(); if (!doc) { LOG( ("Could not check the content blocking allow list because the document " "wasn't available")); return false; } nsCOMPtr cookieJarSettings = doc->CookieJarSettings(); return ContentBlockingAllowList::Check(cookieJarSettings); } /* static */ bool ContentBlockingAllowList::Check(nsIHttpChannel* aChannel) { nsCOMPtr loadInfo = aChannel->LoadInfo(); nsCOMPtr cookieJarSettings; Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); return ContentBlockingAllowList::Check(cookieJarSettings); } nsresult ContentBlockingAllowList::Check( nsIPrincipal* aContentBlockingAllowListPrincipal, bool aIsPrivateBrowsing, bool& aIsAllowListed) { MOZ_ASSERT(XRE_IsParentProcess()); aIsAllowListed = false; if (!aContentBlockingAllowListPrincipal) { // Nothing to do! return NS_OK; } LOG_PRIN(("Deciding whether the user has overridden content blocking for %s", _spec), aContentBlockingAllowListPrincipal); PermissionManager* permManager = PermissionManager::GetInstance(); NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE); // Check both the normal mode and private browsing mode user override // permissions. std::pair types[] = { {"trackingprotection"_ns, false}, {"trackingprotection-pb"_ns, true}}; for (const auto& type : types) { if (aIsPrivateBrowsing != type.second) { continue; } uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION; nsresult rv = permManager->TestPermissionFromPrincipal( aContentBlockingAllowListPrincipal, type.first, &permissions); NS_ENSURE_SUCCESS(rv, rv); if (permissions == nsIPermissionManager::ALLOW_ACTION) { aIsAllowListed = true; LOG(("Found user override type %s", type.first.get())); // Stop checking the next permisson type if we decided to override. break; } } if (!aIsAllowListed) { LOG(("No user override found")); } return NS_OK; } /* static */ void ContentBlockingAllowList::ComputePrincipal( nsIPrincipal* aDocumentPrincipal, nsIPrincipal** aPrincipal) { MOZ_ASSERT(aPrincipal); auto returnInputArgument = MakeScopeExit([&] { NS_IF_ADDREF(*aPrincipal = aDocumentPrincipal); }); BasePrincipal* bp = BasePrincipal::Cast(aDocumentPrincipal); if (!bp || !bp->IsContentPrincipal()) { // If we have something other than a content principal, just return what we // have. This includes the case where we were passed a nullptr. return; } if (aDocumentPrincipal->SchemeIs("chrome") || aDocumentPrincipal->SchemeIs("about")) { returnInputArgument.release(); *aPrincipal = nullptr; return; } // Take the host/port portion so we can allowlist by site. Also ignore the // scheme, since users who put sites on the allowlist probably don't expect // allowlisting to depend on scheme. nsAutoCString escaped("https://"_ns); nsAutoCString temp; nsresult rv = aDocumentPrincipal->GetHostPort(temp); // view-source URIs will be handled by the next block. if (NS_FAILED(rv) && !aDocumentPrincipal->SchemeIs("view-source")) { // Normal for some loads, no need to print a warning return; } // GetHostPort returns an empty string (with a success error code) for file:// // URIs. if (temp.IsEmpty()) { // In this case we want to make sure that our allow list principal would be // computed as null. returnInputArgument.release(); *aPrincipal = nullptr; return; } escaped.Append(temp); nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), escaped); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr principal = BasePrincipal::CreateContentPrincipal( uri, aDocumentPrincipal->OriginAttributesRef()); if (NS_WARN_IF(!principal)) { return; } returnInputArgument.release(); principal.forget(aPrincipal); } /* static */ void ContentBlockingAllowList::RecomputePrincipal( nsIURI* aURIBeingLoaded, const OriginAttributes& aAttrs, nsIPrincipal** aPrincipal) { MOZ_ASSERT(aPrincipal); auto returnInputArgument = MakeScopeExit([&] { *aPrincipal = nullptr; }); // Take the host/port portion so we can allowlist by site. Also ignore the // scheme, since users who put sites on the allowlist probably don't expect // allowlisting to depend on scheme. nsAutoCString escaped("https://"_ns); nsAutoCString temp; nsresult rv = aURIBeingLoaded->GetHostPort(temp); // view-source URIs will be handled by the next block. if (NS_FAILED(rv) && !aURIBeingLoaded->SchemeIs("view-source")) { // Normal for some loads, no need to print a warning return; } // GetHostPort returns an empty string (with a success error code) for file:// // URIs. if (temp.IsEmpty()) { return; } escaped.Append(temp); nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), escaped); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr principal = BasePrincipal::CreateContentPrincipal(uri, aAttrs); if (NS_WARN_IF(!principal)) { return; } returnInputArgument.release(); principal.forget(aPrincipal); } // ContentBlockingAllowListCache nsresult ContentBlockingAllowListCache::CheckForBaseDomain( const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, bool& aIsAllowListed) { MOZ_ASSERT(XRE_IsParentProcess()); NS_ENSURE_TRUE(!aBaseDomain.IsEmpty(), NS_ERROR_INVALID_ARG); aIsAllowListed = false; // Ensure we have the permission list. nsresult rv = EnsureInit(); NS_ENSURE_SUCCESS(rv, rv); if (aOriginAttributes.mPrivateBrowsingId > 0) { aIsAllowListed = mEntriesPrivateBrowsing.Contains(aBaseDomain); } else { aIsAllowListed = mEntries.Contains(aBaseDomain); } return NS_OK; } nsresult ContentBlockingAllowListCache::EnsureInit() { MOZ_ASSERT(XRE_IsParentProcess()); if (mIsInitialized) { return NS_OK; } mIsInitialized = true; // 1. Get all permissions representing allow-list entries. PermissionManager* permManager = PermissionManager::GetInstance(); NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE); nsTArray types; types.AppendElement("trackingprotection"); types.AppendElement("trackingprotection-pb"); nsTArray> permissions; nsresult rv = permManager->GetAllByTypes(types, permissions); NS_ENSURE_SUCCESS(rv, rv); // 2. Populate mEntries and mEntriesPrivateBrowsing from permission list for // faster lookup. for (auto& permission : permissions) { MOZ_ASSERT(permission); nsCOMPtr principal; rv = permission->GetPrincipal(getter_AddRefs(principal)); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(principal); nsAutoCString baseDomain; rv = principal->GetBaseDomain(baseDomain); NS_ENSURE_SUCCESS(rv, rv); // Sort base domains into sets for normal / private browsing. if (principal->OriginAttributesRef().mPrivateBrowsingId > 0) { mEntriesPrivateBrowsing.Insert(baseDomain); } else { mEntries.Insert(baseDomain); } } return NS_OK; }