Bug 985623: Force url classifier clients to specify which tables to lookup, add a pref to skip hash completion checks (r=gcp)
authorMonica Chew <mmc@mozilla.com>
Thu, 20 Mar 2014 14:25:35 -0700
changeset 174985 e7a44e5b762f
parent 174984 fa098f9fe89c
child 174986 ac696b8baf85
push id41400
push usermchew@mozilla.com
push dateMon, 24 Mar 2014 00:59:08 +0000
treeherdermozilla-inbound@e7a44e5b762f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp
bugs985623
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 985623: Force url classifier clients to specify which tables to lookup, add a pref to skip hash completion checks (r=gcp)
browser/components/safebrowsing/content/test/head.js
modules/libpref/src/init/all.js
toolkit/components/downloads/ApplicationReputation.cpp
toolkit/components/url-classifier/Classifier.cpp
toolkit/components/url-classifier/Classifier.h
toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
toolkit/components/url-classifier/nsUrlClassifierProxies.cpp
toolkit/components/url-classifier/nsUrlClassifierProxies.h
toolkit/components/url-classifier/tests/mochitest/test_classifier.html
toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html
toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html
toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
toolkit/components/url-classifier/tests/unit/test_dbservice.js
toolkit/components/url-classifier/tests/unit/test_digest256.js
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -1,3 +1,5 @@
 // Force SafeBrowsing to be initialized for the tests
+Services.prefs.setCharPref("urlclassifier.malware_table", "test-malware-simple");
+Services.prefs.setCharPref("urlclassifier.phish_table", "test-phish-simple");
 SafeBrowsing.init();
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4383,20 +4383,21 @@ pref("dom.voicemail.enabled", false);
 // Numeric default service id for Voice Mail API calls with |serviceId|
 // parameter omitted.
 pref("dom.voicemail.defaultServiceId", 0);
 
 // DOM Inter-App Communication API.
 pref("dom.inter-app-communication-api.enabled", false);
 
 // The tables used for Safebrowsing phishing and malware checks.
-pref("urlclassifier.malware_table", "goog-malware-shavar");
-pref("urlclassifier.phish_table", "goog-phish-shavar");
+pref("urlclassifier.malware_table", "goog-malware-shavar,test-malware-simple");
+pref("urlclassifier.phish_table", "goog-phish-shavar,test-phish-simple");
 pref("urlclassifier.download_block_table", "");
 pref("urlclassifier.download_allow_table", "");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,goog-downloadwhite-digest256");
 
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
 // Wakelock is disabled by default.
 pref("dom.wakelock.enabled", false);
 
 // The URL of the Firefox Accounts auth server backend
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -268,17 +268,29 @@ PendingDBLookup::LookupSpecInternal(cons
   rv = secMan->GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Check local lists to see if the URI has already been whitelisted or
   // blacklisted.
   LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this));
   nsCOMPtr<nsIUrlClassifierDBService> dbService =
     do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
