зеркало из https://github.com/mozilla/gecko-dev.git
2605 строки
79 KiB
C++
2605 строки
79 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "nsCOMPtr.h"
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "nsArrayUtils.h"
|
|
#include "nsCRT.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIPermissionManager.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "nsToolkitCompsCID.h"
|
|
#include "nsUrlClassifierDBService.h"
|
|
#include "nsUrlClassifierUtils.h"
|
|
#include "nsUrlClassifierProxies.h"
|
|
#include "nsURILoader.h"
|
|
#include "nsString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsTArray.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsString.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/Components.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/ErrorNames.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "prnetdb.h"
|
|
#include "Entries.h"
|
|
#include "Classifier.h"
|
|
#include "ProtocolParser.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIUrlListManager.h"
|
|
#include "Classifier.h"
|
|
#include "ProtocolParser.h"
|
|
#include "nsContentUtils.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/PermissionMessageUtils.h"
|
|
#include "mozilla/dom/URLClassifierChild.h"
|
|
#include "mozilla/net/UrlClassifierFeatureFactory.h"
|
|
#include "mozilla/net/UrlClassifierFeatureResult.h"
|
|
#include "mozilla/ipc/URIUtils.h"
|
|
#include "mozilla/SyncRunnable.h"
|
|
#include "UrlClassifierTelemetryUtils.h"
|
|
#include "nsIURLFormatter.h"
|
|
#include "nsIUploadChannel.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsToolkitCompsCID.h"
|
|
|
|
namespace mozilla {
|
|
namespace safebrowsing {
|
|
|
|
nsresult TablesToResponse(const nsACString& tables) {
|
|
if (tables.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We don't check mCheckMalware and friends because disabled tables are
|
|
// never included
|
|
if (FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
|
|
return NS_ERROR_MALWARE_URI;
|
|
}
|
|
if (FindInReadable(NS_LITERAL_CSTRING("-harmful-"), tables)) {
|
|
return NS_ERROR_HARMFUL_URI;
|
|
}
|
|
if (FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
|
|
return NS_ERROR_PHISHING_URI;
|
|
}
|
|
if (FindInReadable(NS_LITERAL_CSTRING("-unwanted-"), tables)) {
|
|
return NS_ERROR_UNWANTED_URI;
|
|
}
|
|
if (FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
|
|
return NS_ERROR_TRACKING_URI;
|
|
}
|
|
if (FindInReadable(NS_LITERAL_CSTRING("-block-"), tables)) {
|
|
return NS_ERROR_BLOCKED_URI;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace safebrowsing
|
|
} // namespace mozilla
|
|
|
|
// This class holds a list of features, their tables, and it stores the lookup
|
|
// results.
|
|
class nsUrlClassifierDBService::FeatureHolder final {
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FeatureHolder);
|
|
|
|
// In order to avoid multiple lookup for the same table, we have a special
|
|
// array for tables and their results. The Features are stored in a separate
|
|
// array together with the references to their tables.
|
|
|
|
class TableData {
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
|
|
nsUrlClassifierDBService::FeatureHolder::TableData);
|
|
|
|
explicit TableData(const nsACString& aTable) : mTable(aTable) {}
|
|
|
|
nsCString mTable;
|
|
LookupResultArray mResults;
|
|
|
|
private:
|
|
~TableData() = default;
|
|
};
|
|
|
|
struct FeatureData {
|
|
RefPtr<nsIUrlClassifierFeature> mFeature;
|
|
nsTArray<RefPtr<TableData>> mTables;
|
|
};
|
|
|
|
static already_AddRefed<FeatureHolder> Create(
|
|
nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures,
|
|
nsIUrlClassifierFeature::listType aListType) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aURI);
|
|
|
|
RefPtr<FeatureHolder> holder = new FeatureHolder(aURI);
|
|
|
|
for (nsIUrlClassifierFeature* feature : aFeatures) {
|
|
FeatureData* featureData = holder->mFeatureData.AppendElement();
|
|
MOZ_ASSERT(featureData);
|
|
|
|
featureData->mFeature = feature;
|
|
nsTArray<nsCString> tables;
|
|
nsresult rv = feature->GetTables(aListType, tables);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (const nsCString& table : tables) {
|
|
TableData* tableData = holder->GetOrCreateTableData(table);
|
|
MOZ_ASSERT(tableData);
|
|
|
|
featureData->mTables.AppendElement(tableData);
|
|
}
|
|
}
|
|
|
|
return holder.forget();
|
|
}
|
|
|
|
nsresult DoLocalLookup(const nsACString& aSpec,
|
|
nsUrlClassifierDBServiceWorker* aWorker) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(aWorker);
|
|
|
|
mozilla::Telemetry::AutoTimer<
|
|
mozilla::Telemetry::URLCLASSIFIER_CL_CHECK_TIME>
|
|
timer;
|
|
|
|
// Get the set of fragments based on the url. This is necessary because we
|
|
// only look up at most 5 URLs per aSpec, even if aSpec has more than 5
|
|
// components.
|
|
nsTArray<nsCString> fragments;
|
|
nsresult rv = LookupCache::GetLookupFragments(aSpec, &fragments);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (TableData* tableData : mTableData) {
|
|
rv = aWorker->DoSingleLocalLookupWithURIFragments(
|
|
fragments, tableData->mTable, tableData->mResults);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// This method is used to convert the LookupResultArray from
|
|
// ::DoSingleLocalLookupWithURIFragments to nsIUrlClassifierFeatureResult
|
|
void GetResults(nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aResults) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// For each table, we must concatenate the results of the corresponding
|
|
// tables.
|
|
|
|
for (FeatureData& featureData : mFeatureData) {
|
|
nsAutoCString list;
|
|
for (TableData* tableData : featureData.mTables) {
|
|
for (uint32_t i = 0; i < tableData->mResults.Length(); ++i) {
|
|
if (!list.IsEmpty()) {
|
|
list.AppendLiteral(",");
|
|
}
|
|
list.Append(tableData->mResults[i]->mTableName);
|
|
}
|
|
}
|
|
|
|
if (list.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
RefPtr<mozilla::net::UrlClassifierFeatureResult> result =
|
|
new mozilla::net::UrlClassifierFeatureResult(
|
|
mURI, featureData.mFeature, list);
|
|
aResults.AppendElement(result);
|
|
}
|
|
}
|
|
|
|
mozilla::UniquePtr<LookupResultArray> GetTableResults() const {
|
|
mozilla::UniquePtr<LookupResultArray> results =
|
|
mozilla::MakeUnique<LookupResultArray>();
|
|
if (NS_WARN_IF(!results)) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (TableData* tableData : mTableData) {
|
|
results->AppendElements(tableData->mResults);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private:
|
|
explicit FeatureHolder(nsIURI* aURI) : mURI(aURI) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
~FeatureHolder() {
|
|
for (FeatureData& featureData : mFeatureData) {
|
|
NS_ReleaseOnMainThread("FeatureHolder:mFeatureData",
|
|
featureData.mFeature.forget());
|
|
}
|
|
|
|
NS_ReleaseOnMainThread("FeatureHolder:mURI", mURI.forget());
|
|
}
|
|
|
|
TableData* GetOrCreateTableData(const nsACString& aTable) {
|
|
for (TableData* tableData : mTableData) {
|
|
if (tableData->mTable == aTable) {
|
|
return tableData;
|
|
}
|
|
}
|
|
|
|
RefPtr<TableData> tableData = new TableData(aTable);
|
|
mTableData.AppendElement(tableData);
|
|
return tableData;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> mURI;
|
|
nsTArray<FeatureData> mFeatureData;
|
|
nsTArray<RefPtr<TableData>> mTableData;
|
|
};
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::safebrowsing;
|
|
|
|
// MOZ_LOG=UrlClassifierDbService:5
|
|
LazyLogModule gUrlClassifierDbServiceLog("UrlClassifierDbService");
|
|
#define LOG(args) \
|
|
MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
|
|
#define LOG_ENABLED() \
|
|
MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
|
|
|
|
#define GETHASH_NOISE_PREF "urlclassifier.gethashnoise"
|
|
#define GETHASH_NOISE_DEFAULT 4
|
|
|
|
// 30 minutes as the maximum negative cache duration.
|
|
#define MAXIMUM_NEGATIVE_CACHE_DURATION_SEC (30 * 60 * 1000)
|
|
|
|
class nsUrlClassifierDBServiceWorker;
|
|
|
|
// Singleton instance.
|
|
static nsUrlClassifierDBService* sUrlClassifierDBService;
|
|
|
|
nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr;
|
|
|
|
// Once we've committed to shutting down, don't do work in the background
|
|
// thread.
|
|
static Atomic<bool> gShuttingDownThread(false);
|
|
|
|
static uint32_t sGethashNoise = GETHASH_NOISE_DEFAULT;
|
|
|
|
NS_IMPL_ISUPPORTS(nsUrlClassifierDBServiceWorker, nsIUrlClassifierDBService)
|
|
|
|
nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker()
|
|
: mInStream(false),
|
|
mGethashNoise(0),
|
|
mPendingLookupLock("nsUrlClassifierDBServerWorker.mPendingLookupLock") {}
|
|
|
|
nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker() {
|
|
NS_ASSERTION(!mClassifier,
|
|
"Db connection not closed, leaking memory! Call CloseDb "
|
|
"to close the connection.");
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::Init(
|
|
uint32_t aGethashNoise, nsCOMPtr<nsIFile> aCacheDir,
|
|
nsUrlClassifierDBService* aDBService) {
|
|
mGethashNoise = aGethashNoise;
|
|
mCacheDir = aCacheDir;
|
|
mDBService = aDBService;
|
|
|
|
ResetUpdate();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::QueueLookup(
|
|
const nsACString& aKey,
|
|
nsUrlClassifierDBService::FeatureHolder* aFeatureHolder,
|
|
nsIUrlClassifierLookupCallback* aCallback) {
|
|
MOZ_ASSERT(aFeatureHolder);
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
MutexAutoLock lock(mPendingLookupLock);
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
PendingLookup* lookup = mPendingLookups.AppendElement(fallible);
|
|
if (NS_WARN_IF(!lookup)) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
lookup->mStartTime = TimeStamp::Now();
|
|
lookup->mKey = aKey;
|
|
lookup->mCallback = aCallback;
|
|
lookup->mFeatureHolder = aFeatureHolder;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::DoSingleLocalLookupWithURIFragments(
|
|
const nsTArray<nsCString>& aSpecFragments, const nsACString& aTable,
|
|
LookupResultArray& aResults) {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
MOZ_ASSERT(
|
|
!NS_IsMainThread(),
|
|
"DoSingleLocalLookupWithURIFragments must be on background thread");
|
|
|
|
// Bail if we haven't been initialized on the background thread.
|
|
if (!mClassifier) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult rv =
|
|
mClassifier->CheckURIFragments(aSpecFragments, aTable, aResults);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
LOG(("Found %zu results.", aResults.Length()));
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Lookup up a key in the database is a two step process:
|
|
*
|
|
* a) First we look for any Entries in the database that might apply to this
|
|
* url. For each URL there are one or two possible domain names to check:
|
|
* the two-part domain name (example.com) and the three-part name
|
|
* (www.example.com). We check the database for both of these.
|
|
* b) If we find any entries, we check the list of fragments for that entry
|
|
* against the possible subfragments of the URL as described in the
|
|
* "Simplified Regular Expression Lookup" section of the protocol doc.
|
|
*/
|
|
nsresult nsUrlClassifierDBServiceWorker::DoLookup(
|
|
const nsACString& spec,
|
|
nsUrlClassifierDBService::FeatureHolder* aFeatureHolder,
|
|
nsIUrlClassifierLookupCallback* c) {
|
|
// Make sure the callback is invoked when a failure occurs,
|
|
// otherwise we will not be able to load any url.
|
|
auto scopeExit = MakeScopeExit([&c]() { c->LookupComplete(nullptr); });
|
|
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
PRIntervalTime clockStart = 0;
|
|
if (LOG_ENABLED()) {
|
|
clockStart = PR_IntervalNow();
|
|
}
|
|
|
|
nsresult rv = aFeatureHolder->DoLocalLookup(spec, this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (LOG_ENABLED()) {
|
|
PRIntervalTime clockEnd = PR_IntervalNow();
|
|
LOG(("query took %dms\n",
|
|
PR_IntervalToMilliseconds(clockEnd - clockStart)));
|
|
}
|
|
|
|
UniquePtr<LookupResultArray> results = aFeatureHolder->GetTableResults();
|
|
if (NS_WARN_IF(!results)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
LOG(("Found %zu results.", results->Length()));
|
|
|
|
for (const RefPtr<const LookupResult> lookupResult : *results) {
|
|
if (!lookupResult->Confirmed() &&
|
|
mDBService->CanComplete(lookupResult->mTableName)) {
|
|
// We're going to be doing a gethash request, add some extra entries.
|
|
// Note that we cannot pass the first two by reference, because we
|
|
// add to completes, which can cause completes to reallocate and move.
|
|
AddNoise(lookupResult->hash.fixedLengthPrefix, lookupResult->mTableName,
|
|
mGethashNoise, *results);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// At this point ownership of 'results' is handed to the callback.
|
|
scopeExit.release();
|
|
c->LookupComplete(std::move(results));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::HandlePendingLookups() {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
MutexAutoLock lock(mPendingLookupLock);
|
|
while (mPendingLookups.Length() > 0) {
|
|
PendingLookup lookup = mPendingLookups[0];
|
|
mPendingLookups.RemoveElementAt(0);
|
|
{
|
|
MutexAutoUnlock unlock(mPendingLookupLock);
|
|
DoLookup(lookup.mKey, lookup.mFeatureHolder, lookup.mCallback);
|
|
}
|
|
double lookupTime = (TimeStamp::Now() - lookup.mStartTime).ToMilliseconds();
|
|
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LOOKUP_TIME_2,
|
|
static_cast<uint32_t>(lookupTime));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::AddNoise(const Prefix aPrefix,
|
|
const nsCString tableName,
|
|
uint32_t aCount,
|
|
LookupResultArray& results) {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
if (aCount < 1) {
|
|
return NS_OK;
|
|
}
|
|
|
|
PrefixArray noiseEntries;
|
|
nsresult rv =
|
|
mClassifier->ReadNoiseEntries(aPrefix, tableName, aCount, noiseEntries);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (const auto noiseEntry : noiseEntries) {
|
|
RefPtr<LookupResult> result = new LookupResult;
|
|
results.AppendElement(result);
|
|
|
|
result->hash.fixedLengthPrefix = noiseEntry;
|
|
result->mNoise = true;
|
|
result->mPartialHashLength = PREFIX_SIZE; // Noise is always 4-byte,
|
|
result->mTableName.Assign(tableName);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Lookup a key in the db.
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal,
|
|
const nsACString& aTables,
|
|
nsIUrlClassifierCallback* c) {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
return HandlePendingLookups();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c) {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
nsresult rv = OpenDb();
|
|
if (NS_FAILED(rv)) {
|
|
NS_ERROR("Unable to open SafeBrowsing database");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString response;
|
|
mClassifier->TableRequest(response);
|
|
LOG(("GetTables: %s", response.get()));
|
|
c->HandleEvent(response);
|
|
|
|
return rv;
|
|
}
|
|
|
|
void nsUrlClassifierDBServiceWorker::ResetStream() {
|
|
LOG(("ResetStream"));
|
|
mInStream = false;
|
|
mProtocolParser = nullptr;
|
|
}
|
|
|
|
void nsUrlClassifierDBServiceWorker::ResetUpdate() {
|
|
LOG(("ResetUpdate"));
|
|
mUpdateWaitSec = 0;
|
|
mUpdateStatus = NS_OK;
|
|
mUpdateObserver = nullptr;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::SetHashCompleter(
|
|
const nsACString& tableName, nsIUrlClassifierHashCompleter* completer) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::BeginUpdate(
|
|
nsIUrlClassifierUpdateObserver* observer, const nsACString& tables) {
|
|
LOG(("nsUrlClassifierDBServiceWorker::BeginUpdate [%s]",
|
|
PromiseFlatCString(tables).get()));
|
|
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
NS_ENSURE_STATE(!mUpdateObserver);
|
|
|
|
nsresult rv = OpenDb();
|
|
if (NS_FAILED(rv)) {
|
|
NS_ERROR("Unable to open SafeBrowsing database");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mUpdateStatus = NS_OK;
|
|
MOZ_ASSERT(mTableUpdates.IsEmpty(),
|
|
"mTableUpdates should have been cleared in FinishUpdate()");
|
|
mUpdateObserver = observer;
|
|
Classifier::SplitTables(tables, mUpdateTables);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Called from the stream updater.
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::BeginStream(const nsACString& table) {
|
|
LOG(("nsUrlClassifierDBServiceWorker::BeginStream"));
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Streaming must be on the background thread");
|
|
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
NS_ENSURE_STATE(mUpdateObserver);
|
|
NS_ENSURE_STATE(!mInStream);
|
|
|
|
mInStream = true;
|
|
|
|
NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser.");
|
|
|
|
// Check if we should use protobuf to parse the update.
|
|
bool useProtobuf = false;
|
|
for (size_t i = 0; i < mUpdateTables.Length(); i++) {
|
|
bool isCurProtobuf =
|
|
StringEndsWith(mUpdateTables[i], NS_LITERAL_CSTRING("-proto"));
|
|
|
|
if (0 == i) {
|
|
// Use the first table name to decice if all the subsequent tables
|
|
// should be '-proto'.
|
|
useProtobuf = isCurProtobuf;
|
|
continue;
|
|
}
|
|
|
|
if (useProtobuf != isCurProtobuf) {
|
|
NS_WARNING(
|
|
"Cannot mix 'proto' tables with other types "
|
|
"within the same provider.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (useProtobuf) {
|
|
mProtocolParser.reset(new (fallible) ProtocolParserProtobuf());
|
|
} else {
|
|
mProtocolParser.reset(new (fallible) ProtocolParserV2());
|
|
}
|
|
if (!mProtocolParser) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return mProtocolParser->Begin(table, mUpdateTables);
|
|
}
|
|
|
|
/**
|
|
* Updating the database:
|
|
*
|
|
* The Update() method takes a series of chunks separated with control data,
|
|
* as described in
|
|
* http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec
|
|
*
|
|
* It will iterate through the control data until it reaches a chunk. By
|
|
* the time it reaches a chunk, it should have received
|
|
* a) the table to which this chunk applies
|
|
* b) the type of chunk (add, delete, expire add, expire delete).
|
|
* c) the chunk ID
|
|
* d) the length of the chunk.
|
|
*
|
|
* For add and subtract chunks, it needs to read the chunk data (expires
|
|
* don't have any data). Chunk data is a list of URI fragments whose
|
|
* encoding depends on the type of table (which is indicated by the end
|
|
* of the table name):
|
|
* a) tables ending with -exp are a zlib-compressed list of URI fragments
|
|
* separated by newlines.
|
|
* b) tables ending with -sha128 have the form
|
|
* [domain][N][frag0]...[fragN]
|
|
* 16 1 16 16
|
|
* If N is 0, the domain is reused as a fragment.
|
|
* c) any other tables are assumed to be a plaintext list of URI fragments
|
|
* separated by newlines.
|
|
*
|
|
* Update() can be fed partial data; It will accumulate data until there is
|
|
* enough to act on. Finish() should be called when there will be no more
|
|
* data.
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::UpdateStream(const nsACString& chunk) {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
MOZ_ASSERT(mProtocolParser);
|
|
|
|
NS_ENSURE_STATE(mInStream);
|
|
|
|
HandlePendingLookups();
|
|
|
|
// Feed the chunk to the parser.
|
|
return mProtocolParser->AppendStream(chunk);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::FinishStream() {
|
|
if (gShuttingDownThread) {
|
|
LOG(("shutting down"));
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
MOZ_ASSERT(mProtocolParser);
|
|
|
|
NS_ENSURE_STATE(mInStream);
|
|
NS_ENSURE_STATE(mUpdateObserver);
|
|
|
|
mInStream = false;
|
|
|
|
mProtocolParser->End();
|
|
|
|
if (NS_SUCCEEDED(mProtocolParser->Status())) {
|
|
if (mProtocolParser->UpdateWaitSec()) {
|
|
mUpdateWaitSec = mProtocolParser->UpdateWaitSec();
|
|
}
|
|
// XXX: Only allow forwards from the initial update?
|
|
const nsTArray<ProtocolParser::ForwardedUpdate>& forwards =
|
|
mProtocolParser->Forwards();
|
|
for (uint32_t i = 0; i < forwards.Length(); i++) {
|
|
const ProtocolParser::ForwardedUpdate& forward = forwards[i];
|
|
mUpdateObserver->UpdateUrlRequested(forward.url, forward.table);
|
|
}
|
|
// Hold on to any TableUpdate objects that were created by the
|
|
// parser.
|
|
mTableUpdates.AppendElements(mProtocolParser->GetTableUpdates());
|
|
mProtocolParser->ForgetTableUpdates();
|
|
|
|
#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
|
|
// The assignment involves no string copy since the source string is
|
|
// sharable.
|
|
mRawTableUpdates = mProtocolParser->GetRawTableUpdates();
|
|
#endif
|
|
} else {
|
|
LOG(
|
|
("nsUrlClassifierDBService::FinishStream Failed to parse the stream "
|
|
"using mProtocolParser."));
|
|
mUpdateStatus = mProtocolParser->Status();
|
|
}
|
|
mUpdateObserver->StreamFinished(mProtocolParser->Status(), 0);
|
|
|
|
if (NS_SUCCEEDED(mUpdateStatus)) {
|
|
if (mProtocolParser->ResetRequested()) {
|
|
mClassifier->ResetTables(Classifier::Clear_All,
|
|
mProtocolParser->TablesToReset());
|
|
}
|
|
}
|
|
|
|
mProtocolParser = nullptr;
|
|
|
|
return mUpdateStatus;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::FinishUpdate() {
|
|
LOG(("nsUrlClassifierDBServiceWorker::FinishUpdate"));
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread(),
|
|
"nsUrlClassifierDBServiceWorker::FinishUpdate "
|
|
"NUST NOT be on the main thread.");
|
|
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
MOZ_ASSERT(!mProtocolParser,
|
|
"Should have been nulled out in FinishStream() "
|
|
"or never created.");
|
|
|
|
NS_ENSURE_STATE(mUpdateObserver);
|
|
|
|
if (NS_FAILED(mUpdateStatus)) {
|
|
LOG(
|
|
("nsUrlClassifierDBServiceWorker::FinishUpdate() Not running "
|
|
"ApplyUpdate() since the update has already failed."));
|
|
mTableUpdates.Clear();
|
|
return NotifyUpdateObserver(mUpdateStatus);
|
|
}
|
|
|
|
if (mTableUpdates.IsEmpty()) {
|
|
LOG(("Nothing to update. Just notify update observer."));
|
|
return NotifyUpdateObserver(NS_OK);
|
|
}
|
|
|
|
RefPtr<nsUrlClassifierDBServiceWorker> self = this;
|
|
nsresult rv = mClassifier->AsyncApplyUpdates(
|
|
mTableUpdates, [self](nsresult aRv) -> void {
|
|
#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
|
|
if (NS_FAILED(aRv) && NS_ERROR_OUT_OF_MEMORY != aRv &&
|
|
NS_ERROR_UC_UPDATE_SHUTDOWNING != aRv) {
|
|
self->mClassifier->DumpRawTableUpdates(self->mRawTableUpdates);
|
|
}
|
|
// Invalidate the raw table updates.
|
|
self->mRawTableUpdates = EmptyCString();
|
|
#endif
|
|
|
|
self->NotifyUpdateObserver(aRv);
|
|
});
|
|
mTableUpdates.Clear(); // Classifier is working on its copy.
|
|
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("Failed to start async update. Notify immediately."));
|
|
NotifyUpdateObserver(rv);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::NotifyUpdateObserver(
|
|
nsresult aUpdateStatus) {
|
|
MOZ_ASSERT(!NS_IsMainThread(),
|
|
"nsUrlClassifierDBServiceWorker::NotifyUpdateObserver "
|
|
"NUST NOT be on the main thread.");
|
|
|
|
LOG(("nsUrlClassifierDBServiceWorker::NotifyUpdateObserver"));
|
|
|
|
// We've either
|
|
// 1) failed starting a download stream
|
|
// 2) succeeded in starting a download stream but failed to obtain
|
|
// table updates
|
|
// 3) succeeded in obtaining table updates but failed to build new
|
|
// tables.
|
|
// 4) succeeded in building new tables but failed to take them.
|
|
// 5) succeeded in taking new tables.
|
|
|
|
mUpdateStatus = aUpdateStatus;
|
|
|
|
nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance();
|
|
if (NS_WARN_IF(!urlUtil)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCString provider;
|
|
// Assume that all the tables in update should have the same provider.
|
|
urlUtil->GetTelemetryProvider(mUpdateTables.SafeElementAt(0, EmptyCString()),
|
|
provider);
|
|
|
|
nsresult updateStatus = mUpdateStatus;
|
|
if (NS_FAILED(mUpdateStatus)) {
|
|
updateStatus =
|
|
NS_ERROR_GET_MODULE(mUpdateStatus) == NS_ERROR_MODULE_URL_CLASSIFIER
|
|
? mUpdateStatus
|
|
: NS_ERROR_UC_UPDATE_UNKNOWN;
|
|
}
|
|
|
|
// Do not record telemetry for testing tables.
|
|
if (!provider.EqualsLiteral(TESTING_TABLE_PROVIDER_NAME)) {
|
|
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR, provider,
|
|
NS_ERROR_GET_CODE(updateStatus));
|
|
}
|
|
|
|
if (!mUpdateObserver) {
|
|
// In the normal shutdown process, CancelUpdate() would NOT be
|
|
// called prior to NotifyUpdateObserver(). However, CancelUpdate()
|
|
// is a public API which can be called in the test case at any point.
|
|
// If the call sequence is FinishUpdate() then CancelUpdate(), the later
|
|
// might be executed before NotifyUpdateObserver() which is triggered
|
|
// by the update thread. In this case, we will get null mUpdateObserver.
|
|
NS_WARNING(
|
|
"CancelUpdate() is called before we asynchronously call "
|
|
"NotifyUpdateObserver() in FinishUpdate().");
|
|
|
|
// The DB cleanup will be done in CancelUpdate() so we can just return.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Null out mUpdateObserver before notifying so that BeginUpdate()
|
|
// becomes available prior to callback.
|
|
nsCOMPtr<nsIUrlClassifierUpdateObserver> updateObserver = nullptr;
|
|
updateObserver.swap(mUpdateObserver);
|
|
|
|
if (NS_SUCCEEDED(mUpdateStatus)) {
|
|
LOG(("Notifying success: %d", mUpdateWaitSec));
|
|
updateObserver->UpdateSuccess(mUpdateWaitSec);
|
|
} else {
|
|
if (LOG_ENABLED()) {
|
|
nsAutoCString errorName;
|
|
mozilla::GetErrorName(mUpdateStatus, errorName);
|
|
LOG(("Notifying error: %s (%" PRIu32 ")", errorName.get(),
|
|
static_cast<uint32_t>(mUpdateStatus)));
|
|
}
|
|
|
|
updateObserver->UpdateError(mUpdateStatus);
|
|
/*
|
|
* mark the tables as spoiled(clear cache in LookupCache), we don't want to
|
|
* block hosts longer than normal because our update failed
|
|
*/
|
|
mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::ResetDatabase() {
|
|
nsresult rv = OpenDb();
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mClassifier->Reset();
|
|
}
|
|
|
|
rv = CloseDb();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::ReloadDatabase() {
|
|
// This will null out mClassifier
|
|
nsresult rv = CloseDb();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Create new mClassifier and load prefixset and completions from disk.
|
|
rv = OpenDb();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::ClearCache() {
|
|
nsTArray<nsCString> tables;
|
|
nsresult rv = mClassifier->ActiveTables(tables);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mClassifier->ResetTables(Classifier::Clear_Cache, tables);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::CancelUpdate() {
|
|
LOG(("nsUrlClassifierDBServiceWorker::CancelUpdate"));
|
|
|
|
if (mUpdateObserver) {
|
|
LOG(("UpdateObserver exists, cancelling"));
|
|
|
|
mUpdateStatus = NS_BINDING_ABORTED;
|
|
|
|
mUpdateObserver->UpdateError(mUpdateStatus);
|
|
|
|
/*
|
|
* mark the tables as spoiled(clear cache in LookupCache), we don't want to
|
|
* block hosts longer than normal because our update failed
|
|
*/
|
|
mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
|
|
|
|
ResetStream();
|
|
ResetUpdate();
|
|
} else {
|
|
LOG(("No UpdateObserver, nothing to cancel"));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate() {
|
|
LOG(("nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate()"));
|
|
|
|
if (mClassifier) {
|
|
mClassifier->FlushAndDisableAsyncUpdate();
|
|
}
|
|
}
|
|
|
|
// Allows the main thread to delete the connection which may be in
|
|
// a background thread.
|
|
// XXX This could be turned into a single shutdown event so the logic
|
|
// is simpler in nsUrlClassifierDBService::Shutdown.
|
|
nsresult nsUrlClassifierDBServiceWorker::CloseDb() {
|
|
if (mClassifier) {
|
|
mClassifier->Close();
|
|
mClassifier = nullptr;
|
|
}
|
|
|
|
// Clear last completion result when close db so we will still cache
|
|
// completion result next time we re-open it.
|
|
mLastResults.Clear();
|
|
|
|
LOG(("urlclassifier db closed\n"));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::PreShutdown() {
|
|
if (mClassifier) {
|
|
// Classifier close will release all lookup caches which may be a
|
|
// time-consuming job. See Bug 1408631.
|
|
mClassifier->Close();
|
|
}
|
|
|
|
// WARNING: nothing we put here should affect an ongoing update thread. When
|
|
// in doubt, put things in Shutdown() instead.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::CacheCompletions(
|
|
const ConstCacheResultArray& aResults) {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
LOG(("nsUrlClassifierDBServiceWorker::CacheCompletions [%p]", this));
|
|
if (!mClassifier) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aResults.Length() == 0) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (IsSameAsLastResults(aResults)) {
|
|
LOG(("Skipping completions that have just been cached already."));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Only cache results for tables that we have, don't take
|
|
// in tables we might accidentally have hit during a completion.
|
|
// This happens due to goog vs googpub lists existing.
|
|
nsTArray<nsCString> tables;
|
|
nsresult rv = mClassifier->ActiveTables(tables);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (LOG_ENABLED()) {
|
|
nsCString s;
|
|
for (size_t i = 0; i < tables.Length(); i++) {
|
|
if (!s.IsEmpty()) {
|
|
s += ",";
|
|
}
|
|
s += tables[i];
|
|
}
|
|
LOG(("Active tables: %s", s.get()));
|
|
}
|
|
|
|
ConstTableUpdateArray updates;
|
|
|
|
for (const auto& result : aResults) {
|
|
bool activeTable = false;
|
|
|
|
for (uint32_t table = 0; table < tables.Length(); table++) {
|
|
if (tables[table].Equals(result->table)) {
|
|
activeTable = true;
|
|
break;
|
|
}
|
|
}
|
|
if (activeTable) {
|
|
UniquePtr<ProtocolParser> pParse;
|
|
if (result->Ver() == CacheResult::V2) {
|
|
pParse.reset(new ProtocolParserV2());
|
|
} else {
|
|
pParse.reset(new ProtocolParserProtobuf());
|
|
}
|
|
|
|
RefPtr<TableUpdate> tu = pParse->GetTableUpdate(result->table);
|
|
|
|
rv = CacheResultToTableUpdate(result, tu);
|
|
if (NS_FAILED(rv)) {
|
|
// We can bail without leaking here because ForgetTableUpdates
|
|
// hasn't been called yet.
|
|
return rv;
|
|
}
|
|
updates.AppendElement(tu);
|
|
pParse->ForgetTableUpdates();
|
|
} else {
|
|
LOG(("Completion received, but table %s is not active, so not caching.",
|
|
result->table.get()));
|
|
}
|
|
}
|
|
|
|
rv = mClassifier->ApplyFullHashes(updates);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mLastResults = aResults.Clone();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::CacheResultToTableUpdate(
|
|
RefPtr<const CacheResult> aCacheResult, RefPtr<TableUpdate> aUpdate) {
|
|
RefPtr<TableUpdateV2> tuV2 = TableUpdate::Cast<TableUpdateV2>(aUpdate);
|
|
if (tuV2) {
|
|
RefPtr<const CacheResultV2> result =
|
|
CacheResult::Cast<const CacheResultV2>(aCacheResult);
|
|
MOZ_ASSERT(result);
|
|
|
|
if (result->miss) {
|
|
return tuV2->NewMissPrefix(result->prefix);
|
|
} else {
|
|
LOG(("CacheCompletion hash %X, Addchunk %d",
|
|
result->completion.ToUint32(), result->addChunk));
|
|
|
|
nsresult rv = tuV2->NewAddComplete(result->addChunk, result->completion);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
return tuV2->NewAddChunk(result->addChunk);
|
|
}
|
|
}
|
|
|
|
RefPtr<TableUpdateV4> tuV4 = TableUpdate::Cast<TableUpdateV4>(aUpdate);
|
|
if (tuV4) {
|
|
RefPtr<const CacheResultV4> result =
|
|
CacheResult::Cast<const CacheResultV4>(aCacheResult);
|
|
MOZ_ASSERT(result);
|
|
|
|
if (LOG_ENABLED()) {
|
|
const FullHashExpiryCache& fullHashes = result->response.fullHashes;
|
|
for (auto iter = fullHashes.ConstIter(); !iter.Done(); iter.Next()) {
|
|
Completion completion;
|
|
completion.Assign(iter.Key());
|
|
LOG(("CacheCompletion(v4) hash %X, CacheExpireTime %" PRId64,
|
|
completion.ToUint32(), iter.Data()));
|
|
}
|
|
}
|
|
|
|
tuV4->NewFullHashResponse(result->prefix, result->response);
|
|
return NS_OK;
|
|
}
|
|
|
|
// tableUpdate object should be either V2 or V4.
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::OpenDb() {
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread");
|
|
// Connection already open, don't do anything.
|
|
if (mClassifier) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv;
|
|
RefPtr<Classifier> classifier = new (fallible) Classifier();
|
|
if (!classifier) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = classifier->Open(*mCacheDir);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mClassifier = classifier;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBServiceWorker::ClearLastResults() {
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
|
|
mLastResults.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBServiceWorker::GetCacheInfo(
|
|
const nsACString& aTable, nsIUrlClassifierCacheInfo** aCache) {
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
|
|
if (!mClassifier) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mClassifier->GetCacheInfo(aTable, aCache);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsUrlClassifierDBServiceWorker::IsSameAsLastResults(
|
|
const ConstCacheResultArray& aResult) const {
|
|
if (mLastResults.Length() != aResult.Length()) {
|
|
return false;
|
|
}
|
|
|
|
bool equal = true;
|
|
for (uint32_t i = 0; i < mLastResults.Length() && equal; i++) {
|
|
RefPtr<const CacheResult> lhs = mLastResults[i];
|
|
RefPtr<const CacheResult> rhs = aResult[i];
|
|
|
|
if (lhs->Ver() != rhs->Ver()) {
|
|
return false;
|
|
}
|
|
|
|
if (lhs->Ver() == CacheResult::V2) {
|
|
equal = *(CacheResult::Cast<const CacheResultV2>(lhs)) ==
|
|
*(CacheResult::Cast<const CacheResultV2>(rhs));
|
|
} else if (lhs->Ver() == CacheResult::V4) {
|
|
equal = *(CacheResult::Cast<const CacheResultV4>(lhs)) ==
|
|
*(CacheResult::Cast<const CacheResultV4>(rhs));
|
|
}
|
|
}
|
|
|
|
return equal;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// nsUrlClassifierLookupCallback
|
|
//
|
|
// This class takes the results of a lookup found on the worker thread
|
|
// and handles any necessary partial hash expansions before calling
|
|
// the client callback.
|
|
|
|
class nsUrlClassifierLookupCallback final
|
|
: public nsIUrlClassifierLookupCallback,
|
|
public nsIUrlClassifierHashCompleterCallback {
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK
|
|
NS_DECL_NSIURLCLASSIFIERHASHCOMPLETERCALLBACK
|
|
|
|
nsUrlClassifierLookupCallback(nsUrlClassifierDBService* dbservice,
|
|
nsIUrlClassifierCallback* c)
|
|
: mDBService(dbservice),
|
|
mResults(nullptr),
|
|
mPendingCompletions(0),
|
|
mCallback(c) {}
|
|
|
|
private:
|
|
~nsUrlClassifierLookupCallback();
|
|
|
|
nsresult HandleResults();
|
|
nsresult ProcessComplete(RefPtr<CacheResult> aCacheResult);
|
|
nsresult CacheMisses();
|
|
|
|
RefPtr<nsUrlClassifierDBService> mDBService;
|
|
UniquePtr<LookupResultArray> mResults;
|
|
|
|
// Completed results to send back to the worker for caching.
|
|
ConstCacheResultArray mCacheResults;
|
|
|
|
uint32_t mPendingCompletions;
|
|
nsCOMPtr<nsIUrlClassifierCallback> mCallback;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsUrlClassifierLookupCallback, nsIUrlClassifierLookupCallback,
|
|
nsIUrlClassifierHashCompleterCallback)
|
|
|
|
nsUrlClassifierLookupCallback::~nsUrlClassifierLookupCallback() {
|
|
if (mCallback) {
|
|
NS_ReleaseOnMainThread("nsUrlClassifierLookupCallback::mCallback",
|
|
mCallback.forget());
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierLookupCallback::LookupComplete(
|
|
UniquePtr<LookupResultArray> results) {
|
|
NS_ASSERTION(
|
|
mResults == nullptr,
|
|
"Should only get one set of results per nsUrlClassifierLookupCallback!");
|
|
|
|
if (!results) {
|
|
HandleResults();
|
|
return NS_OK;
|
|
}
|
|
|
|
mResults = std::move(results);
|
|
|
|
// Check the results entries that need to be completed.
|
|
for (const auto& result : *mResults) {
|
|
// We will complete partial matches and matches that are stale.
|
|
if (!result->Confirmed()) {
|
|
nsCOMPtr<nsIUrlClassifierHashCompleter> completer;
|
|
nsCString gethashUrl;
|
|
nsresult rv;
|
|
nsCOMPtr<nsIUrlListManager> listManager =
|
|
do_GetService("@mozilla.org/url-classifier/listmanager;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = listManager->GetGethashUrl(result->mTableName, gethashUrl);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
LOG(("The match from %s needs to be completed at %s",
|
|
result->mTableName.get(), gethashUrl.get()));
|
|
// gethashUrls may be empty in 2 cases: test tables, and on startup where
|
|
// we may have found a prefix in an existing table before the listmanager
|
|
// has registered the table. In the second case we should not call
|
|
// complete.
|
|
if ((!gethashUrl.IsEmpty() ||
|
|
nsUrlClassifierUtils::IsTestTable(result->mTableName)) &&
|
|
mDBService->GetCompleter(result->mTableName,
|
|
getter_AddRefs(completer))) {
|
|
// Bug 1323953 - Send the first 4 bytes for completion no matter how
|
|
// long we matched the prefix.
|
|
nsresult rv = completer->Complete(result->PartialHash(), gethashUrl,
|
|
result->mTableName, this);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPendingCompletions++;
|
|
}
|
|
} else {
|
|
// For tables with no hash completer, a complete hash match is
|
|
// good enough, we'll consider it is valid.
|
|
if (result->Complete()) {
|
|
result->mConfirmed = true;
|
|
LOG(("Skipping completion in a table without a valid completer (%s).",
|
|
result->mTableName.get()));
|
|
} else {
|
|
NS_WARNING(
|
|
"Partial match in a table without a valid completer, ignoring "
|
|
"partial match.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG(
|
|
("nsUrlClassifierLookupCallback::LookupComplete [%p] "
|
|
"%u pending completions",
|
|
this, mPendingCompletions));
|
|
if (mPendingCompletions == 0) {
|
|
// All results were complete, we're ready!
|
|
HandleResults();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierLookupCallback::CompletionFinished(nsresult status) {
|
|
if (LOG_ENABLED()) {
|
|
nsAutoCString errorName;
|
|
mozilla::GetErrorName(status, errorName);
|
|
LOG(("nsUrlClassifierLookupCallback::CompletionFinished [%p, %s]", this,
|
|
errorName.get()));
|
|
}
|
|
|
|
mPendingCompletions--;
|
|
if (mPendingCompletions == 0) {
|
|
HandleResults();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierLookupCallback::CompletionV2(const nsACString& aCompleteHash,
|
|
const nsACString& aTableName,
|
|
uint32_t aChunkId) {
|
|
LOG(("nsUrlClassifierLookupCallback::Completion [%p, %s, %d]", this,
|
|
PromiseFlatCString(aTableName).get(), aChunkId));
|
|
|
|
MOZ_ASSERT(!StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto")));
|
|
|
|
RefPtr<CacheResultV2> result = new CacheResultV2();
|
|
|
|
result->table = aTableName;
|
|
result->prefix.Assign(aCompleteHash);
|
|
result->completion.Assign(aCompleteHash);
|
|
result->addChunk = aChunkId;
|
|
|
|
return ProcessComplete(result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierLookupCallback::CompletionV4(const nsACString& aPartialHash,
|
|
const nsACString& aTableName,
|
|
uint32_t aNegativeCacheDuration,
|
|
nsIArray* aFullHashes) {
|
|
LOG(("nsUrlClassifierLookupCallback::CompletionV4 [%p, %s, %d]", this,
|
|
PromiseFlatCString(aTableName).get(), aNegativeCacheDuration));
|
|
|
|
MOZ_ASSERT(StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto")));
|
|
|
|
if (!aFullHashes) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (aNegativeCacheDuration > MAXIMUM_NEGATIVE_CACHE_DURATION_SEC) {
|
|
LOG(
|
|
("Negative cache duration too large, clamping it down to"
|
|
"a reasonable value."));
|
|
aNegativeCacheDuration = MAXIMUM_NEGATIVE_CACHE_DURATION_SEC;
|
|
}
|
|
|
|
RefPtr<CacheResultV4> result = new CacheResultV4();
|
|
|
|
int64_t nowSec = PR_Now() / PR_USEC_PER_SEC;
|
|
|
|
result->table = aTableName;
|
|
result->prefix.Assign(aPartialHash);
|
|
result->response.negativeCacheExpirySec = nowSec + aNegativeCacheDuration;
|
|
|
|
// Fill in positive cache entries.
|
|
uint32_t fullHashCount = 0;
|
|
nsresult rv = aFullHashes->GetLength(&fullHashCount);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < fullHashCount; i++) {
|
|
nsCOMPtr<nsIFullHashMatch> match = do_QueryElementAt(aFullHashes, i);
|
|
|
|
nsCString fullHash;
|
|
match->GetFullHash(fullHash);
|
|
|
|
uint32_t duration;
|
|
match->GetCacheDuration(&duration);
|
|
|
|
result->response.fullHashes.Put(fullHash, nowSec + duration);
|
|
}
|
|
|
|
return ProcessComplete(result);
|
|
}
|
|
|
|
nsresult nsUrlClassifierLookupCallback::ProcessComplete(
|
|
RefPtr<CacheResult> aCacheResult) {
|
|
NS_ENSURE_ARG_POINTER(mResults);
|
|
|
|
if (!mCacheResults.AppendElement(aCacheResult, fallible)) {
|
|
// OK if this failed, we just won't cache the item.
|
|
}
|
|
|
|
// Check if this matched any of our results.
|
|
for (const auto& result : *mResults) {
|
|
// Now, see if it verifies a lookup
|
|
if (!result->mNoise && result->mTableName.Equals(aCacheResult->table) &&
|
|
aCacheResult->findCompletion(result->CompleteHash())) {
|
|
result->mProtocolConfirmed = true;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierLookupCallback::HandleResults() {
|
|
if (!mResults) {
|
|
// No results, this URI is clean.
|
|
LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, no results]",
|
|
this));
|
|
return mCallback->HandleEvent(NS_LITERAL_CSTRING(""));
|
|
}
|
|
MOZ_ASSERT(mPendingCompletions == 0,
|
|
"HandleResults() should never be "
|
|
"called while there are pending completions");
|
|
|
|
LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, %zu results]", this,
|
|
mResults->Length()));
|
|
|
|
nsCOMPtr<nsIUrlClassifierClassifyCallback> classifyCallback =
|
|
do_QueryInterface(mCallback);
|
|
|
|
nsTArray<nsCString> tables;
|
|
// Build a stringified list of result tables.
|
|
for (const auto& result : *mResults) {
|
|
// Leave out results that weren't confirmed, as their existence on
|
|
// the list can't be verified. Also leave out randomly-generated
|
|
// noise.
|
|
if (result->mNoise) {
|
|
LOG(("Skipping result %s from table %s (noise)",
|
|
result->PartialHashHex().get(), result->mTableName.get()));
|
|
continue;
|
|
}
|
|
|
|
if (!result->Confirmed()) {
|
|
LOG(("Skipping result %s from table %s (not confirmed)",
|
|
result->PartialHashHex().get(), result->mTableName.get()));
|
|
continue;
|
|
}
|
|
|
|
LOG(("Confirmed result %s from table %s", result->PartialHashHex().get(),
|
|
result->mTableName.get()));
|
|
|
|
if (tables.IndexOf(result->mTableName) == nsTArray<nsCString>::NoIndex) {
|
|
tables.AppendElement(result->mTableName);
|
|
}
|
|
|
|
if (classifyCallback) {
|
|
nsCString fullHashString;
|
|
result->hash.complete.ToString(fullHashString);
|
|
classifyCallback->HandleResult(result->mTableName, fullHashString);
|
|
}
|
|
}
|
|
|
|
// Some parts of this gethash request generated no hits at all.
|
|
// Save the prefixes we checked to prevent repeated requests.
|
|
CacheMisses();
|
|
|
|
// This hands ownership of the cache results array back to the worker
|
|
// thread.
|
|
mDBService->CacheCompletions(mCacheResults);
|
|
mCacheResults.Clear();
|
|
|
|
nsAutoCString tableStr;
|
|
for (uint32_t i = 0; i < tables.Length(); i++) {
|
|
if (i != 0) tableStr.Append(',');
|
|
tableStr.Append(tables[i]);
|
|
}
|
|
|
|
return mCallback->HandleEvent(tableStr);
|
|
}
|
|
|
|
nsresult nsUrlClassifierLookupCallback::CacheMisses() {
|
|
MOZ_ASSERT(mResults);
|
|
|
|
for (const RefPtr<const LookupResult> result : *mResults) {
|
|
// Skip V4 because cache information is already included in the
|
|
// fullhash response so we don't need to manually add it here.
|
|
if (!result->mProtocolV2 || result->Confirmed() || result->mNoise) {
|
|
continue;
|
|
}
|
|
|
|
RefPtr<CacheResultV2> cacheResult = new CacheResultV2();
|
|
|
|
cacheResult->table = result->mTableName;
|
|
cacheResult->prefix = result->hash.fixedLengthPrefix;
|
|
cacheResult->miss = true;
|
|
if (!mCacheResults.AppendElement(cacheResult, fallible)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
struct Provider {
|
|
nsCString name;
|
|
uint8_t priority;
|
|
};
|
|
|
|
// Order matters
|
|
// Provider which is not included in this table has the lowest priority 0
|
|
static const Provider kBuiltInProviders[] = {
|
|
{NS_LITERAL_CSTRING("mozilla"), 1},
|
|
{NS_LITERAL_CSTRING("google4"), 2},
|
|
{NS_LITERAL_CSTRING("google"), 3},
|
|
};
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Helper class for nsIURIClassifier implementation, handle classify result and
|
|
// send back to nsIURIClassifier
|
|
|
|
class nsUrlClassifierClassifyCallback final
|
|
: public nsIUrlClassifierCallback,
|
|
public nsIUrlClassifierClassifyCallback {
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIURLCLASSIFIERCALLBACK
|
|
NS_DECL_NSIURLCLASSIFIERCLASSIFYCALLBACK
|
|
|
|
explicit nsUrlClassifierClassifyCallback(nsIURIClassifierCallback* c)
|
|
: mCallback(c) {}
|
|
|
|
private:
|
|
struct ClassifyMatchedInfo {
|
|
nsCString table;
|
|
nsCString fullhash;
|
|
Provider provider;
|
|
nsresult errorCode;
|
|
};
|
|
|
|
~nsUrlClassifierClassifyCallback() = default;
|
|
|
|
nsCOMPtr<nsIURIClassifierCallback> mCallback;
|
|
nsTArray<ClassifyMatchedInfo> mMatchedArray;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback, nsIUrlClassifierCallback,
|
|
nsIUrlClassifierClassifyCallback)
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables) {
|
|
nsresult response = TablesToResponse(tables);
|
|
ClassifyMatchedInfo* matchedInfo = nullptr;
|
|
|
|
if (NS_FAILED(response)) {
|
|
// Filter all matched info which has correct response
|
|
// In the case multiple tables found, use the higher priority provider
|
|
nsTArray<ClassifyMatchedInfo> matches;
|
|
for (uint32_t i = 0; i < mMatchedArray.Length(); i++) {
|
|
if (mMatchedArray[i].errorCode == response &&
|
|
(!matchedInfo || matchedInfo->provider.priority <
|
|
mMatchedArray[i].provider.priority)) {
|
|
matchedInfo = &mMatchedArray[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
nsCString provider =
|
|
matchedInfo ? matchedInfo->provider.name : EmptyCString();
|
|
nsCString fullhash = matchedInfo ? matchedInfo->fullhash : EmptyCString();
|
|
nsCString table = matchedInfo ? matchedInfo->table : EmptyCString();
|
|
|
|
mCallback->OnClassifyComplete(response, table, provider, fullhash);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierClassifyCallback::HandleResult(const nsACString& aTable,
|
|
const nsACString& aFullHash) {
|
|
LOG(
|
|
("nsUrlClassifierClassifyCallback::HandleResult [%p, table %s full hash "
|
|
"%s]",
|
|
this, PromiseFlatCString(aTable).get(),
|
|
PromiseFlatCString(aFullHash).get()));
|
|
|
|
if (NS_WARN_IF(aTable.IsEmpty()) || NS_WARN_IF(aFullHash.IsEmpty())) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
ClassifyMatchedInfo* matchedInfo = mMatchedArray.AppendElement();
|
|
matchedInfo->table = aTable;
|
|
matchedInfo->fullhash = aFullHash;
|
|
|
|
nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance();
|
|
if (NS_WARN_IF(!urlUtil)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCString provider;
|
|
nsresult rv = urlUtil->GetProvider(aTable, provider);
|
|
|
|
matchedInfo->provider.name = NS_SUCCEEDED(rv) ? provider : EmptyCString();
|
|
matchedInfo->provider.priority = 0;
|
|
for (uint8_t i = 0; i < ArrayLength(kBuiltInProviders); i++) {
|
|
if (kBuiltInProviders[i].name.Equals(matchedInfo->provider.name)) {
|
|
matchedInfo->provider.priority = kBuiltInProviders[i].priority;
|
|
}
|
|
}
|
|
matchedInfo->errorCode = TablesToResponse(aTable);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Proxy class implementation
|
|
|
|
NS_IMPL_ADDREF(nsUrlClassifierDBService)
|
|
NS_IMPL_RELEASE(nsUrlClassifierDBService)
|
|
NS_INTERFACE_MAP_BEGIN(nsUrlClassifierDBService)
|
|
// Only nsIURIClassifier is supported in the content process!
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUrlClassifierDBService,
|
|
XRE_IsParentProcess())
|
|
NS_INTERFACE_MAP_ENTRY(nsIURIClassifier)
|
|
NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierInfo)
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIObserver, XRE_IsParentProcess())
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIClassifier)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
/* static */
|
|
already_AddRefed<nsUrlClassifierDBService>
|
|
nsUrlClassifierDBService::GetInstance(nsresult* result) {
|
|
*result = NS_OK;
|
|
if (!sUrlClassifierDBService) {
|
|
sUrlClassifierDBService = new (fallible) nsUrlClassifierDBService();
|
|
if (!sUrlClassifierDBService) {
|
|
*result = NS_ERROR_OUT_OF_MEMORY;
|
|
return nullptr;
|
|
}
|
|
|
|
*result = sUrlClassifierDBService->Init();
|
|
if (NS_FAILED(*result)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return do_AddRef(sUrlClassifierDBService);
|
|
}
|
|
|
|
nsUrlClassifierDBService::nsUrlClassifierDBService() : mInUpdate(false) {}
|
|
|
|
nsUrlClassifierDBService::~nsUrlClassifierDBService() {
|
|
sUrlClassifierDBService = nullptr;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBService::ReadDisallowCompletionsTablesFromPrefs() {
|
|
nsAutoCString tables;
|
|
|
|
Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, tables);
|
|
Classifier::SplitTables(tables, mDisallowCompletionsTables);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBService::Init() {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Must initialize DB service on main thread");
|
|
|
|
switch (XRE_GetProcessType()) {
|
|
case GeckoProcessType_Default:
|
|
// The parent process is supported.
|
|
break;
|
|
case GeckoProcessType_Content:
|
|
// In a content process, we simply forward all requests to the parent
|
|
// process, so we can skip the initialization steps here. Note that since
|
|
// we never register an observer, Shutdown() will also never be called in
|
|
// the content process.
|
|
return NS_OK;
|
|
default:
|
|
// No other process type is supported!
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
sGethashNoise =
|
|
Preferences::GetUint(GETHASH_NOISE_PREF, GETHASH_NOISE_DEFAULT);
|
|
ReadDisallowCompletionsTablesFromPrefs();
|
|
|
|
// Force nsUrlClassifierUtils loading on main thread.
|
|
if (NS_WARN_IF(!nsUrlClassifierUtils::GetInstance())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Directory providers must also be accessed on the main thread.
|
|
nsresult rv;
|
|
nsCOMPtr<nsIFile> cacheDir;
|
|
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
|
|
getter_AddRefs(cacheDir));
|
|
if (NS_FAILED(rv)) {
|
|
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
|
getter_AddRefs(cacheDir));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Start the background thread.
|
|
rv = NS_NewNamedThread("URL Classifier", &gDbBackgroundThread);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mWorker = new (fallible) nsUrlClassifierDBServiceWorker();
|
|
if (!mWorker) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = mWorker->Init(sGethashNoise, cacheDir, this);
|
|
if (NS_FAILED(rv)) {
|
|
mWorker = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
// Proxy for calling the worker on the background thread
|
|
mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker);
|
|
rv = mWorkerProxy->OpenDb();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// Add an observer for shutdown
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (!observerService) return NS_ERROR_FAILURE;
|
|
|
|
// The application is about to quit
|
|
observerService->AddObserver(this, "quit-application", false);
|
|
observerService->AddObserver(this, "profile-before-change", false);
|
|
|
|
Preferences::AddUintVarCache(&sGethashNoise, GETHASH_NOISE_PREF,
|
|
GETHASH_NOISE_DEFAULT);
|
|
Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsChannelClassifier is the only consumer of this interface.
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
|
|
nsIEventTarget* aEventTarget,
|
|
nsIURIClassifierCallback* c, bool* aResult) {
|
|
NS_ENSURE_ARG(aPrincipal);
|
|
NS_ENSURE_ARG(aResult);
|
|
|
|
if (aPrincipal->IsSystemPrincipal()) {
|
|
*aResult = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
using namespace mozilla::dom;
|
|
|
|
ContentChild* content = ContentChild::GetSingleton();
|
|
MOZ_ASSERT(content);
|
|
|
|
auto actor = static_cast<URLClassifierChild*>(
|
|
content->AllocPURLClassifierChild(IPC::Principal(aPrincipal), aResult));
|
|
MOZ_ASSERT(actor);
|
|
|
|
if (aEventTarget) {
|
|
content->SetEventTargetForActor(actor, aEventTarget);
|
|
}
|
|
|
|
if (!content->SendPURLClassifierConstructor(
|
|
actor, IPC::Principal(aPrincipal), aResult)) {
|
|
*aResult = false;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
actor->SetCallback(c);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsCOMPtr<nsIPermissionManager> permissionManager =
|
|
services::GetPermissionManager();
|
|
if (NS_WARN_IF(!permissionManager)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
uint32_t perm;
|
|
nsresult rv = permissionManager->TestPermissionFromPrincipal(
|
|
aPrincipal, NS_LITERAL_CSTRING("safe-browsing"), &perm);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (perm == nsIPermissionManager::ALLOW_ACTION) {
|
|
*aResult = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTArray<RefPtr<nsIUrlClassifierFeature>> features;
|
|
mozilla::net::UrlClassifierFeatureFactory::GetPhishingProtectionFeatures(
|
|
features);
|
|
if (features.IsEmpty()) {
|
|
*aResult = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
// Casting to BasePrincipal, as we can't get InnerMost URI otherwise
|
|
auto* basePrincipal = BasePrincipal::Cast(aPrincipal);
|
|
rv = basePrincipal->GetURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
|
|
|
// Let's keep the features alive and release them on the correct thread.
|
|
RefPtr<FeatureHolder> holder =
|
|
FeatureHolder::Create(uri, features, nsIUrlClassifierFeature::blacklist);
|
|
if (NS_WARN_IF(!holder)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
uri = NS_GetInnermostURI(uri);
|
|
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
|
|
|
nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance();
|
|
if (NS_WARN_IF(!utilsService)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Canonicalize the url
|
|
nsAutoCString key;
|
|
rv = utilsService->GetKeyForURI(uri, key);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
RefPtr<nsUrlClassifierClassifyCallback> callback =
|
|
new (fallible) nsUrlClassifierClassifyCallback(c);
|
|
if (NS_WARN_IF(!callback)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// The rest is done async.
|
|
rv = LookupURI(key, holder, callback);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
*aResult = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
class ThreatHitReportListener final : public nsIStreamListener {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIREQUESTOBSERVER
|
|
NS_DECL_NSISTREAMLISTENER
|
|
|
|
ThreatHitReportListener() = default;
|
|
|
|
private:
|
|
~ThreatHitReportListener() = default;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ThreatHitReportListener, nsIStreamListener,
|
|
nsIRequestObserver)
|
|
|
|
NS_IMETHODIMP
|
|
ThreatHitReportListener::OnStartRequest(nsIRequest* aRequest) {
|
|
if (!LOG_ENABLED()) {
|
|
return NS_OK; // Nothing to do!
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
|
|
NS_ENSURE_TRUE(httpChannel, NS_OK);
|
|
|
|
nsresult rv, status;
|
|
rv = httpChannel->GetStatus(&status);
|
|
NS_ENSURE_SUCCESS(rv, NS_OK);
|
|
nsAutoCString errorName;
|
|
mozilla::GetErrorName(status, errorName);
|
|
|
|
uint32_t requestStatus;
|
|
rv = httpChannel->GetResponseStatus(&requestStatus);
|
|
NS_ENSURE_SUCCESS(rv, NS_OK);
|
|
|
|
nsAutoCString spec;
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = httpChannel->GetURI(getter_AddRefs(uri));
|
|
if (NS_SUCCEEDED(rv) && uri) {
|
|
uri->GetAsciiSpec(spec);
|
|
}
|
|
nsCOMPtr<nsIURLFormatter> urlFormatter =
|
|
do_GetService("@mozilla.org/toolkit/URLFormatterService;1");
|
|
nsAutoString trimmed;
|
|
rv = urlFormatter->TrimSensitiveURLs(NS_ConvertUTF8toUTF16(spec), trimmed);
|
|
NS_ENSURE_SUCCESS(rv, NS_OK);
|
|
|
|
LOG(
|
|
("ThreatHitReportListener::OnStartRequest "
|
|
"(status=%s, code=%d, uri=%s, this=%p)",
|
|
errorName.get(), requestStatus, NS_ConvertUTF16toUTF8(trimmed).get(),
|
|
this));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ThreatHitReportListener::OnDataAvailable(nsIRequest* aRequest,
|
|
nsIInputStream* aInputStream,
|
|
uint64_t aOffset, uint32_t aCount) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ThreatHitReportListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
|
|
NS_ENSURE_TRUE(httpChannel, aStatus);
|
|
|
|
uint8_t netErrCode =
|
|
NS_FAILED(aStatus) ? mozilla::safebrowsing::NetworkErrorToBucket(aStatus)
|
|
: 0;
|
|
mozilla::Telemetry::Accumulate(
|
|
mozilla::Telemetry::URLCLASSIFIER_THREATHIT_NETWORK_ERROR, netErrCode);
|
|
|
|
uint32_t requestStatus;
|
|
nsresult rv = httpChannel->GetResponseStatus(&requestStatus);
|
|
NS_ENSURE_SUCCESS(rv, aStatus);
|
|
mozilla::Telemetry::Accumulate(
|
|
mozilla::Telemetry::URLCLASSIFIER_THREATHIT_REMOTE_STATUS,
|
|
mozilla::safebrowsing::HTTPStatusToBucket(requestStatus));
|
|
|
|
if (LOG_ENABLED()) {
|
|
nsAutoCString errorName;
|
|
mozilla::GetErrorName(aStatus, errorName);
|
|
|
|
nsAutoCString spec;
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = httpChannel->GetURI(getter_AddRefs(uri));
|
|
if (NS_SUCCEEDED(rv) && uri) {
|
|
uri->GetAsciiSpec(spec);
|
|
}
|
|
nsCOMPtr<nsIURLFormatter> urlFormatter =
|
|
do_GetService("@mozilla.org/toolkit/URLFormatterService;1");
|
|
nsString trimmed;
|
|
rv = urlFormatter->TrimSensitiveURLs(NS_ConvertUTF8toUTF16(spec), trimmed);
|
|
NS_ENSURE_SUCCESS(rv, aStatus);
|
|
|
|
LOG(
|
|
("ThreatHitReportListener::OnStopRequest "
|
|
"(status=%s, code=%d, uri=%s, this=%p)",
|
|
errorName.get(), requestStatus, NS_ConvertUTF16toUTF8(trimmed).get(),
|
|
this));
|
|
}
|
|
|
|
return aStatus;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::SendThreatHitReport(nsIChannel* aChannel,
|
|
const nsACString& aProvider,
|
|
const nsACString& aList,
|
|
const nsACString& aFullHash) {
|
|
NS_ENSURE_ARG_POINTER(aChannel);
|
|
|
|
if (aProvider.IsEmpty()) {
|
|
LOG(("nsUrlClassifierDBService::SendThreatHitReport missing provider"));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (aList.IsEmpty()) {
|
|
LOG(("nsUrlClassifierDBService::SendThreatHitReport missing list"));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (aFullHash.IsEmpty()) {
|
|
LOG(("nsUrlClassifierDBService::SendThreatHitReport missing fullhash"));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsPrintfCString reportUrlPref(
|
|
"browser.safebrowsing.provider.%s.dataSharingURL",
|
|
PromiseFlatCString(aProvider).get());
|
|
|
|
nsCOMPtr<nsIURLFormatter> formatter(
|
|
do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
|
|
if (!formatter) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsString urlStr;
|
|
nsresult rv =
|
|
formatter->FormatURLPref(NS_ConvertUTF8toUTF16(reportUrlPref), urlStr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (urlStr.IsEmpty() || NS_LITERAL_STRING("about:blank").Equals(urlStr)) {
|
|
LOG(("%s is missing a ThreatHit data reporting URL.",
|
|
PromiseFlatCString(aProvider).get()));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance();
|
|
if (NS_WARN_IF(!utilsService)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoCString reportBody;
|
|
rv =
|
|
utilsService->MakeThreatHitReport(aChannel, aList, aFullHash, reportBody);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsCOMPtr<nsIStringInputStream> sis(
|
|
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
|
|
rv = sis->SetData(reportBody.get(), reportBody.Length());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
LOG(("Sending the following ThreatHit report to %s about %s: %s",
|
|
PromiseFlatCString(aProvider).get(), PromiseFlatCString(aList).get(),
|
|
reportBody.get()));
|
|
|
|
nsCOMPtr<nsIURI> reportURI;
|
|
rv = NS_NewURI(getter_AddRefs(reportURI), urlStr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t loadFlags = nsIRequest::LOAD_ANONYMOUS | // no cookies
|
|
nsIChannel::INHIBIT_CACHING |
|
|
nsIChannel::LOAD_BYPASS_CACHE;
|
|
|
|
nsCOMPtr<nsIChannel> reportChannel;
|
|
rv = NS_NewChannel(getter_AddRefs(reportChannel), reportURI,
|
|
nsContentUtils::GetSystemPrincipal(),
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER,
|
|
nullptr, // nsICookieJarSettings
|
|
nullptr, // aPerformanceStorage
|
|
nullptr, // aLoadGroup
|
|
nullptr, loadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = reportChannel->LoadInfo();
|
|
mozilla::OriginAttributes attrs;
|
|
attrs.mFirstPartyDomain.AssignLiteral(NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN);
|
|
loadInfo->SetOriginAttributes(attrs);
|
|
|
|
nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel));
|
|
NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE);
|
|
rv = uploadChannel->SetUploadStream(
|
|
sis, NS_LITERAL_CSTRING("application/x-protobuf"), -1);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel));
|
|
NS_ENSURE_TRUE(httpChannel, NS_ERROR_FAILURE);
|
|
rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
// Disable keepalive.
|
|
rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Connection"),
|
|
NS_LITERAL_CSTRING("close"), false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
RefPtr<ThreatHitReportListener> listener = new ThreatHitReportListener();
|
|
rv = reportChannel->AsyncOpen(listener);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("Failure to send Safe Browsing ThreatHit report"));
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
|
|
const nsACString& aTables,
|
|
nsIUrlClassifierCallback* aCallback) {
|
|
// We don't expect someone with SystemPrincipal calls this API(See Bug
|
|
// 813897).
|
|
MOZ_ASSERT(!aPrincipal->IsSystemPrincipal());
|
|
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsTArray<nsCString> tableArray;
|
|
Classifier::SplitTables(aTables, tableArray);
|
|
|
|
nsCOMPtr<nsIUrlClassifierFeature> feature;
|
|
nsresult rv =
|
|
CreateFeatureWithTables(NS_LITERAL_CSTRING("lookup"), tableArray,
|
|
nsTArray<nsCString>(), getter_AddRefs(feature));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
// Casting to BasePrincipal, as we can't get InnerMost URI otherwise
|
|
auto* basePrincipal = BasePrincipal::Cast(aPrincipal);
|
|
rv = basePrincipal->GetURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
|
|
|
nsTArray<RefPtr<nsIUrlClassifierFeature>> features;
|
|
features.AppendElement(feature.get());
|
|
|
|
// Let's keep the features alive and release them on the correct thread.
|
|
RefPtr<FeatureHolder> holder =
|
|
FeatureHolder::Create(uri, features, nsIUrlClassifierFeature::blacklist);
|
|
if (NS_WARN_IF(!holder)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
uri = NS_GetInnermostURI(uri);
|
|
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
|
|
|
nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance();
|
|
if (NS_WARN_IF(!utilsService)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoCString key;
|
|
// Canonicalize the url
|
|
rv = utilsService->GetKeyForURI(uri, key);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return LookupURI(key, holder, aCallback);
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBService::LookupURI(
|
|
const nsACString& aKey, FeatureHolder* aHolder,
|
|
nsIUrlClassifierCallback* aCallback) {
|
|
MOZ_ASSERT(aHolder);
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// Create an nsUrlClassifierLookupCallback object. This object will
|
|
// take care of confirming partial hash matches if necessary before
|
|
// calling the client's callback.
|
|
nsCOMPtr<nsIUrlClassifierLookupCallback> callback =
|
|
new nsUrlClassifierLookupCallback(this, aCallback);
|
|
|
|
nsCOMPtr<nsIUrlClassifierLookupCallback> proxyCallback =
|
|
new UrlClassifierLookupCallbackProxy(callback);
|
|
|
|
// Queue this lookup and call the lookup function to flush the queue if
|
|
// necessary.
|
|
nsresult rv = mWorker->QueueLookup(aKey, aHolder, proxyCallback);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// This seems to just call HandlePendingLookups.
|
|
nsAutoCString dummy;
|
|
return mWorkerProxy->Lookup(nullptr, dummy, nullptr);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::GetTables(nsIUrlClassifierCallback* c) {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// The proxy callback uses the current thread.
|
|
nsCOMPtr<nsIUrlClassifierCallback> proxyCallback =
|
|
new UrlClassifierCallbackProxy(c);
|
|
|
|
return mWorkerProxy->GetTables(proxyCallback);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::SetHashCompleter(
|
|
const nsACString& tableName, nsIUrlClassifierHashCompleter* completer) {
|
|
if (completer) {
|
|
mCompleters.Put(tableName, completer);
|
|
} else {
|
|
mCompleters.Remove(tableName);
|
|
}
|
|
ClearLastResults();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::ClearLastResults() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->ClearLastResults();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::BeginUpdate(nsIUrlClassifierUpdateObserver* observer,
|
|
const nsACString& updateTables) {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
if (mInUpdate) {
|
|
LOG(("Already updating, not available"));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
if (mWorker->IsBusyUpdating()) {
|
|
// |mInUpdate| used to work well because "notifying update observer"
|
|
// is synchronously done in Worker::FinishUpdate(). Even if the
|
|
// update observer hasn't been notified at this point, we can still
|
|
// dispatch BeginUpdate() since it will NOT be run until the
|
|
// previous Worker::FinishUpdate() returns.
|
|
//
|
|
// However, some tasks in Worker::FinishUpdate() have been moved to
|
|
// another thread. The update observer will NOT be notified when
|
|
// Worker::FinishUpdate() returns. If we only check |mInUpdate|,
|
|
// the following sequence might happen on worker thread:
|
|
//
|
|
// Worker::FinishUpdate() // for update 1
|
|
// Worker::BeginUpdate() // for update 2
|
|
// Worker::NotifyUpdateObserver() // for update 1
|
|
//
|
|
// So, we have to find out a way to reject BeginUpdate() right here
|
|
// if the previous update observer hasn't been notified.
|
|
//
|
|
// Directly probing the worker's state is the most lightweight solution.
|
|
// No lock is required since Worker::BeginUpdate() and
|
|
// Worker::NotifyUpdateObserver() are by nature mutual exclusive.
|
|
// (both run on worker thread.)
|
|
LOG(("The previous update observer hasn't been notified."));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mInUpdate = true;
|
|
|
|
// The proxy observer uses the current thread
|
|
nsCOMPtr<nsIUrlClassifierUpdateObserver> proxyObserver =
|
|
new UrlClassifierUpdateObserverProxy(observer);
|
|
|
|
return mWorkerProxy->BeginUpdate(proxyObserver, updateTables);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::BeginStream(const nsACString& table) {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->BeginStream(table);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::UpdateStream(const nsACString& aUpdateChunk) {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->UpdateStream(aUpdateChunk);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::FinishStream() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->FinishStream();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::FinishUpdate() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
mInUpdate = false;
|
|
|
|
return mWorkerProxy->FinishUpdate();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::CancelUpdate() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
mInUpdate = false;
|
|
|
|
return mWorkerProxy->CancelUpdate();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::ResetDatabase() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
if (mWorker->IsBusyUpdating()) {
|
|
LOG(("Failed to ResetDatabase because of the unfinished update."));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return mWorkerProxy->ResetDatabase();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::ReloadDatabase() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
if (mWorker->IsBusyUpdating()) {
|
|
LOG(("Failed to ReloadDatabase because of the unfinished update."));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return mWorkerProxy->ReloadDatabase();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::ClearCache() {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->ClearCache();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::GetCacheInfo(
|
|
const nsACString& aTable, nsIUrlClassifierGetCacheCallback* aCallback) {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->GetCacheInfo(aTable, aCallback);
|
|
}
|
|
|
|
nsresult nsUrlClassifierDBService::CacheCompletions(
|
|
const ConstCacheResultArray& results) {
|
|
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
return mWorkerProxy->CacheCompletions(results);
|
|
}
|
|
|
|
bool nsUrlClassifierDBService::CanComplete(const nsACString& aTableName) {
|
|
return !mDisallowCompletionsTables.Contains(aTableName);
|
|
}
|
|
|
|
bool nsUrlClassifierDBService::GetCompleter(
|
|
const nsACString& tableName, nsIUrlClassifierHashCompleter** completer) {
|
|
// If we have specified a completer, go ahead and query it. This is only
|
|
// used by tests.
|
|
if (mCompleters.Get(tableName, completer)) {
|
|
return true;
|
|
}
|
|
|
|
if (!CanComplete(tableName)) {
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, call gethash to find the hash completions.
|
|
return NS_SUCCEEDED(
|
|
CallGetService(NS_URLCLASSIFIERHASHCOMPLETER_CONTRACTID, completer));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
|
|
ReadDisallowCompletionsTablesFromPrefs();
|
|
} else if (!strcmp(aTopic, "quit-application")) {
|
|
// Tell the update thread to finish as soon as possible.
|
|
gShuttingDownThread = true;
|
|
|
|
// The code in ::Shutdown() is run on a 'profile-before-change' event and
|
|
// ensures that objects are freed by blocking on this freeing.
|
|
// We can however speed up the shutdown time by using the worker thread to
|
|
// release, in an earlier event, any objects that cannot affect an ongoing
|
|
// update on the update thread.
|
|
PreShutdown();
|
|
} else if (!strcmp(aTopic, "profile-before-change")) {
|
|
gShuttingDownThread = true;
|
|
Shutdown();
|
|
} else {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Post a PreShutdown task to worker thread to release objects without blocking
|
|
// main-thread. Notice that shutdown process may still be blocked by PreShutdown
|
|
// task when ::Shutdown() is executed and synchronously waits for worker thread
|
|
// to finish PreShutdown event.
|
|
nsresult nsUrlClassifierDBService::PreShutdown() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (mWorkerProxy) {
|
|
mWorkerProxy->PreShutdown();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Join the background thread if it exists.
|
|
nsresult nsUrlClassifierDBService::Shutdown() {
|
|
LOG(("shutting down db service\n"));
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (!gDbBackgroundThread) {
|
|
return NS_OK;
|
|
}
|
|
|
|
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_SHUTDOWN_TIME> timer;
|
|
|
|
mCompleters.Clear();
|
|
|
|
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
|
if (prefs) {
|
|
prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this);
|
|
}
|
|
|
|
// 1. Synchronize with worker thread and update thread by
|
|
// *synchronously* dispatching an event to worker thread
|
|
// for shutting down the update thread. The reason not
|
|
// shutting down update thread directly from main thread
|
|
// is to avoid racing for Classifier::mUpdateThread
|
|
// between main thread and the worker thread. (Both threads
|
|
// would access Classifier::mUpdateThread.)
|
|
if (mWorker->IsDBOpened()) {
|
|
using Worker = nsUrlClassifierDBServiceWorker;
|
|
RefPtr<nsIRunnable> r = NewRunnableMethod(
|
|
"nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate", mWorker,
|
|
&Worker::FlushAndDisableAsyncUpdate);
|
|
SyncRunnable::DispatchToThread(gDbBackgroundThread, r);
|
|
}
|
|
// At this point the update thread has been shut down and
|
|
// the worker thread should only have at most one event,
|
|
// which is the callback event.
|
|
|
|
// 2. Send CancelUpdate() event to notify the dangling update.
|
|
// (i.e. BeginUpdate is called but FinishUpdate is not.)
|
|
// and CloseDb() to clear mClassifier. They will be the last two
|
|
// events on the worker thread in the shutdown process.
|
|
DebugOnly<nsresult> rv;
|
|
rv = mWorkerProxy->CancelUpdate();
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to post 'cancel update' event");
|
|
rv = mWorkerProxy->CloseDb();
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to post 'close db' event");
|
|
mWorkerProxy = nullptr;
|
|
|
|
// 3. Invalidate XPCOM APIs by nulling out gDbBackgroundThread
|
|
// since every API checks gDbBackgroundThread first. This has
|
|
// to be done before calling nsIThread.shutdown because it
|
|
// will cause the pending events on the joining thread to
|
|
// be processed.
|
|
nsIThread* backgroundThread = nullptr;
|
|
std::swap(backgroundThread, gDbBackgroundThread);
|
|
|
|
// 4. Wait until the worker thread is down.
|
|
if (backgroundThread) {
|
|
backgroundThread->Shutdown();
|
|
NS_RELEASE(backgroundThread);
|
|
}
|
|
|
|
mWorker = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIThread* nsUrlClassifierDBService::BackgroundThread() {
|
|
return gDbBackgroundThread;
|
|
}
|
|
|
|
// static
|
|
bool nsUrlClassifierDBService::ShutdownHasStarted() {
|
|
return gShuttingDownThread;
|
|
}
|
|
|
|
// static
|
|
nsUrlClassifierDBServiceWorker* nsUrlClassifierDBService::GetWorker() {
|
|
nsresult rv;
|
|
RefPtr<nsUrlClassifierDBService> service =
|
|
nsUrlClassifierDBService::GetInstance(&rv);
|
|
if (!service) {
|
|
return nullptr;
|
|
}
|
|
|
|
return service->mWorker;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures(
|
|
nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures,
|
|
nsIUrlClassifierFeature::listType aListType,
|
|
nsIUrlClassifierFeatureCallback* aCallback) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (gShuttingDownThread) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
|
|
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
|
|
|
// Let's try to use the preferences.
|
|
if (AsyncClassifyLocalWithFeaturesUsingPreferences(uri, aFeatures, aListType,
|
|
aCallback)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance();
|
|
if (NS_WARN_IF(!utilsService)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoCString key;
|
|
// Canonicalize the url
|
|
nsresult rv = utilsService->GetKeyForURI(uri, key);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::ipc;
|
|
|
|
ContentChild* content = ContentChild::GetSingleton();
|
|
if (NS_WARN_IF(!content || content->IsShuttingDown())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
auto actor = new URLClassifierLocalChild();
|
|
|
|
nsTArray<IPCURLClassifierFeature> ipcFeatures;
|
|
for (nsIUrlClassifierFeature* feature : aFeatures) {
|
|
nsAutoCString name;
|
|
rv = feature->GetName(name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
continue;
|
|
}
|
|
|
|
nsTArray<nsCString> tables;
|
|
rv = feature->GetTables(aListType, tables);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
continue;
|
|
}
|
|
|
|
nsAutoCString skipHostList;
|
|
rv = feature->GetSkipHostList(skipHostList);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
continue;
|
|
}
|
|
|
|
ipcFeatures.AppendElement(
|
|
IPCURLClassifierFeature(name, tables, skipHostList));
|
|
}
|
|
|
|
if (!content->SendPURLClassifierLocalConstructor(actor, aURI,
|
|
ipcFeatures)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
actor->SetFeaturesAndCallback(aFeatures, aCallback);
|
|
return NS_OK;
|
|
}
|
|
|
|
using namespace mozilla::Telemetry;
|
|
auto startTime = TimeStamp::Now(); // For telemetry.
|
|
|
|
// Let's keep the features alive and release them on the correct thread.
|
|
RefPtr<FeatureHolder> holder =
|
|
FeatureHolder::Create(aURI, aFeatures, aListType);
|
|
if (NS_WARN_IF(!holder)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
auto worker = mWorker;
|
|
|
|
// Since aCallback will be passed around threads...
|
|
nsMainThreadPtrHandle<nsIUrlClassifierFeatureCallback> callback(
|
|
new nsMainThreadPtrHolder<nsIUrlClassifierFeatureCallback>(
|
|
"nsIURIClassifierFeatureCallback", aCallback));
|
|
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
"nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures",
|
|
[worker, key, holder, callback, startTime]() -> void {
|
|
holder->DoLocalLookup(key, worker);
|
|
|
|
nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
|
|
"nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures",
|
|
[callback, holder, startTime]() -> void {
|
|
// Measure the time diff between calling and callback.
|
|
AccumulateTimeDelta(
|
|
Telemetry::URLCLASSIFIER_ASYNC_CLASSIFYLOCAL_TIME, startTime);
|
|
|
|
nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> results;
|
|
holder->GetResults(results);
|
|
|
|
// |callback| is captured as const value so ...
|
|
auto cb =
|
|
const_cast<nsIUrlClassifierFeatureCallback*>(callback.get());
|
|
cb->OnClassifyComplete(results);
|
|
});
|
|
|
|
NS_DispatchToMainThread(cbRunnable);
|
|
});
|
|
|
|
return gDbBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
bool nsUrlClassifierDBService::AsyncClassifyLocalWithFeaturesUsingPreferences(
|
|
nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures,
|
|
nsIUrlClassifierFeature::listType aListType,
|
|
nsIUrlClassifierFeatureCallback* aCallback) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsAutoCString host;
|
|
nsresult rv = aURI->GetHost(host);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> results;
|
|
|
|
// Let's see if we have special entries set by prefs.
|
|
for (nsIUrlClassifierFeature* feature : aFeatures) {
|
|
bool found = false;
|
|
|
|
nsAutoCString tableName;
|
|
rv = feature->HasHostInPreferences(host, aListType, tableName, &found);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
if (found) {
|
|
MOZ_ASSERT(!tableName.IsEmpty());
|
|
LOG(("URI found in preferences. Table: %s", tableName.get()));
|
|
|
|
RefPtr<mozilla::net::UrlClassifierFeatureResult> result =
|
|
new mozilla::net::UrlClassifierFeatureResult(aURI, feature,
|
|
tableName);
|
|
results.AppendElement(result);
|
|
}
|
|
}
|
|
|
|
if (results.IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// If we have some match using the preferences, we don't need to continue.
|
|
nsCOMPtr<nsIUrlClassifierFeatureCallback> callback(aCallback);
|
|
nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
|
|
"nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures",
|
|
[callback, results = std::move(results)]() {
|
|
callback->OnClassifyComplete(results);
|
|
});
|
|
|
|
NS_DispatchToMainThread(cbRunnable);
|
|
return true;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::GetFeatureByName(const nsACString& aFeatureName,
|
|
nsIUrlClassifierFeature** aFeature) {
|
|
NS_ENSURE_ARG_POINTER(aFeature);
|
|
nsCOMPtr<nsIUrlClassifierFeature> feature =
|
|
mozilla::net::UrlClassifierFeatureFactory::GetFeatureByName(aFeatureName);
|
|
if (NS_WARN_IF(!feature)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
feature.forget(aFeature);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::GetFeatureNames(nsTArray<nsCString>& aArray) {
|
|
mozilla::net::UrlClassifierFeatureFactory::GetFeatureNames(aArray);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsUrlClassifierDBService::CreateFeatureWithTables(
|
|
const nsACString& aName, const nsTArray<nsCString>& aBlacklistTables,
|
|
const nsTArray<nsCString>& aWhitelistTables,
|
|
nsIUrlClassifierFeature** aFeature) {
|
|
NS_ENSURE_ARG_POINTER(aFeature);
|
|
nsCOMPtr<nsIUrlClassifierFeature> feature =
|
|
mozilla::net::UrlClassifierFeatureFactory::CreateFeatureWithTables(
|
|
aName, aBlacklistTables, aWhitelistTables);
|
|
if (NS_WARN_IF(!feature)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
feature.forget(aFeature);
|
|
return NS_OK;
|
|
}
|