forked from mirrors/gecko-dev
		
	 84c9845881
			
		
	
	
		84c9845881
		
	
	
	
	
		
			
			MozReview-Commit-ID: LNtFOVMVw2U --HG-- extra : rebase_source : 8d60d4e49389c4a27e8ea431a9e3a4bce66ff969
		
			
				
	
	
		
			1226 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1226 lines
		
	
	
	
		
			34 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 "Classifier.h"
 | |
| #include "LookupCacheV4.h"
 | |
| #include "nsIPrefBranch.h"
 | |
| #include "nsIPrefService.h"
 | |
| #include "nsISimpleEnumerator.h"
 | |
| #include "nsIRandomGenerator.h"
 | |
| #include "nsIInputStream.h"
 | |
| #include "nsISeekableStream.h"
 | |
| #include "nsIFile.h"
 | |
| #include "nsNetCID.h"
 | |
| #include "nsPrintfCString.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "mozilla/SyncRunnable.h"
 | |
| #include "mozilla/Base64.h"
 | |
| #include "mozilla/Unused.h"
 | |
| 
 | |
| // MOZ_LOG=UrlClassifierDbService:5
 | |
| extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
 | |
| #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
 | |
| #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
 | |
| 
 | |
| #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")
 | |
| 
 | |
| #define METADATA_SUFFIX      NS_LITERAL_CSTRING(".metadata")
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace safebrowsing {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // A scoped-clearer for nsTArray<TableUpdate*>.
 | |
| // The owning elements will be deleted and the array itself
 | |
| // will be cleared on exiting the scope.
 | |
| class ScopedUpdatesClearer {
 | |
| public:
 | |
|   explicit ScopedUpdatesClearer(nsTArray<TableUpdate*> *aUpdates)
 | |
|     : mUpdatesArrayRef(aUpdates)
 | |
|   {
 | |
|     for (auto update : *aUpdates) {
 | |
|       mUpdatesPointerHolder.AppendElement(update);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ~ScopedUpdatesClearer()
 | |
|   {
 | |
|     mUpdatesArrayRef->Clear();
 | |
|   }
 | |
| 
 | |
| private:
 | |
|   nsTArray<TableUpdate*>* mUpdatesArrayRef;
 | |
|   nsTArray<nsAutoPtr<TableUpdate>> mUpdatesPointerHolder;
 | |
| };
 | |
| 
 | |
| } // End of unnamed namespace.
 | |
| 
 | |
| 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++;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static nsresult
 | |
| DeriveProviderFromPref(const nsACString& aTableName, nsCString& aProviderName)
 | |
| {
 | |
|   // Check all preferences "browser.safebrowsing.provider.[provider].list"
 | |
|   // to see which one contains |aTableName|.
 | |
| 
 | |
|   nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
 | |
|   NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE);
 | |
|   nsCOMPtr<nsIPrefBranch> prefBranch;
 | |
|   nsresult rv = prefs->GetBranch("browser.safebrowsing.provider.",
 | |
|                                   getter_AddRefs(prefBranch));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // We've got a pref branch for "browser.safebrowsing.provider.".
 | |
|   // Enumerate all children prefs and parse providers.
 | |
|   uint32_t childCount;
 | |
|   char** childArray;
 | |
|   rv = prefBranch->GetChildList("", &childCount, &childArray);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Collect providers from childArray.
 | |
|   nsTHashtable<nsCStringHashKey> providers;
 | |
|   for (uint32_t i = 0; i < childCount; i++) {
 | |
|     nsCString child(childArray[i]);
 | |
|     auto dotPos = child.FindChar('.');
 | |
|     if (dotPos < 0) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsDependentCSubstring provider = Substring(child, 0, dotPos);
 | |
| 
 | |
|     providers.PutEntry(provider);
 | |
|   }
 | |
|   NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray);
 | |
| 
 | |
|   // Now we have all providers. Check which one owns |aTableName|.
 | |
|   // e.g. The owning lists of provider "google" is defined in
 | |
|   // "browser.safebrowsing.provider.google.lists".
 | |
|   for (auto itr = providers.Iter(); !itr.Done(); itr.Next()) {
 | |
|     auto entry = itr.Get();
 | |
|     nsCString provider(entry->GetKey());
 | |
|     nsPrintfCString owninListsPref("%s.lists", provider.get());
 | |
| 
 | |
|     nsXPIDLCString owningLists;
 | |
|     nsresult rv = prefBranch->GetCharPref(owninListsPref.get(),
 | |
|                                           getter_Copies(owningLists));
 | |
|     if (NS_FAILED(rv)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // We've got the owning lists (represented as string) of |provider|.
 | |
|     // Parse the string and see if |aTableName| is included.
 | |
|     nsTArray<nsCString> tables;
 | |
|     Classifier::SplitTables(owningLists, tables);
 | |
|     if (tables.Contains(aTableName)) {
 | |
|       aProviderName = provider;
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| // Lookup the provider name by table name on non-main thread.
 | |
| // On main thread, just call DeriveProviderFromPref() instead
 | |
| // but DeriveProviderFromPref is supposed to used internally.
 | |
| static nsCString
 | |
| GetProviderByTableName(const nsACString& aTableName)
 | |
| {
 | |
|   MOZ_ASSERT(!NS_IsMainThread(), "GetProviderByTableName MUST be called "
 | |
|                                  "on non-main thread.");
 | |
|   nsCString providerName;
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([&aTableName,
 | |
|                                                     &providerName] () -> void {
 | |
|     nsresult rv = DeriveProviderFromPref(aTableName, providerName);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       LOG(("No provider found for %s", nsCString(aTableName).get()));
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
 | |
|   SyncRunnable::DispatchToThread(mainThread, r);
 | |
| 
 | |
|   return providerName;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::GetPrivateStoreDirectory(nsIFile* aRootStoreDirectory,
 | |
|                                      const nsACString& aTableName,
 | |
|                                      nsIFile** aPrivateStoreDirectory)
 | |
| {
 | |
|   NS_ENSURE_ARG_POINTER(aPrivateStoreDirectory);
 | |
| 
 | |
|   if (!StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto"))) {
 | |
|     // Only V4 table names (ends with '-proto') would be stored
 | |
|     // to per-provider sub-directory.
 | |
|     nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCString providerName = GetProviderByTableName(aTableName);
 | |
|   if (providerName.IsEmpty()) {
 | |
|     // When failing to get provider, just store in the root folder.
 | |
|     nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIFile> providerDirectory;
 | |
| 
 | |
|   // Clone first since we are gonna create a new directory.
 | |
|   nsresult rv = aRootStoreDirectory->Clone(getter_AddRefs(providerDirectory));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Append the provider name to the root store directory.
 | |
|   rv = providerDirectory->AppendNative(providerName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Ensure existence of the provider directory.
 | |
|   bool dirExists;
 | |
|   rv = providerDirectory->Exists(&dirExists);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (!dirExists) {
 | |
|     LOG(("Creating private directory for %s", nsCString(aTableName).get()));
 | |
|     rv = providerDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|     providerDirectory.forget(aPrivateStoreDirectory);
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // Store directory exists. Check if it's a directory.
 | |
|   bool isDir;
 | |
|   rv = providerDirectory->IsDirectory(&isDir);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (!isDir) {
 | |
|     return NS_ERROR_FILE_DESTINATION_NOT_DIR;
 | |
|   }
 | |
| 
 | |
|   providerDirectory.forget(aPrivateStoreDirectory);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| Classifier::Classifier()
 | |
| {
 | |
| }
 | |
| 
 | |
| Classifier::~Classifier()
 | |
| {
 | |
|   Close();
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::SetupPathNames()
 | |
| {
 | |
|   // Get the root directory where to store all the databases.
 | |
|   nsresult rv = mCacheDirectory->Clone(getter_AddRefs(mRootStoreDirectory));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mRootStoreDirectory->AppendNative(STORE_DIRECTORY);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Make sure LookupCaches (which are persistent and survive updates)
 | |
|   // are reading/writing in the right place. We will be moving their
 | |
|   // files "underneath" them during backup/restore.
 | |
|   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
 | |
|     mLookupCaches[i]->UpdateRootDirHandle(mRootStoreDirectory);
 | |
|   }
 | |
| 
 | |
|   // Directory where to move a backup before an update.
 | |
|   rv = mCacheDirectory->Clone(getter_AddRefs(mBackupDirectory));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mBackupDirectory->AppendNative(STORE_DIRECTORY + BACKUP_DIR_SUFFIX);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Directory where to move the backup so we can atomically
 | |
|   // delete (really move) it.
 | |
|   rv = mCacheDirectory->Clone(getter_AddRefs(mToDeleteDirectory));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mToDeleteDirectory->AppendNative(STORE_DIRECTORY + TO_DELETE_DIR_SUFFIX);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::CreateStoreDirectory()
 | |
| {
 | |
|   // Ensure the safebrowsing directory exists.
 | |
|   bool storeExists;
 | |
|   nsresult rv = mRootStoreDirectory->Exists(&storeExists);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (!storeExists) {
 | |
|     rv = mRootStoreDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   } else {
 | |
|     bool storeIsDir;
 | |
|     rv = mRootStoreDirectory->IsDirectory(&storeIsDir);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|     if (!storeIsDir)
 | |
|       return NS_ERROR_FILE_DESTINATION_NOT_DIR;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::Open(nsIFile& aCacheDirectory)
 | |
| {
 | |
|   // Remember the Local profile directory.
 | |
|   nsresult rv = aCacheDirectory.Clone(getter_AddRefs(mCacheDirectory));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Create the handles to the update and backup directories.
 | |
|   rv = SetupPathNames();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Clean up any to-delete directories that haven't been deleted yet.
 | |
|   rv = CleanToDelete();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Check whether we have an incomplete update and recover from the
 | |
|   // backup if so.
 | |
|   rv = RecoverBackups();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Make sure the main store directory exists.
 | |
|   rv = CreateStoreDirectory();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Build the list of know urlclassifier lists
 | |
|   // XXX: Disk IO potentially on the main thread during startup
 | |
|   RegenActiveTables();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::Close()
 | |
| {
 | |
|   DropStores();
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::Reset()
 | |
| {
 | |
|   DropStores();
 | |
| 
 | |
|   mRootStoreDirectory->Remove(true);
 | |
|   mBackupDirectory->Remove(true);
 | |
|   mToDeleteDirectory->Remove(true);
 | |
| 
 | |
|   CreateStoreDirectory();
 | |
| 
 | |
|   mTableFreshness.Clear();
 | |
|   RegenActiveTables();
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::ResetTables(ClearType aType, const nsTArray<nsCString>& aTables)
 | |
| {
 | |
|   for (uint32_t i = 0; i < aTables.Length(); i++) {
 | |
|     LOG(("Resetting table: %s", aTables[i].get()));
 | |
|     // Spoil this table by marking it as no known freshness
 | |
|     mTableFreshness.Remove(aTables[i]);
 | |
|     LookupCache *cache = GetLookupCache(aTables[i]);
 | |
|     if (cache) {
 | |
|       // Remove any cached Completes for this table if clear type is Clear_Cache
 | |
|       if (aType == Clear_Cache) {
 | |
|         cache->ClearCache();
 | |
|       } else {
 | |
|         cache->ClearAll();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Clear on-disk database if clear type is Clear_All
 | |
|   if (aType == Clear_All) {
 | |
|     DeleteTables(mRootStoreDirectory, aTables);
 | |
| 
 | |
|     RegenActiveTables();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::DeleteTables(nsIFile* aDirectory, const nsTArray<nsCString>& aTables)
 | |
| {
 | |
|   nsCOMPtr<nsISimpleEnumerator> entries;
 | |
|   nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
 | |
|   NS_ENSURE_SUCCESS_VOID(rv);
 | |
| 
 | |
|   bool hasMore;
 | |
|   while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
 | |
|     nsCOMPtr<nsISupports> supports;
 | |
|     rv = entries->GetNext(getter_AddRefs(supports));
 | |
|     NS_ENSURE_SUCCESS_VOID(rv);
 | |
| 
 | |
|     nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
 | |
|     NS_ENSURE_TRUE_VOID(file);
 | |
| 
 | |
|     // If |file| is a directory, recurse to find its entries as well.
 | |
|     bool isDirectory;
 | |
|     if (NS_FAILED(file->IsDirectory(&isDirectory))) {
 | |
|       continue;
 | |
|     }
 | |
|     if (isDirectory) {
 | |
|       DeleteTables(file, aTables);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsCString leafName;
 | |
|     rv = file->GetNativeLeafName(leafName);
 | |
|     NS_ENSURE_SUCCESS_VOID(rv);
 | |
| 
 | |
|     leafName.Truncate(leafName.RFind("."));
 | |
|     if (aTables.Contains(leafName)) {
 | |
|       if (NS_FAILED(file->Remove(false))) {
 | |
|         NS_WARNING(nsPrintfCString("Fail to remove file %s from the disk",
 | |
|                                    leafName.get()).get());
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS_VOID(rv);
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::AbortUpdateAndReset(const nsCString& aTable)
 | |
| {
 | |
|   LOG(("Abort updating table %s.", aTable.get()));
 | |
| 
 | |
|   // ResetTables will clear both in-memory & on-disk data.
 | |
|   ResetTables(Clear_All, nsTArray<nsCString> { aTable });
 | |
| 
 | |
|   // Remove the backup and delete directory since we are aborting
 | |
|   // from an update.
 | |
|   Unused << RemoveBackupTables();
 | |
|   Unused << CleanToDelete();
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::TableRequest(nsACString& aResult)
 | |
| {
 | |
|   // Generating v2 table info.
 | |
|   nsTArray<nsCString> tables;
 | |
|   ActiveTables(tables);
 | |
|   for (uint32_t i = 0; i < tables.Length(); i++) {
 | |
|     HashStore store(tables[i], mRootStoreDirectory);
 | |
| 
 | |
|     nsresult rv = store.Open();
 | |
|     if (NS_FAILED(rv))
 | |
|       continue;
 | |
| 
 | |
|     aResult.Append(store.TableName());
 | |
|     aResult.Append(';');
 | |
| 
 | |
|     ChunkSet &adds = store.AddChunks();
 | |
|     ChunkSet &subs = store.SubChunks();
 | |
| 
 | |
|     if (adds.Length() > 0) {
 | |
|       aResult.AppendLiteral("a:");
 | |
|       nsAutoCString addList;
 | |
|       adds.Serialize(addList);
 | |
|       aResult.Append(addList);
 | |
|     }
 | |
| 
 | |
|     if (subs.Length() > 0) {
 | |
|       if (adds.Length() > 0)
 | |
|         aResult.Append(':');
 | |
|       aResult.AppendLiteral("s:");
 | |
|       nsAutoCString subList;
 | |
|       subs.Serialize(subList);
 | |
|       aResult.Append(subList);
 | |
|     }
 | |
| 
 | |
|     aResult.Append('\n');
 | |
|   }
 | |
| 
 | |
|   // Load meta data from *.metadata files in the root directory.
 | |
|   // Specifically for v4 tables.
 | |
|   nsCString metadata;
 | |
|   nsresult rv = LoadMetadata(mRootStoreDirectory, metadata);
 | |
|   NS_ENSURE_SUCCESS_VOID(rv);
 | |
|   aResult.Append(metadata);
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::Check(const nsACString& aSpec,
 | |
|                   const nsACString& aTables,
 | |
|                   uint32_t aFreshnessGuarantee,
 | |
|                   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;
 | |
|   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;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // 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);
 | |
| 
 | |
|     if (LOG_ENABLED()) {
 | |
|       nsAutoCString checking;
 | |
|       lookupHash.ToHexString(checking);
 | |
|       LOG(("Checking fragment %s, hash %s (%X)", fragments[i].get(),
 | |
|            checking.get(), lookupHash.ToUint32()));
 | |
|     }
 | |
| 
 | |
|     for (uint32_t i = 0; i < cacheArray.Length(); i++) {
 | |
|       LookupCache *cache = cacheArray[i];
 | |
|       bool has, complete;
 | |
|       rv = cache->Has(lookupHash, &has, &complete);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       if (has) {
 | |
|         LookupResult *result = aResults.AppendElement();
 | |
|         if (!result)
 | |
|           return NS_ERROR_OUT_OF_MEMORY;
 | |
| 
 | |
|         int64_t age;
 | |
|         bool found = mTableFreshness.Get(cache->TableName(), &age);
 | |
|         if (!found) {
 | |
|           age = 24 * 60 * 60; // just a large number
 | |
|         } else {
 | |
|           int64_t now = (PR_Now() / PR_USEC_PER_SEC);
 | |
|           age = now - age;
 | |
|         }
 | |
| 
 | |
|         LOG(("Found a result in %s: %s (Age: %Lds)",
 | |
|              cache->TableName().get(),
 | |
|              complete ? "complete." : "Not complete.",
 | |
|              age));
 | |
| 
 | |
|         result->hash.complete = lookupHash;
 | |
|         result->mComplete = complete;
 | |
|         result->mFresh = (age < aFreshnessGuarantee);
 | |
|         result->mTableName.Assign(cache->TableName());
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::ApplyUpdates(nsTArray<TableUpdate*>* aUpdates)
 | |
| {
 | |
|   Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_UPDATE_TIME> timer;
 | |
| 
 | |
|   PRIntervalTime clockStart = 0;
 | |
|   if (LOG_ENABLED()) {
 | |
|     clockStart = PR_IntervalNow();
 | |
|   }
 | |
| 
 | |
|   nsresult rv;
 | |
| 
 | |
|   {
 | |
|     ScopedUpdatesClearer scopedUpdatesClearer(aUpdates);
 | |
| 
 | |
|     LOG(("Backup before update."));
 | |
| 
 | |
|     rv = BackupTables();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     LOG(("Applying %d table updates.", aUpdates->Length()));
 | |
| 
 | |
|     for (uint32_t i = 0; i < aUpdates->Length(); i++) {
 | |
|       // Previous UpdateHashStore() may have consumed this update..
 | |
|       if ((*aUpdates)[i]) {
 | |
|         // Run all updates for one table
 | |
|         nsCString updateTable(aUpdates->ElementAt(i)->TableName());
 | |
| 
 | |
|         if (TableUpdate::Cast<TableUpdateV2>((*aUpdates)[i])) {
 | |
|           rv = UpdateHashStore(aUpdates, updateTable);
 | |
|         } else {
 | |
|           rv = UpdateTableV4(aUpdates, updateTable);
 | |
|         }
 | |
| 
 | |
|         if (NS_FAILED(rv)) {
 | |
|           if (rv != NS_ERROR_OUT_OF_MEMORY) {
 | |
|             AbortUpdateAndReset(updateTable);
 | |
|           }
 | |
|           return rv;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   } // End of scopedUpdatesClearer scope.
 | |
| 
 | |
|   rv = RegenActiveTables();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   LOG(("Cleaning up backups."));
 | |
| 
 | |
|   // Move the backup directory away (signaling the transaction finished
 | |
|   // successfully). This is atomic.
 | |
|   rv = RemoveBackupTables();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Do the actual deletion of the backup files.
 | |
|   rv = CleanToDelete();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   LOG(("Done applying updates."));
 | |
| 
 | |
|   if (LOG_ENABLED()) {
 | |
|     PRIntervalTime clockEnd = PR_IntervalNow();
 | |
|     LOG(("update took %dms\n",
 | |
|          PR_IntervalToMilliseconds(clockEnd - clockStart)));
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::ApplyFullHashes(nsTArray<TableUpdate*>* aUpdates)
 | |
| {
 | |
|   LOG(("Applying %d table gethashes.", aUpdates->Length()));
 | |
| 
 | |
|   ScopedUpdatesClearer scopedUpdatesClearer(aUpdates);
 | |
|   for (uint32_t i = 0; i < aUpdates->Length(); i++) {
 | |
|     TableUpdate *update = aUpdates->ElementAt(i);
 | |
| 
 | |
|     nsresult rv = UpdateCache(update);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     aUpdates->ElementAt(i) = nullptr;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| int64_t
 | |
| Classifier::GetLastUpdateTime(const nsACString& aTableName)
 | |
| {
 | |
|   int64_t age;
 | |
|   bool found = mTableFreshness.Get(aTableName, &age);
 | |
|   return found ? (age * PR_MSEC_PER_SEC) : 0;
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::SetLastUpdateTime(const nsACString &aTable,
 | |
|                               uint64_t updateTime)
 | |
| {
 | |
|   LOG(("Marking table %s as last updated on %u",
 | |
|        PromiseFlatCString(aTable).get(), updateTime));
 | |
|   mTableFreshness.Put(aTable, updateTime / PR_MSEC_PER_SEC);
 | |
| }
 | |
| 
 | |
| void
 | |
| Classifier::DropStores()
 | |
| {
 | |
|   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
 | |
|     delete mLookupCaches[i];
 | |
|   }
 | |
|   mLookupCaches.Clear();
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::RegenActiveTables()
 | |
| {
 | |
|   mActiveTablesCache.Clear();
 | |
| 
 | |
|   nsTArray<nsCString> foundTables;
 | |
|   ScanStoreDir(foundTables);
 | |
| 
 | |
|   for (uint32_t i = 0; i < foundTables.Length(); i++) {
 | |
|     nsCString table(foundTables[i]);
 | |
|     HashStore store(table, mRootStoreDirectory);
 | |
| 
 | |
|     nsresult rv = store.Open();
 | |
|     if (NS_FAILED(rv))
 | |
|       continue;
 | |
| 
 | |
|     LookupCache *lookupCache = GetLookupCache(store.TableName());
 | |
|     if (!lookupCache) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!lookupCache->IsPrimed())
 | |
|       continue;
 | |
| 
 | |
|     const ChunkSet &adds = store.AddChunks();
 | |
|     const ChunkSet &subs = store.SubChunks();
 | |
| 
 | |
|     if (adds.Length() == 0 && subs.Length() == 0)
 | |
|       continue;
 | |
| 
 | |
|     LOG(("Active table: %s", store.TableName().get()));
 | |
|     mActiveTablesCache.AppendElement(store.TableName());
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::ScanStoreDir(nsTArray<nsCString>& aTables)
 | |
| {
 | |
|   nsCOMPtr<nsISimpleEnumerator> entries;
 | |
|   nsresult rv = mRootStoreDirectory->GetDirectoryEntries(getter_AddRefs(entries));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   bool hasMore;
 | |
|   while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
 | |
|     nsCOMPtr<nsISupports> supports;
 | |
|     rv = entries->GetNext(getter_AddRefs(supports));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
 | |
| 
 | |
|     nsCString leafName;
 | |
|     rv = file->GetNativeLeafName(leafName);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     nsCString suffix(NS_LITERAL_CSTRING(".sbstore"));
 | |
| 
 | |
|     int32_t dot = leafName.RFind(suffix, 0);
 | |
|     if (dot != -1) {
 | |
|       leafName.Cut(dot, suffix.Length());
 | |
|       aTables.AppendElement(leafName);
 | |
|     }
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::ActiveTables(nsTArray<nsCString>& aTables)
 | |
| {
 | |
|   aTables = mActiveTablesCache;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::CleanToDelete()
 | |
| {
 | |
|   bool exists;
 | |
|   nsresult rv = mToDeleteDirectory->Exists(&exists);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (exists) {
 | |
|     rv = mToDeleteDirectory->Remove(true);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::BackupTables()
 | |
| {
 | |
|   // We have to work in reverse here: first move the normal directory
 | |
|   // away to be the backup directory, then copy the files over
 | |
|   // to the normal directory. This ensures that if we crash the backup
 | |
|   // dir always has a valid, complete copy, instead of a partial one,
 | |
|   // because that's the one we will copy over the normal store dir.
 | |
| 
 | |
|   nsCString backupDirName;
 | |
|   nsresult rv = mBackupDirectory->GetNativeLeafName(backupDirName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCString storeDirName;
 | |
|   rv = mRootStoreDirectory->GetNativeLeafName(storeDirName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mRootStoreDirectory->MoveToNative(nullptr, backupDirName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mRootStoreDirectory->CopyToNative(nullptr, storeDirName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // We moved some things to new places, so move the handles around, too.
 | |
|   rv = SetupPathNames();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::RemoveBackupTables()
 | |
| {
 | |
|   nsCString toDeleteName;
 | |
|   nsresult rv = mToDeleteDirectory->GetNativeLeafName(toDeleteName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = mBackupDirectory->MoveToNative(nullptr, toDeleteName);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // mBackupDirectory now points to toDelete, fix that up.
 | |
|   rv = SetupPathNames();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::RecoverBackups()
 | |
| {
 | |
|   bool backupExists;
 | |
|   nsresult rv = mBackupDirectory->Exists(&backupExists);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (backupExists) {
 | |
|     // Remove the safebrowsing dir if it exists
 | |
|     nsCString storeDirName;
 | |
|     rv = mRootStoreDirectory->GetNativeLeafName(storeDirName);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     bool storeExists;
 | |
|     rv = mRootStoreDirectory->Exists(&storeExists);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     if (storeExists) {
 | |
|       rv = mRootStoreDirectory->Remove(true);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|     }
 | |
| 
 | |
|     // Move the backup to the store location
 | |
|     rv = mBackupDirectory->MoveToNative(nullptr, storeDirName);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     // mBackupDirectory now points to storeDir, fix up.
 | |
|     rv = SetupPathNames();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool
 | |
| Classifier::CheckValidUpdate(nsTArray<TableUpdate*>* aUpdates,
 | |
|                              const nsACString& aTable)
 | |
| {
 | |
|   // take the quick exit if there is no valid update for us
 | |
|   // (common case)
 | |
|   uint32_t validupdates = 0;
 | |
| 
 | |
|   for (uint32_t i = 0; i < aUpdates->Length(); i++) {
 | |
|     TableUpdate *update = aUpdates->ElementAt(i);
 | |
|     if (!update || !update->TableName().Equals(aTable))
 | |
|       continue;
 | |
|     if (update->Empty()) {
 | |
|       aUpdates->ElementAt(i) = nullptr;
 | |
|       continue;
 | |
|     }
 | |
|     validupdates++;
 | |
|   }
 | |
| 
 | |
|   if (!validupdates) {
 | |
|     // This can happen if the update was only valid for one table.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * This will consume+delete updates from the passed nsTArray.
 | |
| */
 | |
| nsresult
 | |
| Classifier::UpdateHashStore(nsTArray<TableUpdate*>* aUpdates,
 | |
|                             const nsACString& aTable)
 | |
| {
 | |
|   LOG(("Classifier::UpdateHashStore(%s)", PromiseFlatCString(aTable).get()));
 | |
| 
 | |
|   HashStore store(aTable, mRootStoreDirectory);
 | |
| 
 | |
|   if (!CheckValidUpdate(aUpdates, store.TableName())) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = store.Open();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   rv = store.BeginUpdate();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Read the part of the store that is (only) in the cache
 | |
|   LookupCacheV2* lookupCache =
 | |
|     LookupCache::Cast<LookupCacheV2>(GetLookupCache(store.TableName()));
 | |
|   if (!lookupCache) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // Clear cache when update
 | |
|   lookupCache->ClearCache();
 | |
| 
 | |
|   FallibleTArray<uint32_t> AddPrefixHashes;
 | |
|   rv = lookupCache->GetPrefixes(AddPrefixHashes);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   rv = store.AugmentAdds(AddPrefixHashes);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   AddPrefixHashes.Clear();
 | |
| 
 | |
|   uint32_t applied = 0;
 | |
| 
 | |
|   for (uint32_t i = 0; i < aUpdates->Length(); i++) {
 | |
|     TableUpdate *update = aUpdates->ElementAt(i);
 | |
|     if (!update || !update->TableName().Equals(store.TableName()))
 | |
|       continue;
 | |
| 
 | |
|     rv = store.ApplyUpdate(*update);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     applied++;
 | |
| 
 | |
|     auto updateV2 = TableUpdate::Cast<TableUpdateV2>(update);
 | |
|     if (updateV2) {
 | |
|       LOG(("Applied update to table %s:", store.TableName().get()));
 | |
|       LOG(("  %d add chunks", updateV2->AddChunks().Length()));
 | |
|       LOG(("  %d add prefixes", updateV2->AddPrefixes().Length()));
 | |
|       LOG(("  %d add completions", updateV2->AddCompletes().Length()));
 | |
|       LOG(("  %d sub chunks", updateV2->SubChunks().Length()));
 | |
|       LOG(("  %d sub prefixes", updateV2->SubPrefixes().Length()));
 | |
|       LOG(("  %d sub completions", updateV2->SubCompletes().Length()));
 | |
|       LOG(("  %d add expirations", updateV2->AddExpirations().Length()));
 | |
|       LOG(("  %d sub expirations", updateV2->SubExpirations().Length()));
 | |
|     }
 | |
| 
 | |
|     aUpdates->ElementAt(i) = nullptr;
 | |
|   }
 | |
| 
 | |
|   LOG(("Applied %d update(s) to %s.", applied, store.TableName().get()));
 | |
| 
 | |
|   rv = store.Rebuild();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   LOG(("Table %s now has:", store.TableName().get()));
 | |
|   LOG(("  %d add chunks", store.AddChunks().Length()));
 | |
|   LOG(("  %d add prefixes", store.AddPrefixes().Length()));
 | |
|   LOG(("  %d add completions", store.AddCompletes().Length()));
 | |
|   LOG(("  %d sub chunks", store.SubChunks().Length()));
 | |
|   LOG(("  %d sub prefixes", store.SubPrefixes().Length()));
 | |
|   LOG(("  %d sub completions", store.SubCompletes().Length()));
 | |
| 
 | |
|   rv = store.WriteFile();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // At this point the store is updated and written out to disk, but
 | |
|   // the data is still in memory.  Build our quick-lookup table here.
 | |
|   rv = lookupCache->Build(store.AddPrefixes(), store.AddCompletes());
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
| #if defined(DEBUG)
 | |
|   lookupCache->DumpCompletions();
 | |
| #endif
 | |
|   rv = lookupCache->WriteFile();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   int64_t now = (PR_Now() / PR_USEC_PER_SEC);
 | |
|   LOG(("Successfully updated %s", store.TableName().get()));
 | |
|   mTableFreshness.Put(store.TableName(), now);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::UpdateTableV4(nsTArray<TableUpdate*>* aUpdates,
 | |
|                           const nsACString& aTable)
 | |
| {
 | |
|   LOG(("Classifier::UpdateTableV4(%s)", PromiseFlatCString(aTable).get()));
 | |
| 
 | |
|   if (!CheckValidUpdate(aUpdates, aTable)) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   LookupCacheV4* lookupCache =
 | |
|     LookupCache::Cast<LookupCacheV4>(GetLookupCache(aTable));
 | |
|   if (!lookupCache) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   // If there are multiple updates for the same table, prefixes1 & prefixes2
 | |
|   // will act as input and output in turn to reduce memory copy overhead.
 | |
|   PrefixStringMap prefixes1, prefixes2;
 | |
|   PrefixStringMap* input = &prefixes1;
 | |
|   PrefixStringMap* output = &prefixes2;
 | |
| 
 | |
|   TableUpdateV4* lastAppliedUpdate = nullptr;
 | |
|   for (uint32_t i = 0; i < aUpdates->Length(); i++) {
 | |
|     TableUpdate *update = aUpdates->ElementAt(i);
 | |
|     if (!update || !update->TableName().Equals(aTable)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     auto updateV4 = TableUpdate::Cast<TableUpdateV4>(update);
 | |
|     NS_ENSURE_TRUE(updateV4, NS_ERROR_FAILURE);
 | |
| 
 | |
|     if (updateV4->IsFullUpdate()) {
 | |
|       input->Clear();
 | |
|       output->Clear();
 | |
|       rv = lookupCache->ApplyUpdate(updateV4, *input, *output);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return rv;
 | |
|       }
 | |
|     } else {
 | |
|       // If both prefix sets are empty, this means we are doing a partial update
 | |
|       // without a prior full/partial update in the loop. In this case we should
 | |
|       // get prefixes from the lookup cache first.
 | |
|       if (prefixes1.IsEmpty() && prefixes2.IsEmpty()) {
 | |
|         lookupCache->GetPrefixes(prefixes1);
 | |
|       } else {
 | |
|         MOZ_ASSERT(prefixes1.IsEmpty() ^ prefixes2.IsEmpty());
 | |
| 
 | |
|         // When there are multiple partial updates, input should always point
 | |
|         // to the non-empty prefix set(filled by previous full/partial update).
 | |
|         // output should always point to the empty prefix set.
 | |
|         input = prefixes1.IsEmpty() ? &prefixes2 : &prefixes1;
 | |
|         output = prefixes1.IsEmpty() ? &prefixes1 : &prefixes2;
 | |
|       }
 | |
| 
 | |
|       rv = lookupCache->ApplyUpdate(updateV4, *input, *output);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return rv;
 | |
|       }
 | |
| 
 | |
|       input->Clear();
 | |
|     }
 | |
| 
 | |
|     // Keep track of the last applied update.
 | |
|     lastAppliedUpdate = updateV4;
 | |
| 
 | |
|     aUpdates->ElementAt(i) = nullptr;
 | |
|   }
 | |
| 
 | |
|   rv = lookupCache->Build(*output);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = lookupCache->WriteFile();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (lastAppliedUpdate) {
 | |
|     LOG(("Write meta data of the last applied update."));
 | |
|     rv = lookupCache->WriteMetadata(lastAppliedUpdate);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   int64_t now = (PR_Now() / PR_USEC_PER_SEC);
 | |
|   LOG(("Successfully updated %s\n", PromiseFlatCString(aTable).get()));
 | |
|   mTableFreshness.Put(aTable, now);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::UpdateCache(TableUpdate* aUpdate)
 | |
| {
 | |
|   if (!aUpdate) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString table(aUpdate->TableName());
 | |
|   LOG(("Classifier::UpdateCache(%s)", table.get()));
 | |
| 
 | |
|   LookupCache *lookupCache = GetLookupCache(table);
 | |
|   if (!lookupCache) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   auto updateV2 = TableUpdate::Cast<TableUpdateV2>(aUpdate);
 | |
|   lookupCache->AddCompletionsToCache(updateV2->AddCompletes());
 | |
| 
 | |
| #if defined(DEBUG)
 | |
|   lookupCache->DumpCache();
 | |
| #endif
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| LookupCache *
 | |
| Classifier::GetLookupCache(const nsACString& aTable)
 | |
| {
 | |
|   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
 | |
|     if (mLookupCaches[i]->TableName().Equals(aTable)) {
 | |
|       return mLookupCaches[i];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // TODO : Bug 1302600, It would be better if we have a more general non-main
 | |
|   //        thread method to convert table name to protocol version. Currently
 | |
|   //        we can only know this by checking if the table name ends with '-proto'.
 | |
|   UniquePtr<LookupCache> cache;
 | |
|   if (StringEndsWith(aTable, NS_LITERAL_CSTRING("-proto"))) {
 | |
|     cache = MakeUnique<LookupCacheV4>(aTable, mRootStoreDirectory);
 | |
|   } else {
 | |
|     cache = MakeUnique<LookupCacheV2>(aTable, mRootStoreDirectory);
 | |
|   }
 | |
| 
 | |
|   nsresult rv = cache->Init();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   rv = cache->Open();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     if (rv == NS_ERROR_FILE_CORRUPTED) {
 | |
|       Reset();
 | |
|     }
 | |
|     return nullptr;
 | |
|   }
 | |
|   mLookupCaches.AppendElement(cache.get());
 | |
|   return cache.release();
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::ReadNoiseEntries(const Prefix& aPrefix,
 | |
|                              const nsACString& aTableName,
 | |
|                              uint32_t aCount,
 | |
|                              PrefixArray* aNoiseEntries)
 | |
| {
 | |
|   // TODO : Bug 1297962, support adding noise for v4
 | |
|   LookupCacheV2 *cache = static_cast<LookupCacheV2*>(GetLookupCache(aTableName));
 | |
|   if (!cache) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   FallibleTArray<uint32_t> prefixes;
 | |
|   nsresult rv = cache->GetPrefixes(prefixes);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   size_t idx = prefixes.BinaryIndexOf(aPrefix.ToUint32());
 | |
| 
 | |
|   if (idx == nsTArray<uint32_t>::NoIndex) {
 | |
|     NS_WARNING("Could not find prefix in PrefixSet during noise lookup");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   idx -= idx % aCount;
 | |
| 
 | |
|   for (size_t i = 0; (i < aCount) && ((idx+i) < prefixes.Length()); i++) {
 | |
|     Prefix newPref;
 | |
|     newPref.FromUint32(prefixes[idx+i]);
 | |
|     if (newPref != aPrefix) {
 | |
|       aNoiseEntries->AppendElement(newPref);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Classifier::LoadMetadata(nsIFile* aDirectory, nsACString& aResult)
 | |
| {
 | |
|   nsCOMPtr<nsISimpleEnumerator> entries;
 | |
|   nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   NS_ENSURE_ARG_POINTER(entries);
 | |
| 
 | |
|   bool hasMore;
 | |
|   while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
 | |
|     nsCOMPtr<nsISupports> supports;
 | |
|     rv = entries->GetNext(getter_AddRefs(supports));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
 | |
| 
 | |
|     // If |file| is a directory, recurse to find its entries as well.
 | |
|     bool isDirectory;
 | |
|     if (NS_FAILED(file->IsDirectory(&isDirectory))) {
 | |
|       continue;
 | |
|     }
 | |
|     if (isDirectory) {
 | |
|       LoadMetadata(file, aResult);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Truncate file extension to get the table name.
 | |
|     nsCString tableName;
 | |
|     rv = file->GetNativeLeafName(tableName);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     int32_t dot = tableName.RFind(METADATA_SUFFIX, 0);
 | |
|     if (dot == -1) {
 | |
|       continue;
 | |
|     }
 | |
|     tableName.Cut(dot, METADATA_SUFFIX.Length());
 | |
| 
 | |
|     LookupCacheV4* lookupCache =
 | |
|       LookupCache::Cast<LookupCacheV4>(GetLookupCache(tableName));
 | |
|     if (!lookupCache) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsCString state;
 | |
|     nsCString checksum;
 | |
|     rv = lookupCache->LoadMetadata(state, checksum);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       LOG(("Failed to get metadata for table %s", tableName.get()));
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // The state might include '\n' so that we have to encode.
 | |
|     nsAutoCString stateBase64;
 | |
|     rv = Base64Encode(state, stateBase64);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     nsAutoCString checksumBase64;
 | |
|     rv = Base64Encode(checksum, checksumBase64);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     LOG(("Appending state '%s' and checksum '%s' for table %s",
 | |
|          stateBase64.get(), checksumBase64.get(), tableName.get()));
 | |
| 
 | |
|     aResult.AppendPrintf("%s;%s:%s\n", tableName.get(),
 | |
|                                        stateBase64.get(),
 | |
|                                        checksumBase64.get());
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| } // namespace safebrowsing
 | |
| } // namespace mozilla
 |