Bug 1108009 - Make synchronous interface nsIURIClassifier.ClassifyLocal. r=gcp

This commit is contained in:
Monica Chew 2014-12-18 10:18:09 -08:00
parent 5c9ad68afc
commit a312dcf860
7 changed files with 200 additions and 63 deletions

View File

@ -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);
};

View File

@ -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<nsCString>& 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<nsICryptoHash> cryptoHash = aCryptoHash;
if (!aCryptoHash) {
MOZ_ASSERT(!NS_IsMainThread(), "mCryptoHash must be used on worker thread");
cryptoHash = mCryptoHash;
}
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_CHECK_TIME> 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());
}
}

View File

@ -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<nsCString>& 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<nsCStringHashKey, int64_t> mTableFreshness;
uint32_t mFreshTime;
};
}

View File

@ -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();

View File

@ -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<nsICryptoHash> mCryptoHash;
nsAutoPtr<Classifier> 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<nsCString> 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<nsCString>::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<nsICryptoHash> 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<nsIObserverService> 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<nsIURI> 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<nsIUrlClassifierUtils> utilsService =
do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
rv = utilsService->GetKeyForURI(uri, key);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoPtr<LookupResultArray> 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,

View File

@ -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<nsICryptoHash> mCryptoHashMain;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, NS_URLCLASSIFIERDBSERVICE_CID)

View File

@ -143,6 +143,15 @@ UrlClassifierDBServiceWorkerProxy::ResetDatabase()
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::OpenDb()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::OpenDb);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CloseDb()
{