-  return dbService->Lookup(principal, this);
+  nsAutoCString tables;
+  nsAutoCString allowlist;
+  Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowlist);
+  if (!allowlist.IsEmpty()) {
+    tables.Append(allowlist);
+  }
+  nsAutoCString blocklist;
+  Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blocklist);
+  if (!mAllowlistOnly && !blocklist.IsEmpty()) {
+    tables.Append(",");
+    tables.Append(blocklist);
+  }
+  return dbService->Lookup(principal, tables, this);
 }
 
 NS_IMETHODIMP
 PendingDBLookup::HandleEvent(const nsACString& tables)
 {
   // HandleEvent is guaranteed to call either:
   // 1) PendingLookup::OnComplete if the URL can be classified locally, or
   // 2) PendingLookup::LookupNext if the URL can be cannot classified locally.
--- a/toolkit/components/url-classifier/Classifier.cpp
+++ b/toolkit/components/url-classifier/Classifier.cpp
@@ -27,16 +27,38 @@ extern PRLogModuleInfo *gUrlClassifierDb
 
 #define STORE_DIRECTORY      NS_LITERAL_CSTRING("safebrowsing")
 #define TO_DELETE_DIR_SUFFIX NS_LITERAL_CSTRING("-to_delete")
 #define BACKUP_DIR_SUFFIX    NS_LITERAL_CSTRING("-backup")
 
 namespace mozilla {
 namespace safebrowsing {
 
+void
+Classifier::SplitTables(const nsACString& str, nsTArray<nsCString>& tables)
+{
+  tables.Clear();
+
+  nsACString::const_iterator begin, iter, end;
+  str.BeginReading(begin);
+  str.EndReading(end);
+  while (begin != end) {
+    iter = begin;
+    FindCharInReadable(',', iter, end);
+    nsDependentCSubstring table = Substring(begin,iter);
+    if (!table.IsEmpty()) {
+      tables.AppendElement(Substring(begin, iter));
+    }
+    begin = iter;
+    if (begin != end) {
+      begin++;
+    }
+  }
+}
+
 Classifier::Classifier()
   : mFreshTime(45 * 60)
 {
 }
 
 Classifier::~Classifier()
 {
   Close();
@@ -190,32 +212,35 @@ Classifier::TableRequest(nsACString& aRe
       aResult.Append(subList);
     }
 
     aResult.Append('\n');
   }
 }
 
 nsresult
-Classifier::Check(const nsACString& aSpec, LookupResultArray& aResults)
+Classifier::Check(const nsACString& aSpec,
+                  const nsACString& aTables,
+                  LookupResultArray& aResults)
 {
   Telemetry::AutoTimer<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);
 
   nsTArray<nsCString> activeTables;
-  ActiveTables(activeTables);
+  SplitTables(aTables, activeTables);
 
   nsTArray<LookupCache*> cacheArray;
   for (uint32_t i = 0; i < activeTables.Length(); i++) {
+    LOG(("Checking table %s", activeTables[i].get()));
     LookupCache *cache = GetLookupCache(activeTables[i]);
     if (cache) {
       cacheArray.AppendElement(cache);
     } else {
       return NS_ERROR_FAILURE;
     }
   }
 
--- a/toolkit/components/url-classifier/Classifier.h
+++ b/toolkit/components/url-classifier/Classifier.h
@@ -38,19 +38,21 @@ public:
   void TableRequest(nsACString& aResult);
 
   /*
    * Get all tables that we know about.
    */
   nsresult ActiveTables(nsTArray<nsCString>& aTables);
 
   /**
-   * Check a URL against the database.
+   * Check a URL against the specified tables.
    */
-  nsresult Check(const nsACString& aSpec, LookupResultArray& aResults);
+  nsresult Check(const nsACString& aSpec,
+                 const nsACString& tables,
+                 LookupResultArray& aResults);
 
   /**
    * Apply the table updates in the array.  Takes ownership of
    * the updates in the array and clears it.  Wacky!
    */
   nsresult ApplyUpdates(nsTArray<TableUpdate*>* aUpdates);
   /**
    * Failed update. Spoil the entries so we don't block hosts
@@ -63,16 +65,18 @@ public:
   /*
    * Get a bunch of extra prefixes to query for completion
    * and mask the real entry being requested
    */
   nsresult ReadNoiseEntries(const Prefix& aPrefix,
                             const nsACString& aTableName,
                             uint32_t aCount,
                             PrefixArray* aNoiseEntries);
+  static void SplitTables(const nsACString& str, nsTArray<nsCString>& tables);
+
 private:
   void DropStores();
   nsresult CreateStoreDirectory();
   nsresult SetupPathNames();
   nsresult RecoverBackups();
   nsresult CleanToDelete();
   nsresult BackupTables();
   nsresult RemoveBackupTables();
--- a/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
+++ b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
@@ -61,27 +61,28 @@ interface nsIUrlClassifierUpdateObserver
   void updateSuccess(in unsigned long requestedTimeout);
 };
 
 /**
  * This is a proxy class that is instantiated and called from the JS thread.
  * It provides async methods for querying and updating the database.  As the
  * methods complete, they call the callback function.
  */
-[scriptable, uuid(8a389f21-f821-4e29-9c6b-3de6f33cd7cf)]
+[scriptable, uuid(3f9e61e5-01bd-45d0-8dd2-f1abcd20dbb7)]
 interface nsIUrlClassifierDBService : nsISupports
 {
   /**
-   * Looks up a URI in the database.
+   * Looks up a URI in the specified tables.
    *
    * @param principal: The principal containing the URI to search.
    * @param c: The callback will be called with a comma-separated list
    *        of tables to which the key belongs.
    */
   void lookup(in nsIPrincipal principal,
+              in ACString tables,
               in nsIUrlClassifierCallback c);
 
   /**
    * Lists the tables along with which chunks are available in each table.
    * This list is in the format of the request body:
    *   tablename;chunkdata\n
    *   tablename2;chunkdata2\n
    *
@@ -179,17 +180,17 @@ interface nsIUrlClassifierDBService : ns
    */
   void resetDatabase();
 };
 
 /**
  * Interface for the actual worker thread.  Implementations of this need not
  * be thread aware and just work on the database.
  */
-[scriptable, uuid(0445be75-b114-43ea-89dc-aa16af26e77e)]
+[scriptable, uuid(abcd7978-c304-4a7d-a44c-33c2ed5441e7)]
 interface nsIUrlClassifierDBServiceWorker : nsIUrlClassifierDBService
 {
   // Provide a way to forcibly close the db connection.
   void closeDb();
 
   [noscript]void cacheCompletions(in CacheCompletionArray completions);
   [noscript]void cacheMisses(in PrefixArray misses);
 };
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -68,16 +68,17 @@ PRLogModuleInfo *gUrlClassifierDbService
 
 #define GETHASH_NOISE_PREF      "urlclassifier.gethashnoise"
 #define GETHASH_NOISE_DEFAULT   4
 
 #define MALWARE_TABLE_PREF      "urlclassifier.malware_table"
 #define PHISH_TABLE_PREF        "urlclassifier.phish_table"
 #define DOWNLOAD_BLOCK_TABLE_PREF "urlclassifier.download_block_table"
 #define DOWNLOAD_ALLOW_TABLE_PREF "urlclassifier.download_allow_table"
+#define DISALLOW_COMPLETION_TABLE_PREF "urlclassifier.disallow_completions"
 
 #define CONFIRM_AGE_PREF        "urlclassifier.max-complete-age"
 #define CONFIRM_AGE_DEFAULT_SEC (45 * 60)
 
 class nsUrlClassifierDBServiceWorker;
 
 // Singleton instance.
 static nsUrlClassifierDBService* sUrlClassifierDBService;
@@ -85,50 +86,34 @@ static nsUrlClassifierDBService* sUrlCla
 nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr;
 
 // Once we've committed to shutting down, don't do work in the background
 // thread.
 static bool gShuttingDownThread = false;
 
 static mozilla::Atomic<int32_t> gFreshnessGuarantee(CONFIRM_AGE_DEFAULT_SEC);
 
-static void
-SplitTables(const nsACString& str, nsTArray<nsCString>& tables)
-{
-  tables.Clear();
-
-  nsACString::const_iterator begin, iter, end;
-  str.BeginReading(begin);
-  str.EndReading(end);
-  while (begin != end) {
-    iter = begin;
-    FindCharInReadable(',', iter, end);
-    tables.AppendElement(Substring(begin, iter));
-    begin = iter;
-    if (begin != end)
-      begin++;
-  }
-}
-
 // -------------------------------------------------------------------------
 // Actual worker implemenatation
 class nsUrlClassifierDBServiceWorker MOZ_FINAL :
   public nsIUrlClassifierDBServiceWorker
 {
 public:
   nsUrlClassifierDBServiceWorker();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURLCLASSIFIERDBSERVICE
   NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
 
   nsresult Init(uint32_t aGethashNoise, nsCOMPtr<nsIFile> aCacheDir);
 
   // Queue a lookup for the worker to perform, called in the main thread.
+  // tables is a comma-separated list of tables to query
   nsresult QueueLookup(const nsACString& lookupKey,
+                       const nsACString& tables,
                        nsIUrlClassifierLookupCallback* callback);
 
   // Handle any queued-up lookups.  We call this function during long-running
   // update operations to prevent lookups from blocking for too long.
   nsresult HandlePendingLookups();
 
 private:
   // No subclassing
@@ -144,17 +129,19 @@ private:
 
   // Reset the in-progress update stream
   void ResetStream();
 
   // Reset the in-progress update
   void ResetUpdate();
 
   // Perform a classifier lookup for a given url.
-  nsresult DoLookup(const nsACString& spec, nsIUrlClassifierLookupCallback* c);
+  nsresult DoLookup(const nsACString& spec,
+                    const nsACString& tables,
+                    nsIUrlClassifierLookupCallback* c);
 
   nsresult AddNoise(const Prefix aPrefix,
                     const nsCString tableName,
                     uint32_t aCount,
                     LookupResultArray& results);
 
   nsCOMPtr<nsICryptoHash> mCryptoHash;
 
@@ -187,16 +174,17 @@ private:
   // Pending lookups are stored in a queue for processing.  The queue
   // is protected by mPendingLookupLock.
   Mutex mPendingLookupLock;
 
   class PendingLookup {
   public:
     TimeStamp mStartTime;
     nsCString mKey;
+    nsCString mTables;
     nsCOMPtr<nsIUrlClassifierLookupCallback> mCallback;
   };
 
   // list of pending lookups
   nsTArray<PendingLookup> mPendingLookups;
 };
 
 NS_IMPL_ISUPPORTS2(nsUrlClassifierDBServiceWorker,
@@ -226,26 +214,28 @@ nsUrlClassifierDBServiceWorker::Init(uin
 
   ResetUpdate();
 
   return NS_OK;
 }
 
 nsresult
 nsUrlClassifierDBServiceWorker::QueueLookup(const nsACString& spec,
+                                            const nsACString& tables,
                                             nsIUrlClassifierLookupCallback* callback)
 {
   MutexAutoLock lock(mPendingLookupLock);
 
   PendingLookup* lookup = mPendingLookups.AppendElement();
   if (!lookup) return NS_ERROR_OUT_OF_MEMORY;
 
   lookup->mStartTime = TimeStamp::Now();
   lookup->mKey = spec;
   lookup->mCallback = callback;
+  lookup->mTables = tables;
 
   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
@@ -253,16 +243,17 @@ nsUrlClassifierDBServiceWorker::QueueLoo
  *    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,
+                                         const nsACString& tables,
                                          nsIUrlClassifierLookupCallback* c)
 {
   if (gShuttingDownThread) {
     c->LookupComplete(nullptr);
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsresult rv = OpenDb();
@@ -283,17 +274,17 @@ nsUrlClassifierDBServiceWorker::DoLookup
   if (!results) {
     c->LookupComplete(nullptr);
     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, *results);
+  mClassifier->Check(spec, tables, *results);
 
   LOG(("Found %d results.", results->Length()));
 
 
 #if defined(PR_LOGGING)
   if (LOG_ENABLED()) {
     PRIntervalTime clockEnd = PR_IntervalNow();
     LOG(("query took %dms\n",
@@ -331,17 +322,17 @@ nsresult
 nsUrlClassifierDBServiceWorker::HandlePendingLookups()
 {
   MutexAutoLock lock(mPendingLookupLock);
   while (mPendingLookups.Length() > 0) {
     PendingLookup lookup = mPendingLookups[0];
     mPendingLookups.RemoveElementAt(0);
     {
       MutexAutoUnlock unlock(mPendingLookupLock);
-      DoLookup(lookup.mKey, lookup.mCallback);
+      DoLookup(lookup.mKey, lookup.mTables, lookup.mCallback);
     }
     double lookupTime = (TimeStamp::Now() - lookup.mStartTime).ToMilliseconds();
     Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LOOKUP_TIME,
                           static_cast<uint32_t>(lookupTime));
   }
 
   return NS_OK;
 }
@@ -373,16 +364,17 @@ nsUrlClassifierDBServiceWorker::AddNoise
   }
 
   return NS_OK;
 }
 
 // Lookup a key in the db.
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal,
+                                       const nsACString& aTables,
                                        nsIUrlClassifierCallback* c)
 {
   return HandlePendingLookups();
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c)
 {
@@ -442,17 +434,17 @@ nsUrlClassifierDBServiceWorker::BeginUpd
   nsresult rv = OpenDb();
   if (NS_FAILED(rv)) {
     NS_ERROR("Unable to open SafeBrowsing database");
     return NS_ERROR_FAILURE;
   }
 
   mUpdateStatus = NS_OK;
   mUpdateObserver = observer;
-  SplitTables(tables, mUpdateTables);
+  Classifier::SplitTables(tables, mUpdateTables);
 
   return NS_OK;
 }
 
 // Called from the stream updater.
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
 {
@@ -1096,26 +1088,30 @@ nsUrlClassifierDBService::Init()
   gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
     CONFIRM_AGE_DEFAULT_SEC);
   mGethashTables.AppendElement(Preferences::GetCString(PHISH_TABLE_PREF));
   mGethashTables.AppendElement(Preferences::GetCString(MALWARE_TABLE_PREF));
   mGethashTables.AppendElement(Preferences::GetCString(
     DOWNLOAD_BLOCK_TABLE_PREF));
   mGethashTables.AppendElement(Preferences::GetCString(
     DOWNLOAD_ALLOW_TABLE_PREF));
+  nsCString tables;
+  Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, &tables);
+  Classifier::SplitTables(tables, mDisallowCompletionsTables);
 
   // Do we *really* need to be able to change all of these at runtime?
   Preferences::AddStrongObserver(this, CHECK_MALWARE_PREF);
   Preferences::AddStrongObserver(this, CHECK_PHISHING_PREF);
   Preferences::AddStrongObserver(this, GETHASH_NOISE_PREF);
   Preferences::AddStrongObserver(this, CONFIRM_AGE_PREF);
   Preferences::AddStrongObserver(this, PHISH_TABLE_PREF);
   Preferences::AddStrongObserver(this, MALWARE_TABLE_PREF);
   Preferences::AddStrongObserver(this, DOWNLOAD_BLOCK_TABLE_PREF);
   Preferences::AddStrongObserver(this, DOWNLOAD_ALLOW_TABLE_PREF);
+  Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF);
 
   // Force PSM loading on main thread
   nsresult rv;
   nsCOMPtr<nsICryptoHash> acryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Directory providers must also be accessed on the main thread.
   nsCOMPtr<nsIFile> cacheDir;
@@ -1151,16 +1147,17 @@ nsUrlClassifierDBService::Init()
     return NS_ERROR_FAILURE;
 
   observerService->AddObserver(this, "profile-before-change", false);
   observerService->AddObserver(this, "xpcom-shutdown-threads", false);
 
   return NS_OK;
 }
 
