mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			362 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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<nsIPrincipal> 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<uint32_t>(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<dom::Document> 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<nsICookieJarSettings> cookieJarSettings = doc->CookieJarSettings();
 | 
						|
 | 
						|
  return ContentBlockingAllowList::Check(cookieJarSettings);
 | 
						|
}
 | 
						|
 | 
						|
/* static */ bool ContentBlockingAllowList::Check(nsIHttpChannel* aChannel) {
 | 
						|
  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
 | 
						|
  nsCOMPtr<nsICookieJarSettings> 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<const nsLiteralCString, bool> 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<nsIURI> uri;
 | 
						|
  rv = NS_NewURI(getter_AddRefs(uri), escaped);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIPrincipal> 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<nsIURI> uri;
 | 
						|
  rv = NS_NewURI(getter_AddRefs(uri), escaped);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIPrincipal> 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;
 | 
						|
}
 | 
						|
 | 
						|
nsTArray<nsCString>
 | 
						|
ContentBlockingAllowListCache::GetAllowListPermissionTypes() {
 | 
						|
  nsTArray<nsCString> types;
 | 
						|
  types.AppendElement("trackingprotection");
 | 
						|
  types.AppendElement("trackingprotection-pb");
 | 
						|
  return types;
 | 
						|
}
 | 
						|
 | 
						|
nsresult ContentBlockingAllowListCache::IsAllowListPermission(
 | 
						|
    nsIPermission* aPermission, bool* aResult) {
 | 
						|
  NS_ENSURE_ARG_POINTER(aPermission);
 | 
						|
  NS_ENSURE_ARG_POINTER(aResult);
 | 
						|
 | 
						|
  // Assert that the permission type matches the types returned by
 | 
						|
  // GetAllowListPermissionTypes.
 | 
						|
#ifdef DEBUG
 | 
						|
  nsAutoCString type;
 | 
						|
  nsresult rv = aPermission->GetType(type);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  MOZ_ASSERT(type.EqualsLiteral("trackingprotection") ||
 | 
						|
             type.EqualsLiteral("trackingprotection-pb"));
 | 
						|
#endif
 | 
						|
 | 
						|
  *aResult = true;
 | 
						|
  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<nsCString> types = GetAllowListPermissionTypes();
 | 
						|
 | 
						|
  nsTArray<RefPtr<nsIPermission>> 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);
 | 
						|
 | 
						|
    // Check if the permission is a suitable allow-list permission.
 | 
						|
    bool result = false;
 | 
						|
    nsresult rv = IsAllowListPermission(permission, &result);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    if (!result) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Permission is suitable, extract base domain from permission principal.
 | 
						|
    nsCOMPtr<nsIPrincipal> 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;
 | 
						|
}
 |