From a312dcf860ca87b998c77b3528cfee05a39f897a Mon Sep 17 00:00:00 2001 From: Monica Chew Date: Thu, 18 Dec 2014 10:18:09 -0800 Subject: [PATCH] Bug 1108009 - Make synchronous interface nsIURIClassifier.ClassifyLocal. r=gcp --- netwerk/base/public/nsIURIClassifier.idl | 10 +- .../components/url-classifier/Classifier.cpp | 18 +- .../components/url-classifier/Classifier.h | 4 +- .../nsIUrlClassifierDBService.idl | 4 +- .../nsUrlClassifierDBService.cpp | 213 +++++++++++++----- .../url-classifier/nsUrlClassifierDBService.h | 5 + .../url-classifier/nsUrlClassifierProxies.cpp | 9 + 7 files changed, 200 insertions(+), 63 deletions(-) diff --git a/netwerk/base/public/nsIURIClassifier.idl b/netwerk/base/public/nsIURIClassifier.idl index a8b5920cfbe..f6fd0214cc9 100644 --- a/netwerk/base/public/nsIURIClassifier.idl +++ b/netwerk/base/public/nsIURIClassifier.idl @@ -30,7 +30,7 @@ interface nsIURIClassifierCallback : nsISupports * The URI classifier service checks a URI against lists of phishing * and malware sites. */ -[scriptable, uuid(de4f03cd-1a28-4f51-906b-c54b47a533c5)] +[scriptable, uuid(03d26681-0ef5-4718-9777-33c9e1ee3e32)] interface nsIURIClassifier : nsISupports { /** @@ -54,4 +54,12 @@ interface nsIURIClassifier : nsISupports boolean classify(in nsIPrincipal aPrincipal, in boolean aTrackingProtectionEnabled, in nsIURIClassifierCallback aCallback); + + /** + * Synchronously classify a Principal locally using its URI. This does not + * make network requests. The result is an error code with which the channel + * should be cancelled, or NS_OK if no result was found. + */ + nsresult classifyLocal(in nsIPrincipal aPrincipal, + in boolean aTrackingProtectionEnabled); }; diff --git a/toolkit/components/url-classifier/Classifier.cpp b/toolkit/components/url-classifier/Classifier.cpp index 1ee34737189..aeb1208c8ef 100644 --- a/toolkit/components/url-classifier/Classifier.cpp +++ b/toolkit/components/url-classifier/Classifier.cpp @@ -11,6 +11,7 @@ #include "nsIInputStream.h" #include "nsISeekableStream.h" #include "nsIFile.h" +#include "nsThreadUtils.h" #include "mozilla/Telemetry.h" #include "prlog.h" @@ -54,7 +55,6 @@ Classifier::SplitTables(const nsACString& str, nsTArray& tables) } Classifier::Classifier() - : mFreshTime(45 * 60) { } @@ -144,6 +144,9 @@ Classifier::Open(nsIFile& aCacheDirectory) rv = CreateStoreDirectory(); NS_ENSURE_SUCCESS(rv, rv); + // Classifier keeps its own cryptoHash for doing operations on the background + // thread. Callers can optionally pass in an nsICryptoHash for working on the + // main thread. mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); @@ -216,8 +219,15 @@ Classifier::TableRequest(nsACString& aResult) nsresult Classifier::Check(const nsACString& aSpec, const nsACString& aTables, + uint32_t aFreshnessGuarantee, + nsICryptoHash* aCryptoHash, LookupResultArray& aResults) { + nsCOMPtr cryptoHash = aCryptoHash; + if (!aCryptoHash) { + MOZ_ASSERT(!NS_IsMainThread(), "mCryptoHash must be used on worker thread"); + cryptoHash = mCryptoHash; + } Telemetry::AutoTimer timer; // Get the set of fragments based on the url. This is necessary because we @@ -244,11 +254,11 @@ Classifier::Check(const nsACString& aSpec, // Now check each lookup fragment against the entries in the DB. for (uint32_t i = 0; i < fragments.Length(); i++) { Completion lookupHash; - lookupHash.FromPlaintext(fragments[i], mCryptoHash); + lookupHash.FromPlaintext(fragments[i], cryptoHash); // Get list of host keys to look up Completion hostKey; - rv = LookupCache::GetKey(fragments[i], &hostKey, mCryptoHash); + rv = LookupCache::GetKey(fragments[i], &hostKey, cryptoHash); if (NS_FAILED(rv)) { // Local host on the network. continue; @@ -288,7 +298,7 @@ Classifier::Check(const nsACString& aSpec, result->hash.complete = lookupHash; result->mComplete = complete; - result->mFresh = (age < mFreshTime); + result->mFresh = (age < aFreshnessGuarantee); result->mTableName.Assign(cache->TableName()); } } diff --git a/toolkit/components/url-classifier/Classifier.h b/toolkit/components/url-classifier/Classifier.h index 246b3cd714c..eff8afb4dc7 100644 --- a/toolkit/components/url-classifier/Classifier.h +++ b/toolkit/components/url-classifier/Classifier.h @@ -47,6 +47,8 @@ public: */ nsresult Check(const nsACString& aSpec, const nsACString& tables, + uint32_t aFreshnessGuarantee, + nsICryptoHash* aCryptoHash, LookupResultArray& aResults); /** @@ -61,7 +63,6 @@ public: nsresult MarkSpoiled(nsTArray& aTables); nsresult CacheCompletions(const CacheResultArray& aResults); uint32_t GetHashKey(void) { return mHashKey; } - void SetFreshTime(uint32_t aTime) { mFreshTime = aTime; } /* * Get a bunch of extra prefixes to query for completion * and mask the real entry being requested @@ -102,7 +103,6 @@ private: uint32_t mHashKey; // Stores the last time a given table was updated (seconds). nsDataHashtable mTableFreshness; - uint32_t mFreshTime; }; } diff --git a/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl index 5fae6652768..efe508e7bd6 100644 --- a/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl +++ b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl @@ -185,9 +185,11 @@ interface nsIUrlClassifierDBService : nsISupports * Interface for the actual worker thread. Implementations of this need not * be thread aware and just work on the database. */ -[scriptable, uuid(abcd7978-c304-4a7d-a44c-33c2ed5441e7)] +[scriptable, uuid(b7b505d0-bfa2-44db-abf8-6e2bfc25bbab)] interface nsIUrlClassifierDBServiceWorker : nsIUrlClassifierDBService { + // Open the DB connection + void openDb(); // Provide a way to forcibly close the db connection. void closeDb(); diff --git a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp index 00ae4d32fd9..138565223ab 100644 --- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp @@ -121,6 +121,13 @@ public: // update operations to prevent lookups from blocking for too long. nsresult HandlePendingLookups(); + // Perform a blocking classifier lookup for a given url. Can be called on + // either the main thread or the worker thread. + nsresult DoLocalLookup(const nsACString& spec, + const nsACString& tables, + nsICryptoHash* cryptoHash, + LookupResultArray* results); + private: // No subclassing ~nsUrlClassifierDBServiceWorker(); @@ -128,8 +135,6 @@ private: // Disallow copy constructor nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&); - nsresult OpenDb(); - // Applies the current transaction and resets the update/working times. nsresult ApplyUpdate(); @@ -149,6 +154,7 @@ private: uint32_t aCount, LookupResultArray& results); + // Can only be used on the background thread nsCOMPtr mCryptoHash; nsAutoPtr mClassifier; @@ -241,6 +247,76 @@ nsUrlClassifierDBServiceWorker::QueueLookup(const nsACString& spec, return NS_OK; } +nsresult +nsUrlClassifierDBServiceWorker::DoLocalLookup(const nsACString& spec, + const nsACString& tables, + nsICryptoHash* cryptoHash, + LookupResultArray* results) +{ + LOG(("nsUrlClassifierDBServiceWorker::DoLocalLookup %s (main=%s) %p", + spec.Data(), NS_IsMainThread() ? "true" : "false", this)); + if (!results) { + return NS_ERROR_FAILURE; + } + // Bail if we haven't been initialized on the background thread. + if (!mClassifier) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We ignore failures from Check because we'd rather return the + // results that were found than fail. + mClassifier->Check(spec, tables, gFreshnessGuarantee, cryptoHash, *results); + + LOG(("Found %d results.", results->Length())); + return NS_OK; +} + +static nsresult +TablesToResponse(const nsACString& tables, + bool checkMalware, + bool checkPhishing, + bool checkTracking) +{ + if (checkMalware && + FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) { + return NS_ERROR_MALWARE_URI; + } + if (checkPhishing && + FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) { + return NS_ERROR_PHISHING_URI; + } + if (checkTracking && + FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) { + return NS_ERROR_TRACKING_URI; + } + return NS_OK; +} + +static nsresult +ProcessLookupResults(LookupResultArray* results, + bool checkMalware, + bool checkPhishing, + bool checkTracking) +{ + // Build a stringified list of result tables. + nsTArray tables; + for (uint32_t i = 0; i < results->Length(); i++) { + LookupResult& result = results->ElementAt(i); + MOZ_ASSERT(!result.mNoise, "Lookup results should not have noise added"); + LOG(("Found result from table %s", result.mTableName.get())); + if (tables.IndexOf(result.mTableName) == nsTArray::NoIndex) { + tables.AppendElement(result.mTableName); + } + } + nsAutoCString tableStr; + for (uint32_t i = 0; i < tables.Length(); i++) { + if (i != 0) + tableStr.Append(','); + tableStr.Append(tables[i]); + } + return TablesToResponse(tableStr, checkMalware, checkPhishing, checkTracking); +} + /** * Lookup up a key in the database is a two step process: * @@ -262,13 +338,6 @@ nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec, return NS_ERROR_NOT_INITIALIZED; } - nsresult rv = OpenDb(); - if (NS_FAILED(rv)) { - c->LookupComplete(nullptr); - NS_ERROR("Unable to open SafeBrowsing database."); - return NS_ERROR_FAILURE; - } - #if defined(PR_LOGGING) PRIntervalTime clockStart = 0; if (LOG_ENABLED()) { @@ -282,10 +351,11 @@ nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec, return NS_ERROR_OUT_OF_MEMORY; } - // we ignore failures from Check because we'd rather return the - // results that were found than fail. - mClassifier->SetFreshTime(gFreshnessGuarantee); - mClassifier->Check(spec, tables, *results); + nsresult rv = DoLocalLookup(spec, tables, nullptr, results); + if (NS_FAILED(rv)) { + c->LookupComplete(nullptr); + return rv; + } LOG(("Found %d results.", results->Length())); @@ -457,6 +527,7 @@ 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; @@ -732,13 +803,12 @@ nsUrlClassifierDBServiceWorker::CacheMisses(PrefixArray *results) nsresult nsUrlClassifierDBServiceWorker::OpenDb() { + MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread"); // Connection already open, don't do anything. if (mClassifier) { return NS_OK; } - LOG(("Opening db")); - nsresult rv; mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); @@ -748,8 +818,6 @@ nsUrlClassifierDBServiceWorker::OpenDb() return NS_ERROR_OUT_OF_MEMORY; } - classifier->SetFreshTime(gFreshnessGuarantee); - rv = classifier->Open(*mCacheDir); NS_ENSURE_SUCCESS(rv, rv); @@ -1025,25 +1093,9 @@ NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback, NS_IMETHODIMP nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables) { - // XXX: we should probably have the wardens tell the service which table - // names match with which classification. For now the table names give - // enough information. - nsresult response = NS_OK; - - if (mCheckMalware && - FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) { - response = NS_ERROR_MALWARE_URI; - } else if (mCheckPhishing && - FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) { - response = NS_ERROR_PHISHING_URI; - } else if (mCheckTracking && - FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) { - LOG(("Blocking tracking uri [this=%p]", this)); - response = NS_ERROR_TRACKING_URI; - } - + nsresult response = TablesToResponse(tables, mCheckMalware, + mCheckPhishing, mCheckTracking); mCallback->OnClassifyComplete(response); - return NS_OK; } @@ -1141,6 +1193,7 @@ nsUrlClassifierDBService::Init() if (!gUrlClassifierDbServiceLog) gUrlClassifierDbServiceLog = PR_NewLogModule("UrlClassifierDbService"); #endif + MOZ_ASSERT(NS_IsMainThread(), "Must initialize DB service on main thread"); // Retrieve all the preferences. mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF, @@ -1170,7 +1223,7 @@ nsUrlClassifierDBService::Init() // Force PSM loading on main thread nsresult rv; - nsCOMPtr acryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + mCryptoHashMain = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); // Directory providers must also be accessed on the main thread. @@ -1199,6 +1252,10 @@ nsUrlClassifierDBService::Init() // 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 observerService = @@ -1212,6 +1269,28 @@ nsUrlClassifierDBService::Init() return NS_OK; } +static void BuildTables(bool aTrackingProtectionEnabled, nsCString &tables) +{ + nsAutoCString malware; + // LookupURI takes a comma-separated list already. + Preferences::GetCString(MALWARE_TABLE_PREF, &malware); + if (!malware.IsEmpty()) { + tables.Append(malware); + } + nsAutoCString phishing; + Preferences::GetCString(PHISH_TABLE_PREF, &phishing); + if (!phishing.IsEmpty()) { + tables.Append(','); + tables.Append(phishing); + } + nsAutoCString tracking; + Preferences::GetCString(TRACKING_TABLE_PREF, &tracking); + if (aTrackingProtectionEnabled && !tracking.IsEmpty()) { + tables.Append(','); + tables.Append(tracking); + } +} + // nsChannelClassifier is the only consumer of this interface. NS_IMETHODIMP nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal, @@ -1233,25 +1312,8 @@ nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal, if (!callback) return NS_ERROR_OUT_OF_MEMORY; nsAutoCString tables; - nsAutoCString malware; - // LookupURI takes a comma-separated list already. - Preferences::GetCString(MALWARE_TABLE_PREF, &malware); - if (!malware.IsEmpty()) { - tables.Append(malware); - } - nsAutoCString phishing; - Preferences::GetCString(PHISH_TABLE_PREF, &phishing); - if (!phishing.IsEmpty()) { - tables.Append(','); - tables.Append(phishing); - } - nsAutoCString tracking; - Preferences::GetCString(TRACKING_TABLE_PREF, &tracking); - if (aTrackingProtectionEnabled && !tracking.IsEmpty()) { - LOG(("Looking up third party in tracking table, [cb=%p]", callback.get())); - tables.Append(','); - tables.Append(tracking); - } + BuildTables(aTrackingProtectionEnabled, tables); + nsresult rv = LookupURI(aPrincipal, tables, callback, false, result); if (rv == NS_ERROR_MALFORMED_URI) { *result = false; @@ -1263,6 +1325,47 @@ nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal, return NS_OK; } +NS_IMETHODIMP +nsUrlClassifierDBService::ClassifyLocal(nsIPrincipal* aPrincipal, + bool aTrackingProtectionEnabled, + nsresult* aResponse) +{ + MOZ_ASSERT(NS_IsMainThread(), "ClassifyLocal must be on main thread"); + *aResponse = NS_OK; + nsAutoCString tables; + BuildTables(aTrackingProtectionEnabled, tables); + + nsCOMPtr uri; + nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + uri = NS_GetInnermostURI(uri); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + nsAutoCString key; + // Canonicalize the url + nsCOMPtr utilsService = + do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID); + rv = utilsService->GetKeyForURI(uri, key); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr results(new LookupResultArray()); + if (!results) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // We don't use the proxy, since this is a blocking lookup. In unittests, we + // may not have been initalized, so don't crash. + rv = mWorker->DoLocalLookup(key, tables, mCryptoHashMain, results); + if (NS_SUCCEEDED(rv)) { + rv = ProcessLookupResults(results, mCheckMalware, mCheckPhishing, + mCheckTracking); + *aResponse = rv; + } + return NS_OK; +} + NS_IMETHODIMP nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal, const nsACString& tables, diff --git a/toolkit/components/url-classifier/nsUrlClassifierDBService.h b/toolkit/components/url-classifier/nsUrlClassifierDBService.h index 06be18acb74..9089da63baf 100644 --- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h +++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h @@ -33,6 +33,7 @@ #define COMPLETE_LENGTH 32 class nsUrlClassifierDBServiceWorker; +class nsICryptoHash; class nsIThread; class nsIURI; @@ -117,6 +118,10 @@ private: // Thread that we do the updates on. static nsIThread* gDbBackgroundThread; + + // nsICryptoHash for doing hash operations on the main thread. This is only + // used for nsIURIClassifier.ClassifyLocal + nsCOMPtr mCryptoHashMain; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, NS_URLCLASSIFIERDBSERVICE_CID) diff --git a/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp b/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp index 150d0b2fd5d..fba5ada85be 100644 --- a/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp @@ -143,6 +143,15 @@ UrlClassifierDBServiceWorkerProxy::ResetDatabase() return DispatchToWorkerThread(r); } +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::OpenDb() +{ + nsCOMPtr r = + NS_NewRunnableMethod(mTarget, + &nsIUrlClassifierDBServiceWorker::OpenDb); + return DispatchToWorkerThread(r); +} + NS_IMETHODIMP UrlClassifierDBServiceWorkerProxy::CloseDb() {