forked from mirrors/gecko-dev
803 lines
28 KiB
C++
803 lines
28 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 "AntiTrackingCommon.h"
|
|
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/ipc/MessageChannel.h"
|
|
#include "mozilla/AbstractThread.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/StaticPrefs.h"
|
|
#include "mozIThirdPartyUtil.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsICookiePermission.h"
|
|
#include "nsICookieService.h"
|
|
#include "nsIHttpChannelInternal.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsIPermissionManager.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURL.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsScriptSecurityManager.h"
|
|
#include "prtime.h"
|
|
|
|
#define ANTITRACKING_PERM_KEY "3rdPartyStorage"
|
|
|
|
using namespace mozilla;
|
|
using mozilla::dom::ContentChild;
|
|
|
|
static LazyLogModule gAntiTrackingLog("AntiTracking");
|
|
static const nsCString::size_type sMaxSpecLength = 128;
|
|
|
|
#define LOG(format) MOZ_LOG(gAntiTrackingLog, mozilla::LogLevel::Debug, format)
|
|
|
|
#define LOG_SPEC(format, uri) \
|
|
PR_BEGIN_MACRO \
|
|
if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) { \
|
|
nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)")); \
|
|
_specStr.Truncate(std::min(_specStr.Length(), sMaxSpecLength)); \
|
|
if (uri) { \
|
|
_specStr = uri->GetSpecOrDefault(); \
|
|
} \
|
|
const char* _spec = _specStr.get(); \
|
|
LOG(format); \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
namespace {
|
|
|
|
bool
|
|
GetParentPrincipalAndTrackingOrigin(nsGlobalWindowInner* a3rdPartyTrackingWindow,
|
|
nsIPrincipal** aTopLevelStoragePrincipal,
|
|
nsACString& aTrackingOrigin)
|
|
{
|
|
MOZ_ASSERT(nsContentUtils::IsTrackingResourceWindow(a3rdPartyTrackingWindow));
|
|
|
|
// Now we need the principal and the origin of the parent window.
|
|
nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal =
|
|
a3rdPartyTrackingWindow->GetTopLevelStorageAreaPrincipal();
|
|
if (NS_WARN_IF(!topLevelStoragePrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
// Let's take the principal and the origin of the tracker.
|
|
nsIPrincipal* trackingPrincipal = a3rdPartyTrackingWindow->GetPrincipal();
|
|
if (NS_WARN_IF(!trackingPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
nsresult rv = trackingPrincipal->GetOriginNoSuffix(aTrackingOrigin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
topLevelStoragePrincipal.forget(aTopLevelStoragePrincipal);
|
|
return true;
|
|
};
|
|
|
|
void
|
|
CreatePermissionKey(const nsCString& aTrackingOrigin,
|
|
const nsCString& aGrantedOrigin,
|
|
nsACString& aPermissionKey)
|
|
{
|
|
if (aTrackingOrigin == aGrantedOrigin) {
|
|
aPermissionKey = nsPrintfCString(ANTITRACKING_PERM_KEY "^%s",
|
|
aTrackingOrigin.get());
|
|
return;
|
|
}
|
|
|
|
aPermissionKey = nsPrintfCString(ANTITRACKING_PERM_KEY "^%s^%s",
|
|
aTrackingOrigin.get(),
|
|
aGrantedOrigin.get());
|
|
}
|
|
|
|
// This internal method returns ACCESS_DENY if the access is denied,
|
|
// ACCESS_DEFAULT if unknown, some other access code if granted.
|
|
nsCookieAccess
|
|
CheckCookiePermissionForPrincipal(nsIPrincipal* aPrincipal)
|
|
{
|
|
nsCookieAccess access = nsICookiePermission::ACCESS_DEFAULT;
|
|
if (!aPrincipal->GetIsCodebasePrincipal()) {
|
|
return access;
|
|
}
|
|
|
|
nsCOMPtr<nsICookiePermission> cps =
|
|
do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
|
|
if (NS_WARN_IF(!cps)) {
|
|
return access;
|
|
}
|
|
|
|
nsresult rv = cps->CanAccess(aPrincipal, &access);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return access;
|
|
}
|
|
|
|
// If we have a custom cookie permission, let's use it.
|
|
return access;
|
|
}
|
|
|
|
int32_t
|
|
CookiesBehavior(nsIPrincipal* aPrincipal)
|
|
{
|
|
// WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
|
|
// (See Bug 1406675 for rationale).
|
|
if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
|
|
return nsICookieService::BEHAVIOR_ACCEPT;
|
|
}
|
|
|
|
return StaticPrefs::network_cookie_cookieBehavior();
|
|
}
|
|
|
|
bool
|
|
CheckContentBlockingAllowList(nsIURI* aTopWinURI)
|
|
{
|
|
bool isAllowed = false;
|
|
nsresult rv =
|
|
AntiTrackingCommon::IsOnContentBlockingAllowList(aTopWinURI, isAllowed);
|
|
if (NS_SUCCEEDED(rv) && isAllowed) {
|
|
LOG_SPEC(("The top-level window (%s) is on the content blocking allow list, "
|
|
"bail out early", _spec), aTopWinURI);
|
|
return true;
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
LOG_SPEC(("Checking the content blocking allow list for %s failed with %x",
|
|
_spec, rv), aTopWinURI);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CheckContentBlockingAllowList(nsPIDOMWindowInner* aWindow)
|
|
{
|
|
nsPIDOMWindowOuter* top = aWindow->GetScriptableTop();
|
|
if (top) {
|
|
nsIURI* topWinURI = top->GetDocumentURI();
|
|
return CheckContentBlockingAllowList(topWinURI);
|
|
}
|
|
|
|
LOG(("Could not check the content blocking allow list because the top "
|
|
"window wasn't accessible"));
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CheckContentBlockingAllowList(nsIHttpChannel* aChannel)
|
|
{
|
|
nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel);
|
|
if (chan) {
|
|
nsCOMPtr<nsIURI> topWinURI;
|
|
nsresult rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return CheckContentBlockingAllowList(topWinURI);
|
|
}
|
|
}
|
|
|
|
LOG(("Could not check the content blocking allow list because the top "
|
|
"window wasn't accessible"));
|
|
return false;
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
/* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
|
|
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigin,
|
|
nsPIDOMWindowInner* aParentWindow)
|
|
{
|
|
MOZ_ASSERT(aParentWindow);
|
|
|
|
LOG(("Adding a first-party storage exception for %s...",
|
|
NS_ConvertUTF16toUTF8(aOrigin).get()));
|
|
|
|
if (StaticPrefs::network_cookie_cookieBehavior() !=
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER) {
|
|
LOG(("Disabled by network.cookie.cookieBehavior pref (%d), bailing out early",
|
|
StaticPrefs::network_cookie_cookieBehavior()));
|
|
return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
if (!StaticPrefs::browser_contentblocking_enabled()) {
|
|
LOG(("The content blocking pref has been disabled, bail out early"));
|
|
return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
if (CheckContentBlockingAllowList(aParentWindow)) {
|
|
return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal;
|
|
nsAutoCString trackingOrigin;
|
|
|
|
nsGlobalWindowInner* parentWindow = nsGlobalWindowInner::Cast(aParentWindow);
|
|
nsGlobalWindowOuter* outerParentWindow =
|
|
nsGlobalWindowOuter::Cast(parentWindow->GetOuterWindow());
|
|
if (NS_WARN_IF(!outerParentWindow)) {
|
|
LOG(("No outer window found for our parent window, bailing out early"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
LOG(("The current resource is %s-party",
|
|
outerParentWindow->IsTopLevelWindow() ? "first" : "third"));
|
|
|
|
// We are a first party resource.
|
|
if (outerParentWindow->IsTopLevelWindow()) {
|
|
CopyUTF16toUTF8(aOrigin, trackingOrigin);
|
|
topLevelStoragePrincipal = parentWindow->GetPrincipal();
|
|
if (NS_WARN_IF(!topLevelStoragePrincipal)) {
|
|
LOG(("Top-level storage area principal not found, bailing out early"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
// We are a 3rd party source.
|
|
} else if (!GetParentPrincipalAndTrackingOrigin(parentWindow,
|
|
getter_AddRefs(topLevelStoragePrincipal),
|
|
trackingOrigin)) {
|
|
LOG(("Error while computing the parent principal and tracking origin, bailing out early"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 grantedOrigin(aOrigin);
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
|
|
trackingOrigin.get(), grantedOrigin.get()));
|
|
|
|
RefPtr<StorageAccessGrantPromise::Private> p = new StorageAccessGrantPromise::Private(__func__);
|
|
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
|
|
trackingOrigin,
|
|
grantedOrigin,
|
|
[p] (bool success) {
|
|
p->Resolve(success, __func__);
|
|
});
|
|
return p;
|
|
}
|
|
|
|
ContentChild* cc = ContentChild::GetSingleton();
|
|
MOZ_ASSERT(cc);
|
|
|
|
LOG(("Asking the parent process to save the permission for us: trackingOrigin=%s, grantedOrigin=%s",
|
|
trackingOrigin.get(), grantedOrigin.get()));
|
|
|
|
// This is not really secure, because here we have the content process sending
|
|
// the request of storing a permission.
|
|
RefPtr<StorageAccessGrantPromise::Private> p = new StorageAccessGrantPromise::Private(__func__);
|
|
cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
|
|
trackingOrigin,
|
|
grantedOrigin)
|
|
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
|
[p] (bool success) {
|
|
p->Resolve(success, __func__);
|
|
}, [p] (ipc::ResponseRejectReason aReason) {
|
|
p->Reject(false, __func__);
|
|
});
|
|
return p;
|
|
}
|
|
|
|
/* static */ void
|
|
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aParentPrincipal,
|
|
const nsCString& aTrackingOrigin,
|
|
const nsCString& aGrantedOrigin,
|
|
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << aParentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
LOG_SPEC(("Saving a first-party storage permission on %s for trackingOrigin=%s grantedOrigin=%s",
|
|
_spec, aTrackingOrigin.get(), aGrantedOrigin.get()), parentPrincipalURI);
|
|
|
|
if (NS_WARN_IF(!aParentPrincipal)) {
|
|
// The child process is sending something wrong. Let's ignore it.
|
|
LOG(("aParentPrincipal is null, bailing out early"));
|
|
aResolver(false);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
|
|
if (NS_WARN_IF(!pm)) {
|
|
LOG(("Permission manager is null, bailing out early"));
|
|
aResolver(false);
|
|
return;
|
|
}
|
|
|
|
// Remember that this pref is stored in seconds!
|
|
uint32_t expirationTime =
|
|
StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000;
|
|
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);
|
|
|
|
LOG(("Computed permission key: %s, expiry: %d, proceeding to save in the permission manager",
|
|
type.get(), expirationTime));
|
|
|
|
nsresult rv = pm->AddFromPrincipal(aParentPrincipal, type.get(),
|
|
nsIPermissionManager::ALLOW_ACTION,
|
|
nsIPermissionManager::EXPIRE_TIME, when);
|
|
Unused << NS_WARN_IF(NS_FAILED(rv));
|
|
aResolver(NS_SUCCEEDED(rv));
|
|
|
|
LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
|
|
}
|
|
|
|
bool
|
|
AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* aWindow,
|
|
nsIURI* aURI)
|
|
{
|
|
MOZ_ASSERT(aWindow);
|
|
MOZ_ASSERT(aURI);
|
|
|
|
LOG_SPEC(("Computing whether window %p has access to URI %s", aWindow, _spec), aURI);
|
|
|
|
nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow);
|
|
nsIPrincipal* toplevelPrincipal = innerWindow->GetTopLevelPrincipal();
|
|
if (!toplevelPrincipal) {
|
|
// We are already the top-level principal. Let's use the window's principal.
|
|
LOG(("Our inner window lacks a top-level principal, use the window's principal instead"));
|
|
toplevelPrincipal = innerWindow->GetPrincipal();
|
|
}
|
|
|
|
if (!toplevelPrincipal) {
|
|
// This should not be possible, right?
|
|
LOG(("No top-level principal, this shouldn't be happening! Bail out early"));
|
|
return false;
|
|
}
|
|
|
|
nsCookieAccess access = CheckCookiePermissionForPrincipal(toplevelPrincipal);
|
|
if (access != nsICookiePermission::ACCESS_DEFAULT) {
|
|
LOG(("CheckCookiePermissionForPrincipal() returned a non-default access code (%d), returning %s",
|
|
int(access), access != nsICookiePermission::ACCESS_DENY ?
|
|
"success" : "failure"));
|
|
return access != nsICookiePermission::ACCESS_DENY;
|
|
}
|
|
|
|
int32_t behavior = CookiesBehavior(toplevelPrincipal);
|
|
if (behavior == nsICookieService::BEHAVIOR_ACCEPT) {
|
|
LOG(("The cookie behavior pref mandates accepting all cookies!"));
|
|
return true;
|
|
}
|
|
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT) {
|
|
LOG(("The cookie behavior pref mandates rejecting all cookies!"));
|
|
return false;
|
|
}
|
|
|
|
// Let's check if this is a 3rd party context.
|
|
if (!nsContentUtils::IsThirdPartyWindowOrChannel(aWindow, nullptr, aURI)) {
|
|
LOG(("Our window isn't a third-party window"));
|
|
return true;
|
|
}
|
|
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
|
|
behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
|
|
// XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
|
|
// simply rejecting the request to use the storage. In the future, if we
|
|
// change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
|
|
// for non-cookie storage types, this may change.
|
|
LOG(("Nothing more to do due to the behavior code %d", int(behavior)));
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER);
|
|
|
|
// Now, we have to also honour the Content Blocking pref.
|
|
if (!StaticPrefs::browser_contentblocking_enabled()) {
|
|
LOG(("The content blocking pref has been disabled, bail out early by "
|
|
"by pretending our window isn't a tracking window"));
|
|
return true;
|
|
}
|
|
|
|
if (CheckContentBlockingAllowList(aWindow)) {
|
|
return true;
|
|
}
|
|
|
|
if (!nsContentUtils::IsTrackingResourceWindow(aWindow)) {
|
|
LOG(("Our window isn't a tracking window"));
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> parentPrincipal;
|
|
nsAutoCString trackingOrigin;
|
|
if (!GetParentPrincipalAndTrackingOrigin(nsGlobalWindowInner::Cast(aWindow),
|
|
getter_AddRefs(parentPrincipal),
|
|
trackingOrigin)) {
|
|
LOG(("Failed to obtain the parent principal and the tracking origin"));
|
|
return false;
|
|
}
|
|
|
|
nsAutoString origin;
|
|
nsresult rv = nsContentUtils::GetUTFOrigin(aURI, origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
|
|
return false;
|
|
}
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(trackingOrigin, NS_ConvertUTF16toUTF8(origin), type);
|
|
|
|
nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
|
|
if (NS_WARN_IF(!pm)) {
|
|
LOG(("Failed to obtain the permission manager"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t result = 0;
|
|
rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to test the permission"));
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)",
|
|
type.get(), _spec, int(result),
|
|
result == nsIPermissionManager::ALLOW_ACTION ?
|
|
"success" : "failure"), parentPrincipalURI);
|
|
|
|
return result == nsIPermissionManager::ALLOW_ACTION;
|
|
}
|
|
|
|
bool
|
|
AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsIHttpChannel* aChannel,
|
|
nsIURI* aURI)
|
|
{
|
|
MOZ_ASSERT(aURI);
|
|
MOZ_ASSERT(aChannel);
|
|
|
|
nsCOMPtr<nsIURI> channelURI;
|
|
Unused << aChannel->GetURI(getter_AddRefs(channelURI));
|
|
LOG_SPEC(("Computing whether channel %p has access to URI %s", aChannel, _spec),
|
|
channelURI);
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
|
|
if (!loadInfo) {
|
|
LOG(("No loadInfo, bail out early"));
|
|
return true;
|
|
}
|
|
|
|
// We need to find the correct principal to check the cookie permission. For
|
|
// third-party contexts, we want to check if the top-level window has a custom
|
|
// cookie permission.
|
|
nsIPrincipal* toplevelPrincipal = loadInfo->TopLevelPrincipal();
|
|
|
|
// If this is already the top-level window, we should use the loading
|
|
// principal.
|
|
if (!toplevelPrincipal) {
|
|
LOG(("Our loadInfo lacks a top-level principal, use the loadInfo's loading principal instead"));
|
|
toplevelPrincipal = loadInfo->LoadingPrincipal();
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
|
nsIScriptSecurityManager* ssm = nsScriptSecurityManager::GetScriptSecurityManager();
|
|
nsresult rv = ssm->GetChannelResultPrincipal(aChannel,
|
|
getter_AddRefs(channelPrincipal));
|
|
|
|
// If we don't have a loading principal and this is a document channel, we are
|
|
// a top-level window!
|
|
if (!toplevelPrincipal) {
|
|
LOG(("We don't have a loading principal, let's see if this is a document channel"
|
|
" that belongs to a top-level window"));
|
|
bool isDocument = false;
|
|
nsresult rv2 = aChannel->GetIsMainDocumentChannel(&isDocument);
|
|
if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && isDocument) {
|
|
toplevelPrincipal = channelPrincipal;
|
|
LOG(("Yes, we guessed right!"));
|
|
} else {
|
|
LOG(("No, we guessed wrong!"));
|
|
}
|
|
}
|
|
|
|
// Let's use the triggering principal then.
|
|
if (!toplevelPrincipal) {
|
|
LOG(("Our loadInfo lacks a top-level principal, use the loadInfo's triggering principal instead"));
|
|
toplevelPrincipal = loadInfo->TriggeringPrincipal();
|
|
}
|
|
|
|
if (NS_WARN_IF(!toplevelPrincipal)) {
|
|
LOG(("No top-level principal! Bail out early"));
|
|
return false;
|
|
}
|
|
|
|
nsCookieAccess access = CheckCookiePermissionForPrincipal(toplevelPrincipal);
|
|
if (access != nsICookiePermission::ACCESS_DEFAULT) {
|
|
LOG(("CheckCookiePermissionForPrincipal() returned a non-default access code (%d), returning %s",
|
|
int(access), access != nsICookiePermission::ACCESS_DENY ?
|
|
"success" : "failure"));
|
|
return access != nsICookiePermission::ACCESS_DENY;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv) || !channelPrincipal)) {
|
|
LOG(("No channel principal, bail out early"));
|
|
return false;
|
|
}
|
|
|
|
int32_t behavior = CookiesBehavior(toplevelPrincipal);
|
|
if (behavior == nsICookieService::BEHAVIOR_ACCEPT) {
|
|
LOG(("The cookie behavior pref mandates accepting all cookies!"));
|
|
return true;
|
|
}
|
|
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT) {
|
|
LOG(("The cookie behavior pref mandates rejecting all cookies!"));
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
|
|
if (!thirdPartyUtil) {
|
|
LOG(("No thirdPartyUtil, bail out early"));
|
|
return true;
|
|
}
|
|
|
|
bool thirdParty = false;
|
|
Unused << thirdPartyUtil->IsThirdPartyChannel(aChannel,
|
|
nullptr,
|
|
&thirdParty);
|
|
// Grant if it's not a 3rd party.
|
|
if (!thirdParty) {
|
|
LOG(("Our channel isn't a third-party channel"));
|
|
return true;
|
|
}
|
|
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
|
|
behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
|
|
// XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
|
|
// simply rejecting the request to use the storage. In the future, if we
|
|
// change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
|
|
// for non-cookie storage types, this may change.
|
|
LOG(("Nothing more to do due to the behavior code %d", int(behavior)));
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER);
|
|
|
|
// Now, we have to also honour the Content Blocking pref.
|
|
if (!StaticPrefs::browser_contentblocking_enabled()) {
|
|
LOG(("The content blocking pref has been disabled, bail out early by "
|
|
"pretending our channel isn't a tracking channel"));
|
|
return true;
|
|
}
|
|
|
|
if (CheckContentBlockingAllowList(aChannel)) {
|
|
return true;
|
|
}
|
|
|
|
nsIPrincipal* parentPrincipal = loadInfo->TopLevelStorageAreaPrincipal();
|
|
if (!parentPrincipal) {
|
|
LOG(("No top-level storage area principal at hand"));
|
|
|
|
// parentPrincipal can be null if the parent window is not the top-level
|
|
// window.
|
|
if (loadInfo->TopLevelPrincipal()) {
|
|
LOG(("Parent window is the top-level window, bail out early"));
|
|
return false;
|
|
}
|
|
|
|
parentPrincipal = toplevelPrincipal;
|
|
if (NS_WARN_IF(!parentPrincipal)) {
|
|
LOG(("No triggering principal, this shouldn't be happening! Bail out early"));
|
|
// Why we are here?!?
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Not a tracker.
|
|
if (!aChannel->GetIsTrackingResource()) {
|
|
LOG(("Our channel isn't a tracking channel"));
|
|
return true;
|
|
}
|
|
|
|
// Let's see if we have to grant the access for this particular channel.
|
|
|
|
nsCOMPtr<nsIURI> trackingURI;
|
|
rv = aChannel->GetURI(getter_AddRefs(trackingURI));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to get the channel URI"));
|
|
return true;
|
|
}
|
|
|
|
nsAutoString trackingOrigin;
|
|
rv = nsContentUtils::GetUTFOrigin(trackingURI, trackingOrigin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG_SPEC(("Failed to compute the origin from %s", _spec), trackingURI);
|
|
return false;
|
|
}
|
|
|
|
nsAutoString origin;
|
|
rv = nsContentUtils::GetUTFOrigin(aURI, origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
|
|
return false;
|
|
}
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(NS_ConvertUTF16toUTF8(trackingOrigin),
|
|
NS_ConvertUTF16toUTF8(origin), type);
|
|
|
|
nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
|
|
if (NS_WARN_IF(!pm)) {
|
|
LOG(("Failed to obtain the permission manager"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t result = 0;
|
|
rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to test the permission"));
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)",
|
|
type.get(), _spec, int(result),
|
|
result == nsIPermissionManager::ALLOW_ACTION ?
|
|
"success" : "failure"), parentPrincipalURI);
|
|
|
|
return result == nsIPermissionManager::ALLOW_ACTION;
|
|
}
|
|
|
|
bool
|
|
AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal)
|
|
{
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
nsCookieAccess access = CheckCookiePermissionForPrincipal(aPrincipal);
|
|
if (access != nsICookiePermission::ACCESS_DEFAULT) {
|
|
return access != nsICookiePermission::ACCESS_DENY;
|
|
}
|
|
|
|
int32_t behavior = CookiesBehavior(aPrincipal);
|
|
return behavior != nsICookieService::BEHAVIOR_REJECT;
|
|
}
|
|
|
|
/* static */ bool
|
|
AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* aFirstPartyWindow,
|
|
nsIURI* aURI)
|
|
{
|
|
MOZ_ASSERT(aFirstPartyWindow);
|
|
MOZ_ASSERT(!nsContentUtils::IsTrackingResourceWindow(aFirstPartyWindow));
|
|
MOZ_ASSERT(aURI);
|
|
|
|
LOG_SPEC(("Computing a best guess as to whether window %p has access to URI %s",
|
|
aFirstPartyWindow, _spec), aURI);
|
|
|
|
if (StaticPrefs::network_cookie_cookieBehavior() !=
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER) {
|
|
LOG(("Disabled by the pref (%d), bail out early",
|
|
StaticPrefs::network_cookie_cookieBehavior()));
|
|
return true;
|
|
}
|
|
|
|
// Now, we have to also honour the Content Blocking pref.
|
|
if (!StaticPrefs::browser_contentblocking_enabled()) {
|
|
LOG(("The content blocking pref has been disabled, bail out early"));
|
|
return true;
|
|
}
|
|
|
|
if (CheckContentBlockingAllowList(aFirstPartyWindow)) {
|
|
return true;
|
|
}
|
|
|
|
if (!nsContentUtils::IsThirdPartyWindowOrChannel(aFirstPartyWindow,
|
|
nullptr, aURI)) {
|
|
LOG(("Our window isn't a third-party window"));
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> parentPrincipal =
|
|
nsGlobalWindowInner::Cast(aFirstPartyWindow)->GetPrincipal();
|
|
if (NS_WARN_IF(!parentPrincipal)) {
|
|
LOG(("Failed to get the first party window's principal"));
|
|
return false;
|
|
}
|
|
|
|
nsCookieAccess access = CheckCookiePermissionForPrincipal(parentPrincipal);
|
|
if (access != nsICookiePermission::ACCESS_DEFAULT) {
|
|
LOG(("CheckCookiePermissionForPrincipal() returned a non-default access code (%d), returning %s",
|
|
int(access), access != nsICookiePermission::ACCESS_DENY ?
|
|
"success" : "failure"));
|
|
return access != nsICookiePermission::ACCESS_DENY;
|
|
}
|
|
|
|
nsAutoString origin;
|
|
nsresult rv = nsContentUtils::GetUTFOrigin(aURI, origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
|
|
return false;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 utf8Origin(origin);
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(utf8Origin, utf8Origin, type);
|
|
|
|
nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
|
|
if (NS_WARN_IF(!pm)) {
|
|
LOG(("Failed to obtain the permission manager"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t result = 0;
|
|
rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to test the permission"));
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)",
|
|
type.get(), _spec, int(result),
|
|
result == nsIPermissionManager::ALLOW_ACTION ?
|
|
"success" : "failure"), parentPrincipalURI);
|
|
|
|
return result == nsIPermissionManager::ALLOW_ACTION;
|
|
}
|
|
|
|
nsresult
|
|
AntiTrackingCommon::IsOnContentBlockingAllowList(nsIURI* aTopWinURI,
|
|
bool& aIsAllowListed)
|
|
{
|
|
aIsAllowListed = false;
|
|
|
|
LOG_SPEC(("Deciding whether the user has overridden content blocking for %s",
|
|
_spec), aTopWinURI);
|
|
|
|
nsCOMPtr<nsIIOService> ios = services::GetIOService();
|
|
NS_ENSURE_TRUE(ios, NS_ERROR_FAILURE);
|
|
|
|
// 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.
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
nsCOMPtr<nsIURL> url = do_QueryInterface(aTopWinURI, &rv);
|
|
if (NS_FAILED(rv)) {
|
|
return rv; // normal for some loads, no need to print a warning
|
|
}
|
|
|
|
nsCString escaped(NS_LITERAL_CSTRING("https://"));
|
|
nsAutoCString temp;
|
|
rv = url->GetHostPort(temp);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
escaped.Append(temp);
|
|
|
|
// Stuff the whole thing back into a URI for the permission manager.
|
|
nsCOMPtr<nsIURI> topWinURI;
|
|
rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIPermissionManager> permMgr =
|
|
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Check both the normal mode and private browsing mode user override permissions.
|
|
const char* types[] = {
|
|
"trackingprotection",
|
|
"trackingprotection-pb"
|
|
};
|
|
|
|
for (size_t i = 0; i < ArrayLength(types); ++i) {
|
|
uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
|
|
rv = permMgr->TestPermission(topWinURI, types[i], &permissions);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (permissions == nsIPermissionManager::ALLOW_ACTION) {
|
|
aIsAllowListed = true;
|
|
LOG_SPEC(("Found user override type %s for %s", types[i], _spec),
|
|
topWinURI);
|
|
// Stop checking the next permisson type if we decided to override.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!aIsAllowListed) {
|
|
LOG(("No user override found"));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|