diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /netwerk/url-classifier | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/url-classifier')
45 files changed, 6897 insertions, 0 deletions
diff --git a/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp new file mode 100644 index 0000000000..4dfb7224e7 --- /dev/null +++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp @@ -0,0 +1,920 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set expandtab ts=4 sw=2 sts=2 cin: */ +/* 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 "Classifier.h" +#include "mozilla/Components.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/net/AsyncUrlChannelClassifier.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "mozilla/net/UrlClassifierFeatureResult.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsIHttpChannel.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsUrlClassifierDBService.h" +#include "nsUrlClassifierUtils.h" + +namespace mozilla { +namespace net { + +namespace { + +// Big picture comment +// ----------------------------------------------------------------------------- +// nsUrlClassifierDBService::channelClassify() classifies a channel using a set +// of URL-Classifier features. This method minimizes the number of lookups and +// URI parsing and this is done using the classes here described. +// +// The first class is 'FeatureTask' which is able to retrieve the list of +// features for this channel using the feature-factory. See +// UrlClassifierFeatureFactory. +// For each feature, it creates a FeatureData object, which contains the +// entitylist and blocklist prefs and tables. The reason why we create +// FeatureData is because: +// - features are not thread-safe. +// - we want to store the state of the classification in the FeatureData +// object. +// +// It can happen that multiple features share the same tables. In order to do +// the lookup just once, we have TableData class. When multiple features +// contain the same table, they have references to the same couple TableData + +// URIData objects. +// +// During the classification, the channel's URIs are fragmented. In order to +// create these fragments just once, we use the URIData class, which is pointed +// by TableData classes. +// +// The creation of these classes happens on the main-thread. The classification +// happens on the worker thread. + +// URIData +// ----------------------------------------------------------------------------- + +// In order to avoid multiple URI parsing, we have this class which contains +// nsIURI and its fragments. +class URIData { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URIData); + + static nsresult Create(nsIURI* aURI, nsIURI* aInnermostURI, + nsIUrlClassifierFeature::URIType aURIType, + URIData** aData); + + bool IsEqual(nsIURI* aURI) const; + + const nsTArray<nsCString>& Fragments(); + + nsIURI* URI() const; + + private: + URIData(); + ~URIData() = default; + + nsCOMPtr<nsIURI> mURI; + nsCString mURISpec; + nsTArray<nsCString> mFragments; + nsIUrlClassifierFeature::URIType mURIType; +}; + +/* static */ +nsresult URIData::Create(nsIURI* aURI, nsIURI* aInnermostURI, + nsIUrlClassifierFeature::URIType aURIType, + URIData** aData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI); + MOZ_ASSERT(aInnermostURI); + + RefPtr<URIData> data = new URIData(); + data->mURI = aURI; + data->mURIType = aURIType; + + nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!utilsService)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = utilsService->GetKeyForURI(aInnermostURI, data->mURISpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + UC_LOG_LEAK( + ("AsyncChannelClassifier::URIData::Create new URIData created for spec " + "%s [this=%p]", + data->mURISpec.get(), data.get())); + + data.forget(aData); + return NS_OK; +} + +URIData::URIData() { MOZ_ASSERT(NS_IsMainThread()); } + +bool URIData::IsEqual(nsIURI* aURI) const { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI); + + bool isEqual = false; + nsresult rv = mURI->Equals(aURI, &isEqual); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return isEqual; +} + +const nsTArray<nsCString>& URIData::Fragments() { + MOZ_ASSERT(!NS_IsMainThread()); + + if (mFragments.IsEmpty()) { + nsresult rv; + + if (mURIType == nsIUrlClassifierFeature::pairwiseEntitylistURI) { + rv = LookupCache::GetLookupEntitylistFragments(mURISpec, &mFragments); + } else { + rv = LookupCache::GetLookupFragments(mURISpec, &mFragments); + } + + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + + return mFragments; +} + +nsIURI* URIData::URI() const { + MOZ_ASSERT(NS_IsMainThread()); + return mURI; +} + +// TableData +// ---------------------------------------------------------------------------- + +// In order to avoid multiple lookups on the same table + URI, we have this +// class. +class TableData { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TableData); + + enum State { + eUnclassified, + eNoMatch, + eMatch, + }; + + TableData(URIData* aURIData, const nsACString& aTable); + + nsIURI* URI() const; + + const nsACString& Table() const; + + const LookupResultArray& Result() const; + + State MatchState() const; + + bool IsEqual(URIData* aURIData, const nsACString& aTable) const; + + // Returns true if the table classifies the URI. This method must be called + // on hte classifier worker thread. + bool DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier); + + private: + ~TableData(); + + RefPtr<URIData> mURIData; + State mState; + + nsCString mTable; + LookupResultArray mResults; +}; + +TableData::TableData(URIData* aURIData, const nsACString& aTable) + : mURIData(aURIData), mState(eUnclassified), mTable(aTable) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURIData); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::TableData CTOR - new TableData created %s " + "[this=%p]", + aTable.BeginReading(), this)); +} + +TableData::~TableData() = default; + +nsIURI* TableData::URI() const { + MOZ_ASSERT(NS_IsMainThread()); + return mURIData->URI(); +} + +const nsACString& TableData::Table() const { + MOZ_ASSERT(NS_IsMainThread()); + return mTable; +} + +const LookupResultArray& TableData::Result() const { + MOZ_ASSERT(NS_IsMainThread()); + return mResults; +} + +TableData::State TableData::MatchState() const { + MOZ_ASSERT(NS_IsMainThread()); + return mState; +} + +bool TableData::IsEqual(URIData* aURIData, const nsACString& aTable) const { + MOZ_ASSERT(NS_IsMainThread()); + return mURIData == aURIData && mTable == aTable; +} + +bool TableData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aWorkerClassifier); + + if (mState == TableData::eUnclassified) { + UC_LOG_LEAK( + ("AsyncChannelClassifier::TableData::DoLookup - starting lookup " + "[this=%p]", + this)); + + const nsTArray<nsCString>& fragments = mURIData->Fragments(); + nsresult rv = aWorkerClassifier->DoSingleLocalLookupWithURIFragments( + fragments, mTable, mResults); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + mState = mResults.IsEmpty() ? TableData::eNoMatch : TableData::eMatch; + + UC_LOG_LEAK( + ("AsyncChannelClassifier::TableData::DoLookup - lookup completed. " + "Matches: %d [this=%p]", + (int)mResults.Length(), this)); + } + + return !mResults.IsEmpty(); +} + +// FeatureData +// ---------------------------------------------------------------------------- + +class FeatureTask; + +// This is class contains all the Feature data. +class FeatureData { + enum State { + eUnclassified, + eNoMatch, + eMatchBlocklist, + eMatchEntitylist, + }; + + public: + FeatureData() = default; + ~FeatureData(); + + nsresult Initialize(FeatureTask* aTask, nsIChannel* aChannel, + nsIUrlClassifierFeature* aFeature); + + void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier); + + // Returns true if the next feature should be processed. + bool MaybeCompleteClassification(nsIChannel* aChannel); + + private: + nsresult InitializeList(FeatureTask* aTask, nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsTArray<RefPtr<TableData>>& aList); + + State mState{eUnclassified}; + nsCOMPtr<nsIUrlClassifierFeature> mFeature; + nsCOMPtr<nsIChannel> mChannel; + + nsTArray<RefPtr<TableData>> mBlocklistTables; + nsTArray<RefPtr<TableData>> mEntitylistTables; + + // blocklist + entitylist. + nsCString mHostInPrefTables[2]; +}; + +FeatureData::~FeatureData() { + NS_ReleaseOnMainThread("FeatureData:mFeature", mFeature.forget()); +} + +nsresult FeatureData::Initialize(FeatureTask* aTask, nsIChannel* aChannel, + nsIUrlClassifierFeature* aFeature) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTask); + MOZ_ASSERT(aChannel); + MOZ_ASSERT(aFeature); + + if (UC_LOG_ENABLED()) { + nsAutoCString name; + aFeature->GetName(name); + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::Initialize - Feature %s " + "[this=%p, channel=%p]", + name.get(), this, aChannel)); + } + + mFeature = aFeature; + mChannel = aChannel; + + nsresult rv = InitializeList( + aTask, aChannel, nsIUrlClassifierFeature::blocklist, mBlocklistTables); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = InitializeList(aTask, aChannel, nsIUrlClassifierFeature::entitylist, + mEntitylistTables); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +void FeatureData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aWorkerClassifier); + MOZ_ASSERT(mState == eUnclassified); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::DoLookup - lookup starting " + "[this=%p]", + this)); + + // This is wrong, but it's fast: we don't want to check if the host is in the + // blocklist table if we know that it's going to be entitylisted by pref. + // So, also if maybe it's not blocklisted, let's consider it 'entitylisted'. + if (!mHostInPrefTables[nsIUrlClassifierFeature::entitylist].IsEmpty()) { + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::DoLookup - entitylisted by pref " + "[this=%p]", + this)); + mState = eMatchEntitylist; + return; + } + + // Let's check if this feature blocklists the URI. + + bool isBlocklisted = + !mHostInPrefTables[nsIUrlClassifierFeature::blocklist].IsEmpty(); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted by pref: " + "%d [this=%p]", + isBlocklisted, this)); + + if (!isBlocklisted) { + // If one of the blocklist table matches the URI, we don't need to continue + // with the others: the feature is blocklisted (but maybe also + // entitylisted). + for (TableData* tableData : mBlocklistTables) { + if (tableData->DoLookup(aWorkerClassifier)) { + isBlocklisted = true; + break; + } + } + } + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted before " + "entitylisting: %d [this=%p]", + isBlocklisted, this)); + + if (!isBlocklisted) { + mState = eNoMatch; + return; + } + + // Now, let's check if we need to entitylist the same URI. + + for (TableData* tableData : mEntitylistTables) { + // If one of the entitylist table matches the URI, we don't need to continue + // with the others: the feature is entitylisted. + if (tableData->DoLookup(aWorkerClassifier)) { + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::DoLookup - entitylisted by " + "table [this=%p]", + this)); + mState = eMatchEntitylist; + return; + } + } + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted [this=%p]", + this)); + mState = eMatchBlocklist; +} + +bool FeatureData::MaybeCompleteClassification(nsIChannel* aChannel) { + MOZ_ASSERT(NS_IsMainThread()); + + nsAutoCString name; + mFeature->GetName(name); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - " + "completing " + "classification [this=%p channel=%p]", + this, aChannel)); + + switch (mState) { + case eNoMatch: + UC_LOG( + ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - " + "no match for feature %s. Let's " + "move on [this=%p channel=%p]", + name.get(), this, aChannel)); + return true; + + case eMatchEntitylist: + UC_LOG( + ("AsyncChannelClassifier::FeatureData::MayebeCompleteClassification " + "- entitylisted by feature %s. Let's " + "move on [this=%p channel=%p]", + name.get(), this, aChannel)); + return true; + + case eMatchBlocklist: + UC_LOG( + ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - " + "blocklisted by feature %s [this=%p channel=%p]", + name.get(), this, aChannel)); + break; + + case eUnclassified: + MOZ_CRASH("We should not be here!"); + break; + } + + MOZ_ASSERT(mState == eMatchBlocklist); + + // Maybe we have to ignore this host + nsAutoCString exceptionList; + nsresult rv = mFeature->GetExceptionHostList(exceptionList); + if (NS_WARN_IF(NS_FAILED(rv))) { + UC_LOG_WARN( + ("AsyncChannelClassifier::FeatureData::MayebeCompleteClassification - " + "error. Let's move on [this=%p channel=%p]", + this, aChannel)); + return true; + } + + if (!mBlocklistTables.IsEmpty() && + nsContentUtils::IsURIInList(mBlocklistTables[0]->URI(), exceptionList)) { + nsCString spec = mBlocklistTables[0]->URI()->GetSpecOrDefault(); + spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength)); + UC_LOG( + ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - " + "uri %s found in " + "exceptionlist of feature %s [this=%p channel=%p]", + spec.get(), name.get(), this, aChannel)); + return true; + } + + nsTArray<nsCString> list; + nsTArray<nsCString> hashes; + if (!mHostInPrefTables[nsIUrlClassifierFeature::blocklist].IsEmpty()) { + list.AppendElement(mHostInPrefTables[nsIUrlClassifierFeature::blocklist]); + + // Telemetry expects every tracking channel has hash, create it for test + // entry + Completion complete; + complete.FromPlaintext( + mHostInPrefTables[nsIUrlClassifierFeature::blocklist]); + hashes.AppendElement(complete.ToString()); + } + + for (TableData* tableData : mBlocklistTables) { + if (tableData->MatchState() == TableData::eMatch) { + list.AppendElement(tableData->Table()); + + for (const auto& r : tableData->Result()) { + hashes.AppendElement(r->hash.complete.ToString()); + } + } + } + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - " + "process channel [this=%p channel=%p]", + this, aChannel)); + + bool shouldContinue = false; + rv = mFeature->ProcessChannel(aChannel, list, hashes, &shouldContinue); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return shouldContinue; +} + +// CallbackHolder +// ---------------------------------------------------------------------------- + +// This class keeps the callback alive and makes sure that we release it on the +// correct thread. +class CallbackHolder final { + public: + NS_INLINE_DECL_REFCOUNTING(CallbackHolder); + + explicit CallbackHolder(std::function<void()>&& aCallback) + : mCallback(std::move(aCallback)) {} + + void Exec() const { mCallback(); } + + private: + ~CallbackHolder() = default; + + std::function<void()> mCallback; +}; + +// FeatureTask +// ---------------------------------------------------------------------------- + +// A FeatureTask is a class that is able to classify a channel using a set of +// features. The features are grouped by: +// - URIs - to avoid extra URI parsing. +// - Tables - to avoid multiple lookup on the same table. +class FeatureTask { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FeatureTask); + + static nsresult Create(nsIChannel* aChannel, + std::function<void()>&& aCallback, + FeatureTask** aTask); + + // Called on the classifier thread. + void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier); + + // Called on the main-thread to process the channel. + void CompleteClassification(); + + nsresult GetOrCreateURIData(nsIURI* aURI, nsIURI* aInnermostURI, + nsIUrlClassifierFeature::URIType aURIType, + URIData** aData); + + nsresult GetOrCreateTableData(URIData* aURIData, const nsACString& aTable, + TableData** aData); + + private: + FeatureTask(nsIChannel* aChannel, std::function<void()>&& aCallback); + ~FeatureTask(); + + nsCOMPtr<nsIChannel> mChannel; + RefPtr<CallbackHolder> mCallbackHolder; + + nsTArray<FeatureData> mFeatures; + nsTArray<RefPtr<URIData>> mURIs; + nsTArray<RefPtr<TableData>> mTables; +}; + +// Features are able to classify particular URIs from a channel. For instance, +// tracking-annotation feature uses the top-level URI to entitylist the current +// channel's URI. Because of +// this, this function aggregates feature per URI and tables. +/* static */ +nsresult FeatureTask::Create(nsIChannel* aChannel, + std::function<void()>&& aCallback, + FeatureTask** aTask) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aChannel); + MOZ_ASSERT(aTask); + + // We need to obtain the list of nsIUrlClassifierFeature objects able to + // classify this channel. If the list is empty, we do an early return. + nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> features; + UrlClassifierFeatureFactory::GetFeaturesFromChannel(aChannel, features); + if (features.IsEmpty()) { + UC_LOG( + ("AsyncChannelClassifier::FeatureTask::Create - no task is needed for " + "channel %p", + aChannel)); + return NS_OK; + } + + RefPtr<FeatureTask> task = new FeatureTask(aChannel, std::move(aCallback)); + + UC_LOG( + ("AsyncChannelClassifier::FeatureTask::Create - FeatureTask %p created " + "for channel %p", + task.get(), aChannel)); + + for (nsIUrlClassifierFeature* feature : features) { + FeatureData* featureData = task->mFeatures.AppendElement(); + nsresult rv = featureData->Initialize(task, aChannel, feature); + if (NS_FAILED(rv)) { + return rv; + } + } + + task.forget(aTask); + return NS_OK; +} + +FeatureTask::FeatureTask(nsIChannel* aChannel, + std::function<void()>&& aCallback) + : mChannel(aChannel) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mChannel); + + std::function<void()> callback = std::move(aCallback); + mCallbackHolder = new CallbackHolder(std::move(callback)); +} + +FeatureTask::~FeatureTask() { + NS_ReleaseOnMainThread("FeatureTask::mChannel", mChannel.forget()); + NS_ReleaseOnMainThread("FeatureTask::mCallbackHolder", + mCallbackHolder.forget()); +} + +nsresult FeatureTask::GetOrCreateURIData( + nsIURI* aURI, nsIURI* aInnermostURI, + nsIUrlClassifierFeature::URIType aURIType, URIData** aData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI); + MOZ_ASSERT(aInnermostURI); + MOZ_ASSERT(aData); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - checking if " + "a URIData must be " + "created [this=%p]", + this)); + + for (URIData* data : mURIs) { + if (data->IsEqual(aURI)) { + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - reuse " + "existing URIData %p [this=%p]", + data, this)); + + RefPtr<URIData> uriData = data; + uriData.forget(aData); + return NS_OK; + } + } + + RefPtr<URIData> data; + nsresult rv = + URIData::Create(aURI, aInnermostURI, aURIType, getter_AddRefs(data)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mURIs.AppendElement(data); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - create new " + "URIData %p [this=%p]", + data.get(), this)); + + data.forget(aData); + return NS_OK; +} + +nsresult FeatureTask::GetOrCreateTableData(URIData* aURIData, + const nsACString& aTable, + TableData** aData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURIData); + MOZ_ASSERT(aData); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::GetOrCreateTableData - checking " + "if TableData must be " + "created [this=%p]", + this)); + + for (TableData* data : mTables) { + if (data->IsEqual(aURIData, aTable)) { + UC_LOG_LEAK( + ("FeatureTask::GetOrCreateTableData - reuse existing TableData %p " + "[this=%p]", + data, this)); + + RefPtr<TableData> tableData = data; + tableData.forget(aData); + return NS_OK; + } + } + + RefPtr<TableData> data = new TableData(aURIData, aTable); + mTables.AppendElement(data); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::GetOrCreateTableData - create new " + "TableData %p [this=%p]", + data.get(), this)); + + data.forget(aData); + return NS_OK; +} + +void FeatureTask::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aWorkerClassifier); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::DoLookup - starting lookup " + "[this=%p]", + this)); + + for (FeatureData& feature : mFeatures) { + feature.DoLookup(aWorkerClassifier); + } + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureTask::DoLookup - lookup completed " + "[this=%p]", + this)); +} + +void FeatureTask::CompleteClassification() { + MOZ_ASSERT(NS_IsMainThread()); + + for (FeatureData& feature : mFeatures) { + if (!feature.MaybeCompleteClassification(mChannel)) { + break; + } + } + + UC_LOG( + ("AsyncChannelClassifier::FeatureTask::CompleteClassification - complete " + "classification for " + "channel %p [this=%p]", + mChannel.get(), this)); + + mCallbackHolder->Exec(); +} + +nsresult FeatureData::InitializeList( + FeatureTask* aTask, nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsTArray<RefPtr<TableData>>& aList) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTask); + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::InitializeList - initialize list " + "%d for channel %p [this=%p]", + aListType, aChannel, this)); + + nsCOMPtr<nsIURI> uri; + nsIUrlClassifierFeature::URIType URIType; + nsresult rv = mFeature->GetURIByListType(aChannel, aListType, &URIType, + getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + if (UC_LOG_ENABLED()) { + nsAutoCString errorName; + GetErrorName(rv, errorName); + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::InitializeList - Got an " + "unexpected error (rv=%s) [this=%p]", + errorName.get(), this)); + } + return rv; + } + + if (!uri) { + // Return success when the URI is empty to conitnue to do the lookup. + UC_LOG_LEAK( + ("AsyncChannelClassifier::FeatureData::InitializeList - got an empty " + "URL [this=%p]", + this)); + return NS_OK; + } + + nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(uri); + if (NS_WARN_IF(!innermostURI)) { + return NS_ERROR_FAILURE; + } + + nsAutoCString host; + rv = innermostURI->GetHost(host); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool found = false; + nsAutoCString tableName; + rv = mFeature->HasHostInPreferences(host, aListType, tableName, &found); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (found) { + mHostInPrefTables[aListType] = tableName; + } + + RefPtr<URIData> uriData; + rv = aTask->GetOrCreateURIData(uri, innermostURI, URIType, + getter_AddRefs(uriData)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(uriData); + + nsTArray<nsCString> tables; + rv = mFeature->GetTables(aListType, tables); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (const nsCString& table : tables) { + RefPtr<TableData> data; + rv = aTask->GetOrCreateTableData(uriData, table, getter_AddRefs(data)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(data); + aList.AppendElement(data); + } + + return NS_OK; +} + +} // namespace + +/* static */ +nsresult AsyncUrlChannelClassifier::CheckChannel( + nsIChannel* aChannel, std::function<void()>&& aCallback) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aChannel); + + if (!aCallback) { + return NS_ERROR_INVALID_ARG; + } + + if (UC_LOG_ENABLED()) { + nsCOMPtr<nsIURI> chanURI; + if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(chanURI)))) { + nsCString chanSpec = chanURI->GetSpecOrDefault(); + chanSpec.Truncate( + std::min(chanSpec.Length(), UrlClassifierCommon::sMaxSpecLength)); + + nsCOMPtr<nsIURI> topWinURI; + Unused << UrlClassifierCommon::GetTopWindowURI(aChannel, + getter_AddRefs(topWinURI)); + nsCString topWinSpec = + topWinURI ? topWinURI->GetSpecOrDefault() : "(null)"_ns; + + topWinSpec.Truncate( + std::min(topWinSpec.Length(), UrlClassifierCommon::sMaxSpecLength)); + + UC_LOG( + ("AsyncUrlChannelClassifier::CheckChannel - starting the " + "classification on channel %p", + aChannel)); + UC_LOG((" uri is %s [channel=%p]", chanSpec.get(), aChannel)); + UC_LOG( + (" top-level uri is %s [channel=%p]", topWinSpec.get(), aChannel)); + } + } + + RefPtr<FeatureTask> task; + nsresult rv = + FeatureTask::Create(aChannel, std::move(aCallback), getter_AddRefs(task)); + if (NS_FAILED(rv)) { + return rv; + } + + if (!task) { + // No task is needed for this channel, return an error so the caller won't + // wait for a callback. + return NS_ERROR_FAILURE; + } + + RefPtr<nsUrlClassifierDBServiceWorker> workerClassifier = + nsUrlClassifierDBService::GetWorker(); + if (NS_WARN_IF(!workerClassifier)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "AsyncUrlChannelClassifier::CheckChannel", + [task, workerClassifier]() -> void { + MOZ_ASSERT(!NS_IsMainThread()); + task->DoLookup(workerClassifier); + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "AsyncUrlChannelClassifier::CheckChannel - return", + [task]() -> void { task->CompleteClassification(); }); + + NS_DispatchToMainThread(r); + }); + + return nsUrlClassifierDBService::BackgroundThread()->Dispatch( + r, NS_DISPATCH_NORMAL); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/AsyncUrlChannelClassifier.h b/netwerk/url-classifier/AsyncUrlChannelClassifier.h new file mode 100644 index 0000000000..52eae5a974 --- /dev/null +++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set expandtab ts=4 sw=2 sts=2 cin: */ +/* 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/. */ + +#ifndef mozilla_net_AsyncUrlChannelClassifier_h +#define mozilla_net_AsyncUrlChannelClassifier_h + +#include "nsISupports.h" +#include <functional> + +class nsIChannel; + +namespace mozilla { +namespace net { + +class AsyncUrlChannelClassifier final { + public: + static nsresult CheckChannel(nsIChannel* aChannel, + std::function<void()>&& aCallback); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_AsyncUrlChannelClassifier_h diff --git a/netwerk/url-classifier/ChannelClassifierService.cpp b/netwerk/url-classifier/ChannelClassifierService.cpp new file mode 100644 index 0000000000..4b1145ba00 --- /dev/null +++ b/netwerk/url-classifier/ChannelClassifierService.cpp @@ -0,0 +1,271 @@ +/* -*- 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 "ChannelClassifierService.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/net/UrlClassifierCommon.h" + +#include "UrlClassifierFeatureCryptominingProtection.h" +#include "UrlClassifierFeatureFingerprintingProtection.h" +#include "UrlClassifierFeatureSocialTrackingProtection.h" +#include "UrlClassifierFeatureTrackingProtection.h" + +#include "mozilla/StaticPtr.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +static StaticRefPtr<ChannelClassifierService> gChannelClassifierService; + +NS_IMPL_ISUPPORTS(UrlClassifierBlockedChannel, nsIUrlClassifierBlockedChannel) + +UrlClassifierBlockedChannel::UrlClassifierBlockedChannel(nsIChannel* aChannel) + : mChannel(aChannel), + mDecision(ChannelBlockDecision::Blocked), + mReason(TRACKING_PROTECTION) { + MOZ_ASSERT(aChannel); +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetReason(uint8_t* aReason) { + NS_ENSURE_ARG_POINTER(aReason); + + *aReason = mReason; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetUrl(nsAString& aUrl) { + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + if (uri) { + CopyUTF8toUTF16(uri->GetSpecOrDefault(), aUrl); + } + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetTabId(uint64_t* aTabId) { + NS_ENSURE_ARG_POINTER(aTabId); + + *aTabId = 0; + + nsCOMPtr<nsILoadInfo> loadInfo = mChannel->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; + } + + // Get top-level browsing context to ensure window global parent is ready + // to use, tabId is the same anyway. + dom::CanonicalBrowsingContext* top = browsingContext->Canonical()->Top(); + dom::WindowGlobalParent* wgp = top->GetCurrentWindowGlobal(); + if (!wgp) { + return NS_ERROR_FAILURE; + } + + RefPtr<dom::BrowserParent> browserParent = wgp->GetBrowserParent(); + if (!browserParent) { + return NS_ERROR_FAILURE; + } + + *aTabId = browserParent->GetTabId(); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetChannelId(uint64_t* aChannelId) { + NS_ENSURE_ARG_POINTER(aChannelId); + + nsCOMPtr<nsIIdentChannel> channel(do_QueryInterface(mChannel)); + *aChannelId = channel ? channel->ChannelId() : 0; + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetTopLevelUrl(nsAString& aTopLevelUrl) { + nsCOMPtr<nsILoadInfo> loadInfo = mChannel->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; + } + + // Get top-level browsing context to ensure window global parent is ready + // to use, tabId is the same anyway. + 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; + } + + CopyUTF8toUTF16(uri->GetSpecOrDefault(), aTopLevelUrl); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetTables(nsACString& aTables) { + aTables.Assign(mTables); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::GetIsPrivateBrowsing(bool* aIsPrivateBrowsing) { + NS_ENSURE_ARG_POINTER(aIsPrivateBrowsing); + + *aIsPrivateBrowsing = NS_UsePrivateBrowsing(mChannel); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::Allow() { + UC_LOG(("ChannelClassifierService: allow loading the channel %p", + mChannel.get())); + + mDecision = ChannelBlockDecision::Allowed; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierBlockedChannel::Replace() { + UC_LOG(("ChannelClassifierService: replace channel %p", mChannel.get())); + + mDecision = ChannelBlockDecision::Replaced; + return NS_OK; +} + +void UrlClassifierBlockedChannel::SetReason(const nsACString& aFeatureName, + const nsACString& aTableName) { + mTables = aTableName; + + nsCOMPtr<nsIUrlClassifierFeature> feature; + feature = + UrlClassifierFeatureTrackingProtection::GetIfNameMatches(aFeatureName); + if (feature) { + mReason = TRACKING_PROTECTION; + return; + } + + feature = UrlClassifierFeatureSocialTrackingProtection::GetIfNameMatches( + aFeatureName); + if (feature) { + mReason = SOCIAL_TRACKING_PROTECTION; + return; + } + + feature = UrlClassifierFeatureFingerprintingProtection::GetIfNameMatches( + aFeatureName); + if (feature) { + mReason = FINGERPRINTING_PROTECTION; + return; + } + + feature = UrlClassifierFeatureCryptominingProtection::GetIfNameMatches( + aFeatureName); + if (feature) { + mReason = CRYPTOMINING_PROTECTION; + return; + } +} + +NS_IMPL_ISUPPORTS(ChannelClassifierService, nsIChannelClassifierService) + +// static +already_AddRefed<nsIChannelClassifierService> +ChannelClassifierService::GetSingleton() { + if (gChannelClassifierService) { + return do_AddRef(gChannelClassifierService); + } + + gChannelClassifierService = new ChannelClassifierService(); + ClearOnShutdown(&gChannelClassifierService); + return do_AddRef(gChannelClassifierService); +} + +ChannelClassifierService::ChannelClassifierService() { mListeners.Clear(); } + +NS_IMETHODIMP +ChannelClassifierService::AddListener(nsIObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ASSERT(!mListeners.Contains(aObserver)); + + mListeners.AppendElement(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +ChannelClassifierService::RemoveListener(nsIObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ASSERT(mListeners.Contains(aObserver)); + + mListeners.RemoveElement(aObserver); + return NS_OK; +} + +/* static */ +ChannelBlockDecision ChannelClassifierService::OnBeforeBlockChannel( + nsIChannel* aChannel, const nsACString& aFeatureName, + const nsACString& aTableName) { + MOZ_ASSERT(aChannel); + + // Don't bother continuing if no one has ever registered listener + if (!gChannelClassifierService || !gChannelClassifierService->HasListener()) { + return ChannelBlockDecision::Blocked; + } + + ChannelBlockDecision decision; + nsresult rv = gChannelClassifierService->OnBeforeBlockChannel( + aChannel, aFeatureName, aTableName, decision); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ChannelBlockDecision::Blocked; + } + + return decision; +} + +nsresult ChannelClassifierService::OnBeforeBlockChannel( + nsIChannel* aChannel, const nsACString& aFeatureName, + const nsACString& aTableName, ChannelBlockDecision& aDecision) { + MOZ_ASSERT(aChannel); + + aDecision = ChannelBlockDecision::Blocked; + + RefPtr<UrlClassifierBlockedChannel> channel = + new UrlClassifierBlockedChannel(aChannel); + channel->SetReason(aFeatureName, aTableName); + + for (const auto& listener : mListeners) { + listener->Observe( + NS_ISUPPORTS_CAST(nsIUrlClassifierBlockedChannel*, channel), + "urlclassifier-before-block-channel", nullptr); + + aDecision = channel->GetDecision(); + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/ChannelClassifierService.h b/netwerk/url-classifier/ChannelClassifierService.h new file mode 100644 index 0000000000..2c6c13b10d --- /dev/null +++ b/netwerk/url-classifier/ChannelClassifierService.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set expandtab ts=2 sw=2 sts=2 cin: */ +/* 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/. */ + +#ifndef mozilla_net_ChannelClassifierService_h +#define mozilla_net_ChannelClassifierService_h + +#include "nsIChannelClassifierService.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +enum class ChannelBlockDecision { + Blocked, + Replaced, + Allowed, +}; + +class UrlClassifierBlockedChannel final + : public nsIUrlClassifierBlockedChannel { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERBLOCKEDCHANNEL + + explicit UrlClassifierBlockedChannel(nsIChannel* aChannel); + + bool IsUnblocked() const { + return mDecision != ChannelBlockDecision::Blocked; + } + + ChannelBlockDecision GetDecision() { return mDecision; }; + + void SetReason(const nsACString& aFeatureName, const nsACString& aTableName); + + protected: + ~UrlClassifierBlockedChannel() = default; + + private: + nsCOMPtr<nsIChannel> mChannel; + ChannelBlockDecision mDecision; + uint8_t mReason; + nsCString mTables; +}; + +class ChannelClassifierService final : public nsIChannelClassifierService { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICHANNELCLASSIFIERSERVICE + + friend class UrlClassifierBlockedChannel; + + static already_AddRefed<nsIChannelClassifierService> GetSingleton(); + + static ChannelBlockDecision OnBeforeBlockChannel( + nsIChannel* aChannel, const nsACString& aFeatureName, + const nsACString& aTableName); + + nsresult OnBeforeBlockChannel(nsIChannel* aChannel, + const nsACString& aFeatureName, + const nsACString& aTableName, + ChannelBlockDecision& aDecision); + + bool HasListener() const { return !mListeners.IsEmpty(); } + + private: + ChannelClassifierService(); + ~ChannelClassifierService() = default; + + nsTArray<nsCOMPtr<nsIObserver>> mListeners; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ChannelClassifierService_h diff --git a/netwerk/url-classifier/UrlClassifierCommon.cpp b/netwerk/url-classifier/UrlClassifierCommon.cpp new file mode 100644 index 0000000000..21589cef94 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierCommon.cpp @@ -0,0 +1,671 @@ +/* -*- 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 "mozilla/net/UrlClassifierCommon.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Components.h" +#include "mozilla/ContentBlockingAllowList.h" +#include "mozilla/ContentBlockingNotifier.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPrefs_channelclassifier.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozIThirdPartyUtil.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsIClassifiedChannel.h" +#include "mozilla/dom/Document.h" +#include "nsIDocShell.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIParentChannel.h" +#include "nsIScriptError.h" +#include "nsIWebProgressListener.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsReadableUtils.h" + +namespace mozilla { +namespace net { + +const nsCString::size_type UrlClassifierCommon::sMaxSpecLength = 128; + +// MOZ_LOG=nsChannelClassifier:5 +LazyLogModule UrlClassifierCommon::sLog("nsChannelClassifier"); +LazyLogModule UrlClassifierCommon::sLogLeak("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::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; + TablesToString(aLists, strLists); + TablesToString(aFullHashes, strHashes); + + parentChannel->SetClassifierMatchedTrackingInfo(strLists, strHashes); + } + + return NS_OK; +} + +/* static */ +nsresult UrlClassifierCommon::SetBlockedContent(nsIChannel* channel, + nsresult aErrorCode, + const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) { + NS_ENSURE_ARG(!aList.IsEmpty()); + + switch (aErrorCode) { + case NS_ERROR_MALWARE_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_MALWARE_URI); + break; + case NS_ERROR_PHISHING_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_PHISHING_URI); + break; + case NS_ERROR_UNWANTED_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_UNWANTED_URI); + break; + case NS_ERROR_TRACKING_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_TRACKING_URI); + break; + case NS_ERROR_BLOCKED_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_BLOCKED_URI); + break; + case NS_ERROR_HARMFUL_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_HARMFUL_URI); + break; + case NS_ERROR_CRYPTOMINING_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_CRYPTOMINING_URI); + break; + case NS_ERROR_FINGERPRINTING_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_FINGERPRINTING_URI); + break; + case NS_ERROR_SOCIALTRACKING_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_SOCIALTRACKING_URI); + break; + case NS_ERROR_EMAILTRACKING_URI: + NS_SetRequestBlockingReason( + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_EMAILTRACKING_URI); + break; + default: + MOZ_CRASH( + "Missing nsILoadInfo::BLOCKING_REASON* for the classification error"); + break; + } + + // Can be called in EITHER the parent or child process. + nsresult rv; + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(channel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (classifiedChannel) { + classifiedChannel->SetMatchedInfo(aList, aProvider, aFullHash); + } + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIParentChannel> parentChannel; + NS_QueryNotificationCallbacks(channel, 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 + // matched info once we move the ContentBlockingLog into the parent. + // This would be done in Bug 1601063. + parentChannel->SetClassifierMatchedInfo(aList, aProvider, aFullHash); + } + + unsigned state = + UrlClassifierFeatureFactory::GetClassifierBlockingEventCode(aErrorCode); + if (!state) { + state = nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT; + } + ContentBlockingNotifier::OnEvent(channel, state); + + return NS_OK; + } + + // TODO: ReportToConsole is called in the child process, + // If nsContentUtils::ReportToConsole is not fission compatiable(cannot report + // to correct top-level window), we need to do this in the parent process + // instead (find the top-level window in the parent and send an IPC to child + // processes to report console). + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + components::ThirdPartyUtil::Service(); + if (NS_WARN_IF(!thirdPartyUtil)) { + return NS_OK; + } + + nsCOMPtr<nsIURI> uriBeingLoaded = + AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(channel); + nsCOMPtr<mozIDOMWindowProxy> win; + rv = thirdPartyUtil->GetTopWindowForChannel(channel, uriBeingLoaded, + getter_AddRefs(win)); + NS_ENSURE_SUCCESS(rv, NS_OK); + auto* pwin = nsPIDOMWindowOuter::From(win); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + if (!docShell) { + return NS_OK; + } + RefPtr<dom::Document> doc = docShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_OK); + + // Log a warning to the web console. + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + AutoTArray<nsString, 1> params; + CopyUTF8toUTF16(uri->GetSpecOrDefault(), *params.AppendElement()); + const char* message; + nsCString category; + + if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode)) { + message = UrlClassifierFeatureFactory:: + ClassifierBlockingErrorCodeToConsoleMessage(aErrorCode, category); + } else { + message = "UnsafeUriBlocked"; + category = "Safe Browsing"_ns; + } + + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, category, doc, + nsContentUtils::eNECKO_PROPERTIES, message, + params); + + return NS_OK; +} + +/* 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)); + Unused << 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)); + Unused << 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 = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &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; +} + +namespace { + +void LowerPriorityHelper(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + bool isBlockingResource = false; + + nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel)); + if (cos) { + if (StaticPrefs::network_http_tailing_enabled()) { + uint32_t cosFlags = 0; + cos->GetClassFlags(&cosFlags); + isBlockingResource = + cosFlags & (nsIClassOfService::UrgentStart | + nsIClassOfService::Leader | nsIClassOfService::Unblocked); + + // Requests not allowed to be tailed are usually those with higher + // prioritization. That overweights being a tracker: don't throttle + // them when not in background. + if (!(cosFlags & nsIClassOfService::TailForbidden)) { + cos->AddClassFlags(nsIClassOfService::Throttleable); + } + } else { + // Yes, we even don't want to evaluate the isBlockingResource when tailing + // is off see bug 1395525. + + cos->AddClassFlags(nsIClassOfService::Throttleable); + } + } + + if (!isBlockingResource) { + nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aChannel); + if (p) { + UC_LOG( + ("UrlClassifierCommon::LowerPriorityHelper - " + "setting PRIORITY_LOWEST for channel %p", + aChannel)); + p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST); + } + } +} + +} // namespace + +// static +void UrlClassifierCommon::SetClassificationFlagsHelper( + nsIChannel* aChannel, uint32_t aClassificationFlags, bool aIsThirdParty) { + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsIParentChannel> parentChannel; + NS_QueryNotificationCallbacks(aChannel, parentChannel); + if (parentChannel) { + // This channel is a parent-process proxy for a child process + // request. We should notify the child process as well. + parentChannel->NotifyClassificationFlags(aClassificationFlags, + aIsThirdParty); + } + + RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(aChannel); + if (httpChannel) { + httpChannel->AddClassificationFlags(aClassificationFlags, aIsThirdParty); + } +} + +// static +void UrlClassifierCommon::AnnotateChannel(nsIChannel* aChannel, + uint32_t aClassificationFlags, + uint32_t aLoadingState) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsIURI> chanURI; + nsresult rv = aChannel->GetURI(getter_AddRefs(chanURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + bool isThirdPartyWithTopLevelWinURI = + AntiTrackingUtils::IsThirdPartyChannel(aChannel); + + SetClassificationFlagsHelper(aChannel, aClassificationFlags, + isThirdPartyWithTopLevelWinURI); + + // We consider valid tracking flags (based on the current strict vs basic list + // prefs) and cryptomining (which is not considered as tracking). + bool validClassificationFlags = + IsTrackingClassificationFlag(aClassificationFlags, + NS_UsePrivateBrowsing(aChannel)) || + IsCryptominingClassificationFlag(aClassificationFlags, + NS_UsePrivateBrowsing(aChannel)); + + if (validClassificationFlags && isThirdPartyWithTopLevelWinURI) { + ContentBlockingNotifier::OnEvent(aChannel, aLoadingState); + } + + if (isThirdPartyWithTopLevelWinURI && + StaticPrefs::privacy_trackingprotection_lower_network_priority()) { + LowerPriorityHelper(aChannel); + } +} + +// static +bool UrlClassifierCommon::IsAllowListed(nsIChannel* aChannel) { + nsCOMPtr<nsIHttpChannelInternal> channel = do_QueryInterface(aChannel); + if (NS_WARN_IF(!channel)) { + return false; + } + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + + bool isAllowListed = false; + if (StaticPrefs::channelclassifier_allowlist_example()) { + UC_LOG( + ("UrlClassifierCommon::IsAllowListed - " + "check allowlisting test domain on channel %p", + aChannel)); + + nsCOMPtr<nsIIOService> ios = components::IO::Service(); + if (NS_WARN_IF(!ios)) { + return false; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = ios->NewURI("http://allowlisted.example.com"_ns, nullptr, + nullptr, getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + nsCOMPtr<nsIPrincipal> cbAllowListPrincipal = + BasePrincipal::CreateContentPrincipal(uri, + loadInfo->GetOriginAttributes()); + + rv = ContentBlockingAllowList::Check( + cbAllowListPrincipal, NS_UsePrivateBrowsing(aChannel), isAllowListed); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + } else { + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + MOZ_ALWAYS_SUCCEEDS( + loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings))); + isAllowListed = cookieJarSettings->GetIsOnContentBlockingAllowList(); + } + + if (isAllowListed) { + UC_LOG(("UrlClassifierCommon::IsAllowListed - user override on channel %p", + aChannel)); + } + + return isAllowListed; +} + +// static +bool UrlClassifierCommon::IsTrackingClassificationFlag(uint32_t aFlag, + bool aIsPrivate) { + bool isLevel2ListEnabled = + aIsPrivate + ? StaticPrefs::privacy_annotate_channels_strict_list_pbmode_enabled() + : StaticPrefs::privacy_annotate_channels_strict_list_enabled(); + + if (isLevel2ListEnabled && + (aFlag & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_ANY_STRICT_TRACKING)) { + return true; + } + + if (StaticPrefs::privacy_socialtracking_block_cookies_enabled() && + IsSocialTrackingClassificationFlag(aFlag)) { + return true; + } + + return ( + aFlag & + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_ANY_BASIC_TRACKING); +} + +// static +bool UrlClassifierCommon::IsSocialTrackingClassificationFlag(uint32_t aFlag) { + return (aFlag & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_ANY_SOCIAL_TRACKING) != 0; +} + +// static +bool UrlClassifierCommon::IsCryptominingClassificationFlag(uint32_t aFlag, + bool aIsPrivate) { + if (aFlag & + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_CRYPTOMINING) { + return true; + } + + bool isLevel2ListEnabled = + aIsPrivate + ? StaticPrefs::privacy_annotate_channels_strict_list_pbmode_enabled() + : StaticPrefs::privacy_annotate_channels_strict_list_enabled(); + + if (isLevel2ListEnabled && + (aFlag & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_CRYPTOMINING_CONTENT)) { + return true; + } + + return false; +} + +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::IsPassiveContent(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + ExtContentPolicyType contentType = loadInfo->GetExternalContentPolicyType(); + + // Return true if aChannel is loading passive display content, as + // defined by the mixed content blocker. + // https://searchfox.org/mozilla-central/rev/c80fa7258c935223fe319c5345b58eae85d4c6ae/dom/security/nsMixedContentBlocker.cpp#532 + return contentType == ExtContentPolicy::TYPE_IMAGE || + contentType == ExtContentPolicy::TYPE_MEDIA || + (contentType == ExtContentPolicy::TYPE_OBJECT_SUBREQUEST && + !StaticPrefs::security_mixed_content_block_object_subrequest()); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierCommon.h b/netwerk/url-classifier/UrlClassifierCommon.h new file mode 100644 index 0000000000..c1d92ef981 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierCommon.h @@ -0,0 +1,103 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierCommon_h +#define mozilla_net_UrlClassifierCommon_h + +#include "mozilla/Logging.h" +#include "nsString.h" + +#include <vector> + +class nsIChannel; +class nsIURI; + +#define UC_LOG(args) MOZ_LOG(UrlClassifierCommon::sLog, LogLevel::Info, args) +#define UC_LOG_DEBUG(args) \ + MOZ_LOG(UrlClassifierCommon::sLog, LogLevel::Debug, args) +#define UC_LOG_WARN(args) \ + MOZ_LOG(UrlClassifierCommon::sLog, LogLevel::Warning, args) +#define UC_LOG_LEAK(args) \ + MOZ_LOG(UrlClassifierCommon::sLogLeak, LogLevel::Info, args) + +#define UC_LOG_ENABLED() \ + MOZ_LOG_TEST(UrlClassifierCommon::sLog, LogLevel::Info) || \ + MOZ_LOG_TEST(UrlClassifierCommon::sLogLeak, LogLevel::Info) + +namespace mozilla { +namespace net { + +class UrlClassifierCommon final { + public: + static const nsCString::size_type sMaxSpecLength; + + static LazyLogModule sLog; + static LazyLogModule sLogLeak; + + static bool AddonMayLoad(nsIChannel* aChannel, nsIURI* aURI); + + static bool ShouldEnableProtectionForChannel(nsIChannel* aChannel); + + static nsresult SetBlockedContent(nsIChannel* channel, nsresult aErrorCode, + const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash); + + static nsresult SetTrackingInfo(nsIChannel* channel, + const nsTArray<nsCString>& aLists, + const nsTArray<nsCString>& aFullHashes); + + // Use this function only when you are looking for a pairwise entitylist uri + // with the format: http://toplevel.page/?resource=channel.uri.domain + static nsresult CreatePairwiseEntityListURI(nsIChannel* aChannel, + nsIURI** aURI); + + static void AnnotateChannel(nsIChannel* aChannel, + uint32_t aClassificationFlags, + uint32_t aLoadingState); + + static bool IsAllowListed(nsIChannel* aChannel); + + static bool IsTrackingClassificationFlag(uint32_t aFlag, bool aIsPrivate); + + static bool IsSocialTrackingClassificationFlag(uint32_t aFlag); + + static bool IsCryptominingClassificationFlag(uint32_t aFlag, bool aIsPrivate); + + // Join the table names in 1 single string. + static void TablesToString(const nsTArray<nsCString>& aList, + nsACString& aString); + + struct ClassificationData { + nsCString mPrefix; + uint32_t mFlag; + }; + + // Checks if the entries in aList are part of the ClassificationData vector + // and it returns the corresponding flags. If none of them is found, the + // default flag is returned. + static uint32_t TablesToClassificationFlags( + const nsTArray<nsCString>& aList, + const std::vector<ClassificationData>& aData, uint32_t aDefaultFlag); + + static bool IsPassiveContent(nsIChannel* aChannel); + + static void SetClassificationFlagsHelper(nsIChannel* aChannel, + uint32_t aClassificationFlags, + bool aIsThirdParty); + + private: + static uint32_t TableToClassificationFlag( + const nsACString& aTable, const std::vector<ClassificationData>& aData); + + friend class AsyncUrlChannelClassifier; + static nsresult GetTopWindowURI(nsIChannel* aChannel, nsIURI** aURI); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierCommon_h diff --git a/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs b/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs new file mode 100644 index 0000000000..8c2bc8a3aa --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs @@ -0,0 +1,168 @@ +/* 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/. */ + +export function UrlClassifierExceptionListService() {} + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", +}); + +const COLLECTION_NAME = "url-classifier-skip-urls"; + +class Feature { + constructor(name, prefName) { + this.name = name; + this.prefName = prefName; + this.observers = new Set(); + this.prefValue = null; + this.remoteEntries = null; + + if (prefName) { + this.prefValue = Services.prefs.getStringPref(this.prefName, null); + Services.prefs.addObserver(prefName, this); + } + } + + async addAndRunObserver(observer) { + this.observers.add(observer); + this.notifyObservers(observer); + } + + removeObserver(observer) { + this.observers.delete(observer); + } + + observe(subject, topic, data) { + if (topic != "nsPref:changed" || data != this.prefName) { + console.error(`Unexpected event ${topic} with ${data}`); + return; + } + + this.prefValue = Services.prefs.getStringPref(this.prefName, null); + this.notifyObservers(); + } + + onRemoteSettingsUpdate(entries) { + this.remoteEntries = []; + + for (let entry of entries) { + if (entry.feature == this.name) { + this.remoteEntries.push(entry.pattern.toLowerCase()); + } + } + } + + notifyObservers(observer = null) { + let entries = []; + if (this.prefValue) { + entries = this.prefValue.split(","); + } + + if (this.remoteEntries) { + for (let entry of this.remoteEntries) { + entries.push(entry); + } + } + + let entriesAsString = entries.join(",").toLowerCase(); + if (observer) { + observer.onExceptionListUpdate(entriesAsString); + } else { + for (let obs of this.observers) { + obs.onExceptionListUpdate(entriesAsString); + } + } + } +} + +UrlClassifierExceptionListService.prototype = { + classID: Components.ID("{b9f4fd03-9d87-4bfd-9958-85a821750ddc}"), + QueryInterface: ChromeUtils.generateQI([ + "nsIUrlClassifierExceptionListService", + ]), + + features: {}, + _initialized: false, + + async lazyInit() { + if (this._initialized) { + return; + } + + let rs = lazy.RemoteSettings(COLLECTION_NAME); + rs.on("sync", event => { + let { + data: { current }, + } = event; + this.entries = current || []; + this.onUpdateEntries(current); + }); + + this._initialized = true; + + // If the remote settings list hasn't been populated yet we have to make sure + // to do it before firing the first notification. + // This has to be run after _initialized is set because we'll be + // blocked while getting entries from RemoteSetting, and we don't want + // LazyInit is executed again. + try { + // The data will be initially available from the local DB (via a + // resource:// URI). + this.entries = await rs.get(); + } catch (e) {} + + // RemoteSettings.get() could return null, ensure passing a list to + // onUpdateEntries. + if (!this.entries) { + this.entries = []; + } + + this.onUpdateEntries(this.entries); + }, + + onUpdateEntries(entries) { + for (let key of Object.keys(this.features)) { + let feature = this.features[key]; + feature.onRemoteSettingsUpdate(entries); + feature.notifyObservers(); + } + }, + + registerAndRunExceptionListObserver(feature, prefName, observer) { + // We don't await this; the caller is C++ and won't await this function, + // and because we prevent re-entering into this method, once it's been + // called once any subsequent calls will early-return anyway - so + // awaiting that would be meaningless. Instead, `Feature` implementations + // make sure not to call into observers until they have data, and we + // make sure to let feature instances know whether we have data + // immediately. + this.lazyInit(); + + if (!this.features[feature]) { + let featureObj = new Feature(feature, prefName); + this.features[feature] = featureObj; + // If we've previously initialized, we need to pass the entries + // we already have to the new feature. + if (this.entries) { + featureObj.onRemoteSettingsUpdate(this.entries); + } + } + this.features[feature].addAndRunObserver(observer); + }, + + unregisterExceptionListObserver(feature, observer) { + if (!this.features[feature]) { + return; + } + this.features[feature].removeObserver(observer); + }, + + clear() { + this.features = {}; + this._initialized = false; + this.entries = null; + }, +}; diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.cpp b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp new file mode 100644 index 0000000000..6f5924ab50 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp @@ -0,0 +1,182 @@ +/* -*- 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 "UrlClassifierFeatureBase.h" +#include "Classifier.h" +#include "mozilla/Preferences.h" + +namespace mozilla { + +using namespace safebrowsing; + +namespace net { + +namespace { + +void OnPrefsChange(const char* aPrefName, void* aArray) { + auto* array = static_cast<nsTArray<nsCString>*>(aArray); + MOZ_ASSERT(array); + + nsAutoCString value; + Preferences::GetCString(aPrefName, value); + Classifier::SplitTables(value, *array); +} + +} // namespace + +NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureBase) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeature) + NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeature) + NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierExceptionListObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(UrlClassifierFeatureBase) +NS_IMPL_RELEASE(UrlClassifierFeatureBase) + +UrlClassifierFeatureBase::UrlClassifierFeatureBase( + const nsACString& aName, const nsACString& aPrefBlocklistTables, + const nsACString& aPrefEntitylistTables, + const nsACString& aPrefBlocklistHosts, + const nsACString& aPrefEntitylistHosts, + const nsACString& aPrefBlocklistTableName, + const nsACString& aPrefEntitylistTableName, + const nsACString& aPrefExceptionHosts) + : mName(aName), mPrefExceptionHosts(aPrefExceptionHosts) { + static_assert(nsIUrlClassifierFeature::blocklist == 0, + "nsIUrlClassifierFeature::blocklist must be 0"); + static_assert(nsIUrlClassifierFeature::entitylist == 1, + "nsIUrlClassifierFeature::entitylist must be 1"); + + mPrefTables[nsIUrlClassifierFeature::blocklist] = aPrefBlocklistTables; + mPrefTables[nsIUrlClassifierFeature::entitylist] = aPrefEntitylistTables; + + mPrefHosts[nsIUrlClassifierFeature::blocklist] = aPrefBlocklistHosts; + mPrefHosts[nsIUrlClassifierFeature::entitylist] = aPrefEntitylistHosts; + + mPrefTableNames[nsIUrlClassifierFeature::blocklist] = aPrefBlocklistTableName; + mPrefTableNames[nsIUrlClassifierFeature::entitylist] = + aPrefEntitylistTableName; +} + +UrlClassifierFeatureBase::~UrlClassifierFeatureBase() = default; + +void UrlClassifierFeatureBase::InitializePreferences() { + for (uint32_t i = 0; i < 2; ++i) { + if (!mPrefTables[i].IsEmpty()) { + Preferences::RegisterCallbackAndCall(OnPrefsChange, mPrefTables[i], + &mTables[i]); + } + + if (!mPrefHosts[i].IsEmpty()) { + Preferences::RegisterCallbackAndCall(OnPrefsChange, mPrefHosts[i], + &mHosts[i]); + } + } + + nsCOMPtr<nsIUrlClassifierExceptionListService> exceptionListService = + do_GetService("@mozilla.org/url-classifier/exception-list-service;1"); + if (NS_WARN_IF(!exceptionListService)) { + return; + } + + exceptionListService->RegisterAndRunExceptionListObserver( + mName, mPrefExceptionHosts, this); +} + +void UrlClassifierFeatureBase::ShutdownPreferences() { + for (uint32_t i = 0; i < 2; ++i) { + if (!mPrefTables[i].IsEmpty()) { + Preferences::UnregisterCallback(OnPrefsChange, mPrefTables[i], + &mTables[i]); + } + + if (!mPrefHosts[i].IsEmpty()) { + Preferences::UnregisterCallback(OnPrefsChange, mPrefHosts[i], &mHosts[i]); + } + } + + nsCOMPtr<nsIUrlClassifierExceptionListService> exceptionListService = + do_GetService("@mozilla.org/url-classifier/exception-list-service;1"); + if (exceptionListService) { + exceptionListService->UnregisterExceptionListObserver(mName, this); + } +} + +NS_IMETHODIMP +UrlClassifierFeatureBase::OnExceptionListUpdate(const nsACString& aList) { + mExceptionHosts = aList; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureBase::GetName(nsACString& aName) { + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureBase::GetTables(nsIUrlClassifierFeature::listType aListType, + nsTArray<nsCString>& aTables) { + if (aListType != nsIUrlClassifierFeature::blocklist && + aListType != nsIUrlClassifierFeature::entitylist) { + return NS_ERROR_INVALID_ARG; + } + + aTables = mTables[aListType].Clone(); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureBase::HasTable(const nsACString& aTable, + nsIUrlClassifierFeature::listType aListType, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (aListType != nsIUrlClassifierFeature::blocklist && + aListType != nsIUrlClassifierFeature::entitylist) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = mTables[aListType].Contains(aTable); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureBase::HasHostInPreferences( + const nsACString& aHost, nsIUrlClassifierFeature::listType aListType, + nsACString& aPrefTableName, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (aListType != nsIUrlClassifierFeature::blocklist && + aListType != nsIUrlClassifierFeature::entitylist) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = mHosts[aListType].Contains(aHost); + if (*aResult) { + aPrefTableName = mPrefTableNames[aListType]; + } + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureBase::GetExceptionHostList(nsACString& aList) { + aList = mExceptionHosts; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureAntiTrackingBase::GetExceptionHostList(nsACString& aList) { + if (!StaticPrefs::privacy_antitracking_enableWebcompat()) { + aList.Truncate(); + return NS_OK; + } + + return UrlClassifierFeatureBase::GetExceptionHostList(aList); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.h b/netwerk/url-classifier/UrlClassifierFeatureBase.h new file mode 100644 index 0000000000..79e1f3d087 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureBase.h @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureBase_h +#define mozilla_net_UrlClassifierFeatureBase_h + +#include "nsIUrlClassifierFeature.h" +#include "nsIUrlClassifierExceptionListService.h" +#include "nsTArray.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureBase : public nsIUrlClassifierFeature, + public nsIUrlClassifierExceptionListObserver { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD + GetName(nsACString& aName) override; + + NS_IMETHOD + GetTables(nsIUrlClassifierFeature::listType aListType, + nsTArray<nsCString>& aResult) override; + + NS_IMETHOD + HasTable(const nsACString& aTable, + nsIUrlClassifierFeature::listType aListType, bool* aResult) override; + + NS_IMETHOD + HasHostInPreferences(const nsACString& aHost, + nsIUrlClassifierFeature::listType aListType, + nsACString& aPrefTableName, bool* aResult) override; + + NS_IMETHOD + GetExceptionHostList(nsACString& aList) override; + + NS_IMETHOD + OnExceptionListUpdate(const nsACString& aList) override; + + protected: + UrlClassifierFeatureBase(const nsACString& aName, + const nsACString& aPrefBlocklistTables, + const nsACString& aPrefEntitylistTables, + const nsACString& aPrefBlocklistHosts, + const nsACString& aPrefEntitylistHosts, + const nsACString& aPrefBlocklistTableName, + const nsACString& aPrefEntitylistTableName, + const nsACString& aPrefExceptionHosts); + + virtual ~UrlClassifierFeatureBase(); + + void InitializePreferences(); + void ShutdownPreferences(); + + nsCString mName; + + private: + nsCString mPrefExceptionHosts; + + // 2: blocklist and entitylist. + nsCString mPrefTables[2]; + nsTArray<nsCString> mTables[2]; + + nsCString mPrefHosts[2]; + nsCString mPrefTableNames[2]; + nsTArray<nsCString> mHosts[2]; + + nsCString mExceptionHosts; +}; + +class UrlClassifierFeatureAntiTrackingBase : public UrlClassifierFeatureBase { + using UrlClassifierFeatureBase::UrlClassifierFeatureBase; + + NS_IMETHOD + GetExceptionHostList(nsACString& aList) override; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureBase_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp new file mode 100644 index 0000000000..dbd46eb298 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp @@ -0,0 +1,170 @@ +/* -*- 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 "UrlClassifierFeatureCryptominingAnnotation.h" + +#include "mozilla/net/UrlClassifierCommon.h" +#include "nsIClassifiedChannel.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsIWebProgressListener.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +namespace { + +#define CRYPTOMINING_ANNOTATION_FEATURE_NAME "cryptomining-annotation" + +#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST \ + "urlclassifier.features.cryptomining.annotate.blacklistTables" +#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.cryptomining.annotate.blacklistHosts" +#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST \ + "urlclassifier.features.cryptomining.annotate.whitelistTables" +#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.cryptomining.annotate.whitelistHosts" +#define URLCLASSIFIER_CRYPTOMINING_ANNOTATION_EXCEPTION_URLS \ + "urlclassifier.features.cryptomining.annotate.skipURLs" +#define TABLE_CRYPTOMINING_ANNOTATION_BLOCKLIST_PREF \ + "cryptomining-annotate-blacklist-pref" +#define TABLE_CRYPTOMINING_ANNOTATION_ENTITYLIST_PREF \ + "cryptomining-annotate-whitelist-pref" + +StaticRefPtr<UrlClassifierFeatureCryptominingAnnotation> + gFeatureCryptominingAnnotation; + +} // namespace + +UrlClassifierFeatureCryptominingAnnotation:: + UrlClassifierFeatureCryptominingAnnotation() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(CRYPTOMINING_ANNOTATION_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST), + nsLiteralCString( + URLCLASSIFIER_CRYPTOMINING_ANNOTATION_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_CRYPTOMINING_ANNOTATION_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_CRYPTOMINING_ANNOTATION_BLOCKLIST_PREF), + nsLiteralCString(TABLE_CRYPTOMINING_ANNOTATION_ENTITYLIST_PREF), + nsLiteralCString( + URLCLASSIFIER_CRYPTOMINING_ANNOTATION_EXCEPTION_URLS)) {} +/* static */ const char* UrlClassifierFeatureCryptominingAnnotation::Name() { + return CRYPTOMINING_ANNOTATION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureCryptominingAnnotation::MaybeInitialize() { + UC_LOG_LEAK(("UrlClassifierFeatureCryptominingAnnotation::MaybeInitialize")); + + if (!gFeatureCryptominingAnnotation) { + gFeatureCryptominingAnnotation = + new UrlClassifierFeatureCryptominingAnnotation(); + gFeatureCryptominingAnnotation->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureCryptominingAnnotation::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureCryptominingAnnotation::MaybeShutdown")); + + if (gFeatureCryptominingAnnotation) { + gFeatureCryptominingAnnotation->ShutdownPreferences(); + gFeatureCryptominingAnnotation = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureCryptominingAnnotation> +UrlClassifierFeatureCryptominingAnnotation::MaybeCreate(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureCryptominingAnnotation::MaybeCreate - channel %p", + aChannel)); + + MaybeInitialize(); + MOZ_ASSERT(gFeatureCryptominingAnnotation); + + RefPtr<UrlClassifierFeatureCryptominingAnnotation> self = + gFeatureCryptominingAnnotation; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureCryptominingAnnotation::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(CRYPTOMINING_ANNOTATION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureCryptominingAnnotation); + + RefPtr<UrlClassifierFeatureCryptominingAnnotation> self = + gFeatureCryptominingAnnotation; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureCryptominingAnnotation::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + // This is not a blocking feature. + *aShouldContinue = true; + + UC_LOG( + ("UrlClassifierFeatureCryptominingAnnotation::ProcessChannel - " + "annotating channel %p", + aChannel)); + + static std::vector<UrlClassifierCommon::ClassificationData> + sClassificationData = { + {"content-cryptomining-track-"_ns, + nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_CRYPTOMINING_CONTENT}, + }; + + uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags( + aList, sClassificationData, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_CRYPTOMINING); + + UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes); + + UrlClassifierCommon::AnnotateChannel( + aChannel, flags, + nsIWebProgressListener::STATE_LOADED_CRYPTOMINING_CONTENT); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureCryptominingAnnotation::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h new file mode 100644 index 0000000000..c843af871d --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureCryptominingAnnotation_h +#define mozilla_net_UrlClassifierFeatureCryptominingAnnotation_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureCryptominingAnnotation final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureCryptominingAnnotation> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureCryptominingAnnotation(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureCryptominingAnnotation_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp new file mode 100644 index 0000000000..72406179bc --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp @@ -0,0 +1,211 @@ +/* -*- 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 "UrlClassifierFeatureCryptominingProtection.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "ChannelClassifierService.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsIWebProgressListener.h" +#include "nsIHttpChannelInternal.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +namespace { + +#define CRYPTOMINING_FEATURE_NAME "cryptomining-protection" + +#define URLCLASSIFIER_CRYPTOMINING_BLOCKLIST \ + "urlclassifier.features.cryptomining.blacklistTables" +#define URLCLASSIFIER_CRYPTOMINING_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.cryptomining.blacklistHosts" +#define URLCLASSIFIER_CRYPTOMINING_ENTITYLIST \ + "urlclassifier.features.cryptomining.whitelistTables" +#define URLCLASSIFIER_CRYPTOMINING_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.cryptomining.whitelistHosts" +#define URLCLASSIFIER_CRYPTOMINING_EXCEPTION_URLS \ + "urlclassifier.features.cryptomining.skipURLs" +#define TABLE_CRYPTOMINING_BLOCKLIST_PREF "cryptomining-blacklist-pref" +#define TABLE_CRYPTOMINING_ENTITYLIST_PREF "cryptomining-whitelist-pref" + +StaticRefPtr<UrlClassifierFeatureCryptominingProtection> + gFeatureCryptominingProtection; + +} // namespace + +UrlClassifierFeatureCryptominingProtection:: + UrlClassifierFeatureCryptominingProtection() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(CRYPTOMINING_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ENTITYLIST), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_CRYPTOMINING_BLOCKLIST_PREF), + nsLiteralCString(TABLE_CRYPTOMINING_ENTITYLIST_PREF), + nsLiteralCString(URLCLASSIFIER_CRYPTOMINING_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureCryptominingProtection::Name() { + return CRYPTOMINING_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureCryptominingProtection::MaybeInitialize() { + UC_LOG_LEAK(("UrlClassifierFeatureCryptominingProtection::MaybeInitialize")); + + if (!gFeatureCryptominingProtection) { + gFeatureCryptominingProtection = + new UrlClassifierFeatureCryptominingProtection(); + gFeatureCryptominingProtection->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureCryptominingProtection::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureCryptominingProtection::MaybeShutdown")); + + if (gFeatureCryptominingProtection) { + gFeatureCryptominingProtection->ShutdownPreferences(); + gFeatureCryptominingProtection = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureCryptominingProtection> +UrlClassifierFeatureCryptominingProtection::MaybeCreate(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureCryptominingProtection::MaybeCreate - channel %p", + aChannel)); + + if (!StaticPrefs::privacy_trackingprotection_cryptomining_enabled()) { + return nullptr; + } + + bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel); + if (!isThirdParty) { + UC_LOG( + ("UrlClassifierFeatureCryptominingProtection::MaybeCreate - " + "skipping first party or top-level load for channel %p", + aChannel)); + return nullptr; + } + + if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureCryptominingProtection); + + RefPtr<UrlClassifierFeatureCryptominingProtection> self = + gFeatureCryptominingProtection; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureCryptominingProtection::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(CRYPTOMINING_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureCryptominingProtection); + + RefPtr<UrlClassifierFeatureCryptominingProtection> self = + gFeatureCryptominingProtection; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureCryptominingProtection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel); + + // This is a blocking feature. + *aShouldContinue = isAllowListed; + + if (isAllowListed) { + return NS_OK; + } + + nsAutoCString list; + UrlClassifierCommon::TablesToString(aList, list); + + ChannelBlockDecision decision = + ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list); + if (decision != ChannelBlockDecision::Blocked) { + uint32_t event = + decision == ChannelBlockDecision::Replaced + ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT + : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT; + + // Need to set aBlocked to True if we replace the Cryptominer with a shim, + // since the shim is treated as a blocked event + // Note: If we need to account for which kind of tracker was replaced, + // we need to create a new event type in nsIWebProgressListener + if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) { + ContentBlockingNotifier::OnEvent(aChannel, event, true); + } else { + ContentBlockingNotifier::OnEvent(aChannel, event, false); + } + + *aShouldContinue = true; + return NS_OK; + } + + UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_CRYPTOMINING_URI, + list, ""_ns, ""_ns); + + UC_LOG( + ("UrlClassifierFeatureCryptominingProtection::ProcessChannel - " + "cancelling channel %p", + aChannel)); + + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel); + + if (httpChannel) { + Unused << httpChannel->CancelByURLClassifier(NS_ERROR_CRYPTOMINING_URI); + } else { + Unused << aChannel->Cancel(NS_ERROR_CRYPTOMINING_URI); + } + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureCryptominingProtection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h new file mode 100644 index 0000000000..5ff888ab17 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureCryptominingProtection_h +#define mozilla_net_UrlClassifierFeatureCryptominingProtection_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureCryptominingProtection final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureCryptominingProtection> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureCryptominingProtection(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureCryptominingProtection_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp new file mode 100644 index 0000000000..ec5ad185ae --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp @@ -0,0 +1,103 @@ +/* -*- 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 "UrlClassifierFeatureCustomTables.h" + +namespace mozilla { + +NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureCustomTables) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeature) + NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeature) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(UrlClassifierFeatureCustomTables) +NS_IMPL_RELEASE(UrlClassifierFeatureCustomTables) + +UrlClassifierFeatureCustomTables::UrlClassifierFeatureCustomTables( + const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables, + const nsTArray<nsCString>& aEntitylistTables) + : mName(aName), + mBlocklistTables(aBlocklistTables.Clone()), + mEntitylistTables(aEntitylistTables.Clone()) {} + +UrlClassifierFeatureCustomTables::~UrlClassifierFeatureCustomTables() = default; + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::GetName(nsACString& aName) { + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::GetTables( + nsIUrlClassifierFeature::listType aListType, nsTArray<nsCString>& aTables) { + if (aListType == nsIUrlClassifierFeature::blocklist) { + aTables = mBlocklistTables.Clone(); + return NS_OK; + } + + if (aListType == nsIUrlClassifierFeature::entitylist) { + aTables = mEntitylistTables.Clone(); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::HasTable( + const nsACString& aTable, nsIUrlClassifierFeature::listType aListType, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aResult = mBlocklistTables.Contains(aTable); + return NS_OK; + } + + if (aListType == nsIUrlClassifierFeature::entitylist) { + *aResult = mEntitylistTables.Contains(aTable); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::HasHostInPreferences( + const nsACString& aHost, nsIUrlClassifierFeature::listType aListType, + nsACString& aPrefTableName, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::GetExceptionHostList(nsACString& aList) { + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + // This is not a blocking feature. + *aShouldContinue = true; + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureCustomTables::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h new file mode 100644 index 0000000000..0fc12243fe --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef mozilla_UrlClassifierFeatureCustomTables_h +#define mozilla_UrlClassifierFeatureCustomTables_h + +#include "nsIUrlClassifierFeature.h" +#include "nsTArray.h" +#include "nsString.h" + +namespace mozilla { + +class UrlClassifierFeatureCustomTables : public nsIUrlClassifierFeature { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERFEATURE + + explicit UrlClassifierFeatureCustomTables( + const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables, + const nsTArray<nsCString>& aEntitylistTables); + + private: + virtual ~UrlClassifierFeatureCustomTables(); + + nsCString mName; + nsTArray<nsCString> mBlocklistTables; + nsTArray<nsCString> mEntitylistTables; +}; + +} // namespace mozilla + +#endif // mozilla_UrlClassifierFeatureCustomTables_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp new file mode 100644 index 0000000000..d31e34047a --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp @@ -0,0 +1,268 @@ +/* -*- 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 "UrlClassifierFeatureEmailTrackingDataCollection.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ContentBlockingNotifier.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" + +#include "nsIChannel.h" +#include "nsIClassifiedChannel.h" +#include "nsILoadInfo.h" + +namespace mozilla::net { + +namespace { + +#define EMAILTRACKING_DATACOLLECTION_FEATURE_NAME \ + "emailtracking-data-collection" + +#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST \ + "urlclassifier.features.emailtracking.datacollection.blocklistTables" +#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.emailtracking.datacollection.blocklistHosts" +#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST \ + "urlclassifier.features.emailtracking.datacollection.allowlistTables" +#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.emailtracking.datacollection.allowlistHosts" +#define URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_EXCEPTION_URLS \ + "urlclassifier.features.emailtracking.datacollection.skipURLs" +#define TABLE_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_PREF \ + "emailtracking-data-collection-blocklist-pref" +#define TABLE_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_PREF \ + "emailtracking-data-collection-allowlist-pref" + +StaticRefPtr<UrlClassifierFeatureEmailTrackingDataCollection> + gFeatureEmailTrackingDataCollection; +StaticAutoPtr<nsCString> gEmailWebAppDomainsPref; +static constexpr char kEmailWebAppDomainPrefName[] = + "privacy.trackingprotection.emailtracking.webapp.domains"; + +void EmailWebAppDomainPrefChangeCallback(const char* aPrefName, void*) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aPrefName, kEmailWebAppDomainPrefName)); + MOZ_ASSERT(gEmailWebAppDomainsPref); + + Preferences::GetCString(kEmailWebAppDomainPrefName, *gEmailWebAppDomainsPref); +} + +} // namespace + +UrlClassifierFeatureEmailTrackingDataCollection:: + UrlClassifierFeatureEmailTrackingDataCollection() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(EMAILTRACKING_DATACOLLECTION_FEATURE_NAME), + nsLiteralCString( + URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST), + nsLiteralCString( + URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST), + nsLiteralCString( + URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_EMAILTRACKING_DATACOLLECTION_BLOCKLIST_PREF), + nsLiteralCString(TABLE_EMAILTRACKING_DATACOLLECTION_ENTITYLIST_PREF), + nsLiteralCString( + URLCLASSIFIER_EMAILTRACKING_DATACOLLECTION_EXCEPTION_URLS)) {} + +/* static */ +const char* UrlClassifierFeatureEmailTrackingDataCollection::Name() { + return EMAILTRACKING_DATACOLLECTION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureEmailTrackingDataCollection::MaybeInitialize() { + UC_LOG_LEAK( + ("UrlClassifierFeatureEmailTrackingDataCollection::MaybeInitialize")); + + if (!gFeatureEmailTrackingDataCollection) { + gFeatureEmailTrackingDataCollection = + new UrlClassifierFeatureEmailTrackingDataCollection(); + gFeatureEmailTrackingDataCollection->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureEmailTrackingDataCollection::MaybeShutdown() { + UC_LOG_LEAK( + ("UrlClassifierFeatureEmailTrackingDataCollection::MaybeShutdown")); + + if (gFeatureEmailTrackingDataCollection) { + gFeatureEmailTrackingDataCollection->ShutdownPreferences(); + gFeatureEmailTrackingDataCollection = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureEmailTrackingDataCollection> +UrlClassifierFeatureEmailTrackingDataCollection::MaybeCreate( + nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureEmailTrackingDataCollection::MaybeCreate - channel " + "%p", + aChannel)); + + if (!StaticPrefs:: + privacy_trackingprotection_emailtracking_data_collection_enabled()) { + return nullptr; + } + + bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel); + + // We don't need to collect data if the email tracker is loaded in first-party + // context. + if (!isThirdParty) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureEmailTrackingDataCollection); + + RefPtr<UrlClassifierFeatureEmailTrackingDataCollection> self = + gFeatureEmailTrackingDataCollection; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureEmailTrackingDataCollection::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(EMAILTRACKING_DATACOLLECTION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureEmailTrackingDataCollection); + + RefPtr<UrlClassifierFeatureEmailTrackingDataCollection> self = + gFeatureEmailTrackingDataCollection; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureEmailTrackingDataCollection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + // This is not a blocking feature. + *aShouldContinue = true; + + UC_LOG( + ("UrlClassifierFeatureEmailTrackingDataCollection::ProcessChannel - " + "collecting data from channel %p", + aChannel)); + + static std::vector<UrlClassifierCommon::ClassificationData> + sClassificationData = { + {"base-email-track-"_ns, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_EMAILTRACKING}, + {"content-email-track-"_ns, + nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_EMAILTRACKING_CONTENT}, + }; + + // Get if the top window is a email webapp. + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + + RefPtr<dom::BrowsingContext> bc; + loadInfo->GetBrowsingContext(getter_AddRefs(bc)); + if (!bc || bc->IsDiscarded()) { + return NS_OK; + } + + RefPtr<dom::WindowGlobalParent> topWindowParent = + bc->Canonical()->GetTopWindowContext(); + if (!topWindowParent || topWindowParent->IsDiscarded()) { + return NS_OK; + } + + // Cache the email webapp domains pref value and register the callback + // function to update the cached value when the pref changes. + if (!gEmailWebAppDomainsPref) { + gEmailWebAppDomainsPref = new nsCString(); + + Preferences::RegisterCallbackAndCall(EmailWebAppDomainPrefChangeCallback, + kEmailWebAppDomainPrefName); + RunOnShutdown([]() { + Preferences::UnregisterCallback(EmailWebAppDomainPrefChangeCallback, + kEmailWebAppDomainPrefName); + gEmailWebAppDomainsPref = nullptr; + }); + } + + bool isTopEmailWebApp = topWindowParent->DocumentPrincipal()->IsURIInList( + *gEmailWebAppDomainsPref); + + uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags( + aList, sClassificationData, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_EMAILTRACKING); + + if (flags & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_EMAILTRACKING_CONTENT) { + Telemetry::AccumulateCategorical( + isTopEmailWebApp + ? Telemetry::LABELS_EMAIL_TRACKER_COUNT::content_email_webapp + : Telemetry::LABELS_EMAIL_TRACKER_COUNT::content_normal); + + // Notify the load event to record the content blocking log. + // + // Note that we need to change the code here if we decided to block content + // email trackers in the future. + ContentBlockingNotifier::OnEvent( + aChannel, + nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT); + } else { + Telemetry::AccumulateCategorical( + isTopEmailWebApp + ? Telemetry::LABELS_EMAIL_TRACKER_COUNT::base_email_webapp + : Telemetry::LABELS_EMAIL_TRACKER_COUNT::base_normal); + // Notify to record content blocking log. Note that we don't need to notify + // if email tracking is enabled because the email tracking protection + // feature will be responsible for notifying the blocking event. + // + // Note that we need to change the code here if we decided to block content + // email trackers in the future. + if (!StaticPrefs::privacy_trackingprotection_emailtracking_enabled()) { + ContentBlockingNotifier::OnEvent( + aChannel, + nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureEmailTrackingDataCollection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace mozilla::net diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h new file mode 100644 index 0000000000..130d56c980 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureEmailTrackingDataCollection_h +#define mozilla_net_UrlClassifierFeatureEmailTrackingDataCollection_h + +#include "UrlClassifierFeatureBase.h" + +namespace mozilla::net { + +class UrlClassifierFeatureEmailTrackingDataCollection final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureEmailTrackingDataCollection> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureEmailTrackingDataCollection(); + + static void MaybeInitialize(); +}; + +} // namespace mozilla::net + +#endif // mozilla_net_UrlClassifierFeatureEmailTrackingDataCollection_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp new file mode 100644 index 0000000000..01a18b5381 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp @@ -0,0 +1,217 @@ +/* -*- 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 "UrlClassifierFeatureEmailTrackingProtection.h" + +#include "ChannelClassifierService.h" +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPtr.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIHttpChannelInternal.h" +#include "nsIWebProgressListener.h" +#include "nsNetUtil.h" + +namespace mozilla::net { + +namespace { + +#define EMAIL_TRACKING_PROTECTION_FEATURE_NAME "emailtracking-protection" + +#define URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST \ + "urlclassifier.features.emailtracking.blocklistTables" +#define URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.emailtracking.blocklistHosts" +#define URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST \ + "urlclassifier.features.emailtracking.allowlistTables" +#define URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.emailtracking.allowlistHosts" +#define URLCLASSIFIER_EMAIL_TRACKING_PROTECTION_EXCEPTION_URLS \ + "urlclassifier.features.emailtracking.skipURLs" +#define TABLE_EMAIL_TRACKING_BLOCKLIST_PREF "emailtracking-blocklist-pref" +#define TABLE_EMAIL_TRACKING_ENTITYLIST_PREF "emailtracking-allowlist-pref" + +StaticRefPtr<UrlClassifierFeatureEmailTrackingProtection> + gFeatureEmailTrackingProtection; + +} // namespace + +UrlClassifierFeatureEmailTrackingProtection:: + UrlClassifierFeatureEmailTrackingProtection() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(EMAIL_TRACKING_PROTECTION_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST), + nsLiteralCString(URLCLASSIFIER_EMAIL_TRACKING_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_EMAIL_TRACKING_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_EMAIL_TRACKING_BLOCKLIST_PREF), + nsLiteralCString(TABLE_EMAIL_TRACKING_ENTITYLIST_PREF), + nsLiteralCString( + URLCLASSIFIER_EMAIL_TRACKING_PROTECTION_EXCEPTION_URLS)) {} + +/* static */ +const char* UrlClassifierFeatureEmailTrackingProtection::Name() { + return EMAIL_TRACKING_PROTECTION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureEmailTrackingProtection::MaybeInitialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + UC_LOG_LEAK(("UrlClassifierFeatureEmailTrackingProtection::MaybeInitialize")); + + if (!gFeatureEmailTrackingProtection) { + gFeatureEmailTrackingProtection = + new UrlClassifierFeatureEmailTrackingProtection(); + gFeatureEmailTrackingProtection->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureEmailTrackingProtection::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureEmailTrackingProtection::MaybeShutdown")); + + if (gFeatureEmailTrackingProtection) { + gFeatureEmailTrackingProtection->ShutdownPreferences(); + gFeatureEmailTrackingProtection = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureEmailTrackingProtection> +UrlClassifierFeatureEmailTrackingProtection::MaybeCreate(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureEmailTrackingProtection::MaybeCreate - channel %p", + aChannel)); + + // Check if the email tracking protection is enabled. + if (!StaticPrefs::privacy_trackingprotection_emailtracking_enabled() && + !(NS_UsePrivateBrowsing(aChannel) && + StaticPrefs:: + privacy_trackingprotection_emailtracking_pbmode_enabled())) { + return nullptr; + } + + bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel); + if (!isThirdParty) { + UC_LOG( + ("UrlClassifierFeatureEmailTrackingProtection::MaybeCreate - " + "skipping first party or top-level load for channel %p", + aChannel)); + return nullptr; + } + + if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureEmailTrackingProtection); + + RefPtr<UrlClassifierFeatureEmailTrackingProtection> self = + gFeatureEmailTrackingProtection; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureEmailTrackingProtection::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(EMAIL_TRACKING_PROTECTION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureEmailTrackingProtection); + + RefPtr<UrlClassifierFeatureEmailTrackingProtection> self = + gFeatureEmailTrackingProtection; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureEmailTrackingProtection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel); + + // This is a blocking feature. + *aShouldContinue = isAllowListed; + + if (isAllowListed) { + return NS_OK; + } + + nsAutoCString list; + UrlClassifierCommon::TablesToString(aList, list); + + ChannelBlockDecision decision = + ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list); + if (decision != ChannelBlockDecision::Blocked) { + uint32_t event = + decision == ChannelBlockDecision::Replaced + ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT + : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT; + + // Need to set aBlocked to True if we replace the Email Tracker with a shim, + // since the shim is treated as a blocked event + // Note: If we need to account for which kind of tracker was replaced, + // we need to create a new event type in nsIWebProgressListener + if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) { + ContentBlockingNotifier::OnEvent(aChannel, event, true); + } else { + ContentBlockingNotifier::OnEvent(aChannel, event, false); + } + + *aShouldContinue = true; + return NS_OK; + } + + UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_EMAILTRACKING_URI, + list, ""_ns, ""_ns); + + UC_LOG( + ("UrlClassifierFeatureEmailTrackingProtection::ProcessChannel - " + "cancelling channel %p", + aChannel)); + + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + Unused << httpChannel->CancelByURLClassifier(NS_ERROR_EMAILTRACKING_URI); + } else { + Unused << aChannel->Cancel(NS_ERROR_EMAILTRACKING_URI); + } + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureEmailTrackingProtection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace mozilla::net diff --git a/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h new file mode 100644 index 0000000000..812a60f255 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureEmailTrackingProtection_h +#define mozilla_net_UrlClassifierFeatureEmailTrackingProtection_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla::net { + +class UrlClassifierFeatureEmailTrackingProtection final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureEmailTrackingProtection> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureEmailTrackingProtection(); + + static void MaybeInitialize(); +}; + +} // namespace mozilla::net + +#endif // mozilla_net_UrlClassifierFeatureEmailTrackingProtection_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp new file mode 100644 index 0000000000..747e938f06 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp @@ -0,0 +1,384 @@ +/* -*- 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 "mozilla/net/UrlClassifierFeatureFactory.h" + +// List of Features +#include "UrlClassifierFeatureCryptominingAnnotation.h" +#include "UrlClassifierFeatureCryptominingProtection.h" +#include "UrlClassifierFeatureEmailTrackingDataCollection.h" +#include "UrlClassifierFeatureEmailTrackingProtection.h" +#include "UrlClassifierFeatureFingerprintingAnnotation.h" +#include "UrlClassifierFeatureFingerprintingProtection.h" +#include "UrlClassifierFeaturePhishingProtection.h" +#include "UrlClassifierFeatureSocialTrackingAnnotation.h" +#include "UrlClassifierFeatureSocialTrackingProtection.h" +#include "UrlClassifierFeatureTrackingProtection.h" +#include "UrlClassifierFeatureTrackingAnnotation.h" +#include "UrlClassifierFeatureCustomTables.h" + +#include "nsIWebProgressListener.h" +#include "nsAppRunner.h" + +namespace mozilla { +namespace net { + +/* static */ +void UrlClassifierFeatureFactory::Shutdown() { + // We want to expose Features only in the parent process. + if (!XRE_IsParentProcess()) { + return; + } + + UrlClassifierFeatureCryptominingAnnotation::MaybeShutdown(); + UrlClassifierFeatureCryptominingProtection::MaybeShutdown(); + UrlClassifierFeatureEmailTrackingDataCollection::MaybeShutdown(); + UrlClassifierFeatureEmailTrackingProtection::MaybeShutdown(); + UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown(); + UrlClassifierFeatureFingerprintingProtection::MaybeShutdown(); + UrlClassifierFeaturePhishingProtection::MaybeShutdown(); + UrlClassifierFeatureSocialTrackingAnnotation::MaybeShutdown(); + UrlClassifierFeatureSocialTrackingProtection::MaybeShutdown(); + UrlClassifierFeatureTrackingAnnotation::MaybeShutdown(); + UrlClassifierFeatureTrackingProtection::MaybeShutdown(); +} + +/* static */ +void UrlClassifierFeatureFactory::GetFeaturesFromChannel( + nsIChannel* aChannel, + nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsIUrlClassifierFeature> feature; + + // Note that the order of the features is extremely important! When more than + // 1 feature classifies the channel, we call ::ProcessChannel() following this + // feature order, and this could produce different results with a different + // feature ordering. + + // Email Tracking Data Collection + // This needs to be run before other features so that other blocking features + // won't stop us to collect data for email trackers. Note that this feature + // is not a blocking feature. + feature = + UrlClassifierFeatureEmailTrackingDataCollection::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Email Tracking Protection + feature = UrlClassifierFeatureEmailTrackingProtection::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Cryptomining Protection + feature = UrlClassifierFeatureCryptominingProtection::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Fingerprinting Protection + feature = UrlClassifierFeatureFingerprintingProtection::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // SocialTracking Protection + feature = UrlClassifierFeatureSocialTrackingProtection::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Tracking Protection + feature = UrlClassifierFeatureTrackingProtection::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Cryptomining Annotation + feature = UrlClassifierFeatureCryptominingAnnotation::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Fingerprinting Annotation + feature = UrlClassifierFeatureFingerprintingAnnotation::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // SocialTracking Annotation + feature = UrlClassifierFeatureSocialTrackingAnnotation::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } + + // Tracking Annotation + feature = UrlClassifierFeatureTrackingAnnotation::MaybeCreate(aChannel); + if (feature) { + aFeatures.AppendElement(feature); + } +} + +/* static */ +void UrlClassifierFeatureFactory::GetPhishingProtectionFeatures( + nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures) { + UrlClassifierFeaturePhishingProtection::MaybeCreate(aFeatures); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureFactory::GetFeatureByName(const nsACString& aName) { + if (!XRE_IsParentProcess()) { + return nullptr; + } + + nsCOMPtr<nsIUrlClassifierFeature> feature; + + // Cryptomining Annotation + feature = UrlClassifierFeatureCryptominingAnnotation::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Cryptomining Protection + feature = UrlClassifierFeatureCryptominingProtection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Email Tracking Data Collection + feature = + UrlClassifierFeatureEmailTrackingDataCollection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Email Tracking Protection + feature = + UrlClassifierFeatureEmailTrackingProtection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Fingerprinting Annotation + feature = + UrlClassifierFeatureFingerprintingAnnotation::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Fingerprinting Protection + feature = + UrlClassifierFeatureFingerprintingProtection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // SocialTracking Annotation + feature = + UrlClassifierFeatureSocialTrackingAnnotation::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // SocialTracking Protection + feature = + UrlClassifierFeatureSocialTrackingProtection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Tracking Protection + feature = UrlClassifierFeatureTrackingProtection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // Tracking Annotation + feature = UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + // PhishingProtection features + feature = UrlClassifierFeaturePhishingProtection::GetIfNameMatches(aName); + if (feature) { + return feature.forget(); + } + + return nullptr; +} + +/* static */ +void UrlClassifierFeatureFactory::GetFeatureNames(nsTArray<nsCString>& aArray) { + if (!XRE_IsParentProcess()) { + return; + } + + nsAutoCString name; + + // Cryptomining Annotation + name.Assign(UrlClassifierFeatureCryptominingAnnotation::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Cryptomining Protection + name.Assign(UrlClassifierFeatureCryptominingProtection::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Email Tracking Data Collection + name.Assign(UrlClassifierFeatureEmailTrackingDataCollection::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Email Tracking Protection + name.Assign(UrlClassifierFeatureEmailTrackingProtection::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Fingerprinting Annotation + name.Assign(UrlClassifierFeatureFingerprintingAnnotation::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Fingerprinting Protection + name.Assign(UrlClassifierFeatureFingerprintingProtection::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // SocialTracking Annotation + name.Assign(UrlClassifierFeatureSocialTrackingAnnotation::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // SocialTracking Protection + name.Assign(UrlClassifierFeatureSocialTrackingProtection::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Tracking Protection + name.Assign(UrlClassifierFeatureTrackingProtection::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // Tracking Annotation + name.Assign(UrlClassifierFeatureTrackingAnnotation::Name()); + if (!name.IsEmpty()) { + aArray.AppendElement(name); + } + + // PhishingProtection features + { + nsTArray<nsCString> features; + UrlClassifierFeaturePhishingProtection::GetFeatureNames(features); + aArray.AppendElements(features); + } +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureFactory::CreateFeatureWithTables( + const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables, + const nsTArray<nsCString>& aEntitylistTables) { + nsCOMPtr<nsIUrlClassifierFeature> feature = + new UrlClassifierFeatureCustomTables(aName, aBlocklistTables, + aEntitylistTables); + return feature.forget(); +} + +namespace { + +struct BlockingErrorCode { + nsresult mErrorCode; + uint32_t mBlockingEventCode; + const char* mConsoleMessage; + nsCString mConsoleCategory; +}; + +static const BlockingErrorCode sBlockingErrorCodes[] = { + {NS_ERROR_TRACKING_URI, + nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT, + "TrackerUriBlocked", "Tracking Protection"_ns}, + {NS_ERROR_FINGERPRINTING_URI, + nsIWebProgressListener::STATE_BLOCKED_FINGERPRINTING_CONTENT, + "TrackerUriBlocked", "Tracking Protection"_ns}, + {NS_ERROR_CRYPTOMINING_URI, + nsIWebProgressListener::STATE_BLOCKED_CRYPTOMINING_CONTENT, + "TrackerUriBlocked", "Tracking Protection"_ns}, + {NS_ERROR_SOCIALTRACKING_URI, + nsIWebProgressListener::STATE_BLOCKED_SOCIALTRACKING_CONTENT, + "TrackerUriBlocked", "Tracking Protection"_ns}, + {NS_ERROR_EMAILTRACKING_URI, + nsIWebProgressListener::STATE_BLOCKED_EMAILTRACKING_CONTENT, + "TrackerUriBlocked", "Tracking Protection"_ns}, +}; + +} // namespace + +/* static */ +bool UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( + nsresult aError) { + // In theory we can iterate through the features, but at the moment, we can + // just have a simple check here. + for (const auto& blockingErrorCode : sBlockingErrorCodes) { + if (aError == blockingErrorCode.mErrorCode) { + return true; + } + } + + return false; +} + +/* static */ +bool UrlClassifierFeatureFactory::IsClassifierBlockingEventCode( + uint32_t aEventCode) { + for (const auto& blockingErrorCode : sBlockingErrorCodes) { + if (aEventCode == blockingErrorCode.mBlockingEventCode) { + return true; + } + } + return false; +} + +/* static */ +uint32_t UrlClassifierFeatureFactory::GetClassifierBlockingEventCode( + nsresult aErrorCode) { + for (const auto& blockingErrorCode : sBlockingErrorCodes) { + if (aErrorCode == blockingErrorCode.mErrorCode) { + return blockingErrorCode.mBlockingEventCode; + } + } + return 0; +} + +/* static */ const char* +UrlClassifierFeatureFactory::ClassifierBlockingErrorCodeToConsoleMessage( + nsresult aError, nsACString& aCategory) { + for (const auto& blockingErrorCode : sBlockingErrorCodes) { + if (aError == blockingErrorCode.mErrorCode) { + aCategory = blockingErrorCode.mConsoleCategory; + return blockingErrorCode.mConsoleMessage; + } + } + + return nullptr; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureFactory.h b/netwerk/url-classifier/UrlClassifierFeatureFactory.h new file mode 100644 index 0000000000..f8a7537561 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.h @@ -0,0 +1,57 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureFactory_h +#define mozilla_net_UrlClassifierFeatureFactory_h + +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsIChannel; +class nsIUrlClassifierFeature; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureFactory final { + public: + static void Shutdown(); + + static void GetFeaturesFromChannel( + nsIChannel* aChannel, + nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures); + + static void GetPhishingProtectionFeatures( + nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures); + + static already_AddRefed<nsIUrlClassifierFeature> GetFeatureByName( + const nsACString& aName); + + static void GetFeatureNames(nsTArray<nsCString>& aArray); + + static already_AddRefed<nsIUrlClassifierFeature> CreateFeatureWithTables( + const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables, + const nsTArray<nsCString>& aEntitylistTables); + + // Returns true if this error is known as one of the blocking error codes. + static bool IsClassifierBlockingErrorCode(nsresult aError); + + // Returns true if this event is a known blocking state from + // nsIWebProgressListener. + static bool IsClassifierBlockingEventCode(uint32_t aEventCode); + + static uint32_t GetClassifierBlockingEventCode(nsresult aErrorCode); + + // This can be called only if IsClassifierBlockingErrorCode(aError) returns + // true. + static const char* ClassifierBlockingErrorCodeToConsoleMessage( + nsresult aError, nsACString& aCategory); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureFactory_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp new file mode 100644 index 0000000000..e5cd9a65f7 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp @@ -0,0 +1,178 @@ +/* -*- 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 "UrlClassifierFeatureFingerprintingAnnotation.h" + +#include "mozilla/net/UrlClassifierCommon.h" +#include "nsIClassifiedChannel.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsIWebProgressListener.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +namespace { + +#define FINGERPRINTING_ANNOTATION_FEATURE_NAME "fingerprinting-annotation" + +#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST \ + "urlclassifier.features.fingerprinting.annotate.blacklistTables" +#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.fingerprinting.annotate.blacklistHosts" +#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST \ + "urlclassifier.features.fingerprinting.annotate.whitelistTables" +#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.fingerprinting.annotate.whitelistHosts" +#define URLCLASSIFIER_FINGERPRINTING_ANNOTATION_EXCEPTION_URLS \ + "urlclassifier.features.fingerprinting.annotate.skipURLs" +#define TABLE_FINGERPRINTING_ANNOTATION_BLOCKLIST_PREF \ + "fingerprinting-annotate-blacklist-pref" +#define TABLE_FINGERPRINTING_ANNOTATION_ENTITYLIST_PREF \ + "fingerprinting-annotate-whitelist-pref" + +StaticRefPtr<UrlClassifierFeatureFingerprintingAnnotation> + gFeatureFingerprintingAnnotation; + +} // namespace + +UrlClassifierFeatureFingerprintingAnnotation:: + UrlClassifierFeatureFingerprintingAnnotation() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(FINGERPRINTING_ANNOTATION_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST), + nsLiteralCString( + URLCLASSIFIER_FINGERPRINTING_ANNOTATION_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_FINGERPRINTING_ANNOTATION_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_FINGERPRINTING_ANNOTATION_BLOCKLIST_PREF), + nsLiteralCString(TABLE_FINGERPRINTING_ANNOTATION_ENTITYLIST_PREF), + nsLiteralCString( + URLCLASSIFIER_FINGERPRINTING_ANNOTATION_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureFingerprintingAnnotation::Name() { + return FINGERPRINTING_ANNOTATION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureFingerprintingAnnotation::MaybeInitialize() { + UC_LOG_LEAK( + ("UrlClassifierFeatureFingerprintingAnnotation::MaybeInitialize")); + + if (!gFeatureFingerprintingAnnotation) { + gFeatureFingerprintingAnnotation = + new UrlClassifierFeatureFingerprintingAnnotation(); + gFeatureFingerprintingAnnotation->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown")); + + if (gFeatureFingerprintingAnnotation) { + gFeatureFingerprintingAnnotation->ShutdownPreferences(); + gFeatureFingerprintingAnnotation = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureFingerprintingAnnotation> +UrlClassifierFeatureFingerprintingAnnotation::MaybeCreate( + nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureFingerprintingAnnotation::MaybeCreate - channel %p", + aChannel)); + + if (UrlClassifierCommon::IsPassiveContent(aChannel)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureFingerprintingAnnotation); + + RefPtr<UrlClassifierFeatureFingerprintingAnnotation> self = + gFeatureFingerprintingAnnotation; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureFingerprintingAnnotation::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(FINGERPRINTING_ANNOTATION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureFingerprintingAnnotation); + + RefPtr<UrlClassifierFeatureFingerprintingAnnotation> self = + gFeatureFingerprintingAnnotation; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureFingerprintingAnnotation::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + // This is not a blocking feature. + *aShouldContinue = true; + + UC_LOG( + ("UrlClassifierFeatureFingerprintingAnnotation::ProcessChannel - " + "annotating channel %p", + aChannel)); + + static std::vector<UrlClassifierCommon::ClassificationData> + sClassificationData = { + {"content-fingerprinting-track-"_ns, + nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_FINGERPRINTING_CONTENT}, + }; + + uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags( + aList, sClassificationData, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_FINGERPRINTING); + + UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes); + + UrlClassifierCommon::AnnotateChannel( + aChannel, flags, + nsIWebProgressListener::STATE_LOADED_FINGERPRINTING_CONTENT); + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureFingerprintingAnnotation::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h new file mode 100644 index 0000000000..ab9dff2f66 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureFingerprintingAnnotation_h +#define mozilla_net_UrlClassifierFeatureFingerprintingAnnotation_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureFingerprintingAnnotation final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureFingerprintingAnnotation> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureFingerprintingAnnotation(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureFingerprintingAnnotation_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp new file mode 100644 index 0000000000..16a352e484 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp @@ -0,0 +1,216 @@ +/* -*- 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 "UrlClassifierFeatureFingerprintingProtection.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "ChannelClassifierService.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsIWebProgressListener.h" +#include "nsIHttpChannelInternal.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +namespace { + +#define FINGERPRINTING_FEATURE_NAME "fingerprinting-protection" + +#define URLCLASSIFIER_FINGERPRINTING_BLOCKLIST \ + "urlclassifier.features.fingerprinting.blacklistTables" +#define URLCLASSIFIER_FINGERPRINTING_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.fingerprinting.blacklistHosts" +#define URLCLASSIFIER_FINGERPRINTING_ENTITYLIST \ + "urlclassifier.features.fingerprinting.whitelistTables" +#define URLCLASSIFIER_FINGERPRINTING_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.fingerprinting.whitelistHosts" +#define URLCLASSIFIER_FINGERPRINTING_EXCEPTION_URLS \ + "urlclassifier.features.fingerprinting.skipURLs" +#define TABLE_FINGERPRINTING_BLOCKLIST_PREF "fingerprinting-blacklist-pref" +#define TABLE_FINGERPRINTING_ENTITYLIST_PREF "fingerprinting-whitelist-pref" + +StaticRefPtr<UrlClassifierFeatureFingerprintingProtection> + gFeatureFingerprintingProtection; + +} // namespace + +UrlClassifierFeatureFingerprintingProtection:: + UrlClassifierFeatureFingerprintingProtection() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(FINGERPRINTING_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_ENTITYLIST), + nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_FINGERPRINTING_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_FINGERPRINTING_BLOCKLIST_PREF), + nsLiteralCString(TABLE_FINGERPRINTING_ENTITYLIST_PREF), + nsLiteralCString(URLCLASSIFIER_FINGERPRINTING_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureFingerprintingProtection::Name() { + return FINGERPRINTING_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureFingerprintingProtection::MaybeInitialize() { + UC_LOG_LEAK( + ("UrlClassifierFeatureFingerprintingProtection::MaybeInitialize")); + + if (!gFeatureFingerprintingProtection) { + gFeatureFingerprintingProtection = + new UrlClassifierFeatureFingerprintingProtection(); + gFeatureFingerprintingProtection->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureFingerprintingProtection::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureFingerprintingProtection::MaybeShutdown")); + + if (gFeatureFingerprintingProtection) { + gFeatureFingerprintingProtection->ShutdownPreferences(); + gFeatureFingerprintingProtection = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureFingerprintingProtection> +UrlClassifierFeatureFingerprintingProtection::MaybeCreate( + nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureFingerprintingProtection::MaybeCreate - channel %p", + aChannel)); + + if (!StaticPrefs::privacy_trackingprotection_fingerprinting_enabled()) { + return nullptr; + } + + bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel); + if (!isThirdParty) { + UC_LOG( + ("UrlClassifierFeatureFingerprintingProtection::MaybeCreate - " + "skipping first party or top-level load for channel %p", + aChannel)); + return nullptr; + } + + if (UrlClassifierCommon::IsPassiveContent(aChannel)) { + return nullptr; + } + + if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureFingerprintingProtection); + + RefPtr<UrlClassifierFeatureFingerprintingProtection> self = + gFeatureFingerprintingProtection; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureFingerprintingProtection::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(FINGERPRINTING_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureFingerprintingProtection); + + RefPtr<UrlClassifierFeatureFingerprintingProtection> self = + gFeatureFingerprintingProtection; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureFingerprintingProtection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel); + + // This is a blocking feature. + *aShouldContinue = isAllowListed; + + if (isAllowListed) { + return NS_OK; + } + + nsAutoCString list; + UrlClassifierCommon::TablesToString(aList, list); + + ChannelBlockDecision decision = + ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list); + if (decision != ChannelBlockDecision::Blocked) { + uint32_t event = + decision == ChannelBlockDecision::Replaced + ? nsIWebProgressListener::STATE_REPLACED_FINGERPRINTING_CONTENT + : nsIWebProgressListener::STATE_ALLOWED_FINGERPRINTING_CONTENT; + + // Need to set aBlocked to True if we replace the Fingerprinter with a shim, + // since the shim is treated as a blocked event + if (event == + nsIWebProgressListener::STATE_REPLACED_FINGERPRINTING_CONTENT) { + ContentBlockingNotifier::OnEvent(aChannel, event, true); + } else { + ContentBlockingNotifier::OnEvent(aChannel, event, false); + } + + *aShouldContinue = true; + return NS_OK; + } + + UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_FINGERPRINTING_URI, + list, ""_ns, ""_ns); + + UC_LOG( + ("UrlClassifierFeatureFingerprintingProtection::ProcessChannel - " + "cancelling channel %p", + aChannel)); + + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + Unused << httpChannel->CancelByURLClassifier(NS_ERROR_FINGERPRINTING_URI); + } else { + Unused << aChannel->Cancel(NS_ERROR_FINGERPRINTING_URI); + } + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureFingerprintingProtection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h new file mode 100644 index 0000000000..2ef0a4fea7 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureFingerprintingProtection_h +#define mozilla_net_UrlClassifierFeatureFingerprintingProtection_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureFingerprintingProtection final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureFingerprintingProtection> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureFingerprintingProtection(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureFingerprintingProtection_h diff --git a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp new file mode 100644 index 0000000000..d03f9c790b --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp @@ -0,0 +1,128 @@ +/* -*- 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 "UrlClassifierFeaturePhishingProtection.h" +#include "mozilla/StaticPrefs_browser.h" +#include "nsCOMPtr.h" + +namespace mozilla { +namespace net { + +struct UrlClassifierFeaturePhishingProtection::PhishingProtectionFeature { + const char* mName; + const char* mBlocklistPrefTables; + bool (*mPref)(); + + RefPtr<UrlClassifierFeaturePhishingProtection> mFeature; +}; + +namespace { + +struct UrlClassifierFeaturePhishingProtection::PhishingProtectionFeature + sPhishingProtectionFeaturesMap[] = { + {"malware", "urlclassifier.malwareTable", + StaticPrefs::browser_safebrowsing_malware_enabled}, + {"phishing", "urlclassifier.phishTable", + StaticPrefs::browser_safebrowsing_phishing_enabled}, + {"blockedURIs", "urlclassifier.blockedTable", + StaticPrefs::browser_safebrowsing_blockedURIs_enabled}, +}; + +} // namespace + +UrlClassifierFeaturePhishingProtection::UrlClassifierFeaturePhishingProtection( + const UrlClassifierFeaturePhishingProtection::PhishingProtectionFeature& + aFeature) + : UrlClassifierFeatureBase( + nsDependentCString(aFeature.mName), + nsDependentCString(aFeature.mBlocklistPrefTables), + ""_ns, // aPrefEntitylistPrefTbles, + ""_ns, // aPrefBlocklistHosts + ""_ns, // aPrefEntitylistHosts + ""_ns, // aPrefBlocklistTableName + ""_ns, // aPrefEntitylistTableName + ""_ns) { // aPrefExceptionHosts +} + +/* static */ +void UrlClassifierFeaturePhishingProtection::GetFeatureNames( + nsTArray<nsCString>& aArray) { + for (const PhishingProtectionFeature& feature : + sPhishingProtectionFeaturesMap) { + if (feature.mPref()) { + aArray.AppendElement(nsDependentCString(feature.mName)); + } + } +} + +/* static */ +void UrlClassifierFeaturePhishingProtection::MaybeInitialize() { + for (PhishingProtectionFeature& feature : sPhishingProtectionFeaturesMap) { + if (!feature.mFeature && feature.mPref()) { + feature.mFeature = new UrlClassifierFeaturePhishingProtection(feature); + feature.mFeature->InitializePreferences(); + } + } +} + +/* static */ +void UrlClassifierFeaturePhishingProtection::MaybeShutdown() { + for (PhishingProtectionFeature& feature : sPhishingProtectionFeaturesMap) { + if (feature.mFeature) { + feature.mFeature->ShutdownPreferences(); + feature.mFeature = nullptr; + } + } +} + +/* static */ +void UrlClassifierFeaturePhishingProtection::MaybeCreate( + nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures) { + MaybeInitialize(); + + for (const PhishingProtectionFeature& feature : + sPhishingProtectionFeaturesMap) { + if (feature.mPref()) { + MOZ_ASSERT(feature.mFeature); + aFeatures.AppendElement(feature.mFeature); + } + } +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeaturePhishingProtection::GetIfNameMatches( + const nsACString& aName) { + MaybeInitialize(); + + for (const PhishingProtectionFeature& feature : + sPhishingProtectionFeaturesMap) { + if (feature.mPref() && aName.Equals(feature.mName)) { + MOZ_ASSERT(feature.mFeature); + nsCOMPtr<nsIUrlClassifierFeature> self = feature.mFeature.get(); + return self.forget(); + } + } + + return nullptr; +} + +NS_IMETHODIMP +UrlClassifierFeaturePhishingProtection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +UrlClassifierFeaturePhishingProtection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h new file mode 100644 index 0000000000..d5498cbb72 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_UrlClassifierFeaturePhishingProtection_h +#define mozilla_UrlClassifierFeaturePhishingProtection_h + +#include "UrlClassifierFeatureBase.h" + +namespace mozilla { +namespace net { + +class UrlClassifierFeaturePhishingProtection final + : public UrlClassifierFeatureBase { + public: + struct PhishingProtectionFeature; + + static void GetFeatureNames(nsTArray<nsCString>& aArray); + + static void MaybeShutdown(); + + static void MaybeCreate(nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD + ProcessChannel(nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + explicit UrlClassifierFeaturePhishingProtection( + const PhishingProtectionFeature& aFeature); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_UrlClassifierFeaturePhishingProtection_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureResult.cpp b/netwerk/url-classifier/UrlClassifierFeatureResult.cpp new file mode 100644 index 0000000000..464c6cefe0 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureResult.cpp @@ -0,0 +1,49 @@ +/* -*- 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 "mozilla/net/UrlClassifierFeatureResult.h" + +namespace mozilla { +namespace net { + +UrlClassifierFeatureResult::UrlClassifierFeatureResult( + nsIURI* aURI, nsIUrlClassifierFeature* aFeature, const nsACString& aList) + : mURI(aURI), mFeature(aFeature), mList(aList) {} + +UrlClassifierFeatureResult::~UrlClassifierFeatureResult() = default; + +NS_IMETHODIMP +UrlClassifierFeatureResult::GetUri(nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aURI); + nsCOMPtr<nsIURI> uri = mURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureResult::GetFeature(nsIUrlClassifierFeature** aFeature) { + NS_ENSURE_ARG_POINTER(aFeature); + nsCOMPtr<nsIUrlClassifierFeature> feature = mFeature; + feature.forget(aFeature); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureResult::GetList(nsACString& aList) { + aList = mList; + return NS_OK; +} + +NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureResult) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeatureResult) + NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeatureResult) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(UrlClassifierFeatureResult) +NS_IMPL_RELEASE(UrlClassifierFeatureResult) + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureResult.h b/netwerk/url-classifier/UrlClassifierFeatureResult.h new file mode 100644 index 0000000000..fba6c95c9b --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureResult.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureResult_h +#define mozilla_net_UrlClassifierFeatureResult_h + +#include "nsIUrlClassifierFeature.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureResult final : public nsIUrlClassifierFeatureResult { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERFEATURERESULT + + UrlClassifierFeatureResult(nsIURI* aURI, nsIUrlClassifierFeature* aFeature, + const nsACString& aList); + + nsIURI* URI() const { return mURI; } + + nsIUrlClassifierFeature* Feature() const { return mFeature; } + + // Comma separated list of tables. + const nsCString& List() const { return mList; } + + protected: + ~UrlClassifierFeatureResult(); + + private: + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIUrlClassifierFeature> mFeature; + const nsCString mList; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureResult_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp new file mode 100644 index 0000000000..e464c0c815 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp @@ -0,0 +1,178 @@ +/* -*- 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 "UrlClassifierFeatureSocialTrackingAnnotation.h" + +#include "mozilla/net/UrlClassifierCommon.h" +#include "nsIClassifiedChannel.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsIWebProgressListener.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +namespace { + +#define SOCIALTRACKING_ANNOTATION_FEATURE_NAME "socialtracking-annotation" + +#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST \ + "urlclassifier.features.socialtracking.annotate.blacklistTables" +#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.socialtracking.annotate.blacklistHosts" +#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST \ + "urlclassifier.features.socialtracking.annotate.whitelistTables" +#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.socialtracking.annotate.whitelistHosts" +#define URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_EXCEPTION_URLS \ + "urlclassifier.features.socialtracking.annotate.skipURLs" +#define TABLE_SOCIALTRACKING_ANNOTATION_BLOCKLIST_PREF \ + "socialtracking-annotate-blacklist-pref" +#define TABLE_SOCIALTRACKING_ANNOTATION_ENTITYLIST_PREF \ + "socialtracking-annotate-whitelist-pref" + +StaticRefPtr<UrlClassifierFeatureSocialTrackingAnnotation> + gFeatureSocialTrackingAnnotation; + +} // namespace + +UrlClassifierFeatureSocialTrackingAnnotation:: + UrlClassifierFeatureSocialTrackingAnnotation() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(SOCIALTRACKING_ANNOTATION_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST), + nsLiteralCString( + URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_SOCIALTRACKING_ANNOTATION_BLOCKLIST_PREF), + nsLiteralCString(TABLE_SOCIALTRACKING_ANNOTATION_ENTITYLIST_PREF), + nsLiteralCString( + URLCLASSIFIER_SOCIALTRACKING_ANNOTATION_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureSocialTrackingAnnotation::Name() { + return SOCIALTRACKING_ANNOTATION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureSocialTrackingAnnotation::MaybeInitialize() { + UC_LOG_LEAK( + ("UrlClassifierFeatureSocialTrackingAnnotation::MaybeInitialize")); + + if (!gFeatureSocialTrackingAnnotation) { + gFeatureSocialTrackingAnnotation = + new UrlClassifierFeatureSocialTrackingAnnotation(); + gFeatureSocialTrackingAnnotation->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureSocialTrackingAnnotation::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureSocialTrackingAnnotation::MaybeShutdown")); + + if (gFeatureSocialTrackingAnnotation) { + gFeatureSocialTrackingAnnotation->ShutdownPreferences(); + gFeatureSocialTrackingAnnotation = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureSocialTrackingAnnotation> +UrlClassifierFeatureSocialTrackingAnnotation::MaybeCreate( + nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureSocialTrackingAnnotation::MaybeCreate - channel %p", + aChannel)); + + MaybeInitialize(); + MOZ_ASSERT(gFeatureSocialTrackingAnnotation); + + RefPtr<UrlClassifierFeatureSocialTrackingAnnotation> self = + gFeatureSocialTrackingAnnotation; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureSocialTrackingAnnotation::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(SOCIALTRACKING_ANNOTATION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureSocialTrackingAnnotation); + + RefPtr<UrlClassifierFeatureSocialTrackingAnnotation> self = + gFeatureSocialTrackingAnnotation; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureSocialTrackingAnnotation::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + // This is not a blocking feature. + *aShouldContinue = true; + + UC_LOG( + ("UrlClassifierFeatureSocialTrackingAnnotation::ProcessChannel" + "annotating channel %p", + aChannel)); + + static std::vector<UrlClassifierCommon::ClassificationData> + sClassificationData = { + {"social-tracking-protection-facebook-"_ns, + nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_SOCIALTRACKING_FACEBOOK}, + {"social-tracking-protection-linkedin-"_ns, + nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_SOCIALTRACKING_LINKEDIN}, + {"social-tracking-protection-twitter-"_ns, + nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_SOCIALTRACKING_TWITTER}, + }; + + uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags( + aList, sClassificationData, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING); + + UrlClassifierCommon::AnnotateChannel( + aChannel, flags, + nsIWebProgressListener::STATE_LOADED_SOCIALTRACKING_CONTENT); + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureSocialTrackingAnnotation::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h new file mode 100644 index 0000000000..1178bb8b29 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureSocialTrackingAnnotation_h +#define mozilla_net_UrlClassifierFeatureSocialTrackingAnnotation_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureSocialTrackingAnnotation final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureSocialTrackingAnnotation> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureSocialTrackingAnnotation(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureSocialTrackingAnnotation_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp new file mode 100644 index 0000000000..a2ca7459db --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp @@ -0,0 +1,213 @@ +/* -*- 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 "UrlClassifierFeatureSocialTrackingProtection.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "ChannelClassifierService.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsIWebProgressListener.h" +#include "nsIHttpChannelInternal.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +namespace { + +#define SOCIALTRACKING_FEATURE_NAME "socialtracking-protection" + +#define URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST \ + "urlclassifier.features.socialtracking.blacklistTables" +#define URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.features.socialtracking.blacklistHosts" +#define URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST \ + "urlclassifier.features.socialtracking.whitelistTables" +#define URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.features.socialtracking.whitelistHosts" +#define URLCLASSIFIER_SOCIALTRACKING_EXCEPTION_URLS \ + "urlclassifier.features.socialtracking.skipURLs" +#define TABLE_SOCIALTRACKING_BLOCKLIST_PREF "socialtracking-blocklist-pref" +#define TABLE_SOCIALTRACKING_ENTITYLIST_PREF "socialtracking-entitylist-pref" + +StaticRefPtr<UrlClassifierFeatureSocialTrackingProtection> + gFeatureSocialTrackingProtection; + +} // namespace + +UrlClassifierFeatureSocialTrackingProtection:: + UrlClassifierFeatureSocialTrackingProtection() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(SOCIALTRACKING_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST), + nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString( + URLCLASSIFIER_SOCIALTRACKING_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_SOCIALTRACKING_BLOCKLIST_PREF), + nsLiteralCString(TABLE_SOCIALTRACKING_ENTITYLIST_PREF), + nsLiteralCString(URLCLASSIFIER_SOCIALTRACKING_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureSocialTrackingProtection::Name() { + return SOCIALTRACKING_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureSocialTrackingProtection::MaybeInitialize() { + UC_LOG_LEAK( + ("UrlClassifierFeatureSocialTrackingProtection::MaybeInitialize")); + + if (!gFeatureSocialTrackingProtection) { + gFeatureSocialTrackingProtection = + new UrlClassifierFeatureSocialTrackingProtection(); + gFeatureSocialTrackingProtection->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureSocialTrackingProtection::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureSocialTrackingProtection::MaybeShutdown")); + + if (gFeatureSocialTrackingProtection) { + gFeatureSocialTrackingProtection->ShutdownPreferences(); + gFeatureSocialTrackingProtection = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureSocialTrackingProtection> +UrlClassifierFeatureSocialTrackingProtection::MaybeCreate( + nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureSocialTrackingProtection::MaybeCreate - channel %p", + aChannel)); + + if (!StaticPrefs::privacy_trackingprotection_socialtracking_enabled()) { + return nullptr; + } + + bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel); + if (!isThirdParty) { + UC_LOG( + ("UrlClassifierFeatureSocialTrackingProtection::MaybeCreate - " + "skipping first party or top-level load for channel %p", + aChannel)); + return nullptr; + } + + if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureSocialTrackingProtection); + + RefPtr<UrlClassifierFeatureSocialTrackingProtection> self = + gFeatureSocialTrackingProtection; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureSocialTrackingProtection::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(SOCIALTRACKING_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureSocialTrackingProtection); + + RefPtr<UrlClassifierFeatureSocialTrackingProtection> self = + gFeatureSocialTrackingProtection; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureSocialTrackingProtection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel); + + // This is a blocking feature. + *aShouldContinue = isAllowListed; + + if (isAllowListed) { + return NS_OK; + } + + nsAutoCString list; + UrlClassifierCommon::TablesToString(aList, list); + + ChannelBlockDecision decision = + ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list); + if (decision != ChannelBlockDecision::Blocked) { + uint32_t event = + decision == ChannelBlockDecision::Replaced + ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT + : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT; + + // Need to set aBlocked to True if we replace the Social Tracker + // with a shim, since the shim is treated as a blocked event + // Note: If we need to account for which kind of tracker was replaced, + // we need to create a new event type in nsIWebProgressListener + if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) { + ContentBlockingNotifier::OnEvent(aChannel, event, true); + } else { + ContentBlockingNotifier::OnEvent(aChannel, event, false); + } + + *aShouldContinue = true; + return NS_OK; + } + + UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_SOCIALTRACKING_URI, + list, ""_ns, ""_ns); + + UC_LOG( + ("UrlClassifierFeatureSocialTrackingProtection::ProcessChannel - " + "cancelling channel %p", + aChannel)); + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel); + + if (httpChannel) { + Unused << httpChannel->CancelByURLClassifier(NS_ERROR_SOCIALTRACKING_URI); + } else { + Unused << aChannel->Cancel(NS_ERROR_SOCIALTRACKING_URI); + } + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureSocialTrackingProtection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h new file mode 100644 index 0000000000..50ec2d0dfe --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureSocialTrackingProtection_h +#define mozilla_net_UrlClassifierFeatureSocialTrackingProtection_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureSocialTrackingProtection final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureSocialTrackingProtection> + MaybeCreate(nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureSocialTrackingProtection(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureSocialTrackingProtection_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp new file mode 100644 index 0000000000..518bde65ef --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp @@ -0,0 +1,180 @@ +/* -*- 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 "UrlClassifierFeatureTrackingAnnotation.h" + +#include "Classifier.h" +#include "mozilla/Logging.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "nsIChannel.h" +#include "nsIClassifiedChannel.h" +#include "nsIWebProgressListener.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace net { + +namespace { + +#define TRACKING_ANNOTATION_FEATURE_NAME "tracking-annotation" + +#define URLCLASSIFIER_ANNOTATION_BLOCKLIST \ + "urlclassifier.trackingAnnotationTable" +#define URLCLASSIFIER_ANNOTATION_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.trackingAnnotationTable.testEntries" +#define URLCLASSIFIER_ANNOTATION_ENTITYLIST \ + "urlclassifier.trackingAnnotationWhitelistTable" +#define URLCLASSIFIER_ANNOTATION_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.trackingAnnotationWhitelistTable.testEntries" +#define URLCLASSIFIER_TRACKING_ANNOTATION_EXCEPTION_URLS \ + "urlclassifier.trackingAnnotationSkipURLs" +#define TABLE_ANNOTATION_BLOCKLIST_PREF "annotation-blacklist-pref" +#define TABLE_ANNOTATION_ENTITYLIST_PREF "annotation-whitelist-pref" + +StaticRefPtr<UrlClassifierFeatureTrackingAnnotation> gFeatureTrackingAnnotation; + +} // namespace + +UrlClassifierFeatureTrackingAnnotation::UrlClassifierFeatureTrackingAnnotation() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(TRACKING_ANNOTATION_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_ANNOTATION_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_ANNOTATION_ENTITYLIST), + nsLiteralCString(URLCLASSIFIER_ANNOTATION_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString(URLCLASSIFIER_ANNOTATION_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_ANNOTATION_BLOCKLIST_PREF), + nsLiteralCString(TABLE_ANNOTATION_ENTITYLIST_PREF), + nsLiteralCString(URLCLASSIFIER_TRACKING_ANNOTATION_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureTrackingAnnotation::Name() { + return TRACKING_ANNOTATION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureTrackingAnnotation::MaybeInitialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + UC_LOG_LEAK(("UrlClassifierFeatureTrackingAnnotation::MaybeInitialize")); + + if (!gFeatureTrackingAnnotation) { + gFeatureTrackingAnnotation = new UrlClassifierFeatureTrackingAnnotation(); + gFeatureTrackingAnnotation->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureTrackingAnnotation::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureTrackingAnnotation::MaybeShutdown")); + + if (gFeatureTrackingAnnotation) { + gFeatureTrackingAnnotation->ShutdownPreferences(); + gFeatureTrackingAnnotation = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureTrackingAnnotation> +UrlClassifierFeatureTrackingAnnotation::MaybeCreate(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureTrackingAnnotation::MaybeCreate - channel %p", + aChannel)); + + if (!StaticPrefs::privacy_trackingprotection_annotate_channels()) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureTrackingAnnotation); + + RefPtr<UrlClassifierFeatureTrackingAnnotation> self = + gFeatureTrackingAnnotation; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(TRACKING_ANNOTATION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureTrackingAnnotation); + + RefPtr<UrlClassifierFeatureTrackingAnnotation> self = + gFeatureTrackingAnnotation; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureTrackingAnnotation::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + // This is not a blocking feature. + *aShouldContinue = true; + + UC_LOG( + ("UrlClassifierFeatureTrackingAnnotation::ProcessChannel - " + "annotating channel %p", + aChannel)); + + static std::vector<UrlClassifierCommon::ClassificationData> + sClassificationData = { + {"ads-track-"_ns, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING_AD}, + {"analytics-track-"_ns, nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_TRACKING_ANALYTICS}, + {"social-track-"_ns, nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_TRACKING_SOCIAL}, + {"content-track-"_ns, nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_TRACKING_CONTENT}, + }; + + uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags( + aList, sClassificationData, + nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING); + + UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes); + + uint32_t notification = + ((flags & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_TRACKING_CONTENT) != 0) + ? nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT + : nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT; + + UrlClassifierCommon::AnnotateChannel(aChannel, flags, notification); + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureTrackingAnnotation::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h new file mode 100644 index 0000000000..5be6cf0765 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureTrackingAnnotation_h +#define mozilla_net_UrlClassifierFeatureTrackingAnnotation_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureTrackingAnnotation final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureTrackingAnnotation> MaybeCreate( + nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureTrackingAnnotation(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureTrackingAnnotation_h diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp new file mode 100644 index 0000000000..8c50b33d4d --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp @@ -0,0 +1,217 @@ +/* -*- 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 "UrlClassifierFeatureTrackingProtection.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "ChannelClassifierService.h" +#include "nsIChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsILoadContext.h" +#include "nsNetUtil.h" +#include "mozilla/StaticPtr.h" +#include "nsXULAppAPI.h" +#include "nsIWebProgressListener.h" + +namespace mozilla { +namespace net { + +namespace { + +#define TRACKING_PROTECTION_FEATURE_NAME "tracking-protection" + +#define URLCLASSIFIER_TRACKING_BLOCKLIST "urlclassifier.trackingTable" +#define URLCLASSIFIER_TRACKING_BLOCKLIST_TEST_ENTRIES \ + "urlclassifier.trackingTable.testEntries" +#define URLCLASSIFIER_TRACKING_ENTITYLIST "urlclassifier.trackingWhitelistTable" +#define URLCLASSIFIER_TRACKING_ENTITYLIST_TEST_ENTRIES \ + "urlclassifier.trackingWhitelistTable.testEntries" +#define URLCLASSIFIER_TRACKING_PROTECTION_EXCEPTION_URLS \ + "urlclassifier.trackingSkipURLs" +#define TABLE_TRACKING_BLOCKLIST_PREF "tracking-blocklist-pref" +#define TABLE_TRACKING_ENTITYLIST_PREF "tracking-entitylist-pref" + +StaticRefPtr<UrlClassifierFeatureTrackingProtection> gFeatureTrackingProtection; + +} // namespace + +UrlClassifierFeatureTrackingProtection::UrlClassifierFeatureTrackingProtection() + : UrlClassifierFeatureAntiTrackingBase( + nsLiteralCString(TRACKING_PROTECTION_FEATURE_NAME), + nsLiteralCString(URLCLASSIFIER_TRACKING_BLOCKLIST), + nsLiteralCString(URLCLASSIFIER_TRACKING_ENTITYLIST), + nsLiteralCString(URLCLASSIFIER_TRACKING_BLOCKLIST_TEST_ENTRIES), + nsLiteralCString(URLCLASSIFIER_TRACKING_ENTITYLIST_TEST_ENTRIES), + nsLiteralCString(TABLE_TRACKING_BLOCKLIST_PREF), + nsLiteralCString(TABLE_TRACKING_ENTITYLIST_PREF), + nsLiteralCString(URLCLASSIFIER_TRACKING_PROTECTION_EXCEPTION_URLS)) {} + +/* static */ const char* UrlClassifierFeatureTrackingProtection::Name() { + return TRACKING_PROTECTION_FEATURE_NAME; +} + +/* static */ +void UrlClassifierFeatureTrackingProtection::MaybeInitialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + UC_LOG_LEAK(("UrlClassifierFeatureTrackingProtection::MaybeInitialize")); + + if (!gFeatureTrackingProtection) { + gFeatureTrackingProtection = new UrlClassifierFeatureTrackingProtection(); + gFeatureTrackingProtection->InitializePreferences(); + } +} + +/* static */ +void UrlClassifierFeatureTrackingProtection::MaybeShutdown() { + UC_LOG_LEAK(("UrlClassifierFeatureTrackingProtection::MaybeShutdown")); + + if (gFeatureTrackingProtection) { + gFeatureTrackingProtection->ShutdownPreferences(); + gFeatureTrackingProtection = nullptr; + } +} + +/* static */ +already_AddRefed<UrlClassifierFeatureTrackingProtection> +UrlClassifierFeatureTrackingProtection::MaybeCreate(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + UC_LOG_LEAK( + ("UrlClassifierFeatureTrackingProtection::MaybeCreate - channel %p", + aChannel)); + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + if (!loadContext) { + // Some channels don't have a loadcontext, check the global tracking + // protection preference. + if (!StaticPrefs::privacy_trackingprotection_enabled() && + !(NS_UsePrivateBrowsing(aChannel) && + StaticPrefs::privacy_trackingprotection_pbmode_enabled())) { + return nullptr; + } + } else if (!loadContext->UseTrackingProtection()) { + return nullptr; + } + + bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(aChannel); + if (!isThirdParty) { + UC_LOG( + ("UrlClassifierFeatureTrackingProtection::MaybeCreate - " + "skipping first party or top-level load for channel %p", + aChannel)); + return nullptr; + } + + if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureTrackingProtection); + + RefPtr<UrlClassifierFeatureTrackingProtection> self = + gFeatureTrackingProtection; + return self.forget(); +} + +/* static */ +already_AddRefed<nsIUrlClassifierFeature> +UrlClassifierFeatureTrackingProtection::GetIfNameMatches( + const nsACString& aName) { + if (!aName.EqualsLiteral(TRACKING_PROTECTION_FEATURE_NAME)) { + return nullptr; + } + + MaybeInitialize(); + MOZ_ASSERT(gFeatureTrackingProtection); + + RefPtr<UrlClassifierFeatureTrackingProtection> self = + gFeatureTrackingProtection; + return self.forget(); +} + +NS_IMETHODIMP +UrlClassifierFeatureTrackingProtection::ProcessChannel( + nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aShouldContinue); + + bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel); + + // This is a blocking feature. + *aShouldContinue = isAllowListed; + + if (isAllowListed) { + return NS_OK; + } + + nsAutoCString list; + UrlClassifierCommon::TablesToString(aList, list); + + ChannelBlockDecision decision = + ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list); + if (decision != ChannelBlockDecision::Blocked) { + uint32_t event = + decision == ChannelBlockDecision::Replaced + ? nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT + : nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT; + + // Need to set aBlocked to True if we replace the Tracker with a shim, + // since the shim is treated as a blocked event + // Note: If we need to account for which kind of tracker was replaced, + // we need to create a new event type in nsIWebProgressListener + if (event == nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT) { + ContentBlockingNotifier::OnEvent(aChannel, event, true); + } else { + ContentBlockingNotifier::OnEvent(aChannel, event, false); + } + + *aShouldContinue = true; + return NS_OK; + } + + UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_TRACKING_URI, list, + ""_ns, ""_ns); + + UC_LOG( + ("UrlClassifierFeatureTrackingProtection::ProcessChannel - " + "cancelling channel %p", + aChannel)); + + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + Unused << httpChannel->CancelByURLClassifier(NS_ERROR_TRACKING_URI); + } else { + Unused << aChannel->Cancel(NS_ERROR_TRACKING_URI); + } + + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierFeatureTrackingProtection::GetURIByListType( + nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aURIType); + NS_ENSURE_ARG_POINTER(aURI); + + if (aListType == nsIUrlClassifierFeature::blocklist) { + *aURIType = nsIUrlClassifierFeature::blocklistURI; + return aChannel->GetURI(aURI); + } + + MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); + + *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; + return UrlClassifierCommon::CreatePairwiseEntityListURI(aChannel, aURI); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h new file mode 100644 index 0000000000..23abfe1fd9 --- /dev/null +++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_UrlClassifierFeatureTrackingProtection_h +#define mozilla_net_UrlClassifierFeatureTrackingProtection_h + +#include "UrlClassifierFeatureBase.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +class UrlClassifierFeatureTrackingProtection final + : public UrlClassifierFeatureAntiTrackingBase { + public: + static const char* Name(); + + static void MaybeShutdown(); + + static already_AddRefed<UrlClassifierFeatureTrackingProtection> MaybeCreate( + nsIChannel* aChannel); + + static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches( + const nsACString& aName); + + NS_IMETHOD ProcessChannel(nsIChannel* aChannel, + const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override; + + NS_IMETHOD GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override; + + private: + UrlClassifierFeatureTrackingProtection(); + + static void MaybeInitialize(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_UrlClassifierFeatureTrackingProtection_h diff --git a/netwerk/url-classifier/components.conf b/netwerk/url-classifier/components.conf new file mode 100644 index 0000000000..3cf63a06f1 --- /dev/null +++ b/netwerk/url-classifier/components.conf @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{7a6da992-dbce-4943-b463-5a2dd011fa1a}', + 'contract_ids': ['@mozilla.org/url-classifier/channel-classifier-service;1'], + 'singleton': True, + 'type': 'nsIChannelClassifierService', + 'constructor': 'mozilla::net::ChannelClassifierService::GetSingleton', + 'headers': ['mozilla/net/ChannelClassifierService.h'], + }, + { + 'cid': '{b9f4fd03-9d87-4bfd-9958-85a821750ddc}', + 'contract_ids': ['@mozilla.org/url-classifier/exception-list-service;1'], + 'esModule': 'resource://gre/modules/UrlClassifierExceptionListService.sys.mjs', + 'constructor': 'UrlClassifierExceptionListService', + }, +] diff --git a/netwerk/url-classifier/moz.build b/netwerk/url-classifier/moz.build new file mode 100644 index 0000000000..d63206780e --- /dev/null +++ b/netwerk/url-classifier/moz.build @@ -0,0 +1,68 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Safe Browsing") + +XPIDL_SOURCES += [ + "nsIChannelClassifierService.idl", + "nsIURIClassifier.idl", + "nsIUrlClassifierExceptionListService.idl", + "nsIUrlClassifierFeature.idl", +] + +XPIDL_MODULE = "url-classifier" + +EXTRA_JS_MODULES += [ + "UrlClassifierExceptionListService.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True +DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True + +UNIFIED_SOURCES += [ + "AsyncUrlChannelClassifier.cpp", + "ChannelClassifierService.cpp", + "nsChannelClassifier.cpp", + "UrlClassifierCommon.cpp", + "UrlClassifierFeatureBase.cpp", + "UrlClassifierFeatureCryptominingAnnotation.cpp", + "UrlClassifierFeatureCryptominingProtection.cpp", + "UrlClassifierFeatureCustomTables.cpp", + "UrlClassifierFeatureEmailTrackingDataCollection.cpp", + "UrlClassifierFeatureEmailTrackingProtection.cpp", + "UrlClassifierFeatureFactory.cpp", + "UrlClassifierFeatureFingerprintingAnnotation.cpp", + "UrlClassifierFeatureFingerprintingProtection.cpp", + "UrlClassifierFeaturePhishingProtection.cpp", + "UrlClassifierFeatureResult.cpp", + "UrlClassifierFeatureSocialTrackingAnnotation.cpp", + "UrlClassifierFeatureSocialTrackingProtection.cpp", + "UrlClassifierFeatureTrackingAnnotation.cpp", + "UrlClassifierFeatureTrackingProtection.cpp", +] + +EXPORTS.mozilla.net += [ + "AsyncUrlChannelClassifier.h", + "ChannelClassifierService.h", + "UrlClassifierCommon.h", + "UrlClassifierFeatureFactory.h", + "UrlClassifierFeatureResult.h", +] + +LOCAL_INCLUDES += [ + "/netwerk/base", + "/netwerk/protocol/http", + "/toolkit/components/url-classifier", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/netwerk/url-classifier/nsChannelClassifier.cpp b/netwerk/url-classifier/nsChannelClassifier.cpp new file mode 100644 index 0000000000..b9bdb3a050 --- /dev/null +++ b/netwerk/url-classifier/nsChannelClassifier.cpp @@ -0,0 +1,483 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 sts=2 ts=8 et 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 "nsChannelClassifier.h" + +#include "nsCharSeparatedTokenizer.h" +#include "nsICacheEntry.h" +#include "nsICachingChannel.h" +#include "nsIChannel.h" +#include "nsIObserverService.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptSecurityManager.h" +#include "nsNetUtil.h" +#include "nsXULAppAPI.h" +#include "nsQueryObject.h" +#include "nsPrintfCString.h" + +#include "mozilla/Components.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Services.h" + +namespace mozilla { +namespace net { + +#define URLCLASSIFIER_EXCEPTION_HOSTNAMES "urlclassifier.skipHostnames" + +// Put CachedPrefs in anonymous namespace to avoid any collision from outside of +// this file. +namespace { + +/** + * It is not recommended to read from Preference everytime a channel is + * connected. + * That is not fast and we should cache preference values and reuse them + */ +class CachedPrefs final { + public: + static CachedPrefs* GetInstance(); + + void Init(); + + nsCString GetExceptionHostnames() const { return mExceptionHostnames; } + void SetExceptionHostnames(const nsACString& aHostnames) { + mExceptionHostnames = aHostnames; + } + + private: + friend class StaticAutoPtr<CachedPrefs>; + CachedPrefs(); + ~CachedPrefs(); + + static void OnPrefsChange(const char* aPrefName, void*); + + nsCString mExceptionHostnames; + + static StaticAutoPtr<CachedPrefs> sInstance; +}; + +StaticAutoPtr<CachedPrefs> CachedPrefs::sInstance; + +// static +void CachedPrefs::OnPrefsChange(const char* aPref, void* aPrefs) { + auto* prefs = static_cast<CachedPrefs*>(aPrefs); + + if (!strcmp(aPref, URLCLASSIFIER_EXCEPTION_HOSTNAMES)) { + nsCString exceptionHostnames; + Preferences::GetCString(URLCLASSIFIER_EXCEPTION_HOSTNAMES, + exceptionHostnames); + ToLowerCase(exceptionHostnames); + prefs->SetExceptionHostnames(exceptionHostnames); + } +} + +void CachedPrefs::Init() { + Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange, + URLCLASSIFIER_EXCEPTION_HOSTNAMES, this); +} + +// static +CachedPrefs* CachedPrefs::GetInstance() { + if (!sInstance) { + sInstance = new CachedPrefs(); + sInstance->Init(); + ClearOnShutdown(&sInstance); + } + MOZ_ASSERT(sInstance); + return sInstance; +} + +CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs); } + +CachedPrefs::~CachedPrefs() { + MOZ_COUNT_DTOR(CachedPrefs); + + Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, + URLCLASSIFIER_EXCEPTION_HOSTNAMES, this); +} + +} // anonymous namespace + +NS_IMPL_ISUPPORTS(nsChannelClassifier, nsIURIClassifierCallback, nsIObserver) + +nsChannelClassifier::nsChannelClassifier(nsIChannel* aChannel) + : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel) { + UC_LOG_LEAK(("nsChannelClassifier::nsChannelClassifier [this=%p]", this)); + MOZ_ASSERT(mChannel); +} + +nsChannelClassifier::~nsChannelClassifier() { + UC_LOG_LEAK(("nsChannelClassifier::~nsChannelClassifier [this=%p]", this)); +} + +void nsChannelClassifier::Start() { + nsresult rv = StartInternal(); + if (NS_FAILED(rv)) { + // If we aren't getting a callback for any reason, assume a good verdict and + // make sure we resume the channel if necessary. + OnClassifyComplete(NS_OK, ""_ns, ""_ns, ""_ns); + } +} + +nsresult nsChannelClassifier::StartInternal() { + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // Don't bother to run the classifier on a load that has already failed. + // (this might happen after a redirect) + nsresult status; + mChannel->GetStatus(&status); + if (NS_FAILED(status)) return status; + + // Don't bother to run the classifier on a cached load that was + // previously classified as good. + if (HasBeenClassified(mChannel)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = mChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't bother checking certain types of URIs. + if (uri->SchemeIs("about")) { + return NS_ERROR_UNEXPECTED; + } + + bool hasFlags; + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_FILE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + nsCString exceptionHostnames = + CachedPrefs::GetInstance()->GetExceptionHostnames(); + if (!exceptionHostnames.IsEmpty()) { + UC_LOG( + ("nsChannelClassifier::StartInternal - entitylisted hostnames = %s " + "[this=%p]", + exceptionHostnames.get(), this)); + if (IsHostnameEntitylisted(uri, exceptionHostnames)) { + return NS_ERROR_UNEXPECTED; + } + } + + nsCOMPtr<nsIURIClassifier> uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || rv == NS_ERROR_NOT_AVAILABLE) { + // no URI classifier, ignore this failure. + return NS_ERROR_NOT_AVAILABLE; + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIScriptSecurityManager> securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal; + rv = securityManager->GetChannelURIPrincipal(mChannel, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + bool expectCallback; + if (UC_LOG_ENABLED()) { + nsCOMPtr<nsIURI> principalURI; + nsCString spec; + principal->GetAsciiSpec(spec); + spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength)); + UC_LOG( + ("nsChannelClassifier::StartInternal - classifying principal %s on " + "channel %p [this=%p]", + spec.get(), mChannel.get(), this)); + } + // The classify is running in parent process, no need to give a valid event + // target + rv = uriClassifier->Classify(principal, this, &expectCallback); + if (NS_FAILED(rv)) { + return rv; + } + + if (expectCallback) { + // Suspend the channel, it will be resumed when we get the classifier + // callback. + rv = mChannel->Suspend(); + if (NS_FAILED(rv)) { + // Some channels (including nsJSChannel) fail on Suspend. This + // shouldn't be fatal, but will prevent malware from being + // blocked on these channels. + UC_LOG_WARN( + ("nsChannelClassifier::StartInternal - couldn't suspend channel " + "[this=%p]", + this)); + return rv; + } + + mSuspendedChannel = true; + UC_LOG( + ("nsChannelClassifier::StartInternal - suspended channel %p [this=%p]", + mChannel.get(), this)); + } else { + UC_LOG_WARN(( + "nsChannelClassifier::StartInternal - not expecting callback [this=%p]", + this)); + return NS_ERROR_FAILURE; + } + + // Add an observer for shutdown + AddShutdownObserver(); + return NS_OK; +} + +bool nsChannelClassifier::IsHostnameEntitylisted( + nsIURI* aUri, const nsACString& aEntitylisted) { + nsAutoCString host; + nsresult rv = aUri->GetHost(host); + if (NS_FAILED(rv) || host.IsEmpty()) { + return false; + } + ToLowerCase(host); + + for (const nsACString& token : + nsCCharSeparatedTokenizer(aEntitylisted, ',').ToRange()) { + if (token.Equals(host)) { + UC_LOG( + ("nsChannelClassifier::StartInternal - skipping %s (entitylisted) " + "[this=%p]", + host.get(), this)); + return true; + } + } + + return false; +} + +// Note in the cache entry that this URL was classified, so that future +// cached loads don't need to be checked. +void nsChannelClassifier::MarkEntryClassified(nsresult status) { + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // Don't cache tracking classifications because we support allowlisting. + if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) || + mIsAllowListed) { + return; + } + + if (UC_LOG_ENABLED()) { + nsAutoCString errorName; + GetErrorName(status, errorName); + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength)); + UC_LOG( + ("nsChannelClassifier::MarkEntryClassified - result is %s " + "for uri %s [this=%p, channel=%p]", + errorName.get(), spec.get(), this, mChannel.get())); + } + + nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel); + if (!cachingChannel) { + return; + } + + nsCOMPtr<nsISupports> cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) { + return; + } + + nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken); + if (!cacheEntry) { + return; + } + + cacheEntry->SetMetaDataElement("necko:classified", + NS_SUCCEEDED(status) ? "1" : nullptr); +} + +bool nsChannelClassifier::HasBeenClassified(nsIChannel* aChannel) { + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel); + if (!cachingChannel) { + return false; + } + + // Only check the tag if we are loading from the cache without + // validation. + bool fromCache; + if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) { + return false; + } + + nsCOMPtr<nsISupports> cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) { + return false; + } + + nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken); + if (!cacheEntry) { + return false; + } + + nsCString tag; + cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag)); + return tag.EqualsLiteral("1"); +} + +/* static */ +nsresult nsChannelClassifier::SendThreatHitReport(nsIChannel* aChannel, + const nsACString& aProvider, + const nsACString& aList, + const nsACString& aFullHash) { + NS_ENSURE_ARG_POINTER(aChannel); + + nsAutoCString provider(aProvider); + nsPrintfCString reportEnablePref( + "browser.safebrowsing.provider.%s.dataSharing.enabled", provider.get()); + if (!Preferences::GetBool(reportEnablePref.get(), false)) { + UC_LOG( + ("nsChannelClassifier::SendThreatHitReport - data sharing disabled for " + "%s", + provider.get())); + return NS_OK; + } + + nsCOMPtr<nsIURIClassifier> uriClassifier = + components::UrlClassifierDB::Service(); + if (!uriClassifier) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = + uriClassifier->SendThreatHitReport(aChannel, aProvider, aList, aFullHash); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode, + const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) { + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT( + !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode)); + + if (mSuspendedChannel) { + MarkEntryClassified(aErrorCode); + + if (NS_FAILED(aErrorCode)) { + if (UC_LOG_ENABLED()) { + nsAutoCString errorName; + GetErrorName(aErrorCode, errorName); + + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + nsCString spec = uri->GetSpecOrDefault(); + spec.Truncate( + std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength)); + UC_LOG( + ("nsChannelClassifier::OnClassifyComplete - cancelling channel %p " + "for %s " + "with error code %s [this=%p]", + mChannel.get(), spec.get(), errorName.get(), this)); + } + + // Channel will be cancelled (page element blocked) due to Safe Browsing. + // Do update the security state of the document and fire a security + // change event. + UrlClassifierCommon::SetBlockedContent(mChannel, aErrorCode, aList, + aProvider, aFullHash); + + if (aErrorCode == NS_ERROR_MALWARE_URI || + aErrorCode == NS_ERROR_PHISHING_URI || + aErrorCode == NS_ERROR_UNWANTED_URI || + aErrorCode == NS_ERROR_HARMFUL_URI) { + SendThreatHitReport(mChannel, aProvider, aList, aFullHash); + } + + mChannel->Cancel(aErrorCode); + } + UC_LOG( + ("nsChannelClassifier::OnClassifyComplete - resuming channel %p " + "[this=%p]", + mChannel.get(), this)); + mChannel->Resume(); + } + + mChannel = nullptr; + RemoveShutdownObserver(); + + return NS_OK; +} + +void nsChannelClassifier::AddShutdownObserver() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, "profile-change-net-teardown", false); + } +} + +void nsChannelClassifier::RemoveShutdownObserver() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, "profile-change-net-teardown"); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIObserver implementation +NS_IMETHODIMP +nsChannelClassifier::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "profile-change-net-teardown")) { + // If we aren't getting a callback for any reason, make sure + // we resume the channel. + + if (mChannel && mSuspendedChannel) { + mSuspendedChannel = false; + mChannel->Cancel(NS_ERROR_ABORT); + mChannel->Resume(); + mChannel = nullptr; + } + + RemoveShutdownObserver(); + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/url-classifier/nsChannelClassifier.h b/netwerk/url-classifier/nsChannelClassifier.h new file mode 100644 index 0000000000..ddd89e2976 --- /dev/null +++ b/netwerk/url-classifier/nsChannelClassifier.h @@ -0,0 +1,70 @@ +/* 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/. */ + +#ifndef nsChannelClassifier_h__ +#define nsChannelClassifier_h__ + +#include "nsIObserver.h" +#include "nsIURIClassifier.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +#include <functional> + +class nsIChannel; + +namespace mozilla { +namespace net { + +class nsChannelClassifier final : public nsIURIClassifierCallback, + public nsIObserver { + public: + explicit nsChannelClassifier(nsIChannel* aChannel); + + NS_DECL_ISUPPORTS + NS_DECL_NSIURICLASSIFIERCALLBACK + NS_DECL_NSIOBSERVER + + // Calls nsIURIClassifier.Classify with the principal of the given channel, + // and cancels the channel on a bad verdict. + void Start(); + + private: + // True if the channel is on the allow list. + bool mIsAllowListed; + // True if the channel has been suspended. + bool mSuspendedChannel; + nsCOMPtr<nsIChannel> mChannel; + + ~nsChannelClassifier(); + // Caches good classifications for the channel principal. + void MarkEntryClassified(nsresult status); + bool HasBeenClassified(nsIChannel* aChannel); + // Helper function so that we ensure we call ContinueBeginConnect once + // Start is called. Returns NS_OK if and only if we will get a callback + // from the classifier service. + nsresult StartInternal(); + // Helper function to check a URI against the hostname entitylist + bool IsHostnameEntitylisted(nsIURI* aUri, const nsACString& aEntitylisted); + + void AddShutdownObserver(); + void RemoveShutdownObserver(); + static nsresult SendThreatHitReport(nsIChannel* aChannel, + const nsACString& aProvider, + const nsACString& aList, + const nsACString& aFullHash); + + public: + // If we are blocking content, update the corresponding flag in the respective + // docshell and call nsDocLoader::OnSecurityChange. + static nsresult SetBlockedContent(nsIChannel* channel, nsresult aErrorCode, + const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash); +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/url-classifier/nsIChannelClassifierService.idl b/netwerk/url-classifier/nsIChannelClassifierService.idl new file mode 100644 index 0000000000..781af289f8 --- /dev/null +++ b/netwerk/url-classifier/nsIChannelClassifierService.idl @@ -0,0 +1,58 @@ +/* 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 "nsIContentPolicy.idl" +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIURI; +interface nsIObserver; + +[scriptable, uuid(9b0353a7-ab46-4914-9178-2215ee221e4e)] +interface nsIUrlClassifierBlockedChannel: nsISupports +{ + // blocked reason + const unsigned long TRACKING_PROTECTION = 0; + const unsigned long SOCIAL_TRACKING_PROTECTION = 1; + const unsigned long FINGERPRINTING_PROTECTION = 2; + const unsigned long CRYPTOMINING_PROTECTION = 3; + + // Feature that blocks this channel. + readonly attribute uint8_t reason; + + // Comma separated list of tables that find a match for the channel's url. + readonly attribute ACString tables; + + readonly attribute AString url; + + readonly attribute uint64_t tabId; + + readonly attribute uint64_t channelId; + + readonly attribute boolean isPrivateBrowsing; + + readonly attribute AString topLevelUrl; + + // Unblock the load, but inform the UI that the tracking content will be + // replaced with a shim. The unblocked channel is still considered as a + // tracking channel. The only difference to allow() is the event sent to the + // UI. Calls to replace will only unblock the channel. Callers are responsible + // for replacing the tracking content. + void replace(); + + // Unblock the load and inform the UI that the channel has been allowed to + // load. The unblocked channel is still considered as a tracking channel. + void allow(); +}; + +[scriptable, uuid(9411409c-5dac-40b9-ba36-2738a7237a4c)] +interface nsIChannelClassifierService : nsISupports +{ + // when a channel is blocked, the observer should receive + // "urlclassifier-before-block-channel" callback an alternative way is to + // use a custom callback instead of using nsIObserver + void addListener(in nsIObserver aObserver); + + void removeListener(in nsIObserver aObserver); +}; diff --git a/netwerk/url-classifier/nsIURIClassifier.idl b/netwerk/url-classifier/nsIURIClassifier.idl new file mode 100644 index 0000000000..788d7b3465 --- /dev/null +++ b/netwerk/url-classifier/nsIURIClassifier.idl @@ -0,0 +1,115 @@ +/* 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 "nsISupports.idl" +#include "nsIUrlClassifierFeature.idl" + +%{C++ +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" +%} +[ref] native StringArrayRef(nsTArray<nsCString>); + +interface nsIChannel; +interface nsISerialEventTarget; +interface nsIPrincipal; +interface nsIURI; +interface nsIUrlClassifierFeatureCallback; + +/** + * Callback function for nsIURIClassifier lookups. + */ +[scriptable, function, uuid(8face46e-0c96-470f-af40-0037dcd797bd)] +interface nsIURIClassifierCallback : nsISupports +{ + /** + * Called by the URI classifier service when it is done checking a URI. + * + * Clients are responsible for associating callback objects with classify() + * calls. + * + * @param aErrorCode + * The error code with which the channel should be cancelled, or + * NS_OK if the load should continue normally. + * @param aList + * Name of the list that matched + * @param aProvider + * Name of provider that matched + * @param aFullHash + * Full hash of URL that matched + */ + void onClassifyComplete(in nsresult aErrorCode, + in ACString aList, + in ACString aProvider, + in ACString aFullHash); +}; + +/** + * The URI classifier service checks a URI against lists of phishing + * and malware sites. + */ +[scriptable, uuid(596620cc-76e3-4133-9d90-360e59a794cf)] +interface nsIURIClassifier : nsISupports +{ + /** + * Classify a Principal using its URI. + * + * @param aPrincipal + * The principal that should be checked by the URI classifier. + * + * @param aCallback + * The URI classifier will call this callback when the URI has been + * classified. + * + * @return <code>false</code> if classification is not necessary. The + * callback will not be called. + * <code>true</code> if classification will be performed. The + * callback will be called. + */ + boolean classify(in nsIPrincipal aPrincipal, + in nsIURIClassifierCallback aCallback); + + /** + * Asynchronously classify a URI with list of features. This does not make + * network requests. + */ + void asyncClassifyLocalWithFeatures(in nsIURI aURI, + in Array<nsIUrlClassifierFeature> aFeatures, + in nsIUrlClassifierFeature_listType aListType, + in nsIUrlClassifierFeatureCallback aCallback); + + /** + * Returns a feature named aFeatureName. + */ + nsIUrlClassifierFeature getFeatureByName(in ACString aFeatureName); + + /** + * Returns all the feature names. + */ + Array<ACString> getFeatureNames(); + + /** + * Create a new feature with a list of tables. This method is just for + * testing! Don't use it elsewhere. + */ + nsIUrlClassifierFeature createFeatureWithTables(in ACString aName, + in Array<ACString> aBlocklistTables, + in Array<ACString> aEntitylistTables); + + /** + * Report to the provider that a Safe Browsing warning was shown. + * + * @param aChannel + * Channel for which the URL matched something on the threat list. + * @param aProvider + * Provider to notify. + * @param aList + * List where the full hash was found. + * @param aFullHash + * Full URL hash that triggered the warning. + */ + + void sendThreatHitReport(in nsIChannel aChannel, in ACString aProvider, + in ACString aList, in ACString aFullHash); +}; diff --git a/netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl b/netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl new file mode 100644 index 0000000000..256d0b89ae --- /dev/null +++ b/netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl @@ -0,0 +1,71 @@ +/* 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 "nsISupports.idl" + +/** + * Observer for exception list updates. + */ +[scriptable, function, uuid(f7c918e5-94bf-4b6e-9758-ef7bdab6af7e)] +interface nsIUrlClassifierExceptionListObserver : nsISupports +{ + /** + * Called by nsIUrlClassifierExceptionListService when the exception list + * for a designated feature changes and when the observer is first registered. + * + * @param aList + * A comma-separated list of url patterns, intended to be parsed + * by nsContentUtils::IsURIInList. + */ + void onExceptionListUpdate(in ACString aList); +}; + +/** + * A service that monitors updates to the exception list of url-classifier + * feature from sources such as a local pref and remote settings updates. + */ +[scriptable, uuid(75c3d1a3-e977-4079-9e27-b3b56bdb76ea)] +interface nsIUrlClassifierExceptionListService : nsISupports +{ + /** + * Register a new observer to exception list updates. When the observer is + * registered it is called immediately once. Afterwards it will be called + * whenever the specified pref changes or when remote settings for + * url-classifier features updates. + * + * @param aFeature + * The feature for which to observe the exception list. + * + * @param aPrefName + * (Optional) A pref name to monitor. The pref must be of string + * type and contain a comma-separated list of URL patterns. + * + * @param aObserver + * An nsIUrlClassifierExceptionListObserver object or function that + * will receive updates to the exception list as a comma-separated + * string. Will be called immediately with the current exception + * list value. + */ + void registerAndRunExceptionListObserver(in ACString aFeature, + in ACString aPrefName, + in nsIUrlClassifierExceptionListObserver aObserver); + + /** + * Unregister an observer. + * + * @param aFeature + * The feature for which to stop observing. + * + * @param aObserver + * The nsIUrlClassifierExceptionListObserver object to unregister. + */ + void unregisterExceptionListObserver(in ACString aFeature, + in nsIUrlClassifierExceptionListObserver aObserver); + + /** + * Clear all data in the service. + * This API is for testing only. + */ + void clear(); +}; diff --git a/netwerk/url-classifier/nsIUrlClassifierFeature.idl b/netwerk/url-classifier/nsIUrlClassifierFeature.idl new file mode 100644 index 0000000000..a7ee58289d --- /dev/null +++ b/netwerk/url-classifier/nsIUrlClassifierFeature.idl @@ -0,0 +1,120 @@ +/* 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 "nsISupports.idl" + +%{C++ +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" +%} +[ref] native StringArrayRef(nsTArray<nsCString>); +[ref] native ConstStringArrayRef(const nsTArray<nsCString>); + +interface nsIChannel; +interface nsIURI; + +/** + * A single URLClassifier feature. + */ +[builtinclass, scriptable, uuid(a6c9b24e-b4f1-426e-af58-2c976c3943a8)] +interface nsIUrlClassifierFeature : nsISupports +{ + cenum listType: 8 { + blocklist = 0, + entitylist = 1, + }; + + cenum URIType: 8 { + blocklistURI = 0, + entitylistURI = 1, + pairwiseEntitylistURI = 2, + }; + + /** + * The feature name + */ + readonly attribute ACString name; + + /** + * Returns the tables for one of the possible lists. + */ + [noscript] StringArrayRef getTables(in nsIUrlClassifierFeature_listType aListType); + + /** + * Returns true if |aTable| is part of the tables of |aListType| type. + */ + [noscript] boolean hasTable(in ACString aTable, + in nsIUrlClassifierFeature_listType aListType); + + /** + * Returns true if |aHost| is contained in the preference of |aListType| type. + * |aPrefTableName| will be set to the table name to use. + */ + [noscript] boolean hasHostInPreferences(in ACString aHost, + in nsIUrlClassifierFeature_listType aListType, + out ACString aPrefTableName); + + /** + * Returns a comma-separated list of hosts to be ignored. + */ + readonly attribute ACString exceptionHostList; + + /** + * When this feature matches the channel, this method is executed to do + * 'something' on the channel. For instance, a tracking-annotation feature + * would mark the channel as tracker, a tracking-protection feature would + * cancel the channel. + * Returns if we should process other feature results or not. For instance, + * tracking-protection cancel the channel, and after that we should stop + * processing other features. + */ + [noscript] boolean processChannel(in nsIChannel aChannel, + in ConstStringArrayRef aList, + in ConstStringArrayRef aHashes); + + /** + * Features can work with different URLs from a channel (channel url, or + * top-level, or something else). This method returns what we need to use for + * the current list. + * If the returned URI is created by CreatePairwiseEntityListURI(), the + * URIType is pairwiseEntitylistURI. Otherwise, it depends on the listType. + */ + [noscript] nsIURI getURIByListType(in nsIChannel channel, + in nsIUrlClassifierFeature_listType listType, + out nsIUrlClassifierFeature_URIType URIType); +}; + +/** + * The result of the classifier operation is this interface. + * See asyncClassifyLocalWithFeatures() in nsIURIClassifier.idl. + */ +[builtinclass, scriptable, uuid(ccb88140-5d66-4873-9815-a1b98d6cdc92)] +interface nsIUrlClassifierFeatureResult : nsISupports +{ + readonly attribute nsIURI uri; + + readonly attribute nsIUrlClassifierFeature feature; + + // Comma separate tables or preferences. + readonly attribute ACString list; +}; + +/** + * Callback function for nsIURIClassifier lookups. + * See asyncClassifyLocalWithFeatures() in nsIURIClassifier.idl. + */ +[scriptable, function, uuid(2ea83c26-dfc9-44ed-9cfc-171d4753d78e)] +interface nsIUrlClassifierFeatureCallback : nsISupports +{ + /** + * Called by the URI classifier service when it is done checking a URI. + * + * Clients are responsible for associating callback objects with classify() + * calls. + * + * @param aResults + * List of nsIUrlClassifierFeatureResult objects. + */ + void onClassifyComplete(in Array<nsIUrlClassifierFeatureResult> aResults); +}; |