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(NS_IsMainThread());
|
|
aIsAllowListed = false;
|
|
|
|
if (!aContentBlockingAllowListPrincipal) {
|
|
// Nothing to do!
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG_PRIN(("Deciding whether the user has overridden content blocking for %s",
|
|
_spec),
|
|
aContentBlockingAllowListPrincipal);
|
|
|
|
RefPtr<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.IsPrivateBrowsing()) {
|
|
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.
|
|
RefPtr<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().IsPrivateBrowsing()) {
|
|
mEntriesPrivateBrowsing.Insert(baseDomain);
|
|
} else {
|
|
mEntries.Insert(baseDomain);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|