summaryrefslogtreecommitdiffstats
path: root/netwerk/url-classifier
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/url-classifier
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/url-classifier/AsyncUrlChannelClassifier.cpp920
-rw-r--r--netwerk/url-classifier/AsyncUrlChannelClassifier.h27
-rw-r--r--netwerk/url-classifier/ChannelClassifierService.cpp271
-rw-r--r--netwerk/url-classifier/ChannelClassifierService.h80
-rw-r--r--netwerk/url-classifier/UrlClassifierCommon.cpp671
-rw-r--r--netwerk/url-classifier/UrlClassifierCommon.h103
-rw-r--r--netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs168
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureBase.cpp182
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureBase.h86
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp170
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp211
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp103
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCustomTables.h35
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.cpp268
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingDataCollection.h45
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.cpp217
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureEmailTrackingProtection.h47
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFactory.cpp384
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFactory.h57
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp178
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp216
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp128
-rw-r--r--netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureResult.cpp49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureResult.h45
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp178
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp213
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp180
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp217
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h49
-rw-r--r--netwerk/url-classifier/components.conf22
-rw-r--r--netwerk/url-classifier/moz.build68
-rw-r--r--netwerk/url-classifier/nsChannelClassifier.cpp483
-rw-r--r--netwerk/url-classifier/nsChannelClassifier.h70
-rw-r--r--netwerk/url-classifier/nsIChannelClassifierService.idl58
-rw-r--r--netwerk/url-classifier/nsIURIClassifier.idl115
-rw-r--r--netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl71
-rw-r--r--netwerk/url-classifier/nsIUrlClassifierFeature.idl120
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);
+};