12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274 |
- //* -*- 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/Logging.h"
- #include "mozilla/SyncRunnable.h"
- #include "mozilla/Base64.h"
- #include "mozilla/Unused.h"
- #include "mozilla/TypedEnumBits.h"
- #include "nsIUrlClassifierUtils.h"
- #include "nsUrlClassifierDBService.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++;
- }
- }
- }
- nsresult
- Classifier::GetPrivateStoreDirectory(nsIFile* aRootStoreDirectory,
- const nsACString& aTableName,
- const nsACString& aProvider,
- 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;
- }
- if (aProvider.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(aProvider);
- 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)
- {
- // We don't need to reset while shutting down. It will only slow us down.
- if (nsUrlClassifierDBService::ShutdownHasStarted()) {
- return;
- }
- 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], GetProvider(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);
- }
- // This is used to record the matching statistics for v2 and v4.
- enum class PrefixMatch : uint8_t {
- eNoMatch = 0x00,
- eMatchV2Prefix = 0x01,
- eMatchV4Prefix = 0x02,
- eMatchBoth = eMatchV2Prefix | eMatchV4Prefix
- };
- MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PrefixMatch)
- nsresult
- Classifier::Check(const nsACString& aSpec,
- const nsACString& aTables,
- uint32_t aFreshnessGuarantee,
- LookupResultArray& aResults)
- {
- // 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;
- }
- }
- PrefixMatch matchingStatistics = PrefixMatch::eNoMatch;
- // 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;
- if (LookupCache::Cast<LookupCacheV4>(cache)) {
- // TODO Bug 1312339 Return length in LookupCache.Has and support
- // VariableLengthPrefix in LookupResultArray
- rv = cache->Has(lookupHash, &has, &complete);
- if (NS_FAILED(rv)) {
- LOG(("Failed to lookup fragment %s V4", fragments[i].get()));
- }
- if (has) {
- matchingStatistics |= PrefixMatch::eMatchV4Prefix;
- // TODO: Bug 1311935 - Implement Safe Browsing v4 caching
- // Should check cache expired
- }
- continue;
- }
- 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());
- matchingStatistics |= PrefixMatch::eMatchV2Prefix;
- }
- }
- }
- return NS_OK;
- }
- nsresult
- Classifier::ApplyUpdates(nsTArray<TableUpdate*>* aUpdates)
- {
- 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) {
- #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
- DumpFailedUpdate();
- #endif
- 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, GetProvider(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;
- }
- #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
- already_AddRefed<nsIFile>
- Classifier::GetFailedUpdateDirectroy()
- {
- nsCString failedUpdatekDirName = STORE_DIRECTORY + nsCString("-failedupdate");
- nsCOMPtr<nsIFile> failedUpdatekDirectory;
- if (NS_FAILED(mCacheDirectory->Clone(getter_AddRefs(failedUpdatekDirectory))) ||
- NS_FAILED(failedUpdatekDirectory->AppendNative(failedUpdatekDirName))) {
- LOG(("Failed to init failedUpdatekDirectory."));
- return nullptr;
- }
- return failedUpdatekDirectory.forget();
- }
- nsresult
- Classifier::DumpRawTableUpdates(const nsACString& aRawUpdates)
- {
- // DumpFailedUpdate() MUST be called first to create the
- // "failed update" directory
- LOG(("Dumping raw table updates..."));
- nsCOMPtr<nsIFile> failedUpdatekDirectory = GetFailedUpdateDirectroy();
- // Create tableupdate.bin and dump raw table update data.
- nsCOMPtr<nsIFile> rawTableUpdatesFile;
- nsCOMPtr<nsIOutputStream> outputStream;
- if (NS_FAILED(failedUpdatekDirectory->Clone(getter_AddRefs(rawTableUpdatesFile))) ||
- NS_FAILED(rawTableUpdatesFile->AppendNative(nsCString("tableupdates.bin"))) ||
- NS_FAILED(NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
- rawTableUpdatesFile,
- PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE))) {
- LOG(("Failed to create file to dump raw table updates."));
- return NS_ERROR_FAILURE;
- }
- // Write out the data.
- uint32_t written;
- nsresult rv = outputStream->Write(aRawUpdates.BeginReading(),
- aRawUpdates.Length(), &written);
- NS_ENSURE_SUCCESS(rv, rv);
- NS_ENSURE_TRUE(written == aRawUpdates.Length(), NS_ERROR_FAILURE);
- return rv;
- }
- nsresult
- Classifier::DumpFailedUpdate()
- {
- LOG(("Dumping failed update..."));
- nsCOMPtr<nsIFile> failedUpdatekDirectory = GetFailedUpdateDirectroy();
- // Remove the "failed update" directory no matter it exists or not.
- // Failure is fine because the directory may not exist.
- failedUpdatekDirectory->Remove(true);
- nsCString failedUpdatekDirName;
- nsresult rv = failedUpdatekDirectory->GetNativeLeafName(failedUpdatekDirName);
- NS_ENSURE_SUCCESS(rv, rv);
- // Move backup to a clean "failed update" directory.
- nsCOMPtr<nsIFile> backupDirectory;
- if (NS_FAILED(mBackupDirectory->Clone(getter_AddRefs(backupDirectory))) ||
- NS_FAILED(backupDirectory->MoveToNative(nullptr, failedUpdatekDirName))) {
- LOG(("Failed to move backup to the \"failed update\" directory %s",
- failedUpdatekDirName.get()));
- return NS_ERROR_FAILURE;
- }
- return rv;
- }
- #endif // MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
- 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;
- }
- nsCString
- Classifier::GetProvider(const nsACString& aTableName)
- {
- nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
- do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
- nsCString provider;
- nsresult rv = urlUtil->GetProvider(aTableName, provider);
- return NS_SUCCEEDED(rv) ? provider : EmptyCString();
- }
- /*
- * This will consume+delete updates from the passed nsTArray.
- */
- nsresult
- Classifier::UpdateHashStore(nsTArray<TableUpdate*>* aUpdates,
- const nsACString& aTable)
- {
- if (nsUrlClassifierDBService::ShutdownHasStarted()) {
- return NS_ERROR_ABORT;
- }
- LOG(("Classifier::UpdateHashStore(%s)", PromiseFlatCString(aTable).get()));
- HashStore store(aTable, GetProvider(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)
- {
- if (nsUrlClassifierDBService::ShutdownHasStarted()) {
- return NS_ERROR_ABORT;
- }
- 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;
- nsCString provider = GetProvider(aTable);
- if (StringEndsWith(aTable, NS_LITERAL_CSTRING("-proto"))) {
- cache = MakeUnique<LookupCacheV4>(aTable, provider, mRootStoreDirectory);
- } else {
- cache = MakeUnique<LookupCacheV2>(aTable, provider, 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
|