+// nsChannelClassifier is the only consumer of this interface.
 NS_IMETHODIMP
 nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
                                    nsIURIClassifierCallback* c,
                                    bool* result)
 {
   NS_ENSURE_ARG(aPrincipal);
   NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
 
@@ -1168,39 +1165,53 @@ nsUrlClassifierDBService::Classify(nsIPr
     *result = false;
     return NS_OK;
   }
 
   nsRefPtr<nsUrlClassifierClassifyCallback> callback =
     new nsUrlClassifierClassifyCallback(c, mCheckMalware, mCheckPhishing);
   if (!callback) return NS_ERROR_OUT_OF_MEMORY;
 
-  nsresult rv = LookupURI(aPrincipal, callback, false, result);
+  nsAutoCString tables;
+  nsAutoCString malware;
+  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);
+  }
+  nsresult rv = LookupURI(aPrincipal, tables, callback, false, result);
   if (rv == NS_ERROR_MALFORMED_URI) {
     *result = false;
     // The URI had no hostname, don't try to classify it.
     return NS_OK;
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
+                                 const nsACString& tables,
                                  nsIUrlClassifierCallback* c)
 {
   NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
 
   bool dummy;
-  return LookupURI(aPrincipal, c, true, &dummy);
+  return LookupURI(aPrincipal, tables, c, true, &dummy);
 }
 
 nsresult
 nsUrlClassifierDBService::LookupURI(nsIPrincipal* aPrincipal,
+                                    const nsACString& tables,
                                     nsIUrlClassifierCallback* c,
                                     bool forceLookup,
                                     bool *didLookup)
 {
   NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_ARG(aPrincipal);
 
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
@@ -1257,21 +1268,22 @@ nsUrlClassifierDBService::LookupURI(nsIP
   if (!callback)
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsCOMPtr<nsIUrlClassifierLookupCallback> proxyCallback =
     new UrlClassifierLookupCallbackProxy(callback);
 
   // Queue this lookup and call the lookup function to flush the queue if
   // necessary.
-  rv = mWorker->QueueLookup(key, proxyCallback);
+  rv = mWorker->QueueLookup(key, tables, proxyCallback);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // This seems to just call HandlePendingLookups.
-  return mWorkerProxy->Lookup(nullptr, nullptr);
+  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.
@@ -1380,24 +1392,30 @@ nsUrlClassifierDBService::CacheMisses(Pr
 
   return mWorkerProxy->CacheMisses(results);
 }
 
 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 (!mGethashTables.Contains(tableName)) {
+  // If we don't know about this table at all, or are disallowing completions
+  // for it, skip completion checks.
+  if (!mGethashTables.Contains(tableName) ||
+      mDisallowCompletionsTables.Contains(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)
 {
@@ -1418,16 +1436,21 @@ nsUrlClassifierDBService::Observe(nsISup
       // Just read everything again.
       mGethashTables.Clear();
       mGethashTables.AppendElement(Preferences::GetCString(PHISH_TABLE_PREF));
       mGethashTables.AppendElement(Preferences::GetCString(MALWARE_TABLE_PREF));
       mGethashTables.AppendElement(Preferences::GetCString(
         DOWNLOAD_BLOCK_TABLE_PREF));
       mGethashTables.AppendElement(Preferences::GetCString(
         DOWNLOAD_ALLOW_TABLE_PREF));
+    } else if (NS_LITERAL_STRING(DISALLOW_COMPLETION_TABLE_PREF).Equals(aData)) {
+      mDisallowCompletionsTables.Clear();
+      nsCString tables;
+      Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, &tables);
+      Classifier::SplitTables(tables, mDisallowCompletionsTables);
     } else if (NS_LITERAL_STRING(CONFIRM_AGE_PREF).Equals(aData)) {
       gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
         CONFIRM_AGE_DEFAULT_SEC);
     }
   } else if (!strcmp(aTopic, "profile-before-change") ||
              !strcmp(aTopic, "xpcom-shutdown-threads")) {
     Shutdown();
   } else {
@@ -1451,16 +1474,17 @@ nsUrlClassifierDBService::Shutdown()
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
     prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
     prefs->RemoveObserver(PHISH_TABLE_PREF, this);
     prefs->RemoveObserver(MALWARE_TABLE_PREF, this);
     prefs->RemoveObserver(DOWNLOAD_BLOCK_TABLE_PREF, this);
     prefs->RemoveObserver(DOWNLOAD_ALLOW_TABLE_PREF, this);
+    prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this);
     prefs->RemoveObserver(CONFIRM_AGE_PREF, this);
   }
 
   DebugOnly<nsresult> rv;
   // First close the db connection.
   if (mWorker) {
     rv = mWorkerProxy->CancelUpdate();
     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post cancel update event");
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -66,16 +66,17 @@ public:
 private:
   // No subclassing
   ~nsUrlClassifierDBService();
 
   // Disallow copy constructor
   nsUrlClassifierDBService(nsUrlClassifierDBService&);
 
   nsresult LookupURI(nsIPrincipal* aPrincipal,
+                     const nsACString& tables,
                      nsIUrlClassifierCallback* c,
                      bool forceCheck, bool *didCheck);
 
   // Close db connection and join the background thread if it exists.
   nsresult Shutdown();
 
   // Check if the key is on a known-clean host.
   nsresult CheckClean(const nsACString &lookupKey,
@@ -98,15 +99,18 @@ private:
   // CancelUpdate()/FinishUpdate().  This is used to prevent competing
   // updates, not to determine whether an update is still being
   // processed.
   bool mInUpdate;
 
   // The list of tables that can use the default hash completer object.
   nsTArray<nsCString> mGethashTables;
 
+  // The list of tables that should never be hash completed.
+  nsTArray<nsCString> mDisallowCompletionsTables;
+
   // Thread that we do the updates on.
   static nsIThread* gDbBackgroundThread;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, NS_URLCLASSIFIERDBSERVICE_CID)
 
 #endif // nsUrlClassifierDBService_h_
--- a/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp
@@ -18,26 +18,28 @@ DispatchToWorkerThread(nsIRunnable* r)
   return t->Dispatch(r, NS_DISPATCH_NORMAL);
 }
 
 NS_IMPL_ISUPPORTS1(UrlClassifierDBServiceWorkerProxy,
                               nsIUrlClassifierDBServiceWorker)
 
 NS_IMETHODIMP
 UrlClassifierDBServiceWorkerProxy::Lookup(nsIPrincipal* aPrincipal,
+                                          const nsACString& aTables,
                                           nsIUrlClassifierCallback* aCB)
 {
-  nsCOMPtr<nsIRunnable> r = new LookupRunnable(mTarget, aPrincipal, aCB);
+  nsCOMPtr<nsIRunnable> r = new LookupRunnable(mTarget, aPrincipal, aTables,
+                                               aCB);
   return DispatchToWorkerThread(r);
 }
 
 NS_IMETHODIMP
 UrlClassifierDBServiceWorkerProxy::LookupRunnable::Run()
 {
-  (void) mTarget->Lookup(mPrincipal, mCB);
+  (void) mTarget->Lookup(mPrincipal, mLookupTables, mCB);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UrlClassifierDBServiceWorkerProxy::GetTables(nsIUrlClassifierCallback* aCB)
 {
   nsCOMPtr<nsIRunnable> r = new GetTablesRunnable(mTarget, aCB);
   return DispatchToWorkerThread(r);
--- a/toolkit/components/url-classifier/nsUrlClassifierProxies.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.h
@@ -29,27 +29,30 @@ public:
   NS_DECL_NSIURLCLASSIFIERDBSERVICE
   NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
 
   class LookupRunnable : public nsRunnable
   {
   public:
     LookupRunnable(nsIUrlClassifierDBServiceWorker* aTarget,
                    nsIPrincipal* aPrincipal,
+                   const nsACString& aTables,
                    nsIUrlClassifierCallback* aCB)
       : mTarget(aTarget)
       , mPrincipal(aPrincipal)
+      , mLookupTables(aTables)
       , mCB(aCB)
     { }
 
     NS_DECL_NSIRUNNABLE
 
   private:
     nsCOMPtr<nsIUrlClassifierDBServiceWorker> mTarget;
     nsCOMPtr<nsIPrincipal> mPrincipal;
+    nsCString mLookupTables;
     nsCOMPtr<nsIUrlClassifierCallback> mCB;
   };
 
   class GetTablesRunnable : public nsRunnable
   {
   public:
     GetTablesRunnable(nsIUrlClassifierDBServiceWorker* aTarget,
                       nsIUrlClassifierCallback* aCB)
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
@@ -1,17 +1,16 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test the URI Classifier</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
-<body onload="doUpdate(testUpdate);">
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 
 <script class="testbody" type="text/javascript">
 
 var Cc = SpecialPowers.Cc;
@@ -56,16 +55,21 @@ function doUpdate(update) {
 
   dbService.beginUpdate(listener, "test-malware-simple", "");
   dbService.beginStream("", "");
   dbService.updateStream(update);
   dbService.finishStream();
   dbService.finishUpdate();
 }
 
+SpecialPowers.pushPrefEnv(
+  {"set" : [["urlclassifier.malware_table", "test-malware-simple"],
+            ["urlclassifier.phish_table", "test-phish-simple"]]},
+  function() { doUpdate(testUpdate); });
+
 // Expected finish() call is in "classifierFrame.html".
 SimpleTest.waitForExplicitFinish();
 
 </script>
 
 </pre>
 <iframe id="testFrame" onload=""></iframe>
 </body>
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html
@@ -1,17 +1,16 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test the URI Classifier</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
-<body onload="doUpdate(testUpdate);">
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 
 <script class="testbody" type="text/javascript">
 
 var Cc = SpecialPowers.Cc;
@@ -68,16 +67,21 @@ function onmessage(event)
   if (pieces[0] == "finish") {
     SimpleTest.finish();
     return;
   }
 
   is(pieces[0], "success", pieces[1]);
 }
 
+SpecialPowers.pushPrefEnv(
+  {"set" : [["urlclassifier.malware_table", "test-malware-simple"],
+            ["urlclassifier.phish_table", "test-phish-simple"]]},
+  function() { doUpdate(testUpdate); });
+
 window.addEventListener("message", onmessage, false);
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 
 </pre>
 <iframe id="testFrame" onload=""></iframe>
--- a/toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html
@@ -14,16 +14,16 @@
 <script type="text/javascript">
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 
 var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
                 .getService(Ci.nsIUrlClassifierDBService);
 
-dbService.lookup(document.nodePrincipal, function(arg) {});
+dbService.lookup(document.nodePrincipal, "", function(arg) {});
 
 ok(true, "lookup() didn't crash");
 
 </script>
 </pre>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
@@ -26,16 +26,19 @@ var secMan = Cc["@mozilla.org/scriptsecu
 var prefBranch = Cc["@mozilla.org/preferences-service;1"].
                  getService(Ci.nsIPrefBranch);
 prefBranch.setIntPref("urlclassifier.gethashnoise", 0);
 
 // Enable malware/phishing checking for tests
 prefBranch.setBoolPref("browser.safebrowsing.malware.enabled", true);
 prefBranch.setBoolPref("browser.safebrowsing.enabled", true);
 
+// Enable all completions for tests
+prefBranch.setCharPref("urlclassifier.disallow_completions", "");
+
 function delFile(name) {
   try {
     // Delete a previously created sqlite file
     var file = dirSvc.get('ProfLD', Ci.nsIFile);
     file.append(name);
     if (file.exists())
       file.remove(false);
   } catch(e) {
@@ -48,16 +51,18 @@ function cleanUp() {
   delFile("safebrowsing/test-phish-simple.sbstore");
   delFile("safebrowsing/test-malware-simple.sbstore");
   delFile("safebrowsing/test-phish-simple.cache");
   delFile("safebrowsing/test-malware-simple.cache");
   delFile("safebrowsing/test-phish-simple.pset");
   delFile("safebrowsing/test-malware-simple.pset");
 }
 
+var allTables = "test-phish-simple,test-malware-simple";
+
 var dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(Ci.nsIUrlClassifierDBService);
 var streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
                     .getService(Ci.nsIUrlClassifierStreamUpdater);
 
 
 /*
  * Builds an update from an object that looks like:
  *{ "test-phish-simple" : [{
@@ -195,21 +200,21 @@ tableData : function(expectedTables, cb)
 checkUrls: function(urls, expected, cb)
 {
   // work with a copy of the list.
   urls = urls.slice(0);
   var doLookup = function() {
     if (urls.length > 0) {
       var fragment = urls.shift();
       var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + fragment, null, null));
-      dbservice.lookup(principal,
-                       function(arg) {
-                         do_check_eq(expected, arg);
-                         doLookup();
-                       }, true);
+      dbservice.lookup(principal, allTables,
+                                function(arg) {
+                                  do_check_eq(expected, arg);
+                                  doLookup();
+                                }, true);
     } else {
       cb();
     }
   };
   doLookup();
 },
 
 urlsDontExist: function(urls, cb)
--- a/toolkit/components/url-classifier/tests/unit/test_dbservice.js
+++ b/toolkit/components/url-classifier/tests/unit/test_dbservice.js
@@ -91,17 +91,17 @@ function testFailure(arg) {
 }
 
 function checkNoHost()
 {
   // Looking up a no-host uri such as a data: uri should throw an exception.
   var exception;
   try {
     var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("data:text/html,<b>test</b>", null, null));
-    dbservice.lookup(principal);
+    dbservice.lookup(principal, allTables);
 
     exception = false;
   } catch(e) {
     exception = true;
   }
   do_check_true(exception);
 
   do_test_finished();
@@ -110,17 +110,17 @@ function checkNoHost()
 function tablesCallbackWithoutSub(tables)
 {
   var parts = tables.split("\n");
   parts.sort();
 
   // there's a leading \n here because splitting left an empty string
   // after the trailing newline, which will sort first
   do_check_eq(parts.join("\n"),
-              "\ntesting-malware-simple;a:1\ntesting-phish-simple;a:2");
+              "\ntest-malware-simple;a:1\ntest-phish-simple;a:2");
 
   checkNoHost();
 }
 
 
 function expireSubSuccess(result) {
   dbservice.getTables(tablesCallbackWithoutSub);
 }
@@ -128,22 +128,22 @@ function expireSubSuccess(result) {
 function tablesCallbackWithSub(tables)
 {
   var parts = tables.split("\n");
   parts.sort();
 
   // there's a leading \n here because splitting left an empty string
   // after the trailing newline, which will sort first
   do_check_eq(parts.join("\n"),
-              "\ntesting-malware-simple;a:1\ntesting-phish-simple;a:2:s:3");
+              "\ntest-malware-simple;a:1\ntest-phish-simple;a:2:s:3");
 
   // verify that expiring a sub chunk removes its name from the list
   var data =
     "n:1000\n" +
-    "i:testing-phish-simple\n" +
+    "i:test-phish-simple\n" +
     "sd:3\n";
 
   doSimpleUpdate(data, expireSubSuccess, testFailure);
 }
 
 function checkChunksWithSub()
 {
   dbservice.getTables(tablesCallbackWithSub);
@@ -152,74 +152,74 @@ function checkChunksWithSub()
 function checkDone() {
   if (--numExpecting == 0)
     checkChunksWithSub();
 }
 
 function phishExists(result) {
   dumpn("phishExists: " + result);
   try {
-    do_check_true(result.indexOf("testing-phish-simple") != -1);
+    do_check_true(result.indexOf("test-phish-simple") != -1);
   } finally {
     checkDone();
   }
 }
 
 function phishDoesntExist(result) {
   dumpn("phishDoesntExist: " + result);
   try {
-    do_check_true(result.indexOf("testing-phish-simple") == -1);
+    do_check_true(result.indexOf("test-phish-simple") == -1);
   } finally {
     checkDone();
   }
 }
 
 function malwareExists(result) {
   dumpn("malwareExists: " + result);
 
   try {
-    do_check_true(result.indexOf("testing-malware-simple") != -1);
+    do_check_true(result.indexOf("test-malware-simple") != -1);
   } finally {
     checkDone();
   }
 }
 
 function checkState()
 {
   numExpecting = 0;
 
   for (var key in phishExpected) {
     var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + key, null, null));
-    dbservice.lookup(principal, phishExists, true);
+    dbservice.lookup(principal, allTables, phishExists, true);
     numExpecting++;
   }
 
   for (var key in phishUnexpected) {
     var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + key, null, null));
-    dbservice.lookup(principal, phishDoesntExist, true);
+    dbservice.lookup(principal, allTables, phishDoesntExist, true);
     numExpecting++;
   }
 
   for (var key in malwareExpected) {
     var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + key, null, null));
-    dbservice.lookup(principal, malwareExists, true);
+    dbservice.lookup(principal, allTables, malwareExists, true);
     numExpecting++;
   }
 }
 
 function testSubSuccess(result)
 {
   do_check_eq(result, "1000");
   checkState();
 }
 
 function do_subs() {
   var data =
     "n:1000\n" +
-    "i:testing-phish-simple\n" +
+    "i:test-phish-simple\n" +
     "s:3:32:" + chunk3Sub.length + "\n" +
     chunk3Sub + "\n" +
     "ad:1\n" +
     "ad:4-6\n";
 
   doSimpleUpdate(data, testSubSuccess, testFailure);
 }
 
@@ -231,28 +231,28 @@ function testAddSuccess(arg) {
 
 function do_adds() {
   // This test relies on the fact that only -regexp tables are ungzipped,
   // and only -hash tables are assumed to be pre-md5'd.  So we use
   // a 'simple' table type to get simple hostname-per-line semantics.
 
   var data =
     "n:1000\n" +
-    "i:testing-phish-simple\n" +
+    "i:test-phish-simple\n" +
     "a:1:32:" + chunk1.length + "\n" +
     chunk1 + "\n" +
     "a:2:32:" + chunk2.length + "\n" +
     chunk2 + "\n" +
     "a:4:32:" + chunk4.length + "\n" +
     chunk4 + "\n" +
     "a:5:32:" + chunk5.length + "\n" +
     chunk5 + "\n" +
     "a:6:32:" + chunk6.length + "\n" +
     chunk6 + "\n" +
-    "i:testing-malware-simple\n" +
+    "i:test-malware-simple\n" +
     "a:1:32:" + chunk2.length + "\n" +
       chunk2 + "\n";
 
   doSimpleUpdate(data, testAddSuccess, testFailure);
 }
 
 function run_test() {
   do_adds();
--- a/toolkit/components/url-classifier/tests/unit/test_digest256.js
+++ b/toolkit/components/url-classifier/tests/unit/test_digest256.js
@@ -120,25 +120,27 @@ add_test(function test_update() {
     "goog-downloadwhite-digest256",
     "goog-downloadwhite-digest256;\n",
     updateSuccess, handleError, handleError);
 });
 
 add_test(function test_url_not_whitelisted() {
   let uri = createURI("http://example.com");
   let principal = gSecMan.getNoAppCodebasePrincipal(uri);
-  gDbService.lookup(principal, function handleEvent(aEvent) {
-    // This URI is not on any lists.
-    do_check_eq("", aEvent);
-    run_next_test();
-  });
+  gDbService.lookup(principal, "goog-downloadwhite-digest256",
+    function handleEvent(aEvent) {
+      // This URI is not on any lists.
+      do_check_eq("", aEvent);
+      run_next_test();
+    });
 });
 
 add_test(function test_url_whitelisted() {
   // Hash of "whitelisted.com/" (canonicalized URL) is:
   // 93CA5F48E15E9861CD37C2D95DB43D23CC6E6DE5C3F8FA6E8BE66F97CC518907
   let uri = createURI("http://whitelisted.com");
   let principal = gSecMan.getNoAppCodebasePrincipal(uri);
-  gDbService.lookup(principal, function handleEvent(aEvent) {
-    do_check_eq("goog-downloadwhite-digest256", aEvent);
-    run_next_test();
-  });
+  gDbService.lookup(principal, "goog-downloadwhite-digest256",
+    function handleEvent(aEvent) {
+      do_check_eq("goog-downloadwhite-digest256", aEvent);
+      run_next_test();
+    });
 });