/* 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 "mozilla/net/UrlClassifierCommon.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Components.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "nsIChannel.h"
#include "nsIClassifiedChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIParentChannel.h"
#include "nsNetUtil.h"
#include "nsReadableUtils.h"
namespace mozilla {
namespace net {
const nsCString::size_type UrlClassifierCommon::sMaxSpecLength =
128;
// MOZ_LOG=nsChannelClassifier:5
LazyLogModule gChannelClassifierLog(
"nsChannelClassifier");
LazyLogModule gChannelClassifierLogLeak(
"nsChannelClassifierLeak");
/* static */
bool UrlClassifierCommon::AddonMayLoad(nsIChannel* aChannel, nsIURI* aURI) {
nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->LoadInfo();
// loadingPrincipal is used here to ensure we are loading into an
// addon principal. This allows an addon, with explicit permission, to
// call out to API endpoints that may otherwise get blocked.
nsIPrincipal* loadingPrincipal = channelLoadInfo->GetLoadingPrincipal();
if (!loadingPrincipal) {
return false;
}
return BasePrincipal::Cast(loadingPrincipal)->AddonAllowsLoad(aURI, true);
}
/* static */
bool UrlClassifierCommon::ShouldEnableProtectionForChannel(
nsIChannel* aChannel) {
MOZ_ASSERT(aChannel);
nsCOMPtr<nsIURI> chanURI;
nsresult rv = aChannel->GetURI(getter_AddRefs(chanURI));
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (UrlClassifierCommon::AddonMayLoad(aChannel, chanURI)) {
return false;
}
nsCOMPtr<nsIURI> topWinURI;
nsCOMPtr<nsIHttpChannelInternal> channel = do_QueryInterface(aChannel);
if (NS_WARN_IF(!channel)) {
return false;
}
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
MOZ_ASSERT(loadInfo);
auto policyType = loadInfo->GetExternalContentPolicyType();
if (policyType == ExtContentPolicy::TYPE_DOCUMENT) {
UC_LOG(
(
"UrlClassifierCommon::ShouldEnableProtectionForChannel - "
"skipping top-level load for channel %p",
aChannel));
return false;
}
// Tracking protection will be enabled so return without updating
// the security state. If any channels are subsequently cancelled
// (page elements blocked) the state will be then updated.
return true;
}
/* static */
nsresult UrlClassifierCommon::GetTopWindowURI(nsIChannel* aChannel,
nsIURI** aURI) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aChannel);
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
MOZ_ASSERT(loadInfo);
RefPtr<dom::BrowsingContext> browsingContext;
nsresult rv =
loadInfo->GetTargetBrowsingContext(getter_AddRefs(browsingContext));
if (NS_WARN_IF(NS_FAILED(rv)) || !browsingContext) {
return NS_ERROR_FAILURE;
}
dom::CanonicalBrowsingContext* top = browsingContext->Canonical()->Top();
dom::WindowGlobalParent* wgp = top->GetCurrentWindowGlobal();
if (!wgp) {
return NS_ERROR_FAILURE;
}
RefPtr<nsIURI> uri = wgp->GetDocumentURI();
if (!uri) {
return NS_ERROR_FAILURE;
}
uri.forget(aURI);
return NS_OK;
}
/* static */
nsresult UrlClassifierCommon::CreatePairwiseEntityListURI(nsIChannel* aChannel,
nsIURI** aURI) {
MOZ_ASSERT(aChannel);
MOZ_ASSERT(aURI);
nsresult rv;
nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (!chan) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> topWinURI;
rv =
UrlClassifierCommon::GetTopWindowURI(aChannel, getter_AddRefs(topWinURI));
if (NS_FAILED(rv) || !topWinURI) {
// SharedWorker and ServiceWorker don't have an associated window, use
// client's URI instead.
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
MOZ_ASSERT(loadInfo);
Maybe<dom::ClientInfo> clientInfo = loadInfo->GetClientInfo();
if (clientInfo.isSome()) {
if ((clientInfo->Type() == dom::ClientType::Sharedworker) ||
(clientInfo->Type() == dom::ClientType::Serviceworker)) {
UC_LOG(
(
"UrlClassifierCommon::CreatePairwiseEntityListURI - "
"channel %p initiated by worker, get uri from client",
aChannel));
auto clientPrincipalOrErr = clientInfo->GetPrincipal();
if (clientPrincipalOrErr.isOk()) {
nsCOMPtr<nsIPrincipal> principal = clientPrincipalOrErr.unwrap();
if (principal) {
auto* basePrin = BasePrincipal::Cast(principal);
rv = basePrin->GetURI(getter_AddRefs(topWinURI));
(
void)NS_WARN_IF(NS_FAILED(rv));
}
}
}
}
if (!topWinURI) {
UC_LOG(
(
"UrlClassifierCommon::CreatePairwiseEntityListURI - "
"no top-level window associated with channel %p, "
"get uri from loading principal",
aChannel));
nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
if (principal) {
auto* basePrin = BasePrincipal::Cast(principal);
rv = basePrin->GetURI(getter_AddRefs(topWinURI));
(
void)NS_WARN_IF(NS_FAILED(rv));
}
}
}
if (!topWinURI) {
UC_LOG(
(
"UrlClassifierCommon::CreatePairwiseEntityListURI - "
"fail to get top-level window uri for channel %p",
aChannel));
// Return success because we want to continue to look up even without
// whitelist.
return NS_OK;
}
nsCOMPtr<nsIScriptSecurityManager> securityManager;
securityManager = mozilla::components::ScriptSecurityManager::Service(&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> chanPrincipal;
rv = securityManager->GetChannelURIPrincipal(aChannel,
getter_AddRefs(chanPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
// Craft a entitylist URL like "toplevel.page/?resource=third.party.domain"
nsAutoCString pageHostname, resourceDomain;
rv = topWinURI->GetHost(pageHostname);
if (NS_FAILED(rv)) {
// When the top-level page doesn't support GetHost, for example, about:home,
// we don't return an error here; instead, we return success to make sure
// that the lookup process calling this API continues to run.
if (UC_LOG_ENABLED()) {
nsCString topWinSpec =
topWinURI ? topWinURI->GetSpecOrDefault() :
"(null)"_ns;
topWinSpec.Truncate(
std::min(topWinSpec.Length(), UrlClassifierCommon::sMaxSpecLength));
UC_LOG(
(
"UrlClassifierCommon::CreatePairwiseEntityListURI - "
"cannot get host from the top-level uri %s of channel %p",
topWinSpec.get(), aChannel));
}
return NS_OK;
}
rv = chanPrincipal->GetBaseDomain(resourceDomain);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString entitylistEntry =
"http://"_ns + pageHostname + "/?resource="_ns + resourceDomain;
UC_LOG(
(
"UrlClassifierCommon::CreatePairwiseEntityListURI - looking for %s in "
"the entitylist on channel %p",
entitylistEntry.get(), aChannel));
nsCOMPtr<nsIURI> entitylistURI;
rv = NS_NewURI(getter_AddRefs(entitylistURI), entitylistEntry);
if (NS_FAILED(rv)) {
return rv;
}
entitylistURI.forget(aURI);
return NS_OK;
}
/* static */
nsresult UrlClassifierCommon::SetTrackingInfo(
nsIChannel* aChannel,
const nsTArray<nsCString>& aLists,
const nsTArray<nsCString>& aFullHashes) {
NS_ENSURE_ARG(!aLists.IsEmpty());
// Can be called in EITHER the parent or child process.
nsresult rv;
nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
do_QueryInterface(aChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (classifiedChannel) {
classifiedChannel->SetMatchedTrackingInfo(aLists, aFullHashes);
}
nsCOMPtr<nsIParentChannel> 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 as well.
// TODO: We can remove the code sending the IPC to content to update
// tracking info once we move the ContentBlockingLog into the parent.
// This would be done in Bug 1599046.
nsAutoCString strLists, strHashes;
UrlClassifierCommon::TablesToString(aLists, strLists);
UrlClassifierCommon::TablesToString(aFullHashes, strHashes);
parentChannel->SetClassifierMatchedTrackingInfo(strLists, strHashes);
}
return NS_OK;
}
void UrlClassifierCommon::TablesToString(
const nsTArray<nsCString>& aList,
nsACString& aString) {
// Truncate and append rather than assigning because that's more efficient if
// aString is an nsAutoCString.
aString.Truncate();
StringJoinAppend(aString,
","_ns, aList);
}
uint32_t UrlClassifierCommon::TablesToClassificationFlags(
const nsTArray<nsCString>& aList,
const std::vector<ClassificationData>& aData, uint32_t aDefaultFlag) {
uint32_t flags =
0;
for (
const nsCString& table : aList) {
flags |= TableToClassificationFlag(table, aData);
}
if (flags ==
0) {
flags |= aDefaultFlag;
}
return flags;
}
uint32_t UrlClassifierCommon::TableToClassificationFlag(
const nsACString& aTable,
const std::vector<ClassificationData>& aData) {
for (
const ClassificationData& data : aData) {
if (StringBeginsWith(aTable, data.mPrefix)) {
return data.mFlag;
}
}
return 0;
}
/* static */
bool UrlClassifierCommon::ShouldProcessWithProtectionFeature(
nsIChannel* aChannel) {
MOZ_ASSERT(aChannel);
bool shouldProcess = true;
bool isPrivateMode = NS_UsePrivateBrowsing(aChannel);
nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
do_QueryInterface(aChannel);
if (classifiedChannel) {
if (classifiedChannel->GetClassificationFlags() &
nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_CONSENTMANAGER) {
// Channel is classified as consent manager
if (StaticPrefs::
privacy_trackingprotection_consentmanager_skip_enabled() ||
(StaticPrefs::
privacy_trackingprotection_consentmanager_skip_pbmode_enabled() &&
isPrivateMode)) {
// Don't process channel
shouldProcess =
false;
UC_LOG(
(
"UrlClassifierCommon::ShouldProcessWithProtectionFeature - "
"Skipping channel %p because annotated as a consent manager",
aChannel));
}
}
if (classifiedChannel->GetClassificationFlags() &
nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_ANTIFRAUD) {
// Channel is classified as anti-fraud
if (StaticPrefs::privacy_trackingprotection_antifraud_skip_enabled() ||
(StaticPrefs::
privacy_trackingprotection_antifraud_skip_pbmode_enabled() &&
isPrivateMode)) {
// Don't process channel
shouldProcess =
false;
UC_LOG(
(
"UrlClassifierCommon::ShouldProcessWithProtectionFeature - "
"Skipping channel %p because it is annotated as anti-fraud",
aChannel));
}
}
}
return shouldProcess;
}
}
// namespace net
}
// namespace mozilla