diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /netwerk/url-classifier/AsyncUrlChannelClassifier.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/url-classifier/AsyncUrlChannelClassifier.cpp')
-rw-r--r-- | netwerk/url-classifier/AsyncUrlChannelClassifier.cpp | 920 |
1 files changed, 920 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 |