/* -*- 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/IntegerPrintfMacros.h" #include "mozilla/Logging.h" #include "mozilla/Pair.h" #include "mozilla/StaticPrefs.h" #include "mozIThirdPartyUtil.h" #include "nsContentUtils.h" #include "nsGlobalWindowInner.h" #include "nsCookiePermission.h" #include "nsICookieService.h" #include "nsIDocShell.h" #include "nsIEffectiveTLDService.h" #include "nsIHttpChannelInternal.h" #include "nsIIOService.h" #include "nsIParentChannel.h" #include "nsIPermission.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" #include "nsIScriptError.h" #include "nsIURI.h" #include "nsIURIFixup.h" #include "nsIURL.h" #include "nsIWebProgressListener.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "nsPrintfCString.h" #include "nsScriptSecurityManager.h" #include "nsSandboxFlags.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, nsIURI** aTrackingURI, nsIPrincipal** aTrackingPrincipal) { if (!nsContentUtils::IsThirdPartyTrackingResourceWindow(a3rdPartyTrackingWindow)) { return false; } nsIDocument* doc = a3rdPartyTrackingWindow->GetDocument(); // Make sure storage access isn't disabled if (doc && ((doc->GetSandboxFlags() & SANDBOXED_STORAGE_ACCESS) != 0 || nsContentUtils::IsInPrivateBrowsing(doc))) { return false; } // Now we need the principal and the origin of the parent window. nsCOMPtr topLevelStoragePrincipal = a3rdPartyTrackingWindow->GetTopLevelStorageAreaPrincipal(); if (!topLevelStoragePrincipal) { LOG(("No top-level storage area principal at hand")); return false; } // Let's take the principal and the origin of the tracker. nsCOMPtr trackingPrincipal = a3rdPartyTrackingWindow->GetPrincipal(); if (NS_WARN_IF(!trackingPrincipal)) { return false; } nsCOMPtr trackingURI; nsresult rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI)); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = trackingPrincipal->GetOriginNoSuffix(aTrackingOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } topLevelStoragePrincipal.forget(aTopLevelStoragePrincipal); if (aTrackingURI) { trackingURI.forget(aTrackingURI); } if (aTrackingPrincipal) { trackingPrincipal.forget(aTrackingPrincipal); } 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 cps = nsCookiePermission::GetOrCreate(); 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* aTopLevelPrincipal, nsIPrincipal* a3rdPartyPrincipal) { // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior // (See Bug 1406675 for rationale). if (BasePrincipal::Cast(aTopLevelPrincipal)->AddonPolicy()) { return nsICookieService::BEHAVIOR_ACCEPT; } if (a3rdPartyPrincipal && BasePrincipal::Cast(a3rdPartyPrincipal)->AddonPolicy()) { return nsICookieService::BEHAVIOR_ACCEPT; } return StaticPrefs::network_cookie_cookieBehavior(); } bool CheckContentBlockingAllowList(nsIURI* aTopWinURI, bool aIsPrivateBrowsing) { bool isAllowed = false; nsresult rv = AntiTrackingCommon::IsOnContentBlockingAllowList(aTopWinURI, aIsPrivateBrowsing, AntiTrackingCommon::eStorageChecks, 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 %" PRIx32, _spec, static_cast(rv)), aTopWinURI); } return false; } bool CheckContentBlockingAllowList(nsPIDOMWindowInner* aWindow) { nsPIDOMWindowOuter* top = aWindow->GetScriptableTop(); if (top) { nsIURI* topWinURI = top->GetDocumentURI(); nsIDocument* doc = top->GetExtantDoc(); bool isPrivateBrowsing = doc ? nsContentUtils::IsInPrivateBrowsing(doc) : false; return CheckContentBlockingAllowList(topWinURI, isPrivateBrowsing); } LOG(("Could not check the content blocking allow list because the top " "window wasn't accessible")); return false; } bool CheckContentBlockingAllowList(nsIHttpChannel* aChannel) { nsCOMPtr chan = do_QueryInterface(aChannel); if (chan) { nsCOMPtr topWinURI; nsresult rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI)); if (NS_SUCCEEDED(rv)) { return CheckContentBlockingAllowList(topWinURI, NS_UsePrivateBrowsing(aChannel)); } } LOG(("Could not check the content blocking allow list because the top " "window wasn't accessible")); return false; } void ReportBlockingToConsole(nsPIDOMWindowOuter* aWindow, nsIURI* aURI, uint32_t aRejectedReason) { MOZ_ASSERT(aWindow && aURI); MOZ_ASSERT(aRejectedReason == 0 || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN || aRejectedReason == nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT); nsCOMPtr docShell = aWindow->GetDocShell(); if (NS_WARN_IF(!docShell)) { return; } nsCOMPtr doc = docShell->GetDocument(); if (NS_WARN_IF(!doc)) { return; } const char* message = nullptr; switch (aRejectedReason) { case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION: message = "CookieBlockedByPermission"; break; case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER: message = "CookieBlockedTracker"; break; case nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL: message = "CookieBlockedAll"; break; case nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN: message = "CookieBlockedForeign"; break; case nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT: message = "CookieBlockedSlowTrackingContent"; break; default: return; } MOZ_ASSERT(message); // Strip the URL of any possible username/password and make it ready to be // presented in the UI. nsCOMPtr urifixup = services::GetURIFixup(); NS_ENSURE_TRUE_VOID(urifixup); nsCOMPtr exposableURI; nsresult rv = urifixup->CreateExposableURI(aURI, getter_AddRefs(exposableURI)); NS_ENSURE_SUCCESS_VOID(rv); NS_ConvertUTF8toUTF16 spec(exposableURI->GetSpecOrDefault()); const char16_t* params[] = { spec.get() }; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Content Blocking"), doc, nsContentUtils::eNECKO_PROPERTIES, message, params, ArrayLength(params)); } void ReportUnblockingConsole(nsPIDOMWindowInner* aWindow, const nsAString& aTrackingOrigin, const nsAString& aGrantedOrigin, AntiTrackingCommon::StorageAccessGrantedReason aReason) { nsCOMPtr principal = nsGlobalWindowInner::Cast(aWindow)->GetPrincipal(); if (NS_WARN_IF(!principal)) { return; } nsAutoString origin; nsresult rv = nsContentUtils::GetUTFOrigin(principal, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr docShell = aWindow->GetDocShell(); if (NS_WARN_IF(!docShell)) { return; } nsCOMPtr doc = docShell->GetDocument(); if (NS_WARN_IF(!doc)) { return; } const char16_t* params[] = { origin.BeginReading(), aTrackingOrigin.BeginReading(), aGrantedOrigin.BeginReading() }; const char* messageWithDifferentOrigin = nullptr; const char *messageWithSameOrigin = nullptr; switch (aReason) { case AntiTrackingCommon::eStorageAccessAPI: messageWithDifferentOrigin = "CookieAllowedForOriginOnTrackerByStorageAccessAPI"; messageWithSameOrigin = "CookieAllowedForTrackerByStorageAccessAPI"; break; case AntiTrackingCommon::eOpenerAfterUserInteraction: MOZ_FALLTHROUGH; case AntiTrackingCommon::eOpener: messageWithDifferentOrigin = "CookieAllowedForOriginOnTrackerByHeuristic"; messageWithSameOrigin = "CookieAllowedForTrackerByHeuristic"; break; } if (aTrackingOrigin == aGrantedOrigin) { nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Content Blocking"), doc, nsContentUtils::eNECKO_PROPERTIES, messageWithSameOrigin, params, 2); } else { nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Content Blocking"), doc, nsContentUtils::eNECKO_PROPERTIES, messageWithDifferentOrigin, params, 3); } } already_AddRefed GetTopWindow(nsPIDOMWindowInner* aWindow) { nsIDocument* document = aWindow->GetExtantDoc(); if (!document) { return nullptr; } nsIChannel* channel = document->GetChannel(); if (!channel) { return nullptr; } nsCOMPtr pwin; auto* outer = nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow()); if (outer) { pwin = outer->GetTopOuter(); } if (!pwin) { return nullptr; } return pwin.forget(); } bool CompareBaseDomains(nsIURI* aTrackingURI, nsIURI* aParentPrincipalBaseURI) { nsCOMPtr eTLDService = services::GetEffectiveTLDService(); if (NS_WARN_IF(!eTLDService)) { LOG(("Failed to get the TLD service")); return false; } nsAutoCString trackingBaseDomain; nsAutoCString parentPrincipalBaseDomain; nsresult rv = eTLDService->GetBaseDomain(aTrackingURI, 0, trackingBaseDomain); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("Can't get the base domain from tracking URI")); return false; } rv = eTLDService->GetBaseDomain(aParentPrincipalBaseURI, 0, parentPrincipalBaseDomain); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("Can't get the base domain from parent principal")); return false; } return trackingBaseDomain.Equals(parentPrincipalBaseDomain, nsCaseInsensitiveCStringComparator()); } } // anonymous /* static */ RefPtr AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aParentWindow, StorageAccessGrantedReason aReason, const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks) { MOZ_ASSERT(aParentWindow); nsCOMPtr uri; aPrincipal->GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(!uri)) { LOG(("Can't get the URI from the principal")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } nsAutoString origin; nsresult rv = nsContentUtils::GetUTFOrigin(uri, origin); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("Can't get the origin from the URI")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } LOG(("Adding a first-party storage exception for %s...", NS_ConvertUTF16toUTF8(origin).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 (CheckContentBlockingAllowList(aParentWindow)) { return StorageAccessGrantPromise::CreateAndResolve(true, __func__); } nsCOMPtr topLevelStoragePrincipal; nsCOMPtr trackingURI; nsAutoCString trackingOrigin; nsCOMPtr trackingPrincipal; RefPtr 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(origin, trackingOrigin); trackingPrincipal = aPrincipal; rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI)); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("Couldn't get the tracking principal URI")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } 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, getter_AddRefs(trackingURI), getter_AddRefs(trackingPrincipal))) { LOG(("Error while computing the parent principal and tracking origin, bailing out early")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } nsCOMPtr topOuterWindow = outerParentWindow->GetTop(); nsGlobalWindowOuter* topWindow = nsGlobalWindowOuter::Cast(topOuterWindow); if (NS_WARN_IF(!topWindow)) { LOG(("No top outer window.")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } nsPIDOMWindowInner* topInnerWindow = topWindow->GetCurrentInnerWindow(); if (NS_WARN_IF(!topInnerWindow)) { LOG(("No top inner window.")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } // We hardcode this block reason since the first-party storage access // permission is granted for the purpose of blocking trackers. // Note that if aReason is eOpenerAfterUserInteraction and the // trackingPrincipal is not in a blacklist, we don't check the // user-interaction state, because it could be that the current process has // just sent the request to store the user-interaction permission into the // parent, without having received the permission itself yet. // // We define this as an enum, since without that MSVC fails to capturing this // name inside the lambda without the explicit capture and clang warns if // there is an explicit capture with -Wunused-lambda-capture. enum : uint32_t { blockReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER }; if ((aReason != eOpenerAfterUserInteraction || nsContentUtils::IsURIInPrefList(trackingURI, "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts")) && !HasUserInteraction(trackingPrincipal)) { LOG_SPEC(("Tracking principal (%s) hasn't been interacted with before, " "refusing to add a first-party storage permission to access it", _spec), trackingURI); NotifyBlockingDecision(aParentWindow, BlockingDecision::eBlock, blockReason); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } nsCOMPtr pwin = GetTopWindow(parentWindow); if (!pwin) { LOG(("Couldn't get the top window")); return StorageAccessGrantPromise::CreateAndReject(false, __func__); } auto storePermission = [pwin, parentWindow, origin, trackingOrigin, trackingPrincipal, trackingURI, topInnerWindow, topLevelStoragePrincipal, aReason] (bool aAnySite) -> RefPtr { NS_ConvertUTF16toUTF8 grantedOrigin(origin); nsAutoCString permissionKey; CreatePermissionKey(trackingOrigin, grantedOrigin, permissionKey); // Let's store the permission in the current parent window. topInnerWindow->SaveStorageAccessGranted(permissionKey); // Let's inform the parent window. parentWindow->StorageAccessGranted(); nsIChannel* channel = pwin->GetCurrentInnerWindow()->GetExtantDoc()->GetChannel(); pwin->NotifyContentBlockingState(blockReason, channel, false, trackingURI); ReportUnblockingConsole(parentWindow, NS_ConvertUTF8toUTF16(trackingOrigin), origin, aReason); if (XRE_IsParentProcess()) { LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s", trackingOrigin.get(), grantedOrigin.get())); return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal, trackingPrincipal, trackingOrigin, grantedOrigin, aAnySite) ->Then(GetCurrentThreadSerialEventTarget(), __func__, [] (FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) { if (aValue.IsResolve()) { return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()), __func__); } return StorageAccessGrantPromise::CreateAndReject(false, __func__); }); } 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. return cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal), IPC::Principal(trackingPrincipal), trackingOrigin, grantedOrigin, aAnySite) ->Then(GetCurrentThreadSerialEventTarget(), __func__, [] (const ContentChild::FirstPartyStorageAccessGrantedForOriginPromise::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { return StorageAccessGrantPromise::CreateAndResolve(aValue.ResolveValue(), __func__); } return StorageAccessGrantPromise::CreateAndReject(false, __func__); }); }; if (aPerformFinalChecks) { return aPerformFinalChecks() ->Then(GetCurrentThreadSerialEventTarget(), __func__, [storePermission] (StorageAccessGrantPromise::ResolveOrRejectValue&& aValue) { if (aValue.IsResolve()) { return storePermission(aValue.ResolveValue()); } return StorageAccessGrantPromise::CreateAndReject(false, __func__); }); } return storePermission(false); } /* static */ RefPtr AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aParentPrincipal, nsIPrincipal* aTrackingPrincipal, const nsCString& aTrackingOrigin, const nsCString& aGrantedOrigin, bool aAnySite) { MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr 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")); return FirstPartyStorageAccessGrantPromise::CreateAndReject(false, __func__); } nsCOMPtr pm = services::GetPermissionManager(); if (NS_WARN_IF(!pm)) { LOG(("Permission manager is null, bailing out early")); return FirstPartyStorageAccessGrantPromise::CreateAndReject(false, __func__); } // Remember that this pref is stored in seconds! uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME; uint32_t expirationTime = StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000; int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime; nsresult rv; if (aAnySite) { uint32_t privateBrowsingId = 0; rv = aTrackingPrincipal->GetPrivateBrowsingId(&privateBrowsingId); if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) { // If we are coming from a private window, make sure to store a session-only // permission which won't get persisted to disk. expirationType = nsIPermissionManager::EXPIRE_SESSION; when = 0; } LOG(("Setting 'any site' permission expiry: %u, proceeding to save in the permission manager", expirationTime)); rv = pm->AddFromPrincipal(aTrackingPrincipal, "cookie", nsICookiePermission::ACCESS_ALLOW, expirationType, when); } else { uint32_t privateBrowsingId = 0; rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId); if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) { // If we are coming from a private window, make sure to store a session-only // permission which won't get persisted to disk. expirationType = nsIPermissionManager::EXPIRE_SESSION; when = 0; } nsAutoCString type; CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type); LOG(("Computed permission key: %s, expiry: %u, proceeding to save in the permission manager", type.get(), expirationTime)); rv = pm->AddFromPrincipal(aParentPrincipal, type.get(), nsIPermissionManager::ALLOW_ACTION, expirationType, when); } Unused << NS_WARN_IF(NS_FAILED(rv)); LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure")); return FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__); } // static bool AntiTrackingCommon::IsStorageAccessPermission(nsIPermission* aPermission, nsIPrincipal* aPrincipal) { MOZ_ASSERT(aPermission); MOZ_ASSERT(aPrincipal); nsAutoCString origin; nsresult rv = aPrincipal->GetOriginNoSuffix(origin); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } // 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; CreatePermissionKey(origin, origin, permissionKey); nsAutoCString type; rv = aPermission->GetType(type); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return StringBeginsWith(type, permissionKey); } bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* aWindow, nsIURI* aURI, uint32_t* aRejectedReason) { MOZ_ASSERT(aWindow); MOZ_ASSERT(aURI); // Let's avoid a null check on aRejectedReason everywhere else. uint32_t rejectedReason = 0; if (!aRejectedReason) { aRejectedReason = &rejectedReason; } LOG_SPEC(("Computing whether window %p has access to URI %s", aWindow, _spec), aURI); nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow); nsIPrincipal* windowPrincipal = innerWindow->GetPrincipal(); if (!windowPrincipal) { LOG(("Our window has no principal")); return false; } 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 = windowPrincipal; } MOZ_ASSERT(toplevelPrincipal); nsCookieAccess access = CheckCookiePermissionForPrincipal(toplevelPrincipal); if (access != nsICookiePermission::ACCESS_DEFAULT) { LOG(("CheckCookiePermissionForPrincipal() returned a non-default access code (%d) for top-level window's principal, returning %s", int(access), access != nsICookiePermission::ACCESS_DENY ? "success" : "failure")); if (access != nsICookiePermission::ACCESS_DENY) { return true; } *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; return false; } access = CheckCookiePermissionForPrincipal(windowPrincipal); if (access != nsICookiePermission::ACCESS_DEFAULT) { LOG(("CheckCookiePermissionForPrincipal() returned a non-default access code (%d) for window's principal, returning %s", int(access), access != nsICookiePermission::ACCESS_DENY ? "success" : "failure")); if (access != nsICookiePermission::ACCESS_DENY) { return true; } *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; return false; } int32_t behavior = CookiesBehavior(toplevelPrincipal, windowPrincipal); if (behavior == nsICookieService::BEHAVIOR_ACCEPT) { LOG(("The cookie behavior pref mandates accepting all cookies!")); return true; } if (CheckContentBlockingAllowList(aWindow)) { return true; } if (behavior == nsICookieService::BEHAVIOR_REJECT) { LOG(("The cookie behavior pref mandates rejecting all cookies!")); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL; 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))); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; return false; } MOZ_ASSERT(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER); if (!nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow)) { LOG(("Our window isn't a third-party tracking window")); return true; } nsCOMPtr parentPrincipal; nsCOMPtr parentPrincipalURI; nsCOMPtr trackingURI; nsAutoCString trackingOrigin; if (!GetParentPrincipalAndTrackingOrigin(nsGlobalWindowInner::Cast(aWindow), getter_AddRefs(parentPrincipal), trackingOrigin, getter_AddRefs(trackingURI), nullptr)) { LOG(("Failed to obtain the parent principal and the tracking origin")); return false; } Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI)); if (CompareBaseDomains(trackingURI, parentPrincipalURI)) { LOG(("Grant access across the same eTLD+1 because same domain trackers " "are considered part of the same organization")); return true; } 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 grantedOrigin(origin); nsGlobalWindowOuter* outerWindow = nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow()); if (NS_WARN_IF(!outerWindow)) { LOG(("No outer window.")); return false; } nsCOMPtr topOuterWindow = outerWindow->GetTop(); nsGlobalWindowOuter* topWindow = nsGlobalWindowOuter::Cast(topOuterWindow); if (NS_WARN_IF(!topWindow)) { LOG(("No top outer window.")); return false; } nsPIDOMWindowInner* topInnerWindow = topWindow->GetCurrentInnerWindow(); if (NS_WARN_IF(!topInnerWindow)) { LOG(("No top inner window.")); return false; } nsAutoCString type; CreatePermissionKey(trackingOrigin, grantedOrigin, type); if (topInnerWindow->HasStorageAccessGranted(type)) { LOG(("Permission stored in the window. All good.")); return true; } nsCOMPtr 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; } LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)", type.get(), _spec, int(result), result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"), parentPrincipalURI); if (result != nsIPermissionManager::ALLOW_ACTION) { *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; return false; } return true; } bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsIHttpChannel* aChannel, nsIURI* aURI, uint32_t* aRejectedReason) { MOZ_ASSERT(aURI); MOZ_ASSERT(aChannel); // Let's avoid a null check on aRejectedReason everywhere else. uint32_t rejectedReason = 0; if (!aRejectedReason) { aRejectedReason = &rejectedReason; } nsCOMPtr channelURI; Unused << aChannel->GetURI(getter_AddRefs(channelURI)); LOG_SPEC(("Computing whether channel %p has access to URI %s", aChannel, _spec), channelURI); nsCOMPtr 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->GetTopLevelPrincipal(); // 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 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) for top-level window's principal, returning %s", int(access), access != nsICookiePermission::ACCESS_DENY ? "success" : "failure")); if (access != nsICookiePermission::ACCESS_DENY) { return true; } *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; return false; } if (NS_WARN_IF(NS_FAILED(rv) || !channelPrincipal)) { LOG(("No channel principal, bail out early")); return false; } access = CheckCookiePermissionForPrincipal(channelPrincipal); if (access != nsICookiePermission::ACCESS_DEFAULT) { LOG(("CheckCookiePermissionForPrincipal() returned a non-default access code (%d) for channel's principal, returning %s", int(access), access != nsICookiePermission::ACCESS_DENY ? "success" : "failure")); if (access != nsICookiePermission::ACCESS_DENY) { return true; } *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; return false; } int32_t behavior = CookiesBehavior(toplevelPrincipal, channelPrincipal); if (behavior == nsICookieService::BEHAVIOR_ACCEPT) { LOG(("The cookie behavior pref mandates accepting all cookies!")); return true; } if (CheckContentBlockingAllowList(aChannel)) { return true; } if (behavior == nsICookieService::BEHAVIOR_REJECT) { LOG(("The cookie behavior pref mandates rejecting all cookies!")); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL; return false; } nsCOMPtr thirdPartyUtil = services::GetThirdPartyUtil(); if (!thirdPartyUtil) { LOG(("No thirdPartyUtil, bail out early")); return true; } bool thirdParty = false; rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &thirdParty); // Grant if it's not a 3rd party. // Be careful to check the return value of IsThirdPartyChannel, since // IsThirdPartyChannel() will fail if the channel's loading principal is the // system principal... if (NS_SUCCEEDED(rv) && !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))); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; return false; } MOZ_ASSERT(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER); // Not a tracker. if (!aChannel->GetIsThirdPartyTrackingResource()) { LOG(("Our channel isn't a third-party tracking channel")); return true; } nsIPrincipal* parentPrincipal = loadInfo->GetTopLevelStorageAreaPrincipal(); 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->GetTopLevelPrincipal()) { 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; } } nsCOMPtr parentPrincipalURI; Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI)); // Let's see if we have to grant the access for this particular channel. nsCOMPtr trackingURI; rv = aChannel->GetURI(getter_AddRefs(trackingURI)); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("Failed to get the channel URI")); return true; } if (CompareBaseDomains(trackingURI, parentPrincipalURI)) { LOG(("Grant access across the same eTLD+1 because same domain trackers " "are considered part of the same organization")); 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 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; } LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)", type.get(), _spec, int(result), result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"), parentPrincipalURI); if (result != nsIPermissionManager::ALLOW_ACTION) { *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; return false; } return true; } 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, nullptr); return behavior != nsICookieService::BEHAVIOR_REJECT; } /* static */ bool AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* aFirstPartyWindow, nsIURI* aURI) { MOZ_ASSERT(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; } if (CheckContentBlockingAllowList(aFirstPartyWindow)) { return true; } if (!nsContentUtils::IsThirdPartyWindowOrChannel(aFirstPartyWindow, nullptr, aURI)) { LOG(("Our window isn't a third-party window")); return true; } nsCOMPtr 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 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 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 aIsPrivateBrowsing, AntiTrackingCommon::ContentBlockingAllowListPurpose aPurpose, bool& aIsAllowListed) { aIsAllowListed = false; // For storage checks, check the storage pref, and for annotations checks, // check the corresponding pref as well. This allows each set of checks to // be disabled individually if needed. if ((aPurpose == eStorageChecks && !StaticPrefs::browser_contentblocking_allowlist_storage_enabled()) || (aPurpose == eTrackingAnnotations && !StaticPrefs::browser_contentblocking_allowlist_annotations_enabled())) { LOG(("Attempting to check the content blocking allow list aborted because " "the third-party cookies UI has been disabled.")); return NS_OK; } LOG_SPEC(("Deciding whether the user has overridden content blocking for %s", _spec), aTopWinURI); nsCOMPtr 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 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 topWinURI; rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr permMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); // Check both the normal mode and private browsing mode user override permissions. Pair types[] = { {"trackingprotection", false}, {"trackingprotection-pb", true} }; for (size_t i = 0; i < ArrayLength(types); ++i) { if (aIsPrivateBrowsing != types[i].second()) { continue; } uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION; rv = permMgr->TestPermission(topWinURI, types[i].first(), &permissions); NS_ENSURE_SUCCESS(rv, rv); if (permissions == nsIPermissionManager::ALLOW_ACTION) { aIsAllowListed = true; LOG_SPEC(("Found user override type %s for %s", types[i].first(), _spec), topWinURI); // Stop checking the next permisson type if we decided to override. break; } } if (!aIsAllowListed) { LOG(("No user override found")); } return NS_OK; } /* static */ void AntiTrackingCommon::NotifyBlockingDecision(nsIChannel* aChannel, BlockingDecision aDecision, uint32_t aRejectedReason) { MOZ_ASSERT(aRejectedReason == 0 || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN || aRejectedReason == nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT); MOZ_ASSERT(aDecision == BlockingDecision::eBlock || aDecision == BlockingDecision::eAllow); if (!aChannel) { return; } // When we allow loads, collapse all cookie related reason codes into STATE_COOKIES_LOADED. bool sendCookieLoadedNotification = false; if (aRejectedReason != nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT) { sendCookieLoadedNotification = true; } // Can be called in EITHER the parent or child process. nsCOMPtr parentChannel; NS_QueryNotificationCallbacks(aChannel, parentChannel); if (parentChannel) { // This channel is a parent-process proxy for a child process request. // Tell the child process channel to do this instead. if (aDecision == BlockingDecision::eBlock) { parentChannel->NotifyTrackingCookieBlocked(aRejectedReason); } else if (sendCookieLoadedNotification) { // Ignore the code related to fastblock parentChannel->NotifyCookieAllowed(); } return; } nsCOMPtr thirdPartyUtil = services::GetThirdPartyUtil(); if (!thirdPartyUtil) { return; } nsCOMPtr win; nsresult rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win)); NS_ENSURE_SUCCESS_VOID(rv); nsCOMPtr pwin = nsPIDOMWindowOuter::From(win); if (!pwin) { return; } nsCOMPtr uri; aChannel->GetURI(getter_AddRefs(uri)); if (aDecision == BlockingDecision::eBlock) { pwin->NotifyContentBlockingState(aRejectedReason, aChannel, true, uri); ReportBlockingToConsole(pwin, uri, aRejectedReason); } if (sendCookieLoadedNotification) { pwin->NotifyContentBlockingState(nsIWebProgressListener::STATE_COOKIES_LOADED, aChannel, false, uri); } } /* static */ void AntiTrackingCommon::NotifyBlockingDecision(nsPIDOMWindowInner* aWindow, BlockingDecision aDecision, uint32_t aRejectedReason) { MOZ_ASSERT(aWindow); MOZ_ASSERT(aRejectedReason == 0 || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL || aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN || aRejectedReason == nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT); MOZ_ASSERT(aDecision == BlockingDecision::eBlock || aDecision == BlockingDecision::eAllow); // When we allow loads, collapse all cookie related reason codes into STATE_COOKIES_LOADED. bool sendCookieLoadedNotification = false; if (aRejectedReason != nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT) { sendCookieLoadedNotification = true; } nsCOMPtr pwin = GetTopWindow(aWindow); if (!pwin) { return; } nsPIDOMWindowInner* inner = pwin->GetCurrentInnerWindow(); if (!inner) { return; } nsIDocument* pwinDoc = inner->GetExtantDoc(); if (!pwinDoc) { return; } nsIChannel* channel = pwinDoc->GetChannel(); if (!channel) { return; } nsIDocument* document = aWindow->GetExtantDoc(); if (!document) { return; } nsIURI* uri = document->GetDocumentURI(); if (aDecision == BlockingDecision::eBlock) { pwin->NotifyContentBlockingState(aRejectedReason, channel, true, uri); ReportBlockingToConsole(pwin, uri, aRejectedReason); } if (sendCookieLoadedNotification) { pwin->NotifyContentBlockingState(nsIWebProgressListener::STATE_COOKIES_LOADED, channel, false, uri); } } /* static */ void AntiTrackingCommon::StoreUserInteractionFor(nsIPrincipal* aPrincipal) { if (XRE_IsParentProcess()) { nsCOMPtr uri; Unused << aPrincipal->GetURI(getter_AddRefs(uri)); LOG_SPEC(("Saving the userInteraction for %s", _spec), uri); nsCOMPtr pm = services::GetPermissionManager(); if (NS_WARN_IF(!pm)) { LOG(("Permission manager is null, bailing out early")); return; } // Remember that this pref is stored in seconds! uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME; uint32_t expirationTime = StaticPrefs::privacy_userInteraction_expiration() * 1000; int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime; uint32_t privateBrowsingId = 0; nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId); if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) { // If we are coming from a private window, make sure to store a session-only // permission which won't get persisted to disk. expirationType = nsIPermissionManager::EXPIRE_SESSION; when = 0; } rv = pm->AddFromPrincipal(aPrincipal, USER_INTERACTION_PERM, nsIPermissionManager::ALLOW_ACTION, expirationType, when); Unused << NS_WARN_IF(NS_FAILED(rv)); return; } ContentChild* cc = ContentChild::GetSingleton(); MOZ_ASSERT(cc); nsCOMPtr uri; Unused << aPrincipal->GetURI(getter_AddRefs(uri)); LOG_SPEC(("Asking the parent process to save the user-interaction for us: %s", _spec), uri); cc->SendStoreUserInteractionAsPermission(IPC::Principal(aPrincipal)); } /* static */ bool AntiTrackingCommon::HasUserInteraction(nsIPrincipal* aPrincipal) { nsCOMPtr pm = services::GetPermissionManager(); if (NS_WARN_IF(!pm)) { return false; } uint32_t result = 0; nsresult rv = pm->TestPermissionFromPrincipal(aPrincipal, USER_INTERACTION_PERM, &result); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return result == nsIPermissionManager::ALLOW_ACTION; }