diff options
Diffstat (limited to '')
196 files changed, 54649 insertions, 0 deletions
diff --git a/toolkit/components/url-classifier/ChunkSet.cpp b/toolkit/components/url-classifier/ChunkSet.cpp new file mode 100644 index 0000000000..af488d43d7 --- /dev/null +++ b/toolkit/components/url-classifier/ChunkSet.cpp @@ -0,0 +1,248 @@ +//* -*- 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 "ChunkSet.h" + +#include "nsReadableUtils.h" + +namespace mozilla { +namespace safebrowsing { + +const size_t ChunkSet::IO_BUFFER_SIZE; + +nsresult ChunkSet::Serialize(nsACString& aChunkStr) { + // Truncate and append rather than assigning because that's more efficient if + // aString is an nsAutoCString. + aChunkStr.Truncate(); + StringJoinAppend(aChunkStr, ","_ns, mRanges, + [](nsACString& dst, const Range& range) { + dst.AppendInt((int32_t)range.Begin()); + if (range.Begin() != range.End()) { + dst.Append('-'); + dst.AppendInt((int32_t)range.End()); + } + }); + + return NS_OK; +} + +nsresult ChunkSet::Set(uint32_t aChunk) { + if (!Has(aChunk)) { + Range chunkRange(aChunk, aChunk); + + if (mRanges.Length() == 0) { + if (!mRanges.AppendElement(chunkRange, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + if (mRanges.LastElement().Precedes(chunkRange)) { + mRanges.LastElement().End(aChunk); + } else if (chunkRange.Precedes(mRanges[0])) { + mRanges[0].Begin(aChunk); + } else { + ChunkSet tmp; + if (!tmp.mRanges.AppendElement(chunkRange, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return Merge(tmp); + } + } + + return NS_OK; +} + +bool ChunkSet::Has(uint32_t aChunk) const { + size_t idx; + return BinarySearchIf(mRanges, 0, mRanges.Length(), + Range::IntersectionComparator(Range(aChunk, aChunk)), + &idx); + // IntersectionComparator works because we create a + // single-chunk range. +} + +nsresult ChunkSet::Merge(const ChunkSet& aOther) { + size_t oldLen = mRanges.Length(); + + for (const Range& mergeRange : aOther.mRanges) { + if (!HasSubrange(mergeRange)) { + if (!mRanges.InsertElementSorted(mergeRange, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + if (oldLen < mRanges.Length()) { + for (size_t i = 1; i < mRanges.Length(); i++) { + while (mRanges[i - 1].FoldLeft(mRanges[i])) { + mRanges.RemoveElementAt(i); + + if (i == mRanges.Length()) { + return NS_OK; + } + } + } + } + + return NS_OK; +} + +uint32_t ChunkSet::Length() const { + uint32_t len = 0; + for (const Range& range : mRanges) { + len += range.Length(); + } + + return len; +} + +nsresult ChunkSet::Remove(const ChunkSet& aOther) { + for (const Range& removalRange : aOther.mRanges) { + if (mRanges.Length() == 0) { + return NS_OK; + } + + if (mRanges.LastElement().End() < removalRange.Begin() || + aOther.mRanges.LastElement().End() < mRanges[0].Begin()) { + return NS_OK; + } + + size_t intersectionIdx; + while (BinarySearchIf(mRanges, 0, mRanges.Length(), + Range::IntersectionComparator(removalRange), + &intersectionIdx)) { + ChunkSet remains; + nsresult rv = mRanges[intersectionIdx].Remove(removalRange, remains); + + if (NS_FAILED(rv)) { + return rv; + } + + mRanges.RemoveElementAt(intersectionIdx); + if (!mRanges.InsertElementsAt(intersectionIdx, remains.mRanges, + fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + return NS_OK; +} + +void ChunkSet::Clear() { mRanges.Clear(); } + +nsresult ChunkSet::Write(nsIOutputStream* aOut) const { + nsTArray<uint32_t> chunks(IO_BUFFER_SIZE); + + for (const Range& range : mRanges) { + for (uint32_t chunk = range.Begin(); chunk <= range.End(); chunk++) { + chunks.AppendElement(chunk); + + if (chunks.Length() == chunks.Capacity()) { + nsresult rv = WriteTArray(aOut, chunks); + + if (NS_FAILED(rv)) { + return rv; + } + + chunks.Clear(); + } + } + } + + nsresult rv = WriteTArray(aOut, chunks); + + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +nsresult ChunkSet::Read(nsIInputStream* aIn, uint32_t aNumElements) { + nsTArray<uint32_t> chunks(IO_BUFFER_SIZE); + + while (aNumElements != 0) { + chunks.Clear(); + + uint32_t numToRead = + aNumElements > IO_BUFFER_SIZE ? IO_BUFFER_SIZE : aNumElements; + + nsresult rv = ReadTArray(aIn, &chunks, numToRead); + + if (NS_FAILED(rv)) { + return rv; + } + + aNumElements -= numToRead; + + for (uint32_t c : chunks) { + rv = Set(c); + + if (NS_FAILED(rv)) { + return rv; + } + } + } + + return NS_OK; +} + +bool ChunkSet::HasSubrange(const Range& aSubrange) const { + for (const Range& range : mRanges) { + if (range.Contains(aSubrange)) { + return true; + } else if (!(aSubrange.Begin() > range.End() || + range.Begin() > aSubrange.End())) { + // In this case, aSubrange overlaps this range but is not a subrange. + // because the ChunkSet implementation ensures that there are no + // overlapping ranges, this means that aSubrange cannot be a subrange of + // any of the following ranges + return false; + } + } + + return false; +} + +uint32_t ChunkSet::Range::Length() const { return mEnd - mBegin + 1; } + +nsresult ChunkSet::Range::Remove(const Range& aRange, + ChunkSet& aRemainderSet) const { + if (mBegin < aRange.mBegin && aRange.mBegin <= mEnd) { + // aRange overlaps & follows this range + Range range(mBegin, aRange.mBegin - 1); + if (!aRemainderSet.mRanges.AppendElement(range, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (mBegin <= aRange.mEnd && aRange.mEnd < mEnd) { + // aRange overlaps & precedes this range + Range range(aRange.mEnd + 1, mEnd); + if (!aRemainderSet.mRanges.AppendElement(range, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_OK; +} + +bool ChunkSet::Range::FoldLeft(const Range& aRange) { + if (Contains(aRange)) { + return true; + } else if (Precedes(aRange) || + (mBegin <= aRange.mBegin && aRange.mBegin <= mEnd)) { + mEnd = aRange.mEnd; + return true; + } + + return false; +} + +} // namespace safebrowsing +} // namespace mozilla diff --git a/toolkit/components/url-classifier/ChunkSet.h b/toolkit/components/url-classifier/ChunkSet.h new file mode 100644 index 0000000000..d9978b21fd --- /dev/null +++ b/toolkit/components/url-classifier/ChunkSet.h @@ -0,0 +1,96 @@ +//* -*- 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/. */ + +#ifndef ChunkSet_h__ +#define ChunkSet_h__ + +#include "Entries.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace safebrowsing { + +/** + * Store the chunk numbers as an array of ranges of uint32_t. + * We need chunk numbers in order to ask for incremental updates from the + * server. + */ +class ChunkSet { + public: + nsresult Serialize(nsACString& aStr); + nsresult Set(uint32_t aChunk); + bool Has(uint32_t chunk) const; + nsresult Merge(const ChunkSet& aOther); + uint32_t Length() const; + nsresult Remove(const ChunkSet& aOther); + void Clear(); + + nsresult Write(nsIOutputStream* aOut) const; + nsresult Read(nsIInputStream* aIn, uint32_t aNumElements); + + ChunkSet InfallibleClone() const { + ChunkSet result; + if (!result.mRanges.Assign(mRanges, fallible)) { + MOZ_CRASH("Out of memory"); + } + return result; + } + + private: + class Range { + public: + Range(uint32_t aBegin, uint32_t aEnd) : mBegin(aBegin), mEnd(aEnd) {} + + uint32_t Length() const; + nsresult Remove(const Range& aRange, ChunkSet& aRemainderSet) const; + bool FoldLeft(const Range& aRange); + + bool operator==(const Range& rhs) const { return mBegin == rhs.mBegin; } + bool operator<(const Range& rhs) const { return mBegin < rhs.mBegin; } + + uint32_t Begin() const { return mBegin; } + void Begin(const uint32_t aBegin) { mBegin = aBegin; } + uint32_t End() const { return mEnd; } + void End(const uint32_t aEnd) { mEnd = aEnd; } + + bool Contains(const Range& aRange) const { + return mBegin <= aRange.mBegin && aRange.mEnd <= mEnd; + } + bool Precedes(const Range& aRange) const { + return mEnd + 1 == aRange.mBegin; + } + + struct IntersectionComparator { + int operator()(const Range& aRange) const { + if (aRange.mBegin > mTarget.mEnd) { + return -1; + } + if (mTarget.mBegin > aRange.mEnd) { + return 1; + } + return 0; + } + + explicit IntersectionComparator(const Range& aTarget) + : mTarget(aTarget) {} + const Range& mTarget; + }; + + private: + uint32_t mBegin; + uint32_t mEnd; + }; + + static const size_t IO_BUFFER_SIZE = 1024; + FallibleTArray<Range> mRanges; + + bool HasSubrange(const Range& aSubrange) const; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/Classifier.cpp b/toolkit/components/url-classifier/Classifier.cpp new file mode 100644 index 0000000000..7a78a59243 --- /dev/null +++ b/toolkit/components/url-classifier/Classifier.cpp @@ -0,0 +1,1786 @@ +//* -*- 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 "nsIFile.h" +#include "nsNetCID.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" +#include "mozilla/Components.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/Logging.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Base64.h" +#include "mozilla/Unused.h" +#include "mozilla/UniquePtr.h" +#include "nsUrlClassifierDBService.h" +#include "nsUrlClassifierUtils.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 "safebrowsing"_ns +#define TO_DELETE_DIR_SUFFIX "-to_delete"_ns +#define BACKUP_DIR_SUFFIX "-backup"_ns +#define UPDATING_DIR_SUFFIX "-updating"_ns + +#define V4_METADATA_SUFFIX ".metadata"_ns +#define V2_METADATA_SUFFIX ".sbstore"_ns + +// The amount of time, in milliseconds, that our IO thread will stay alive after +// the last event it processes. +#define DEFAULT_THREAD_TIMEOUT_MS 5000 + +namespace mozilla { +namespace safebrowsing { + +bool Classifier::OnUpdateThread() const { + bool onthread = false; + if (mUpdateThread) { + mUpdateThread->IsOnCurrentThread(&onthread); + } + return onthread; +} + +void Classifier::SplitTables(const nsACString& str, + nsTArray<nsCString>& tables) { + tables.Clear(); + + for (const auto& table : str.Split(',')) { + if (!table.IsEmpty()) { + tables.AppendElement(table); + } + } + + // Remove duplicates + tables.Sort(); + const auto newEnd = std::unique(tables.begin(), tables.end()); + tables.TruncateLength(std::distance(tables.begin(), newEnd)); +} + +nsresult Classifier::GetPrivateStoreDirectory( + nsIFile* aRootStoreDirectory, const nsACString& aTableName, + const nsACString& aProvider, nsIFile** aPrivateStoreDirectory) { + NS_ENSURE_ARG_POINTER(aPrivateStoreDirectory); + + if (!StringEndsWith(aTableName, "-proto"_ns)) { + // 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() + : mIsTableRequestResultOutdated(true), + mUpdateInterrupted(true), + mIsClosed(false) { + // Make a lazy thread for any IO + mUpdateThread = + new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Classifier Update", + LazyIdleThread::ShutdownMethod::ManualShutdown); +} + +Classifier::~Classifier() { + if (mUpdateThread) { + mUpdateThread->Shutdown(); + mUpdateThread = nullptr; + } + + 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 be working on the update. + rv = mCacheDirectory->Clone(getter_AddRefs(mUpdatingDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mUpdatingDirectory->AppendNative(STORE_DIRECTORY + UPDATING_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() { + if (ShouldAbort()) { + return NS_OK; // nothing to do, the classifier is done + } + + // 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; +} + +// Testing entries are created directly in LookupCache instead of +// created via update(Bug 1531354). We can remove unused testing +// files from profile. +// TODO: See Bug 723153 to clear old safebrowsing store +nsresult Classifier::ClearLegacyFiles() { + if (ShouldAbort()) { + return NS_OK; // nothing to do, the classifier is done + } + + nsTArray<nsLiteralCString> tables = { + "test-phish-simple"_ns, "test-malware-simple"_ns, + "test-unwanted-simple"_ns, "test-harmful-simple"_ns, + "test-track-simple"_ns, "test-trackwhite-simple"_ns, + "test-block-simple"_ns, + }; + + const auto fnFindAndRemove = [](nsIFile* aRootDirectory, + const nsACString& aFileName) { + nsCOMPtr<nsIFile> file; + nsresult rv = aRootDirectory->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return false; + } + + rv = file->AppendNative(aFileName); + if (NS_FAILED(rv)) { + return false; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + return false; + } + + rv = file->Remove(false); + if (NS_FAILED(rv)) { + return false; + } + + return true; + }; + + for (const auto& table : tables) { + // Remove both .sbstore and .vlpse if .sbstore exists + if (fnFindAndRemove(mRootStoreDirectory, table + ".sbstore"_ns)) { + fnFindAndRemove(mRootStoreDirectory, table + ".vlpset"_ns); + } + } + + 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. + // This is still required for backward compatibility. + rv = CleanToDelete(); + NS_ENSURE_SUCCESS(rv, rv); + + // If we met a crash during the previous update, "safebrowsing-updating" + // directory will exist and let's remove it. + rv = mUpdatingDirectory->Remove(true); + if (NS_SUCCEEDED(rv)) { + // If the "safebrowsing-updating" exists, it implies a crash occurred + // in the previous update. + LOG(("We may have hit a crash in the previous update.")); + } + + // 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); + + rv = ClearLegacyFiles(); + Unused << NS_WARN_IF(NS_FAILED(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() { + // Close will be called by PreShutdown, so it is important to note that + // things put here should not affect an ongoing update thread. + mIsClosed = true; + DropStores(); +} + +void Classifier::Reset() { + MOZ_ASSERT(!OnUpdateThread(), "Reset() MUST NOT be called on update thread"); + + LOG(("Reset() is called so we interrupt the update.")); + mUpdateInterrupted = true; + + // We don't pass the ref counted object 'Classifier' to resetFunc because we + // don't want to release 'Classifier in the update thread, which triggers an + // assertion when LazyIdelUpdate thread is not created and removed by the same + // thread (worker thread). Since |resetFuc| is a synchronous call, we can just + // pass the reference of Classifier because Classifier's life cycle is + // guarantee longer than |resetFunc|. + auto resetFunc = [&] { + if (this->mIsClosed) { + return; // too late to reset, bail + } + this->DropStores(); + + this->mRootStoreDirectory->Remove(true); + this->mBackupDirectory->Remove(true); + this->mUpdatingDirectory->Remove(true); + this->mToDeleteDirectory->Remove(true); + + this->CreateStoreDirectory(); + this->RegenActiveTables(); + }; + + if (!mUpdateThread) { + LOG(("Async update has been disabled. Just Reset() on worker thread.")); + resetFunc(); + return; + } + + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("safebrowsing::Classifier::Reset", resetFunc); + SyncRunnable::DispatchToThread(mUpdateThread, r); +} + +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())); + RefPtr<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(); + } +} + +// |DeleteTables| is used by |GetLookupCache| to remove on-disk data when +// we detect prefix file corruption. So make sure not to call |GetLookupCache| +// again in this function to avoid infinite loop. +void Classifier::DeleteTables(nsIFile* aDirectory, + const nsTArray<nsCString>& aTables) { + nsCOMPtr<nsIDirectoryEnumerator> entries; + nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(file))) && + 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); + + // Remove file extension if there's one. + int32_t dotPosition = leafName.RFind("."); + if (dotPosition >= 0) { + leafName.Truncate(dotPosition); + } + + if (!leafName.IsEmpty() && 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); +} + +// This function is I/O intensive. It should only be called before applying +// an update. +void Classifier::TableRequest(nsACString& aResult) { + MOZ_ASSERT(!NS_IsMainThread(), + "TableRequest must be called on the classifier worker thread."); + + // This function and all disk I/O are guaranteed to occur + // on the same thread so we don't need to add a lock around. + if (!mIsTableRequestResultOutdated) { + aResult = mTableRequestResult; + return; + } + + // We reset tables failed to load here; not just tables are corrupted. + // It is because this is a safer way to ensure Safe Browsing databases + // can be recovered from any bad situations. + nsTArray<nsCString> failedTables; + + // Load meta data from *.sbstore files in the root directory. + // Specifically for v4 tables. + nsCString v2Metadata; + nsresult rv = LoadHashStore(mRootStoreDirectory, v2Metadata, failedTables); + if (NS_SUCCEEDED(rv)) { + aResult.Append(v2Metadata); + } + + // Load meta data from *.metadata files in the root directory. + // Specifically for v4 tables. + nsCString v4Metadata; + rv = LoadMetadata(mRootStoreDirectory, v4Metadata, failedTables); + if (NS_SUCCEEDED(rv)) { + aResult.Append(v4Metadata); + } + + // Clear data for tables that we failed to open, a full update should + // be requested for those tables. + if (failedTables.Length() != 0) { + LOG(("Reset tables failed to open before applying an update")); + ResetTables(Clear_All, failedTables); + } + + // Update the TableRequest result in-memory cache. + mTableRequestResult = aResult; + mIsTableRequestResultOutdated = false; +} + +nsresult Classifier::CheckURIFragments( + const nsTArray<nsCString>& aSpecFragments, const nsACString& aTable, + LookupResultArray& aResults) { + // A URL can form up to 30 different fragments + MOZ_ASSERT(aSpecFragments.Length() != 0); + MOZ_ASSERT(aSpecFragments.Length() <= + (MAX_HOST_COMPONENTS * (MAX_PATH_COMPONENTS + 2))); + + if (LOG_ENABLED()) { + uint32_t urlIdx = 0; + for (uint32_t i = 1; i < aSpecFragments.Length(); i++) { + if (aSpecFragments[urlIdx].Length() < aSpecFragments[i].Length()) { + urlIdx = i; + } + } + LOG(("Checking table %s, URL is %s", aTable.BeginReading(), + aSpecFragments[urlIdx].get())); + } + + RefPtr<LookupCache> cache = GetLookupCache(aTable); + if (NS_WARN_IF(!cache)) { + return NS_ERROR_FAILURE; + } + + // Now check each lookup fragment against the entries in the DB. + for (uint32_t i = 0; i < aSpecFragments.Length(); i++) { + Completion lookupHash; + lookupHash.FromPlaintext(aSpecFragments[i]); + + bool has, confirmed; + uint32_t matchLength; + + nsresult rv = cache->Has(lookupHash, &has, &matchLength, &confirmed); + NS_ENSURE_SUCCESS(rv, rv); + + if (has) { + RefPtr<LookupResult> result = new LookupResult; + aResults.AppendElement(result); + + if (LOG_ENABLED()) { + nsAutoCString checking; + lookupHash.ToHexString(checking); + LOG(("Found a result in fragment %s, hash %s (%X)", + aSpecFragments[i].get(), checking.get(), lookupHash.ToUint32())); + LOG(("Result %s, match %d-bytes prefix", + confirmed ? "confirmed." : "Not confirmed.", matchLength)); + } + + result->hash.complete = lookupHash; + result->mConfirmed = confirmed; + result->mTableName.Assign(cache->TableName()); + result->mPartialHashLength = confirmed ? COMPLETE_SIZE : matchLength; + result->mProtocolV2 = LookupCache::Cast<LookupCacheV2>(cache); + } + } + + return NS_OK; +} + +static nsresult SwapDirectoryContent(nsIFile* aDir1, nsIFile* aDir2, + nsIFile* aParentDir, nsIFile* aTempDir) { + // Pre-condition: |aDir1| and |aDir2| are directory and their parent + // are both |aParentDir|. + // + // Post-condition: The locations where aDir1 and aDir2 point to will not + // change but their contents will be exchanged. If we failed + // to swap their content, everything will be rolled back. + + nsAutoCString tempDirName; + aTempDir->GetNativeLeafName(tempDirName); + + nsresult rv; + + nsAutoCString dirName1, dirName2; + aDir1->GetNativeLeafName(dirName1); + aDir2->GetNativeLeafName(dirName2); + + LOG(("Swapping directories %s and %s...", dirName1.get(), dirName2.get())); + + // 1. Rename "dirName1" to "temp" + rv = aDir1->RenameToNative(nullptr, tempDirName); + if (NS_FAILED(rv)) { + LOG(("Unable to rename %s to %s", dirName1.get(), tempDirName.get())); + return rv; // Nothing to roll back. + } + + // 1.1. Create a handle for temp directory. This is required since + // |nsIFile.rename| will not change the location where the + // object points to. + nsCOMPtr<nsIFile> tempDirectory; + rv = aParentDir->Clone(getter_AddRefs(tempDirectory)); + rv = tempDirectory->AppendNative(tempDirName); + + // 2. Rename "dirName2" to "dirName1". + rv = aDir2->RenameToNative(nullptr, dirName1); + if (NS_FAILED(rv)) { + LOG(("Failed to rename %s to %s. Rename temp directory back to %s", + dirName2.get(), dirName1.get(), dirName1.get())); + nsresult rbrv = tempDirectory->RenameToNative(nullptr, dirName1); + NS_ENSURE_SUCCESS(rbrv, rbrv); + return rv; + } + + // 3. Rename "temp" to "dirName2". + rv = tempDirectory->RenameToNative(nullptr, dirName2); + if (NS_FAILED(rv)) { + LOG(("Failed to rename temp directory to %s. ", dirName2.get())); + // We've done (1) renaming "dir1 to temp" and + // (2) renaming "dir2 to dir1" + // so the rollback is + // (1) renaming "dir1 to dir2" and + // (2) renaming "temp to dir1" + nsresult rbrv; // rollback result + rbrv = aDir1->RenameToNative(nullptr, dirName2); + NS_ENSURE_SUCCESS(rbrv, rbrv); + rbrv = tempDirectory->RenameToNative(nullptr, dirName1); + NS_ENSURE_SUCCESS(rbrv, rbrv); + return rv; + } + + return rv; +} + +void Classifier::RemoveUpdateIntermediaries() { + // Remove old LookupCaches. + mNewLookupCaches.Clear(); + + // Remove the "old" directory. (despite its looking-new name) + if (NS_FAILED(mUpdatingDirectory->Remove(true))) { + // If the directory is locked from removal for some reason, + // we will fail here and it doesn't matter until the next + // update. (the next udpate will fail due to the removable + // "safebrowsing-udpating" directory.) + LOG(("Failed to remove updating directory.")); + } +} + +void Classifier::CopyAndInvalidateFullHashCache() { + MOZ_ASSERT(!OnUpdateThread(), + "CopyAndInvalidateFullHashCache cannot be called on update thread " + "since it mutates mLookupCaches which is only safe on " + "worker thread."); + + // New lookup caches are built from disk, data likes cache which is + // generated online won't exist. We have to manually copy cache from + // old LookupCache to new LookupCache. + for (auto& newCache : mNewLookupCaches) { + for (auto& oldCache : mLookupCaches) { + if (oldCache->TableName() == newCache->TableName()) { + newCache->CopyFullHashCache(oldCache); + break; + } + } + } + + // Clear cache when update. + // Invalidate cache entries in CopyAndInvalidateFullHashCache because only + // at this point we will have cache data in LookupCache. + for (auto& newCache : mNewLookupCaches) { + newCache->InvalidateExpiredCacheEntries(); + } +} + +void Classifier::MergeNewLookupCaches() { + MOZ_ASSERT(!OnUpdateThread(), + "MergeNewLookupCaches cannot be called on update thread " + "since it mutates mLookupCaches which is only safe on " + "worker thread."); + + for (auto& newCache : mNewLookupCaches) { + // For each element in mNewLookCaches, it will be swapped with + // - An old cache in mLookupCache with the same table name or + // - nullptr (mLookupCache will be expaned) otherwise. + size_t swapIndex = 0; + for (; swapIndex < mLookupCaches.Length(); swapIndex++) { + if (mLookupCaches[swapIndex]->TableName() == newCache->TableName()) { + break; + } + } + if (swapIndex == mLookupCaches.Length()) { + mLookupCaches.AppendElement(nullptr); + } + + std::swap(mLookupCaches[swapIndex], newCache); + mLookupCaches[swapIndex]->UpdateRootDirHandle(mRootStoreDirectory); + } + + // At this point, mNewLookupCaches's length remains the same but + // will contain either old cache (override) or nullptr (append). +} + +nsresult Classifier::SwapInNewTablesAndCleanup() { + nsresult rv; + + // Step 1. Swap in on-disk tables. The idea of using "safebrowsing-backup" + // as the intermediary directory is we can get databases recovered if + // crash occurred in any step of the swap. (We will recover from + // "safebrowsing-backup" in OpenDb().) + rv = SwapDirectoryContent(mUpdatingDirectory, // contains new tables + mRootStoreDirectory, // contains old tables + mCacheDirectory, // common parent dir + mBackupDirectory); // intermediary dir for swap + if (NS_FAILED(rv)) { + LOG(("Failed to swap in on-disk tables.")); + RemoveUpdateIntermediaries(); + return rv; + } + + // Step 2. Merge mNewLookupCaches into mLookupCaches. The outdated + // LookupCaches will be stored in mNewLookupCaches and be cleaned + // up later. + MergeNewLookupCaches(); + + // Step 3. Re-generate active tables based on on-disk tables. + rv = RegenActiveTables(); + if (NS_FAILED(rv)) { + LOG(("Failed to re-generate active tables!")); + } + + // Step 4. Clean up intermediaries for update. + RemoveUpdateIntermediaries(); + + // Step 5. Invalidate cached tableRequest request. + mIsTableRequestResultOutdated = true; + + LOG(("Done swap in updated tables.")); + + return rv; +} + +void Classifier::FlushAndDisableAsyncUpdate() { + LOG(("Classifier::FlushAndDisableAsyncUpdate [%p, %p]", this, + mUpdateThread.get())); + + if (!mUpdateThread) { + LOG(("Async update has been disabled.")); + return; + } + + mUpdateThread->Shutdown(); + mUpdateThread = nullptr; +} + +nsresult Classifier::AsyncApplyUpdates(const TableUpdateArray& aUpdates, + const AsyncUpdateCallback& aCallback) { + LOG(("Classifier::AsyncApplyUpdates")); + + if (!mUpdateThread) { + LOG(("Async update has already been disabled.")); + return NS_ERROR_FAILURE; + } + + // Caller thread | Update thread + // -------------------------------------------------------- + // | ApplyUpdatesBackground + // (processing other task) | (bg-update done. ping back to caller + // thread) (processing other task) | idle... ApplyUpdatesForeground | + // callback | + + MOZ_ASSERT(mNewLookupCaches.IsEmpty(), + "There should be no leftovers from a previous update."); + + mUpdateInterrupted = false; + nsresult rv = + mRootStoreDirectory->Clone(getter_AddRefs(mRootStoreDirectoryForUpdate)); + if (NS_FAILED(rv)) { + LOG(("Failed to clone mRootStoreDirectory for update.")); + return rv; + } + + nsCOMPtr<nsIThread> callerThread = NS_GetCurrentThread(); + MOZ_ASSERT(!OnUpdateThread()); + + RefPtr<Classifier> self = this; + nsCOMPtr<nsIRunnable> bgRunnable = NS_NewRunnableFunction( + "safebrowsing::Classifier::AsyncApplyUpdates", + [self, aUpdates = aUpdates.Clone(), aCallback, callerThread]() mutable { + MOZ_ASSERT(self->OnUpdateThread(), "MUST be on update thread"); + + nsresult bgRv; + nsTArray<nsCString> failedTableNames; + + TableUpdateArray updates; + + // Make a copy of the array since we'll be removing entries as + // we process them on the background thread. + if (updates.AppendElements(std::move(aUpdates), fallible)) { + LOG(("Step 1. ApplyUpdatesBackground on update thread.")); + bgRv = self->ApplyUpdatesBackground(updates, failedTableNames); + } else { + LOG( + ("Step 1. Not enough memory to run ApplyUpdatesBackground on " + "update thread.")); + bgRv = NS_ERROR_OUT_OF_MEMORY; + } + + // Classifier is created in the worker thread and it has to be released + // in the worker thread(because of the constrain that LazyIdelThread has + // to be created and released in the same thread). We transfer the + // ownership to the caller thread here to gurantee that we don't release + // it in the udpate thread. + nsCOMPtr<nsIRunnable> fgRunnable = NS_NewRunnableFunction( + "safebrowsing::Classifier::AsyncApplyUpdates", + [self = std::move(self), aCallback, bgRv, + failedTableNames = std::move(failedTableNames), + callerThread]() mutable { + RefPtr<Classifier> classifier = std::move(self); + + MOZ_ASSERT(NS_GetCurrentThread() == callerThread, + "MUST be on caller thread"); + + LOG(("Step 2. ApplyUpdatesForeground on caller thread")); + nsresult rv = + classifier->ApplyUpdatesForeground(bgRv, failedTableNames); + + LOG(("Step 3. Updates applied! Fire callback.")); + aCallback(rv); + }); + + callerThread->Dispatch(fgRunnable, NS_DISPATCH_NORMAL); + }); + + return mUpdateThread->Dispatch(bgRunnable, NS_DISPATCH_NORMAL); +} + +nsresult Classifier::ApplyUpdatesBackground( + TableUpdateArray& aUpdates, nsTArray<nsCString>& aFailedTableNames) { + // |mUpdateInterrupted| is guaranteed to have been unset. + // If |mUpdateInterrupted| is set at any point, Reset() must have + // been called then we need to interrupt the update process. + // We only add checkpoints for non-trivial tasks. + + if (aUpdates.IsEmpty()) { + return NS_OK; + } + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return NS_ERROR_FAILURE; + } + + nsCString provider; + // Assume all TableUpdate objects should have the same provider. + urlUtil->GetTelemetryProvider(aUpdates[0]->TableName(), provider); + + Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_KEYED_UPDATE_TIME> + keyedTimer(provider); + + PRIntervalTime clockStart = 0; + if (LOG_ENABLED()) { + clockStart = PR_IntervalNow(); + } + + nsresult rv; + + // Check point 1: Copying files takes time so we check ShouldAbort() + // inside CopyInUseDirForUpdate(). + rv = CopyInUseDirForUpdate(); // i.e. mUpdatingDirectory will be setup. + if (NS_FAILED(rv)) { + LOG(("Failed to copy in-use directory for update.")); + return (rv == NS_ERROR_ABORT) ? NS_OK : rv; + } + + LOG(("Applying %zu table updates.", aUpdates.Length())); + + for (uint32_t i = 0; i < aUpdates.Length(); i++) { + RefPtr<const TableUpdate> update = aUpdates[i]; + if (!update) { + // Previous UpdateHashStore() may have consumed this update.. + continue; + } + + // Run all updates for one table + nsAutoCString updateTable(update->TableName()); + + // Check point 2: Processing downloaded data takes time. + if (ShouldAbort()) { + LOG(("Update is interrupted. Stop building new tables.")); + return NS_OK; + } + + // Will update the mirrored in-memory and on-disk databases. + if (TableUpdate::Cast<TableUpdateV2>(update)) { + rv = UpdateHashStore(aUpdates, updateTable); + } else { + rv = UpdateTableV4(aUpdates, updateTable); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG(("Failed to update table: %s", updateTable.get())); + // We don't quit the updating process immediately when we discover + // a failure. Instead, we continue to apply updates to the + // remaining tables to find other tables which may also fail to + // apply an update. This help us reset all the corrupted tables + // within a single update. + // Note that changes that result from successful updates don't take + // effect after the updating process is finished. This is because + // when an error occurs during the updating process, we ignore all + // changes that have happened during the udpating process. + aFailedTableNames.AppendElement(updateTable); + continue; + } + } + + if (!aFailedTableNames.IsEmpty()) { + RemoveUpdateIntermediaries(); + return NS_ERROR_FAILURE; + } + + if (LOG_ENABLED()) { + PRIntervalTime clockEnd = PR_IntervalNow(); + LOG(("update took %dms\n", + PR_IntervalToMilliseconds(clockEnd - clockStart))); + } + + return rv; +} + +nsresult Classifier::ApplyUpdatesForeground( + nsresult aBackgroundRv, const nsTArray<nsCString>& aFailedTableNames) { + if (ShouldAbort()) { + LOG(("Update is interrupted! Just remove update intermediaries.")); + RemoveUpdateIntermediaries(); + return NS_OK; + } + if (NS_SUCCEEDED(aBackgroundRv)) { + // Copy and Invalidate fullhash cache here because this call requires + // mLookupCaches which is only available on work-thread + CopyAndInvalidateFullHashCache(); + + return SwapInNewTablesAndCleanup(); + } + if (NS_ERROR_OUT_OF_MEMORY != aBackgroundRv) { + ResetTables(Clear_All, aFailedTableNames); + } + return aBackgroundRv; +} + +nsresult Classifier::ApplyFullHashes(ConstTableUpdateArray& aUpdates) { + MOZ_ASSERT(!OnUpdateThread(), + "ApplyFullHashes() MUST NOT be called on update thread"); + MOZ_ASSERT( + !NS_IsMainThread(), + "ApplyFullHashes() must be called on the classifier worker thread."); + + LOG(("Applying %zu table gethashes.", aUpdates.Length())); + + for (uint32_t i = 0; i < aUpdates.Length(); i++) { + nsresult rv = UpdateCache(aUpdates[i]); + NS_ENSURE_SUCCESS(rv, rv); + + aUpdates[i] = nullptr; + } + + return NS_OK; +} + +void Classifier::GetCacheInfo(const nsACString& aTable, + nsIUrlClassifierCacheInfo** aCache) { + RefPtr<const LookupCache> lookupCache = GetLookupCache(aTable); + if (!lookupCache) { + return; + } + + lookupCache->GetCacheInfo(aCache); +} + +void Classifier::DropStores() { + // See the comment in Classifier::Close() before adding anything here. + mLookupCaches.Clear(); +} + +nsresult Classifier::RegenActiveTables() { + if (ShouldAbort()) { + return NS_OK; // nothing to do, the classifier is done + } + + mActiveTablesCache.Clear(); + + // The extension of V2 and V4 prefix files is .vlpset + // We still check .pset here for legacy load. + nsTArray<nsCString> exts = {".vlpset"_ns, ".pset"_ns}; + nsTArray<nsCString> foundTables; + nsresult rv = ScanStoreDir(mRootStoreDirectory, exts, foundTables); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + // We don't have test tables on disk, add Moz built-in entries here + rv = AddMozEntries(foundTables); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + for (const auto& table : foundTables) { + RefPtr<const LookupCache> lookupCache = GetLookupCache(table); + if (!lookupCache) { + LOG(("Inactive table (no cache): %s", table.get())); + continue; + } + + if (!lookupCache->IsPrimed()) { + LOG(("Inactive table (cache not primed): %s", table.get())); + continue; + } + + LOG(("Active %s table: %s", + LookupCache::Cast<const LookupCacheV4>(lookupCache) ? "v4" : "v2", + table.get())); + + mActiveTablesCache.AppendElement(table); + } + + return NS_OK; +} + +nsresult Classifier::AddMozEntries(nsTArray<nsCString>& aTables) { + nsTArray<nsLiteralCString> tables = { + "moztest-phish-simple"_ns, "moztest-malware-simple"_ns, + "moztest-unwanted-simple"_ns, "moztest-harmful-simple"_ns, + "moztest-track-simple"_ns, "moztest-trackwhite-simple"_ns, + "moztest-block-simple"_ns, + }; + + for (const auto& table : tables) { + RefPtr<LookupCache> c = GetLookupCache(table, false); + RefPtr<LookupCacheV2> lookupCache = LookupCache::Cast<LookupCacheV2>(c); + if (!lookupCache || lookupCache->IsPrimed()) { + continue; + } + + aTables.AppendElement(table); + } + + return NS_OK; +} + +nsresult Classifier::ScanStoreDir(nsIFile* aDirectory, + const nsTArray<nsCString>& aExtensions, + nsTArray<nsCString>& aTables) { + nsCOMPtr<nsIDirectoryEnumerator> entries; + nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(file))) && + file) { + // If |file| is a directory, recurse to find its entries as well. + bool isDirectory; + if (NS_FAILED(file->IsDirectory(&isDirectory))) { + continue; + } + if (isDirectory) { + ScanStoreDir(file, aExtensions, aTables); + continue; + } + + nsAutoCString leafName; + rv = file->GetNativeLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + for (const auto& ext : aExtensions) { + if (StringEndsWith(leafName, ext)) { + aTables.AppendElement( + Substring(leafName, 0, leafName.Length() - strlen(ext.get()))); + break; + } + } + } + + return NS_OK; +} + +nsresult Classifier::ActiveTables(nsTArray<nsCString>& aTables) const { + aTables = mActiveTablesCache.Clone(); + 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) { + LOG(("Dumping raw table updates...")); + + DumpFailedUpdate(); + + 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); + + // Copy the in-use directory to a clean "failed update" directory. + nsCOMPtr<nsIFile> inUseDirectory; + if (NS_FAILED(mRootStoreDirectory->Clone(getter_AddRefs(inUseDirectory))) || + NS_FAILED(inUseDirectory->CopyToNative(nullptr, failedUpdatekDirName))) { + LOG(("Failed to move in-use to the \"failed update\" directory %s", + failedUpdatekDirName.get())); + return NS_ERROR_FAILURE; + } + + return rv; +} + +#endif // MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + +/** + * This function copies the files one by one to the destination folder. + * Before copying a file, it checks ::ShouldAbort and returns + * NS_ERROR_ABORT if the flag is set. + */ +nsresult Classifier::CopyDirectoryInterruptible(nsCOMPtr<nsIFile>& aDestDir, + nsCOMPtr<nsIFile>& aSourceDir) { + nsCOMPtr<nsIDirectoryEnumerator> entries; + nsresult rv = aSourceDir->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(entries); + + nsCOMPtr<nsIFile> source; + while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(source))) && + source) { + if (ShouldAbort()) { + LOG(("Update is interrupted. Aborting the directory copy")); + return NS_ERROR_ABORT; + } + + bool isDirectory; + rv = source->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + // If it is a directory, recursively copy the files inside the directory. + nsAutoCString leaf; + source->GetNativeLeafName(leaf); + MOZ_ASSERT(!leaf.IsEmpty()); + + nsCOMPtr<nsIFile> dest; + aDestDir->Clone(getter_AddRefs(dest)); + dest->AppendNative(leaf); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CopyDirectoryInterruptible(dest, source); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = source->CopyToNative(aDestDir, ""_ns); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // If the destination directory doesn't exist in the end, it means that the + // source directory is empty, we should copy the directory here. + bool exist; + rv = aDestDir->Exists(&exist); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exist) { + rv = aDestDir->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult Classifier::CopyInUseDirForUpdate() { + LOG(("Copy in-use directory content for update.")); + if (ShouldAbort()) { + return NS_ERROR_UC_UPDATE_SHUTDOWNING; + } + + // We copy everything from in-use directory to a temporary directory + // for updating. + + // Remove the destination directory first (just in case) the do the copy. + mUpdatingDirectory->Remove(true); + if (!mRootStoreDirectoryForUpdate) { + LOG(("mRootStoreDirectoryForUpdate is null.")); + return NS_ERROR_NULL_POINTER; + } + + nsresult rv = CopyDirectoryInterruptible(mUpdatingDirectory, + mRootStoreDirectoryForUpdate); + 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(TableUpdateArray& 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++) { + RefPtr<const TableUpdate> update = aUpdates[i]; + if (!update || !update->TableName().Equals(aTable)) { + continue; + } + if (update->Empty()) { + aUpdates[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) { + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return ""_ns; + } + + nsCString provider; + nsresult rv = urlUtil->GetProvider(aTableName, provider); + + return NS_SUCCEEDED(rv) ? provider : ""_ns; +} + +/* + * This will consume+delete updates from the passed nsTArray. + */ +nsresult Classifier::UpdateHashStore(TableUpdateArray& aUpdates, + const nsACString& aTable) { + if (ShouldAbort()) { + return NS_ERROR_UC_UPDATE_SHUTDOWNING; + } + + LOG(("Classifier::UpdateHashStore(%s)", PromiseFlatCString(aTable).get())); + + // moztest- tables don't support update because they are directly created + // in LookupCache. To test updates, use tables begin with "test-" instead. + // Also, recommend using 'test-' tables while writing testcases because + // it is more like the real world scenario. + MOZ_ASSERT(!nsUrlClassifierUtils::IsMozTestTable(aTable)); + + HashStore store(aTable, GetProvider(aTable), mUpdatingDirectory); + + if (!CheckValidUpdate(aUpdates, store.TableName())) { + return NS_OK; + } + + nsresult rv = store.Open(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = store.BeginUpdate(); + NS_ENSURE_SUCCESS(rv, rv); + + // Read the part of the store that is (only) in the cache + RefPtr<LookupCacheV2> lookupCacheV2; + { + RefPtr<LookupCache> lookupCache = + GetLookupCacheForUpdate(store.TableName()); + if (lookupCache) { + lookupCacheV2 = LookupCache::Cast<LookupCacheV2>(lookupCache); + } + } + if (!lookupCacheV2) { + return NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND; + } + + FallibleTArray<uint32_t> AddPrefixHashes; + FallibleTArray<nsCString> AddCompletesHashes; + rv = lookupCacheV2->GetPrefixes(AddPrefixHashes, AddCompletesHashes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = store.AugmentAdds(AddPrefixHashes, AddCompletesHashes); + NS_ENSURE_SUCCESS(rv, rv); + + AddPrefixHashes.Clear(); + AddCompletesHashes.Clear(); + + uint32_t applied = 0; + + for (uint32_t i = 0; i < aUpdates.Length(); i++) { + RefPtr<TableUpdate> update = aUpdates[i]; + if (!update || !update->TableName().Equals(store.TableName())) { + continue; + } + + RefPtr<TableUpdateV2> updateV2 = TableUpdate::Cast<TableUpdateV2>(update); + NS_ENSURE_TRUE(updateV2, NS_ERROR_UC_UPDATE_UNEXPECTED_VERSION); + + rv = store.ApplyUpdate(updateV2); + NS_ENSURE_SUCCESS(rv, rv); + + applied++; + + LOG(("Applied update to table %s:", store.TableName().get())); + LOG((" %d add chunks", updateV2->AddChunks().Length())); + LOG((" %zu add prefixes", updateV2->AddPrefixes().Length())); + LOG((" %zu add completions", updateV2->AddCompletes().Length())); + LOG((" %d sub chunks", updateV2->SubChunks().Length())); + LOG((" %zu sub prefixes", updateV2->SubPrefixes().Length())); + LOG((" %zu sub completions", updateV2->SubCompletes().Length())); + LOG((" %d add expirations", updateV2->AddExpirations().Length())); + LOG((" %d sub expirations", updateV2->SubExpirations().Length())); + + aUpdates[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((" %zu add prefixes", store.AddPrefixes().Length())); + LOG((" %zu add completions", store.AddCompletes().Length())); + LOG((" %d sub chunks", store.SubChunks().Length())); + LOG((" %zu sub prefixes", store.SubPrefixes().Length())); + LOG((" %zu 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 = lookupCacheV2->Build(store.AddPrefixes(), store.AddCompletes()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE); + + rv = lookupCacheV2->WriteFile(); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK); + + LOG(("Successfully updated %s", store.TableName().get())); + + return NS_OK; +} + +nsresult Classifier::UpdateTableV4(TableUpdateArray& aUpdates, + const nsACString& aTable) { + MOZ_ASSERT(!NS_IsMainThread(), + "UpdateTableV4 must be called on the classifier worker thread."); + if (ShouldAbort()) { + return NS_ERROR_UC_UPDATE_SHUTDOWNING; + } + + // moztest- tables don't support update, see comment in UpdateHashStore. + MOZ_ASSERT(!nsUrlClassifierUtils::IsMozTestTable(aTable)); + + LOG(("Classifier::UpdateTableV4(%s)", PromiseFlatCString(aTable).get())); + + if (!CheckValidUpdate(aUpdates, aTable)) { + return NS_OK; + } + + RefPtr<LookupCacheV4> lookupCacheV4; + { + RefPtr<LookupCache> lookupCache = GetLookupCacheForUpdate(aTable); + if (lookupCache) { + lookupCacheV4 = LookupCache::Cast<LookupCacheV4>(lookupCache); + } + } + if (!lookupCacheV4) { + return NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND; + } + + 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; + + RefPtr<const TableUpdateV4> lastAppliedUpdate = nullptr; + for (uint32_t i = 0; i < aUpdates.Length(); i++) { + RefPtr<TableUpdate> update = aUpdates[i]; + if (!update || !update->TableName().Equals(aTable)) { + continue; + } + + RefPtr<TableUpdateV4> updateV4 = TableUpdate::Cast<TableUpdateV4>(update); + NS_ENSURE_TRUE(updateV4, NS_ERROR_UC_UPDATE_UNEXPECTED_VERSION); + + if (updateV4->IsFullUpdate()) { + input->Clear(); + output->Clear(); + rv = lookupCacheV4->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()) { + lookupCacheV4->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 = lookupCacheV4->ApplyUpdate(updateV4, *input, *output); + if (NS_FAILED(rv)) { + return rv; + } + + input->Clear(); + } + + // Keep track of the last applied update. + lastAppliedUpdate = updateV4; + + aUpdates[i] = nullptr; + } + + rv = lookupCacheV4->Build(*output); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE); + + rv = lookupCacheV4->WriteFile(); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK); + + if (lastAppliedUpdate) { + LOG(("Write meta data of the last applied update.")); + rv = lookupCacheV4->WriteMetadata(lastAppliedUpdate); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK); + } + + LOG(("Successfully updated %s\n", PromiseFlatCString(aTable).get())); + + return NS_OK; +} + +nsresult Classifier::UpdateCache(RefPtr<const TableUpdate> aUpdate) { + if (!aUpdate) { + return NS_OK; + } + + nsAutoCString table(aUpdate->TableName()); + LOG(("Classifier::UpdateCache(%s)", table.get())); + + RefPtr<LookupCache> lookupCache = GetLookupCache(table); + if (!lookupCache) { + return NS_ERROR_FAILURE; + } + + RefPtr<LookupCacheV2> lookupV2 = + LookupCache::Cast<LookupCacheV2>(lookupCache); + if (lookupV2) { + RefPtr<const TableUpdateV2> updateV2 = + TableUpdate::Cast<TableUpdateV2>(aUpdate); + lookupV2->AddGethashResultToCache(updateV2->AddCompletes(), + updateV2->MissPrefixes()); + } else { + RefPtr<LookupCacheV4> lookupV4 = + LookupCache::Cast<LookupCacheV4>(lookupCache); + if (!lookupV4) { + return NS_ERROR_FAILURE; + } + + RefPtr<const TableUpdateV4> updateV4 = + TableUpdate::Cast<TableUpdateV4>(aUpdate); + lookupV4->AddFullHashResponseToCache(updateV4->FullHashResponse()); + } + +#if defined(DEBUG) + lookupCache->DumpCache(); +#endif + + return NS_OK; +} + +RefPtr<LookupCache> Classifier::GetLookupCache(const nsACString& aTable, + bool aForUpdate) { + // GetLookupCache(aForUpdate==true) can only be called on update thread. + MOZ_ASSERT_IF(aForUpdate, OnUpdateThread()); + + LookupCacheArray& lookupCaches = + aForUpdate ? mNewLookupCaches : mLookupCaches; + auto& rootStoreDirectory = + aForUpdate ? mUpdatingDirectory : mRootStoreDirectory; + + for (auto c : lookupCaches) { + if (c->TableName().Equals(aTable)) { + return c; + } + } + + // We don't want to create lookupcache when shutdown is already happening. + if (ShouldAbort()) { + return nullptr; + } + + // 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'. + RefPtr<LookupCache> cache; + nsCString provider = GetProvider(aTable); + + // Google requests SafeBrowsing related feature should only be enabled when + // the databases are update-to-date. Since we disable Safe Browsing update in + // Safe Mode, ignore tables provided by Google to ensure we don't show + // outdated warnings. + if (nsUrlClassifierUtils::IsInSafeMode()) { + if (provider.EqualsASCII("google") || provider.EqualsASCII("google4")) { + return nullptr; + } + } + + if (StringEndsWith(aTable, "-proto"_ns)) { + cache = new LookupCacheV4(aTable, provider, rootStoreDirectory); + } else { + cache = new LookupCacheV2(aTable, provider, rootStoreDirectory); + } + + nsresult rv = cache->Init(); + if (NS_FAILED(rv)) { + return nullptr; + } + rv = cache->Open(); + if (NS_SUCCEEDED(rv)) { + lookupCaches.AppendElement(cache); + return cache; + } + + // At this point we failed to open LookupCache. + // + // GetLookupCache for update and for other usage will run on update thread + // and worker thread respectively (Bug 1339760). Removing stuff only in + // their own realms potentially increases the concurrency. + + if (aForUpdate) { + // Remove intermediaries no matter if it's due to file corruption or not. + RemoveUpdateIntermediaries(); + return nullptr; + } + + // Non-update case. + if (rv == NS_ERROR_FILE_CORRUPTED) { + // Remove all the on-disk data when the table's prefix file is corrupted. + LOG(("Failed to get prefixes from file for table %s, delete on-disk data!", + aTable.BeginReading())); + + DeleteTables(mRootStoreDirectory, nsTArray<nsCString>{nsCString(aTable)}); + } + return nullptr; +} + +nsresult Classifier::ReadNoiseEntries(const Prefix& aPrefix, + const nsACString& aTableName, + uint32_t aCount, + PrefixArray& aNoiseEntries) { + RefPtr<LookupCache> cache = GetLookupCache(aTableName); + if (!cache) { + return NS_ERROR_FAILURE; + } + + RefPtr<LookupCacheV2> cacheV2 = LookupCache::Cast<LookupCacheV2>(cache); + RefPtr<LookupCacheV4> cacheV4 = LookupCache::Cast<LookupCacheV4>(cache); + MOZ_ASSERT_IF(cacheV2, !cacheV4); + + if (cache->PrefixLength() == 0) { + NS_WARNING("Could not find prefix in PrefixSet during noise lookup"); + return NS_ERROR_FAILURE; + } + + // We do not want to simply pick random prefixes, because this would allow + // averaging out the noise by analysing the traffic from Firefox users. + // Instead, we ensure the 'noise' is the same for the same prefix by seeding + // the random number generator with the prefix. We prefer not to use rand() + // which isn't thread safe, and the reseeding of which could trip up other + // parts othe code that expect actual random numbers. + // Here we use a simple LCG (Linear Congruential Generator) to generate + // random numbers. We seed the LCG with the prefix we are generating noise + // for. + // http://en.wikipedia.org/wiki/Linear_congruential_generator + + uint32_t m = cache->PrefixLength(); + uint32_t a = aCount % m; + uint32_t idx = aPrefix.ToUint32() % m; + + for (size_t i = 0; i < aCount; i++) { + idx = (a * idx + a) % m; + + uint32_t hash; + + nsresult rv; + if (cacheV2) { + rv = cacheV2->GetPrefixByIndex(idx, &hash); + } else { + // We don't add noises for variable length prefix because of simplicity, + // so we will only get fixed length prefix (4 bytes). + rv = cacheV4->GetFixedLengthPrefixByIndex(idx, &hash); + } + + if (NS_FAILED(rv)) { + NS_WARNING( + "Could not find the target prefix in PrefixSet during noise lookup"); + return NS_ERROR_FAILURE; + } + + Prefix newPrefix; + // In the case V4 little endian, we did swapping endian when converting from + // char* to int, should revert endian to make sure we will send hex string + // correctly See https://bugzilla.mozilla.org/show_bug.cgi?id=1283007#c23 + if (!cacheV2 && !bool(MOZ_BIG_ENDIAN())) { + hash = NativeEndian::swapFromBigEndian(hash); + } + + newPrefix.FromUint32(hash); + if (newPrefix != aPrefix) { + aNoiseEntries.AppendElement(newPrefix); + } + } + + return NS_OK; +} + +nsresult Classifier::LoadHashStore(nsIFile* aDirectory, nsACString& aResult, + nsTArray<nsCString>& aFailedTableNames) { + nsTArray<nsCString> tables; + nsTArray<nsCString> exts = {V2_METADATA_SUFFIX}; + + nsresult rv = ScanStoreDir(mRootStoreDirectory, exts, tables); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (const auto& table : tables) { + HashStore store(table, GetProvider(table), mRootStoreDirectory); + + nsresult rv = store.Open(); + if (NS_FAILED(rv) || !GetLookupCache(table)) { + // TableRequest is called right before applying an update. + // If we cannot retrieve metadata for a given table or we fail to + // load the prefixes for a table, reset the table to esnure we + // apply a full update to the table. + LOG(("Failed to get metadata for v2 table %s", table.get())); + aFailedTableNames.AppendElement(table); + continue; + } + + ChunkSet& adds = store.AddChunks(); + ChunkSet& subs = store.SubChunks(); + + // Open HashStore will always succeed even that is not a v2 table. + // So exception tables without add and sub chunks. + if (adds.Length() == 0 && subs.Length() == 0) { + continue; + } + + aResult.Append(store.TableName()); + aResult.Append(';'); + + 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'); + } + + return rv; +} + +nsresult Classifier::LoadMetadata(nsIFile* aDirectory, nsACString& aResult, + nsTArray<nsCString>& aFailedTableNames) { + nsTArray<nsCString> tables; + nsTArray<nsCString> exts = {V4_METADATA_SUFFIX}; + + nsresult rv = ScanStoreDir(mRootStoreDirectory, exts, tables); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (const auto& table : tables) { + RefPtr<LookupCache> c = GetLookupCache(table); + RefPtr<LookupCacheV4> lookupCacheV4 = LookupCache::Cast<LookupCacheV4>(c); + + if (!lookupCacheV4) { + aFailedTableNames.AppendElement(table); + continue; + } + + nsCString state, sha256; + rv = lookupCacheV4->LoadMetadata(state, sha256); + Telemetry::Accumulate(Telemetry::URLCLASSIFIER_VLPS_METADATA_CORRUPT, + rv == NS_ERROR_FILE_CORRUPTED); + if (NS_FAILED(rv)) { + LOG(("Failed to get metadata for v4 table %s", table.get())); + aFailedTableNames.AppendElement(table); + continue; + } + + // The state might include '\n' so that we have to encode. + nsAutoCString stateBase64; + rv = Base64Encode(state, stateBase64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString checksumBase64; + rv = Base64Encode(sha256, checksumBase64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("Appending state '%s' and checksum '%s' for table %s", + stateBase64.get(), checksumBase64.get(), table.get())); + + aResult.AppendPrintf("%s;%s:%s\n", table.get(), stateBase64.get(), + checksumBase64.get()); + } + + return rv; +} + +bool Classifier::ShouldAbort() const { + return mIsClosed || nsUrlClassifierDBService::ShutdownHasStarted() || + (mUpdateInterrupted && mUpdateThread->IsOnCurrentThread()); +} + +} // namespace safebrowsing +} // namespace mozilla diff --git a/toolkit/components/url-classifier/Classifier.h b/toolkit/components/url-classifier/Classifier.h new file mode 100644 index 0000000000..a9af2736d2 --- /dev/null +++ b/toolkit/components/url-classifier/Classifier.h @@ -0,0 +1,258 @@ +//* -*- 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/. */ + +#ifndef Classifier_h__ +#define Classifier_h__ + +#include "Entries.h" +#include "HashStore.h" +#include "ProtocolParser.h" +#include "LookupCache.h" +#include "mozilla/Atomics.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" + +namespace mozilla { + +class LazyIdleThread; + +namespace safebrowsing { + +/** + * Maintains the stores and LookupCaches for the url classifier. + */ +class Classifier { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Classifier); + + Classifier(); + + nsresult Open(nsIFile& aCacheDirectory); + void Close(); + void Reset(); // Not including any intermediary for update. + + /** + * Clear data for specific tables. + * If ClearType is Clear_Cache, this function will only clear cache in lookup + * cache, otherwise, it will clear data in lookup cache and data stored on + * disk. + */ + enum ClearType { + Clear_Cache, + Clear_All, + }; + void ResetTables(ClearType aType, const nsTArray<nsCString>& aTables); + + /** + * Get the list of active tables and their chunks in a format + * suitable for an update request. + */ + void TableRequest(nsACString& aResult); + + /* + * Get all tables that we know about. + */ + nsresult ActiveTables(nsTArray<nsCString>& aTables) const; + + /** + * Check URL fragments against a specified table. + * The fragments should be generated by |LookupCache::GetLookupFragments| + */ + nsresult CheckURIFragments(const nsTArray<nsCString>& aSpecFragments, + const nsACString& table, + LookupResultArray& aResults); + + /** + * Asynchronously apply updates to the in-use databases. When the + * update is complete, the caller can be notified by |aCallback|, which + * will occur on the caller thread. + */ + using AsyncUpdateCallback = std::function<void(nsresult)>; + nsresult AsyncApplyUpdates(const TableUpdateArray& aUpdates, + const AsyncUpdateCallback& aCallback); + + /** + * Wait until the ongoing async update is finished and callback + * is fired. Once this function returns, AsyncApplyUpdates is + * no longer available. + */ + void FlushAndDisableAsyncUpdate(); + + /** + * Apply full hashes retrived from gethash to cache. + */ + nsresult ApplyFullHashes(ConstTableUpdateArray& aUpdates); + + /* + * 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); + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + nsresult DumpRawTableUpdates(const nsACString& aRawUpdates); +#endif + + static void SplitTables(const nsACString& str, nsTArray<nsCString>& tables); + + // Given a root store directory, return a private store directory + // based on the table name. To avoid migration issue, the private + // store directory is only different from root directory for V4 tables. + // + // For V4 tables (suffixed by '-proto'), the private directory would + // be [root directory path]/[provider]. The provider of V4 tables is + // 'google4'. + // + // Note that if the table name is not owned by any provider, just use + // the root directory. + static nsresult GetPrivateStoreDirectory(nsIFile* aRootStoreDirectory, + const nsACString& aTableName, + const nsACString& aProvider, + nsIFile** aPrivateStoreDirectory); + + // Swap in in-memory and on-disk database and remove all + // update intermediaries. + nsresult SwapInNewTablesAndCleanup(); + + RefPtr<LookupCache> GetLookupCache(const nsACString& aTable, + bool aForUpdate = false); + + void GetCacheInfo(const nsACString& aTable, + nsIUrlClassifierCacheInfo** aCache); + + bool OnUpdateThread() const; + + private: + ~Classifier(); + + void DropStores(); + void DeleteTables(nsIFile* aDirectory, const nsTArray<nsCString>& aTables); + + nsresult CreateStoreDirectory(); + nsresult SetupPathNames(); + nsresult RecoverBackups(); + nsresult CleanToDelete(); + nsresult CopyInUseDirForUpdate(); + nsresult CopyDirectoryInterruptible(nsCOMPtr<nsIFile>& aDestDir, + nsCOMPtr<nsIFile>& aSourceDir); + nsresult RegenActiveTables(); + + void MergeNewLookupCaches(); // Merge mNewLookupCaches into mLookupCaches. + + void CopyAndInvalidateFullHashCache(); + + // Remove any intermediary for update, including in-memory + // and on-disk data. + void RemoveUpdateIntermediaries(); + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + already_AddRefed<nsIFile> GetFailedUpdateDirectroy(); + nsresult DumpFailedUpdate(); +#endif + + nsresult ScanStoreDir(nsIFile* aDirectory, + const nsTArray<nsCString>& aExtensions, + nsTArray<nsCString>& aTables); + + nsresult UpdateHashStore(TableUpdateArray& aUpdates, + const nsACString& aTable); + + nsresult UpdateTableV4(TableUpdateArray& aUpdates, const nsACString& aTable); + + nsresult UpdateCache(RefPtr<const TableUpdate> aUpdates); + + RefPtr<LookupCache> GetLookupCacheForUpdate(const nsACString& aTable) { + return GetLookupCache(aTable, true); + } + + RefPtr<LookupCache> GetLookupCacheFrom(const nsACString& aTable, + LookupCacheArray& aLookupCaches, + nsIFile* aRootStoreDirectory); + + bool CheckValidUpdate(TableUpdateArray& aUpdates, const nsACString& aTable); + + nsresult LoadHashStore(nsIFile* aDirectory, nsACString& aResult, + nsTArray<nsCString>& aFailedTableNames); + + nsresult LoadMetadata(nsIFile* aDirectory, nsACString& aResult, + nsTArray<nsCString>& aFailedTableNames); + + static nsCString GetProvider(const nsACString& aTableName); + + /** + * The "background" part of ApplyUpdates. Once the background update + * is called, the foreground update has to be called along with the + * background result no matter whether the background update is + * successful or not. + */ + nsresult ApplyUpdatesBackground(TableUpdateArray& aUpdates, + nsTArray<nsCString>& aFailedTableNames); + + /** + * The "foreground" part of ApplyUpdates. The in-use data (in-memory and + * on-disk) will be touched so this MUST be mutually exclusive to other + * member functions. + * + * If |aBackgroundRv| is successful, the return value is the result of + * bringing stuff to the foreground. Otherwise, the foreground table may + * be reset according to the background update failed reason and + * |aBackgroundRv| will be returned to forward the background update result. + */ + nsresult ApplyUpdatesForeground(nsresult aBackgroundRv, + const nsTArray<nsCString>& aFailedTableNames); + + // Used by worker thread and update thread to abort current operation. + bool ShouldAbort() const; + + // Add built-in entries for testing. + nsresult AddMozEntries(nsTArray<nsCString>& aTables); + + // Remove test files if exist + nsresult ClearLegacyFiles(); + + // Root dir of the Local profile. + nsCOMPtr<nsIFile> mCacheDirectory; + // Main directory where to store the databases. + nsCOMPtr<nsIFile> mRootStoreDirectory; + // Used for atomically updating the other dirs. + nsCOMPtr<nsIFile> mBackupDirectory; + nsCOMPtr<nsIFile> mUpdatingDirectory; // For update only. + nsCOMPtr<nsIFile> mToDeleteDirectory; + LookupCacheArray mLookupCaches; // For query only. + nsTArray<nsCString> mActiveTablesCache; + uint32_t mHashKey; + + // In-memory cache for the result of TableRequest. See + // nsIUrlClassifierDBService.getTables for the format. + nsCString mTableRequestResult; + + // Whether mTableRequestResult is outdated and needs to + // be reloaded from disk. + bool mIsTableRequestResultOutdated; + + // The copy of mLookupCaches for update only. + LookupCacheArray mNewLookupCaches; + + // True when Reset() is called. + bool mUpdateInterrupted; + + // True once CLose() has been called + Atomic<bool> mIsClosed; + + RefPtr<LazyIdleThread> mUpdateThread; // For async update. + + // Identical to mRootStoreDirectory but for update only because + // nsIFile is not thread safe and mRootStoreDirectory needs to + // be accessed in CopyInUseDirForUpdate(). + // It will be initialized right before update on the worker thread. + nsCOMPtr<nsIFile> mRootStoreDirectoryForUpdate; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/Entries.h b/toolkit/components/url-classifier/Entries.h new file mode 100644 index 0000000000..713867c2b7 --- /dev/null +++ b/toolkit/components/url-classifier/Entries.h @@ -0,0 +1,347 @@ +//* -*- 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/. */ + +// This header file defines the storage types of the actual safebrowsing +// chunk data, which may be either 32-bit hashes or complete 256-bit hashes. +// Chunk numbers are represented in ChunkSet.h. + +#ifndef SBEntries_h__ +#define SBEntries_h__ + +#include "nsTArray.h" +#include "nsString.h" +#include "nsICryptoHash.h" +#include "nsNetUtil.h" +#include "nsIOutputStream.h" +#include "nsClassHashtable.h" +#include "nsComponentManagerUtils.h" +#include "nsTHashMap.h" +#include "plbase64.h" + +namespace mozilla { +namespace safebrowsing { + +#define PREFIX_SIZE 4 +#define COMPLETE_SIZE 32 + +// This is the struct that contains 4-byte hash prefixes. +template <uint32_t S, class Comparator> +struct SafebrowsingHash { + static_assert(S >= 4, "The SafebrowsingHash should be at least 4 bytes."); + + static const uint32_t sHashSize = S; + typedef SafebrowsingHash<S, Comparator> self_type; + uint8_t buf[S]; + + nsresult FromPlaintext(const nsACString& aPlainText) { + // From the protocol doc: + // Each entry in the chunk is composed + // of the SHA 256 hash of a suffix/prefix expression. + nsresult rv; + nsCOMPtr<nsICryptoHash> hash = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hash->Init(nsICryptoHash::SHA256); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hash->Update( + reinterpret_cast<const uint8_t*>(aPlainText.BeginReading()), + aPlainText.Length()); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString hashed; + rv = hash->Finish(false, hashed); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(hashed.Length() >= sHashSize, + "not enough characters in the hash"); + + memcpy(buf, hashed.BeginReading(), sHashSize); + + return NS_OK; + } + + void Assign(const nsACString& aStr) { + NS_ASSERTION(aStr.Length() >= sHashSize, + "string must be at least sHashSize characters long"); + memcpy(buf, aStr.BeginReading(), sHashSize); + } + + int Compare(const self_type& aOther) const { + return Comparator::Compare(buf, aOther.buf); + } + + bool operator==(const self_type& aOther) const { + return Comparator::Compare(buf, aOther.buf) == 0; + } + + bool operator!=(const self_type& aOther) const { + return Comparator::Compare(buf, aOther.buf) != 0; + } + + bool operator<(const self_type& aOther) const { + return Comparator::Compare(buf, aOther.buf) < 0; + } + + void ToString(nsACString& aStr) const { + // Base64 represents 6-bits data as 8-bits output. + uint32_t len = ((sHashSize + 2) / 3) * 4; + + aStr.SetLength(len); + PL_Base64Encode((char*)buf, sHashSize, aStr.BeginWriting()); + MOZ_ASSERT(aStr.BeginReading()[len] == '\0'); + } + + nsCString ToString() const { + nsAutoCString str; + ToString(str); + return std::move(str); + } + + void ToHexString(nsACString& aStr) const { + static const char* const lut = "0123456789ABCDEF"; + // 32 bytes is the longest hash + size_t len = 32; + + aStr.SetCapacity(2 * len); + for (size_t i = 0; i < len; ++i) { + const char c = static_cast<char>(buf[i]); + aStr.Append(lut[(c >> 4) & 0x0F]); + aStr.Append(lut[c & 15]); + } + } + + uint32_t ToUint32() const { + uint32_t n; + memcpy(&n, buf, sizeof(n)); + return n; + } + void FromUint32(uint32_t aHash) { memcpy(buf, &aHash, sizeof(aHash)); } +}; + +class PrefixComparator { + public: + static int Compare(const uint8_t* a, const uint8_t* b) { + uint32_t first; + memcpy(&first, a, sizeof(uint32_t)); + + uint32_t second; + memcpy(&second, b, sizeof(uint32_t)); + + if (first > second) { + return 1; + } else if (first == second) { + return 0; + } else { + return -1; + } + } +}; +// Use this for 4-byte hashes +typedef SafebrowsingHash<PREFIX_SIZE, PrefixComparator> Prefix; +typedef nsTArray<Prefix> PrefixArray; + +class CompletionComparator { + public: + static int Compare(const uint8_t* a, const uint8_t* b) { + return memcmp(a, b, COMPLETE_SIZE); + } +}; +// Use this for 32-byte hashes +typedef SafebrowsingHash<COMPLETE_SIZE, CompletionComparator> Completion; +typedef nsTArray<Completion> CompletionArray; + +struct AddPrefix { + // The truncated hash. + Prefix prefix; + // The chunk number to which it belongs. + uint32_t addChunk; + + AddPrefix() : addChunk(0) {} + + // Returns the chunk number. + uint32_t Chunk() const { return addChunk; } + const Prefix& PrefixHash() const { return prefix; } + + template <class T> + int Compare(const T& other) const { + int cmp = prefix.Compare(other.PrefixHash()); + if (cmp != 0) { + return cmp; + } + return addChunk - other.addChunk; + } +}; + +struct AddComplete { + Completion complete; + uint32_t addChunk; + + AddComplete() : addChunk(0) {} + + uint32_t Chunk() const { return addChunk; } + // The 4-byte prefix of the sha256 hash. + uint32_t ToUint32() const { return complete.ToUint32(); } + // The 32-byte sha256 hash. + const Completion& CompleteHash() const { return complete; } + + template <class T> + int Compare(const T& other) const { + int cmp = complete.Compare(other.CompleteHash()); + if (cmp != 0) { + return cmp; + } + return addChunk - other.addChunk; + } + + bool operator!=(const AddComplete& aOther) const { + if (addChunk != aOther.addChunk) { + return true; + } + return complete != aOther.complete; + } +}; + +struct SubPrefix { + // The hash to subtract. + Prefix prefix; + // The chunk number of the add chunk to which the hash belonged. + uint32_t addChunk; + // The chunk number of this sub chunk. + uint32_t subChunk; + + SubPrefix() : addChunk(0), subChunk(0) {} + + uint32_t Chunk() const { return subChunk; } + uint32_t AddChunk() const { return addChunk; } + const Prefix& PrefixHash() const { return prefix; } + + template <class T> + // Returns 0 if and only if the chunks are the same in every way. + int Compare(const T& aOther) const { + int cmp = prefix.Compare(aOther.PrefixHash()); + if (cmp != 0) return cmp; + if (addChunk != aOther.addChunk) return addChunk - aOther.addChunk; + return subChunk - aOther.subChunk; + } + + template <class T> + int CompareAlt(const T& aOther) const { + Prefix other; + other.FromUint32(aOther.ToUint32()); + int cmp = prefix.Compare(other); + if (cmp != 0) return cmp; + return addChunk - aOther.addChunk; + } +}; + +struct SubComplete { + Completion complete; + uint32_t addChunk; + uint32_t subChunk; + + SubComplete() : addChunk(0), subChunk(0) {} + + uint32_t Chunk() const { return subChunk; } + uint32_t AddChunk() const { return addChunk; } + const Completion& CompleteHash() const { return complete; } + // The 4-byte prefix of the sha256 hash. + uint32_t ToUint32() const { return complete.ToUint32(); } + + int Compare(const SubComplete& aOther) const { + int cmp = complete.Compare(aOther.complete); + if (cmp != 0) return cmp; + if (addChunk != aOther.addChunk) return addChunk - aOther.addChunk; + return subChunk - aOther.subChunk; + } +}; + +typedef FallibleTArray<AddPrefix> AddPrefixArray; +typedef FallibleTArray<AddComplete> AddCompleteArray; +typedef FallibleTArray<SubPrefix> SubPrefixArray; +typedef FallibleTArray<SubComplete> SubCompleteArray; +typedef FallibleTArray<Prefix> MissPrefixArray; + +/** + * Sort an array of store entries. + */ +template <class T, class Alloc> +void EntrySort(nsTArray_Impl<T, Alloc>& aArray) { + aArray.Sort([](const T& aA, const T& aB) { return aA.Compare(aB); }); +} + +template <class T, class Alloc> +nsresult ReadTArray(nsIInputStream* aStream, nsTArray_Impl<T, Alloc>* aArray, + uint32_t aNumElements) { + if (!aArray->SetLength(aNumElements, fallible)) return NS_ERROR_OUT_OF_MEMORY; + + void* buffer = aArray->Elements(); + nsresult rv = + NS_ReadInputStreamToBuffer(aStream, &buffer, (aNumElements * sizeof(T))); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +template <class T, class Alloc> +nsresult WriteTArray(nsIOutputStream* aStream, + nsTArray_Impl<T, Alloc>& aArray) { + uint32_t written; + return aStream->Write(reinterpret_cast<char*>(aArray.Elements()), + aArray.Length() * sizeof(T), &written); +} + +typedef nsClassHashtable<nsUint32HashKey, nsCString> PrefixStringMap; + +typedef nsTHashMap<nsCStringHashKey, int64_t> TableFreshnessMap; + +typedef nsCStringHashKey FullHashString; + +typedef nsTHashMap<FullHashString, int64_t> FullHashExpiryCache; + +struct CachedFullHashResponse { + int64_t negativeCacheExpirySec; + + // Map contains all matches found in Fullhash response, this field might be + // empty. + FullHashExpiryCache fullHashes; + + CachedFullHashResponse& operator=(const CachedFullHashResponse& aOther) { + negativeCacheExpirySec = aOther.negativeCacheExpirySec; + + fullHashes = aOther.fullHashes.Clone(); + + return *this; + } + + bool operator==(const CachedFullHashResponse& aOther) const { + if (negativeCacheExpirySec != aOther.negativeCacheExpirySec || + fullHashes.Count() != aOther.fullHashes.Count()) { + return false; + } + for (const auto& entry : fullHashes) { + if (entry.GetData() != aOther.fullHashes.Get(entry.GetKey())) { + return false; + } + } + return true; + } +}; + +typedef nsClassHashtable<nsUint32HashKey, CachedFullHashResponse> + FullHashResponseMap; + +template <class T> +void CopyClassHashTable(const T& aSource, T& aDestination) { + for (const auto& entry : aSource) { + auto value = aDestination.GetOrInsertNew(entry.GetKey()); + *value = *(entry.GetData()); + } +} + +} // namespace safebrowsing +} // namespace mozilla + +#endif // SBEntries_h__ diff --git a/toolkit/components/url-classifier/HashStore.cpp b/toolkit/components/url-classifier/HashStore.cpp new file mode 100644 index 0000000000..146f8ac104 --- /dev/null +++ b/toolkit/components/url-classifier/HashStore.cpp @@ -0,0 +1,1175 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// Originally based on Chrome sources: +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "HashStore.h" +#include "nsICryptoHash.h" +#include "nsISeekableStream.h" +#include "nsNetUtil.h" +#include "nsCheckSummedOutputStream.h" +#include "prio.h" +#include "mozilla/Logging.h" +#include "zlib.h" +#include "Classifier.h" +#include "nsUrlClassifierDBService.h" +#include "mozilla/Telemetry.h" + +// Main store for SafeBrowsing protocol data. We store +// known add/sub chunks, prefixes and completions in memory +// during an update, and serialize to disk. +// We do not store the add prefixes, those are retrieved by +// decompressing the PrefixSet cache whenever we need to apply +// an update. +// +// byte slicing: Many of the 4-byte values stored here are strongly +// correlated in the upper bytes, and uncorrelated in the lower +// bytes. Because zlib/DEFLATE requires match lengths of at least +// 3 to achieve good compression, and we don't get those if only +// the upper 16-bits are correlated, it is worthwhile to slice 32-bit +// values into 4 1-byte slices and compress the slices individually. +// The slices corresponding to MSBs will compress very well, and the +// slice corresponding to LSB almost nothing. Because of this, we +// only apply DEFLATE to the 3 most significant bytes, and store the +// LSB uncompressed. +// +// byte sliced (numValues) data format: +// uint32_t compressed-size +// compressed-size bytes zlib DEFLATE data +// 0...numValues byte MSB of 4-byte numValues data +// uint32_t compressed-size +// compressed-size bytes zlib DEFLATE data +// 0...numValues byte 2nd byte of 4-byte numValues data +// uint32_t compressed-size +// compressed-size bytes zlib DEFLATE data +// 0...numValues byte 3rd byte of 4-byte numValues data +// 0...numValues byte LSB of 4-byte numValues data +// +// Store data format: +// uint32_t magic +// uint32_t version +// uint32_t numAddChunks +// uint32_t numSubChunks +// uint32_t numAddPrefixes +// uint32_t numSubPrefixes +// uint32_t numAddCompletes +// uint32_t numSubCompletes +// 0...numAddChunks uint32_t addChunk +// 0...numSubChunks uint32_t subChunk +// byte sliced (numAddPrefixes) uint32_t add chunk of AddPrefixes +// byte sliced (numSubPrefixes) uint32_t add chunk of SubPrefixes +// byte sliced (numSubPrefixes) uint32_t sub chunk of SubPrefixes +// byte sliced (numSubPrefixes) uint32_t SubPrefixes +// byte sliced (numAddCompletes) uint32_t add chunk of AddCompletes +// 0...numSubCompletes 32-byte Completions + uint32_t addChunk +// + uint32_t subChunk +// 16-byte MD5 of all preceding data + +// Name of the SafeBrowsing store +#define STORE_SUFFIX ".sbstore" + +// 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) + +namespace mozilla::safebrowsing { + +const uint32_t STORE_MAGIC = 0x1231af3b; +const uint32_t CURRENT_VERSION = 4; + +nsresult TableUpdateV2::NewAddPrefix(uint32_t aAddChunk, const Prefix& aHash) { + AddPrefix* add = mAddPrefixes.AppendElement(fallible); + if (!add) return NS_ERROR_OUT_OF_MEMORY; + add->addChunk = aAddChunk; + add->prefix = aHash; + return NS_OK; +} + +nsresult TableUpdateV2::NewSubPrefix(uint32_t aAddChunk, const Prefix& aHash, + uint32_t aSubChunk) { + SubPrefix* sub = mSubPrefixes.AppendElement(fallible); + if (!sub) return NS_ERROR_OUT_OF_MEMORY; + sub->addChunk = aAddChunk; + sub->prefix = aHash; + sub->subChunk = aSubChunk; + return NS_OK; +} + +nsresult TableUpdateV2::NewAddComplete(uint32_t aAddChunk, + const Completion& aHash) { + AddComplete* add = mAddCompletes.AppendElement(fallible); + if (!add) return NS_ERROR_OUT_OF_MEMORY; + add->addChunk = aAddChunk; + add->complete = aHash; + return NS_OK; +} + +nsresult TableUpdateV2::NewSubComplete(uint32_t aAddChunk, + const Completion& aHash, + uint32_t aSubChunk) { + SubComplete* sub = mSubCompletes.AppendElement(fallible); + if (!sub) return NS_ERROR_OUT_OF_MEMORY; + sub->addChunk = aAddChunk; + sub->complete = aHash; + sub->subChunk = aSubChunk; + return NS_OK; +} + +nsresult TableUpdateV2::NewMissPrefix(const Prefix& aPrefix) { + Prefix* prefix = mMissPrefixes.AppendElement(aPrefix, fallible); + if (!prefix) return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +void TableUpdateV4::NewPrefixes(int32_t aSize, const nsACString& aPrefixes) { + NS_ENSURE_TRUE_VOID(aSize >= 4 && aSize <= COMPLETE_SIZE); + NS_ENSURE_TRUE_VOID(aPrefixes.Length() % aSize == 0); + NS_ENSURE_TRUE_VOID(!mPrefixesMap.Contains(aSize)); + + int numOfPrefixes = aPrefixes.Length() / aSize; + + if (aSize > 4) { + // TODO Bug 1364043 we may have a better API to record multiple samples into + // histograms with one call +#ifdef NIGHTLY_BUILD + for (int i = 0; i < std::min(20, numOfPrefixes); i++) { + Telemetry::Accumulate(Telemetry::URLCLASSIFIER_VLPS_LONG_PREFIXES, aSize); + } +#endif + } else if (LOG_ENABLED()) { + const uint32_t* p = + reinterpret_cast<const uint32_t*>(ToNewCString(aPrefixes)); + + // Dump the first/last 10 fixed-length prefixes for debugging. + LOG(("* The first 10 (maximum) fixed-length prefixes: ")); + for (int i = 0; i < std::min(10, numOfPrefixes); i++) { + const uint8_t* c = reinterpret_cast<const uint8_t*>(&p[i]); + LOG(("%.2X%.2X%.2X%.2X", c[0], c[1], c[2], c[3])); + } + + LOG(("* The last 10 (maximum) fixed-length prefixes: ")); + for (int i = std::max(0, numOfPrefixes - 10); i < numOfPrefixes; i++) { + const uint8_t* c = reinterpret_cast<const uint8_t*>(&p[i]); + LOG(("%.2X%.2X%.2X%.2X", c[0], c[1], c[2], c[3])); + } + + LOG(("---- %zu fixed-length prefixes in total.", + aPrefixes.Length() / aSize)); + } + + mPrefixesMap.InsertOrUpdate(aSize, MakeUnique<nsCString>(aPrefixes)); +} + +nsresult TableUpdateV4::NewRemovalIndices(const uint32_t* aIndices, + size_t aNumOfIndices) { + MOZ_ASSERT(mRemovalIndiceArray.IsEmpty(), + "mRemovalIndiceArray must be empty"); + + if (!mRemovalIndiceArray.SetCapacity(aNumOfIndices, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (size_t i = 0; i < aNumOfIndices; i++) { + mRemovalIndiceArray.AppendElement(aIndices[i]); + } + return NS_OK; +} + +void TableUpdateV4::SetSHA256(const std::string& aSHA256) { + mSHA256.Assign(aSHA256.data(), aSHA256.size()); +} + +nsresult TableUpdateV4::NewFullHashResponse( + const Prefix& aPrefix, const CachedFullHashResponse& aResponse) { + CachedFullHashResponse* response = + mFullHashResponseMap.GetOrInsertNew(aPrefix.ToUint32()); + if (!response) { + return NS_ERROR_OUT_OF_MEMORY; + } + *response = aResponse; + return NS_OK; +} + +void TableUpdateV4::Clear() { + mPrefixesMap.Clear(); + mRemovalIndiceArray.Clear(); +} + +HashStore::HashStore(const nsACString& aTableName, const nsACString& aProvider, + nsIFile* aRootStoreDir) + : mTableName(aTableName), mInUpdate(false), mFileSize(0) { + nsresult rv = Classifier::GetPrivateStoreDirectory( + aRootStoreDir, aTableName, aProvider, getter_AddRefs(mStoreDirectory)); + if (NS_FAILED(rv)) { + LOG(("Failed to get private store directory for %s", mTableName.get())); + mStoreDirectory = aRootStoreDir; + } +} + +HashStore::~HashStore() = default; + +nsresult HashStore::Reset() { + LOG(("HashStore resetting")); + + // Close InputStream before removing the file + if (mInputStream) { + nsresult rv = mInputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + mInputStream = nullptr; + } + + nsCOMPtr<nsIFile> storeFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = storeFile->AppendNative(mTableName + nsLiteralCString(STORE_SUFFIX)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = storeFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + + mFileSize = 0; + + return NS_OK; +} + +nsresult HashStore::CheckChecksum(uint32_t aFileSize) { + if (!mInputStream) { + return NS_OK; + } + + // Check for file corruption by + // comparing the stored checksum to actual checksum of data + nsAutoCString hash; + nsAutoCString compareHash; + uint32_t read; + + nsresult rv = CalculateChecksum(hash, aFileSize, true); + NS_ENSURE_SUCCESS(rv, rv); + + compareHash.SetLength(hash.Length()); + + if (hash.Length() > aFileSize) { + NS_WARNING("SafeBrowsing file not long enough to store its hash"); + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsISeekableStream> seekIn = do_QueryInterface(mInputStream); + rv = seekIn->Seek(nsISeekableStream::NS_SEEK_SET, aFileSize - hash.Length()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mInputStream->Read(compareHash.BeginWriting(), hash.Length(), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(read == hash.Length(), "Could not read hash bytes"); + + if (!hash.Equals(compareHash)) { + NS_WARNING("SafeBrowsing file failed checksum."); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult HashStore::Open(uint32_t aVersion) { + nsCOMPtr<nsIFile> storeFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = storeFile->AppendNative(mTableName + ".sbstore"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> origStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(origStream), storeFile, + PR_RDONLY | nsIFile::OS_READAHEAD); + + if (rv == NS_ERROR_FILE_NOT_FOUND) { + UpdateHeader(); + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + + int64_t fileSize; + rv = storeFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileSize < 0 || fileSize > UINT32_MAX) { + return NS_ERROR_FAILURE; + } + + mFileSize = static_cast<uint32_t>(fileSize); + rv = NS_NewBufferedInputStream(getter_AddRefs(mInputStream), + origStream.forget(), mFileSize); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadHeader(); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG(("Failed to read header for %s", mTableName.get())); + return NS_ERROR_FILE_CORRUPTED; + } + + rv = SanityCheck(aVersion); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::ReadHeader() { + if (!mInputStream) { + UpdateHeader(); + return NS_OK; + } + + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream); + nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + NS_ENSURE_SUCCESS(rv, rv); + + void* buffer = &mHeader; + rv = NS_ReadInputStreamToBuffer(mInputStream, &buffer, sizeof(Header)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::SanityCheck(uint32_t aVersion) const { + const uint32_t VER = aVersion == 0 ? CURRENT_VERSION : aVersion; + if (mHeader.magic != STORE_MAGIC || mHeader.version != VER) { + NS_WARNING("Unexpected header data in the store."); + // Version mismatch is also considered file corrupted, + // We need this error code to know if we should remove the file. + return NS_ERROR_FILE_CORRUPTED; + } + + return NS_OK; +} + +nsresult HashStore::CalculateChecksum(nsAutoCString& aChecksum, + uint32_t aFileSize, + bool aChecksumPresent) { + aChecksum.Truncate(); + + // Reset mInputStream to start + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream); + nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + nsCOMPtr<nsICryptoHash> hash = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Size of MD5 hash in bytes + const uint32_t CHECKSUM_SIZE = 16; + + // MD5 is not a secure hash function, but since this is a filesystem integrity + // check, this usage is ok. + rv = hash->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aChecksumPresent) { + // Hash entire file + rv = hash->UpdateFromStream(mInputStream, UINT32_MAX); + } else { + // Hash everything but last checksum bytes + if (aFileSize < CHECKSUM_SIZE) { + NS_WARNING("SafeBrowsing file isn't long enough to store its checksum"); + return NS_ERROR_FAILURE; + } + rv = hash->UpdateFromStream(mInputStream, aFileSize - CHECKSUM_SIZE); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = hash->Finish(false, aChecksum); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +void HashStore::UpdateHeader() { + mHeader.magic = STORE_MAGIC; + mHeader.version = CURRENT_VERSION; + + mHeader.numAddChunks = mAddChunks.Length(); + mHeader.numSubChunks = mSubChunks.Length(); + mHeader.numAddPrefixes = mAddPrefixes.Length(); + mHeader.numSubPrefixes = mSubPrefixes.Length(); + mHeader.numAddCompletes = mAddCompletes.Length(); + mHeader.numSubCompletes = mSubCompletes.Length(); +} + +nsresult HashStore::ReadChunkNumbers() { + if (!mInputStream) { + return NS_OK; + } + + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream); + nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, sizeof(Header)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mAddChunks.Read(mInputStream, mHeader.numAddChunks); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(mAddChunks.Length() == mHeader.numAddChunks, + "Read the right amount of add chunks."); + + rv = mSubChunks.Read(mInputStream, mHeader.numSubChunks); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(mSubChunks.Length() == mHeader.numSubChunks, + "Read the right amount of sub chunks."); + + return NS_OK; +} + +nsresult HashStore::ReadHashes() { + if (!mInputStream) { + // BeginUpdate has been called but Open hasn't initialized mInputStream, + // because the existing HashStore is empty. + return NS_OK; + } + + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream); + + uint32_t offset = sizeof(Header); + offset += (mHeader.numAddChunks + mHeader.numSubChunks) * sizeof(uint32_t); + nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadAddPrefixes(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadSubPrefixes(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadAddCompletes(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadTArray(mInputStream, &mSubCompletes, mHeader.numSubCompletes); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::PrepareForUpdate() { + nsresult rv = CheckChecksum(mFileSize); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadChunkNumbers(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadHashes(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::BeginUpdate() { + // Check wether the file is corrupted and read the rest of the store + // in memory. + nsresult rv = PrepareForUpdate(); + NS_ENSURE_SUCCESS(rv, rv); + + // Close input stream, won't be needed any more and + // we will rewrite ourselves. + if (mInputStream) { + rv = mInputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + } + + mInUpdate = true; + + return NS_OK; +} + +template <class T> +static nsresult Merge(ChunkSet* aStoreChunks, FallibleTArray<T>* aStorePrefixes, + const ChunkSet& aUpdateChunks, + FallibleTArray<T>& aUpdatePrefixes, + bool aAllowMerging = false) { + EntrySort(aUpdatePrefixes); + + auto storeIter = aStorePrefixes->begin(); + auto storeEnd = aStorePrefixes->end(); + + // use a separate array so we can keep the iterators valid + // if the nsTArray grows + nsTArray<T> adds; + + for (const auto& updatePrefix : aUpdatePrefixes) { + // skip this chunk if we already have it, unless we're + // merging completions, in which case we'll always already + // have the chunk from the original prefix + if (aStoreChunks->Has(updatePrefix.Chunk())) + if (!aAllowMerging) continue; + // XXX: binary search for insertion point might be faster in common + // case? + while (storeIter < storeEnd && (storeIter->Compare(updatePrefix) < 0)) { + // skip forward to matching element (or not...) + storeIter++; + } + // no match, add + if (storeIter == storeEnd || storeIter->Compare(updatePrefix) != 0) { + if (!adds.AppendElement(updatePrefix, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + // Chunks can be empty, but we should still report we have them + // to make the chunkranges continuous. + aStoreChunks->Merge(aUpdateChunks); + + if (!aStorePrefixes->AppendElements(adds, fallible)) + return NS_ERROR_OUT_OF_MEMORY; + + EntrySort(*aStorePrefixes); + + return NS_OK; +} + +nsresult HashStore::ApplyUpdate(RefPtr<TableUpdateV2> aUpdate) { + MOZ_ASSERT(mTableName.Equals(aUpdate->TableName())); + + nsresult rv = mAddExpirations.Merge(aUpdate->AddExpirations()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mSubExpirations.Merge(aUpdate->SubExpirations()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Expire(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Merge(&mAddChunks, &mAddPrefixes, aUpdate->AddChunks(), + aUpdate->AddPrefixes()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Merge(&mAddChunks, &mAddCompletes, aUpdate->AddChunks(), + aUpdate->AddCompletes(), true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Merge(&mSubChunks, &mSubPrefixes, aUpdate->SubChunks(), + aUpdate->SubPrefixes()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Merge(&mSubChunks, &mSubCompletes, aUpdate->SubChunks(), + aUpdate->SubCompletes(), true); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::Rebuild() { + NS_ASSERTION(mInUpdate, "Must be in update to rebuild."); + + nsresult rv = ProcessSubs(); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateHeader(); + + return NS_OK; +} + +template <class T> +static void ExpireEntries(FallibleTArray<T>* aEntries, ChunkSet& aExpirations) { + auto addIter = aEntries->begin(); + + for (const auto& entry : *aEntries) { + if (!aExpirations.Has(entry.Chunk())) { + *addIter = entry; + addIter++; + } + } + + aEntries->TruncateLength(addIter - aEntries->begin()); +} + +nsresult HashStore::Expire() { + ExpireEntries(&mAddPrefixes, mAddExpirations); + ExpireEntries(&mAddCompletes, mAddExpirations); + ExpireEntries(&mSubPrefixes, mSubExpirations); + ExpireEntries(&mSubCompletes, mSubExpirations); + + mAddChunks.Remove(mAddExpirations); + mSubChunks.Remove(mSubExpirations); + + mAddExpirations.Clear(); + mSubExpirations.Clear(); + + return NS_OK; +} + +template <class T> +nsresult DeflateWriteTArray(nsIOutputStream* aStream, nsTArray<T>& aIn) { + uLongf insize = aIn.Length() * sizeof(T); + uLongf outsize = compressBound(insize); + FallibleTArray<char> outBuff; + if (!outBuff.SetLength(outsize, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int zerr = compress(reinterpret_cast<Bytef*>(outBuff.Elements()), &outsize, + reinterpret_cast<const Bytef*>(aIn.Elements()), insize); + if (zerr != Z_OK) { + return NS_ERROR_FAILURE; + } + LOG(("DeflateWriteTArray: %lu in %lu out", insize, outsize)); + + outBuff.TruncateLength(outsize); + + // Length of compressed data stream + uint32_t dataLen = outBuff.Length(); + uint32_t written; + nsresult rv = aStream->Write(reinterpret_cast<char*>(&dataLen), + sizeof(dataLen), &written); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(written == sizeof(dataLen), "Error writing deflate length"); + + // Store to stream + rv = WriteTArray(aStream, outBuff); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +template <class T> +nsresult InflateReadTArray(nsIInputStream* aStream, FallibleTArray<T>* aOut, + uint32_t aExpectedSize) { + uint32_t inLen; + uint32_t read; + nsresult rv = + aStream->Read(reinterpret_cast<char*>(&inLen), sizeof(inLen), &read); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(read == sizeof(inLen), "Error reading inflate length"); + + FallibleTArray<char> inBuff; + if (!inBuff.SetLength(inLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = ReadTArray(aStream, &inBuff, inLen); + NS_ENSURE_SUCCESS(rv, rv); + + uLongf insize = inLen; + uLongf outsize = aExpectedSize * sizeof(T); + if (!aOut->SetLength(aExpectedSize, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int zerr = + uncompress(reinterpret_cast<Bytef*>(aOut->Elements()), &outsize, + reinterpret_cast<const Bytef*>(inBuff.Elements()), insize); + if (zerr != Z_OK) { + return NS_ERROR_FAILURE; + } + LOG(("InflateReadTArray: %lu in %lu out", insize, outsize)); + + NS_ASSERTION(outsize == aExpectedSize * sizeof(T), + "Decompression size mismatch"); + + return NS_OK; +} + +static nsresult ByteSliceWrite(nsIOutputStream* aOut, + nsTArray<uint32_t>& aData) { + nsTArray<uint8_t> slice; + uint32_t count = aData.Length(); + + // Only process one slice at a time to avoid using too much memory. + if (!slice.SetLength(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Process slice 1. + for (uint32_t i = 0; i < count; i++) { + slice[i] = (aData[i] >> 24); + } + + nsresult rv = DeflateWriteTArray(aOut, slice); + NS_ENSURE_SUCCESS(rv, rv); + + // Process slice 2. + for (uint32_t i = 0; i < count; i++) { + slice[i] = ((aData[i] >> 16) & 0xFF); + } + + rv = DeflateWriteTArray(aOut, slice); + NS_ENSURE_SUCCESS(rv, rv); + + // Process slice 3. + for (uint32_t i = 0; i < count; i++) { + slice[i] = ((aData[i] >> 8) & 0xFF); + } + + rv = DeflateWriteTArray(aOut, slice); + NS_ENSURE_SUCCESS(rv, rv); + + // Process slice 4. + for (uint32_t i = 0; i < count; i++) { + slice[i] = (aData[i] & 0xFF); + } + + // The LSB slice is generally uncompressible, don't bother + // compressing it. + rv = WriteTArray(aOut, slice); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult ByteSliceRead(nsIInputStream* aInStream, + FallibleTArray<uint32_t>* aData, uint32_t count) { + FallibleTArray<uint8_t> slice1; + FallibleTArray<uint8_t> slice2; + FallibleTArray<uint8_t> slice3; + FallibleTArray<uint8_t> slice4; + + nsresult rv = InflateReadTArray(aInStream, &slice1, count); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InflateReadTArray(aInStream, &slice2, count); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InflateReadTArray(aInStream, &slice3, count); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadTArray(aInStream, &slice4, count); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aData->SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < count; i++) { + // SetCapacity was just called, these cannot fail. + (void)aData->AppendElement( + (slice1[i] << 24) | (slice2[i] << 16) | (slice3[i] << 8) | (slice4[i]), + fallible); + } + + return NS_OK; +} + +nsresult HashStore::ReadAddPrefixes() { + FallibleTArray<uint32_t> chunks; + uint32_t count = mHeader.numAddPrefixes; + + nsresult rv = ByteSliceRead(mInputStream, &chunks, count); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mAddPrefixes.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < count; i++) { + AddPrefix* add = mAddPrefixes.AppendElement(fallible); + add->prefix.FromUint32(0); + add->addChunk = chunks[i]; + } + + return NS_OK; +} + +nsresult HashStore::ReadAddCompletes() { + FallibleTArray<uint32_t> chunks; + uint32_t count = mHeader.numAddCompletes; + + nsresult rv = ByteSliceRead(mInputStream, &chunks, count); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mAddCompletes.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < count; i++) { + AddComplete* add = mAddCompletes.AppendElement(fallible); + add->addChunk = chunks[i]; + } + + return NS_OK; +} + +nsresult HashStore::ReadSubPrefixes() { + FallibleTArray<uint32_t> addchunks; + FallibleTArray<uint32_t> subchunks; + FallibleTArray<uint32_t> prefixes; + uint32_t count = mHeader.numSubPrefixes; + + nsresult rv = ByteSliceRead(mInputStream, &addchunks, count); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ByteSliceRead(mInputStream, &subchunks, count); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ByteSliceRead(mInputStream, &prefixes, count); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mSubPrefixes.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < count; i++) { + SubPrefix* sub = mSubPrefixes.AppendElement(fallible); + sub->addChunk = addchunks[i]; + sub->prefix.FromUint32(prefixes[i]); + sub->subChunk = subchunks[i]; + } + + return NS_OK; +} + +// Split up PrefixArray back into the constituents +nsresult HashStore::WriteAddPrefixChunks(nsIOutputStream* aOut) { + nsTArray<uint32_t> chunks; + uint32_t count = mAddPrefixes.Length(); + if (!chunks.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < count; i++) { + chunks.AppendElement(mAddPrefixes[i].Chunk()); + } + + nsresult rv = ByteSliceWrite(aOut, chunks); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::WriteAddCompleteChunks(nsIOutputStream* aOut) { + nsTArray<uint32_t> chunks; + uint32_t count = mAddCompletes.Length(); + if (!chunks.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < count; i++) { + chunks.AppendElement(mAddCompletes[i].Chunk()); + } + + nsresult rv = ByteSliceWrite(aOut, chunks); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::WriteSubPrefixes(nsIOutputStream* aOut) { + nsTArray<uint32_t> addchunks; + nsTArray<uint32_t> subchunks; + nsTArray<uint32_t> prefixes; + uint32_t count = mSubPrefixes.Length(); + if (!addchunks.SetCapacity(count, fallible) || + !subchunks.SetCapacity(count, fallible) || + !prefixes.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < count; i++) { + addchunks.AppendElement(mSubPrefixes[i].AddChunk()); + prefixes.AppendElement(mSubPrefixes[i].PrefixHash().ToUint32()); + subchunks.AppendElement(mSubPrefixes[i].Chunk()); + } + + nsresult rv = ByteSliceWrite(aOut, addchunks); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ByteSliceWrite(aOut, subchunks); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ByteSliceWrite(aOut, prefixes); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::WriteFile() { + NS_ASSERTION(mInUpdate, "Must be in update to write database."); + if (nsUrlClassifierDBService::ShutdownHasStarted()) { + return NS_ERROR_ABORT; + } + + nsCOMPtr<nsIFile> storeFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = storeFile->AppendNative(mTableName + ".sbstore"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIOutputStream> out; + rv = NS_NewCheckSummedOutputStream(getter_AddRefs(out), storeFile); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t written; + rv = out->Write(reinterpret_cast<char*>(&mHeader), sizeof(mHeader), &written); + NS_ENSURE_SUCCESS(rv, rv); + + // Write chunk numbers. + rv = mAddChunks.Write(out); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mSubChunks.Write(out); + NS_ENSURE_SUCCESS(rv, rv); + + // Write hashes. + rv = WriteAddPrefixChunks(out); + NS_ENSURE_SUCCESS(rv, rv); + + rv = WriteSubPrefixes(out); + NS_ENSURE_SUCCESS(rv, rv); + + rv = WriteAddCompleteChunks(out); + NS_ENSURE_SUCCESS(rv, rv); + + rv = WriteTArray(out, mSubCompletes); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISafeOutputStream> safeOut = do_QueryInterface(out, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = safeOut->Finish(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult HashStore::ReadCompletionsLegacyV3(AddCompleteArray& aCompletes) { + if (mHeader.version != 3) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> storeFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = storeFile->AppendNative(mTableName + nsLiteralCString(STORE_SUFFIX)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t offset = mFileSize - + sizeof(struct AddComplete) * mHeader.numAddCompletes - + sizeof(struct SubComplete) * mHeader.numSubCompletes - + nsCheckSummedOutputStream::CHECKSUM_SIZE; + + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream); + + rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadTArray(mInputStream, &aCompletes, mHeader.numAddCompletes); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +template <class T> +static void Erase(FallibleTArray<T>* array, + typename FallibleTArray<T>::iterator& iterStart, + typename FallibleTArray<T>::iterator& iterEnd) { + array->RemoveElementsRange(iterStart, iterEnd); +} + +// Find items matching between |subs| and |adds|, and remove them, +// recording the item from |adds| in |adds_removed|. To minimize +// copies, the inputs are processing in parallel, so |subs| and |adds| +// should be compatibly ordered (either by SBAddPrefixLess or +// SBAddPrefixHashLess). +// +// |predAS| provides add < sub, |predSA| provides sub < add, for the +// tightest compare appropriate (see calls in SBProcessSubs). +template <class TSub, class TAdd> +static void KnockoutSubs(FallibleTArray<TSub>* aSubs, + FallibleTArray<TAdd>* aAdds) { + // Keep a pair of output iterators for writing kept items. Due to + // deletions, these may lag the main iterators. Using erase() on + // individual items would result in O(N^2) copies. Using a list + // would work around that, at double or triple the memory cost. + auto addOut = aAdds->begin(); + auto addIter = aAdds->begin(); + + auto subOut = aSubs->begin(); + auto subIter = aSubs->begin(); + + auto addEnd = aAdds->end(); + auto subEnd = aSubs->end(); + + while (addIter != addEnd && subIter != subEnd) { + // additer compare, so it compares on add chunk + int32_t cmp = addIter->Compare(*subIter); + if (cmp > 0) { + // If |*sub_iter| < |*add_iter|, retain the sub. + *subOut = *subIter; + ++subOut; + ++subIter; + } else if (cmp < 0) { + // If |*add_iter| < |*sub_iter|, retain the add. + *addOut = *addIter; + ++addOut; + ++addIter; + } else { + // Drop equal items + ++addIter; + ++subIter; + } + } + + Erase(aAdds, addOut, addIter); + Erase(aSubs, subOut, subIter); +} + +// Remove items in |removes| from |fullHashes|. |fullHashes| and +// |removes| should be ordered by SBAddPrefix component. +template <class T> +static void RemoveMatchingPrefixes(const SubPrefixArray& aSubs, + FallibleTArray<T>* aFullHashes) { + // Where to store kept items. + auto out = aFullHashes->begin(); + auto hashIter = aFullHashes->begin(); + auto hashEnd = aFullHashes->end(); + + auto removeIter = aSubs.begin(); + auto removeEnd = aSubs.end(); + + while (hashIter != hashEnd && removeIter != removeEnd) { + int32_t cmp = removeIter->CompareAlt(*hashIter); + if (cmp > 0) { + // Keep items less than |*removeIter|. + *out = *hashIter; + ++out; + ++hashIter; + } else if (cmp < 0) { + // No hit for |*removeIter|, bump it forward. + ++removeIter; + } else { + // Drop equal items, there may be multiple hits. + do { + ++hashIter; + } while (hashIter != hashEnd && !(removeIter->CompareAlt(*hashIter) < 0)); + ++removeIter; + } + } + Erase(aFullHashes, out, hashIter); +} + +static void RemoveDeadSubPrefixes(SubPrefixArray& aSubs, ChunkSet& aAddChunks) { + auto subIter = aSubs.begin(); + + for (const auto& sub : aSubs) { + bool hasChunk = aAddChunks.Has(sub.AddChunk()); + // Keep the subprefix if the chunk it refers to is one + // we haven't seen it yet. + if (!hasChunk) { + *subIter = sub; + subIter++; + } + } + + LOG(("Removed %" PRId64 " dead SubPrefix entries.", + static_cast<int64_t>(aSubs.end() - subIter))); + aSubs.TruncateLength(subIter - aSubs.begin()); +} + +#ifdef DEBUG +template <class T> +static void EnsureSorted(FallibleTArray<T>* aArray) { + auto start = aArray->begin(); + auto end = aArray->end(); + auto iter = start; + auto previous = start; + + while (iter != end) { + previous = iter; + ++iter; + if (iter != end) { + MOZ_ASSERT(iter->Compare(*previous) >= 0); + } + } +} +#endif + +nsresult HashStore::ProcessSubs() { +#ifdef DEBUG + EnsureSorted(&mAddPrefixes); + EnsureSorted(&mSubPrefixes); + EnsureSorted(&mAddCompletes); + EnsureSorted(&mSubCompletes); + LOG(("All databases seem to have a consistent sort order.")); +#endif + + RemoveMatchingPrefixes(mSubPrefixes, &mAddCompletes); + RemoveMatchingPrefixes(mSubPrefixes, &mSubCompletes); + + // Remove any remaining subbed prefixes from both addprefixes + // and addcompletes. + KnockoutSubs(&mSubPrefixes, &mAddPrefixes); + KnockoutSubs(&mSubCompletes, &mAddCompletes); + + // Remove any remaining subprefixes referring to addchunks that + // we have (and hence have been processed above). + RemoveDeadSubPrefixes(mSubPrefixes, mAddChunks); + +#ifdef DEBUG + EnsureSorted(&mAddPrefixes); + EnsureSorted(&mSubPrefixes); + EnsureSorted(&mAddCompletes); + EnsureSorted(&mSubCompletes); + LOG(("All databases seem to have a consistent sort order.")); +#endif + + return NS_OK; +} + +nsresult HashStore::AugmentAdds(const nsTArray<uint32_t>& aPrefixes, + const nsTArray<nsCString>& aCompletes) { + if (aPrefixes.Length() != mAddPrefixes.Length() || + aCompletes.Length() != mAddCompletes.Length()) { + LOG(( + "Amount of prefixes/completes in cache not consistent with store prefixes(%zu vs %zu), \ + store completes(%zu vs %zu)", + aPrefixes.Length(), mAddPrefixes.Length(), aCompletes.Length(), + mAddCompletes.Length())); + return NS_ERROR_FAILURE; + } + + for (size_t i = 0; i < mAddPrefixes.Length(); i++) { + mAddPrefixes[i].prefix.FromUint32(aPrefixes[i]); + } + + for (size_t i = 0; i < mAddCompletes.Length(); i++) { + mAddCompletes[i].complete.Assign(aCompletes[i]); + } + + return NS_OK; +} + +ChunkSet& HashStore::AddChunks() { + ReadChunkNumbers(); + + return mAddChunks; +} + +ChunkSet& HashStore::SubChunks() { + ReadChunkNumbers(); + + return mSubChunks; +} + +} // namespace mozilla::safebrowsing diff --git a/toolkit/components/url-classifier/HashStore.h b/toolkit/components/url-classifier/HashStore.h new file mode 100644 index 0000000000..ccb665efa7 --- /dev/null +++ b/toolkit/components/url-classifier/HashStore.h @@ -0,0 +1,321 @@ +/* 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/. */ + +#ifndef HashStore_h__ +#define HashStore_h__ + +#include "Entries.h" +#include "ChunkSet.h" + +#include "nsString.h" +#include "nsTArray.h" +#include "nsIFile.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include <string> + +namespace mozilla { +namespace safebrowsing { + +// The abstract class of TableUpdateV2 and TableUpdateV4. This +// is convenient for passing the TableUpdate* around associated +// with v2 and v4 instance. +class TableUpdate { + public: + TableUpdate(const nsACString& aTable) : mTable(aTable) {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TableUpdate); + + // To be overriden. + virtual bool Empty() const = 0; + + // Common interfaces. + const nsCString& TableName() const { return mTable; } + + template <typename T> + static T* Cast(TableUpdate* aThat) { + return (T::TAG == aThat->Tag() ? reinterpret_cast<T*>(aThat) : nullptr); + } + template <typename T> + static const T* Cast(const TableUpdate* aThat) { + return (T::TAG == aThat->Tag() ? reinterpret_cast<const T*>(aThat) + : nullptr); + } + + protected: + virtual ~TableUpdate() = default; + + private: + virtual int Tag() const = 0; + + const nsCString mTable; +}; + +typedef nsTArray<RefPtr<TableUpdate>> TableUpdateArray; +typedef nsTArray<RefPtr<const TableUpdate>> ConstTableUpdateArray; + +// A table update is built from a single update chunk from the server. As the +// protocol parser processes each chunk, it constructs a table update with the +// new hashes. +class TableUpdateV2 : public TableUpdate { + public: + explicit TableUpdateV2(const nsACString& aTable) : TableUpdate(aTable) {} + + bool Empty() const override { + return mAddChunks.Length() == 0 && mSubChunks.Length() == 0 && + mAddExpirations.Length() == 0 && mSubExpirations.Length() == 0 && + mAddPrefixes.Length() == 0 && mSubPrefixes.Length() == 0 && + mAddCompletes.Length() == 0 && mSubCompletes.Length() == 0 && + mMissPrefixes.Length() == 0; + } + + // Throughout, uint32_t aChunk refers only to the chunk number. Chunk data is + // stored in the Prefix structures. + [[nodiscard]] nsresult NewAddChunk(uint32_t aChunk) { + return mAddChunks.Set(aChunk); + }; + [[nodiscard]] nsresult NewSubChunk(uint32_t aChunk) { + return mSubChunks.Set(aChunk); + }; + [[nodiscard]] nsresult NewAddExpiration(uint32_t aChunk) { + return mAddExpirations.Set(aChunk); + }; + [[nodiscard]] nsresult NewSubExpiration(uint32_t aChunk) { + return mSubExpirations.Set(aChunk); + }; + [[nodiscard]] nsresult NewAddPrefix(uint32_t aAddChunk, + const Prefix& aPrefix); + [[nodiscard]] nsresult NewSubPrefix(uint32_t aAddChunk, const Prefix& aPrefix, + uint32_t aSubChunk); + [[nodiscard]] nsresult NewAddComplete(uint32_t aChunk, + const Completion& aCompletion); + [[nodiscard]] nsresult NewSubComplete(uint32_t aAddChunk, + const Completion& aCompletion, + uint32_t aSubChunk); + [[nodiscard]] nsresult NewMissPrefix(const Prefix& aPrefix); + + const ChunkSet& AddChunks() const { return mAddChunks; } + const ChunkSet& SubChunks() const { return mSubChunks; } + + // Expirations for chunks. + const ChunkSet& AddExpirations() const { return mAddExpirations; } + const ChunkSet& SubExpirations() const { return mSubExpirations; } + + // Hashes associated with this chunk. + AddPrefixArray& AddPrefixes() { return mAddPrefixes; } + SubPrefixArray& SubPrefixes() { return mSubPrefixes; } + const AddCompleteArray& AddCompletes() const { return mAddCompletes; } + AddCompleteArray& AddCompletes() { return mAddCompletes; } + SubCompleteArray& SubCompletes() { return mSubCompletes; } + + // Entries that cannot be completed. + const MissPrefixArray& MissPrefixes() const { return mMissPrefixes; } + + // For downcasting. + static const int TAG = 2; + + private: + // The list of chunk numbers that we have for each of the type of chunks. + ChunkSet mAddChunks; + ChunkSet mSubChunks; + ChunkSet mAddExpirations; + ChunkSet mSubExpirations; + + // 4-byte sha256 prefixes. + AddPrefixArray mAddPrefixes; + SubPrefixArray mSubPrefixes; + + // This is only used by gethash so don't add this to Header. + MissPrefixArray mMissPrefixes; + + // 32-byte hashes. + AddCompleteArray mAddCompletes; + SubCompleteArray mSubCompletes; + + virtual int Tag() const override { return TAG; } +}; + +// Structure for DBService/HashStore/Classifiers to update. +// It would contain the prefixes (both fixed and variable length) +// for addition and indices to removal. See Bug 1283009. +class TableUpdateV4 : public TableUpdate { + public: + typedef nsTArray<int32_t> RemovalIndiceArray; + + public: + explicit TableUpdateV4(const nsACString& aTable) + : TableUpdate(aTable), mFullUpdate(false) {} + + bool Empty() const override { + return mPrefixesMap.IsEmpty() && mRemovalIndiceArray.IsEmpty() && + mFullHashResponseMap.IsEmpty(); + } + + bool IsFullUpdate() const { return mFullUpdate; } + const PrefixStringMap& Prefixes() const { return mPrefixesMap; } + const RemovalIndiceArray& RemovalIndices() const { + return mRemovalIndiceArray; + } + const nsACString& ClientState() const { return mClientState; } + const nsACString& SHA256() const { return mSHA256; } + const FullHashResponseMap& FullHashResponse() const { + return mFullHashResponseMap; + } + + // For downcasting. + static const int TAG = 4; + + void SetFullUpdate(bool aIsFullUpdate) { mFullUpdate = aIsFullUpdate; } + void NewPrefixes(int32_t aSize, const nsACString& aPrefixes); + void SetNewClientState(const nsACString& aState) { mClientState = aState; } + void SetSHA256(const std::string& aSHA256); + + nsresult NewRemovalIndices(const uint32_t* aIndices, size_t aNumOfIndices); + nsresult NewFullHashResponse(const Prefix& aPrefix, + const CachedFullHashResponse& aResponse); + + // Clear Prefixes & Removal indice. + void Clear(); + + private: + virtual int Tag() const override { return TAG; } + + bool mFullUpdate; + PrefixStringMap mPrefixesMap; + RemovalIndiceArray mRemovalIndiceArray; + nsCString mClientState; + nsCString mSHA256; + + // This is used to store response from fullHashes.find. + FullHashResponseMap mFullHashResponseMap; +}; + +// There is one hash store per table. +class HashStore { + public: + HashStore(const nsACString& aTableName, const nsACString& aProvider, + nsIFile* aRootStoreFile); + ~HashStore(); + + const nsCString& TableName() const { return mTableName; } + + // Version is set to 0 by default, it is only used when we want to open + // a specific version of HashStore. Note that the intention of aVersion + // is only to pass SanityCheck, reading data from older version should + // be handled additionally. + nsresult Open(uint32_t aVersion = 0); + + // Add Prefixes/Completes are stored partly in the PrefixSet (contains the + // Prefix data organized for fast lookup/low RAM usage) and partly in the + // HashStore (Add Chunk numbers - only used for updates, slow retrieval). + // AugmentAdds function joins the separate datasets into one complete + // prefixes+chunknumbers dataset. + nsresult AugmentAdds(const nsTArray<uint32_t>& aPrefixes, + const nsTArray<nsCString>& aCompletes); + + ChunkSet& AddChunks(); + ChunkSet& SubChunks(); + AddPrefixArray& AddPrefixes() { return mAddPrefixes; } + SubPrefixArray& SubPrefixes() { return mSubPrefixes; } + AddCompleteArray& AddCompletes() { return mAddCompletes; } + SubCompleteArray& SubCompletes() { return mSubCompletes; } + + // ======= + // Updates + // ======= + // Begin the update process. Reads the store into memory. + nsresult BeginUpdate(); + + // Imports the data from a TableUpdate. + nsresult ApplyUpdate(RefPtr<TableUpdateV2> aUpdate); + + // Process expired chunks + nsresult Expire(); + + // Rebuild the store, Incorporating all the applied updates. + nsresult Rebuild(); + + // Write the current state of the store to disk. + // If you call between ApplyUpdate() and Rebuild(), you'll + // have a mess on your hands. + nsresult WriteFile(); + + nsresult ReadCompletionsLegacyV3(AddCompleteArray& aCompletes); + + nsresult Reset(); + + private: + nsresult ReadHeader(); + nsresult SanityCheck(uint32_t aVersion = 0) const; + nsresult CalculateChecksum(nsAutoCString& aChecksum, uint32_t aFileSize, + bool aChecksumPresent); + nsresult CheckChecksum(uint32_t aFileSize); + void UpdateHeader(); + + nsresult ReadCompletions(); + nsresult ReadChunkNumbers(); + nsresult ReadHashes(); + + nsresult ReadAddPrefixes(); + nsresult ReadSubPrefixes(); + nsresult ReadAddCompletes(); + + nsresult WriteAddPrefixChunks(nsIOutputStream* aOut); + nsresult WriteSubPrefixes(nsIOutputStream* aOut); + nsresult WriteAddCompleteChunks(nsIOutputStream* aOut); + + nsresult ProcessSubs(); + + nsresult PrepareForUpdate(); + + // This is used for checking that the database is correct and for figuring out + // the number of chunks, etc. to read from disk on restart. + struct Header { + uint32_t magic; + uint32_t version; + uint32_t numAddChunks; + uint32_t numSubChunks; + uint32_t numAddPrefixes; + uint32_t numSubPrefixes; + uint32_t numAddCompletes; + uint32_t numSubCompletes; + }; + + Header mHeader; + + // The name of the table (must end in -shavar or -digest256, or evidently + // -simple for unittesting. + const nsCString mTableName; + nsCOMPtr<nsIFile> mStoreDirectory; + + bool mInUpdate; + + nsCOMPtr<nsIInputStream> mInputStream; + + // Chunk numbers, stored as uint32_t arrays. + ChunkSet mAddChunks; + ChunkSet mSubChunks; + + ChunkSet mAddExpirations; + ChunkSet mSubExpirations; + + // Chunk data for shavar tables. See Entries.h for format. + AddPrefixArray mAddPrefixes; + SubPrefixArray mSubPrefixes; + + // See bug 806422 for background. We must be able to distinguish between + // updates from the completion server and updates from the regular server. + AddCompleteArray mAddCompletes; + SubCompleteArray mSubCompletes; + + uint32_t mFileSize; + + // For gtest to inspect private members. + friend class PerProviderDirectoryTestUtils; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/IUrlClassifierUITelemetry.idl b/toolkit/components/url-classifier/IUrlClassifierUITelemetry.idl new file mode 100644 index 0000000000..12ca91659a --- /dev/null +++ b/toolkit/components/url-classifier/IUrlClassifierUITelemetry.idl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl" + +[scriptable, uuid(a6c62ce5-3a95-41bb-b0f1-8cd4f4ca00e3)] +interface IUrlClassifierUITelemetry : nsISupports { + +const uint32_t WARNING_MALWARE_PAGE_TOP = 1; +const uint32_t WARNING_MALWARE_PAGE_TOP_WHY_BLOCKED = 2; +const uint32_t WARNING_MALWARE_PAGE_TOP_GET_ME_OUT_OF_HERE = 3; +const uint32_t WARNING_MALWARE_PAGE_TOP_IGNORE_WARNING = 4; +const uint32_t WARNING_MALWARE_PAGE_FRAME = 5; +const uint32_t WARNING_MALWARE_PAGE_FRAME_WHY_BLOCKED = 6; +const uint32_t WARNING_MALWARE_PAGE_FRAME_GET_ME_OUT_OF_HERE = 7; +const uint32_t WARNING_MALWARE_PAGE_FRAME_IGNORE_WARNING = 8; + +const uint32_t WARNING_PHISHING_PAGE_TOP = 9; +const uint32_t WARNING_PHISHING_PAGE_TOP_WHY_BLOCKED = 10; +const uint32_t WARNING_PHISHING_PAGE_TOP_GET_ME_OUT_OF_HERE = 11; +const uint32_t WARNING_PHISHING_PAGE_TOP_IGNORE_WARNING = 12; +const uint32_t WARNING_PHISHING_PAGE_FRAME = 13; +const uint32_t WARNING_PHISHING_PAGE_FRAME_WHY_BLOCKED = 14; +const uint32_t WARNING_PHISHING_PAGE_FRAME_GET_ME_OUT_OF_HERE = 15; +const uint32_t WARNING_PHISHING_PAGE_FRAME_IGNORE_WARNING = 16; + +const uint32_t WARNING_UNWANTED_PAGE_TOP = 17; +const uint32_t WARNING_UNWANTED_PAGE_TOP_WHY_BLOCKED = 18; +const uint32_t WARNING_UNWANTED_PAGE_TOP_GET_ME_OUT_OF_HERE = 19; +const uint32_t WARNING_UNWANTED_PAGE_TOP_IGNORE_WARNING = 20; +const uint32_t WARNING_UNWANTED_PAGE_FRAME = 21; +const uint32_t WARNING_UNWANTED_PAGE_FRAME_WHY_BLOCKED = 22; +const uint32_t WARNING_UNWANTED_PAGE_FRAME_GET_ME_OUT_OF_HERE = 23; +const uint32_t WARNING_UNWANTED_PAGE_FRAME_IGNORE_WARNING = 24; + +const uint32_t WARNING_HARMFUL_PAGE_TOP = 25; +const uint32_t WARNING_HARMFUL_PAGE_TOP_WHY_BLOCKED = 26; +const uint32_t WARNING_HARMFUL_PAGE_TOP_GET_ME_OUT_OF_HERE = 27; +const uint32_t WARNING_HARMFUL_PAGE_TOP_IGNORE_WARNING = 28; +const uint32_t WARNING_HARMFUL_PAGE_FRAME = 29; +const uint32_t WARNING_HARMFUL_PAGE_FRAME_WHY_BLOCKED = 30; +const uint32_t WARNING_HARMFUL_PAGE_FRAME_GET_ME_OUT_OF_HERE = 31; +const uint32_t WARNING_HARMFUL_PAGE_FRAME_IGNORE_WARNING = 32; + +}; diff --git a/toolkit/components/url-classifier/LookupCache.cpp b/toolkit/components/url-classifier/LookupCache.cpp new file mode 100644 index 0000000000..3cb7b2f9eb --- /dev/null +++ b/toolkit/components/url-classifier/LookupCache.cpp @@ -0,0 +1,1107 @@ +//* -*- 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 "LookupCache.h" +#include "LookupCacheV4.h" +#include "HashStore.h" +#include "nsIFileStreams.h" +#include "nsISeekableStream.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Logging.h" +#include "nsNetUtil.h" +#include "nsCheckSummedOutputStream.h" +#include "crc32c.h" +#include "prprf.h" +#include "Classifier.h" +#include "nsUrlClassifierInfo.h" +#include "nsUrlClassifierUtils.h" +#include "nsUrlClassifierDBService.h" + +#ifdef DEBUG +# include "nsPrintfCString.h" +#endif + +// We act as the main entry point for all the real lookups, +// so note that those are not done to the actual HashStore. +// The latter solely exists to store the data needed to handle +// the updates from the protocol. + +// This module provides a front for PrefixSet, mUpdateCompletions, +// and mGetHashCache, which together contain everything needed to +// provide a classification as long as the data is up to date. + +// PrefixSet stores and provides lookups for 4-byte prefixes. +// mUpdateCompletions contains 32-byte completions which were +// contained in updates. They are retrieved from HashStore/.sbtore +// on startup. +// mGetHashCache contains 32-byte completions which were +// returned from the gethash server. They are not serialized, +// only cached until the next update. + +// 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) + +namespace mozilla { +namespace safebrowsing { + +const uint32_t LookupCache::MAX_BUFFER_SIZE = 64 * 1024; + +const int CacheResultV2::VER = CacheResult::V2; +const int CacheResultV4::VER = CacheResult::V4; + +const int LookupCacheV2::VER = 2; +const uint32_t LookupCacheV2::VLPSET_MAGIC = 0xe5b862e7; +const uint32_t LookupCacheV2::VLPSET_VERSION = 1; + +namespace { + +////////////////////////////////////////////////////////////////////////// +// A set of lightweight functions for reading/writing value from/to file. +template <typename T> +struct ValueTraits { + static_assert(sizeof(T) <= LookupCacheV4::MAX_METADATA_VALUE_LENGTH, + "LookupCacheV4::MAX_METADATA_VALUE_LENGTH is too small."); + static uint32_t Length(const T& aValue) { return sizeof(T); } + static char* WritePtr(T& aValue, uint32_t aLength) { return (char*)&aValue; } + static const char* ReadPtr(const T& aValue) { return (char*)&aValue; } + static bool IsFixedLength() { return true; } +}; + +template <> +struct ValueTraits<nsACString> { + static bool IsFixedLength() { return false; } + + static uint32_t Length(const nsACString& aValue) { return aValue.Length(); } + + static char* WritePtr(nsACString& aValue, uint32_t aLength) { + aValue.SetLength(aLength); + return aValue.BeginWriting(); + } + + static const char* ReadPtr(const nsACString& aValue) { + return aValue.BeginReading(); + } +}; + +void CStringToHexString(const nsACString& aIn, nsACString& aOut) { + static const char* const lut = "0123456789ABCDEF"; + + size_t len = aIn.Length(); + MOZ_ASSERT(len <= COMPLETE_SIZE); + + aOut.SetCapacity(2 * len); + for (size_t i = 0; i < aIn.Length(); ++i) { + const char c = static_cast<char>(aIn[i]); + aOut.Append(lut[(c >> 4) & 0x0F]); + aOut.Append(lut[c & 15]); + } +} + +#ifdef DEBUG +nsCString GetFormattedTimeString(int64_t aCurTimeSec) { + PRExplodedTime pret; + PR_ExplodeTime(aCurTimeSec * PR_USEC_PER_SEC, PR_GMTParameters, &pret); + + return nsPrintfCString("%04d-%02d-%02d %02d:%02d:%02d UTC", pret.tm_year, + pret.tm_month + 1, pret.tm_mday, pret.tm_hour, + pret.tm_min, pret.tm_sec); +} +#endif + +} // end of unnamed namespace. +//////////////////////////////////////////////////////////////////////// + +template <typename T> +nsresult LookupCache::WriteValue(nsIOutputStream* aOutputStream, + const T& aValue) { + uint32_t writeLength = ValueTraits<T>::Length(aValue); + MOZ_ASSERT(writeLength <= LookupCacheV4::MAX_METADATA_VALUE_LENGTH, + "LookupCacheV4::MAX_METADATA_VALUE_LENGTH is too small."); + if (!ValueTraits<T>::IsFixedLength()) { + // We need to write out the variable value length. + nsresult rv = WriteValue(aOutputStream, writeLength); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Write out the value. + auto valueReadPtr = ValueTraits<T>::ReadPtr(aValue); + uint32_t written; + nsresult rv = aOutputStream->Write(valueReadPtr, writeLength, &written); + NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(written != writeLength)) { + return NS_ERROR_FAILURE; + } + + return rv; +} + +template <typename T> +nsresult LookupCache::ReadValue(nsIInputStream* aInputStream, T& aValue) { + nsresult rv; + + uint32_t readLength; + if (ValueTraits<T>::IsFixedLength()) { + readLength = ValueTraits<T>::Length(aValue); + } else { + // Read the variable value length from file. + nsresult rv = ReadValue(aInputStream, readLength); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Sanity-check the readLength in case of disk corruption + // (see bug 1433636). + if (readLength > LookupCacheV4::MAX_METADATA_VALUE_LENGTH) { + return NS_ERROR_FILE_CORRUPTED; + } + + // Read the value. + uint32_t read; + auto valueWritePtr = ValueTraits<T>::WritePtr(aValue, readLength); + rv = aInputStream->Read(valueWritePtr, readLength, &read); + if (NS_FAILED(rv) || read != readLength) { + LOG(("Failed to read the value.")); + return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE; + } + + return rv; +} + +// These symbols are referenced from another compilation unit, but their +// implementation depends on local symbols. Workaround this by forcing their +// instantiation there. +template nsresult mozilla::safebrowsing::LookupCache::WriteValue( + nsIOutputStream*, nsTSubstring<char> const&); +template nsresult mozilla::safebrowsing::LookupCache::ReadValue( + nsIInputStream*, nsTSubstring<char>&); + +LookupCache::LookupCache(const nsACString& aTableName, + const nsACString& aProvider, + nsCOMPtr<nsIFile>& aRootStoreDir) + : mPrimed(false), + mTableName(aTableName), + mProvider(aProvider), + mRootStoreDirectory(aRootStoreDir), + mVLPrefixSet(nullptr) { + UpdateRootDirHandle(mRootStoreDirectory); +} + +nsresult LookupCache::Open() { + LOG(("Loading PrefixSet for %s", mTableName.get())); + nsresult rv; + if (nsUrlClassifierUtils::IsMozTestTable(mTableName)) { + // For built-in test table, we don't load it from disk, + // test entries are directly added in memory. + rv = LoadMozEntries(); + } else { + rv = LoadPrefixSet(); + } + + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return rv; +} + +nsresult LookupCache::Init() { + MOZ_ASSERT(!mVLPrefixSet); + + mVLPrefixSet = new VariableLengthPrefixSet(); + nsresult rv = mVLPrefixSet->Init(mTableName); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult LookupCache::UpdateRootDirHandle( + nsCOMPtr<nsIFile>& aNewRootStoreDirectory) { + nsresult rv; + + if (aNewRootStoreDirectory != mRootStoreDirectory) { + rv = aNewRootStoreDirectory->Clone(getter_AddRefs(mRootStoreDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = Classifier::GetPrivateStoreDirectory(mRootStoreDirectory, mTableName, + mProvider, + getter_AddRefs(mStoreDirectory)); + + if (NS_FAILED(rv)) { + LOG(("Failed to get private store directory for %s", mTableName.get())); + mStoreDirectory = mRootStoreDirectory; + } + + if (LOG_ENABLED()) { + nsString path; + mStoreDirectory->GetPath(path); + LOG(("Private store directory for %s is %s", mTableName.get(), + NS_ConvertUTF16toUTF8(path).get())); + } + + return rv; +} + +nsresult LookupCache::WriteFile() { + if (nsUrlClassifierDBService::ShutdownHasStarted()) { + return NS_ERROR_ABORT; + } + + nsCOMPtr<nsIFile> psFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = psFile->AppendNative(mTableName + GetPrefixSetSuffix()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = StoreToFile(psFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG(("Failed to store the prefixset for table %s", mTableName.get())); + return rv; + } + + return NS_OK; +} + +nsresult LookupCache::CheckCache(const Completion& aCompletion, bool* aHas, + bool* aConfirmed) { + // Shouldn't call this function if prefix is not in the database. + MOZ_ASSERT(*aHas); + + *aConfirmed = false; + + uint32_t prefix = aCompletion.ToUint32(); + + CachedFullHashResponse* fullHashResponse = mFullHashCache.Get(prefix); + if (!fullHashResponse) { + return NS_OK; + } + + int64_t nowSec = PR_Now() / PR_USEC_PER_SEC; + int64_t expiryTimeSec; + + FullHashExpiryCache& fullHashes = fullHashResponse->fullHashes; + nsDependentCSubstring completion( + reinterpret_cast<const char*>(aCompletion.buf), COMPLETE_SIZE); + + // Check if we can find the fullhash in positive cache + if (fullHashes.Get(completion, &expiryTimeSec)) { + if (nowSec <= expiryTimeSec) { + // Url is NOT safe. + *aConfirmed = true; + LOG(("Found a valid fullhash in the positive cache")); + } else { + // Trigger a gethash request in this case(aConfirmed is false). + LOG(("Found an expired fullhash in the positive cache")); + + // Remove fullhash entry from the cache when the negative cache + // is also expired because whether or not the fullhash is cached + // locally, we will need to consult the server next time we + // lookup this hash. We may as well remove it from our cache. + if (fullHashResponse->negativeCacheExpirySec < expiryTimeSec) { + fullHashes.Remove(completion); + if (fullHashes.Count() == 0 && + fullHashResponse->negativeCacheExpirySec < nowSec) { + mFullHashCache.Remove(prefix); + } + } + } + return NS_OK; + } + + // Check negative cache. + if (fullHashResponse->negativeCacheExpirySec >= nowSec) { + // Url is safe. + LOG(("Found a valid prefix in the negative cache")); + *aHas = false; + } else { + LOG(("Found an expired prefix in the negative cache")); + if (fullHashes.Count() == 0) { + mFullHashCache.Remove(prefix); + } + } + + return NS_OK; +} + +// This function remove cache entries whose negative cache time is expired. +// It is possible that a cache entry whose positive cache time is not yet +// expired but still being removed after calling this API. Right now we call +// this on every update. +void LookupCache::InvalidateExpiredCacheEntries() { + int64_t nowSec = PR_Now() / PR_USEC_PER_SEC; + + for (auto iter = mFullHashCache.Iter(); !iter.Done(); iter.Next()) { + CachedFullHashResponse* response = iter.UserData(); + if (response->negativeCacheExpirySec < nowSec) { + iter.Remove(); + } + } +} + +void LookupCache::CopyFullHashCache(const LookupCache* aSource) { + if (!aSource) { + return; + } + + CopyClassHashTable<FullHashResponseMap>(aSource->mFullHashCache, + mFullHashCache); +} + +void LookupCache::ClearCache() { mFullHashCache.Clear(); } + +void LookupCache::ClearAll() { + ClearCache(); + ClearPrefixes(); + mPrimed = false; +} + +nsresult LookupCache::ClearPrefixes() { + // Clear by seting a empty map + PrefixStringMap map; + return mVLPrefixSet->SetPrefixes(map); +} + +bool LookupCache::IsEmpty() const { + bool isEmpty; + mVLPrefixSet->IsEmpty(&isEmpty); + return isEmpty; +} + +void LookupCache::GetCacheInfo(nsIUrlClassifierCacheInfo** aCache) const { + MOZ_ASSERT(aCache); + + RefPtr<nsUrlClassifierCacheInfo> info = new nsUrlClassifierCacheInfo; + info->table = mTableName; + + for (const auto& cacheEntry : mFullHashCache) { + RefPtr<nsUrlClassifierCacheEntry> entry = new nsUrlClassifierCacheEntry; + + // Set prefix of the cache entry. + nsAutoCString prefix(reinterpret_cast<const char*>(&cacheEntry.GetKey()), + PREFIX_SIZE); + CStringToHexString(prefix, entry->prefix); + + // Set expiry of the cache entry. + CachedFullHashResponse* response = cacheEntry.GetWeak(); + entry->expirySec = response->negativeCacheExpirySec; + + // Set positive cache. + FullHashExpiryCache& fullHashes = response->fullHashes; + for (const auto& fullHashEntry : fullHashes) { + RefPtr<nsUrlClassifierPositiveCacheEntry> match = + new nsUrlClassifierPositiveCacheEntry; + + // Set fullhash of positive cache entry. + CStringToHexString(fullHashEntry.GetKey(), match->fullhash); + + // Set expiry of positive cache entry. + match->expirySec = fullHashEntry.GetData(); + + entry->matches.AppendElement( + static_cast<nsIUrlClassifierPositiveCacheEntry*>(match)); + } + + info->entries.AppendElement( + static_cast<nsIUrlClassifierCacheEntry*>(entry)); + } + + info.forget(aCache); +} + +/* static */ +bool LookupCache::IsCanonicalizedIP(const nsACString& aHost) { + // The canonicalization process will have left IP addresses in dotted + // decimal with no surprises. + uint32_t i1, i2, i3, i4; + char c; + if (PR_sscanf(PromiseFlatCString(aHost).get(), "%u.%u.%u.%u%c", &i1, &i2, &i3, + &i4, &c) == 4) { + return (i1 <= 0xFF && i2 <= 0xFF && i3 <= 0xFF && i4 <= 0xFF); + } + + return false; +} + +// This is used when the URL is created by CreatePairwiseEntityListURI(), +// which returns an URI like "toplevel.page/?resource=third.party.domain" +// The fragment rule for the hostname(toplevel.page) is still the same +// as Safe Browsing protocol. +// The difference is that we always keep the path and query string and +// generate an additional fragment by removing the leading component of +// third.party.domain. This is to make sure we can find a match when a +// exceptionlisted domain is eTLD. +/* static */ +nsresult LookupCache::GetLookupEntitylistFragments( + const nsACString& aSpec, nsTArray<nsCString>* aFragments) { + aFragments->Clear(); + + nsACString::const_iterator begin, end, iter, iter_end; + aSpec.BeginReading(begin); + aSpec.EndReading(end); + + iter = begin; + iter_end = end; + + // Fallback to use default fragment rule when the URL doesn't contain + // "/?resoruce=" because this means the URL is not generated in + // CreatePairwiseEntityListURI() + if (!FindInReadable("/?resource="_ns, iter, iter_end)) { + return GetLookupFragments(aSpec, aFragments); + } + + const nsACString& topLevelURL = Substring(begin, iter++); + const nsACString& thirdPartyURL = Substring(iter_end, end); + + /** + * For the top-level URL, we follow the host fragment rule defined + * in the Safe Browsing protocol. + */ + nsTArray<nsCString> topLevelURLs; + topLevelURLs.AppendElement(topLevelURL); + + if (!IsCanonicalizedIP(topLevelURL)) { + topLevelURL.BeginReading(begin); + topLevelURL.EndReading(end); + int numTopLevelURLComponents = 0; + while (RFindInReadable("."_ns, begin, end) && + numTopLevelURLComponents < MAX_HOST_COMPONENTS) { + // don't bother checking toplevel domains + if (++numTopLevelURLComponents >= 2) { + topLevelURL.EndReading(iter); + topLevelURLs.AppendElement(Substring(end, iter)); + } + end = begin; + topLevelURL.BeginReading(begin); + } + } + + /** + * The whiltelisted domain in the entity list may be eTLD or eTLD+1. + * Since the number of the domain name part in the third-party URL searching + * is always less than or equal to eTLD+1, we remove the leading + * component from the third-party domain to make sure we can find a match + * if the exceptionlisted domain stoed in the entity list is eTLD. + */ + nsTArray<nsCString> thirdPartyURLs; + thirdPartyURLs.AppendElement(thirdPartyURL); + + if (!IsCanonicalizedIP(thirdPartyURL)) { + thirdPartyURL.BeginReading(iter); + thirdPartyURL.EndReading(end); + if (FindCharInReadable('.', iter, end)) { + iter++; + nsAutoCString thirdPartyURLToAdd; + thirdPartyURLToAdd.Assign(Substring(iter++, end)); + + // don't bother checking toplevel domains + if (FindCharInReadable('.', iter, end)) { + thirdPartyURLs.AppendElement(thirdPartyURLToAdd); + } + } + } + + for (size_t i = 0; i < topLevelURLs.Length(); i++) { + for (size_t j = 0; j < thirdPartyURLs.Length(); j++) { + nsAutoCString key; + key.Assign(topLevelURLs[i]); + key.Append("/?resource="); + key.Append(thirdPartyURLs[j]); + + aFragments->AppendElement(key); + } + } + + return NS_OK; +} + +/* static */ +nsresult LookupCache::GetLookupFragments(const nsACString& aSpec, + nsTArray<nsCString>* aFragments) + +{ + aFragments->Clear(); + + nsACString::const_iterator begin, end, iter; + aSpec.BeginReading(begin); + aSpec.EndReading(end); + + iter = begin; + if (!FindCharInReadable('/', iter, end)) { + return NS_OK; + } + + const nsACString& host = Substring(begin, iter++); + nsAutoCString path; + path.Assign(Substring(iter, end)); + + /** + * From the protocol doc: + * For the hostname, the client will try at most 5 different strings. They + * are: + * a) The exact hostname of the url + * b) The 4 hostnames formed by starting with the last 5 components and + * successivly removing the leading component. The top-level component + * can be skipped. This is not done if the hostname is a numerical IP. + */ + nsTArray<nsCString> hosts; + hosts.AppendElement(host); + + if (!IsCanonicalizedIP(host)) { + host.BeginReading(begin); + host.EndReading(end); + int numHostComponents = 0; + while (RFindInReadable("."_ns, begin, end) && + numHostComponents < MAX_HOST_COMPONENTS) { + // don't bother checking toplevel domains + if (++numHostComponents >= 2) { + host.EndReading(iter); + hosts.AppendElement(Substring(end, iter)); + } + end = begin; + host.BeginReading(begin); + } + } + + /** + * From the protocol doc: + * For the path, the client will also try at most 6 different strings. + * They are: + * a) the exact path of the url, including query parameters + * b) the exact path of the url, without query parameters + * c) the 4 paths formed by starting at the root (/) and + * successively appending path components, including a trailing + * slash. This behavior should only extend up to the next-to-last + * path component, that is, a trailing slash should never be + * appended that was not present in the original url. + */ + nsTArray<nsCString> paths; + nsAutoCString pathToAdd; + + path.BeginReading(begin); + path.EndReading(end); + iter = begin; + if (FindCharInReadable('?', iter, end)) { + pathToAdd = Substring(begin, iter); + paths.AppendElement(pathToAdd); + end = iter; + } + + int numPathComponents = 1; + iter = begin; + while (FindCharInReadable('/', iter, end) && + numPathComponents < MAX_PATH_COMPONENTS) { + iter++; + pathToAdd.Assign(Substring(begin, iter)); + paths.AppendElement(pathToAdd); + numPathComponents++; + } + + // If we haven't already done so, add the full path + if (!pathToAdd.Equals(path)) { + paths.AppendElement(path); + } + // Check an empty path (for whole-domain blocklist entries) + if (!paths.Contains(""_ns)) { + paths.AppendElement(""_ns); + } + + for (uint32_t hostIndex = 0; hostIndex < hosts.Length(); hostIndex++) { + for (uint32_t pathIndex = 0; pathIndex < paths.Length(); pathIndex++) { + nsCString key; + key.Assign(hosts[hostIndex]); + key.Append('/'); + key.Append(paths[pathIndex]); + + aFragments->AppendElement(key); + } + } + + return NS_OK; +} + +nsresult LookupCache::LoadPrefixSet() { + nsCOMPtr<nsIFile> psFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = psFile->AppendNative(mTableName + GetPrefixSetSuffix()); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = psFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + LOG(("stored PrefixSet exists, loading from disk")); + rv = LoadFromFile(psFile); + if (NS_FAILED(rv)) { + return rv; + } + mPrimed = true; + } else { + // The only scenario we load the old .pset file is when we haven't received + // a SafeBrowsng update before. After receiving an update, new .vlpset will + // be stored while old .pset will be removed. + if (NS_SUCCEEDED(LoadLegacyFile())) { + mPrimed = true; + } else { + LOG(("no (usable) stored PrefixSet found")); + } + } + +#ifdef DEBUG + if (mPrimed) { + uint32_t size = SizeOfPrefixSet(); + LOG(("SB tree done, size = %d bytes\n", size)); + } +#endif + + return NS_OK; +} + +size_t LookupCache::SizeOfPrefixSet() const { + return mVLPrefixSet->SizeOfIncludingThis(moz_malloc_size_of); +} + +#if defined(DEBUG) +void LookupCache::DumpCache() const { + if (!LOG_ENABLED()) { + return; + } + + for (const auto& cacheEntry : mFullHashCache) { + CachedFullHashResponse* response = cacheEntry.GetWeak(); + + nsAutoCString prefix; + CStringToHexString( + nsCString(reinterpret_cast<const char*>(&cacheEntry.GetKey()), + PREFIX_SIZE), + prefix); + LOG(("Cache prefix(%s): %s, Expiry: %s", mTableName.get(), prefix.get(), + GetFormattedTimeString(response->negativeCacheExpirySec).get())); + + FullHashExpiryCache& fullHashes = response->fullHashes; + for (const auto& fullHashEntry : fullHashes) { + nsAutoCString fullhash; + CStringToHexString(fullHashEntry.GetKey(), fullhash); + LOG((" - %s, Expiry: %s", fullhash.get(), + GetFormattedTimeString(fullHashEntry.GetData()).get())); + } + } +} +#endif + +nsresult LookupCache::StoreToFile(nsCOMPtr<nsIFile>& aFile) { + NS_ENSURE_ARG_POINTER(aFile); + + uint32_t fileSize = sizeof(Header) + + mVLPrefixSet->CalculatePreallocateSize() + + nsCrc32CheckSumedOutputStream::CHECKSUM_SIZE; + + nsCOMPtr<nsIOutputStream> localOutFile; + nsresult rv = + NS_NewSafeLocalFileOutputStream(getter_AddRefs(localOutFile), aFile, + PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Preallocate the file storage + { + nsCOMPtr<nsIFileOutputStream> fos(do_QueryInterface(localOutFile)); + Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_VLPS_FALLOCATE_TIME> timer; + + Unused << fos->Preallocate(fileSize); + } + + nsCOMPtr<nsIOutputStream> out; + rv = NS_NewCrc32OutputStream(getter_AddRefs(out), localOutFile.forget(), + std::min(fileSize, MAX_BUFFER_SIZE)); + + // Write header + Header header; + GetHeader(header); + + rv = WriteValue(out, header); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Write prefixes + rv = mVLPrefixSet->WritePrefixes(out); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Write checksum + nsCOMPtr<nsISafeOutputStream> safeOut = do_QueryInterface(out, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = safeOut->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("[%s] Storing PrefixSet successful", mTableName.get())); + + // This is to remove old ".pset" files if exist + Unused << ClearLegacyFile(); + return NS_OK; +} + +nsresult LookupCache::LoadFromFile(nsCOMPtr<nsIFile>& aFile) { + NS_ENSURE_ARG_POINTER(aFile); + + Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_VLPS_FILELOAD_TIME> timer; + + nsCOMPtr<nsIInputStream> localInFile; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(localInFile), aFile, + PR_RDONLY | nsIFile::OS_READAHEAD); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Calculate how big the file is, make sure our read buffer isn't bigger + // than the file itself which is just wasting memory. + int64_t fileSize; + rv = aFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (fileSize < 0 || fileSize > UINT32_MAX) { + return NS_ERROR_FAILURE; + } + + uint32_t bufferSize = + std::min<uint32_t>(static_cast<uint32_t>(fileSize), MAX_BUFFER_SIZE); + + // Convert to buffered stream + nsCOMPtr<nsIInputStream> in; + rv = NS_NewBufferedInputStream(getter_AddRefs(in), localInFile.forget(), + bufferSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Load header + Header header; + rv = ReadValue(in, header); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG(("Failed to read header for %s", mTableName.get())); + return NS_ERROR_FILE_CORRUPTED; + } + + rv = SanityCheck(header); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Load data + rv = mVLPrefixSet->LoadPrefixes(in); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Load crc32 checksum and verify + rv = VerifyCRC32(in); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mPrimed = true; + + LOG(("[%s] Loading PrefixSet successful", mTableName.get())); + return NS_OK; +} + +// This function assumes CRC32 checksum is in the end of the input stream +nsresult LookupCache::VerifyCRC32(nsCOMPtr<nsIInputStream>& aIn) { + nsCOMPtr<nsISeekableStream> seekIn = do_QueryInterface(aIn); + nsresult rv = seekIn->Seek(nsISeekableStream::NS_SEEK_SET, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint64_t len; + rv = aIn->Available(&len); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t calculateCrc32 = ~0; + + // We don't want to include the checksum itself + len = len - nsCrc32CheckSumedOutputStream::CHECKSUM_SIZE; + + static const uint64_t STREAM_BUFFER_SIZE = 4096; + char buffer[STREAM_BUFFER_SIZE]; + while (len) { + uint32_t read; + uint64_t readLimit = std::min<uint64_t>(STREAM_BUFFER_SIZE, len); + + rv = aIn->Read(buffer, readLimit, &read); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + calculateCrc32 = ComputeCrc32c( + calculateCrc32, reinterpret_cast<const uint8_t*>(buffer), read); + + len -= read; + } + + // Now read the CRC32 + uint32_t crc32; + ReadValue(aIn, crc32); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (crc32 != calculateCrc32) { + return NS_ERROR_FILE_CORRUPTED; + } + + return NS_OK; +} + +nsresult LookupCacheV2::Has(const Completion& aCompletion, bool* aHas, + uint32_t* aMatchLength, bool* aConfirmed) { + *aHas = *aConfirmed = false; + *aMatchLength = 0; + + uint32_t length = 0; + nsDependentCSubstring fullhash; + fullhash.Rebind((const char*)aCompletion.buf, COMPLETE_SIZE); + + uint32_t prefix = aCompletion.ToUint32(); + + nsresult rv = mVLPrefixSet->Matches(prefix, fullhash, &length); + NS_ENSURE_SUCCESS(rv, rv); + + if (length == 0) { + return NS_OK; + } + + MOZ_ASSERT(length == PREFIX_SIZE || length == COMPLETE_SIZE); + + *aHas = true; + *aMatchLength = length; + *aConfirmed = length == COMPLETE_SIZE; + + if (!(*aConfirmed)) { + rv = CheckCache(aCompletion, aHas, aConfirmed); + } + + return rv; +} + +nsresult LookupCacheV2::Build(AddPrefixArray& aAddPrefixes, + AddCompleteArray& aAddCompletes) { + nsresult rv = mVLPrefixSet->SetPrefixes(aAddPrefixes, aAddCompletes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mPrimed = true; + + return NS_OK; +} + +nsresult LookupCacheV2::GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes) { + if (!mPrimed) { + // This can happen if its a new table, so no error. + LOG(("GetPrefixes from empty LookupCache")); + return NS_OK; + } + + return mVLPrefixSet->GetFixedLengthPrefixes(&aAddPrefixes, nullptr); +} + +nsresult LookupCacheV2::GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes, + FallibleTArray<nsCString>& aAddCompletes) { + if (!mPrimed) { + // This can happen if its a new table, so no error. + LOG(("GetHashes from empty LookupCache")); + return NS_OK; + } + + return mVLPrefixSet->GetFixedLengthPrefixes(&aAddPrefixes, &aAddCompletes); +} + +nsresult LookupCacheV2::GetPrefixByIndex(uint32_t aIndex, + uint32_t* aOutPrefix) const { + NS_ENSURE_ARG_POINTER(aOutPrefix); + + return mVLPrefixSet->GetFixedLengthPrefixByIndex(aIndex, aOutPrefix); +} + +void LookupCacheV2::AddGethashResultToCache( + const AddCompleteArray& aAddCompletes, const MissPrefixArray& aMissPrefixes, + int64_t aExpirySec) { + static const int64_t CACHE_DURATION_SEC = 15 * 60; + int64_t defaultExpirySec = PR_Now() / PR_USEC_PER_SEC + CACHE_DURATION_SEC; + if (aExpirySec != 0) { + defaultExpirySec = aExpirySec; + } + + for (const AddComplete& add : aAddCompletes) { + nsDependentCSubstring fullhash( + reinterpret_cast<const char*>(add.CompleteHash().buf), COMPLETE_SIZE); + + CachedFullHashResponse* response = + mFullHashCache.GetOrInsertNew(add.ToUint32()); + response->negativeCacheExpirySec = defaultExpirySec; + + FullHashExpiryCache& fullHashes = response->fullHashes; + fullHashes.InsertOrUpdate(fullhash, defaultExpirySec); + } + + for (const Prefix& prefix : aMissPrefixes) { + CachedFullHashResponse* response = + mFullHashCache.GetOrInsertNew(prefix.ToUint32()); + + response->negativeCacheExpirySec = defaultExpirySec; + } +} + +void LookupCacheV2::GetHeader(Header& aHeader) { + aHeader.magic = LookupCacheV2::VLPSET_MAGIC; + aHeader.version = LookupCacheV2::VLPSET_VERSION; +} + +nsresult LookupCacheV2::SanityCheck(const Header& aHeader) { + if (aHeader.magic != LookupCacheV2::VLPSET_MAGIC) { + return NS_ERROR_FILE_CORRUPTED; + } + + if (aHeader.version != LookupCacheV2::VLPSET_VERSION) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult LookupCacheV2::LoadLegacyFile() { + // Because mozilla Safe Browsing v2 server only includes completions + // in the update, we can simplify this function by only loading .sbtore + if (!mProvider.EqualsLiteral("mozilla")) { + return NS_OK; + } + + HashStore store(mTableName, mProvider, mRootStoreDirectory); + + // Support loading version 3 HashStore. + nsresult rv = store.Open(3); + NS_ENSURE_SUCCESS(rv, rv); + + if (store.AddChunks().Length() == 0 && store.SubChunks().Length() == 0) { + // Return when file doesn't exist + return NS_OK; + } + + AddPrefixArray prefix; + AddCompleteArray addComplete; + + rv = store.ReadCompletionsLegacyV3(addComplete); + NS_ENSURE_SUCCESS(rv, rv); + + return Build(prefix, addComplete); +} + +nsresult LookupCacheV2::ClearLegacyFile() { + nsCOMPtr<nsIFile> file; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->AppendNative(mTableName + ".pset"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + rv = file->Remove(false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("[%s]Old PrefixSet is successfully removed!", mTableName.get())); + } + + return NS_OK; +} + +nsCString LookupCacheV2::GetPrefixSetSuffix() const { return ".vlpset"_ns; } + +// Support creating built-in entries for phsihing, malware, unwanted, harmful, +// tracking/tracking exceptionlist and flash block tables. +// +nsresult LookupCacheV2::LoadMozEntries() { + // We already have the entries, return + if (!IsEmpty() || IsPrimed()) { + return NS_OK; + } + + nsTArray<nsLiteralCString> entries; + + if (mTableName.EqualsLiteral("moztest-phish-simple")) { + // Entries for phishing table + entries.AppendElement("itisatrap.org/firefox/its-a-trap.html"_ns); + } else if (mTableName.EqualsLiteral("moztest-malware-simple")) { + // Entries for malware table + entries.AppendElement("itisatrap.org/firefox/its-an-attack.html"_ns); + } else if (mTableName.EqualsLiteral("moztest-unwanted-simple")) { + // Entries for unwanted table + entries.AppendElement("itisatrap.org/firefox/unwanted.html"_ns); + } else if (mTableName.EqualsLiteral("moztest-harmful-simple")) { + // Entries for harmfule tables + entries.AppendElement("itisatrap.org/firefox/harmful.html"_ns); + } else if (mTableName.EqualsLiteral("moztest-track-simple")) { + // Entries for tracking table + entries.AppendElement("trackertest.org/"_ns); + entries.AppendElement("itisatracker.org/"_ns); + } else if (mTableName.EqualsLiteral("moztest-trackwhite-simple")) { + // Entries for tracking entitylist table + entries.AppendElement("itisatrap.org/?resource=itisatracker.org"_ns); + } else if (mTableName.EqualsLiteral("moztest-block-simple")) { + // Entries for flash block table + entries.AppendElement("itisatrap.org/firefox/blocked.html"_ns); + } else { + MOZ_ASSERT_UNREACHABLE(); + } + + AddPrefixArray prefix; + AddCompleteArray completes; + for (const auto& entry : entries) { + AddComplete add; + if (NS_FAILED(add.complete.FromPlaintext(entry))) { + continue; + } + if (!completes.AppendElement(add, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return Build(prefix, completes); +} + +} // namespace safebrowsing +} // namespace mozilla diff --git a/toolkit/components/url-classifier/LookupCache.h b/toolkit/components/url-classifier/LookupCache.h new file mode 100644 index 0000000000..090f8eb733 --- /dev/null +++ b/toolkit/components/url-classifier/LookupCache.h @@ -0,0 +1,356 @@ +//* -*- 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/. */ + +#ifndef LookupCache_h__ +#define LookupCache_h__ + +#include "Entries.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "mozilla/RefPtr.h" +#include "nsUrlClassifierPrefixSet.h" +#include "VariableLengthPrefixSet.h" +#include "mozilla/Logging.h" +#include "mozilla/TypedEnumBits.h" +#include "nsIUrlClassifierInfo.h" + +namespace mozilla { +namespace safebrowsing { + +#define MAX_HOST_COMPONENTS 5 +#define MAX_PATH_COMPONENTS 4 + +class LookupResult { + public: + LookupResult() + : mNoise(false), + mProtocolConfirmed(false), + mPartialHashLength(0), + mConfirmed(false), + mProtocolV2(true) {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LookupResult); + + // The fragment that matched in the LookupCache + union { + Prefix fixedLengthPrefix; + Completion complete; + } hash; + + const Completion& CompleteHash() const { + MOZ_ASSERT(!mNoise); + return hash.complete; + } + + nsCString PartialHash() const { + MOZ_ASSERT(mPartialHashLength <= COMPLETE_SIZE); + if (mNoise) { + return nsCString( + reinterpret_cast<const char*>(hash.fixedLengthPrefix.buf), + PREFIX_SIZE); + } else { + return nsCString(reinterpret_cast<const char*>(hash.complete.buf), + mPartialHashLength); + } + } + + nsAutoCString PartialHashHex() const { + nsAutoCString hex; + for (size_t i = 0; i < mPartialHashLength; i++) { + hex.AppendPrintf("%.2X", hash.complete.buf[i]); + } + return hex; + } + + bool Confirmed() const { return mConfirmed || mProtocolConfirmed; } + + // True if we have a complete match for this hash in the table. + bool Complete() const { return mPartialHashLength == COMPLETE_SIZE; } + + // True if this is a noise entry, i.e. an extra entry + // that is inserted to mask the true URL we are requesting. + // Noise entries will not have a complete 256-bit hash as + // they are fetched from the local 32-bit database and we + // don't know the corresponding full URL. + bool mNoise; + + bool mProtocolConfirmed; + + nsCString mTableName; + + uint32_t mPartialHashLength; + + // True as long as this lookup is complete and hasn't expired. + bool mConfirmed; + + bool mProtocolV2; + + private: + ~LookupResult() = default; +}; + +typedef nsTArray<RefPtr<LookupResult>> LookupResultArray; + +class CacheResult { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheResult); + + enum { V2, V4 }; + + virtual int Ver() const = 0; + virtual bool findCompletion(const Completion& aCompletion) const = 0; + + template <typename T> + static const T* Cast(const CacheResult* aThat) { + return ((aThat && T::VER == aThat->Ver()) + ? reinterpret_cast<const T*>(aThat) + : nullptr); + } + + nsCString table; + Prefix prefix; + + protected: + virtual ~CacheResult() = default; +}; + +class CacheResultV2 final : public CacheResult { + public: + static const int VER; + + // True when 'prefix' in CacheResult indicates a prefix that + // cannot be completed. + bool miss = false; + + // 'completion' and 'addChunk' are used when 'miss' field is false. + Completion completion; + uint32_t addChunk; + + bool operator==(const CacheResultV2& aOther) const { + if (table != aOther.table || prefix != aOther.prefix || + miss != aOther.miss) { + return false; + } + + if (miss) { + return true; + } + return completion == aOther.completion && addChunk == aOther.addChunk; + } + + bool findCompletion(const Completion& aCompletion) const override { + return completion == aCompletion; + } + + virtual int Ver() const override { return VER; } +}; + +class CacheResultV4 final : public CacheResult { + public: + static const int VER; + + CachedFullHashResponse response; + + bool operator==(const CacheResultV4& aOther) const { + return table == aOther.table && prefix == aOther.prefix && + response == aOther.response; + } + + bool findCompletion(const Completion& aCompletion) const override { + nsDependentCSubstring completion( + reinterpret_cast<const char*>(aCompletion.buf), COMPLETE_SIZE); + return response.fullHashes.Contains(completion); + } + + virtual int Ver() const override { return VER; } +}; + +typedef nsTArray<RefPtr<const CacheResult>> ConstCacheResultArray; + +class LookupCache { + public: + // Check for a canonicalized IP address. + static bool IsCanonicalizedIP(const nsACString& aHost); + + // take a lookup string (www.hostname.com/path/to/resource.html) and + // expand it into the set of fragments that should be searched for in an + // entry + static nsresult GetLookupFragments(const nsACString& aSpec, + nsTArray<nsCString>* aFragments); + + static nsresult GetLookupEntitylistFragments(const nsACString& aSpec, + nsTArray<nsCString>* aFragments); + + LookupCache(const nsACString& aTableName, const nsACString& aProvider, + nsCOMPtr<nsIFile>& aStoreFile); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LookupCache); + + const nsCString& TableName() const { return mTableName; } + + // The directory handle where we operate will + // be moved away when a backup is made. + nsresult UpdateRootDirHandle(nsCOMPtr<nsIFile>& aRootStoreDirectory); + + // Write data stored in lookup cache to disk. + nsresult WriteFile(); + + bool IsPrimed() const { return mPrimed; }; + + // Called when update to clear expired entries. + void InvalidateExpiredCacheEntries(); + + // Copy fullhash cache from another LookupCache. + void CopyFullHashCache(const LookupCache* aSource); + + // Clear fullhash cache from fullhash/gethash response. + void ClearCache(); + + // Check if completions can be found in cache. + // Currently this is only used by testcase. + bool IsInCache(uint32_t key) const { return mFullHashCache.Get(key); }; + + uint32_t PrefixLength() const { + return mVLPrefixSet->FixedLengthPrefixLength(); + } + +#if DEBUG + void DumpCache() const; +#endif + + void GetCacheInfo(nsIUrlClassifierCacheInfo** aCache) const; + + nsresult VerifyCRC32(nsCOMPtr<nsIInputStream>& aIn); + + virtual nsresult Open(); + virtual nsresult Init(); + ; + virtual nsresult ClearPrefixes(); + virtual nsresult Has(const Completion& aCompletion, bool* aHas, + uint32_t* aMatchLength, bool* aConfirmed) = 0; + + // Prefix files file header + struct Header { + uint32_t magic; + uint32_t version; + }; + + virtual nsresult StoreToFile(nsCOMPtr<nsIFile>& aFile); + virtual nsresult LoadFromFile(nsCOMPtr<nsIFile>& aFile); + + virtual bool IsEmpty() const; + + virtual void ClearAll(); + + virtual nsresult LoadMozEntries() = 0; + + template <typename T> + static T* Cast(LookupCache* aThat) { + return ((aThat && T::VER == aThat->Ver()) ? reinterpret_cast<T*>(aThat) + : nullptr); + } + template <typename T> + static const T* Cast(const LookupCache* aThat) { + return ((aThat && T::VER == aThat->Ver()) + ? reinterpret_cast<const T*>(aThat) + : nullptr); + } + + private: + nsresult LoadPrefixSet(); + + virtual size_t SizeOfPrefixSet() const; + virtual nsCString GetPrefixSetSuffix() const = 0; + + virtual int Ver() const = 0; + + virtual void GetHeader(Header& aHeader) = 0; + virtual nsresult SanityCheck(const Header& aHeader) = 0; + virtual nsresult LoadLegacyFile() = 0; + virtual nsresult ClearLegacyFile() = 0; + + protected: + virtual ~LookupCache() = default; + + // Buffer size for file read/write + static const uint32_t MAX_BUFFER_SIZE; + + // Check completions in positive cache and prefix in negative cache. + // 'aHas' and 'aConfirmed' are output parameters. + nsresult CheckCache(const Completion& aCompletion, bool* aHas, + bool* aConfirmed); + + bool mPrimed; // true when the PrefixSet has been loaded (or constructed) + const nsCString mTableName; + const nsCString mProvider; + nsCOMPtr<nsIFile> mRootStoreDirectory; + nsCOMPtr<nsIFile> mStoreDirectory; + + // For gtest to inspect private members. + friend class PerProviderDirectoryTestUtils; + + // Cache stores fullhash response(V4)/gethash response(V2) + FullHashResponseMap mFullHashCache; + + RefPtr<VariableLengthPrefixSet> mVLPrefixSet; + + template <typename T> + static nsresult WriteValue(nsIOutputStream* aOutputStream, const T& aValue); + template <typename T> + static nsresult ReadValue(nsIInputStream* aInputStream, T& aValue); +}; + +typedef nsTArray<RefPtr<LookupCache>> LookupCacheArray; + +class LookupCacheV2 final : public LookupCache { + public: + explicit LookupCacheV2(const nsACString& aTableName, + const nsACString& aProvider, + nsCOMPtr<nsIFile>& aStoreFile) + : LookupCache(aTableName, aProvider, aStoreFile) {} + + virtual nsresult Has(const Completion& aCompletion, bool* aHas, + uint32_t* aMatchLength, bool* aConfirmed) override; + + nsresult Build(AddPrefixArray& aAddPrefixes, AddCompleteArray& aAddCompletes); + + nsresult GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes); + nsresult GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes, + FallibleTArray<nsCString>& aAddCompletes); + nsresult GetPrefixByIndex(uint32_t aIndex, uint32_t* aOutPrefix) const; + + // This will Clear() the passed arrays when done. + // 'aExpirySec' is used by testcase to config an expired time. + void AddGethashResultToCache(const AddCompleteArray& aAddCompletes, + const MissPrefixArray& aMissPrefixes, + int64_t aExpirySec = 0); + + virtual nsresult LoadMozEntries() override; + + static const int VER; + static const uint32_t VLPSET_MAGIC; + static const uint32_t VLPSET_VERSION; + + protected: + virtual nsCString GetPrefixSetSuffix() const override; + + private: + ~LookupCacheV2() = default; + + virtual int Ver() const override { return VER; } + + virtual void GetHeader(Header& aHeader) override; + virtual nsresult SanityCheck(const Header& aHeader) override; + + virtual nsresult LoadLegacyFile() override; + virtual nsresult ClearLegacyFile() override; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/LookupCacheV4.cpp b/toolkit/components/url-classifier/LookupCacheV4.cpp new file mode 100644 index 0000000000..14ebc890d4 --- /dev/null +++ b/toolkit/components/url-classifier/LookupCacheV4.cpp @@ -0,0 +1,577 @@ +//* -*- 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 "LookupCacheV4.h" +#include "HashStore.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsCheckSummedOutputStream.h" +#include "nsUrlClassifierDBService.h" +#include "crc32c.h" +#include <string> + +// 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 METADATA_SUFFIX ".metadata"_ns + +namespace mozilla { +namespace safebrowsing { + +//////////////////////////////////////////////////////////////////////// + +// Prefixes coming from updates and VLPrefixSet are both stored in the HashTable +// where the (key, value) pair is a prefix size and a lexicographic-sorted +// string. The difference is prefixes from updates use std:string(to avoid +// additional copies) and prefixes from VLPrefixSet use nsCString. This class +// provides a common interface for the partial update algorithm to make it +// easier to operate on two different kind prefix string map.. +class VLPrefixSet { + public: + explicit VLPrefixSet(const PrefixStringMap& aMap); + + // This function will merge the prefix map in VLPrefixSet to aPrefixMap. + void Merge(PrefixStringMap& aPrefixMap); + + // Find the smallest string from the map in VLPrefixSet. + bool GetSmallestPrefix(nsACString& aOutString) const; + + // Return the number of prefixes in the map + uint32_t Count() const { return mCount; } + + private: + // PrefixString structure contains a lexicographic-sorted string with + // a |pos| variable to indicate which substring we are pointing to right now. + // |pos| increases each time GetSmallestPrefix finds the smallest string. + struct PrefixString { + PrefixString(const nsACString& aStr, uint32_t aSize) + : data(aStr), pos(0), size(aSize) { + MOZ_ASSERT(data.Length() % size == 0, + "PrefixString length must be a multiple of the prefix size."); + } + + void getRemainingString(nsACString& out) { + MOZ_ASSERT(out.IsEmpty()); + if (remaining() > 0) { + out = Substring(data, pos); + } + } + void getPrefix(nsACString& out) { + MOZ_ASSERT(out.IsEmpty()); + if (remaining() >= size) { + out = Substring(data, pos, size); + } else { + MOZ_ASSERT(remaining() == 0, + "Remaining bytes but not enough for a (size)-byte prefix."); + } + } + void next() { + pos += size; + MOZ_ASSERT(pos <= data.Length()); + } + uint32_t remaining() { + return data.Length() - pos; + MOZ_ASSERT(pos <= data.Length()); + } + + nsCString data; + uint32_t pos; + uint32_t size; + }; + + nsClassHashtable<nsUint32HashKey, PrefixString> mMap; + uint32_t mCount; +}; + +nsresult LookupCacheV4::Has(const Completion& aCompletion, bool* aHas, + uint32_t* aMatchLength, bool* aConfirmed) { + *aHas = *aConfirmed = false; + *aMatchLength = 0; + + uint32_t length = 0; + nsDependentCSubstring fullhash; + fullhash.Rebind((const char*)aCompletion.buf, COMPLETE_SIZE); + + // It is tricky that we use BigEndian read for V4 while use + // Completion.ToUint32 for V2. This is because in V2, prefixes are converted + // to integers and then sorted internally. In V4, prefixes recieved are + // already lexicographical order sorted, so when we manipulate these prefixes + // with integer form, we always use big endian so prefixes remain the same + // order. + uint32_t prefix = BigEndian::readUint32( + reinterpret_cast<const uint32_t*>(fullhash.BeginReading())); + + nsresult rv = mVLPrefixSet->Matches(prefix, fullhash, &length); + NS_ENSURE_SUCCESS(rv, rv); + + if (length == 0) { + return NS_OK; + } + + MOZ_ASSERT(length >= PREFIX_SIZE && length <= COMPLETE_SIZE); + + // For V4, We don't set |aConfirmed| to true even if we found a match + // for 32-bytes prefix. |aConfirmed| is only set if a match is found in cache. + *aHas = true; + *aMatchLength = length; + + // Even though V4 supports variable-length prefix, we always send 4-bytes for + // completion (Bug 1323953). This means cached prefix length is also 4-bytes. + return CheckCache(aCompletion, aHas, aConfirmed); +} + +nsresult LookupCacheV4::Build(PrefixStringMap& aPrefixMap) { + Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_VLPS_CONSTRUCT_TIME> timer; + + nsresult rv = mVLPrefixSet->SetPrefixes(aPrefixMap); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mPrimed = true; + + return NS_OK; +} + +nsresult LookupCacheV4::GetPrefixes(PrefixStringMap& aPrefixMap) { + if (!mPrimed) { + // This can happen if its a new table, so no error. + LOG(("GetPrefixes from empty LookupCache")); + return NS_OK; + } + return mVLPrefixSet->GetPrefixes(aPrefixMap); +} + +nsresult LookupCacheV4::GetFixedLengthPrefixes( + FallibleTArray<uint32_t>& aPrefixes) { + return mVLPrefixSet->GetFixedLengthPrefixes(&aPrefixes, nullptr); +} + +nsresult LookupCacheV4::GetFixedLengthPrefixByIndex( + uint32_t aIndex, uint32_t* aOutPrefix) const { + NS_ENSURE_ARG_POINTER(aOutPrefix); + + return mVLPrefixSet->GetFixedLengthPrefixByIndex(aIndex, aOutPrefix); +} + +nsresult LookupCacheV4::ClearLegacyFile() { + nsCOMPtr<nsIFile> file; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->AppendNative(mTableName + ".pset"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + rv = file->Remove(false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("[%s] Old PrefixSet is successfully removed!", mTableName.get())); + } + + return NS_OK; +} + +nsresult LookupCacheV4::LoadLegacyFile() { + nsCOMPtr<nsIFile> file; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->AppendNative(mTableName + ".pset"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStream> localInFile; + rv = NS_NewLocalFileInputStream(getter_AddRefs(localInFile), file, + PR_RDONLY | nsIFile::OS_READAHEAD); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Calculate how big the file is, make sure our read buffer isn't bigger + // than the file itself which is just wasting memory. + int64_t fileSize; + rv = file->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (fileSize < 0 || fileSize > UINT32_MAX) { + return NS_ERROR_FAILURE; + } + + uint32_t bufferSize = + std::min<uint32_t>(static_cast<uint32_t>(fileSize), MAX_BUFFER_SIZE); + + // Convert to buffered stream + nsCOMPtr<nsIInputStream> in; + rv = NS_NewBufferedInputStream(getter_AddRefs(in), localInFile.forget(), + bufferSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Load data + rv = mVLPrefixSet->LoadPrefixes(in); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mPrimed = true; + + LOG(("[%s] Loading Legacy PrefixSet successful", mTableName.get())); + return NS_OK; +} + +void LookupCacheV4::GetHeader(Header& aHeader) { + aHeader.magic = LookupCacheV4::VLPSET_MAGIC; + aHeader.version = LookupCacheV4::VLPSET_VERSION; +} + +nsresult LookupCacheV4::SanityCheck(const Header& aHeader) { + if (aHeader.magic != LookupCacheV4::VLPSET_MAGIC) { + return NS_ERROR_FILE_CORRUPTED; + } + + if (aHeader.version != LookupCacheV4::VLPSET_VERSION) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsCString LookupCacheV4::GetPrefixSetSuffix() const { return ".vlpset"_ns; } + +static nsresult AppendPrefixToMap(PrefixStringMap& prefixes, + const nsACString& prefix) { + uint32_t len = prefix.Length(); + MOZ_ASSERT(len >= PREFIX_SIZE && len <= COMPLETE_SIZE); + if (!len) { + return NS_OK; + } + + nsCString* prefixString = prefixes.GetOrInsertNew(len); + if (!prefixString->Append(prefix, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +static nsresult InitCrypto(nsCOMPtr<nsICryptoHash>& aCrypto) { + nsresult rv; + aCrypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aCrypto->Init(nsICryptoHash::SHA256); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "InitCrypto failed"); + + return rv; +} + +// Read prefix into a buffer and also update the hash which +// keeps track of the sha256 hash +static void UpdateSHA256(nsICryptoHash* aCrypto, const nsACString& aPrefix) { + MOZ_ASSERT(aCrypto); + aCrypto->Update( + reinterpret_cast<uint8_t*>(const_cast<char*>(aPrefix.BeginReading())), + aPrefix.Length()); +} + +// Please see https://bug1287058.bmoattachments.org/attachment.cgi?id=8795366 +// for detail about partial update algorithm. +nsresult LookupCacheV4::ApplyUpdate(RefPtr<TableUpdateV4> aTableUpdate, + PrefixStringMap& aInputMap, + PrefixStringMap& aOutputMap) { + MOZ_ASSERT(aOutputMap.IsEmpty()); + + nsCOMPtr<nsICryptoHash> crypto; + nsresult rv = InitCrypto(crypto); + if (NS_FAILED(rv)) { + return rv; + } + + // oldPSet contains prefixes we already have or we just merged last round. + // addPSet contains prefixes stored in tableUpdate which should be merged with + // oldPSet. + VLPrefixSet oldPSet(aInputMap); + VLPrefixSet addPSet(aTableUpdate->Prefixes()); + + // RemovalIndiceArray is a sorted integer array indicating the index of prefix + // we should remove from the old prefix set(according to lexigraphic order). + // |removalIndex| is the current index of RemovalIndiceArray. + // |numOldPrefixPicked| is used to record how many prefixes we picked from the + // old map. + const TableUpdateV4::RemovalIndiceArray& removalArray = + aTableUpdate->RemovalIndices(); + uint32_t removalIndex = 0; + int32_t numOldPrefixPicked = -1; + + nsAutoCString smallestOldPrefix; + nsAutoCString smallestAddPrefix; + + bool isOldMapEmpty = false, isAddMapEmpty = false; + + // This is used to avoid infinite loop for partial update algorithm. + // The maximum loops will be the number of old prefixes plus the number of add + // prefixes. + int32_t index = oldPSet.Count() + addPSet.Count() + 1; + for (; index > 0; index--) { + // Get smallest prefix from the old prefix set if we don't have one + if (smallestOldPrefix.IsEmpty() && !isOldMapEmpty) { + isOldMapEmpty = !oldPSet.GetSmallestPrefix(smallestOldPrefix); + } + + // Get smallest prefix from add prefix set if we don't have one + if (smallestAddPrefix.IsEmpty() && !isAddMapEmpty) { + isAddMapEmpty = !addPSet.GetSmallestPrefix(smallestAddPrefix); + } + + bool pickOld; + + // If both prefix sets are not empty, then compare to find the smaller one. + if (!isOldMapEmpty && !isAddMapEmpty) { + if (smallestOldPrefix == smallestAddPrefix) { + LOG(("Add prefix should not exist in the original prefix set.")); + return NS_ERROR_UC_UPDATE_DUPLICATE_PREFIX; + } + + // Compare the smallest string in old prefix set and add prefix set, + // merge the smaller one into new map to ensure merged string still + // follows lexigraphic order. + pickOld = smallestOldPrefix < smallestAddPrefix; + } else if (!isOldMapEmpty && isAddMapEmpty) { + pickOld = true; + } else if (isOldMapEmpty && !isAddMapEmpty) { + pickOld = false; + // If both maps are empty, then partial update is complete. + } else { + break; + } + + if (pickOld) { + numOldPrefixPicked++; + + // If the number of picks from old map matches the removalIndex, then this + // prefix will be removed by not merging it to new map. + if (removalIndex < removalArray.Length() && + numOldPrefixPicked == removalArray[removalIndex]) { + removalIndex++; + } else { + rv = AppendPrefixToMap(aOutputMap, smallestOldPrefix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + UpdateSHA256(crypto, smallestOldPrefix); + } + smallestOldPrefix.SetLength(0); + } else { + rv = AppendPrefixToMap(aOutputMap, smallestAddPrefix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + UpdateSHA256(crypto, smallestAddPrefix); + smallestAddPrefix.SetLength(0); + } + } + + // We expect index will be greater to 0 because max number of runs will be + // the number of original prefix plus add prefix. + if (index <= 0) { + LOG(("There are still prefixes remaining after reaching maximum runs.")); + return NS_ERROR_UC_UPDATE_INFINITE_LOOP; + } + + if (removalIndex < removalArray.Length()) { + LOG( + ("There are still prefixes to remove after exhausting the old " + "PrefixSet.")); + return NS_ERROR_UC_UPDATE_WRONG_REMOVAL_INDICES; + } + + // Prefixes and removal indice from update is no longer required + // after merging the data with local prefixes. + aTableUpdate->Clear(); + + nsAutoCString sha256; + crypto->Finish(false, sha256); + if (aTableUpdate->SHA256().IsEmpty()) { + LOG(("Update sha256 hash missing.")); + Telemetry::Accumulate( + Telemetry::URLCLASSIFIER_UPDATE_ERROR, mProvider, + NS_ERROR_GET_CODE(NS_ERROR_UC_UPDATE_MISSING_CHECKSUM)); + + // Generate our own sha256 to tableUpdate to ensure there is always + // checksum in .metadata + std::string stdSha256(sha256.BeginReading(), sha256.Length()); + aTableUpdate->SetSHA256(stdSha256); + } else if (aTableUpdate->SHA256() != sha256) { + LOG(("SHA256 hash mismatch after applying partial update")); + return NS_ERROR_UC_UPDATE_CHECKSUM_MISMATCH; + } + + return NS_OK; +} + +nsresult LookupCacheV4::AddFullHashResponseToCache( + const FullHashResponseMap& aResponseMap) { + CopyClassHashTable<FullHashResponseMap>(aResponseMap, mFullHashCache); + + return NS_OK; +} + +nsresult LookupCacheV4::WriteMetadata( + RefPtr<const TableUpdateV4> aTableUpdate) { + NS_ENSURE_ARG_POINTER(aTableUpdate); + if (nsUrlClassifierDBService::ShutdownHasStarted()) { + return NS_ERROR_ABORT; + } + + nsCOMPtr<nsIFile> metaFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(metaFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metaFile->AppendNative(mTableName + METADATA_SUFFIX); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIOutputStream> outputStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), metaFile, + PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE); + NS_ENSURE_SUCCESS(rv, rv); + + // Write the state. + rv = WriteValue(outputStream, aTableUpdate->ClientState()); + NS_ENSURE_SUCCESS(rv, rv); + + // Write the SHA256 hash. + rv = WriteValue(outputStream, aTableUpdate->SHA256()); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +nsresult LookupCacheV4::LoadMetadata(nsACString& aState, nsACString& aSHA256) { + nsCOMPtr<nsIFile> metaFile; + nsresult rv = mStoreDirectory->Clone(getter_AddRefs(metaFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metaFile->AppendNative(mTableName + METADATA_SUFFIX); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> localInFile; + rv = NS_NewLocalFileInputStream(getter_AddRefs(localInFile), metaFile, + PR_RDONLY | nsIFile::OS_READAHEAD); + if (NS_FAILED(rv)) { + LOG(("Unable to open metadata file.")); + return rv; + } + + // Read the list state. + rv = ReadValue(localInFile, aState); + if (NS_FAILED(rv)) { + LOG(("Failed to read state.")); + return rv; + } + + // Read the SHA256 hash. + rv = ReadValue(localInFile, aSHA256); + if (NS_FAILED(rv)) { + LOG(("Failed to read SHA256 hash.")); + return rv; + } + + return rv; +} + +VLPrefixSet::VLPrefixSet(const PrefixStringMap& aMap) : mCount(0) { + for (const auto& entry : aMap) { + uint32_t size = entry.GetKey(); + MOZ_ASSERT(entry.GetData()->Length() % size == 0, + "PrefixString must be a multiple of the prefix size."); + mMap.InsertOrUpdate(size, MakeUnique<PrefixString>(*entry.GetData(), size)); + mCount += entry.GetData()->Length() / size; + } +} + +void VLPrefixSet::Merge(PrefixStringMap& aPrefixMap) { + for (const auto& entry : mMap) { + nsCString* prefixString = aPrefixMap.GetOrInsertNew(entry.GetKey()); + PrefixString* str = entry.GetWeak(); + + nsAutoCString remainingString; + str->getRemainingString(remainingString); + if (!remainingString.IsEmpty()) { + MOZ_ASSERT(remainingString.Length() == str->remaining()); + prefixString->Append(remainingString); + } + } +} + +bool VLPrefixSet::GetSmallestPrefix(nsACString& aOutString) const { + PrefixString* pick = nullptr; + for (const auto& entry : mMap) { + PrefixString* str = entry.GetWeak(); + + if (str->remaining() <= 0) { + continue; + } + + if (aOutString.IsEmpty()) { + str->getPrefix(aOutString); + MOZ_ASSERT(aOutString.Length() == entry.GetKey()); + pick = str; + continue; + } + + nsAutoCString cur; + str->getPrefix(cur); + if (!cur.IsEmpty() && cur < aOutString) { + aOutString.Assign(cur); + MOZ_ASSERT(aOutString.Length() == entry.GetKey()); + pick = str; + } + } + + if (pick) { + pick->next(); + } + + return pick != nullptr; +} + +nsresult LookupCacheV4::LoadMozEntries() { return NS_ERROR_NOT_IMPLEMENTED; } + +} // namespace safebrowsing +} // namespace mozilla diff --git a/toolkit/components/url-classifier/LookupCacheV4.h b/toolkit/components/url-classifier/LookupCacheV4.h new file mode 100644 index 0000000000..b0bfbadcc3 --- /dev/null +++ b/toolkit/components/url-classifier/LookupCacheV4.h @@ -0,0 +1,70 @@ +//* -*- 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/. */ + +#ifndef LookupCacheV4_h__ +#define LookupCacheV4_h__ + +#include "LookupCache.h" + +namespace mozilla { +namespace safebrowsing { + +// Forward declaration. +class TableUpdateV4; + +class LookupCacheV4 final : public LookupCache { + public: + explicit LookupCacheV4(const nsACString& aTableName, + const nsACString& aProvider, + nsCOMPtr<nsIFile>& aStoreFile) + : LookupCache(aTableName, aProvider, aStoreFile) {} + + virtual nsresult Has(const Completion& aCompletion, bool* aHas, + uint32_t* aMatchLength, bool* aConfirmed) override; + + nsresult Build(PrefixStringMap& aPrefixMap); + + nsresult GetPrefixes(PrefixStringMap& aPrefixMap); + nsresult GetFixedLengthPrefixes(FallibleTArray<uint32_t>& aPrefixes); + nsresult GetFixedLengthPrefixByIndex(uint32_t aIndex, + uint32_t* aOutPrefix) const; + + // ApplyUpdate will merge data stored in aTableUpdate with prefixes in + // aInputMap. + nsresult ApplyUpdate(RefPtr<TableUpdateV4> aTableUpdate, + PrefixStringMap& aInputMap, PrefixStringMap& aOutputMap); + + nsresult AddFullHashResponseToCache(const FullHashResponseMap& aResponseMap); + + nsresult WriteMetadata(RefPtr<const TableUpdateV4> aTableUpdate); + nsresult LoadMetadata(nsACString& aState, nsACString& aChecksum); + + virtual nsresult LoadMozEntries() override; + + static constexpr int VER = 4; + static constexpr uint32_t MAX_METADATA_VALUE_LENGTH = 256; + static constexpr uint32_t VLPSET_MAGIC = 1; + static constexpr uint32_t VLPSET_VERSION = 0x36044a35; + + protected: + virtual nsCString GetPrefixSetSuffix() const override; + nsCString GetMetadataSuffix() const; + + private: + ~LookupCacheV4() = default; + + virtual int Ver() const override { return VER; } + + virtual nsresult LoadLegacyFile() override; + virtual nsresult ClearLegacyFile() override; + + virtual void GetHeader(Header& aHeader) override; + virtual nsresult SanityCheck(const Header& aHeader) override; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/ProtocolParser.cpp b/toolkit/components/url-classifier/ProtocolParser.cpp new file mode 100644 index 0000000000..2c9c919c58 --- /dev/null +++ b/toolkit/components/url-classifier/ProtocolParser.cpp @@ -0,0 +1,1080 @@ +//* -*- 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 "ProtocolParser.h" +#include "LookupCache.h" +#include "nsNetCID.h" +#include "mozilla/Components.h" +#include "mozilla/Logging.h" +#include "prnetdb.h" +#include "prprf.h" +#include "Classifier.h" + +#include "nsUrlClassifierDBService.h" +#include "nsUrlClassifierUtils.h" +#include "nsPrintfCString.h" +#include "mozilla/Base64.h" +#include "RiceDeltaDecoder.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/IntegerPrintfMacros.h" + +// MOZ_LOG=UrlClassifierProtocolParser:5 +extern mozilla::LazyLogModule gUrlClassifierDbServiceLog; +mozilla::LazyLogModule gUrlClassifierProtocolParserLog( + "UrlClassifierProtocolParser"); +#define PARSER_LOG(args) \ + MOZ_LOG(gUrlClassifierProtocolParserLog, mozilla::LogLevel::Debug, args) + +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug) + +namespace mozilla { +namespace safebrowsing { + +// Updates will fail if fed chunks larger than this +const uint32_t MAX_CHUNK_SIZE = (4 * 1024 * 1024); +// Updates will fail if the total number of tocuhed chunks is larger than this +const uint32_t MAX_CHUNK_RANGE = 1000000; + +const uint32_t DOMAIN_SIZE = 4; + +// Parse one stringified range of chunks of the form "n" or "n-m" from a +// comma-separated list of chunks. Upon return, 'begin' will point to the +// next range of chunks in the list of chunks. +static bool ParseChunkRange(const nsAutoCString& string, uint32_t* aFirst, + uint32_t* aLast) { + uint32_t numRead = PR_sscanf(string.get(), "%u-%u", aFirst, aLast); + if (numRead == 2) { + if (*aFirst > *aLast) { + uint32_t tmp = *aFirst; + *aFirst = *aLast; + *aLast = tmp; + } + return true; + } + + if (numRead == 1) { + *aLast = *aFirst; + return true; + } + + return false; +} + +/////////////////////////////////////////////////////////////// +// ProtocolParser implementation + +ProtocolParser::ProtocolParser() : mUpdateStatus(NS_OK), mUpdateWaitSec(0) {} + +ProtocolParser::~ProtocolParser() = default; + +nsresult ProtocolParser::Begin(const nsACString& aTable, + const nsTArray<nsCString>& aUpdateTables) { + // ProtocolParser objects should never be reused. + MOZ_ASSERT(mPending.IsEmpty()); + MOZ_ASSERT(mTableUpdates.IsEmpty()); + MOZ_ASSERT(mForwards.IsEmpty()); + MOZ_ASSERT(mRequestedTables.IsEmpty()); + MOZ_ASSERT(mTablesToReset.IsEmpty()); + + if (!aTable.IsEmpty()) { + SetCurrentTable(aTable); + } + SetRequestedTables(aUpdateTables); + + return NS_OK; +} + +RefPtr<TableUpdate> ProtocolParser::GetTableUpdate(const nsACString& aTable) { + for (uint32_t i = 0; i < mTableUpdates.Length(); i++) { + if (aTable.Equals(mTableUpdates[i]->TableName())) { + return mTableUpdates[i]; + } + } + + // We free automatically on destruction, ownership of these + // updates can be transferred to DBServiceWorker, which passes + // them back to Classifier when doing the updates, and that + // will free them. + RefPtr<TableUpdate> update = CreateTableUpdate(aTable); + mTableUpdates.AppendElement(update); + return update; +} + +/////////////////////////////////////////////////////////////////////// +// ProtocolParserV2 + +ProtocolParserV2::ProtocolParserV2() + : mState(PROTOCOL_STATE_CONTROL), mTableUpdate(nullptr) {} + +ProtocolParserV2::~ProtocolParserV2() = default; + +void ProtocolParserV2::SetCurrentTable(const nsACString& aTable) { + RefPtr<TableUpdate> update = GetTableUpdate(aTable); + mTableUpdate = TableUpdate::Cast<TableUpdateV2>(update); +} + +nsresult ProtocolParserV2::AppendStream(const nsACString& aData) { + if (NS_FAILED(mUpdateStatus)) return mUpdateStatus; + + nsresult rv; + if (!mPending.Append(aData, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + mRawUpdate.Append(aData); +#endif + + bool done = false; + while (!done) { + if (nsUrlClassifierDBService::ShutdownHasStarted()) { + return NS_ERROR_ABORT; + } + + if (mState == PROTOCOL_STATE_CONTROL) { + rv = ProcessControl(&done); + } else if (mState == PROTOCOL_STATE_CHUNK) { + rv = ProcessChunk(&done); + } else { + NS_ERROR("Unexpected protocol state"); + rv = NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) { + mUpdateStatus = rv; + return rv; + } + } + return NS_OK; +} + +void ProtocolParserV2::End() { + // Inbound data has already been processed in every AppendStream() call. + mTableUpdate = nullptr; +} + +nsresult ProtocolParserV2::ProcessControl(bool* aDone) { + nsresult rv; + + nsAutoCString line; + *aDone = true; + while (NextLine(line)) { + PARSER_LOG(("Processing %s\n", line.get())); + + if (StringBeginsWith(line, "i:"_ns)) { + // Set the table name from the table header line. + SetCurrentTable(Substring(line, 2)); + } else if (StringBeginsWith(line, "n:"_ns)) { + if (PR_sscanf(line.get(), "n:%d", &mUpdateWaitSec) != 1) { + PARSER_LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWaitSec)); + return NS_ERROR_FAILURE; + } + } else if (line.EqualsLiteral("r:pleasereset")) { + PARSER_LOG(("All tables will be reset.")); + mTablesToReset = mRequestedTables.Clone(); + } else if (StringBeginsWith(line, "u:"_ns)) { + rv = ProcessForward(line); + NS_ENSURE_SUCCESS(rv, rv); + } else if (StringBeginsWith(line, "a:"_ns) || + StringBeginsWith(line, "s:"_ns)) { + rv = ProcessChunkControl(line); + NS_ENSURE_SUCCESS(rv, rv); + *aDone = false; + return NS_OK; + } else if (StringBeginsWith(line, "ad:"_ns) || + StringBeginsWith(line, "sd:"_ns)) { + rv = ProcessExpirations(line); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + *aDone = true; + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessExpirations(const nsCString& aLine) { + if (!mTableUpdate) { + NS_WARNING("Got an expiration without a table."); + return NS_ERROR_FAILURE; + } + const nsACString& list = Substring(aLine, 3); + for (const auto& str : list.Split(',')) { + uint32_t first, last; + if (ParseChunkRange(nsAutoCString(str), &first, &last)) { + if (last < first) return NS_ERROR_FAILURE; + if (last - first > MAX_CHUNK_RANGE) return NS_ERROR_FAILURE; + for (uint32_t num = first; num <= last; num++) { + if (aLine[0] == 'a') { + nsresult rv = mTableUpdate->NewAddExpiration(num); + if (NS_FAILED(rv)) { + return rv; + } + } else { + nsresult rv = mTableUpdate->NewSubExpiration(num); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } else { + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessChunkControl(const nsCString& aLine) { + if (!mTableUpdate) { + NS_WARNING("Got a chunk before getting a table."); + return NS_ERROR_FAILURE; + } + + mState = PROTOCOL_STATE_CHUNK; + char command; + + mChunkState.Clear(); + + if (PR_sscanf(aLine.get(), "%c:%d:%d:%d", &command, &mChunkState.num, + &mChunkState.hashSize, &mChunkState.length) != 4) { + NS_WARNING(("PR_sscanf failed")); + return NS_ERROR_FAILURE; + } + + if (mChunkState.length > MAX_CHUNK_SIZE) { + NS_WARNING("Invalid length specified in update."); + return NS_ERROR_FAILURE; + } + + if (!(mChunkState.hashSize == PREFIX_SIZE || + mChunkState.hashSize == COMPLETE_SIZE)) { + NS_WARNING("Invalid hash size specified in update."); + return NS_ERROR_FAILURE; + } + + if (StringEndsWith(mTableUpdate->TableName(), "-shavar"_ns) || + StringEndsWith(mTableUpdate->TableName(), "-simple"_ns)) { + // Accommodate test tables ending in -simple for now. + mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB; + } else if (StringEndsWith(mTableUpdate->TableName(), "-digest256"_ns)) { + mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST; + } + nsresult rv; + switch (mChunkState.type) { + case CHUNK_ADD: + rv = mTableUpdate->NewAddChunk(mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + break; + case CHUNK_SUB: + rv = mTableUpdate->NewSubChunk(mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + break; + case CHUNK_ADD_DIGEST: + rv = mTableUpdate->NewAddChunk(mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + break; + case CHUNK_SUB_DIGEST: + rv = mTableUpdate->NewSubChunk(mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + break; + } + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessForward(const nsCString& aLine) { + const nsACString& forward = Substring(aLine, 2); + return AddForward(forward); +} + +nsresult ProtocolParserV2::AddForward(const nsACString& aUrl) { + if (!mTableUpdate) { + NS_WARNING("Forward without a table name."); + return NS_ERROR_FAILURE; + } + + ForwardedUpdate* forward = mForwards.AppendElement(); + forward->table = mTableUpdate->TableName(); + forward->url.Assign(aUrl); + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessChunk(bool* aDone) { + if (!mTableUpdate) { + NS_WARNING("Processing chunk without an active table."); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number."); + + if (mPending.Length() < mChunkState.length) { + *aDone = true; + return NS_OK; + } + + // Pull the chunk out of the pending stream data. + nsAutoCString chunk; + chunk.Assign(Substring(mPending, 0, mChunkState.length)); + mPending.Cut(0, mChunkState.length); + + *aDone = false; + mState = PROTOCOL_STATE_CONTROL; + + if (StringEndsWith(mTableUpdate->TableName(), "-shavar"_ns)) { + return ProcessShaChunk(chunk); + } + if (StringEndsWith(mTableUpdate->TableName(), "-digest256"_ns)) { + return ProcessDigestChunk(chunk); + } + return ProcessPlaintextChunk(chunk); +} + +/** + * Process a plaintext chunk (currently only used in unit tests). + */ +nsresult ProtocolParserV2::ProcessPlaintextChunk(const nsACString& aChunk) { + if (!mTableUpdate) { + NS_WARNING("Chunk received with no table."); + return NS_ERROR_FAILURE; + } + + PARSER_LOG(("Handling a %zd-byte simple chunk", aChunk.Length())); + + nsTArray<nsCString> lines; + ParseString(PromiseFlatCString(aChunk), '\n', lines); + + // non-hashed tables need to be hashed + for (uint32_t i = 0; i < lines.Length(); i++) { + nsCString& line = lines[i]; + + if (mChunkState.type == CHUNK_ADD) { + if (mChunkState.hashSize == COMPLETE_SIZE) { + Completion hash; + hash.FromPlaintext(line); + nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash); + if (NS_FAILED(rv)) { + return rv; + } + } else { + NS_ASSERTION(mChunkState.hashSize == 4, + "Only 32- or 4-byte hashes can be used for add chunks."); + Prefix hash; + hash.FromPlaintext(line); + nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash); + if (NS_FAILED(rv)) { + return rv; + } + } + } else { + nsCString::const_iterator begin, iter, end; + line.BeginReading(begin); + line.EndReading(end); + iter = begin; + uint32_t addChunk; + if (!FindCharInReadable(':', iter, end) || + PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) { + NS_WARNING("Received sub chunk without associated add chunk."); + return NS_ERROR_FAILURE; + } + iter++; + + if (mChunkState.hashSize == COMPLETE_SIZE) { + Completion hash; + hash.FromPlaintext(Substring(iter, end)); + nsresult rv = + mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + } else { + NS_ASSERTION(mChunkState.hashSize == 4, + "Only 32- or 4-byte hashes can be used for add chunks."); + Prefix hash; + hash.FromPlaintext(Substring(iter, end)); + nsresult rv = + mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessShaChunk(const nsACString& aChunk) { + uint32_t start = 0; + while (start < aChunk.Length()) { + // First four bytes are the domain key. + Prefix domain; + domain.Assign(Substring(aChunk, start, DOMAIN_SIZE)); + start += DOMAIN_SIZE; + + // Then a count of entries. + uint8_t numEntries = static_cast<uint8_t>(aChunk[start]); + start++; + + PARSER_LOG( + ("Handling a %zd-byte shavar chunk containing %u entries" + " for domain %X", + aChunk.Length(), numEntries, domain.ToUint32())); + + nsresult rv; + if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) { + rv = ProcessHostAdd(domain, numEntries, aChunk, &start); + } else if (mChunkState.type == CHUNK_ADD && + mChunkState.hashSize == COMPLETE_SIZE) { + rv = ProcessHostAddComplete(numEntries, aChunk, &start); + } else if (mChunkState.type == CHUNK_SUB && + mChunkState.hashSize == PREFIX_SIZE) { + rv = ProcessHostSub(domain, numEntries, aChunk, &start); + } else if (mChunkState.type == CHUNK_SUB && + mChunkState.hashSize == COMPLETE_SIZE) { + rv = ProcessHostSubComplete(numEntries, aChunk, &start); + } else { + NS_WARNING("Unexpected chunk type/hash size!"); + PARSER_LOG(("Got an unexpected chunk type/hash size: %s:%d", + mChunkState.type == CHUNK_ADD ? "add" : "sub", + mChunkState.hashSize)); + return NS_ERROR_FAILURE; + } + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessDigestChunk(const nsACString& aChunk) { + PARSER_LOG(("Handling a %zd-byte digest256 chunk", aChunk.Length())); + + if (mChunkState.type == CHUNK_ADD_DIGEST) { + return ProcessDigestAdd(aChunk); + } + if (mChunkState.type == CHUNK_SUB_DIGEST) { + return ProcessDigestSub(aChunk); + } + return NS_ERROR_UNEXPECTED; +} + +nsresult ProtocolParserV2::ProcessDigestAdd(const nsACString& aChunk) { + MOZ_ASSERT(mTableUpdate); + // The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes. + MOZ_ASSERT(aChunk.Length() % 32 == 0, + "Chunk length in bytes must be divisible by 4"); + uint32_t start = 0; + while (start < aChunk.Length()) { + Completion hash; + hash.Assign(Substring(aChunk, start, COMPLETE_SIZE)); + start += COMPLETE_SIZE; + nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessDigestSub(const nsACString& aChunk) { + MOZ_ASSERT(mTableUpdate); + // The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM + // is a 4 byte chunk number, and HASH is 32 bytes. + MOZ_ASSERT(aChunk.Length() % 36 == 0, + "Chunk length in bytes must be divisible by 36"); + uint32_t start = 0; + while (start < aChunk.Length()) { + // Read ADDCHUNKNUM + const nsACString& addChunkStr = Substring(aChunk, start, 4); + start += 4; + + uint32_t addChunk; + memcpy(&addChunk, addChunkStr.BeginReading(), 4); + addChunk = PR_ntohl(addChunk); + + // Read the hash + Completion hash; + hash.Assign(Substring(aChunk, start, COMPLETE_SIZE)); + start += COMPLETE_SIZE; + + nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessHostAdd(const Prefix& aDomain, + uint8_t aNumEntries, + const nsACString& aChunk, + uint32_t* aStart) { + MOZ_ASSERT(mTableUpdate); + NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE, + "ProcessHostAdd should only be called for prefix hashes."); + + if (aNumEntries == 0) { + nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, aDomain); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; + } + + if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) { + NS_WARNING("Chunk is not long enough to contain the expected entries."); + return NS_ERROR_FAILURE; + } + + for (uint8_t i = 0; i < aNumEntries; i++) { + Prefix hash; + hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE)); + PARSER_LOG(("Add prefix %X", hash.ToUint32())); + nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash); + if (NS_FAILED(rv)) { + return rv; + } + *aStart += PREFIX_SIZE; + } + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessHostSub(const Prefix& aDomain, + uint8_t aNumEntries, + const nsACString& aChunk, + uint32_t* aStart) { + MOZ_ASSERT(mTableUpdate); + NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE, + "ProcessHostSub should only be called for prefix hashes."); + + if (aNumEntries == 0) { + if ((*aStart) + 4 > aChunk.Length()) { + NS_WARNING("Received a zero-entry sub chunk without an associated add."); + return NS_ERROR_FAILURE; + } + + const nsACString& addChunkStr = Substring(aChunk, *aStart, 4); + *aStart += 4; + + uint32_t addChunk; + memcpy(&addChunk, addChunkStr.BeginReading(), 4); + addChunk = PR_ntohl(addChunk); + + PARSER_LOG(("Sub prefix (addchunk=%u)", addChunk)); + nsresult rv = + mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; + } + + if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) { + NS_WARNING("Chunk is not long enough to contain the expected entries."); + return NS_ERROR_FAILURE; + } + + for (uint8_t i = 0; i < aNumEntries; i++) { + const nsACString& addChunkStr = Substring(aChunk, *aStart, 4); + *aStart += 4; + + uint32_t addChunk; + memcpy(&addChunk, addChunkStr.BeginReading(), 4); + addChunk = PR_ntohl(addChunk); + + Prefix prefix; + prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE)); + *aStart += PREFIX_SIZE; + + PARSER_LOG(("Sub prefix %X (addchunk=%u)", prefix.ToUint32(), addChunk)); + nsresult rv = mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessHostAddComplete(uint8_t aNumEntries, + const nsACString& aChunk, + uint32_t* aStart) { + MOZ_ASSERT(mTableUpdate); + NS_ASSERTION( + mChunkState.hashSize == COMPLETE_SIZE, + "ProcessHostAddComplete should only be called for complete hashes."); + + if (aNumEntries == 0) { + // this is totally comprehensible. + // My sarcasm detector is going off! + NS_WARNING("Expected > 0 entries for a 32-byte hash add."); + return NS_OK; + } + + if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) { + NS_WARNING("Chunk is not long enough to contain the expected entries."); + return NS_ERROR_FAILURE; + } + + for (uint8_t i = 0; i < aNumEntries; i++) { + Completion hash; + hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE)); + nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash); + if (NS_FAILED(rv)) { + return rv; + } + *aStart += COMPLETE_SIZE; + } + + return NS_OK; +} + +nsresult ProtocolParserV2::ProcessHostSubComplete(uint8_t aNumEntries, + const nsACString& aChunk, + uint32_t* aStart) { + MOZ_ASSERT(mTableUpdate); + NS_ASSERTION( + mChunkState.hashSize == COMPLETE_SIZE, + "ProcessHostSubComplete should only be called for complete hashes."); + + if (aNumEntries == 0) { + // this is totally comprehensible. + NS_WARNING("Expected > 0 entries for a 32-byte hash sub."); + return NS_OK; + } + + if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) { + NS_WARNING("Chunk is not long enough to contain the expected entries."); + return NS_ERROR_FAILURE; + } + + for (uint8_t i = 0; i < aNumEntries; i++) { + const nsACString& addChunkStr = Substring(aChunk, *aStart, 4); + *aStart += 4; + + uint32_t addChunk; + memcpy(&addChunk, addChunkStr.BeginReading(), 4); + addChunk = PR_ntohl(addChunk); + + Completion hash; + hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE)); + *aStart += COMPLETE_SIZE; + + nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +bool ProtocolParserV2::NextLine(nsACString& aLine) { + int32_t newline = mPending.FindChar('\n'); + if (newline == kNotFound) { + return false; + } + aLine.Assign(Substring(mPending, 0, newline)); + mPending.Cut(0, newline + 1); + return true; +} + +RefPtr<TableUpdate> ProtocolParserV2::CreateTableUpdate( + const nsACString& aTableName) const { + return new TableUpdateV2(aTableName); +} + +/////////////////////////////////////////////////////////////////////// +// ProtocolParserProtobuf + +ProtocolParserProtobuf::ProtocolParserProtobuf() = default; + +ProtocolParserProtobuf::~ProtocolParserProtobuf() = default; + +void ProtocolParserProtobuf::SetCurrentTable(const nsACString& aTable) { + // Should never occur. + MOZ_ASSERT_UNREACHABLE("SetCurrentTable shouldn't be called"); +} + +RefPtr<TableUpdate> ProtocolParserProtobuf::CreateTableUpdate( + const nsACString& aTableName) const { + return new TableUpdateV4(aTableName); +} + +nsresult ProtocolParserProtobuf::AppendStream(const nsACString& aData) { + // Protobuf data cannot be parsed progressively. Just save the incoming data. + if (!mPending.Append(aData, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void ProtocolParserProtobuf::End() { + // mUpdateStatus will be updated to success as long as not all + // the responses are invalid. + mUpdateStatus = NS_ERROR_FAILURE; + + FetchThreatListUpdatesResponse response; + if (!response.ParseFromArray(mPending.get(), mPending.Length())) { + NS_WARNING("ProtocolParserProtobuf failed parsing data."); + return; + } + + auto minWaitDuration = response.minimum_wait_duration(); + mUpdateWaitSec = + minWaitDuration.seconds() + minWaitDuration.nanos() / 1000000000; + + for (int i = 0; i < response.list_update_responses_size(); i++) { + auto r = response.list_update_responses(i); + nsAutoCString listName; + nsresult rv = ProcessOneResponse(r, listName); + if (NS_SUCCEEDED(rv)) { + mUpdateStatus = rv; + } else { + nsAutoCString errorName; + mozilla::GetErrorName(rv, errorName); + NS_WARNING(nsPrintfCString("Failed to process one response for '%s': %s", + listName.get(), errorName.get()) + .get()); + if (!listName.IsEmpty()) { + PARSER_LOG(("Table %s will be reset.", listName.get())); + mTablesToReset.AppendElement(listName); + } + } + } +} + +nsresult ProtocolParserProtobuf::ProcessOneResponse( + const ListUpdateResponse& aResponse, nsACString& aListName) { + MOZ_ASSERT(aListName.IsEmpty()); + + // A response must have a threat type. + if (!aResponse.has_threat_type()) { + NS_WARNING( + "Threat type not initialized. This seems to be an invalid response."); + return NS_ERROR_UC_PARSER_MISSING_PARAM; + } + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return NS_ERROR_FAILURE; + } + + // Convert threat type to list name. + nsCString possibleListNames; + nsresult rv = urlUtil->ConvertThreatTypeToListNames(aResponse.threat_type(), + possibleListNames); + if (NS_FAILED(rv)) { + PARSER_LOG(("Threat type to list name conversion error: %d", + aResponse.threat_type())); + return NS_ERROR_UC_PARSER_UNKNOWN_THREAT; + } + + // Match the table name we received with one of the ones we requested. + // We ignore the case where a threat type matches more than one list + // per provider and return the first one. See bug 1287059." + nsTArray<nsCString> possibleListNameArray; + Classifier::SplitTables(possibleListNames, possibleListNameArray); + for (auto possibleName : possibleListNameArray) { + if (mRequestedTables.Contains(possibleName)) { + aListName = possibleName; + break; + } + } + + if (aListName.IsEmpty()) { + PARSER_LOG( + ("We received an update for a list we didn't ask for. Ignoring it.")); + return NS_ERROR_FAILURE; + } + + // Test if this is a full update. + bool isFullUpdate = false; + if (aResponse.has_response_type()) { + isFullUpdate = aResponse.response_type() == ListUpdateResponse::FULL_UPDATE; + } else { + NS_WARNING("Response type not initialized."); + return NS_ERROR_UC_PARSER_MISSING_PARAM; + } + + // Warn if there's no new state. + if (!aResponse.has_new_client_state()) { + NS_WARNING("New state not initialized."); + return NS_ERROR_UC_PARSER_MISSING_PARAM; + } + + auto tu = GetTableUpdate(aListName); + auto tuV4 = TableUpdate::Cast<TableUpdateV4>(tu); + NS_ENSURE_TRUE(tuV4, NS_ERROR_FAILURE); + + nsCString state(aResponse.new_client_state().c_str(), + aResponse.new_client_state().size()); + tuV4->SetNewClientState(state); + + if (aResponse.has_checksum()) { + tuV4->SetSHA256(aResponse.checksum().sha256()); + } + + PARSER_LOG( + ("==== Update for threat type '%d' ====", aResponse.threat_type())); + PARSER_LOG(("* aListName: %s\n", PromiseFlatCString(aListName).get())); + PARSER_LOG(("* newState: %s\n", aResponse.new_client_state().c_str())); + PARSER_LOG(("* isFullUpdate: %s\n", (isFullUpdate ? "yes" : "no"))); + PARSER_LOG( + ("* hasChecksum: %s\n", (aResponse.has_checksum() ? "yes" : "no"))); + PARSER_LOG(("* additions: %d\n", aResponse.additions().size())); + PARSER_LOG(("* removals: %d\n", aResponse.removals().size())); + + tuV4->SetFullUpdate(isFullUpdate); + + rv = ProcessAdditionOrRemoval(*tuV4, aResponse.additions(), + true /*aIsAddition*/); + NS_ENSURE_SUCCESS(rv, rv); + rv = ProcessAdditionOrRemoval(*tuV4, aResponse.removals(), false); + NS_ENSURE_SUCCESS(rv, rv); + + PARSER_LOG(("\n\n")); + + return NS_OK; +} + +nsresult ProtocolParserProtobuf::ProcessAdditionOrRemoval( + TableUpdateV4& aTableUpdate, const ThreatEntrySetList& aUpdate, + bool aIsAddition) { + nsresult ret = NS_OK; + + for (int i = 0; i < aUpdate.size(); i++) { + auto update = aUpdate.Get(i); + if (!update.has_compression_type()) { + NS_WARNING(nsPrintfCString("%s with no compression type.", + aIsAddition ? "Addition" : "Removal") + .get()); + continue; + } + + switch (update.compression_type()) { + case COMPRESSION_TYPE_UNSPECIFIED: + NS_WARNING("Unspecified compression type."); + break; + + case RAW: + ret = (aIsAddition ? ProcessRawAddition(aTableUpdate, update) + : ProcessRawRemoval(aTableUpdate, update)); + break; + + case RICE: + ret = (aIsAddition ? ProcessEncodedAddition(aTableUpdate, update) + : ProcessEncodedRemoval(aTableUpdate, update)); + break; + } + } + + return ret; +} + +nsresult ProtocolParserProtobuf::ProcessRawAddition( + TableUpdateV4& aTableUpdate, const ThreatEntrySet& aAddition) { + if (!aAddition.has_raw_hashes()) { + PARSER_LOG(("* No raw addition.")); + return NS_OK; + } + + auto rawHashes = aAddition.raw_hashes(); + if (!rawHashes.has_prefix_size()) { + NS_WARNING("Raw hash has no prefix size"); + return NS_OK; + } + + uint32_t prefixSize = rawHashes.prefix_size(); + MOZ_ASSERT(prefixSize >= PREFIX_SIZE && prefixSize <= COMPLETE_SIZE); + + nsCString prefixes; + if (!prefixes.Assign(rawHashes.raw_hashes().c_str(), + rawHashes.raw_hashes().size(), mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + MOZ_ASSERT(prefixes.Length() % prefixSize == 0, + "PrefixString length must be a multiple of the prefix size."); + + if (LOG_ENABLED()) { + PARSER_LOG((" Raw addition (%d-byte prefixes)", prefixSize)); + PARSER_LOG((" - # of prefixes: %zu", prefixes.Length() / prefixSize)); + if (4 == prefixSize) { + uint32_t* fixedLengthPrefixes = (uint32_t*)prefixes.get(); + PARSER_LOG((" - Memory address: 0x%p", fixedLengthPrefixes)); + } + } + + aTableUpdate.NewPrefixes(prefixSize, prefixes); + return NS_OK; +} + +nsresult ProtocolParserProtobuf::ProcessRawRemoval( + TableUpdateV4& aTableUpdate, const ThreatEntrySet& aRemoval) { + if (!aRemoval.has_raw_indices()) { + NS_WARNING("A removal has no indices."); + return NS_OK; + } + + // indices is an array of int32. + auto indices = aRemoval.raw_indices().indices(); + PARSER_LOG(("* Raw removal")); + PARSER_LOG((" - # of removal: %d", indices.size())); + + nsresult rv = aTableUpdate.NewRemovalIndices((const uint32_t*)indices.data(), + indices.size()); + if (NS_FAILED(rv)) { + PARSER_LOG(("Failed to create new removal indices.")); + return rv; + } + + return NS_OK; +} + +static nsresult DoRiceDeltaDecode(const RiceDeltaEncoding& aEncoding, + nsTArray<uint32_t>& aDecoded) { + if (aEncoding.num_entries() > 0 && + (!aEncoding.has_rice_parameter() || !aEncoding.has_encoded_data())) { + PARSER_LOG(("Rice parameter or encoded data is missing.")); + return NS_ERROR_UC_PARSER_MISSING_PARAM; + } else if (aEncoding.num_entries() == 0 && !aEncoding.has_first_value()) { + PARSER_LOG(("Missing first_value for an single-integer Rice encoding.")); + return NS_ERROR_UC_PARSER_MISSING_VALUE; + } + + auto first_value = aEncoding.has_first_value() ? aEncoding.first_value() : 0; + + PARSER_LOG(("* Encoding info:")); + PARSER_LOG((" - First value: %" PRId64, first_value)); + PARSER_LOG((" - Num of entries: %d", aEncoding.num_entries())); + PARSER_LOG((" - Rice parameter: %d", aEncoding.rice_parameter())); + + // Set up the input buffer. Note that the bits should be read + // from LSB to MSB so that we in-place reverse the bits before + // feeding to the decoder. + auto encoded = + const_cast<RiceDeltaEncoding&>(aEncoding).mutable_encoded_data(); + RiceDeltaDecoder decoder((uint8_t*)encoded->c_str(), encoded->size()); + + // Setup the output buffer. The "first value" is included in + // the output buffer. + if (!aDecoded.SetLength(aEncoding.num_entries() + 1, mozilla::fallible)) { + NS_WARNING("Not enough memory to decode the RiceDelta input."); + return NS_ERROR_OUT_OF_MEMORY; + } + + // Decode! + bool rv = decoder.Decode( + aEncoding.rice_parameter(), first_value, + aEncoding.num_entries(), // # of entries (first value not included). + &aDecoded[0]); + + NS_ENSURE_TRUE(rv, NS_ERROR_UC_PARSER_DECODE_FAILURE); + + return NS_OK; +} + +nsresult ProtocolParserProtobuf::ProcessEncodedAddition( + TableUpdateV4& aTableUpdate, const ThreatEntrySet& aAddition) { + if (!aAddition.has_rice_hashes()) { + PARSER_LOG(("* No rice encoded addition.")); + return NS_OK; + } + + nsTArray<uint32_t> decoded; + nsresult rv = DoRiceDeltaDecode(aAddition.rice_hashes(), decoded); + if (NS_FAILED(rv)) { + PARSER_LOG(("Failed to parse encoded prefixes.")); + return rv; + } + + // Say we have the following raw prefixes + // BE LE + // 00 00 00 01 1 16777216 + // 00 00 02 00 512 131072 + // 00 03 00 00 196608 768 + // 04 00 00 00 67108864 4 + // + // which can be treated as uint32 (big-endian) sorted in increasing order: + // + // [1, 512, 196608, 67108864] + // + // According to https://developers.google.com/safe-browsing/v4/compression, + // the following should be done prior to compression: + // + // 1) re-interpret in little-endian ==> [16777216, 131072, 768, 4] + // 2) sort in increasing order ==> [4, 768, 131072, 16777216] + // + // In order to get the original byte stream from |decoded| + // ([4, 768, 131072, 16777216] in this case), we have to: + // + // 1) sort in big-endian order ==> [16777216, 131072, 768, 4] + // 2) copy each uint32 in little-endian to the result string + // + + // The 4-byte prefixes have to be re-sorted in Big-endian increasing order. + struct CompareBigEndian { + bool Equals(const uint32_t& aA, const uint32_t& aB) const { + return aA == aB; + } + + bool LessThan(const uint32_t& aA, const uint32_t& aB) const { + return NativeEndian::swapToBigEndian(aA) < + NativeEndian::swapToBigEndian(aB); + } + }; + decoded.Sort(CompareBigEndian()); + + // The encoded prefixes are always 4 bytes. + nsCString prefixes; + if (!prefixes.SetCapacity(decoded.Length() * 4, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (size_t i = 0; i < decoded.Length(); i++) { + // Note that the third argument is the number of elements we want + // to copy (and swap) but not the number of bytes we want to copy. + char p[4]; + NativeEndian::copyAndSwapToLittleEndian(p, &decoded[i], 1); + prefixes.Append(p, 4); + } + + aTableUpdate.NewPrefixes(4, prefixes); + return NS_OK; +} + +nsresult ProtocolParserProtobuf::ProcessEncodedRemoval( + TableUpdateV4& aTableUpdate, const ThreatEntrySet& aRemoval) { + if (!aRemoval.has_rice_indices()) { + PARSER_LOG(("* No rice encoded removal.")); + return NS_OK; + } + + nsTArray<uint32_t> decoded; + nsresult rv = DoRiceDeltaDecode(aRemoval.rice_indices(), decoded); + if (NS_FAILED(rv)) { + PARSER_LOG(("Failed to decode encoded removal indices.")); + return rv; + } + + // The encoded prefixes are always 4 bytes. + rv = aTableUpdate.NewRemovalIndices(&decoded[0], decoded.Length()); + if (NS_FAILED(rv)) { + PARSER_LOG(("Failed to create new removal indices.")); + return rv; + } + + return NS_OK; +} + +} // namespace safebrowsing +} // namespace mozilla diff --git a/toolkit/components/url-classifier/ProtocolParser.h b/toolkit/components/url-classifier/ProtocolParser.h new file mode 100644 index 0000000000..125255f10c --- /dev/null +++ b/toolkit/components/url-classifier/ProtocolParser.h @@ -0,0 +1,215 @@ +//* -*- 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/. */ + +#ifndef ProtocolParser_h__ +#define ProtocolParser_h__ + +#include "HashStore.h" +#include "chromium/safebrowsing.pb.h" + +namespace mozilla { +namespace safebrowsing { + +/** + * Abstract base class for parsing update data in multiple formats. + */ +class ProtocolParser { + public: + struct ForwardedUpdate { + nsCString table; + nsCString url; + }; + + ProtocolParser(); + virtual ~ProtocolParser(); + + nsresult Status() const { return mUpdateStatus; } + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + virtual nsCString GetRawTableUpdates() const { return mPending; } +#endif + + virtual void SetCurrentTable(const nsACString& aTable) = 0; + + void SetRequestedTables(const nsTArray<nsCString>& aRequestTables) { + mRequestedTables = aRequestTables.Clone(); + } + + nsresult Begin(const nsACString& aTable, + const nsTArray<nsCString>& aUpdateTables); + virtual nsresult AppendStream(const nsACString& aData) = 0; + + uint32_t UpdateWaitSec() { return mUpdateWaitSec; } + + // Notify that the inbound data is ready for parsing if progressive + // parsing is not supported, for example in V4. + virtual void End() = 0; + + RefPtr<TableUpdate> GetTableUpdate(const nsACString& aTable); + void ForgetTableUpdates() { mTableUpdates.Clear(); } + const TableUpdateArray& GetTableUpdates() { return mTableUpdates; } + + // These are only meaningful to V2. Since they were originally public, + // moving them to ProtocolParserV2 requires a dymamic cast in the call + // sites. As a result, we will leave them until we remove support + // for V2 entirely.. + virtual const nsTArray<ForwardedUpdate>& Forwards() const { + return mForwards; + } + bool ResetRequested() const { return !mTablesToReset.IsEmpty(); } + const nsTArray<nsCString>& TablesToReset() const { return mTablesToReset; } + + protected: + virtual RefPtr<TableUpdate> CreateTableUpdate( + const nsACString& aTableName) const = 0; + + nsCString mPending; + nsresult mUpdateStatus; + + // Keep track of updates to apply before passing them to the DBServiceWorkers. + TableUpdateArray mTableUpdates; + + nsTArray<ForwardedUpdate> mForwards; + + // The table names that were requested from the client. + nsTArray<nsCString> mRequestedTables; + + // The table names that failed to update and need to be reset. + nsTArray<nsCString> mTablesToReset; + + // How long we should wait until the next update. + uint32_t mUpdateWaitSec; +}; + +/** + * Helpers to parse the "shavar", "digest256" and "simple" list formats. + */ +class ProtocolParserV2 final : public ProtocolParser { + public: + ProtocolParserV2(); + virtual ~ProtocolParserV2(); + + virtual void SetCurrentTable(const nsACString& aTable) override; + virtual nsresult AppendStream(const nsACString& aData) override; + virtual void End() override; + + // Update information. + virtual const nsTArray<ForwardedUpdate>& Forwards() const override { + return mForwards; + } + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + // Unfortunately we have to override to return mRawUpdate which + // will not be modified during the parsing, unlike mPending. + virtual nsCString GetRawTableUpdates() const override { return mRawUpdate; } +#endif + + private: + virtual RefPtr<TableUpdate> CreateTableUpdate( + const nsACString& aTableName) const override; + + nsresult ProcessControl(bool* aDone); + nsresult ProcessExpirations(const nsCString& aLine); + nsresult ProcessChunkControl(const nsCString& aLine); + nsresult ProcessForward(const nsCString& aLine); + nsresult AddForward(const nsACString& aUrl); + nsresult ProcessChunk(bool* done); + // Remove this, it's only used for testing + nsresult ProcessPlaintextChunk(const nsACString& aChunk); + nsresult ProcessShaChunk(const nsACString& aChunk); + nsresult ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries, + const nsACString& aChunk, uint32_t* aStart); + nsresult ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries, + const nsACString& aChunk, uint32_t* aStart); + nsresult ProcessHostAddComplete(uint8_t aNumEntries, const nsACString& aChunk, + uint32_t* aStart); + nsresult ProcessHostSubComplete(uint8_t numEntries, const nsACString& aChunk, + uint32_t* start); + // Digest chunks are very similar to shavar chunks, except digest chunks + // always contain the full hash, so there is no need for chunk data to + // contain prefix sizes. + nsresult ProcessDigestChunk(const nsACString& aChunk); + nsresult ProcessDigestAdd(const nsACString& aChunk); + nsresult ProcessDigestSub(const nsACString& aChunk); + bool NextLine(nsACString& aLine); + + enum ParserState { PROTOCOL_STATE_CONTROL, PROTOCOL_STATE_CHUNK }; + ParserState mState; + + enum ChunkType { + // Types for shavar tables. + CHUNK_ADD, + CHUNK_SUB, + // Types for digest256 tables. digest256 tables differ in format from + // shavar tables since they only contain complete hashes. + CHUNK_ADD_DIGEST, + CHUNK_SUB_DIGEST + }; + + struct ChunkState { + ChunkType type; + uint32_t num; + uint32_t hashSize; + uint32_t length; + void Clear() { + num = 0; + hashSize = 0; + length = 0; + } + }; + ChunkState mChunkState; + + // Updates to apply to the current table being parsed. + RefPtr<TableUpdateV2> mTableUpdate; + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + nsCString mRawUpdate; // Keep a copy of mPending before it's processed. +#endif +}; + +// Helpers to parse the "proto" list format. +class ProtocolParserProtobuf final : public ProtocolParser { + public: + typedef FetchThreatListUpdatesResponse_ListUpdateResponse ListUpdateResponse; + typedef google::protobuf::RepeatedPtrField<ThreatEntrySet> ThreatEntrySetList; + + public: + ProtocolParserProtobuf(); + + virtual void SetCurrentTable(const nsACString& aTable) override; + virtual nsresult AppendStream(const nsACString& aData) override; + virtual void End() override; + + private: + virtual ~ProtocolParserProtobuf(); + + virtual RefPtr<TableUpdate> CreateTableUpdate( + const nsACString& aTableName) const override; + + // For parsing update info. + nsresult ProcessOneResponse(const ListUpdateResponse& aResponse, + nsACString& aListName); + + nsresult ProcessAdditionOrRemoval(TableUpdateV4& aTableUpdate, + const ThreatEntrySetList& aUpdate, + bool aIsAddition); + + nsresult ProcessRawAddition(TableUpdateV4& aTableUpdate, + const ThreatEntrySet& aAddition); + + nsresult ProcessRawRemoval(TableUpdateV4& aTableUpdate, + const ThreatEntrySet& aRemoval); + + nsresult ProcessEncodedAddition(TableUpdateV4& aTableUpdate, + const ThreatEntrySet& aAddition); + + nsresult ProcessEncodedRemoval(TableUpdateV4& aTableUpdate, + const ThreatEntrySet& aRemoval); +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/RiceDeltaDecoder.cpp b/toolkit/components/url-classifier/RiceDeltaDecoder.cpp new file mode 100644 index 0000000000..ea16888fdb --- /dev/null +++ b/toolkit/components/url-classifier/RiceDeltaDecoder.cpp @@ -0,0 +1,227 @@ +/* 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 "RiceDeltaDecoder.h" +#include "mozilla/Logging.h" + +#include <limits> + +extern mozilla::LazyLogModule gUrlClassifierDbServiceLog; +#define LOG(args) \ + MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args) + +namespace { + +//////////////////////////////////////////////////////////////////////// +// BitBuffer is copied and modified from webrtc/base/bitbuffer.h +// + +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree (webrtc/base/bitbuffer.h/cc). An additional intellectual property + * rights grant can be found in the file PATENTS. All contributing + * project authors may be found in the AUTHORS file in the root of + * the source tree. + */ + +class BitBuffer { + public: + BitBuffer(const uint8_t* bytes, size_t byte_count); + + // The remaining bits in the byte buffer. + uint64_t RemainingBitCount() const; + + // Reads bit-sized values from the buffer. Returns false if there isn't enough + // data left for the specified bit count.. + bool ReadBits(uint32_t* val, size_t bit_count); + + // Peeks bit-sized values from the buffer. Returns false if there isn't enough + // data left for the specified number of bits. Doesn't move the current + // offset. + bool PeekBits(uint32_t* val, size_t bit_count); + + // Reads the exponential golomb encoded value at the current offset. + // Exponential golomb values are encoded as: + // 1) x = source val + 1 + // 2) In binary, write [countbits(x) - 1] 1s, then x + // To decode, we count the number of leading 1 bits, read that many + 1 bits, + // and increment the result by 1. + // Returns false if there isn't enough data left for the specified type, or if + // the value wouldn't fit in a uint32_t. + bool ReadExponentialGolomb(uint32_t* val); + + // Moves current position |bit_count| bits forward. Returns false if + // there aren't enough bits left in the buffer. + bool ConsumeBits(size_t bit_count); + + protected: + const uint8_t* const bytes_; + // The total size of |bytes_|. + size_t byte_count_; + // The current offset, in bytes, from the start of |bytes_|. + size_t byte_offset_; + // The current offset, in bits, into the current byte. + size_t bit_offset_; +}; + +} // end of unnamed namespace + +static void ReverseByte(uint8_t& b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; +} + +namespace mozilla { +namespace safebrowsing { + +RiceDeltaDecoder::RiceDeltaDecoder(uint8_t* aEncodedData, + size_t aEncodedDataSize) + : mEncodedData(aEncodedData), mEncodedDataSize(aEncodedDataSize) {} + +bool RiceDeltaDecoder::Decode(uint32_t aRiceParameter, uint32_t aFirstValue, + uint32_t aNumEntries, uint32_t* aDecodedData) { + // Reverse each byte before reading bits from the byte buffer. + for (size_t i = 0; i < mEncodedDataSize; i++) { + ReverseByte(mEncodedData[i]); + } + + BitBuffer bitBuffer(mEncodedData, mEncodedDataSize); + + // q = quotient + // r = remainder + // k = RICE parameter + const uint32_t k = aRiceParameter; + aDecodedData[0] = aFirstValue; + for (uint32_t i = 0; i < aNumEntries; i++) { + // Read the quotient of N. + uint32_t q; + if (!bitBuffer.ReadExponentialGolomb(&q)) { + LOG(("Encoded data underflow!")); + return false; + } + + // Read the remainder of N, one bit at a time. + uint32_t r = 0; + for (uint32_t j = 0; j < k; j++) { + uint32_t b = 0; + if (!bitBuffer.ReadBits(&b, 1)) { + // Insufficient bits. Just leave them as zeros. + break; + } + // Add the bit to the right position so that it's in Little Endian order. + r |= b << j; + } + + // Caculate N from q,r,k. + uint32_t N = (q << k) + r; + + // We start filling aDecodedData from [1]. + aDecodedData[i + 1] = N + aDecodedData[i]; + } + + return true; +} + +} // namespace safebrowsing +} // namespace mozilla + +namespace { +////////////////////////////////////////////////////////////////////////// +// The BitBuffer impl is copied and modified from webrtc/base/bitbuffer.cc +// + +// Returns the lowest (right-most) |bit_count| bits in |byte|. +uint8_t LowestBits(uint8_t byte, size_t bit_count) { + return byte & ((1 << bit_count) - 1); +} + +// Returns the highest (left-most) |bit_count| bits in |byte|, shifted to the +// lowest bits (to the right). +uint8_t HighestBits(uint8_t byte, size_t bit_count) { + MOZ_ASSERT(bit_count < 8u); + uint8_t shift = 8 - static_cast<uint8_t>(bit_count); + uint8_t mask = 0xFF << shift; + return (byte & mask) >> shift; +} + +BitBuffer::BitBuffer(const uint8_t* bytes, size_t byte_count) + : bytes_(bytes), byte_count_(byte_count), byte_offset_(), bit_offset_() { + MOZ_ASSERT(static_cast<uint64_t>(byte_count_) <= + std::numeric_limits<uint32_t>::max()); +} + +uint64_t BitBuffer::RemainingBitCount() const { + return (static_cast<uint64_t>(byte_count_) - byte_offset_) * 8 - bit_offset_; +} + +bool BitBuffer::PeekBits(uint32_t* val, size_t bit_count) { + if (!val || bit_count > RemainingBitCount() || bit_count > 32) { + return false; + } + const uint8_t* bytes = bytes_ + byte_offset_; + size_t remaining_bits_in_current_byte = 8 - bit_offset_; + uint32_t bits = LowestBits(*bytes++, remaining_bits_in_current_byte); + // If we're reading fewer bits than what's left in the current byte, just + // return the portion of this byte that we need. + if (bit_count < remaining_bits_in_current_byte) { + *val = HighestBits(bits, bit_offset_ + bit_count); + return true; + } + // Otherwise, subtract what we've read from the bit count and read as many + // full bytes as we can into bits. + bit_count -= remaining_bits_in_current_byte; + while (bit_count >= 8) { + bits = (bits << 8) | *bytes++; + bit_count -= 8; + } + // Whatever we have left is smaller than a byte, so grab just the bits we need + // and shift them into the lowest bits. + if (bit_count > 0) { + bits <<= bit_count; + bits |= HighestBits(*bytes, bit_count); + } + *val = bits; + return true; +} + +bool BitBuffer::ReadBits(uint32_t* val, size_t bit_count) { + return PeekBits(val, bit_count) && ConsumeBits(bit_count); +} + +bool BitBuffer::ConsumeBits(size_t bit_count) { + if (bit_count > RemainingBitCount()) { + return false; + } + + byte_offset_ += (bit_offset_ + bit_count) / 8; + bit_offset_ = (bit_offset_ + bit_count) % 8; + return true; +} + +bool BitBuffer::ReadExponentialGolomb(uint32_t* val) { + if (!val) { + return false; + } + + *val = 0; + + // Count the number of leading 0 bits by peeking/consuming them one at a time. + size_t one_bit_count = 0; + uint32_t peeked_bit; + while (PeekBits(&peeked_bit, 1) && peeked_bit == 1) { + one_bit_count++; + ConsumeBits(1); + } + if (!ConsumeBits(1)) { + return false; // The stream is incorrectly terminated at '1'. + } + + *val = one_bit_count; + return true; +} +} // namespace diff --git a/toolkit/components/url-classifier/RiceDeltaDecoder.h b/toolkit/components/url-classifier/RiceDeltaDecoder.h new file mode 100644 index 0000000000..6aa3313fbe --- /dev/null +++ b/toolkit/components/url-classifier/RiceDeltaDecoder.h @@ -0,0 +1,40 @@ +/* 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/. */ + +#ifndef RICE_DELTA_DECODER_H +#define RICE_DELTA_DECODER_H + +#include <cstddef> +#include <cstdint> + +namespace mozilla { +namespace safebrowsing { + +class RiceDeltaDecoder { + public: + // This decoder is tailored for safebrowsing v4, including the + // bit reading order and how the remainder part is interpreted. + // The caller just needs to feed the byte stream received from + // network directly. Note that the input buffer must be mutable + // since the decoder will do some pre-processing before decoding. + RiceDeltaDecoder(uint8_t* aEncodedData, size_t aEncodedDataSize); + + // @param aNumEntries The number of values to be decoded, not including + // the first value. + // @param aDecodedData A pre-allocated output buffer. Note that + // aDecodedData[0] will be filled with |aFirstValue| + // and the buffer length (in byte) should be + // ((aNumEntries + 1) * sizeof(uint32_t)). + bool Decode(uint32_t aRiceParameter, uint32_t aFirstValue, + uint32_t aNumEntries, uint32_t* aDecodedData); + + private: + uint8_t* mEncodedData; + size_t mEncodedDataSize; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif // UPDATE_V4_DECODER_H diff --git a/toolkit/components/url-classifier/SafeBrowsing.sys.mjs b/toolkit/components/url-classifier/SafeBrowsing.sys.mjs new file mode 100644 index 0000000000..5f0b5faec8 --- /dev/null +++ b/toolkit/components/url-classifier/SafeBrowsing.sys.mjs @@ -0,0 +1,575 @@ +/* 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/. */ + +const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug"; +let loggingEnabled = false; + +// Log only if browser.safebrowsing.debug is true +function log(...stuff) { + if (!loggingEnabled) { + return; + } + + var d = new Date(); + let msg = "SafeBrowsing: " + d.toTimeString() + ": " + stuff.join(" "); + dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n"); +} + +function getLists(prefName) { + log("getLists: " + prefName); + let pref = Services.prefs.getCharPref(prefName, ""); + + // Splitting an empty string returns [''], we really want an empty array. + if (!pref) { + return []; + } + + return pref.split(",").map(value => value.trim()); +} + +const FEATURES = [ + { + name: "phishing", + list: ["urlclassifier.phishTable"], + enabled() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.phishing.enabled" + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.phishing.update", + this.enabled() + ); + }, + }, + { + name: "malware", + list: ["urlclassifier.malwareTable"], + enabled() { + return Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.malware.update", + this.enabled() + ); + }, + }, + { + name: "blockedURIs", + list: ["urlclassifier.blockedTable"], + enabled() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.blockedURIs.enabled" + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.blockedURIs.update", + this.enabled() + ); + }, + }, + { + name: "downloads", + list: [ + "urlclassifier.downloadBlockTable", + "urlclassifier.downloadAllowTable", + ], + enabled() { + return ( + Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled") && + Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled") + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.downloads.update", + this.enabled() + ); + }, + }, + { + name: "trackingAnnotation", + list: [ + "urlclassifier.trackingAnnotationTable", + "urlclassifier.trackingAnnotationWhitelistTable", + ], + enabled() { + return Services.prefs.getBoolPref( + "privacy.trackingprotection.annotate_channels" + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.trackingAnnotation.update", + this.enabled() + ); + }, + }, + { + name: "trackingProtection", + list: [ + "urlclassifier.trackingTable", + "urlclassifier.trackingWhitelistTable", + ], + enabled() { + return ( + Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || + Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled") + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.trackingProtection.update", + this.enabled() + ); + }, + }, + { + name: "fingerprinting-annotation", + list: [ + "urlclassifier.features.fingerprinting.annotate.blacklistTables", + "urlclassifier.features.fingerprinting.annotate.whitelistTables", + ], + enabled() { + // Annotation features are enabled by default. + return true; + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.fingerprinting.annotate.update", + this.enabled() + ); + }, + }, + { + name: "fingerprinting-protection", + list: [ + "urlclassifier.features.fingerprinting.blacklistTables", + "urlclassifier.features.fingerprinting.whitelistTables", + ], + enabled() { + return Services.prefs.getBoolPref( + "privacy.trackingprotection.fingerprinting.enabled", + false + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.fingerprinting.update", + this.enabled() + ); + }, + }, + { + name: "cryptomining-annotation", + list: [ + "urlclassifier.features.cryptomining.annotate.blacklistTables", + "urlclassifier.features.cryptomining.annotate.whitelistTables", + ], + enabled() { + // Annotation features are enabled by default. + return true; + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.cryptomining.annotate.update", + this.enabled() + ); + }, + }, + { + name: "cryptomining-protection", + list: [ + "urlclassifier.features.cryptomining.blacklistTables", + "urlclassifier.features.cryptomining.whitelistTables", + ], + enabled() { + return Services.prefs.getBoolPref( + "privacy.trackingprotection.cryptomining.enabled", + false + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.cryptomining.update", + this.enabled() + ); + }, + }, + { + name: "socialtracking-annotation", + list: [ + "urlclassifier.features.socialtracking.annotate.blacklistTables", + "urlclassifier.features.socialtracking.annotate.whitelistTables", + ], + enabled() { + // Annotation features are enabled by default. + return true; + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.socialtracking.annotate.update", + this.enabled() + ); + }, + }, + { + name: "socialtracking-protection", + list: [ + "urlclassifier.features.socialtracking.blacklistTables", + "urlclassifier.features.socialtracking.whitelistTables", + ], + enabled() { + return Services.prefs.getBoolPref( + "privacy.trackingprotection.socialtracking.enabled", + false + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.socialtracking.update", + this.enabled() + ); + }, + }, + { + name: "emailtracking-protection", + list: [ + "urlclassifier.features.emailtracking.blocklistTables", + "urlclassifier.features.emailtracking.allowlistTables", + ], + enabled() { + return ( + Services.prefs.getBoolPref( + "privacy.trackingprotection.emailtracking.enabled", + false + ) || + Services.prefs.getBoolPref( + "privacy.trackingprotection.emailtracking.pbmode.enabled", + false + ) + ); + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.emailtracking.update", + this.enabled() + ); + }, + }, + { + name: "emailtracking-data-collection", + list: [ + "urlclassifier.features.emailtracking.datacollection.blocklistTables", + "urlclassifier.features.emailtracking.datacollection.allowlistTables", + ], + enabled() { + // Data collection features are enabled by default. + return true; + }, + update() { + return Services.prefs.getBoolPref( + "browser.safebrowsing.features.emailtracking.datacollection.update", + this.enabled() + ); + }, + }, +]; + +export var SafeBrowsing = { + init() { + if (this.initialized) { + log("Already initialized"); + return; + } + + Services.prefs.addObserver("browser.safebrowsing", this); + Services.prefs.addObserver("privacy.trackingprotection", this); + Services.prefs.addObserver("urlclassifier", this); + + this.readPrefs(); + + this.controlUpdateChecking(); + this.initialized = true; + + log("init() finished"); + }, + + registerTableWithURLs(listname) { + let listManager = Cc[ + "@mozilla.org/url-classifier/listmanager;1" + ].getService(Ci.nsIUrlListManager); + + let providerName = this.listToProvider[listname]; + let provider = this.providers[providerName]; + + if (!providerName || !provider) { + log("No provider info found for " + listname); + log("Check browser.safebrowsing.provider.[google/mozilla].lists"); + return; + } + + if (!provider.updateURL) { + log("Invalid update url " + listname); + return; + } + + listManager.registerTable( + listname, + providerName, + provider.updateURL, + provider.gethashURL + ); + }, + + registerTables() { + this.features.forEach(feature => { + feature.list.forEach(table => { + this.registerTableWithURLs(table); + }); + }); + }, + + unregisterTables(obsoleteLists) { + let listManager = Cc[ + "@mozilla.org/url-classifier/listmanager;1" + ].getService(Ci.nsIUrlListManager); + + obsoleteLists.forEach(list => { + list.forEach(table => { + listManager.unregisterTable(table); + }); + }); + }, + + initialized: false, + + features: [], + + updateURL: null, + gethashURL: null, + reportURL: null, + + getReportURL(kind, info) { + let pref; + switch (kind) { + case "Phish": + pref = "browser.safebrowsing.reportPhishURL"; + break; + + case "PhishMistake": + case "MalwareMistake": + pref = + "browser.safebrowsing.provider." + + info.provider + + ".report" + + kind + + "URL"; + break; + + default: + let err = + "SafeBrowsing getReportURL() called with unknown kind: " + kind; + console.error(err); + throw err; + } + + // The "Phish" reports are about submitting new phishing URLs to Google so + // they don't have an associated list URL + if (kind != "Phish" && (!info.list || !info.uri)) { + return null; + } + + let reportUrl = Services.urlFormatter.formatURLPref(pref); + // formatURLPref might return "about:blank" if getting the pref fails + if (reportUrl == "about:blank") { + reportUrl = null; + } + + if (reportUrl) { + reportUrl += encodeURIComponent(info.uri); + } + return reportUrl; + }, + + observe(aSubject, aTopic, aData) { + // skip nextupdatetime and lastupdatetime + if (aData.includes("lastupdatetime") || aData.includes("nextupdatetime")) { + return; + } + + if (aData == PREF_DEBUG_ENABLED) { + loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED); + return; + } + + this.readPrefs(); + }, + + readPrefs() { + loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED); + log("reading prefs"); + + let obsoleteLists = []; + // Make a copy of the original lists before we re-read the prefs. + if (this.initialized) { + obsoleteLists = this.features.map(feature => { + return feature.list; + }); + } + + // Allow to disable all feature updates with a single preference for tests. + let update = Services.prefs.getBoolPref( + "browser.safebrowsing.update.enabled", + true + ); + + this.features = []; + for (let i = 0; i < FEATURES.length; ++i) { + this.features[i] = { + name: FEATURES[i].name, + list: [], + enabled: FEATURES[i].enabled(), + update: FEATURES[i].update() && update, + }; + + FEATURES[i].list.forEach(pref => { + this.features[i].list.push(...getLists(pref)); + }); + } + + for (let i = 0; i < obsoleteLists.length; ++i) { + obsoleteLists[i] = obsoleteLists[i].filter( + list => !this.features[i].list.includes(list) + ); + } + + this.updateProviderURLs(); + this.registerTables(); + if (obsoleteLists) { + this.unregisterTables(obsoleteLists); + } + + // XXX The listManager backend gets confused if this is called before the + // lists are registered. So only call it here when a pref changes, and not + // when doing initialization. I expect to refactor this later, so pardon the hack. + if (this.initialized) { + this.controlUpdateChecking(); + } + }, + + updateProviderURLs() { + try { + var clientID = Services.prefs.getCharPref("browser.safebrowsing.id"); + } catch (e) { + clientID = Services.appinfo.name; + } + + log("initializing safe browsing URLs, client id", clientID); + + // Get the different providers + let branch = Services.prefs.getBranch("browser.safebrowsing.provider."); + let children = branch.getChildList(""); + this.providers = {}; + this.listToProvider = {}; + + for (let child of children) { + log("Child: " + child); + let prefComponents = child.split("."); + let providerName = prefComponents[0]; + this.providers[providerName] = {}; + } + + if (loggingEnabled) { + let providerStr = ""; + Object.keys(this.providers).forEach(function (provider) { + if (providerStr === "") { + providerStr = provider; + } else { + providerStr += ", " + provider; + } + }); + log("Providers: " + providerStr); + } + + Object.keys(this.providers).forEach(function (provider) { + if (provider == "test") { + return; // skip + } + let updateURL = Services.urlFormatter.formatURLPref( + "browser.safebrowsing.provider." + provider + ".updateURL" + ); + let gethashURL = Services.urlFormatter.formatURLPref( + "browser.safebrowsing.provider." + provider + ".gethashURL" + ); + updateURL = updateURL.replace("SAFEBROWSING_ID", clientID); + gethashURL = gethashURL.replace("SAFEBROWSING_ID", clientID); + + // Disable updates and gethash if the Google API key is missing. + let googleSafebrowsingKey = Services.urlFormatter + .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%") + .trim(); + if ( + (provider == "google" || provider == "google4") && + (!googleSafebrowsingKey || + googleSafebrowsingKey == "no-google-safebrowsing-api-key") + ) { + log( + "Missing Google SafeBrowsing API key, clearing updateURL and gethashURL." + ); + updateURL = ""; + gethashURL = ""; + } + + log("Provider: " + provider + " updateURL=" + updateURL); + log("Provider: " + provider + " gethashURL=" + gethashURL); + + // Urls used to update DB + this.providers[provider].updateURL = updateURL; + this.providers[provider].gethashURL = gethashURL; + + // Get lists this provider manages + let lists = getLists( + "browser.safebrowsing.provider." + provider + ".lists" + ); + if (lists) { + lists.forEach(function (list) { + this.listToProvider[list] = provider; + }, this); + } else { + log("Update URL given but no lists managed for provider: " + provider); + } + }, this); + }, + + controlUpdateChecking() { + if (loggingEnabled) { + this.features.forEach(feature => { + log("feature " + feature.name + ":"); + log(" enabled:" + feature.enabled); + log(" update:" + feature.update); + log(" tables:" + feature.list); + }); + } + + let listManager = Cc[ + "@mozilla.org/url-classifier/listmanager;1" + ].getService(Ci.nsIUrlListManager); + + listManager.disableAllUpdates(); + + this.features.forEach(feature => { + if (feature.update) { + feature.list.forEach(table => { + listManager.enableUpdate(table); + }); + } + }); + + listManager.maybeToggleUpdateChecking(); + }, +}; diff --git a/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs b/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs new file mode 100644 index 0000000000..48a4c432fb --- /dev/null +++ b/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs @@ -0,0 +1,965 @@ +/* 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/. */ + +// COMPLETE_LENGTH and PARTIAL_LENGTH copied from nsUrlClassifierDBService.h, +// they correspond to the length, in bytes, of a hash prefix and the total +// hash. +const COMPLETE_LENGTH = 32; +const PARTIAL_LENGTH = 4; + +// Upper limit on the server response minimumWaitDuration +const MIN_WAIT_DURATION_MAX_VALUE = 24 * 60 * 60 * 1000; +const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug"; + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gDbService", + "@mozilla.org/url-classifier/dbservice;1", + "nsIUrlClassifierDBService" +); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gUrlUtil", + "@mozilla.org/url-classifier/utils;1", + "nsIUrlClassifierUtils" +); + +let loggingEnabled = false; + +// Log only if browser.safebrowsing.debug is true +function log(...stuff) { + if (!loggingEnabled) { + return; + } + + var d = new Date(); + let msg = "hashcompleter: " + d.toTimeString() + ": " + stuff.join(" "); + dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n"); +} + +// Map the HTTP response code to a Telemetry bucket +// https://developers.google.com/safe-browsing/developers_guide_v2?hl=en +// eslint-disable-next-line complexity +function httpStatusToBucket(httpStatus) { + var statusBucket; + switch (httpStatus) { + case 100: + case 101: + // Unexpected 1xx return code + statusBucket = 0; + break; + case 200: + // OK - Data is available in the HTTP response body. + statusBucket = 1; + break; + case 201: + case 202: + case 203: + case 205: + case 206: + // Unexpected 2xx return code + statusBucket = 2; + break; + case 204: + // No Content - There are no full-length hashes with the requested prefix. + statusBucket = 3; + break; + case 300: + case 301: + case 302: + case 303: + case 304: + case 305: + case 307: + case 308: + // Unexpected 3xx return code + statusBucket = 4; + break; + case 400: + // Bad Request - The HTTP request was not correctly formed. + // The client did not provide all required CGI parameters. + statusBucket = 5; + break; + case 401: + case 402: + case 405: + case 406: + case 407: + case 409: + case 410: + case 411: + case 412: + case 414: + case 415: + case 416: + case 417: + case 421: + case 426: + case 428: + case 429: + case 431: + case 451: + // Unexpected 4xx return code + statusBucket = 6; + break; + case 403: + // Forbidden - The client id is invalid. + statusBucket = 7; + break; + case 404: + // Not Found + statusBucket = 8; + break; + case 408: + // Request Timeout + statusBucket = 9; + break; + case 413: + // Request Entity Too Large - Bug 1150334 + statusBucket = 10; + break; + case 500: + case 501: + case 510: + // Unexpected 5xx return code + statusBucket = 11; + break; + case 502: + case 504: + case 511: + // Local network errors, we'll ignore these. + statusBucket = 12; + break; + case 503: + // Service Unavailable - The server cannot handle the request. + // Clients MUST follow the backoff behavior specified in the + // Request Frequency section. + statusBucket = 13; + break; + case 505: + // HTTP Version Not Supported - The server CANNOT handle the requested + // protocol major version. + statusBucket = 14; + break; + default: + statusBucket = 15; + } + return statusBucket; +} + +function FullHashMatch(table, hash, duration) { + this.tableName = table; + this.fullHash = hash; + this.cacheDuration = duration; +} + +FullHashMatch.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIFullHashMatch"]), + + tableName: null, + fullHash: null, + cacheDuration: null, +}; + +export function HashCompleter() { + // The current HashCompleterRequest in flight. Once it is started, it is set + // to null. It may be used by multiple calls to |complete| in succession to + // avoid creating multiple requests to the same gethash URL. + this._currentRequest = null; + // An Array of ongoing gethash requests which is used to find requests for + // the same hash prefix. + this._ongoingRequests = []; + // A map of gethashUrls to HashCompleterRequests that haven't yet begun. + this._pendingRequests = {}; + + // A map of gethash URLs to RequestBackoff objects. + this._backoffs = {}; + + // Whether we have been informed of a shutdown by the shutdown event. + this._shuttingDown = false; + + // A map of gethash URLs to next gethash time in miliseconds + this._nextGethashTimeMs = {}; + + Services.obs.addObserver(this, "quit-application"); + Services.prefs.addObserver(PREF_DEBUG_ENABLED, this); + + loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED); +} + +HashCompleter.prototype = { + classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"), + QueryInterface: ChromeUtils.generateQI([ + "nsIUrlClassifierHashCompleter", + "nsIRunnable", + "nsIObserver", + "nsISupportsWeakReference", + "nsITimerCallback", + ]), + + // This is mainly how the HashCompleter interacts with other components. + // Even though it only takes one partial hash and callback, subsequent + // calls are made into the same HTTP request by using a thread dispatch. + complete: function HC_complete( + aPartialHash, + aGethashUrl, + aTableName, + aCallback + ) { + if (!aGethashUrl) { + throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED); + } + + // Check ongoing requests before creating a new HashCompleteRequest + for (let r of this._ongoingRequests) { + if (r.find(aPartialHash, aGethashUrl, aTableName)) { + log( + "Merge gethash request in " + + aTableName + + " for prefix : " + + btoa(aPartialHash) + ); + r.add(aPartialHash, aCallback, aTableName); + return; + } + } + + if (!this._currentRequest) { + this._currentRequest = new HashCompleterRequest(this, aGethashUrl); + } + if (this._currentRequest.gethashUrl == aGethashUrl) { + this._currentRequest.add(aPartialHash, aCallback, aTableName); + } else { + if (!this._pendingRequests[aGethashUrl]) { + this._pendingRequests[aGethashUrl] = new HashCompleterRequest( + this, + aGethashUrl + ); + } + this._pendingRequests[aGethashUrl].add( + aPartialHash, + aCallback, + aTableName + ); + } + + if (!this._backoffs[aGethashUrl]) { + // Initialize request backoffs separately, since requests are deleted + // after they are dispatched. + var jslib = + Cc["@mozilla.org/url-classifier/jslib;1"].getService().wrappedJSObject; + + // Using the V4 backoff algorithm for both V2 and V4. See bug 1273398. + this._backoffs[aGethashUrl] = new jslib.RequestBackoffV4( + 10 /* keep track of max requests */, + 0 /* don't throttle on successful requests per time period */, + lazy.gUrlUtil.getProvider(aTableName) /* used by testcase */ + ); + } + + if (!this._nextGethashTimeMs[aGethashUrl]) { + this._nextGethashTimeMs[aGethashUrl] = 0; + } + + // Start off this request. Without dispatching to a thread, every call to + // complete makes an individual HTTP request. + Services.tm.dispatchToMainThread(this); + }, + + // This is called after several calls to |complete|, or after the + // currentRequest has finished. It starts off the HTTP request by making a + // |begin| call to the HashCompleterRequest. + run() { + // Clear everything on shutdown + if (this._shuttingDown) { + this._currentRequest = null; + this._pendingRequests = null; + this._nextGethashTimeMs = null; + + for (var url in this._backoffs) { + this._backoffs[url] = null; + } + throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED); + } + + // If we don't have an in-flight request, make one + let pendingUrls = Object.keys(this._pendingRequests); + if (!this._currentRequest && pendingUrls.length) { + let nextUrl = pendingUrls[0]; + this._currentRequest = this._pendingRequests[nextUrl]; + delete this._pendingRequests[nextUrl]; + } + + if (this._currentRequest) { + try { + if (this._currentRequest.begin()) { + this._ongoingRequests.push(this._currentRequest); + } + } finally { + // If |begin| fails, we should get rid of our request. + this._currentRequest = null; + } + } + }, + + // Pass the server response status to the RequestBackoff for the given + // gethashUrl and fetch the next pending request, if there is one. + finishRequest(aRequest, aStatus) { + this._ongoingRequests = this._ongoingRequests.filter(v => v != aRequest); + + this._backoffs[aRequest.gethashUrl].noteServerResponse(aStatus); + Services.tm.dispatchToMainThread(this); + }, + + // Returns true if we can make a request from the given url, false otherwise. + canMakeRequest(aGethashUrl) { + return ( + this._backoffs[aGethashUrl].canMakeRequest() && + Date.now() >= this._nextGethashTimeMs[aGethashUrl] + ); + }, + + // Notifies the RequestBackoff of a new request so we can throttle based on + // max requests/time period. This must be called before a channel is opened, + // and finishRequest must be called once the response is received. + noteRequest(aGethashUrl) { + return this._backoffs[aGethashUrl].noteRequest(); + }, + + observe: function HC_observe(aSubject, aTopic, aData) { + switch (aTopic) { + case "quit-application": + this._shuttingDown = true; + Services.obs.removeObserver(this, "quit-application"); + break; + case "nsPref:changed": + if (aData == PREF_DEBUG_ENABLED) { + loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED); + } + break; + } + }, +}; + +function HashCompleterRequest(aCompleter, aGethashUrl) { + // HashCompleter object that created this HashCompleterRequest. + this._completer = aCompleter; + // The internal set of hashes and callbacks that this request corresponds to. + this._requests = []; + // nsIChannel that the hash completion query is transmitted over. + this._channel = null; + // Response body of hash completion. Created in onDataAvailable. + this._response = ""; + // Whether we have been informed of a shutdown by the quit-application event. + this._shuttingDown = false; + this.gethashUrl = aGethashUrl; + + this.provider = ""; + // Multiple partial hashes can be associated with the same tables + // so we use a map here. + this.tableNames = new Map(); + + this.telemetryProvider = ""; + this.telemetryClockStart = 0; +} +HashCompleterRequest.prototype = { + QueryInterface: ChromeUtils.generateQI([ + "nsIRequestObserver", + "nsIStreamListener", + "nsIObserver", + ]), + + // This is called by the HashCompleter to add a hash and callback to the + // HashCompleterRequest. It must be called before calling |begin|. + add: function HCR_add(aPartialHash, aCallback, aTableName) { + this._requests.push({ + partialHash: aPartialHash, + callback: aCallback, + tableName: aTableName, + response: { matches: [] }, + }); + + if (aTableName) { + let isTableNameV4 = aTableName.endsWith("-proto"); + if (0 === this.tableNames.size) { + // Decide if this request is v4 by the first added partial hash. + this.isV4 = isTableNameV4; + } else if (this.isV4 !== isTableNameV4) { + log( + 'ERROR: Cannot mix "proto" tables with other types within ' + + "the same gethash URL." + ); + } + if (!this.tableNames.has(aTableName)) { + this.tableNames.set(aTableName); + } + + // Assuming all tables with the same gethash URL have the same provider + if (this.provider == "") { + this.provider = lazy.gUrlUtil.getProvider(aTableName); + } + + if (this.telemetryProvider == "") { + this.telemetryProvider = lazy.gUrlUtil.getTelemetryProvider(aTableName); + } + } + }, + + find: function HCR_find(aPartialHash, aGetHashUrl, aTableName) { + if (this.gethashUrl != aGetHashUrl || !this.tableNames.has(aTableName)) { + return false; + } + + return this._requests.find(function (r) { + return r.partialHash === aPartialHash; + }); + }, + + fillTableStatesBase64: function HCR_fillTableStatesBase64(aCallback) { + lazy.gDbService.getTables(aTableData => { + aTableData.split("\n").forEach(line => { + let p = line.indexOf(";"); + if (-1 === p) { + return; + } + // [tableName];[stateBase64]:[checksumBase64] + let tableName = line.substring(0, p); + if (this.tableNames.has(tableName)) { + let metadata = line.substring(p + 1).split(":"); + let stateBase64 = metadata[0]; + this.tableNames.set(tableName, stateBase64); + } + }); + + aCallback(); + }); + }, + + // This initiates the HTTP request. It can fail due to backoff timings and + // will notify all callbacks as necessary. We notify the backoff object on + // begin. + begin: function HCR_begin() { + if (!this._completer.canMakeRequest(this.gethashUrl)) { + log("Can't make request to " + this.gethashUrl + "\n"); + this.notifyFailure(Cr.NS_ERROR_ABORT); + return false; + } + + Services.obs.addObserver(this, "quit-application"); + + // V4 requires table states to build the request so we need + // a async call to retrieve the table states from disk. + // Note that |HCR_begin| is fine to be sync because + // it doesn't appear in a sync call chain. + this.fillTableStatesBase64(() => { + try { + this.openChannel(); + // Notify the RequestBackoff if opening the channel succeeded. At this + // point, finishRequest must be called. + this._completer.noteRequest(this.gethashUrl); + } catch (err) { + this._completer._ongoingRequests = + this._completer._ongoingRequests.filter(v => v != this); + this.notifyFailure(err); + throw err; + } + }); + + return true; + }, + + notify: function HCR_notify() { + // If we haven't gotten onStopRequest, just cancel. This will call us + // with onStopRequest since we implement nsIStreamListener on the + // channel. + if (this._channel && this._channel.isPending()) { + log("cancelling request to " + this.gethashUrl + " (timeout)\n"); + Services.telemetry + .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2") + .add(this.telemetryProvider, 1); + this._channel.cancel(Cr.NS_BINDING_ABORTED); + } + }, + + // Creates an nsIChannel for the request and fills the body. + // Enforce bypassing URL Classifier check because if the request is + // blocked, it means SafeBrowsing is malfunction. + openChannel: function HCR_openChannel() { + let loadFlags = + Ci.nsIChannel.INHIBIT_CACHING | + Ci.nsIChannel.LOAD_BYPASS_CACHE | + Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER; + + this.request = { + url: this.gethashUrl, + body: "", + }; + + if (this.isV4) { + // As per spec, we add the request payload to the gethash url. + this.request.url += "&$req=" + this.buildRequestV4(); + } + + log("actualGethashUrl: " + this.request.url); + + let channel = NetUtil.newChannel({ + uri: this.request.url, + loadUsingSystemPrincipal: true, + }); + channel.loadFlags = loadFlags; + channel.loadInfo.originAttributes = { + // The firstPartyDomain value should sync with NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN + // defined in nsNetUtil.h. + firstPartyDomain: + "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla", + }; + + // Disable keepalive. + let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); + httpChannel.setRequestHeader("Connection", "close", false); + + this._channel = channel; + + if (this.isV4) { + httpChannel.setRequestHeader("X-HTTP-Method-Override", "POST", false); + } else { + let body = this.buildRequest(); + this.addRequestBody(body); + } + + // Set a timer that cancels the channel after timeout_ms in case we + // don't get a gethash response. + this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // Ask the timer to use nsITimerCallback (.notify()) when ready + let timeout = Services.prefs.getIntPref("urlclassifier.gethash.timeout_ms"); + this.timer_.initWithCallback(this, timeout, this.timer_.TYPE_ONE_SHOT); + channel.asyncOpen(this); + this.telemetryClockStart = Date.now(); + }, + + buildRequestV4: function HCR_buildRequestV4() { + // Convert the "name to state" mapping to two equal-length arrays. + let tableNameArray = []; + let stateArray = []; + this.tableNames.forEach((state, name) => { + // We skip the table which is not associated with a state. + if (state) { + tableNameArray.push(name); + stateArray.push(state); + } + }); + + // Build the "distinct" prefix array. + // The array is sorted to make sure the entries are arbitrary mixed in a + // deterministic way + let prefixSet = new Set(); + this._requests.forEach(r => prefixSet.add(btoa(r.partialHash))); + let prefixArray = Array.from(prefixSet).sort(); + + log( + "Build v4 gethash request with " + + JSON.stringify(tableNameArray) + + ", " + + JSON.stringify(stateArray) + + ", " + + JSON.stringify(prefixArray) + ); + + return lazy.gUrlUtil.makeFindFullHashRequestV4( + tableNameArray, + stateArray, + prefixArray + ); + }, + + // Returns a string for the request body based on the contents of + // this._requests. + buildRequest: function HCR_buildRequest() { + // Sometimes duplicate entries are sent to HashCompleter but we do not need + // to propagate these to the server. (bug 633644) + let prefixes = []; + + for (let i = 0; i < this._requests.length; i++) { + let request = this._requests[i]; + if (!prefixes.includes(request.partialHash)) { + prefixes.push(request.partialHash); + } + } + + // Sort to make sure the entries are arbitrary mixed in a deterministic way + prefixes.sort(); + + let body; + body = + PARTIAL_LENGTH + + ":" + + PARTIAL_LENGTH * prefixes.length + + "\n" + + prefixes.join(""); + + log( + "Requesting completions for " + + prefixes.length + + " " + + PARTIAL_LENGTH + + "-byte prefixes: " + + body + ); + return body; + }, + + // Sets the request body of this._channel. + addRequestBody: function HCR_addRequestBody(aBody) { + let inputStream = Cc[ + "@mozilla.org/io/string-input-stream;1" + ].createInstance(Ci.nsIStringInputStream); + + inputStream.setData(aBody, aBody.length); + + let uploadChannel = this._channel.QueryInterface(Ci.nsIUploadChannel); + uploadChannel.setUploadStream(inputStream, "text/plain", -1); + + let httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel); + httpChannel.requestMethod = "POST"; + }, + + // Parses the response body and eventually adds items to the |response.matches| array + // for elements of |this._requests|. + handleResponse: function HCR_handleResponse() { + if (this._response == "") { + return; + } + + if (this.isV4) { + this.handleResponseV4(); + return; + } + + let start = 0; + + let length = this._response.length; + while (start != length) { + start = this.handleTable(start); + } + }, + + handleResponseV4: function HCR_handleResponseV4() { + let callback = { + // onCompleteHashFound will be called for each fullhash found in + // FullHashResponse. + onCompleteHashFound: ( + aCompleteHash, + aTableNames, + aPerHashCacheDuration + ) => { + log( + "V4 fullhash response complete hash found callback: " + + aTableNames + + ", CacheDuration(" + + aPerHashCacheDuration + + ")" + ); + + // Filter table names which we didn't requested. + let filteredTables = aTableNames.split(",").filter(name => { + return this.tableNames.get(name); + }); + if (0 === filteredTables.length) { + log("ERROR: Got complete hash which is from unknown table."); + return; + } + if (filteredTables.length > 1) { + log("WARNING: Got complete hash which has ambigious threat type."); + } + + this.handleItem({ + completeHash: aCompleteHash, + tableName: filteredTables[0], + cacheDuration: aPerHashCacheDuration, + }); + }, + + // onResponseParsed will be called no matter if there is match in + // FullHashResponse, the callback is mainly used to pass negative cache + // duration and minimum wait duration. + onResponseParsed: (aMinWaitDuration, aNegCacheDuration) => { + log( + "V4 fullhash response parsed callback: " + + "MinWaitDuration(" + + aMinWaitDuration + + "), " + + "NegativeCacheDuration(" + + aNegCacheDuration + + ")" + ); + + let minWaitDuration = aMinWaitDuration; + + if (aMinWaitDuration > MIN_WAIT_DURATION_MAX_VALUE) { + log( + "WARNING: Minimum wait duration too large, clamping it down " + + "to a reasonable value." + ); + minWaitDuration = MIN_WAIT_DURATION_MAX_VALUE; + } else if (aMinWaitDuration < 0) { + log("WARNING: Minimum wait duration is negative, reset it to 0"); + minWaitDuration = 0; + } + + this._completer._nextGethashTimeMs[this.gethashUrl] = + Date.now() + minWaitDuration; + + // A fullhash request may contain more than one prefix, so the negative + // cache duration should be set for all the prefixes in the request. + this._requests.forEach(request => { + request.response.negCacheDuration = aNegCacheDuration; + }); + }, + }; + + lazy.gUrlUtil.parseFindFullHashResponseV4(this._response, callback); + }, + + // This parses a table entry in the response body and calls |handleItem| + // for complete hash in the table entry. + handleTable: function HCR_handleTable(aStart) { + let body = this._response.substring(aStart); + + // deal with new line indexes as there could be + // new line characters in the data parts. + let newlineIndex = body.indexOf("\n"); + if (newlineIndex == -1) { + throw errorWithStack(); + } + let header = body.substring(0, newlineIndex); + let entries = header.split(":"); + if (entries.length != 3) { + throw errorWithStack(); + } + + let list = entries[0]; + let addChunk = parseInt(entries[1]); + let dataLength = parseInt(entries[2]); + + log("Response includes add chunks for " + list + ": " + addChunk); + if ( + dataLength % COMPLETE_LENGTH != 0 || + dataLength == 0 || + dataLength > body.length - (newlineIndex + 1) + ) { + throw errorWithStack(); + } + + let data = body.substr(newlineIndex + 1, dataLength); + for (let i = 0; i < dataLength / COMPLETE_LENGTH; i++) { + this.handleItem({ + completeHash: data.substr(i * COMPLETE_LENGTH, COMPLETE_LENGTH), + tableName: list, + chunkId: addChunk, + }); + } + + return aStart + newlineIndex + 1 + dataLength; + }, + + // This adds a complete hash to any entry in |this._requests| that matches + // the hash. + handleItem: function HCR_handleItem(aData) { + let provider = lazy.gUrlUtil.getProvider(aData.tableName); + if (provider != this.provider) { + log( + "Ignoring table " + + aData.tableName + + " since it belongs to " + + provider + + " while the response came from " + + this.provider + + "." + ); + return; + } + + for (let i = 0; i < this._requests.length; i++) { + let request = this._requests[i]; + if (aData.completeHash.startsWith(request.partialHash)) { + request.response.matches.push(aData); + } + } + }, + + // notifySuccess and notifyFailure are used to alert the callbacks with + // results. notifySuccess makes |completion| and |completionFinished| calls + // while notifyFailure only makes a |completionFinished| call with the error + // code. + notifySuccess: function HCR_notifySuccess() { + // V2 completion handler + let completionV2 = req => { + req.response.matches.forEach(m => { + req.callback.completionV2(m.completeHash, m.tableName, m.chunkId); + }); + + req.callback.completionFinished(Cr.NS_OK); + }; + + // V4 completion handler + let completionV4 = req => { + let matches = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray + ); + + req.response.matches.forEach(m => { + matches.appendElement( + new FullHashMatch(m.tableName, m.completeHash, m.cacheDuration) + ); + }); + + req.callback.completionV4( + req.partialHash, + req.tableName, + req.response.negCacheDuration, + matches + ); + + req.callback.completionFinished(Cr.NS_OK); + }; + + let completion = this.isV4 ? completionV4 : completionV2; + this._requests.forEach(req => { + completion(req); + }); + }, + + notifyFailure: function HCR_notifyFailure(aStatus) { + log("notifying failure\n"); + for (let i = 0; i < this._requests.length; i++) { + let request = this._requests[i]; + request.callback.completionFinished(aStatus); + } + }, + + onDataAvailable: function HCR_onDataAvailable( + aRequest, + aInputStream, + aOffset, + aCount + ) { + let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(aInputStream); + this._response += sis.readBytes(aCount); + }, + + onStartRequest: function HCR_onStartRequest(aRequest) { + // At this point no data is available for us and we have no reason to + // terminate the connection, so we do nothing until |onStopRequest|. + this._completer._nextGethashTimeMs[this.gethashUrl] = 0; + + if (this.telemetryClockStart > 0) { + let msecs = Date.now() - this.telemetryClockStart; + Services.telemetry + .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_SERVER_RESPONSE_TIME") + .add(this.telemetryProvider, msecs); + } + }, + + onStopRequest: function HCR_onStopRequest(aRequest, aStatusCode) { + Services.obs.removeObserver(this, "quit-application"); + + if (this.timer_) { + this.timer_.cancel(); + this.timer_ = null; + } + + this.telemetryClockStart = 0; + + if (this._shuttingDown) { + throw Components.Exception("", Cr.NS_ERROR_ABORT); + } + + // Default HTTP status to service unavailable, in case we can't retrieve + // the true status from the channel. + let httpStatus = 503; + if (Components.isSuccessCode(aStatusCode)) { + let channel = aRequest.QueryInterface(Ci.nsIHttpChannel); + let success = channel.requestSucceeded; + httpStatus = channel.responseStatus; + if (!success) { + aStatusCode = Cr.NS_ERROR_ABORT; + } + } + let success = Components.isSuccessCode(aStatusCode); + log( + "Received a " + + httpStatus + + " status code from the " + + this.provider + + " gethash server (success=" + + success + + "): " + + btoa(this._response) + ); + + Services.telemetry + .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_REMOTE_STATUS2") + .add(this.telemetryProvider, httpStatusToBucket(httpStatus)); + if (httpStatus == 400) { + dump( + "Safe Browsing server returned a 400 during completion: request= " + + this.request.url + + ",payload= " + + this.request.body + + "\n" + ); + } + + Services.telemetry + .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2") + .add(this.telemetryProvider, 0); + + // Notify the RequestBackoff once a response is received. + this._completer.finishRequest(this, httpStatus); + + if (success) { + try { + this.handleResponse(); + } catch (err) { + log(err.stack); + aStatusCode = err.value; + success = false; + } + } + + if (success) { + this.notifySuccess(); + } else { + this.notifyFailure(aStatusCode); + } + }, + + observe: function HCR_observe(aSubject, aTopic, aData) { + if (aTopic == "quit-application") { + this._shuttingDown = true; + if (this._channel) { + this._channel.cancel(Cr.NS_ERROR_ABORT); + this.telemetryClockStart = 0; + } + + Services.obs.removeObserver(this, "quit-application"); + } + }, +}; + +function errorWithStack() { + let err = new Error(); + err.value = Cr.NS_ERROR_FAILURE; + return err; +} diff --git a/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs b/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs new file mode 100644 index 0000000000..aad054ce15 --- /dev/null +++ b/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs @@ -0,0 +1,226 @@ +/* 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/. */ + +// We wastefully reload the same JS files across components. This puts all +// the common JS files used by safebrowsing and url-classifier into a +// single component. + +const PREF_DISABLE_TEST_BACKOFF = + "browser.safebrowsing.provider.test.disableBackoff"; + +/** + * Partially applies a function to a particular "this object" and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of |this| "pre-specified". + * + * Remaining arguments specified at call-time are appended to the pre- + * specified ones. + * + * Usage: + * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2"); + * barMethBound("arg3", "arg4"); + * + * @param fn {string} Reference to the function to be bound + * + * @param self {object} Specifies the object which |this| should point to + * when the function is run. If the value is null or undefined, it will default + * to the global object. + * + * @returns {function} A partially-applied form of the speficied function. + */ +export function BindToObject(fn, self, opt_args) { + var boundargs = fn.boundArgs_ || []; + boundargs = boundargs.concat( + Array.prototype.slice.call(arguments, 2, arguments.length) + ); + + if (fn.boundSelf_) { + self = fn.boundSelf_; + } + if (fn.boundFn_) { + fn = fn.boundFn_; + } + + var newfn = function () { + // Combine the static args and the new args into one big array + var args = boundargs.concat(Array.prototype.slice.call(arguments)); + return fn.apply(self, args); + }; + + newfn.boundArgs_ = boundargs; + newfn.boundSelf_ = self; + newfn.boundFn_ = fn; + + return newfn; +} + +// This implements logic for stopping requests if the server starts to return +// too many errors. If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we +// back off for TIMEOUT_INCREMENT minutes. If we get another error +// immediately after we restart, we double the timeout and add +// TIMEOUT_INCREMENT minutes, etc. +// +// This is similar to the logic used by the search suggestion service. + +// HTTP responses that count as an error. We also include any 5xx response +// as an error. +const HTTP_FOUND = 302; +const HTTP_SEE_OTHER = 303; +const HTTP_TEMPORARY_REDIRECT = 307; + +/** + * @param maxErrors Number of times to request before backing off. + * @param retryIncrement Time (ms) for each retry before backing off. + * @param maxRequests Number the number of requests needed to trigger backoff + * @param requestPeriod Number time (ms) in which maxRequests have to occur to + * trigger the backoff behavior (0 to disable maxRequests) + * @param timeoutIncrement Number time (ms) the starting timeout period + * we double this time for consecutive errors + * @param maxTimeout Number time (ms) maximum timeout period + * @param tolerance Checking next request tolerance. + */ +function RequestBackoff( + maxErrors, + retryIncrement, + maxRequests, + requestPeriod, + timeoutIncrement, + maxTimeout, + tolerance, + provider = null +) { + this.MAX_ERRORS_ = maxErrors; + this.RETRY_INCREMENT_ = retryIncrement; + this.MAX_REQUESTS_ = maxRequests; + this.REQUEST_PERIOD_ = requestPeriod; + this.TIMEOUT_INCREMENT_ = timeoutIncrement; + this.MAX_TIMEOUT_ = maxTimeout; + this.TOLERANCE_ = tolerance; + + // Queue of ints keeping the time of all requests + this.requestTimes_ = []; + + this.numErrors_ = 0; + this.errorTimeout_ = 0; + this.nextRequestTime_ = 0; + + // For test provider, we will disable backoff if preference is set to false. + if (provider === "test") { + this.canMakeRequestDefault = this.canMakeRequest; + this.canMakeRequest = function () { + if (Services.prefs.getBoolPref(PREF_DISABLE_TEST_BACKOFF, true)) { + return true; + } + return this.canMakeRequestDefault(); + }; + } +} + +/** + * Reset the object for reuse. This deliberately doesn't clear requestTimes_. + */ +RequestBackoff.prototype.reset = function () { + this.numErrors_ = 0; + this.errorTimeout_ = 0; + this.nextRequestTime_ = 0; +}; + +/** + * Check to see if we can make a request. + */ +RequestBackoff.prototype.canMakeRequest = function () { + var now = Date.now(); + // Note that nsITimer delay is approximate: the timer can be fired before the + // requested time has elapsed. So, give it a tolerance + if (now + this.TOLERANCE_ < this.nextRequestTime_) { + return false; + } + + return ( + this.requestTimes_.length < this.MAX_REQUESTS_ || + now - this.requestTimes_[0] > this.REQUEST_PERIOD_ + ); +}; + +RequestBackoff.prototype.noteRequest = function () { + var now = Date.now(); + this.requestTimes_.push(now); + + // We only care about keeping track of MAX_REQUESTS + if (this.requestTimes_.length > this.MAX_REQUESTS_) { + this.requestTimes_.shift(); + } +}; + +RequestBackoff.prototype.nextRequestDelay = function () { + return Math.max(0, this.nextRequestTime_ - Date.now()); +}; + +/** + * Notify this object of the last server response. If it's an error, + */ +RequestBackoff.prototype.noteServerResponse = function (status) { + if (this.isErrorStatus(status)) { + this.numErrors_++; + + if (this.numErrors_ < this.MAX_ERRORS_) { + this.errorTimeout_ = this.RETRY_INCREMENT_; + } else if (this.numErrors_ == this.MAX_ERRORS_) { + this.errorTimeout_ = this.TIMEOUT_INCREMENT_; + } else { + this.errorTimeout_ *= 2; + } + + this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_); + this.nextRequestTime_ = Date.now() + this.errorTimeout_; + } else { + // Reset error timeout, allow requests to go through. + this.reset(); + } +}; + +/** + * We consider 302, 303, 307, 4xx, and 5xx http responses to be errors. + * @param status Number http status + * @return Boolean true if we consider this http status an error + */ +RequestBackoff.prototype.isErrorStatus = function (status) { + return ( + (400 <= status && status <= 599) || + HTTP_FOUND == status || + HTTP_SEE_OTHER == status || + HTTP_TEMPORARY_REDIRECT == status + ); +}; + +// Wrap a general-purpose |RequestBackoff| to a v4-specific one +// since both listmanager and hashcompleter would use it. +// Note that |maxRequests| and |requestPeriod| is still configurable +// to throttle pending requests. +function RequestBackoffV4(maxRequests, requestPeriod, provider = null) { + let rand = Math.random(); + let retryInterval = Math.floor(15 * 60 * 1000 * (rand + 1)); // 15 ~ 30 min. + let backoffInterval = Math.floor(30 * 60 * 1000 * (rand + 1)); // 30 ~ 60 min. + + return new RequestBackoff( + 2 /* max errors */, + retryInterval /* retry interval, 15~30 min */, + maxRequests /* num requests */, + requestPeriod /* request time, 60 min */, + backoffInterval /* backoff interval, 60 min */, + 24 * 60 * 60 * 1000 /* max backoff, 24hr */, + 1000 /* tolerance of 1 sec */, + provider /* provider name */ + ); +} + +export function UrlClassifierLib() { + this.wrappedJSObject = { + RequestBackoff, + RequestBackoffV4, + BindToObject, + }; +} + +UrlClassifierLib.prototype.QueryInterface = ChromeUtils.generateQI([]); diff --git a/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs b/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs new file mode 100644 index 0000000000..410e203672 --- /dev/null +++ b/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs @@ -0,0 +1,864 @@ +/* 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/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +// This is the only implementation of nsIUrlListManager. +// A class that manages lists, namely exception and block lists for +// phishing or malware protection. The ListManager knows how to fetch, +// update, and store lists. +// +// There is a single listmanager for the whole application. +// +// TODO more comprehensive update tests, for example add unittest check +// that the listmanagers tables are properly written on updates + +// Lower and upper limits on the server-provided polling frequency +const minDelayMs = 5 * 60 * 1000; +const maxDelayMs = 24 * 60 * 60 * 1000; +const defaultUpdateIntervalMs = 30 * 60 * 1000; +// The threshold to check if the browser is idle. We will defer the update in +// order to save the power consumption if the browser has been idle for one hour +// because it's likely that the browser will keep idle for a longer period. +const browserIdleThresholdMs = 60 * 60 * 1000; +const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug"; +const PREF_TEST_NOTIFICATIONS = + "browser.safebrowsing.test-notifications.enabled"; + +let loggingEnabled = false; + +// Variables imported from library. +let BindToObject, RequestBackoffV4; + +// Log only if browser.safebrowsing.debug is true +function log(...stuff) { + if (!loggingEnabled) { + return; + } + + var d = new Date(); + let msg = "listmanager: " + d.toTimeString() + ": " + stuff.join(" "); + msg = Services.urlFormatter.trimSensitiveURLs(msg); + Services.console.logStringMessage(msg); + dump(msg + "\n"); +} + +/** + * A ListManager keeps track of exception and block lists and knows + * how to update them. + * + * @constructor + */ +function PROT_ListManager() { + loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED); + + log("Initializing list manager"); + + // A map of tableNames to objects of type + // { updateUrl: <updateUrl>, gethashUrl: <gethashUrl> } + this.tablesData = {}; + // A map of updateUrls to maps of tables requiring updates, e.g. + // { safebrowsing-update-url: { goog-phish-shavar: true, + // goog-malware-shavar: true } + this.needsUpdate_ = {}; + + // A map of updateUrls to single-use nsITimer. An entry exists if and only if + // there is at least one table with updates enabled for that url. nsITimers + // are reset when enabling/disabling updates or on update callbacks (update + // success, update failure, download error). + this.updateCheckers_ = {}; + this.requestBackoffs_ = {}; + + // This is only used by testcases to ensure SafeBrowsing.jsm is inited + this.registered = false; + + this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIUrlClassifierDBService + ); + + this.idleService_ = Cc["@mozilla.org/widget/useridleservice;1"].getService( + Ci.nsIUserIdleService + ); + + Services.obs.addObserver(this, "quit-application"); + Services.prefs.addObserver(PREF_DEBUG_ENABLED, this); +} + +/** + * Register a new table table + * @param tableName - the name of the table + * @param updateUrl - the url for updating the table + * @param gethashUrl - the url for fetching hash completions + * @returns true if the table could be created; false otherwise + */ +PROT_ListManager.prototype.registerTable = function ( + tableName, + providerName, + updateUrl, + gethashUrl +) { + this.registered = true; + + this.tablesData[tableName] = {}; + if (!updateUrl) { + log("Can't register table " + tableName + " without updateUrl"); + return false; + } + log("registering " + tableName + " with " + updateUrl); + this.tablesData[tableName].updateUrl = updateUrl; + this.tablesData[tableName].gethashUrl = gethashUrl; + this.tablesData[tableName].provider = providerName; + + // Keep track of all of our update URLs. + if (!this.needsUpdate_[updateUrl]) { + this.needsUpdate_[updateUrl] = {}; + + // Using the V4 backoff algorithm for both V2 and V4. See bug 1273398. + this.requestBackoffs_[updateUrl] = new RequestBackoffV4( + 4 /* num requests */, + 60 * 60 * 1000 /* request time, 60 min */, + providerName /* used by testcase */ + ); + } + this.needsUpdate_[updateUrl][tableName] = false; + + return true; +}; + +/** + * Unregister a table table from list + */ +PROT_ListManager.prototype.unregisterTable = function (tableName) { + log("unregistering " + tableName); + var table = this.tablesData[tableName]; + if (table) { + if ( + !this.updatesNeeded_(table.updateUrl) && + this.updateCheckers_[table.updateUrl] + ) { + this.updateCheckers_[table.updateUrl].cancel(); + this.updateCheckers_[table.updateUrl] = null; + } + delete this.needsUpdate_[table.updateUrl][tableName]; + } + delete this.tablesData[tableName]; +}; + +/** + * Delete all of our data tables which seem to leak otherwise. + * Remove observers + */ +PROT_ListManager.prototype.shutdown_ = function () { + this.stopUpdateCheckers(); + for (var name in this.tablesData) { + delete this.tablesData[name]; + } + Services.obs.removeObserver(this, "quit-application"); + Services.prefs.removeObserver(PREF_DEBUG_ENABLED, this); +}; + +/** + * xpcom-shutdown callback + */ +PROT_ListManager.prototype.observe = function (aSubject, aTopic, aData) { + switch (aTopic) { + case "quit-application": + this.shutdown_(); + break; + case "nsPref:changed": + if (aData == PREF_DEBUG_ENABLED) { + loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED); + } + break; + } +}; + +PROT_ListManager.prototype.getGethashUrl = function (tableName) { + if (this.tablesData[tableName] && this.tablesData[tableName].gethashUrl) { + return this.tablesData[tableName].gethashUrl; + } + return ""; +}; + +PROT_ListManager.prototype.getUpdateUrl = function (tableName) { + if (this.tablesData[tableName] && this.tablesData[tableName].updateUrl) { + return this.tablesData[tableName].updateUrl; + } + return ""; +}; + +/** + * Enable updates for a single table. + */ +PROT_ListManager.prototype.enableUpdate = function (tableName) { + var table = this.tablesData[tableName]; + if (table) { + log("Enabling table updates for " + tableName); + this.needsUpdate_[table.updateUrl][tableName] = true; + } +}; + +PROT_ListManager.prototype.isRegistered = function () { + return this.registered; +}; + +/** + * Returns true if any table associated with the updateUrl requires updates. + * @param updateUrl - the updateUrl + */ +PROT_ListManager.prototype.updatesNeeded_ = function (updateUrl) { + let updatesNeeded = false; + for (var tableName in this.needsUpdate_[updateUrl]) { + if (this.needsUpdate_[updateUrl][tableName]) { + updatesNeeded = true; + } + } + return updatesNeeded; +}; + +/** + * Disable updates for all tables. + */ +PROT_ListManager.prototype.disableAllUpdates = function () { + for (const tableName of Object.keys(this.tablesData)) { + this.disableUpdate(tableName); + } +}; + +/** + * Disables updates for a single table. Avoid this internal function + * and use disableAllUpdates() instead. + */ +PROT_ListManager.prototype.disableUpdate = function (tableName) { + var table = this.tablesData[tableName]; + if (table) { + log("Disabling table updates for " + tableName); + this.needsUpdate_[table.updateUrl][tableName] = false; + if ( + !this.updatesNeeded_(table.updateUrl) && + this.updateCheckers_[table.updateUrl] + ) { + this.updateCheckers_[table.updateUrl].cancel(); + this.updateCheckers_[table.updateUrl] = null; + } + } +}; + +/** + * Determine if we have some tables that need updating. + */ +PROT_ListManager.prototype.requireTableUpdates = function () { + for (var name in this.tablesData) { + // Tables that need updating even if other tables don't require it + if (this.needsUpdate_[this.tablesData[name].updateUrl][name]) { + return true; + } + } + + return false; +}; + +/** + * Set timer to check update after delay + */ +PROT_ListManager.prototype.setUpdateCheckTimer = function (updateUrl, delay) { + this.updateCheckers_[updateUrl] = Cc["@mozilla.org/timer;1"].createInstance( + Ci.nsITimer + ); + + // A helper function to trigger the table update. + let update = function () { + if (!this.checkForUpdates(updateUrl)) { + // Make another attempt later. + this.setUpdateCheckTimer(updateUrl, defaultUpdateIntervalMs); + } + }.bind(this); + + this.updateCheckers_[updateUrl].initWithCallback( + () => { + this.updateCheckers_[updateUrl] = null; + // Check if we are in the idle mode. We will stop the current update and + // defer it to the next user interaction active if the browser is + // considered in idle mode. + if (this.idleService_.idleTime > browserIdleThresholdMs) { + let observer = function () { + Services.obs.removeObserver(observer, "user-interaction-active"); + update(); + }; + + Services.obs.addObserver(observer, "user-interaction-active"); + return; + } + + update(); + }, + delay, + Ci.nsITimer.TYPE_ONE_SHOT + ); +}; +/** + * Acts as a nsIUrlClassifierCallback for getTables. + */ +PROT_ListManager.prototype.kickoffUpdate_ = function () { + this.startingUpdate_ = false; + var initialUpdateDelay = 3000; + // Add a fuzz of 0-1 minutes for both v2 and v4 according to Bug 1305478. + initialUpdateDelay += Math.floor(Math.random() * (1 * 60 * 1000)); + + // If the user has never downloaded tables, do the check now. + log("needsUpdate: " + JSON.stringify(this.needsUpdate_, undefined, 2)); + for (var updateUrl in this.needsUpdate_) { + // If we haven't already kicked off updates for this updateUrl, set a + // non-repeating timer for it. The timer delay will be reset either on + // updateSuccess to the default update interval, or backed off on + // downloadError. Don't set the updateChecker unless at least one table has + // updates enabled. + if (this.updatesNeeded_(updateUrl) && !this.updateCheckers_[updateUrl]) { + let provider = null; + Object.keys(this.tablesData).forEach(function (table) { + if (this.tablesData[table].updateUrl === updateUrl) { + let newProvider = this.tablesData[table].provider; + if (provider) { + if (newProvider !== provider) { + log( + "Multiple tables for the same updateURL have a different provider?!" + ); + } + } else { + provider = newProvider; + } + } + }, this); + log( + "Initializing update checker for " + + updateUrl + + " provided by " + + provider + ); + + // Use the initialUpdateDelay + fuzz unless we had previous updates + // and the server told us when to try again. + let updateDelay = initialUpdateDelay; + let nextUpdatePref = + "browser.safebrowsing.provider." + provider + ".nextupdatetime"; + let nextUpdate = Services.prefs.getCharPref(nextUpdatePref, ""); + + if (nextUpdate) { + updateDelay = Math.min( + maxDelayMs, + Math.max(0, nextUpdate - Date.now()) + ); + log("Next update at " + nextUpdate); + } + log("Next update " + Math.round(updateDelay / 60000) + "min from now"); + + this.setUpdateCheckTimer(updateUrl, updateDelay); + } else { + log("No updates needed or already initialized for " + updateUrl); + } + } +}; + +PROT_ListManager.prototype.stopUpdateCheckers = function () { + log("Stopping updates"); + for (var updateUrl in this.updateCheckers_) { + if (this.updateCheckers_[updateUrl]) { + this.updateCheckers_[updateUrl].cancel(); + this.updateCheckers_[updateUrl] = null; + } + } +}; + +/** + * Determine if we have any tables that require updating. Different + * Wardens may call us with new tables that need to be updated. + */ +PROT_ListManager.prototype.maybeToggleUpdateChecking = function () { + // We update tables if we have some tables that want updates. If there + // are no tables that want to be updated - we dont need to check anything. + if (this.requireTableUpdates()) { + log("Starting managing lists"); + + // Get the list of existing tables from the DBService before making any + // update requests. + if (!this.startingUpdate_) { + this.startingUpdate_ = true; + // check the current state of tables in the database + this.kickoffUpdate_(); + } + } else { + log("Stopping managing lists (if currently active)"); + this.stopUpdateCheckers(); // Cancel pending updates + } +}; + +/** + * Force updates for the given tables. This API may trigger more than one update + * if the table lists provided belong to multiple updateurl (multiple provider). + * Return false when any update is fail due to back-off algorithm. + */ +PROT_ListManager.prototype.forceUpdates = function (tables) { + log("forceUpdates with " + tables); + if (!tables) { + return false; + } + + let updateUrls = new Set(); + tables.split(",").forEach(table => { + if (this.tablesData[table]) { + updateUrls.add(this.tablesData[table].updateUrl); + } + }); + + let ret = true; + + updateUrls.forEach(url => { + // Cancel current update timer for the url because we are forcing an update. + if (this.updateCheckers_[url]) { + this.updateCheckers_[url].cancel(); + this.updateCheckers_[url] = null; + } + + // Trigger an update for the given url. + if (!this.checkForUpdates(url, true)) { + ret = false; + } + }); + + return ret; +}; + +/** + * Updates our internal tables from the update server + * + * @param updateUrl: request updates for tables associated with that url, or + * for all tables if the url is empty. + * @param manual: the update is triggered manually + */ +PROT_ListManager.prototype.checkForUpdates = function ( + updateUrl, + manual = false +) { + log("checkForUpdates with " + updateUrl); + // See if we've triggered the request backoff logic. + if (!updateUrl) { + return false; + } + + // Disable SafeBrowsing updates in Safe Mode, but still allow manually + // triggering an update for debugging. + if (Services.appinfo.inSafeMode && !manual) { + log("update is disabled in Safe Mode"); + return false; + } + + if (lazy.enableTestNotifications) { + Services.obs.notifyObservers( + null, + "safebrowsing-update-attempt", + updateUrl + ); + } + + if ( + !this.requestBackoffs_[updateUrl] || + !this.requestBackoffs_[updateUrl].canMakeRequest() + ) { + log("Can't make update request"); + return false; + } + // Grab the current state of the tables from the database + this.dbService_.getTables( + BindToObject(this.makeUpdateRequest_, this, updateUrl) + ); + return true; +}; + +/** + * Method that fires the actual HTTP update request. + * First we reset any tables that have disappeared. + * @param tableData List of table data already in the database, in the form + * tablename;<chunk ranges>\n + */ +PROT_ListManager.prototype.makeUpdateRequest_ = function ( + updateUrl, + tableData +) { + log("this.tablesData: " + JSON.stringify(this.tablesData, undefined, 2)); + log("existing chunks: " + tableData + "\n"); + // Disallow blank updateUrls + if (!updateUrl) { + return; + } + // An object of the form + // { tableList: comma-separated list of tables to request, + // tableNames: map of tables that need updating, + // request: list of tables and existing chunk ranges from tableData + // } + var streamerMap = { + tableList: null, + tableNames: {}, + requestPayload: "", + isPostRequest: true, + }; + + let useProtobuf = false; + let onceThru = false; + for (var tableName in this.tablesData) { + // Skip tables not matching this update url + if (this.tablesData[tableName].updateUrl != updateUrl) { + continue; + } + + // Check if |updateURL| is for 'proto'. (only v4 uses protobuf for now.) + // We use the table name 'goog-*-proto' and an additional provider "google4" + // to describe the v4 settings. + let isCurTableProto = tableName.endsWith("-proto"); + if (!onceThru) { + useProtobuf = isCurTableProto; + onceThru = true; + } else if (useProtobuf !== isCurTableProto) { + log( + 'ERROR: Cannot mix "proto" tables with other types ' + + "within the same provider." + ); + } + + if (this.needsUpdate_[this.tablesData[tableName].updateUrl][tableName]) { + streamerMap.tableNames[tableName] = true; + } + if (!streamerMap.tableList) { + streamerMap.tableList = tableName; + } else { + streamerMap.tableList += "," + tableName; + } + } + + if (useProtobuf) { + let tableArray = []; + Object.keys(streamerMap.tableNames).forEach(aTableName => { + if (streamerMap.tableNames[aTableName]) { + tableArray.push(aTableName); + } + }); + + // Build the <tablename, stateBase64> mapping. + let tableState = {}; + tableData.split("\n").forEach(line => { + let p = line.indexOf(";"); + if (-1 === p) { + return; + } + let tableName = line.substring(0, p); + if (tableName in streamerMap.tableNames) { + let metadata = line.substring(p + 1).split(":"); + let stateBase64 = metadata[0]; + log(tableName + " ==> " + stateBase64); + tableState[tableName] = stateBase64; + } + }); + + // The state is a byte stream which server told us from the + // last table update. The state would be used to do the partial + // update and the empty string means the table has + // never been downloaded. See Bug 1287058 for supporting + // partial update. + let stateArray = []; + tableArray.forEach(listName => { + stateArray.push(tableState[listName] || ""); + }); + + log("stateArray: " + stateArray); + + let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils + ); + + streamerMap.requestPayload = urlUtils.makeUpdateRequestV4( + tableArray, + stateArray + ); + streamerMap.isPostRequest = false; + } else { + // Build the request. For each table already in the database, include the + // chunk data from the database + var lines = tableData.split("\n"); + for (var i = 0; i < lines.length; i++) { + var fields = lines[i].split(";"); + var name = fields[0]; + if (streamerMap.tableNames[name]) { + streamerMap.requestPayload += lines[i] + "\n"; + delete streamerMap.tableNames[name]; + } + } + // For each requested table that didn't have chunk data in the database, + // request it fresh + for (let tableName in streamerMap.tableNames) { + streamerMap.requestPayload += tableName + ";\n"; + } + + streamerMap.isPostRequest = true; + } + + log("update request: " + JSON.stringify(streamerMap, undefined, 2) + "\n"); + + // Don't send an empty request. + if (streamerMap.requestPayload.length) { + this.makeUpdateRequestForEntry_( + updateUrl, + streamerMap.tableList, + streamerMap.requestPayload, + streamerMap.isPostRequest + ); + } else { + // We were disabled between kicking off getTables and now. + log("Not sending empty request"); + } +}; + +PROT_ListManager.prototype.makeUpdateRequestForEntry_ = function ( + updateUrl, + tableList, + requestPayload, + isPostRequest +) { + log( + "makeUpdateRequestForEntry_: requestPayload " + + requestPayload + + " update: " + + updateUrl + + " tablelist: " + + tableList + + "\n" + ); + var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"].getService( + Ci.nsIUrlClassifierStreamUpdater + ); + + this.requestBackoffs_[updateUrl].noteRequest(); + + if ( + !streamer.downloadUpdates( + tableList, + requestPayload, + isPostRequest, + updateUrl, + BindToObject(this.updateSuccess_, this, tableList, updateUrl), + BindToObject(this.updateError_, this, tableList, updateUrl), + BindToObject(this.downloadError_, this, tableList, updateUrl) + ) + ) { + // Our alarm gets reset in one of the 3 callbacks. + log("pending update, queued request until later"); + } else { + let table = Object.keys(this.tablesData).find(key => { + return this.tablesData[key].updateUrl === updateUrl; + }); + let provider = this.tablesData[table].provider; + Services.obs.notifyObservers(null, "safebrowsing-update-begin", provider); + } +}; + +/** + * Callback function if the update request succeeded. + * @param waitForUpdate String The number of seconds that the client should + * wait before requesting again. + */ +PROT_ListManager.prototype.updateSuccess_ = function ( + tableList, + updateUrl, + waitForUpdateSec +) { + log( + "update success for " + + tableList + + " from " + + updateUrl + + ": " + + waitForUpdateSec + + "\n" + ); + + // The time unit below are all milliseconds if not specified. + + var delay = 0; + if (waitForUpdateSec) { + delay = parseInt(waitForUpdateSec, 10) * 1000; + } + // As long as the delay is something sane (5 min to 1 day), update + // our delay time for requesting updates. We always use a non-repeating + // timer since the delay is set differently at every callback. + if (delay > maxDelayMs) { + log( + "Ignoring delay from server (too long), waiting " + + Math.round(maxDelayMs / 60000) + + "min" + ); + delay = maxDelayMs; + } else if (delay < minDelayMs) { + log( + "Ignoring delay from server (too short), waiting " + + Math.round(defaultUpdateIntervalMs / 60000) + + "min" + ); + delay = defaultUpdateIntervalMs; + } else { + log("Waiting " + Math.round(delay / 60000) + "min"); + } + + this.setUpdateCheckTimer(updateUrl, delay); + + // Let the backoff object know that we completed successfully. + this.requestBackoffs_[updateUrl].noteServerResponse(200); + + // Set last update time for provider + // Get the provider for these tables, check for consistency + let tables = tableList.split(","); + let provider = null; + for (let table of tables) { + let newProvider = this.tablesData[table].provider; + if (provider) { + if (newProvider !== provider) { + log( + "Multiple tables for the same updateURL have a different provider?!" + ); + } + } else { + provider = newProvider; + } + } + + // Store the last update time (needed to know if the table is "fresh") + // and the next update time (to know when to update next). + let lastUpdatePref = + "browser.safebrowsing.provider." + provider + ".lastupdatetime"; + let now = Date.now(); + log("Setting last update of " + provider + " to " + now); + Services.prefs.setCharPref(lastUpdatePref, now.toString()); + + let nextUpdatePref = + "browser.safebrowsing.provider." + provider + ".nextupdatetime"; + let targetTime = now + delay; + log( + "Setting next update of " + + provider + + " to " + + targetTime + + " (" + + Math.round(delay / 60000) + + "min from now)" + ); + Services.prefs.setCharPref(nextUpdatePref, targetTime.toString()); + + Services.obs.notifyObservers(null, "safebrowsing-update-finished", "success"); +}; + +/** + * Callback function if the update request succeeded. + * @param result String The error code of the failure + */ +PROT_ListManager.prototype.updateError_ = function (table, updateUrl, result) { + log( + "update error for " + table + " from " + updateUrl + ": " + result + "\n" + ); + // There was some trouble applying the updates. Don't try again for at least + // updateInterval milliseconds. + this.setUpdateCheckTimer(updateUrl, defaultUpdateIntervalMs); + + Services.obs.notifyObservers( + null, + "safebrowsing-update-finished", + "update error: " + result + ); +}; + +/** + * Callback function when the download failed + * @param status String http status or an empty string if connection refused. + */ +PROT_ListManager.prototype.downloadError_ = function ( + table, + updateUrl, + status +) { + log("download error for " + table + ": " + status + "\n"); + // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED + // error. In this case, we treat this is a http 500 error. + if (!status) { + status = 500; + } + status = parseInt(status, 10); + this.requestBackoffs_[updateUrl].noteServerResponse(status); + let delay = defaultUpdateIntervalMs; + if (this.requestBackoffs_[updateUrl].isErrorStatus(status)) { + // Schedule an update for when our backoff is complete + delay = this.requestBackoffs_[updateUrl].nextRequestDelay(); + } else { + log("Got non error status for error callback?!"); + } + + this.setUpdateCheckTimer(updateUrl, delay); + + Services.obs.notifyObservers( + null, + "safebrowsing-update-finished", + "download error: " + status + ); +}; + +/** + * Get back-off time for the given provider. + * Return 0 if we are not in back-off mode. + */ +PROT_ListManager.prototype.getBackOffTime = function (provider) { + let updateUrl = ""; + for (var table in this.tablesData) { + if (this.tablesData[table].provider == provider) { + updateUrl = this.tablesData[table].updateUrl; + break; + } + } + + if (!updateUrl || !this.requestBackoffs_[updateUrl]) { + return 0; + } + + let delay = this.requestBackoffs_[updateUrl].nextRequestDelay(); + return delay == 0 ? 0 : Date.now() + delay; +}; + +PROT_ListManager.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsIUrlListManager", + "nsIObserver", + "nsITimerCallback", +]); + +let initialized = false; +function Init() { + if (initialized) { + return; + } + + // Pull the library in. + var jslib = + Cc["@mozilla.org/url-classifier/jslib;1"].getService().wrappedJSObject; + BindToObject = jslib.BindToObject; + RequestBackoffV4 = jslib.RequestBackoffV4; + + initialized = true; +} + +export function RegistrationData() { + Init(); + return new PROT_ListManager(); +} + +const lazy = {}; + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "enableTestNotifications", + PREF_TEST_NOTIFICATIONS, + false +); diff --git a/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs b/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs new file mode 100644 index 0000000000..42f8cf272a --- /dev/null +++ b/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs @@ -0,0 +1,141 @@ +/* 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/. */ + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", +}); + +const COLLECTION_NAME = "tracking-protection-lists"; + +// SafeBrowsing protocol parameters. +export const SBRS_UPDATE_MINIMUM_DELAY = 21600; // Minimum delay before polling again in seconds + +export function UrlClassifierRemoteSettingsService() {} +UrlClassifierRemoteSettingsService.prototype = { + classID: Components.ID("{1980624c-c50b-4b46-a91c-dfaba7792706}"), + QueryInterface: ChromeUtils.generateQI([ + "nsIUrlClassifierRemoteSettingsService", + ]), + + _initialized: false, + + // Entries that are retrieved from RemoteSettings.get(). keyed by the table name. + _entries: {}, + + async lazyInit() { + if (this._initialized) { + return; + } + + let rs = lazy.RemoteSettings(COLLECTION_NAME); + // Bug 1750191: Notify listmanager to trigger an update instead + // of polling data periodically. + rs.on("sync", async event => { + let { + data: { current }, + } = event; + this._onUpdateEntries(current); + }); + + this._initialized = true; + + let entries; + try { + entries = await rs.get(); + } catch (e) {} + + this._onUpdateEntries(entries || []); + }, + + _onUpdateEntries(aEntries) { + aEntries.map(entry => { + this._entries[entry.Name] = entry; + }); + }, + + // Parse the update request. See UrlClassifierListManager.jsm makeUpdateRequest + // for more details about how we build the update request. + // + // @param aRequest the request payload of the update request + // @return array The array of requested tables. Each item in the array is composed + // with [table name, chunk numner] + _parseRequest(aRequest) { + let lines = aRequest.split("\n"); + let requests = []; + for (let line of lines) { + let fields = line.split(";"); + let chunkNum = fields[1]?.match(/(?<=a:).*/); + requests.push([fields[0], chunkNum]); + } + return requests; + }, + + async _getLists(aRequest, aListener) { + await this.lazyInit(); + + let rs = lazy.RemoteSettings(COLLECTION_NAME); + let payload = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n"; + + let requests = this._parseRequest(aRequest); + for (let request of requests) { + let [reqTableName, reqChunkNum] = request; + let entry = this._entries[reqTableName]; + if (!entry?.attachment) { + continue; + } + + // If the request version is the same as what we have in Remote Settings, + // we are up-to-date now. + if (entry.Version == reqChunkNum) { + continue; + } + + let downloadError = false; + try { + // SafeBrowsing maintains its own files, so we can remove the downloaded + // files after SafeBrowsing processes the data. + let buffer = await rs.attachments.downloadAsBytes(entry); + let bytes = new Uint8Array(buffer); + let strData = ""; + for (let i = 0; i < bytes.length; i++) { + strData += String.fromCharCode(bytes[i]); + } + + // Construct the payload + payload += "i:" + reqTableName + "\n"; + payload += strData; + } catch (e) { + downloadError = true; + } + + if (downloadError) { + try { + aListener.onStartRequest(null); + aListener.onStopRequest(null, Cr.NS_ERROR_FAILURE); + } catch (e) {} + return; + } + } + + // Send the update response over stream listener interface + let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(payload, payload.length); + + aListener.onStartRequest(null); + aListener.onDataAvailable(null, stream, 0, payload.length); + aListener.onStopRequest(null, Cr.NS_OK); + }, + + fetchList(aPayload, aListener) { + this._getLists(aPayload, aListener); + }, + + clear() { + this._initialized = false; + this._entries = {}; + }, +}; diff --git a/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.cpp b/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.cpp new file mode 100644 index 0000000000..c6fe9d6ca6 --- /dev/null +++ b/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.cpp @@ -0,0 +1,157 @@ +/* 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 "UrlClassifierTelemetryUtils.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace safebrowsing { + +uint8_t NetworkErrorToBucket(nsresult rv) { + switch (rv) { + // Connection errors + case NS_ERROR_ALREADY_CONNECTED: + return 2; + case NS_ERROR_NOT_CONNECTED: + return 3; + case NS_ERROR_CONNECTION_REFUSED: + return 4; + case NS_ERROR_NET_TIMEOUT: + return 5; + case NS_ERROR_OFFLINE: + return 6; + case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: + return 7; + case NS_ERROR_NET_RESET: + return 8; + case NS_ERROR_NET_INTERRUPT: + return 9; + case NS_ERROR_PROXY_CONNECTION_REFUSED: + return 10; + case NS_ERROR_NET_PARTIAL_TRANSFER: + return 11; + case NS_ERROR_NET_INADEQUATE_SECURITY: + return 12; + // DNS errors + case NS_ERROR_UNKNOWN_HOST: + return 13; + case NS_ERROR_DNS_LOOKUP_QUEUE_FULL: + return 14; + case NS_ERROR_UNKNOWN_PROXY_HOST: + return 15; + // Others + default: + return 1; + } +} + +uint32_t HTTPStatusToBucket(uint32_t status) { + uint32_t statusBucket; + switch (status) { + case 100: + case 101: + // Unexpected 1xx return code + statusBucket = 0; + break; + case 200: + // OK - Data is available in the HTTP response body. + statusBucket = 1; + break; + case 201: + case 202: + case 203: + case 205: + case 206: + // Unexpected 2xx return code + statusBucket = 2; + break; + case 204: + // No Content + statusBucket = 3; + break; + case 300: + case 301: + case 302: + case 303: + case 304: + case 305: + case 307: + case 308: + // Unexpected 3xx return code + statusBucket = 4; + break; + case 400: + // Bad Request - The HTTP request was not correctly formed. + // The client did not provide all required CGI parameters. + statusBucket = 5; + break; + case 401: + case 402: + case 405: + case 406: + case 407: + case 409: + case 410: + case 411: + case 412: + case 414: + case 415: + case 416: + case 417: + case 421: + case 426: + case 428: + case 429: + case 431: + case 451: + // Unexpected 4xx return code + statusBucket = 6; + break; + case 403: + // Forbidden - The client id is invalid. + statusBucket = 7; + break; + case 404: + // Not Found + statusBucket = 8; + break; + case 408: + // Request Timeout + statusBucket = 9; + break; + case 413: + // Request Entity Too Large - Bug 1150334 + statusBucket = 10; + break; + case 500: + case 501: + case 510: + // Unexpected 5xx return code + statusBucket = 11; + break; + case 502: + case 504: + case 511: + // Local network errors, we'll ignore these. + statusBucket = 12; + break; + case 503: + // Service Unavailable - The server cannot handle the request. + // Clients MUST follow the backoff behavior specified in the + // Request Frequency section. + statusBucket = 13; + break; + case 505: + // HTTP Version Not Supported - The server CANNOT handle the requested + // protocol major version. + statusBucket = 14; + break; + default: + statusBucket = 15; + }; + return statusBucket; +} + +} // namespace safebrowsing +} // namespace mozilla diff --git a/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.h b/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.h new file mode 100644 index 0000000000..a0c2d181df --- /dev/null +++ b/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.h @@ -0,0 +1,31 @@ +//* -*- 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/. */ + +#ifndef UrlClassifierTelemetryUtils_h__ +#define UrlClassifierTelemetryUtils_h__ + +#include "mozilla/TypedEnumBits.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace safebrowsing { + +// We might need to expand the bucket here if telemetry shows lots of errors +// are neither connection errors nor DNS errors. +uint8_t NetworkErrorToBucket(nsresult rv); + +// Map the HTTP response code to a Telemetry bucket +uint32_t HTTPStatusToBucket(uint32_t status); + +enum UpdateTimeout { + eNoTimeout = 0, + eResponseTimeout = 1, + eDownloadTimeout = 2, +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif // UrlClassifierTelemetryUtils_h__ diff --git a/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp b/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp new file mode 100644 index 0000000000..40ea436845 --- /dev/null +++ b/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp @@ -0,0 +1,496 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "VariableLengthPrefixSet.h" +#include "nsIInputStream.h" +#include "nsUrlClassifierPrefixSet.h" +#include "nsPrintfCString.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/UniquePtr.h" +#include <algorithm> + +// MOZ_LOG=UrlClassifierPrefixSet:5 +static mozilla::LazyLogModule gUrlClassifierPrefixSetLog( + "UrlClassifierPrefixSet"); +#define LOG(args) \ + MOZ_LOG(gUrlClassifierPrefixSetLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gUrlClassifierPrefixSetLog, mozilla::LogLevel::Debug) + +namespace mozilla::safebrowsing { + +#define PREFIX_SIZE_FIXED 4 + +#ifdef DEBUG +namespace { + +template <class T> +void EnsureSorted(T* aArray) { + MOZ_ASSERT(std::is_sorted(aArray->Elements(), + aArray->Elements() + aArray->Length())); +} + +} // namespace +#endif + +NS_IMPL_ISUPPORTS(VariableLengthPrefixSet, nsIMemoryReporter) + +// This class will process prefix size between 4~32. But for 4 bytes prefixes, +// they will be passed to nsUrlClassifierPrefixSet because of better +// optimization. +VariableLengthPrefixSet::VariableLengthPrefixSet() + : mLock("VariableLengthPrefixSet.mLock"), + mFixedPrefixSet(new nsUrlClassifierPrefixSet) {} + +nsresult VariableLengthPrefixSet::Init(const nsACString& aName) { + mName = aName; + mMemoryReportPath = nsPrintfCString( + "explicit/storage/prefix-set/%s", + (!aName.IsEmpty() ? PromiseFlatCString(aName).get() : "?!")); + + RegisterWeakMemoryReporter(this); + + return mFixedPrefixSet->Init(aName); +} + +VariableLengthPrefixSet::~VariableLengthPrefixSet() { + UnregisterWeakMemoryReporter(this); +} + +nsresult VariableLengthPrefixSet::SetPrefixes(AddPrefixArray& aAddPrefixes, + AddCompleteArray& aAddCompletes) { + MutexAutoLock lock(mLock); + + // We may modify the prefix string in this function, clear this data + // before returning to ensure no one use the data after this API. + auto scopeExit = MakeScopeExit([&]() { + aAddPrefixes.Clear(); + aAddCompletes.Clear(); + }); + + // Clear old prefixSet before setting new one. + mFixedPrefixSet->SetPrefixes(nullptr, 0); + mVLPrefixSet.Clear(); + + // Build fixed-length prefix set + nsTArray<uint32_t> array; + if (!array.SetCapacity(aAddPrefixes.Length(), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (size_t i = 0; i < aAddPrefixes.Length(); i++) { + array.AppendElement(aAddPrefixes[i].PrefixHash().ToUint32()); + } + +#ifdef DEBUG + // PrefixSet requires sorted order + EnsureSorted(&array); +#endif + + nsresult rv = mFixedPrefixSet->SetPrefixes(array.Elements(), array.Length()); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef DEBUG + uint32_t size; + size = mFixedPrefixSet->SizeOfIncludingThis(moz_malloc_size_of); + LOG(("SB tree done, size = %d bytes\n", size)); +#endif + + CompletionArray completions; + for (size_t i = 0; i < aAddCompletes.Length(); i++) { + completions.AppendElement(aAddCompletes[i].CompleteHash()); + } + completions.Sort(); + + UniquePtr<nsCString> completionStr(new nsCString); + completionStr->SetCapacity(completions.Length() * COMPLETE_SIZE); + for (size_t i = 0; i < completions.Length(); i++) { + const char* buf = reinterpret_cast<const char*>(completions[i].buf); + completionStr->Append(buf, COMPLETE_SIZE); + } + mVLPrefixSet.InsertOrUpdate(COMPLETE_SIZE, std::move(completionStr)); + + return NS_OK; +} + +nsresult VariableLengthPrefixSet::SetPrefixes(PrefixStringMap& aPrefixMap) { + MutexAutoLock lock(mLock); + + // We may modify the prefix string in this function, clear this data + // before returning to ensure no one use the data after this API. + auto scopeExit = MakeScopeExit([&]() { aPrefixMap.Clear(); }); + + // Prefix size should not less than 4-bytes or greater than 32-bytes + for (const auto& key : aPrefixMap.Keys()) { + if (key < PREFIX_SIZE_FIXED || key > COMPLETE_SIZE) { + return NS_ERROR_FAILURE; + } + } + + // Clear old prefixSet before setting new one. + mFixedPrefixSet->SetPrefixes(nullptr, 0); + mVLPrefixSet.Clear(); + + // 4-bytes prefixes are handled by nsUrlClassifierPrefixSet. + nsCString* prefixes = aPrefixMap.Get(PREFIX_SIZE_FIXED); + if (prefixes) { + NS_ENSURE_TRUE(prefixes->Length() % PREFIX_SIZE_FIXED == 0, + NS_ERROR_FAILURE); + + uint32_t numPrefixes = prefixes->Length() / PREFIX_SIZE_FIXED; + + // Prefixes are lexicographically-sorted, so the interger array + // passed to nsUrlClassifierPrefixSet should also follow the same order. + // Reverse byte order in-place in Little-Endian platform. +#if MOZ_LITTLE_ENDIAN() + char* begin = prefixes->BeginWriting(); + char* end = prefixes->EndWriting(); + + while (begin != end) { + uint32_t* p = reinterpret_cast<uint32_t*>(begin); + *p = BigEndian::readUint32(begin); + begin += sizeof(uint32_t); + } +#endif + const uint32_t* arrayPtr = + reinterpret_cast<const uint32_t*>(prefixes->BeginReading()); + + nsresult rv = mFixedPrefixSet->SetPrefixes(arrayPtr, numPrefixes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // 5~32 bytes prefixes are stored in mVLPrefixSet. + for (const auto& entry : aPrefixMap) { + // Skip 4bytes prefixes because it is already stored in mFixedPrefixSet. + if (entry.GetKey() == PREFIX_SIZE_FIXED) { + continue; + } + + mVLPrefixSet.InsertOrUpdate(entry.GetKey(), + MakeUnique<nsCString>(*entry.GetData())); + } + + return NS_OK; +} + +nsresult VariableLengthPrefixSet::GetPrefixes(PrefixStringMap& aPrefixMap) { + MutexAutoLock lock(mLock); + + // 4-bytes prefixes are handled by nsUrlClassifierPrefixSet. + FallibleTArray<uint32_t> array; + nsresult rv = mFixedPrefixSet->GetPrefixesNative(array); + NS_ENSURE_SUCCESS(rv, rv); + + size_t count = array.Length(); + if (count) { + UniquePtr<nsCString> prefixes(new nsCString()); + if (!prefixes->SetLength(PREFIX_SIZE_FIXED * count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Writing integer array to character array + uint32_t* begin = reinterpret_cast<uint32_t*>(prefixes->BeginWriting()); + for (uint32_t i = 0; i < count; i++) { + begin[i] = NativeEndian::swapToBigEndian(array[i]); + } + + aPrefixMap.InsertOrUpdate(PREFIX_SIZE_FIXED, std::move(prefixes)); + } + + // Copy variable-length prefix set + for (const auto& entry : mVLPrefixSet) { + aPrefixMap.InsertOrUpdate(entry.GetKey(), + MakeUnique<nsCString>(*entry.GetData())); + } + + return NS_OK; +} + +// This is used by V2 protocol which prefixes are either 4-bytes or 32-bytes. +nsresult VariableLengthPrefixSet::GetFixedLengthPrefixes( + FallibleTArray<uint32_t>* aPrefixes, + FallibleTArray<nsCString>* aCompletes) { + MOZ_ASSERT(aPrefixes || aCompletes); + MOZ_ASSERT_IF(aPrefixes, aPrefixes->IsEmpty()); + MOZ_ASSERT_IF(aCompletes, aCompletes->IsEmpty()); + + if (aPrefixes) { + nsresult rv = mFixedPrefixSet->GetPrefixesNative(*aPrefixes); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (aCompletes) { + nsCString* completes = mVLPrefixSet.Get(COMPLETE_SIZE); + if (completes) { + uint32_t count = completes->Length() / COMPLETE_SIZE; + if (!aCompletes->SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < count; i++) { + // SetCapacity was just called, these cannot fail. + (void)aCompletes->AppendElement( + Substring(*completes, i * COMPLETE_SIZE, COMPLETE_SIZE), fallible); + } + } + } + + return NS_OK; +} + +nsresult VariableLengthPrefixSet::GetFixedLengthPrefixByIndex( + uint32_t aIndex, uint32_t* aOutPrefix) const { + NS_ENSURE_ARG_POINTER(aOutPrefix); + + return mFixedPrefixSet->GetPrefixByIndex(aIndex, aOutPrefix); +} + +// It should never be the case that more than one hash prefixes match a given +// full hash. However, if that happens, this method returns any one of them. +// It does not guarantee which one of those will be returned. +nsresult VariableLengthPrefixSet::Matches(uint32_t aPrefix, + const nsACString& aFullHash, + uint32_t* aLength) const { + MutexAutoLock lock(mLock); + + // Only allow full-length hash to check if match any of the prefix + MOZ_ASSERT(aFullHash.Length() == COMPLETE_SIZE); + NS_ENSURE_ARG_POINTER(aLength); + + *aLength = 0; + + // Check if it matches 4-bytes prefixSet first + bool found = false; + nsresult rv = mFixedPrefixSet->Contains(aPrefix, &found); + NS_ENSURE_SUCCESS(rv, rv); + + if (found) { + *aLength = PREFIX_SIZE_FIXED; + return NS_OK; + } + + for (const auto& entry : mVLPrefixSet) { + if (BinarySearch(aFullHash, *entry.GetData(), entry.GetKey())) { + *aLength = entry.GetKey(); + MOZ_ASSERT(*aLength > 4); + return NS_OK; + } + } + + return NS_OK; +} + +nsresult VariableLengthPrefixSet::IsEmpty(bool* aEmpty) const { + MutexAutoLock lock(mLock); + + NS_ENSURE_ARG_POINTER(aEmpty); + + mFixedPrefixSet->IsEmpty(aEmpty); + *aEmpty = *aEmpty && mVLPrefixSet.IsEmpty(); + + return NS_OK; +} + +nsresult VariableLengthPrefixSet::LoadPrefixes(nsCOMPtr<nsIInputStream>& in) { + MutexAutoLock lock(mLock); + + // First read prefixes from fixed-length prefix set + nsresult rv = mFixedPrefixSet->LoadPrefixes(in); + NS_ENSURE_SUCCESS(rv, rv); + + // Then read prefixes from variable-length prefix set + uint32_t magic; + uint32_t read; + + rv = in->Read(reinterpret_cast<char*>(&magic), sizeof(uint32_t), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE); + + if (magic != PREFIXSET_VERSION_MAGIC) { + LOG(("[%s] Version magic mismatch, not loading", mName.get())); + return NS_ERROR_FILE_CORRUPTED; + } + + mVLPrefixSet.Clear(); + + uint32_t count; + rv = in->Read(reinterpret_cast<char*>(&count), sizeof(uint32_t), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE); + + uint32_t totalPrefixes = 0; + for (; count > 0; count--) { + uint8_t prefixSize; + rv = in->Read(reinterpret_cast<char*>(&prefixSize), sizeof(uint8_t), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint8_t), NS_ERROR_FAILURE); + + if (prefixSize < PREFIX_SIZE || prefixSize > COMPLETE_SIZE) { + return NS_ERROR_FILE_CORRUPTED; + } + + uint32_t stringLength; + rv = in->Read(reinterpret_cast<char*>(&stringLength), sizeof(uint32_t), + &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE); + + NS_ENSURE_TRUE(stringLength % prefixSize == 0, NS_ERROR_FILE_CORRUPTED); + uint32_t prefixCount = stringLength / prefixSize; + + UniquePtr<nsCString> vlPrefixes(new nsCString()); + if (!vlPrefixes->SetLength(stringLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = in->Read(reinterpret_cast<char*>(vlPrefixes->BeginWriting()), + stringLength, &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == stringLength, NS_ERROR_FAILURE); + + mVLPrefixSet.InsertOrUpdate(prefixSize, std::move(vlPrefixes)); + totalPrefixes += prefixCount; + LOG(("[%s] Loaded %u %u-byte prefixes", mName.get(), prefixCount, + prefixSize)); + } + + LOG(("[%s] Loading VLPrefixSet successful (%u total prefixes)", mName.get(), + totalPrefixes)); + + return NS_OK; +} + +uint32_t VariableLengthPrefixSet::CalculatePreallocateSize() const { + uint32_t fileSize = 0; + + // Size of fixed length prefix set. + fileSize += mFixedPrefixSet->CalculatePreallocateSize(); + + // Size of variable length prefix set. + // Store how many prefix string. + fileSize += sizeof(uint32_t); + + for (const auto& data : mVLPrefixSet.Values()) { + // Store prefix size, prefix string length, and prefix string. + fileSize += sizeof(uint8_t); + fileSize += sizeof(uint32_t); + fileSize += data->Length(); + } + return fileSize; +} + +uint32_t VariableLengthPrefixSet::FixedLengthPrefixLength() const { + return mFixedPrefixSet->Length(); +} + +nsresult VariableLengthPrefixSet::WritePrefixes( + nsCOMPtr<nsIOutputStream>& out) const { + MutexAutoLock lock(mLock); + + // First, write fixed length prefix set + nsresult rv = mFixedPrefixSet->WritePrefixes(out); + NS_ENSURE_SUCCESS(rv, rv); + + // Then, write variable length prefix set + uint32_t written; + uint32_t writelen = sizeof(uint32_t); + uint32_t magic = PREFIXSET_VERSION_MAGIC; + rv = out->Write(reinterpret_cast<char*>(&magic), writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + uint32_t count = mVLPrefixSet.Count(); + rv = out->Write(reinterpret_cast<char*>(&count), writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + // Store PrefixSize, Length of Prefix String and then Prefix String + for (const auto& entry : mVLPrefixSet) { + const nsCString& vlPrefixes = *entry.GetData(); + + uint8_t prefixSize = entry.GetKey(); + writelen = sizeof(uint8_t); + rv = out->Write(reinterpret_cast<char*>(&prefixSize), writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + uint32_t stringLength = vlPrefixes.Length(); + writelen = sizeof(uint32_t); + rv = out->Write(reinterpret_cast<char*>(&stringLength), writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + rv = out->Write(const_cast<char*>(vlPrefixes.BeginReading()), stringLength, + &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(stringLength == written, NS_ERROR_FAILURE); + } + + return NS_OK; +} + +bool VariableLengthPrefixSet::BinarySearch(const nsACString& aFullHash, + const nsACString& aPrefixes, + uint32_t aPrefixSize) const { + const char* fullhash = aFullHash.BeginReading(); + const char* prefixes = aPrefixes.BeginReading(); + int32_t begin = 0, end = aPrefixes.Length() / aPrefixSize; + + while (end > begin) { + int32_t mid = (begin + end) >> 1; + int cmp = memcmp(fullhash, prefixes + mid * aPrefixSize, aPrefixSize); + if (cmp < 0) { + end = mid; + } else if (cmp > 0) { + begin = mid + 1; + } else { + return true; + } + } + return false; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(UrlClassifierMallocSizeOf) + +NS_IMETHODIMP +VariableLengthPrefixSet::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_ASSERT(NS_IsMainThread()); + + size_t amount = SizeOfIncludingThis(UrlClassifierMallocSizeOf); + + return aHandleReport->Callback( + ""_ns, mMemoryReportPath, KIND_HEAP, UNITS_BYTES, amount, + nsLiteralCString("Memory used by the variable-length prefix set for a " + "URL classifier."), + aData); +} + +size_t VariableLengthPrefixSet::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + MutexAutoLock lock(mLock); + + size_t n = 0; + n += aMallocSizeOf(this); + + n += mFixedPrefixSet->SizeOfIncludingThis(moz_malloc_size_of) - + aMallocSizeOf(mFixedPrefixSet); + + n += mVLPrefixSet.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mVLPrefixSet.Values()) { + n += data->SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return n; +} + +} // namespace mozilla::safebrowsing diff --git a/toolkit/components/url-classifier/VariableLengthPrefixSet.h b/toolkit/components/url-classifier/VariableLengthPrefixSet.h new file mode 100644 index 0000000000..2593267163 --- /dev/null +++ b/toolkit/components/url-classifier/VariableLengthPrefixSet.h @@ -0,0 +1,73 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef VariableLengthPrefixSet_h +#define VariableLengthPrefixSet_h + +#include "nsISupports.h" +#include "nsIMemoryReporter.h" +#include "Entries.h" +#include "nsTArray.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" + +class nsUrlClassifierPrefixSet; + +namespace mozilla { +namespace safebrowsing { + +class VariableLengthPrefixSet final : public nsIMemoryReporter { + public: + VariableLengthPrefixSet(); + + nsresult Init(const nsACString& aName); + nsresult SetPrefixes(mozilla::safebrowsing::PrefixStringMap& aPrefixMap); + nsresult SetPrefixes(AddPrefixArray& aAddPrefixes, + AddCompleteArray& aAddCompletes); + nsresult GetPrefixes(mozilla::safebrowsing::PrefixStringMap& aPrefixMap); + nsresult GetFixedLengthPrefixes(FallibleTArray<uint32_t>* aPrefixes, + FallibleTArray<nsCString>* aCompletes); + nsresult GetFixedLengthPrefixByIndex(uint32_t aIndex, + uint32_t* aOutPrefix) const; + nsresult Matches(uint32_t aPrefix, const nsACString& aFullHash, + uint32_t* aLength) const; + nsresult IsEmpty(bool* aEmpty) const; + + nsresult WritePrefixes(nsCOMPtr<nsIOutputStream>& out) const; + nsresult LoadPrefixes(nsCOMPtr<nsIInputStream>& in); + uint32_t CalculatePreallocateSize() const; + uint32_t FixedLengthPrefixLength() const; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + private: + virtual ~VariableLengthPrefixSet(); + + static const uint32_t PREFIXSET_VERSION_MAGIC = 1; + + bool BinarySearch(const nsACString& aFullHash, const nsACString& aPrefixes, + uint32_t aPrefixSize) const; + + // Lock to prevent races between the url-classifier thread (which does most + // of the operations) and the main thread (which does memory reporting). + // It should be held for all operations between Init() and destruction that + // touch this class's data members. + mutable mozilla::Mutex mLock MOZ_UNANNOTATED; + + const RefPtr<nsUrlClassifierPrefixSet> mFixedPrefixSet; + mozilla::safebrowsing::PrefixStringMap mVLPrefixSet; + + nsCString mName; + nsCString mMemoryReportPath; +}; + +} // namespace safebrowsing +} // namespace mozilla + +#endif diff --git a/toolkit/components/url-classifier/chromium/README.txt b/toolkit/components/url-classifier/chromium/README.txt new file mode 100644 index 0000000000..e4d4285804 --- /dev/null +++ b/toolkit/components/url-classifier/chromium/README.txt @@ -0,0 +1,41 @@ +# Overview + +'safebrowsing.proto' is modified from [1] with the following changes: + +- Added "package mozilla.safebrowsing;" +- Added more threatHit information + +################################## + // Client-reported identification. + optional ClientInfo client_info = 5; + + // Details about the user that encountered the threat. + message UserInfo { + // The UN M.49 region code associated with the user's location. + optional string region_code = 1; + + // Unique ID stable over a week or two + optional bytes user_id = 2; + } + + // Details about the user that encountered the threat. + optional UserInfo user_info = 6; +#################################### + +to avoid naming pollution. We use this source file along with protobuf compiler (protoc) to generate safebrowsing.pb.h/cc for safebrowsing v4 update and hash completion. The current generated files are compiled by protoc 2.6.1 since the protobuf library in gecko is not upgraded to 3.0 yet. + +# Update + +If you want to update to the latest upstream version, + +1. Checkout the latest one in [2] +2. Use protoc to generate safebrowsing.pb.h and safebrowsing.pb.cc. For example, + +$ protoc -I=. --cpp_out="../protobuf/" safebrowsing.proto + +(Note that we should use protoc v2.6.1 [3] to compile. You can find the compiled protoc in [4] if you don't have one.) + +[1] https://chromium.googlesource.com/chromium/src.git/+/9c4485f1ce7cac7ae82f7a4ae36ccc663afe806c/components/safe_browsing_db/safebrowsing.proto +[2] https://chromium.googlesource.com/chromium/src.git/+/master/components/safe_browsing_db/safebrowsing.proto +[3] https://github.com/google/protobuf/releases/tag/v2.6.1 +[4] https://repo1.maven.org/maven2/com/google/protobuf/protoc diff --git a/toolkit/components/url-classifier/chromium/safebrowsing.pb.cc b/toolkit/components/url-classifier/chromium/safebrowsing.pb.cc new file mode 100644 index 0000000000..ec0994bfd6 --- /dev/null +++ b/toolkit/components/url-classifier/chromium/safebrowsing.pb.cc @@ -0,0 +1,8925 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: safebrowsing.proto + +#include "safebrowsing.pb.h" + +#include <algorithm> + +#include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/extension_set.h> +#include <google/protobuf/wire_format_lite.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> +// @@protoc_insertion_point(includes) +#include <google/protobuf/port_def.inc> + +PROTOBUF_PRAGMA_INIT_SEG + +namespace _pb = ::PROTOBUF_NAMESPACE_ID; +namespace _pbi = _pb::internal; + +namespace mozilla { +namespace safebrowsing { +PROTOBUF_CONSTEXPR ThreatInfo::ThreatInfo( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.threat_types_)*/{} + , /*decltype(_impl_.platform_types_)*/{} + , /*decltype(_impl_.threat_entries_)*/{} + , /*decltype(_impl_.threat_entry_types_)*/{} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ThreatInfoDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatInfoDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatInfoDefaultTypeInternal() {} + union { + ThreatInfo _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatInfoDefaultTypeInternal _ThreatInfo_default_instance_; +PROTOBUF_CONSTEXPR ThreatMatch::ThreatMatch( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.threat_)*/nullptr + , /*decltype(_impl_.threat_entry_metadata_)*/nullptr + , /*decltype(_impl_.cache_duration_)*/nullptr + , /*decltype(_impl_.threat_type_)*/0 + , /*decltype(_impl_.platform_type_)*/0 + , /*decltype(_impl_.threat_entry_type_)*/0} {} +struct ThreatMatchDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatMatchDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatMatchDefaultTypeInternal() {} + union { + ThreatMatch _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatMatchDefaultTypeInternal _ThreatMatch_default_instance_; +PROTOBUF_CONSTEXPR FindThreatMatchesRequest::FindThreatMatchesRequest( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.client_)*/nullptr + , /*decltype(_impl_.threat_info_)*/nullptr} {} +struct FindThreatMatchesRequestDefaultTypeInternal { + PROTOBUF_CONSTEXPR FindThreatMatchesRequestDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FindThreatMatchesRequestDefaultTypeInternal() {} + union { + FindThreatMatchesRequest _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FindThreatMatchesRequestDefaultTypeInternal _FindThreatMatchesRequest_default_instance_; +PROTOBUF_CONSTEXPR FindThreatMatchesResponse::FindThreatMatchesResponse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.matches_)*/{} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct FindThreatMatchesResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR FindThreatMatchesResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FindThreatMatchesResponseDefaultTypeInternal() {} + union { + FindThreatMatchesResponse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FindThreatMatchesResponseDefaultTypeInternal _FindThreatMatchesResponse_default_instance_; +PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.supported_compressions_)*/{} + , /*decltype(_impl_.region_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.max_update_entries_)*/0 + , /*decltype(_impl_.max_database_entries_)*/0} {} +struct FetchThreatListUpdatesRequest_ListUpdateRequest_ConstraintsDefaultTypeInternal { + PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest_ListUpdateRequest_ConstraintsDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FetchThreatListUpdatesRequest_ListUpdateRequest_ConstraintsDefaultTypeInternal() {} + union { + FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FetchThreatListUpdatesRequest_ListUpdateRequest_ConstraintsDefaultTypeInternal _FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints_default_instance_; +PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest_ListUpdateRequest::FetchThreatListUpdatesRequest_ListUpdateRequest( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.state_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.constraints_)*/nullptr + , /*decltype(_impl_.threat_type_)*/0 + , /*decltype(_impl_.platform_type_)*/0 + , /*decltype(_impl_.threat_entry_type_)*/0} {} +struct FetchThreatListUpdatesRequest_ListUpdateRequestDefaultTypeInternal { + PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest_ListUpdateRequestDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FetchThreatListUpdatesRequest_ListUpdateRequestDefaultTypeInternal() {} + union { + FetchThreatListUpdatesRequest_ListUpdateRequest _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FetchThreatListUpdatesRequest_ListUpdateRequestDefaultTypeInternal _FetchThreatListUpdatesRequest_ListUpdateRequest_default_instance_; +PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest::FetchThreatListUpdatesRequest( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.list_update_requests_)*/{} + , /*decltype(_impl_.client_)*/nullptr + , /*decltype(_impl_.chrome_client_info_)*/nullptr} {} +struct FetchThreatListUpdatesRequestDefaultTypeInternal { + PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequestDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FetchThreatListUpdatesRequestDefaultTypeInternal() {} + union { + FetchThreatListUpdatesRequest _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FetchThreatListUpdatesRequestDefaultTypeInternal _FetchThreatListUpdatesRequest_default_instance_; +PROTOBUF_CONSTEXPR FetchThreatListUpdatesResponse_ListUpdateResponse::FetchThreatListUpdatesResponse_ListUpdateResponse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.additions_)*/{} + , /*decltype(_impl_.removals_)*/{} + , /*decltype(_impl_.new_client_state_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.checksum_)*/nullptr + , /*decltype(_impl_.threat_type_)*/0 + , /*decltype(_impl_.threat_entry_type_)*/0 + , /*decltype(_impl_.platform_type_)*/0 + , /*decltype(_impl_.response_type_)*/0} {} +struct FetchThreatListUpdatesResponse_ListUpdateResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR FetchThreatListUpdatesResponse_ListUpdateResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FetchThreatListUpdatesResponse_ListUpdateResponseDefaultTypeInternal() {} + union { + FetchThreatListUpdatesResponse_ListUpdateResponse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FetchThreatListUpdatesResponse_ListUpdateResponseDefaultTypeInternal _FetchThreatListUpdatesResponse_ListUpdateResponse_default_instance_; +PROTOBUF_CONSTEXPR FetchThreatListUpdatesResponse::FetchThreatListUpdatesResponse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.list_update_responses_)*/{} + , /*decltype(_impl_.minimum_wait_duration_)*/nullptr} {} +struct FetchThreatListUpdatesResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR FetchThreatListUpdatesResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FetchThreatListUpdatesResponseDefaultTypeInternal() {} + union { + FetchThreatListUpdatesResponse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FetchThreatListUpdatesResponseDefaultTypeInternal _FetchThreatListUpdatesResponse_default_instance_; +PROTOBUF_CONSTEXPR FindFullHashesRequest::FindFullHashesRequest( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.client_states_)*/{} + , /*decltype(_impl_.client_)*/nullptr + , /*decltype(_impl_.threat_info_)*/nullptr} {} +struct FindFullHashesRequestDefaultTypeInternal { + PROTOBUF_CONSTEXPR FindFullHashesRequestDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FindFullHashesRequestDefaultTypeInternal() {} + union { + FindFullHashesRequest _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FindFullHashesRequestDefaultTypeInternal _FindFullHashesRequest_default_instance_; +PROTOBUF_CONSTEXPR FindFullHashesResponse::FindFullHashesResponse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.matches_)*/{} + , /*decltype(_impl_.minimum_wait_duration_)*/nullptr + , /*decltype(_impl_.negative_cache_duration_)*/nullptr} {} +struct FindFullHashesResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR FindFullHashesResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~FindFullHashesResponseDefaultTypeInternal() {} + union { + FindFullHashesResponse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 FindFullHashesResponseDefaultTypeInternal _FindFullHashesResponse_default_instance_; +PROTOBUF_CONSTEXPR ThreatHit_ThreatSource::ThreatHit_ThreatSource( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.url_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.remote_ip_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.referrer_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.type_)*/0} {} +struct ThreatHit_ThreatSourceDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatHit_ThreatSourceDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatHit_ThreatSourceDefaultTypeInternal() {} + union { + ThreatHit_ThreatSource _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatHit_ThreatSourceDefaultTypeInternal _ThreatHit_ThreatSource_default_instance_; +PROTOBUF_CONSTEXPR ThreatHit_UserInfo::ThreatHit_UserInfo( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.region_code_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.user_id_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct ThreatHit_UserInfoDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatHit_UserInfoDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatHit_UserInfoDefaultTypeInternal() {} + union { + ThreatHit_UserInfo _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatHit_UserInfoDefaultTypeInternal _ThreatHit_UserInfo_default_instance_; +PROTOBUF_CONSTEXPR ThreatHit::ThreatHit( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.resources_)*/{} + , /*decltype(_impl_.entry_)*/nullptr + , /*decltype(_impl_.client_info_)*/nullptr + , /*decltype(_impl_.user_info_)*/nullptr + , /*decltype(_impl_.threat_type_)*/0 + , /*decltype(_impl_.platform_type_)*/0} {} +struct ThreatHitDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatHitDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatHitDefaultTypeInternal() {} + union { + ThreatHit _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatHitDefaultTypeInternal _ThreatHit_default_instance_; +PROTOBUF_CONSTEXPR ClientInfo::ClientInfo( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.client_id_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.client_version_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct ClientInfoDefaultTypeInternal { + PROTOBUF_CONSTEXPR ClientInfoDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ClientInfoDefaultTypeInternal() {} + union { + ClientInfo _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ClientInfoDefaultTypeInternal _ClientInfo_default_instance_; +PROTOBUF_CONSTEXPR ChromeClientInfo::ChromeClientInfo( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.reporting_population_)*/0} {} +struct ChromeClientInfoDefaultTypeInternal { + PROTOBUF_CONSTEXPR ChromeClientInfoDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ChromeClientInfoDefaultTypeInternal() {} + union { + ChromeClientInfo _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ChromeClientInfoDefaultTypeInternal _ChromeClientInfo_default_instance_; +PROTOBUF_CONSTEXPR Checksum::Checksum( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.sha256_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct ChecksumDefaultTypeInternal { + PROTOBUF_CONSTEXPR ChecksumDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ChecksumDefaultTypeInternal() {} + union { + Checksum _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ChecksumDefaultTypeInternal _Checksum_default_instance_; +PROTOBUF_CONSTEXPR ThreatEntry::ThreatEntry( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.hash_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.url_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct ThreatEntryDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatEntryDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatEntryDefaultTypeInternal() {} + union { + ThreatEntry _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatEntryDefaultTypeInternal _ThreatEntry_default_instance_; +PROTOBUF_CONSTEXPR ThreatEntrySet::ThreatEntrySet( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.raw_hashes_)*/nullptr + , /*decltype(_impl_.raw_indices_)*/nullptr + , /*decltype(_impl_.rice_hashes_)*/nullptr + , /*decltype(_impl_.rice_indices_)*/nullptr + , /*decltype(_impl_.compression_type_)*/0} {} +struct ThreatEntrySetDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatEntrySetDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatEntrySetDefaultTypeInternal() {} + union { + ThreatEntrySet _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatEntrySetDefaultTypeInternal _ThreatEntrySet_default_instance_; +PROTOBUF_CONSTEXPR RawIndices::RawIndices( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.indices_)*/{} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct RawIndicesDefaultTypeInternal { + PROTOBUF_CONSTEXPR RawIndicesDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RawIndicesDefaultTypeInternal() {} + union { + RawIndices _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RawIndicesDefaultTypeInternal _RawIndices_default_instance_; +PROTOBUF_CONSTEXPR RawHashes::RawHashes( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.raw_hashes_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.prefix_size_)*/0} {} +struct RawHashesDefaultTypeInternal { + PROTOBUF_CONSTEXPR RawHashesDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RawHashesDefaultTypeInternal() {} + union { + RawHashes _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RawHashesDefaultTypeInternal _RawHashes_default_instance_; +PROTOBUF_CONSTEXPR RiceDeltaEncoding::RiceDeltaEncoding( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.encoded_data_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.first_value_)*/int64_t{0} + , /*decltype(_impl_.rice_parameter_)*/0 + , /*decltype(_impl_.num_entries_)*/0} {} +struct RiceDeltaEncodingDefaultTypeInternal { + PROTOBUF_CONSTEXPR RiceDeltaEncodingDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~RiceDeltaEncodingDefaultTypeInternal() {} + union { + RiceDeltaEncoding _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 RiceDeltaEncodingDefaultTypeInternal _RiceDeltaEncoding_default_instance_; +PROTOBUF_CONSTEXPR ThreatEntryMetadata_MetadataEntry::ThreatEntryMetadata_MetadataEntry( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.key_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.value_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct ThreatEntryMetadata_MetadataEntryDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatEntryMetadata_MetadataEntryDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatEntryMetadata_MetadataEntryDefaultTypeInternal() {} + union { + ThreatEntryMetadata_MetadataEntry _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatEntryMetadata_MetadataEntryDefaultTypeInternal _ThreatEntryMetadata_MetadataEntry_default_instance_; +PROTOBUF_CONSTEXPR ThreatEntryMetadata::ThreatEntryMetadata( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.entries_)*/{} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ThreatEntryMetadataDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatEntryMetadataDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatEntryMetadataDefaultTypeInternal() {} + union { + ThreatEntryMetadata _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatEntryMetadataDefaultTypeInternal _ThreatEntryMetadata_default_instance_; +PROTOBUF_CONSTEXPR ThreatListDescriptor::ThreatListDescriptor( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.threat_type_)*/0 + , /*decltype(_impl_.platform_type_)*/0 + , /*decltype(_impl_.threat_entry_type_)*/0} {} +struct ThreatListDescriptorDefaultTypeInternal { + PROTOBUF_CONSTEXPR ThreatListDescriptorDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ThreatListDescriptorDefaultTypeInternal() {} + union { + ThreatListDescriptor _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ThreatListDescriptorDefaultTypeInternal _ThreatListDescriptor_default_instance_; +PROTOBUF_CONSTEXPR ListThreatListsResponse::ListThreatListsResponse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.threat_lists_)*/{} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ListThreatListsResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR ListThreatListsResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ListThreatListsResponseDefaultTypeInternal() {} + union { + ListThreatListsResponse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ListThreatListsResponseDefaultTypeInternal _ListThreatListsResponse_default_instance_; +PROTOBUF_CONSTEXPR Duration::Duration( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.seconds_)*/int64_t{0} + , /*decltype(_impl_.nanos_)*/0} {} +struct DurationDefaultTypeInternal { + PROTOBUF_CONSTEXPR DurationDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DurationDefaultTypeInternal() {} + union { + Duration _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DurationDefaultTypeInternal _Duration_default_instance_; +} // namespace safebrowsing +} // namespace mozilla +namespace mozilla { +namespace safebrowsing { +bool FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_strings[3] = {}; + +static const char FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_names[] = + "FULL_UPDATE" + "PARTIAL_UPDATE" + "RESPONSE_TYPE_UNSPECIFIED"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries[] = { + { {FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_names + 0, 11}, 2 }, + { {FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_names + 11, 14}, 1 }, + { {FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_names + 25, 25}, 0 }, +}; + +static const int FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries_by_number[] = { + 2, // 0 -> RESPONSE_TYPE_UNSPECIFIED + 1, // 1 -> PARTIAL_UPDATE + 0, // 2 -> FULL_UPDATE +}; + +const std::string& FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Name( + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries, + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries_by_number, + 3, FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries, + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries_by_number, + 3, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_strings[idx].get(); +} +bool FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_entries, 3, name, &int_value); + if (success) { + *value = static_cast<FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType>(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::RESPONSE_TYPE_UNSPECIFIED; +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::PARTIAL_UPDATE; +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::FULL_UPDATE; +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::ResponseType_MIN; +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::ResponseType_MAX; +constexpr int FetchThreatListUpdatesResponse_ListUpdateResponse::ResponseType_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool ThreatHit_ThreatSourceType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> ThreatHit_ThreatSourceType_strings[5] = {}; + +static const char ThreatHit_ThreatSourceType_names[] = + "MATCHING_URL" + "TAB_REDIRECT" + "TAB_RESOURCE" + "TAB_URL" + "THREAT_SOURCE_TYPE_UNSPECIFIED"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ThreatHit_ThreatSourceType_entries[] = { + { {ThreatHit_ThreatSourceType_names + 0, 12}, 1 }, + { {ThreatHit_ThreatSourceType_names + 12, 12}, 3 }, + { {ThreatHit_ThreatSourceType_names + 24, 12}, 4 }, + { {ThreatHit_ThreatSourceType_names + 36, 7}, 2 }, + { {ThreatHit_ThreatSourceType_names + 43, 30}, 0 }, +}; + +static const int ThreatHit_ThreatSourceType_entries_by_number[] = { + 4, // 0 -> THREAT_SOURCE_TYPE_UNSPECIFIED + 0, // 1 -> MATCHING_URL + 3, // 2 -> TAB_URL + 1, // 3 -> TAB_REDIRECT + 2, // 4 -> TAB_RESOURCE +}; + +const std::string& ThreatHit_ThreatSourceType_Name( + ThreatHit_ThreatSourceType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + ThreatHit_ThreatSourceType_entries, + ThreatHit_ThreatSourceType_entries_by_number, + 5, ThreatHit_ThreatSourceType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + ThreatHit_ThreatSourceType_entries, + ThreatHit_ThreatSourceType_entries_by_number, + 5, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + ThreatHit_ThreatSourceType_strings[idx].get(); +} +bool ThreatHit_ThreatSourceType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ThreatHit_ThreatSourceType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + ThreatHit_ThreatSourceType_entries, 5, name, &int_value); + if (success) { + *value = static_cast<ThreatHit_ThreatSourceType>(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr ThreatHit_ThreatSourceType ThreatHit::THREAT_SOURCE_TYPE_UNSPECIFIED; +constexpr ThreatHit_ThreatSourceType ThreatHit::MATCHING_URL; +constexpr ThreatHit_ThreatSourceType ThreatHit::TAB_URL; +constexpr ThreatHit_ThreatSourceType ThreatHit::TAB_REDIRECT; +constexpr ThreatHit_ThreatSourceType ThreatHit::TAB_RESOURCE; +constexpr ThreatHit_ThreatSourceType ThreatHit::ThreatSourceType_MIN; +constexpr ThreatHit_ThreatSourceType ThreatHit::ThreatSourceType_MAX; +constexpr int ThreatHit::ThreatSourceType_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool ChromeClientInfo_SafeBrowsingReportingPopulation_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> ChromeClientInfo_SafeBrowsingReportingPopulation_strings[4] = {}; + +static const char ChromeClientInfo_SafeBrowsingReportingPopulation_names[] = + "EXTENDED" + "OPT_OUT" + "SCOUT" + "UNSPECIFIED"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ChromeClientInfo_SafeBrowsingReportingPopulation_entries[] = { + { {ChromeClientInfo_SafeBrowsingReportingPopulation_names + 0, 8}, 2 }, + { {ChromeClientInfo_SafeBrowsingReportingPopulation_names + 8, 7}, 1 }, + { {ChromeClientInfo_SafeBrowsingReportingPopulation_names + 15, 5}, 3 }, + { {ChromeClientInfo_SafeBrowsingReportingPopulation_names + 20, 11}, 0 }, +}; + +static const int ChromeClientInfo_SafeBrowsingReportingPopulation_entries_by_number[] = { + 3, // 0 -> UNSPECIFIED + 1, // 1 -> OPT_OUT + 0, // 2 -> EXTENDED + 2, // 3 -> SCOUT +}; + +const std::string& ChromeClientInfo_SafeBrowsingReportingPopulation_Name( + ChromeClientInfo_SafeBrowsingReportingPopulation value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + ChromeClientInfo_SafeBrowsingReportingPopulation_entries, + ChromeClientInfo_SafeBrowsingReportingPopulation_entries_by_number, + 4, ChromeClientInfo_SafeBrowsingReportingPopulation_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + ChromeClientInfo_SafeBrowsingReportingPopulation_entries, + ChromeClientInfo_SafeBrowsingReportingPopulation_entries_by_number, + 4, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + ChromeClientInfo_SafeBrowsingReportingPopulation_strings[idx].get(); +} +bool ChromeClientInfo_SafeBrowsingReportingPopulation_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ChromeClientInfo_SafeBrowsingReportingPopulation* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + ChromeClientInfo_SafeBrowsingReportingPopulation_entries, 4, name, &int_value); + if (success) { + *value = static_cast<ChromeClientInfo_SafeBrowsingReportingPopulation>(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::UNSPECIFIED; +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::OPT_OUT; +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::EXTENDED; +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::SCOUT; +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::SafeBrowsingReportingPopulation_MIN; +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::SafeBrowsingReportingPopulation_MAX; +constexpr int ChromeClientInfo::SafeBrowsingReportingPopulation_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool ThreatType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 13: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> ThreatType_strings[12] = {}; + +static const char ThreatType_names[] = + "API_ABUSE" + "CLIENT_INCIDENT" + "CSD_DOWNLOAD_WHITELIST" + "CSD_WHITELIST" + "MALICIOUS_BINARY" + "MALWARE_THREAT" + "POTENTIALLY_HARMFUL_APPLICATION" + "SOCIAL_ENGINEERING" + "SOCIAL_ENGINEERING_PUBLIC" + "SUBRESOURCE_FILTER" + "THREAT_TYPE_UNSPECIFIED" + "UNWANTED_SOFTWARE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ThreatType_entries[] = { + { {ThreatType_names + 0, 9}, 6 }, + { {ThreatType_names + 9, 15}, 10 }, + { {ThreatType_names + 24, 22}, 9 }, + { {ThreatType_names + 46, 13}, 8 }, + { {ThreatType_names + 59, 16}, 7 }, + { {ThreatType_names + 75, 14}, 1 }, + { {ThreatType_names + 89, 31}, 4 }, + { {ThreatType_names + 120, 18}, 5 }, + { {ThreatType_names + 138, 25}, 2 }, + { {ThreatType_names + 163, 18}, 13 }, + { {ThreatType_names + 181, 23}, 0 }, + { {ThreatType_names + 204, 17}, 3 }, +}; + +static const int ThreatType_entries_by_number[] = { + 10, // 0 -> THREAT_TYPE_UNSPECIFIED + 5, // 1 -> MALWARE_THREAT + 8, // 2 -> SOCIAL_ENGINEERING_PUBLIC + 11, // 3 -> UNWANTED_SOFTWARE + 6, // 4 -> POTENTIALLY_HARMFUL_APPLICATION + 7, // 5 -> SOCIAL_ENGINEERING + 0, // 6 -> API_ABUSE + 4, // 7 -> MALICIOUS_BINARY + 3, // 8 -> CSD_WHITELIST + 2, // 9 -> CSD_DOWNLOAD_WHITELIST + 1, // 10 -> CLIENT_INCIDENT + 9, // 13 -> SUBRESOURCE_FILTER +}; + +const std::string& ThreatType_Name( + ThreatType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + ThreatType_entries, + ThreatType_entries_by_number, + 12, ThreatType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + ThreatType_entries, + ThreatType_entries_by_number, + 12, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + ThreatType_strings[idx].get(); +} +bool ThreatType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ThreatType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + ThreatType_entries, 12, name, &int_value); + if (success) { + *value = static_cast<ThreatType>(int_value); + } + return success; +} +bool PlatformType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> PlatformType_strings[9] = {}; + +static const char PlatformType_names[] = + "ALL_PLATFORMS" + "ANDROID_PLATFORM" + "ANY_PLATFORM" + "CHROME_PLATFORM" + "IOS_PLATFORM" + "LINUX_PLATFORM" + "OSX_PLATFORM" + "PLATFORM_TYPE_UNSPECIFIED" + "WINDOWS_PLATFORM"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry PlatformType_entries[] = { + { {PlatformType_names + 0, 13}, 7 }, + { {PlatformType_names + 13, 16}, 3 }, + { {PlatformType_names + 29, 12}, 6 }, + { {PlatformType_names + 41, 15}, 8 }, + { {PlatformType_names + 56, 12}, 5 }, + { {PlatformType_names + 68, 14}, 2 }, + { {PlatformType_names + 82, 12}, 4 }, + { {PlatformType_names + 94, 25}, 0 }, + { {PlatformType_names + 119, 16}, 1 }, +}; + +static const int PlatformType_entries_by_number[] = { + 7, // 0 -> PLATFORM_TYPE_UNSPECIFIED + 8, // 1 -> WINDOWS_PLATFORM + 5, // 2 -> LINUX_PLATFORM + 1, // 3 -> ANDROID_PLATFORM + 6, // 4 -> OSX_PLATFORM + 4, // 5 -> IOS_PLATFORM + 2, // 6 -> ANY_PLATFORM + 0, // 7 -> ALL_PLATFORMS + 3, // 8 -> CHROME_PLATFORM +}; + +const std::string& PlatformType_Name( + PlatformType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + PlatformType_entries, + PlatformType_entries_by_number, + 9, PlatformType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + PlatformType_entries, + PlatformType_entries_by_number, + 9, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + PlatformType_strings[idx].get(); +} +bool PlatformType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, PlatformType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + PlatformType_entries, 9, name, &int_value); + if (success) { + *value = static_cast<PlatformType>(int_value); + } + return success; +} +bool CompressionType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> CompressionType_strings[3] = {}; + +static const char CompressionType_names[] = + "COMPRESSION_TYPE_UNSPECIFIED" + "RAW" + "RICE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry CompressionType_entries[] = { + { {CompressionType_names + 0, 28}, 0 }, + { {CompressionType_names + 28, 3}, 1 }, + { {CompressionType_names + 31, 4}, 2 }, +}; + +static const int CompressionType_entries_by_number[] = { + 0, // 0 -> COMPRESSION_TYPE_UNSPECIFIED + 1, // 1 -> RAW + 2, // 2 -> RICE +}; + +const std::string& CompressionType_Name( + CompressionType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + CompressionType_entries, + CompressionType_entries_by_number, + 3, CompressionType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + CompressionType_entries, + CompressionType_entries_by_number, + 3, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + CompressionType_strings[idx].get(); +} +bool CompressionType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, CompressionType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + CompressionType_entries, 3, name, &int_value); + if (success) { + *value = static_cast<CompressionType>(int_value); + } + return success; +} +bool ThreatEntryType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> ThreatEntryType_strings[7] = {}; + +static const char ThreatEntryType_names[] = + "CERT" + "CHROME_EXTENSION" + "EXECUTABLE" + "FILENAME" + "IP_RANGE" + "THREAT_ENTRY_TYPE_UNSPECIFIED" + "URL"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ThreatEntryType_entries[] = { + { {ThreatEntryType_names + 0, 4}, 6 }, + { {ThreatEntryType_names + 4, 16}, 4 }, + { {ThreatEntryType_names + 20, 10}, 2 }, + { {ThreatEntryType_names + 30, 8}, 5 }, + { {ThreatEntryType_names + 38, 8}, 3 }, + { {ThreatEntryType_names + 46, 29}, 0 }, + { {ThreatEntryType_names + 75, 3}, 1 }, +}; + +static const int ThreatEntryType_entries_by_number[] = { + 5, // 0 -> THREAT_ENTRY_TYPE_UNSPECIFIED + 6, // 1 -> URL + 2, // 2 -> EXECUTABLE + 4, // 3 -> IP_RANGE + 1, // 4 -> CHROME_EXTENSION + 3, // 5 -> FILENAME + 0, // 6 -> CERT +}; + +const std::string& ThreatEntryType_Name( + ThreatEntryType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + ThreatEntryType_entries, + ThreatEntryType_entries_by_number, + 7, ThreatEntryType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + ThreatEntryType_entries, + ThreatEntryType_entries_by_number, + 7, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + ThreatEntryType_strings[idx].get(); +} +bool ThreatEntryType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ThreatEntryType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + ThreatEntryType_entries, 7, name, &int_value); + if (success) { + *value = static_cast<ThreatEntryType>(int_value); + } + return success; +} + +// =================================================================== + +class ThreatInfo::_Internal { + public: +}; + +ThreatInfo::ThreatInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatInfo) +} +ThreatInfo::ThreatInfo(const ThreatInfo& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatInfo* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.threat_types_){from._impl_.threat_types_} + , decltype(_impl_.platform_types_){from._impl_.platform_types_} + , decltype(_impl_.threat_entries_){from._impl_.threat_entries_} + , decltype(_impl_.threat_entry_types_){from._impl_.threat_entry_types_} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatInfo) +} + +inline void ThreatInfo::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.threat_types_){arena} + , decltype(_impl_.platform_types_){arena} + , decltype(_impl_.threat_entries_){arena} + , decltype(_impl_.threat_entry_types_){arena} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +ThreatInfo::~ThreatInfo() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatInfo) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatInfo::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.threat_types_.~RepeatedField(); + _impl_.platform_types_.~RepeatedField(); + _impl_.threat_entries_.~RepeatedPtrField(); + _impl_.threat_entry_types_.~RepeatedField(); +} + +void ThreatInfo::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatInfo::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatInfo) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.threat_types_.Clear(); + _impl_.platform_types_.Clear(); + _impl_.threat_entries_.Clear(); + _impl_.threat_entry_types_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatInfo::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .mozilla.safebrowsing.ThreatType threat_types = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + ptr -= 1; + do { + ptr += 1; + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatType_IsValid(val))) { + _internal_add_threat_types(static_cast<::mozilla::safebrowsing::ThreatType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<8>(ptr)); + } else if (static_cast<uint8_t>(tag) == 10) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedEnumParser<std::string>(_internal_mutable_threat_types(), ptr, ctx, ::mozilla::safebrowsing::ThreatType_IsValid, &_internal_metadata_, 1); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.PlatformType platform_types = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + ptr -= 1; + do { + ptr += 1; + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::PlatformType_IsValid(val))) { + _internal_add_platform_types(static_cast<::mozilla::safebrowsing::PlatformType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<16>(ptr)); + } else if (static_cast<uint8_t>(tag) == 18) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedEnumParser<std::string>(_internal_mutable_platform_types(), ptr, ctx, ::mozilla::safebrowsing::PlatformType_IsValid, &_internal_metadata_, 2); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.ThreatEntry threat_entries = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_threat_entries(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<26>(ptr)); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.ThreatEntryType threat_entry_types = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 32)) { + ptr -= 1; + do { + ptr += 1; + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatEntryType_IsValid(val))) { + _internal_add_threat_entry_types(static_cast<::mozilla::safebrowsing::ThreatEntryType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(4, val, mutable_unknown_fields()); + } + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<32>(ptr)); + } else if (static_cast<uint8_t>(tag) == 34) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedEnumParser<std::string>(_internal_mutable_threat_entry_types(), ptr, ctx, ::mozilla::safebrowsing::ThreatEntryType_IsValid, &_internal_metadata_, 4); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatInfo::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatInfo) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatType threat_types = 1; + for (int i = 0, n = this->_internal_threat_types_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_threat_types(i), target); + } + + // repeated .mozilla.safebrowsing.PlatformType platform_types = 2; + for (int i = 0, n = this->_internal_platform_types_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_platform_types(i), target); + } + + // repeated .mozilla.safebrowsing.ThreatEntry threat_entries = 3; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_threat_entries_size()); i < n; i++) { + const auto& repfield = this->_internal_threat_entries(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, repfield, repfield.GetCachedSize(), target, stream); + } + + // repeated .mozilla.safebrowsing.ThreatEntryType threat_entry_types = 4; + for (int i = 0, n = this->_internal_threat_entry_types_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 4, this->_internal_threat_entry_types(i), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatInfo) + return target; +} + +size_t ThreatInfo::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatInfo) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatType threat_types = 1; + { + size_t data_size = 0; + unsigned int count = static_cast<unsigned int>(this->_internal_threat_types_size());for (unsigned int i = 0; i < count; i++) { + data_size += ::_pbi::WireFormatLite::EnumSize( + this->_internal_threat_types(static_cast<int>(i))); + } + total_size += (1UL * count) + data_size; + } + + // repeated .mozilla.safebrowsing.PlatformType platform_types = 2; + { + size_t data_size = 0; + unsigned int count = static_cast<unsigned int>(this->_internal_platform_types_size());for (unsigned int i = 0; i < count; i++) { + data_size += ::_pbi::WireFormatLite::EnumSize( + this->_internal_platform_types(static_cast<int>(i))); + } + total_size += (1UL * count) + data_size; + } + + // repeated .mozilla.safebrowsing.ThreatEntry threat_entries = 3; + total_size += 1UL * this->_internal_threat_entries_size(); + for (const auto& msg : this->_impl_.threat_entries_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // repeated .mozilla.safebrowsing.ThreatEntryType threat_entry_types = 4; + { + size_t data_size = 0; + unsigned int count = static_cast<unsigned int>(this->_internal_threat_entry_types_size());for (unsigned int i = 0; i < count; i++) { + data_size += ::_pbi::WireFormatLite::EnumSize( + this->_internal_threat_entry_types(static_cast<int>(i))); + } + total_size += (1UL * count) + data_size; + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatInfo::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatInfo*>( + &from)); +} + +void ThreatInfo::MergeFrom(const ThreatInfo& from) { + ThreatInfo* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatInfo) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.threat_types_.MergeFrom(from._impl_.threat_types_); + _this->_impl_.platform_types_.MergeFrom(from._impl_.platform_types_); + _this->_impl_.threat_entries_.MergeFrom(from._impl_.threat_entries_); + _this->_impl_.threat_entry_types_.MergeFrom(from._impl_.threat_entry_types_); + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatInfo::CopyFrom(const ThreatInfo& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatInfo) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatInfo::IsInitialized() const { + return true; +} + +void ThreatInfo::InternalSwap(ThreatInfo* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.threat_types_.InternalSwap(&other->_impl_.threat_types_); + _impl_.platform_types_.InternalSwap(&other->_impl_.platform_types_); + _impl_.threat_entries_.InternalSwap(&other->_impl_.threat_entries_); + _impl_.threat_entry_types_.InternalSwap(&other->_impl_.threat_entry_types_); +} + +std::string ThreatInfo::GetTypeName() const { + return "mozilla.safebrowsing.ThreatInfo"; +} + + +// =================================================================== + +class ThreatMatch::_Internal { + public: + using HasBits = decltype(std::declval<ThreatMatch>()._impl_._has_bits_); + static void set_has_threat_type(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_platform_type(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_threat_entry_type(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static const ::mozilla::safebrowsing::ThreatEntry& threat(const ThreatMatch* msg); + static void set_has_threat(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::ThreatEntryMetadata& threat_entry_metadata(const ThreatMatch* msg); + static void set_has_threat_entry_metadata(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::mozilla::safebrowsing::Duration& cache_duration(const ThreatMatch* msg); + static void set_has_cache_duration(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +const ::mozilla::safebrowsing::ThreatEntry& +ThreatMatch::_Internal::threat(const ThreatMatch* msg) { + return *msg->_impl_.threat_; +} +const ::mozilla::safebrowsing::ThreatEntryMetadata& +ThreatMatch::_Internal::threat_entry_metadata(const ThreatMatch* msg) { + return *msg->_impl_.threat_entry_metadata_; +} +const ::mozilla::safebrowsing::Duration& +ThreatMatch::_Internal::cache_duration(const ThreatMatch* msg) { + return *msg->_impl_.cache_duration_; +} +ThreatMatch::ThreatMatch(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatMatch) +} +ThreatMatch::ThreatMatch(const ThreatMatch& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatMatch* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.threat_){nullptr} + , decltype(_impl_.threat_entry_metadata_){nullptr} + , decltype(_impl_.cache_duration_){nullptr} + , decltype(_impl_.threat_type_){} + , decltype(_impl_.platform_type_){} + , decltype(_impl_.threat_entry_type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_threat()) { + _this->_impl_.threat_ = new ::mozilla::safebrowsing::ThreatEntry(*from._impl_.threat_); + } + if (from._internal_has_threat_entry_metadata()) { + _this->_impl_.threat_entry_metadata_ = new ::mozilla::safebrowsing::ThreatEntryMetadata(*from._impl_.threat_entry_metadata_); + } + if (from._internal_has_cache_duration()) { + _this->_impl_.cache_duration_ = new ::mozilla::safebrowsing::Duration(*from._impl_.cache_duration_); + } + ::memcpy(&_impl_.threat_type_, &from._impl_.threat_type_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.threat_entry_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.threat_entry_type_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatMatch) +} + +inline void ThreatMatch::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.threat_){nullptr} + , decltype(_impl_.threat_entry_metadata_){nullptr} + , decltype(_impl_.cache_duration_){nullptr} + , decltype(_impl_.threat_type_){0} + , decltype(_impl_.platform_type_){0} + , decltype(_impl_.threat_entry_type_){0} + }; +} + +ThreatMatch::~ThreatMatch() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatMatch) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatMatch::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.threat_; + if (this != internal_default_instance()) delete _impl_.threat_entry_metadata_; + if (this != internal_default_instance()) delete _impl_.cache_duration_; +} + +void ThreatMatch::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatMatch::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatMatch) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.threat_ != nullptr); + _impl_.threat_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.threat_entry_metadata_ != nullptr); + _impl_.threat_entry_metadata_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.cache_duration_ != nullptr); + _impl_.cache_duration_->Clear(); + } + } + if (cached_has_bits & 0x00000038u) { + ::memset(&_impl_.threat_type_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.threat_entry_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.threat_entry_type_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatMatch::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatType_IsValid(val))) { + _internal_set_threat_type(static_cast<::mozilla::safebrowsing::ThreatType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::PlatformType_IsValid(val))) { + _internal_set_platform_type(static_cast<::mozilla::safebrowsing::PlatformType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntry threat = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_threat(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntryMetadata threat_entry_metadata = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_threat_entry_metadata(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.Duration cache_duration = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_cache_duration(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 48)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatEntryType_IsValid(val))) { + _internal_set_threat_entry_type(static_cast<::mozilla::safebrowsing::ThreatEntryType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(6, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatMatch::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatMatch) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_threat_type(), target); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_platform_type(), target); + } + + // optional .mozilla.safebrowsing.ThreatEntry threat = 3; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::threat(this), + _Internal::threat(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ThreatEntryMetadata threat_entry_metadata = 4; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::threat_entry_metadata(this), + _Internal::threat_entry_metadata(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.Duration cache_duration = 5; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::cache_duration(this), + _Internal::cache_duration(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 6; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 6, this->_internal_threat_entry_type(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatMatch) + return target; +} + +size_t ThreatMatch::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatMatch) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000003fu) { + // optional .mozilla.safebrowsing.ThreatEntry threat = 3; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.threat_); + } + + // optional .mozilla.safebrowsing.ThreatEntryMetadata threat_entry_metadata = 4; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.threat_entry_metadata_); + } + + // optional .mozilla.safebrowsing.Duration cache_duration = 5; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.cache_duration_); + } + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_type()); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_platform_type()); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 6; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_entry_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatMatch::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatMatch*>( + &from)); +} + +void ThreatMatch::MergeFrom(const ThreatMatch& from) { + ThreatMatch* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatMatch) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000003fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_threat()->::mozilla::safebrowsing::ThreatEntry::MergeFrom( + from._internal_threat()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_threat_entry_metadata()->::mozilla::safebrowsing::ThreatEntryMetadata::MergeFrom( + from._internal_threat_entry_metadata()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_cache_duration()->::mozilla::safebrowsing::Duration::MergeFrom( + from._internal_cache_duration()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.threat_type_ = from._impl_.threat_type_; + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.platform_type_ = from._impl_.platform_type_; + } + if (cached_has_bits & 0x00000020u) { + _this->_impl_.threat_entry_type_ = from._impl_.threat_entry_type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatMatch::CopyFrom(const ThreatMatch& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatMatch) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatMatch::IsInitialized() const { + return true; +} + +void ThreatMatch::InternalSwap(ThreatMatch* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ThreatMatch, _impl_.threat_entry_type_) + + sizeof(ThreatMatch::_impl_.threat_entry_type_) + - PROTOBUF_FIELD_OFFSET(ThreatMatch, _impl_.threat_)>( + reinterpret_cast<char*>(&_impl_.threat_), + reinterpret_cast<char*>(&other->_impl_.threat_)); +} + +std::string ThreatMatch::GetTypeName() const { + return "mozilla.safebrowsing.ThreatMatch"; +} + + +// =================================================================== + +class FindThreatMatchesRequest::_Internal { + public: + using HasBits = decltype(std::declval<FindThreatMatchesRequest>()._impl_._has_bits_); + static const ::mozilla::safebrowsing::ClientInfo& client(const FindThreatMatchesRequest* msg); + static void set_has_client(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::ThreatInfo& threat_info(const FindThreatMatchesRequest* msg); + static void set_has_threat_info(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::safebrowsing::ClientInfo& +FindThreatMatchesRequest::_Internal::client(const FindThreatMatchesRequest* msg) { + return *msg->_impl_.client_; +} +const ::mozilla::safebrowsing::ThreatInfo& +FindThreatMatchesRequest::_Internal::threat_info(const FindThreatMatchesRequest* msg) { + return *msg->_impl_.threat_info_; +} +FindThreatMatchesRequest::FindThreatMatchesRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FindThreatMatchesRequest) +} +FindThreatMatchesRequest::FindThreatMatchesRequest(const FindThreatMatchesRequest& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FindThreatMatchesRequest* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.client_){nullptr} + , decltype(_impl_.threat_info_){nullptr}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_client()) { + _this->_impl_.client_ = new ::mozilla::safebrowsing::ClientInfo(*from._impl_.client_); + } + if (from._internal_has_threat_info()) { + _this->_impl_.threat_info_ = new ::mozilla::safebrowsing::ThreatInfo(*from._impl_.threat_info_); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FindThreatMatchesRequest) +} + +inline void FindThreatMatchesRequest::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.client_){nullptr} + , decltype(_impl_.threat_info_){nullptr} + }; +} + +FindThreatMatchesRequest::~FindThreatMatchesRequest() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FindThreatMatchesRequest) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FindThreatMatchesRequest::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.client_; + if (this != internal_default_instance()) delete _impl_.threat_info_; +} + +void FindThreatMatchesRequest::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FindThreatMatchesRequest::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FindThreatMatchesRequest) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.client_ != nullptr); + _impl_.client_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.threat_info_ != nullptr); + _impl_.threat_info_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FindThreatMatchesRequest::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ClientInfo client = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr = ctx->ParseMessage(_internal_mutable_client(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_threat_info(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FindThreatMatchesRequest::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FindThreatMatchesRequest) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ClientInfo client = 1; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, _Internal::client(this), + _Internal::client(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 2; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, _Internal::threat_info(this), + _Internal::threat_info(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FindThreatMatchesRequest) + return target; +} + +size_t FindThreatMatchesRequest::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FindThreatMatchesRequest) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional .mozilla.safebrowsing.ClientInfo client = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.client_); + } + + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.threat_info_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FindThreatMatchesRequest::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FindThreatMatchesRequest*>( + &from)); +} + +void FindThreatMatchesRequest::MergeFrom(const FindThreatMatchesRequest& from) { + FindThreatMatchesRequest* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FindThreatMatchesRequest) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_client()->::mozilla::safebrowsing::ClientInfo::MergeFrom( + from._internal_client()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_threat_info()->::mozilla::safebrowsing::ThreatInfo::MergeFrom( + from._internal_threat_info()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FindThreatMatchesRequest::CopyFrom(const FindThreatMatchesRequest& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FindThreatMatchesRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FindThreatMatchesRequest::IsInitialized() const { + return true; +} + +void FindThreatMatchesRequest::InternalSwap(FindThreatMatchesRequest* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FindThreatMatchesRequest, _impl_.threat_info_) + + sizeof(FindThreatMatchesRequest::_impl_.threat_info_) + - PROTOBUF_FIELD_OFFSET(FindThreatMatchesRequest, _impl_.client_)>( + reinterpret_cast<char*>(&_impl_.client_), + reinterpret_cast<char*>(&other->_impl_.client_)); +} + +std::string FindThreatMatchesRequest::GetTypeName() const { + return "mozilla.safebrowsing.FindThreatMatchesRequest"; +} + + +// =================================================================== + +class FindThreatMatchesResponse::_Internal { + public: +}; + +FindThreatMatchesResponse::FindThreatMatchesResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FindThreatMatchesResponse) +} +FindThreatMatchesResponse::FindThreatMatchesResponse(const FindThreatMatchesResponse& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FindThreatMatchesResponse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.matches_){from._impl_.matches_} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FindThreatMatchesResponse) +} + +inline void FindThreatMatchesResponse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.matches_){arena} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +FindThreatMatchesResponse::~FindThreatMatchesResponse() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FindThreatMatchesResponse) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FindThreatMatchesResponse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.matches_.~RepeatedPtrField(); +} + +void FindThreatMatchesResponse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FindThreatMatchesResponse::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FindThreatMatchesResponse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.matches_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FindThreatMatchesResponse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_matches(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FindThreatMatchesResponse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FindThreatMatchesResponse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_matches_size()); i < n; i++) { + const auto& repfield = this->_internal_matches(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FindThreatMatchesResponse) + return target; +} + +size_t FindThreatMatchesResponse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FindThreatMatchesResponse) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + total_size += 1UL * this->_internal_matches_size(); + for (const auto& msg : this->_impl_.matches_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FindThreatMatchesResponse::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FindThreatMatchesResponse*>( + &from)); +} + +void FindThreatMatchesResponse::MergeFrom(const FindThreatMatchesResponse& from) { + FindThreatMatchesResponse* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FindThreatMatchesResponse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.matches_.MergeFrom(from._impl_.matches_); + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FindThreatMatchesResponse::CopyFrom(const FindThreatMatchesResponse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FindThreatMatchesResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FindThreatMatchesResponse::IsInitialized() const { + return true; +} + +void FindThreatMatchesResponse::InternalSwap(FindThreatMatchesResponse* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.matches_.InternalSwap(&other->_impl_.matches_); +} + +std::string FindThreatMatchesResponse::GetTypeName() const { + return "mozilla.safebrowsing.FindThreatMatchesResponse"; +} + + +// =================================================================== + +class FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_Internal { + public: + using HasBits = decltype(std::declval<FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints>()._impl_._has_bits_); + static void set_has_max_update_entries(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_max_database_entries(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_region(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) +} +FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.supported_compressions_){from._impl_.supported_compressions_} + , decltype(_impl_.region_){} + , decltype(_impl_.max_update_entries_){} + , decltype(_impl_.max_database_entries_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.region_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.region_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_region()) { + _this->_impl_.region_.Set(from._internal_region(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.max_update_entries_, &from._impl_.max_update_entries_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.max_database_entries_) - + reinterpret_cast<char*>(&_impl_.max_update_entries_)) + sizeof(_impl_.max_database_entries_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) +} + +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.supported_compressions_){arena} + , decltype(_impl_.region_){} + , decltype(_impl_.max_update_entries_){0} + , decltype(_impl_.max_database_entries_){0} + }; + _impl_.region_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.region_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::~FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.supported_compressions_.~RepeatedField(); + _impl_.region_.Destroy(); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.supported_compressions_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + _impl_.region_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000006u) { + ::memset(&_impl_.max_update_entries_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.max_database_entries_) - + reinterpret_cast<char*>(&_impl_.max_update_entries_)) + sizeof(_impl_.max_database_entries_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional int32 max_update_entries = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + _Internal::set_has_max_update_entries(&has_bits); + _impl_.max_update_entries_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional int32 max_database_entries = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + _Internal::set_has_max_database_entries(&has_bits); + _impl_.max_database_entries_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string region = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + auto str = _internal_mutable_region(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.CompressionType supported_compressions = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 32)) { + ptr -= 1; + do { + ptr += 1; + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::CompressionType_IsValid(val))) { + _internal_add_supported_compressions(static_cast<::mozilla::safebrowsing::CompressionType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(4, val, mutable_unknown_fields()); + } + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<32>(ptr)); + } else if (static_cast<uint8_t>(tag) == 34) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedEnumParser<std::string>(_internal_mutable_supported_compressions(), ptr, ctx, ::mozilla::safebrowsing::CompressionType_IsValid, &_internal_metadata_, 4); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional int32 max_update_entries = 1; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(1, this->_internal_max_update_entries(), target); + } + + // optional int32 max_database_entries = 2; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(2, this->_internal_max_database_entries(), target); + } + + // optional string region = 3; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_region(), target); + } + + // repeated .mozilla.safebrowsing.CompressionType supported_compressions = 4; + for (int i = 0, n = this->_internal_supported_compressions_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 4, this->_internal_supported_compressions(i), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + return target; +} + +size_t FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.CompressionType supported_compressions = 4; + { + size_t data_size = 0; + unsigned int count = static_cast<unsigned int>(this->_internal_supported_compressions_size());for (unsigned int i = 0; i < count; i++) { + data_size += ::_pbi::WireFormatLite::EnumSize( + this->_internal_supported_compressions(static_cast<int>(i))); + } + total_size += (1UL * count) + data_size; + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional string region = 3; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_region()); + } + + // optional int32 max_update_entries = 1; + if (cached_has_bits & 0x00000002u) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_max_update_entries()); + } + + // optional int32 max_database_entries = 2; + if (cached_has_bits & 0x00000004u) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_max_database_entries()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints*>( + &from)); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::MergeFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from) { + FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.supported_compressions_.MergeFrom(from._impl_.supported_compressions_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_region(from._internal_region()); + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.max_update_entries_ = from._impl_.max_update_entries_; + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.max_database_entries_ = from._impl_.max_database_entries_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::CopyFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::IsInitialized() const { + return true; +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::InternalSwap(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.supported_compressions_.InternalSwap(&other->_impl_.supported_compressions_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.region_, lhs_arena, + &other->_impl_.region_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints, _impl_.max_database_entries_) + + sizeof(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_impl_.max_database_entries_) + - PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints, _impl_.max_update_entries_)>( + reinterpret_cast<char*>(&_impl_.max_update_entries_), + reinterpret_cast<char*>(&other->_impl_.max_update_entries_)); +} + +std::string FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::GetTypeName() const { + return "mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints"; +} + + +// =================================================================== + +class FetchThreatListUpdatesRequest_ListUpdateRequest::_Internal { + public: + using HasBits = decltype(std::declval<FetchThreatListUpdatesRequest_ListUpdateRequest>()._impl_._has_bits_); + static void set_has_threat_type(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_platform_type(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_threat_entry_type(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_state(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& constraints(const FetchThreatListUpdatesRequest_ListUpdateRequest* msg); + static void set_has_constraints(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& +FetchThreatListUpdatesRequest_ListUpdateRequest::_Internal::constraints(const FetchThreatListUpdatesRequest_ListUpdateRequest* msg) { + return *msg->_impl_.constraints_; +} +FetchThreatListUpdatesRequest_ListUpdateRequest::FetchThreatListUpdatesRequest_ListUpdateRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) +} +FetchThreatListUpdatesRequest_ListUpdateRequest::FetchThreatListUpdatesRequest_ListUpdateRequest(const FetchThreatListUpdatesRequest_ListUpdateRequest& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FetchThreatListUpdatesRequest_ListUpdateRequest* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.state_){} + , decltype(_impl_.constraints_){nullptr} + , decltype(_impl_.threat_type_){} + , decltype(_impl_.platform_type_){} + , decltype(_impl_.threat_entry_type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_state()) { + _this->_impl_.state_.Set(from._internal_state(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_constraints()) { + _this->_impl_.constraints_ = new ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(*from._impl_.constraints_); + } + ::memcpy(&_impl_.threat_type_, &from._impl_.threat_type_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.threat_entry_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.threat_entry_type_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) +} + +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.state_){} + , decltype(_impl_.constraints_){nullptr} + , decltype(_impl_.threat_type_){0} + , decltype(_impl_.platform_type_){0} + , decltype(_impl_.threat_entry_type_){0} + }; + _impl_.state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +FetchThreatListUpdatesRequest_ListUpdateRequest::~FetchThreatListUpdatesRequest_ListUpdateRequest() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.state_.Destroy(); + if (this != internal_default_instance()) delete _impl_.constraints_; +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.state_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.constraints_ != nullptr); + _impl_.constraints_->Clear(); + } + } + if (cached_has_bits & 0x0000001cu) { + ::memset(&_impl_.threat_type_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.threat_entry_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.threat_entry_type_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FetchThreatListUpdatesRequest_ListUpdateRequest::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatType_IsValid(val))) { + _internal_set_threat_type(static_cast<::mozilla::safebrowsing::ThreatType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::PlatformType_IsValid(val))) { + _internal_set_platform_type(static_cast<::mozilla::safebrowsing::PlatformType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional bytes state = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + auto str = _internal_mutable_state(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints constraints = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_constraints(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 40)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatEntryType_IsValid(val))) { + _internal_set_threat_entry_type(static_cast<::mozilla::safebrowsing::ThreatEntryType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(5, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FetchThreatListUpdatesRequest_ListUpdateRequest::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_threat_type(), target); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_platform_type(), target); + } + + // optional bytes state = 3; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 3, this->_internal_state(), target); + } + + // optional .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints constraints = 4; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::constraints(this), + _Internal::constraints(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 5; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 5, this->_internal_threat_entry_type(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + return target; +} + +size_t FetchThreatListUpdatesRequest_ListUpdateRequest::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + // optional bytes state = 3; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_state()); + } + + // optional .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints constraints = 4; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.constraints_); + } + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_type()); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_platform_type()); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 5; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_entry_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FetchThreatListUpdatesRequest_ListUpdateRequest*>( + &from)); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest::MergeFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest& from) { + FetchThreatListUpdatesRequest_ListUpdateRequest* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_state(from._internal_state()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_constraints()->::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::MergeFrom( + from._internal_constraints()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.threat_type_ = from._impl_.threat_type_; + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.platform_type_ = from._impl_.platform_type_; + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.threat_entry_type_ = from._impl_.threat_entry_type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest::CopyFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FetchThreatListUpdatesRequest_ListUpdateRequest::IsInitialized() const { + return true; +} + +void FetchThreatListUpdatesRequest_ListUpdateRequest::InternalSwap(FetchThreatListUpdatesRequest_ListUpdateRequest* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.state_, lhs_arena, + &other->_impl_.state_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesRequest_ListUpdateRequest, _impl_.threat_entry_type_) + + sizeof(FetchThreatListUpdatesRequest_ListUpdateRequest::_impl_.threat_entry_type_) + - PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesRequest_ListUpdateRequest, _impl_.constraints_)>( + reinterpret_cast<char*>(&_impl_.constraints_), + reinterpret_cast<char*>(&other->_impl_.constraints_)); +} + +std::string FetchThreatListUpdatesRequest_ListUpdateRequest::GetTypeName() const { + return "mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest"; +} + + +// =================================================================== + +class FetchThreatListUpdatesRequest::_Internal { + public: + using HasBits = decltype(std::declval<FetchThreatListUpdatesRequest>()._impl_._has_bits_); + static const ::mozilla::safebrowsing::ClientInfo& client(const FetchThreatListUpdatesRequest* msg); + static void set_has_client(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::ChromeClientInfo& chrome_client_info(const FetchThreatListUpdatesRequest* msg); + static void set_has_chrome_client_info(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::safebrowsing::ClientInfo& +FetchThreatListUpdatesRequest::_Internal::client(const FetchThreatListUpdatesRequest* msg) { + return *msg->_impl_.client_; +} +const ::mozilla::safebrowsing::ChromeClientInfo& +FetchThreatListUpdatesRequest::_Internal::chrome_client_info(const FetchThreatListUpdatesRequest* msg) { + return *msg->_impl_.chrome_client_info_; +} +FetchThreatListUpdatesRequest::FetchThreatListUpdatesRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest) +} +FetchThreatListUpdatesRequest::FetchThreatListUpdatesRequest(const FetchThreatListUpdatesRequest& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FetchThreatListUpdatesRequest* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.list_update_requests_){from._impl_.list_update_requests_} + , decltype(_impl_.client_){nullptr} + , decltype(_impl_.chrome_client_info_){nullptr}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_client()) { + _this->_impl_.client_ = new ::mozilla::safebrowsing::ClientInfo(*from._impl_.client_); + } + if (from._internal_has_chrome_client_info()) { + _this->_impl_.chrome_client_info_ = new ::mozilla::safebrowsing::ChromeClientInfo(*from._impl_.chrome_client_info_); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest) +} + +inline void FetchThreatListUpdatesRequest::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.list_update_requests_){arena} + , decltype(_impl_.client_){nullptr} + , decltype(_impl_.chrome_client_info_){nullptr} + }; +} + +FetchThreatListUpdatesRequest::~FetchThreatListUpdatesRequest() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FetchThreatListUpdatesRequest::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.list_update_requests_.~RepeatedPtrField(); + if (this != internal_default_instance()) delete _impl_.client_; + if (this != internal_default_instance()) delete _impl_.chrome_client_info_; +} + +void FetchThreatListUpdatesRequest::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FetchThreatListUpdatesRequest::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.list_update_requests_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.client_ != nullptr); + _impl_.client_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.chrome_client_info_ != nullptr); + _impl_.chrome_client_info_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FetchThreatListUpdatesRequest::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ClientInfo client = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr = ctx->ParseMessage(_internal_mutable_client(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest list_update_requests = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_list_update_requests(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<26>(ptr)); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ChromeClientInfo chrome_client_info = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_chrome_client_info(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FetchThreatListUpdatesRequest::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ClientInfo client = 1; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, _Internal::client(this), + _Internal::client(this).GetCachedSize(), target, stream); + } + + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest list_update_requests = 3; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_list_update_requests_size()); i < n; i++) { + const auto& repfield = this->_internal_list_update_requests(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, repfield, repfield.GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ChromeClientInfo chrome_client_info = 4; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::chrome_client_info(this), + _Internal::chrome_client_info(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + return target; +} + +size_t FetchThreatListUpdatesRequest::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest list_update_requests = 3; + total_size += 1UL * this->_internal_list_update_requests_size(); + for (const auto& msg : this->_impl_.list_update_requests_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional .mozilla.safebrowsing.ClientInfo client = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.client_); + } + + // optional .mozilla.safebrowsing.ChromeClientInfo chrome_client_info = 4; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.chrome_client_info_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FetchThreatListUpdatesRequest::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FetchThreatListUpdatesRequest*>( + &from)); +} + +void FetchThreatListUpdatesRequest::MergeFrom(const FetchThreatListUpdatesRequest& from) { + FetchThreatListUpdatesRequest* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.list_update_requests_.MergeFrom(from._impl_.list_update_requests_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_client()->::mozilla::safebrowsing::ClientInfo::MergeFrom( + from._internal_client()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_chrome_client_info()->::mozilla::safebrowsing::ChromeClientInfo::MergeFrom( + from._internal_chrome_client_info()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FetchThreatListUpdatesRequest::CopyFrom(const FetchThreatListUpdatesRequest& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FetchThreatListUpdatesRequest::IsInitialized() const { + return true; +} + +void FetchThreatListUpdatesRequest::InternalSwap(FetchThreatListUpdatesRequest* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.list_update_requests_.InternalSwap(&other->_impl_.list_update_requests_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesRequest, _impl_.chrome_client_info_) + + sizeof(FetchThreatListUpdatesRequest::_impl_.chrome_client_info_) + - PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesRequest, _impl_.client_)>( + reinterpret_cast<char*>(&_impl_.client_), + reinterpret_cast<char*>(&other->_impl_.client_)); +} + +std::string FetchThreatListUpdatesRequest::GetTypeName() const { + return "mozilla.safebrowsing.FetchThreatListUpdatesRequest"; +} + + +// =================================================================== + +class FetchThreatListUpdatesResponse_ListUpdateResponse::_Internal { + public: + using HasBits = decltype(std::declval<FetchThreatListUpdatesResponse_ListUpdateResponse>()._impl_._has_bits_); + static void set_has_threat_type(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_threat_entry_type(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_platform_type(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_response_type(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static void set_has_new_client_state(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::Checksum& checksum(const FetchThreatListUpdatesResponse_ListUpdateResponse* msg); + static void set_has_checksum(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::safebrowsing::Checksum& +FetchThreatListUpdatesResponse_ListUpdateResponse::_Internal::checksum(const FetchThreatListUpdatesResponse_ListUpdateResponse* msg) { + return *msg->_impl_.checksum_; +} +FetchThreatListUpdatesResponse_ListUpdateResponse::FetchThreatListUpdatesResponse_ListUpdateResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) +} +FetchThreatListUpdatesResponse_ListUpdateResponse::FetchThreatListUpdatesResponse_ListUpdateResponse(const FetchThreatListUpdatesResponse_ListUpdateResponse& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FetchThreatListUpdatesResponse_ListUpdateResponse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.additions_){from._impl_.additions_} + , decltype(_impl_.removals_){from._impl_.removals_} + , decltype(_impl_.new_client_state_){} + , decltype(_impl_.checksum_){nullptr} + , decltype(_impl_.threat_type_){} + , decltype(_impl_.threat_entry_type_){} + , decltype(_impl_.platform_type_){} + , decltype(_impl_.response_type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.new_client_state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.new_client_state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_new_client_state()) { + _this->_impl_.new_client_state_.Set(from._internal_new_client_state(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_checksum()) { + _this->_impl_.checksum_ = new ::mozilla::safebrowsing::Checksum(*from._impl_.checksum_); + } + ::memcpy(&_impl_.threat_type_, &from._impl_.threat_type_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.response_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.response_type_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) +} + +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.additions_){arena} + , decltype(_impl_.removals_){arena} + , decltype(_impl_.new_client_state_){} + , decltype(_impl_.checksum_){nullptr} + , decltype(_impl_.threat_type_){0} + , decltype(_impl_.threat_entry_type_){0} + , decltype(_impl_.platform_type_){0} + , decltype(_impl_.response_type_){0} + }; + _impl_.new_client_state_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.new_client_state_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +FetchThreatListUpdatesResponse_ListUpdateResponse::~FetchThreatListUpdatesResponse_ListUpdateResponse() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.additions_.~RepeatedPtrField(); + _impl_.removals_.~RepeatedPtrField(); + _impl_.new_client_state_.Destroy(); + if (this != internal_default_instance()) delete _impl_.checksum_; +} + +void FetchThreatListUpdatesResponse_ListUpdateResponse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FetchThreatListUpdatesResponse_ListUpdateResponse::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.additions_.Clear(); + _impl_.removals_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.new_client_state_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.checksum_ != nullptr); + _impl_.checksum_->Clear(); + } + } + if (cached_has_bits & 0x0000003cu) { + ::memset(&_impl_.threat_type_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.response_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.response_type_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FetchThreatListUpdatesResponse_ListUpdateResponse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatType_IsValid(val))) { + _internal_set_threat_type(static_cast<::mozilla::safebrowsing::ThreatType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatEntryType_IsValid(val))) { + _internal_set_threat_entry_type(static_cast<::mozilla::safebrowsing::ThreatEntryType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.PlatformType platform_type = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::PlatformType_IsValid(val))) { + _internal_set_platform_type(static_cast<::mozilla::safebrowsing::PlatformType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(3, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.ResponseType response_type = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 32)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_IsValid(val))) { + _internal_set_response_type(static_cast<::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(4, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.ThreatEntrySet additions = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_additions(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.ThreatEntrySet removals = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 50)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_removals(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<50>(ptr)); + } else + goto handle_unusual; + continue; + // optional bytes new_client_state = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 58)) { + auto str = _internal_mutable_new_client_state(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.Checksum checksum = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 66)) { + ptr = ctx->ParseMessage(_internal_mutable_checksum(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FetchThreatListUpdatesResponse_ListUpdateResponse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_threat_type(), target); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 2; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_threat_entry_type(), target); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 3; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 3, this->_internal_platform_type(), target); + } + + // optional .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.ResponseType response_type = 4; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 4, this->_internal_response_type(), target); + } + + // repeated .mozilla.safebrowsing.ThreatEntrySet additions = 5; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_additions_size()); i < n; i++) { + const auto& repfield = this->_internal_additions(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, repfield, repfield.GetCachedSize(), target, stream); + } + + // repeated .mozilla.safebrowsing.ThreatEntrySet removals = 6; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_removals_size()); i < n; i++) { + const auto& repfield = this->_internal_removals(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(6, repfield, repfield.GetCachedSize(), target, stream); + } + + // optional bytes new_client_state = 7; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 7, this->_internal_new_client_state(), target); + } + + // optional .mozilla.safebrowsing.Checksum checksum = 8; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(8, _Internal::checksum(this), + _Internal::checksum(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + return target; +} + +size_t FetchThreatListUpdatesResponse_ListUpdateResponse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatEntrySet additions = 5; + total_size += 1UL * this->_internal_additions_size(); + for (const auto& msg : this->_impl_.additions_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // repeated .mozilla.safebrowsing.ThreatEntrySet removals = 6; + total_size += 1UL * this->_internal_removals_size(); + for (const auto& msg : this->_impl_.removals_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000003fu) { + // optional bytes new_client_state = 7; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_new_client_state()); + } + + // optional .mozilla.safebrowsing.Checksum checksum = 8; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.checksum_); + } + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_type()); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 2; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_entry_type()); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 3; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_platform_type()); + } + + // optional .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.ResponseType response_type = 4; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_response_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FetchThreatListUpdatesResponse_ListUpdateResponse::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FetchThreatListUpdatesResponse_ListUpdateResponse*>( + &from)); +} + +void FetchThreatListUpdatesResponse_ListUpdateResponse::MergeFrom(const FetchThreatListUpdatesResponse_ListUpdateResponse& from) { + FetchThreatListUpdatesResponse_ListUpdateResponse* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.additions_.MergeFrom(from._impl_.additions_); + _this->_impl_.removals_.MergeFrom(from._impl_.removals_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000003fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_new_client_state(from._internal_new_client_state()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_checksum()->::mozilla::safebrowsing::Checksum::MergeFrom( + from._internal_checksum()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.threat_type_ = from._impl_.threat_type_; + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.threat_entry_type_ = from._impl_.threat_entry_type_; + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.platform_type_ = from._impl_.platform_type_; + } + if (cached_has_bits & 0x00000020u) { + _this->_impl_.response_type_ = from._impl_.response_type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FetchThreatListUpdatesResponse_ListUpdateResponse::CopyFrom(const FetchThreatListUpdatesResponse_ListUpdateResponse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FetchThreatListUpdatesResponse_ListUpdateResponse::IsInitialized() const { + return true; +} + +void FetchThreatListUpdatesResponse_ListUpdateResponse::InternalSwap(FetchThreatListUpdatesResponse_ListUpdateResponse* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.additions_.InternalSwap(&other->_impl_.additions_); + _impl_.removals_.InternalSwap(&other->_impl_.removals_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.new_client_state_, lhs_arena, + &other->_impl_.new_client_state_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesResponse_ListUpdateResponse, _impl_.response_type_) + + sizeof(FetchThreatListUpdatesResponse_ListUpdateResponse::_impl_.response_type_) + - PROTOBUF_FIELD_OFFSET(FetchThreatListUpdatesResponse_ListUpdateResponse, _impl_.checksum_)>( + reinterpret_cast<char*>(&_impl_.checksum_), + reinterpret_cast<char*>(&other->_impl_.checksum_)); +} + +std::string FetchThreatListUpdatesResponse_ListUpdateResponse::GetTypeName() const { + return "mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse"; +} + + +// =================================================================== + +class FetchThreatListUpdatesResponse::_Internal { + public: + using HasBits = decltype(std::declval<FetchThreatListUpdatesResponse>()._impl_._has_bits_); + static const ::mozilla::safebrowsing::Duration& minimum_wait_duration(const FetchThreatListUpdatesResponse* msg); + static void set_has_minimum_wait_duration(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +const ::mozilla::safebrowsing::Duration& +FetchThreatListUpdatesResponse::_Internal::minimum_wait_duration(const FetchThreatListUpdatesResponse* msg) { + return *msg->_impl_.minimum_wait_duration_; +} +FetchThreatListUpdatesResponse::FetchThreatListUpdatesResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FetchThreatListUpdatesResponse) +} +FetchThreatListUpdatesResponse::FetchThreatListUpdatesResponse(const FetchThreatListUpdatesResponse& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FetchThreatListUpdatesResponse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.list_update_responses_){from._impl_.list_update_responses_} + , decltype(_impl_.minimum_wait_duration_){nullptr}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_minimum_wait_duration()) { + _this->_impl_.minimum_wait_duration_ = new ::mozilla::safebrowsing::Duration(*from._impl_.minimum_wait_duration_); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FetchThreatListUpdatesResponse) +} + +inline void FetchThreatListUpdatesResponse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.list_update_responses_){arena} + , decltype(_impl_.minimum_wait_duration_){nullptr} + }; +} + +FetchThreatListUpdatesResponse::~FetchThreatListUpdatesResponse() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FetchThreatListUpdatesResponse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.list_update_responses_.~RepeatedPtrField(); + if (this != internal_default_instance()) delete _impl_.minimum_wait_duration_; +} + +void FetchThreatListUpdatesResponse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FetchThreatListUpdatesResponse::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.list_update_responses_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.minimum_wait_duration_ != nullptr); + _impl_.minimum_wait_duration_->Clear(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FetchThreatListUpdatesResponse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse list_update_responses = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_list_update_responses(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_minimum_wait_duration(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FetchThreatListUpdatesResponse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse list_update_responses = 1; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_list_update_responses_size()); i < n; i++) { + const auto& repfield = this->_internal_list_update_responses(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, _Internal::minimum_wait_duration(this), + _Internal::minimum_wait_duration(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + return target; +} + +size_t FetchThreatListUpdatesResponse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse list_update_responses = 1; + total_size += 1UL * this->_internal_list_update_responses_size(); + for (const auto& msg : this->_impl_.list_update_responses_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.minimum_wait_duration_); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FetchThreatListUpdatesResponse::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FetchThreatListUpdatesResponse*>( + &from)); +} + +void FetchThreatListUpdatesResponse::MergeFrom(const FetchThreatListUpdatesResponse& from) { + FetchThreatListUpdatesResponse* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.list_update_responses_.MergeFrom(from._impl_.list_update_responses_); + if (from._internal_has_minimum_wait_duration()) { + _this->_internal_mutable_minimum_wait_duration()->::mozilla::safebrowsing::Duration::MergeFrom( + from._internal_minimum_wait_duration()); + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FetchThreatListUpdatesResponse::CopyFrom(const FetchThreatListUpdatesResponse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FetchThreatListUpdatesResponse::IsInitialized() const { + return true; +} + +void FetchThreatListUpdatesResponse::InternalSwap(FetchThreatListUpdatesResponse* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.list_update_responses_.InternalSwap(&other->_impl_.list_update_responses_); + swap(_impl_.minimum_wait_duration_, other->_impl_.minimum_wait_duration_); +} + +std::string FetchThreatListUpdatesResponse::GetTypeName() const { + return "mozilla.safebrowsing.FetchThreatListUpdatesResponse"; +} + + +// =================================================================== + +class FindFullHashesRequest::_Internal { + public: + using HasBits = decltype(std::declval<FindFullHashesRequest>()._impl_._has_bits_); + static const ::mozilla::safebrowsing::ClientInfo& client(const FindFullHashesRequest* msg); + static void set_has_client(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::ThreatInfo& threat_info(const FindFullHashesRequest* msg); + static void set_has_threat_info(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::safebrowsing::ClientInfo& +FindFullHashesRequest::_Internal::client(const FindFullHashesRequest* msg) { + return *msg->_impl_.client_; +} +const ::mozilla::safebrowsing::ThreatInfo& +FindFullHashesRequest::_Internal::threat_info(const FindFullHashesRequest* msg) { + return *msg->_impl_.threat_info_; +} +FindFullHashesRequest::FindFullHashesRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FindFullHashesRequest) +} +FindFullHashesRequest::FindFullHashesRequest(const FindFullHashesRequest& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FindFullHashesRequest* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.client_states_){from._impl_.client_states_} + , decltype(_impl_.client_){nullptr} + , decltype(_impl_.threat_info_){nullptr}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_client()) { + _this->_impl_.client_ = new ::mozilla::safebrowsing::ClientInfo(*from._impl_.client_); + } + if (from._internal_has_threat_info()) { + _this->_impl_.threat_info_ = new ::mozilla::safebrowsing::ThreatInfo(*from._impl_.threat_info_); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FindFullHashesRequest) +} + +inline void FindFullHashesRequest::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.client_states_){arena} + , decltype(_impl_.client_){nullptr} + , decltype(_impl_.threat_info_){nullptr} + }; +} + +FindFullHashesRequest::~FindFullHashesRequest() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FindFullHashesRequest) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FindFullHashesRequest::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.client_states_.~RepeatedPtrField(); + if (this != internal_default_instance()) delete _impl_.client_; + if (this != internal_default_instance()) delete _impl_.threat_info_; +} + +void FindFullHashesRequest::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FindFullHashesRequest::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FindFullHashesRequest) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.client_states_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.client_ != nullptr); + _impl_.client_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.threat_info_ != nullptr); + _impl_.threat_info_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FindFullHashesRequest::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ClientInfo client = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr = ctx->ParseMessage(_internal_mutable_client(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated bytes client_states = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_client_states(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<18>(ptr)); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_threat_info(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FindFullHashesRequest::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FindFullHashesRequest) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ClientInfo client = 1; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, _Internal::client(this), + _Internal::client(this).GetCachedSize(), target, stream); + } + + // repeated bytes client_states = 2; + for (int i = 0, n = this->_internal_client_states_size(); i < n; i++) { + const auto& s = this->_internal_client_states(i); + target = stream->WriteBytes(2, s, target); + } + + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 3; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::threat_info(this), + _Internal::threat_info(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FindFullHashesRequest) + return target; +} + +size_t FindFullHashesRequest::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FindFullHashesRequest) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated bytes client_states = 2; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.client_states_.size()); + for (int i = 0, n = _impl_.client_states_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + _impl_.client_states_.Get(i)); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional .mozilla.safebrowsing.ClientInfo client = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.client_); + } + + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.threat_info_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FindFullHashesRequest::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FindFullHashesRequest*>( + &from)); +} + +void FindFullHashesRequest::MergeFrom(const FindFullHashesRequest& from) { + FindFullHashesRequest* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FindFullHashesRequest) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.client_states_.MergeFrom(from._impl_.client_states_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_client()->::mozilla::safebrowsing::ClientInfo::MergeFrom( + from._internal_client()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_threat_info()->::mozilla::safebrowsing::ThreatInfo::MergeFrom( + from._internal_threat_info()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FindFullHashesRequest::CopyFrom(const FindFullHashesRequest& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FindFullHashesRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FindFullHashesRequest::IsInitialized() const { + return true; +} + +void FindFullHashesRequest::InternalSwap(FindFullHashesRequest* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.client_states_.InternalSwap(&other->_impl_.client_states_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FindFullHashesRequest, _impl_.threat_info_) + + sizeof(FindFullHashesRequest::_impl_.threat_info_) + - PROTOBUF_FIELD_OFFSET(FindFullHashesRequest, _impl_.client_)>( + reinterpret_cast<char*>(&_impl_.client_), + reinterpret_cast<char*>(&other->_impl_.client_)); +} + +std::string FindFullHashesRequest::GetTypeName() const { + return "mozilla.safebrowsing.FindFullHashesRequest"; +} + + +// =================================================================== + +class FindFullHashesResponse::_Internal { + public: + using HasBits = decltype(std::declval<FindFullHashesResponse>()._impl_._has_bits_); + static const ::mozilla::safebrowsing::Duration& minimum_wait_duration(const FindFullHashesResponse* msg); + static void set_has_minimum_wait_duration(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::Duration& negative_cache_duration(const FindFullHashesResponse* msg); + static void set_has_negative_cache_duration(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::safebrowsing::Duration& +FindFullHashesResponse::_Internal::minimum_wait_duration(const FindFullHashesResponse* msg) { + return *msg->_impl_.minimum_wait_duration_; +} +const ::mozilla::safebrowsing::Duration& +FindFullHashesResponse::_Internal::negative_cache_duration(const FindFullHashesResponse* msg) { + return *msg->_impl_.negative_cache_duration_; +} +FindFullHashesResponse::FindFullHashesResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.FindFullHashesResponse) +} +FindFullHashesResponse::FindFullHashesResponse(const FindFullHashesResponse& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + FindFullHashesResponse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.matches_){from._impl_.matches_} + , decltype(_impl_.minimum_wait_duration_){nullptr} + , decltype(_impl_.negative_cache_duration_){nullptr}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_minimum_wait_duration()) { + _this->_impl_.minimum_wait_duration_ = new ::mozilla::safebrowsing::Duration(*from._impl_.minimum_wait_duration_); + } + if (from._internal_has_negative_cache_duration()) { + _this->_impl_.negative_cache_duration_ = new ::mozilla::safebrowsing::Duration(*from._impl_.negative_cache_duration_); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.FindFullHashesResponse) +} + +inline void FindFullHashesResponse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.matches_){arena} + , decltype(_impl_.minimum_wait_duration_){nullptr} + , decltype(_impl_.negative_cache_duration_){nullptr} + }; +} + +FindFullHashesResponse::~FindFullHashesResponse() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.FindFullHashesResponse) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void FindFullHashesResponse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.matches_.~RepeatedPtrField(); + if (this != internal_default_instance()) delete _impl_.minimum_wait_duration_; + if (this != internal_default_instance()) delete _impl_.negative_cache_duration_; +} + +void FindFullHashesResponse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void FindFullHashesResponse::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.FindFullHashesResponse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.matches_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.minimum_wait_duration_ != nullptr); + _impl_.minimum_wait_duration_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.negative_cache_duration_ != nullptr); + _impl_.negative_cache_duration_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* FindFullHashesResponse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_matches(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_minimum_wait_duration(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.Duration negative_cache_duration = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_negative_cache_duration(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* FindFullHashesResponse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.FindFullHashesResponse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_matches_size()); i < n; i++) { + const auto& repfield = this->_internal_matches(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, _Internal::minimum_wait_duration(this), + _Internal::minimum_wait_duration(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.Duration negative_cache_duration = 3; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::negative_cache_duration(this), + _Internal::negative_cache_duration(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.FindFullHashesResponse) + return target; +} + +size_t FindFullHashesResponse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.FindFullHashesResponse) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + total_size += 1UL * this->_internal_matches_size(); + for (const auto& msg : this->_impl_.matches_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.minimum_wait_duration_); + } + + // optional .mozilla.safebrowsing.Duration negative_cache_duration = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.negative_cache_duration_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FindFullHashesResponse::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const FindFullHashesResponse*>( + &from)); +} + +void FindFullHashesResponse::MergeFrom(const FindFullHashesResponse& from) { + FindFullHashesResponse* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.FindFullHashesResponse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.matches_.MergeFrom(from._impl_.matches_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_minimum_wait_duration()->::mozilla::safebrowsing::Duration::MergeFrom( + from._internal_minimum_wait_duration()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_negative_cache_duration()->::mozilla::safebrowsing::Duration::MergeFrom( + from._internal_negative_cache_duration()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void FindFullHashesResponse::CopyFrom(const FindFullHashesResponse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.FindFullHashesResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FindFullHashesResponse::IsInitialized() const { + return true; +} + +void FindFullHashesResponse::InternalSwap(FindFullHashesResponse* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.matches_.InternalSwap(&other->_impl_.matches_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(FindFullHashesResponse, _impl_.negative_cache_duration_) + + sizeof(FindFullHashesResponse::_impl_.negative_cache_duration_) + - PROTOBUF_FIELD_OFFSET(FindFullHashesResponse, _impl_.minimum_wait_duration_)>( + reinterpret_cast<char*>(&_impl_.minimum_wait_duration_), + reinterpret_cast<char*>(&other->_impl_.minimum_wait_duration_)); +} + +std::string FindFullHashesResponse::GetTypeName() const { + return "mozilla.safebrowsing.FindFullHashesResponse"; +} + + +// =================================================================== + +class ThreatHit_ThreatSource::_Internal { + public: + using HasBits = decltype(std::declval<ThreatHit_ThreatSource>()._impl_._has_bits_); + static void set_has_url(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_remote_ip(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_referrer(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +ThreatHit_ThreatSource::ThreatHit_ThreatSource(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatHit.ThreatSource) +} +ThreatHit_ThreatSource::ThreatHit_ThreatSource(const ThreatHit_ThreatSource& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatHit_ThreatSource* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.url_){} + , decltype(_impl_.remote_ip_){} + , decltype(_impl_.referrer_){} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_url()) { + _this->_impl_.url_.Set(from._internal_url(), + _this->GetArenaForAllocation()); + } + _impl_.remote_ip_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.remote_ip_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_remote_ip()) { + _this->_impl_.remote_ip_.Set(from._internal_remote_ip(), + _this->GetArenaForAllocation()); + } + _impl_.referrer_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.referrer_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_referrer()) { + _this->_impl_.referrer_.Set(from._internal_referrer(), + _this->GetArenaForAllocation()); + } + _this->_impl_.type_ = from._impl_.type_; + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatHit.ThreatSource) +} + +inline void ThreatHit_ThreatSource::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.url_){} + , decltype(_impl_.remote_ip_){} + , decltype(_impl_.referrer_){} + , decltype(_impl_.type_){0} + }; + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.remote_ip_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.remote_ip_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.referrer_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.referrer_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ThreatHit_ThreatSource::~ThreatHit_ThreatSource() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatHit.ThreatSource) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatHit_ThreatSource::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.url_.Destroy(); + _impl_.remote_ip_.Destroy(); + _impl_.referrer_.Destroy(); +} + +void ThreatHit_ThreatSource::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatHit_ThreatSource::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatHit.ThreatSource) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.url_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.remote_ip_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + _impl_.referrer_.ClearNonDefaultToEmpty(); + } + } + _impl_.type_ = 0; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatHit_ThreatSource::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string url = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + auto str = _internal_mutable_url(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatHit.ThreatSourceType type = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatHit_ThreatSourceType_IsValid(val))) { + _internal_set_type(static_cast<::mozilla::safebrowsing::ThreatHit_ThreatSourceType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional string remote_ip = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + auto str = _internal_mutable_remote_ip(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string referrer = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + auto str = _internal_mutable_referrer(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatHit_ThreatSource::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatHit.ThreatSource) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string url = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_url(), target); + } + + // optional .mozilla.safebrowsing.ThreatHit.ThreatSourceType type = 2; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_type(), target); + } + + // optional string remote_ip = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_remote_ip(), target); + } + + // optional string referrer = 4; + if (cached_has_bits & 0x00000004u) { + target = stream->WriteStringMaybeAliased( + 4, this->_internal_referrer(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatHit.ThreatSource) + return target; +} + +size_t ThreatHit_ThreatSource::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatHit.ThreatSource) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional string url = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_url()); + } + + // optional string remote_ip = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_remote_ip()); + } + + // optional string referrer = 4; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_referrer()); + } + + // optional .mozilla.safebrowsing.ThreatHit.ThreatSourceType type = 2; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatHit_ThreatSource::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatHit_ThreatSource*>( + &from)); +} + +void ThreatHit_ThreatSource::MergeFrom(const ThreatHit_ThreatSource& from) { + ThreatHit_ThreatSource* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatHit.ThreatSource) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_url(from._internal_url()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_remote_ip(from._internal_remote_ip()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_set_referrer(from._internal_referrer()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.type_ = from._impl_.type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatHit_ThreatSource::CopyFrom(const ThreatHit_ThreatSource& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatHit.ThreatSource) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatHit_ThreatSource::IsInitialized() const { + return true; +} + +void ThreatHit_ThreatSource::InternalSwap(ThreatHit_ThreatSource* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.url_, lhs_arena, + &other->_impl_.url_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.remote_ip_, lhs_arena, + &other->_impl_.remote_ip_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.referrer_, lhs_arena, + &other->_impl_.referrer_, rhs_arena + ); + swap(_impl_.type_, other->_impl_.type_); +} + +std::string ThreatHit_ThreatSource::GetTypeName() const { + return "mozilla.safebrowsing.ThreatHit.ThreatSource"; +} + + +// =================================================================== + +class ThreatHit_UserInfo::_Internal { + public: + using HasBits = decltype(std::declval<ThreatHit_UserInfo>()._impl_._has_bits_); + static void set_has_region_code(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_user_id(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +ThreatHit_UserInfo::ThreatHit_UserInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatHit.UserInfo) +} +ThreatHit_UserInfo::ThreatHit_UserInfo(const ThreatHit_UserInfo& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatHit_UserInfo* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.region_code_){} + , decltype(_impl_.user_id_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.region_code_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.region_code_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_region_code()) { + _this->_impl_.region_code_.Set(from._internal_region_code(), + _this->GetArenaForAllocation()); + } + _impl_.user_id_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.user_id_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_user_id()) { + _this->_impl_.user_id_.Set(from._internal_user_id(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatHit.UserInfo) +} + +inline void ThreatHit_UserInfo::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.region_code_){} + , decltype(_impl_.user_id_){} + }; + _impl_.region_code_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.region_code_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.user_id_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.user_id_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ThreatHit_UserInfo::~ThreatHit_UserInfo() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatHit.UserInfo) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatHit_UserInfo::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.region_code_.Destroy(); + _impl_.user_id_.Destroy(); +} + +void ThreatHit_UserInfo::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatHit_UserInfo::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatHit.UserInfo) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.region_code_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.user_id_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatHit_UserInfo::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string region_code = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + auto str = _internal_mutable_region_code(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes user_id = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + auto str = _internal_mutable_user_id(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatHit_UserInfo::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatHit.UserInfo) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string region_code = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_region_code(), target); + } + + // optional bytes user_id = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_user_id(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatHit.UserInfo) + return target; +} + +size_t ThreatHit_UserInfo::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatHit.UserInfo) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional string region_code = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_region_code()); + } + + // optional bytes user_id = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_user_id()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatHit_UserInfo::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatHit_UserInfo*>( + &from)); +} + +void ThreatHit_UserInfo::MergeFrom(const ThreatHit_UserInfo& from) { + ThreatHit_UserInfo* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatHit.UserInfo) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_region_code(from._internal_region_code()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_user_id(from._internal_user_id()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatHit_UserInfo::CopyFrom(const ThreatHit_UserInfo& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatHit.UserInfo) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatHit_UserInfo::IsInitialized() const { + return true; +} + +void ThreatHit_UserInfo::InternalSwap(ThreatHit_UserInfo* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.region_code_, lhs_arena, + &other->_impl_.region_code_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.user_id_, lhs_arena, + &other->_impl_.user_id_, rhs_arena + ); +} + +std::string ThreatHit_UserInfo::GetTypeName() const { + return "mozilla.safebrowsing.ThreatHit.UserInfo"; +} + + +// =================================================================== + +class ThreatHit::_Internal { + public: + using HasBits = decltype(std::declval<ThreatHit>()._impl_._has_bits_); + static void set_has_threat_type(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_platform_type(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static const ::mozilla::safebrowsing::ThreatEntry& entry(const ThreatHit* msg); + static void set_has_entry(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::ClientInfo& client_info(const ThreatHit* msg); + static void set_has_client_info(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::mozilla::safebrowsing::ThreatHit_UserInfo& user_info(const ThreatHit* msg); + static void set_has_user_info(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +const ::mozilla::safebrowsing::ThreatEntry& +ThreatHit::_Internal::entry(const ThreatHit* msg) { + return *msg->_impl_.entry_; +} +const ::mozilla::safebrowsing::ClientInfo& +ThreatHit::_Internal::client_info(const ThreatHit* msg) { + return *msg->_impl_.client_info_; +} +const ::mozilla::safebrowsing::ThreatHit_UserInfo& +ThreatHit::_Internal::user_info(const ThreatHit* msg) { + return *msg->_impl_.user_info_; +} +ThreatHit::ThreatHit(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatHit) +} +ThreatHit::ThreatHit(const ThreatHit& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatHit* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.resources_){from._impl_.resources_} + , decltype(_impl_.entry_){nullptr} + , decltype(_impl_.client_info_){nullptr} + , decltype(_impl_.user_info_){nullptr} + , decltype(_impl_.threat_type_){} + , decltype(_impl_.platform_type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_entry()) { + _this->_impl_.entry_ = new ::mozilla::safebrowsing::ThreatEntry(*from._impl_.entry_); + } + if (from._internal_has_client_info()) { + _this->_impl_.client_info_ = new ::mozilla::safebrowsing::ClientInfo(*from._impl_.client_info_); + } + if (from._internal_has_user_info()) { + _this->_impl_.user_info_ = new ::mozilla::safebrowsing::ThreatHit_UserInfo(*from._impl_.user_info_); + } + ::memcpy(&_impl_.threat_type_, &from._impl_.threat_type_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.platform_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.platform_type_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatHit) +} + +inline void ThreatHit::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.resources_){arena} + , decltype(_impl_.entry_){nullptr} + , decltype(_impl_.client_info_){nullptr} + , decltype(_impl_.user_info_){nullptr} + , decltype(_impl_.threat_type_){0} + , decltype(_impl_.platform_type_){0} + }; +} + +ThreatHit::~ThreatHit() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatHit) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatHit::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.resources_.~RepeatedPtrField(); + if (this != internal_default_instance()) delete _impl_.entry_; + if (this != internal_default_instance()) delete _impl_.client_info_; + if (this != internal_default_instance()) delete _impl_.user_info_; +} + +void ThreatHit::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatHit::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatHit) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.resources_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.entry_ != nullptr); + _impl_.entry_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.client_info_ != nullptr); + _impl_.client_info_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.user_info_ != nullptr); + _impl_.user_info_->Clear(); + } + } + if (cached_has_bits & 0x00000018u) { + ::memset(&_impl_.threat_type_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.platform_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.platform_type_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatHit::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatType_IsValid(val))) { + _internal_set_threat_type(static_cast<::mozilla::safebrowsing::ThreatType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::PlatformType_IsValid(val))) { + _internal_set_platform_type(static_cast<::mozilla::safebrowsing::PlatformType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntry entry = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_entry(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .mozilla.safebrowsing.ThreatHit.ThreatSource resources = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_resources(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<34>(ptr)); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ClientInfo client_info = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_client_info(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatHit.UserInfo user_info = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 50)) { + ptr = ctx->ParseMessage(_internal_mutable_user_info(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatHit::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatHit) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_threat_type(), target); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_platform_type(), target); + } + + // optional .mozilla.safebrowsing.ThreatEntry entry = 3; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::entry(this), + _Internal::entry(this).GetCachedSize(), target, stream); + } + + // repeated .mozilla.safebrowsing.ThreatHit.ThreatSource resources = 4; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_resources_size()); i < n; i++) { + const auto& repfield = this->_internal_resources(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, repfield, repfield.GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ClientInfo client_info = 5; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::client_info(this), + _Internal::client_info(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.ThreatHit.UserInfo user_info = 6; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(6, _Internal::user_info(this), + _Internal::user_info(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatHit) + return target; +} + +size_t ThreatHit::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatHit) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatHit.ThreatSource resources = 4; + total_size += 1UL * this->_internal_resources_size(); + for (const auto& msg : this->_impl_.resources_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + // optional .mozilla.safebrowsing.ThreatEntry entry = 3; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.entry_); + } + + // optional .mozilla.safebrowsing.ClientInfo client_info = 5; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.client_info_); + } + + // optional .mozilla.safebrowsing.ThreatHit.UserInfo user_info = 6; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.user_info_); + } + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_type()); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_platform_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatHit::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatHit*>( + &from)); +} + +void ThreatHit::MergeFrom(const ThreatHit& from) { + ThreatHit* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatHit) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.resources_.MergeFrom(from._impl_.resources_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_entry()->::mozilla::safebrowsing::ThreatEntry::MergeFrom( + from._internal_entry()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_client_info()->::mozilla::safebrowsing::ClientInfo::MergeFrom( + from._internal_client_info()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_user_info()->::mozilla::safebrowsing::ThreatHit_UserInfo::MergeFrom( + from._internal_user_info()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.threat_type_ = from._impl_.threat_type_; + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.platform_type_ = from._impl_.platform_type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatHit::CopyFrom(const ThreatHit& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatHit) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatHit::IsInitialized() const { + return true; +} + +void ThreatHit::InternalSwap(ThreatHit* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.resources_.InternalSwap(&other->_impl_.resources_); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ThreatHit, _impl_.platform_type_) + + sizeof(ThreatHit::_impl_.platform_type_) + - PROTOBUF_FIELD_OFFSET(ThreatHit, _impl_.entry_)>( + reinterpret_cast<char*>(&_impl_.entry_), + reinterpret_cast<char*>(&other->_impl_.entry_)); +} + +std::string ThreatHit::GetTypeName() const { + return "mozilla.safebrowsing.ThreatHit"; +} + + +// =================================================================== + +class ClientInfo::_Internal { + public: + using HasBits = decltype(std::declval<ClientInfo>()._impl_._has_bits_); + static void set_has_client_id(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_client_version(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +ClientInfo::ClientInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ClientInfo) +} +ClientInfo::ClientInfo(const ClientInfo& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ClientInfo* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.client_id_){} + , decltype(_impl_.client_version_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.client_id_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.client_id_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_client_id()) { + _this->_impl_.client_id_.Set(from._internal_client_id(), + _this->GetArenaForAllocation()); + } + _impl_.client_version_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.client_version_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_client_version()) { + _this->_impl_.client_version_.Set(from._internal_client_version(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ClientInfo) +} + +inline void ClientInfo::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.client_id_){} + , decltype(_impl_.client_version_){} + }; + _impl_.client_id_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.client_id_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.client_version_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.client_version_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ClientInfo::~ClientInfo() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ClientInfo) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ClientInfo::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.client_id_.Destroy(); + _impl_.client_version_.Destroy(); +} + +void ClientInfo::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ClientInfo::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ClientInfo) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.client_id_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.client_version_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ClientInfo::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string client_id = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + auto str = _internal_mutable_client_id(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string client_version = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + auto str = _internal_mutable_client_version(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ClientInfo::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ClientInfo) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string client_id = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_client_id(), target); + } + + // optional string client_version = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_client_version(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ClientInfo) + return target; +} + +size_t ClientInfo::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ClientInfo) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional string client_id = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_client_id()); + } + + // optional string client_version = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_client_version()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ClientInfo::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ClientInfo*>( + &from)); +} + +void ClientInfo::MergeFrom(const ClientInfo& from) { + ClientInfo* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ClientInfo) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_client_id(from._internal_client_id()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_client_version(from._internal_client_version()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ClientInfo::CopyFrom(const ClientInfo& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ClientInfo) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ClientInfo::IsInitialized() const { + return true; +} + +void ClientInfo::InternalSwap(ClientInfo* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.client_id_, lhs_arena, + &other->_impl_.client_id_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.client_version_, lhs_arena, + &other->_impl_.client_version_, rhs_arena + ); +} + +std::string ClientInfo::GetTypeName() const { + return "mozilla.safebrowsing.ClientInfo"; +} + + +// =================================================================== + +class ChromeClientInfo::_Internal { + public: + using HasBits = decltype(std::declval<ChromeClientInfo>()._impl_._has_bits_); + static void set_has_reporting_population(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +ChromeClientInfo::ChromeClientInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ChromeClientInfo) +} +ChromeClientInfo::ChromeClientInfo(const ChromeClientInfo& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ChromeClientInfo* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.reporting_population_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _this->_impl_.reporting_population_ = from._impl_.reporting_population_; + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ChromeClientInfo) +} + +inline void ChromeClientInfo::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.reporting_population_){0} + }; +} + +ChromeClientInfo::~ChromeClientInfo() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ChromeClientInfo) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ChromeClientInfo::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void ChromeClientInfo::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ChromeClientInfo::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ChromeClientInfo) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.reporting_population_ = 0; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ChromeClientInfo::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ChromeClientInfo.SafeBrowsingReportingPopulation reporting_population = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation_IsValid(val))) { + _internal_set_reporting_population(static_cast<::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ChromeClientInfo::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ChromeClientInfo) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ChromeClientInfo.SafeBrowsingReportingPopulation reporting_population = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_reporting_population(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ChromeClientInfo) + return target; +} + +size_t ChromeClientInfo::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ChromeClientInfo) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // optional .mozilla.safebrowsing.ChromeClientInfo.SafeBrowsingReportingPopulation reporting_population = 1; + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_reporting_population()); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ChromeClientInfo::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ChromeClientInfo*>( + &from)); +} + +void ChromeClientInfo::MergeFrom(const ChromeClientInfo& from) { + ChromeClientInfo* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ChromeClientInfo) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_has_reporting_population()) { + _this->_internal_set_reporting_population(from._internal_reporting_population()); + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ChromeClientInfo::CopyFrom(const ChromeClientInfo& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ChromeClientInfo) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ChromeClientInfo::IsInitialized() const { + return true; +} + +void ChromeClientInfo::InternalSwap(ChromeClientInfo* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.reporting_population_, other->_impl_.reporting_population_); +} + +std::string ChromeClientInfo::GetTypeName() const { + return "mozilla.safebrowsing.ChromeClientInfo"; +} + + +// =================================================================== + +class Checksum::_Internal { + public: + using HasBits = decltype(std::declval<Checksum>()._impl_._has_bits_); + static void set_has_sha256(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +Checksum::Checksum(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.Checksum) +} +Checksum::Checksum(const Checksum& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + Checksum* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.sha256_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.sha256_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.sha256_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_sha256()) { + _this->_impl_.sha256_.Set(from._internal_sha256(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.Checksum) +} + +inline void Checksum::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.sha256_){} + }; + _impl_.sha256_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.sha256_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +Checksum::~Checksum() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.Checksum) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Checksum::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.sha256_.Destroy(); +} + +void Checksum::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Checksum::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.Checksum) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + _impl_.sha256_.ClearNonDefaultToEmpty(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* Checksum::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional bytes sha256 = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + auto str = _internal_mutable_sha256(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Checksum::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.Checksum) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional bytes sha256 = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_sha256(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.Checksum) + return target; +} + +size_t Checksum::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.Checksum) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // optional bytes sha256 = 1; + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_sha256()); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void Checksum::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const Checksum*>( + &from)); +} + +void Checksum::MergeFrom(const Checksum& from) { + Checksum* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.Checksum) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_has_sha256()) { + _this->_internal_set_sha256(from._internal_sha256()); + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void Checksum::CopyFrom(const Checksum& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.Checksum) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Checksum::IsInitialized() const { + return true; +} + +void Checksum::InternalSwap(Checksum* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.sha256_, lhs_arena, + &other->_impl_.sha256_, rhs_arena + ); +} + +std::string Checksum::GetTypeName() const { + return "mozilla.safebrowsing.Checksum"; +} + + +// =================================================================== + +class ThreatEntry::_Internal { + public: + using HasBits = decltype(std::declval<ThreatEntry>()._impl_._has_bits_); + static void set_has_hash(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_url(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +ThreatEntry::ThreatEntry(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatEntry) +} +ThreatEntry::ThreatEntry(const ThreatEntry& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatEntry* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.hash_){} + , decltype(_impl_.url_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.hash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_hash()) { + _this->_impl_.hash_.Set(from._internal_hash(), + _this->GetArenaForAllocation()); + } + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_url()) { + _this->_impl_.url_.Set(from._internal_url(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatEntry) +} + +inline void ThreatEntry::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.hash_){} + , decltype(_impl_.url_){} + }; + _impl_.hash_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.hash_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ThreatEntry::~ThreatEntry() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatEntry) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatEntry::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.hash_.Destroy(); + _impl_.url_.Destroy(); +} + +void ThreatEntry::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatEntry::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatEntry) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.hash_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.url_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatEntry::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional bytes hash = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + auto str = _internal_mutable_hash(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string url = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + auto str = _internal_mutable_url(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatEntry::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatEntry) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional bytes hash = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_hash(), target); + } + + // optional string url = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_url(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatEntry) + return target; +} + +size_t ThreatEntry::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatEntry) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional bytes hash = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_hash()); + } + + // optional string url = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_url()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatEntry::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatEntry*>( + &from)); +} + +void ThreatEntry::MergeFrom(const ThreatEntry& from) { + ThreatEntry* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatEntry) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_hash(from._internal_hash()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_url(from._internal_url()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatEntry::CopyFrom(const ThreatEntry& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatEntry) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatEntry::IsInitialized() const { + return true; +} + +void ThreatEntry::InternalSwap(ThreatEntry* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.hash_, lhs_arena, + &other->_impl_.hash_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.url_, lhs_arena, + &other->_impl_.url_, rhs_arena + ); +} + +std::string ThreatEntry::GetTypeName() const { + return "mozilla.safebrowsing.ThreatEntry"; +} + + +// =================================================================== + +class ThreatEntrySet::_Internal { + public: + using HasBits = decltype(std::declval<ThreatEntrySet>()._impl_._has_bits_); + static void set_has_compression_type(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static const ::mozilla::safebrowsing::RawHashes& raw_hashes(const ThreatEntrySet* msg); + static void set_has_raw_hashes(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::safebrowsing::RawIndices& raw_indices(const ThreatEntrySet* msg); + static void set_has_raw_indices(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::mozilla::safebrowsing::RiceDeltaEncoding& rice_hashes(const ThreatEntrySet* msg); + static void set_has_rice_hashes(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static const ::mozilla::safebrowsing::RiceDeltaEncoding& rice_indices(const ThreatEntrySet* msg); + static void set_has_rice_indices(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +const ::mozilla::safebrowsing::RawHashes& +ThreatEntrySet::_Internal::raw_hashes(const ThreatEntrySet* msg) { + return *msg->_impl_.raw_hashes_; +} +const ::mozilla::safebrowsing::RawIndices& +ThreatEntrySet::_Internal::raw_indices(const ThreatEntrySet* msg) { + return *msg->_impl_.raw_indices_; +} +const ::mozilla::safebrowsing::RiceDeltaEncoding& +ThreatEntrySet::_Internal::rice_hashes(const ThreatEntrySet* msg) { + return *msg->_impl_.rice_hashes_; +} +const ::mozilla::safebrowsing::RiceDeltaEncoding& +ThreatEntrySet::_Internal::rice_indices(const ThreatEntrySet* msg) { + return *msg->_impl_.rice_indices_; +} +ThreatEntrySet::ThreatEntrySet(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatEntrySet) +} +ThreatEntrySet::ThreatEntrySet(const ThreatEntrySet& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatEntrySet* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.raw_hashes_){nullptr} + , decltype(_impl_.raw_indices_){nullptr} + , decltype(_impl_.rice_hashes_){nullptr} + , decltype(_impl_.rice_indices_){nullptr} + , decltype(_impl_.compression_type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + if (from._internal_has_raw_hashes()) { + _this->_impl_.raw_hashes_ = new ::mozilla::safebrowsing::RawHashes(*from._impl_.raw_hashes_); + } + if (from._internal_has_raw_indices()) { + _this->_impl_.raw_indices_ = new ::mozilla::safebrowsing::RawIndices(*from._impl_.raw_indices_); + } + if (from._internal_has_rice_hashes()) { + _this->_impl_.rice_hashes_ = new ::mozilla::safebrowsing::RiceDeltaEncoding(*from._impl_.rice_hashes_); + } + if (from._internal_has_rice_indices()) { + _this->_impl_.rice_indices_ = new ::mozilla::safebrowsing::RiceDeltaEncoding(*from._impl_.rice_indices_); + } + _this->_impl_.compression_type_ = from._impl_.compression_type_; + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatEntrySet) +} + +inline void ThreatEntrySet::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.raw_hashes_){nullptr} + , decltype(_impl_.raw_indices_){nullptr} + , decltype(_impl_.rice_hashes_){nullptr} + , decltype(_impl_.rice_indices_){nullptr} + , decltype(_impl_.compression_type_){0} + }; +} + +ThreatEntrySet::~ThreatEntrySet() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatEntrySet) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatEntrySet::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.raw_hashes_; + if (this != internal_default_instance()) delete _impl_.raw_indices_; + if (this != internal_default_instance()) delete _impl_.rice_hashes_; + if (this != internal_default_instance()) delete _impl_.rice_indices_; +} + +void ThreatEntrySet::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatEntrySet::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatEntrySet) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.raw_hashes_ != nullptr); + _impl_.raw_hashes_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.raw_indices_ != nullptr); + _impl_.raw_indices_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.rice_hashes_ != nullptr); + _impl_.rice_hashes_->Clear(); + } + if (cached_has_bits & 0x00000008u) { + GOOGLE_DCHECK(_impl_.rice_indices_ != nullptr); + _impl_.rice_indices_->Clear(); + } + } + _impl_.compression_type_ = 0; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatEntrySet::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.CompressionType compression_type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::CompressionType_IsValid(val))) { + _internal_set_compression_type(static_cast<::mozilla::safebrowsing::CompressionType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.RawHashes raw_hashes = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_raw_hashes(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.RawIndices raw_indices = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_raw_indices(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_hashes = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_rice_hashes(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_indices = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_rice_indices(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatEntrySet::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatEntrySet) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.CompressionType compression_type = 1; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_compression_type(), target); + } + + // optional .mozilla.safebrowsing.RawHashes raw_hashes = 2; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, _Internal::raw_hashes(this), + _Internal::raw_hashes(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.RawIndices raw_indices = 3; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::raw_indices(this), + _Internal::raw_indices(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_hashes = 4; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::rice_hashes(this), + _Internal::rice_hashes(this).GetCachedSize(), target, stream); + } + + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_indices = 5; + if (cached_has_bits & 0x00000008u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::rice_indices(this), + _Internal::rice_indices(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatEntrySet) + return target; +} + +size_t ThreatEntrySet::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatEntrySet) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + // optional .mozilla.safebrowsing.RawHashes raw_hashes = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.raw_hashes_); + } + + // optional .mozilla.safebrowsing.RawIndices raw_indices = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.raw_indices_); + } + + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_hashes = 4; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.rice_hashes_); + } + + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_indices = 5; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.rice_indices_); + } + + // optional .mozilla.safebrowsing.CompressionType compression_type = 1; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_compression_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatEntrySet::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatEntrySet*>( + &from)); +} + +void ThreatEntrySet::MergeFrom(const ThreatEntrySet& from) { + ThreatEntrySet* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatEntrySet) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_raw_hashes()->::mozilla::safebrowsing::RawHashes::MergeFrom( + from._internal_raw_hashes()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_raw_indices()->::mozilla::safebrowsing::RawIndices::MergeFrom( + from._internal_raw_indices()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_rice_hashes()->::mozilla::safebrowsing::RiceDeltaEncoding::MergeFrom( + from._internal_rice_hashes()); + } + if (cached_has_bits & 0x00000008u) { + _this->_internal_mutable_rice_indices()->::mozilla::safebrowsing::RiceDeltaEncoding::MergeFrom( + from._internal_rice_indices()); + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.compression_type_ = from._impl_.compression_type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatEntrySet::CopyFrom(const ThreatEntrySet& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatEntrySet) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatEntrySet::IsInitialized() const { + return true; +} + +void ThreatEntrySet::InternalSwap(ThreatEntrySet* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ThreatEntrySet, _impl_.compression_type_) + + sizeof(ThreatEntrySet::_impl_.compression_type_) + - PROTOBUF_FIELD_OFFSET(ThreatEntrySet, _impl_.raw_hashes_)>( + reinterpret_cast<char*>(&_impl_.raw_hashes_), + reinterpret_cast<char*>(&other->_impl_.raw_hashes_)); +} + +std::string ThreatEntrySet::GetTypeName() const { + return "mozilla.safebrowsing.ThreatEntrySet"; +} + + +// =================================================================== + +class RawIndices::_Internal { + public: +}; + +RawIndices::RawIndices(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.RawIndices) +} +RawIndices::RawIndices(const RawIndices& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + RawIndices* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.indices_){from._impl_.indices_} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.RawIndices) +} + +inline void RawIndices::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.indices_){arena} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +RawIndices::~RawIndices() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.RawIndices) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RawIndices::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.indices_.~RepeatedField(); +} + +void RawIndices::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RawIndices::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.RawIndices) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.indices_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* RawIndices::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated int32 indices = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_indices(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr)); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<8>(ptr)); + } else if (static_cast<uint8_t>(tag) == 10) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedInt32Parser(_internal_mutable_indices(), ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RawIndices::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.RawIndices) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated int32 indices = 1; + for (int i = 0, n = this->_internal_indices_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(1, this->_internal_indices(i), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.RawIndices) + return target; +} + +size_t RawIndices::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.RawIndices) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated int32 indices = 1; + { + size_t data_size = ::_pbi::WireFormatLite:: + Int32Size(this->_impl_.indices_); + total_size += 1 * + ::_pbi::FromIntSize(this->_internal_indices_size()); + total_size += data_size; + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void RawIndices::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const RawIndices*>( + &from)); +} + +void RawIndices::MergeFrom(const RawIndices& from) { + RawIndices* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.RawIndices) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.indices_.MergeFrom(from._impl_.indices_); + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void RawIndices::CopyFrom(const RawIndices& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.RawIndices) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RawIndices::IsInitialized() const { + return true; +} + +void RawIndices::InternalSwap(RawIndices* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.indices_.InternalSwap(&other->_impl_.indices_); +} + +std::string RawIndices::GetTypeName() const { + return "mozilla.safebrowsing.RawIndices"; +} + + +// =================================================================== + +class RawHashes::_Internal { + public: + using HasBits = decltype(std::declval<RawHashes>()._impl_._has_bits_); + static void set_has_prefix_size(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_raw_hashes(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +RawHashes::RawHashes(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.RawHashes) +} +RawHashes::RawHashes(const RawHashes& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + RawHashes* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.raw_hashes_){} + , decltype(_impl_.prefix_size_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.raw_hashes_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.raw_hashes_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_raw_hashes()) { + _this->_impl_.raw_hashes_.Set(from._internal_raw_hashes(), + _this->GetArenaForAllocation()); + } + _this->_impl_.prefix_size_ = from._impl_.prefix_size_; + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.RawHashes) +} + +inline void RawHashes::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.raw_hashes_){} + , decltype(_impl_.prefix_size_){0} + }; + _impl_.raw_hashes_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.raw_hashes_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +RawHashes::~RawHashes() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.RawHashes) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RawHashes::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.raw_hashes_.Destroy(); +} + +void RawHashes::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RawHashes::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.RawHashes) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + _impl_.raw_hashes_.ClearNonDefaultToEmpty(); + } + _impl_.prefix_size_ = 0; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* RawHashes::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional int32 prefix_size = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + _Internal::set_has_prefix_size(&has_bits); + _impl_.prefix_size_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes raw_hashes = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + auto str = _internal_mutable_raw_hashes(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RawHashes::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.RawHashes) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional int32 prefix_size = 1; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(1, this->_internal_prefix_size(), target); + } + + // optional bytes raw_hashes = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_raw_hashes(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.RawHashes) + return target; +} + +size_t RawHashes::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.RawHashes) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional bytes raw_hashes = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_raw_hashes()); + } + + // optional int32 prefix_size = 1; + if (cached_has_bits & 0x00000002u) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_prefix_size()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void RawHashes::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const RawHashes*>( + &from)); +} + +void RawHashes::MergeFrom(const RawHashes& from) { + RawHashes* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.RawHashes) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_raw_hashes(from._internal_raw_hashes()); + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.prefix_size_ = from._impl_.prefix_size_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void RawHashes::CopyFrom(const RawHashes& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.RawHashes) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RawHashes::IsInitialized() const { + return true; +} + +void RawHashes::InternalSwap(RawHashes* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.raw_hashes_, lhs_arena, + &other->_impl_.raw_hashes_, rhs_arena + ); + swap(_impl_.prefix_size_, other->_impl_.prefix_size_); +} + +std::string RawHashes::GetTypeName() const { + return "mozilla.safebrowsing.RawHashes"; +} + + +// =================================================================== + +class RiceDeltaEncoding::_Internal { + public: + using HasBits = decltype(std::declval<RiceDeltaEncoding>()._impl_._has_bits_); + static void set_has_first_value(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_rice_parameter(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_num_entries(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_encoded_data(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +RiceDeltaEncoding::RiceDeltaEncoding(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.RiceDeltaEncoding) +} +RiceDeltaEncoding::RiceDeltaEncoding(const RiceDeltaEncoding& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + RiceDeltaEncoding* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.encoded_data_){} + , decltype(_impl_.first_value_){} + , decltype(_impl_.rice_parameter_){} + , decltype(_impl_.num_entries_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.encoded_data_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.encoded_data_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_encoded_data()) { + _this->_impl_.encoded_data_.Set(from._internal_encoded_data(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.first_value_, &from._impl_.first_value_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.num_entries_) - + reinterpret_cast<char*>(&_impl_.first_value_)) + sizeof(_impl_.num_entries_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.RiceDeltaEncoding) +} + +inline void RiceDeltaEncoding::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.encoded_data_){} + , decltype(_impl_.first_value_){int64_t{0}} + , decltype(_impl_.rice_parameter_){0} + , decltype(_impl_.num_entries_){0} + }; + _impl_.encoded_data_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.encoded_data_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +RiceDeltaEncoding::~RiceDeltaEncoding() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.RiceDeltaEncoding) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void RiceDeltaEncoding::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.encoded_data_.Destroy(); +} + +void RiceDeltaEncoding::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void RiceDeltaEncoding::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.RiceDeltaEncoding) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + _impl_.encoded_data_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x0000000eu) { + ::memset(&_impl_.first_value_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.num_entries_) - + reinterpret_cast<char*>(&_impl_.first_value_)) + sizeof(_impl_.num_entries_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* RiceDeltaEncoding::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional int64 first_value = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + _Internal::set_has_first_value(&has_bits); + _impl_.first_value_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional int32 rice_parameter = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + _Internal::set_has_rice_parameter(&has_bits); + _impl_.rice_parameter_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional int32 num_entries = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) { + _Internal::set_has_num_entries(&has_bits); + _impl_.num_entries_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes encoded_data = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) { + auto str = _internal_mutable_encoded_data(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* RiceDeltaEncoding::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.RiceDeltaEncoding) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional int64 first_value = 1; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt64ToArray(1, this->_internal_first_value(), target); + } + + // optional int32 rice_parameter = 2; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(2, this->_internal_rice_parameter(), target); + } + + // optional int32 num_entries = 3; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(3, this->_internal_num_entries(), target); + } + + // optional bytes encoded_data = 4; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 4, this->_internal_encoded_data(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.RiceDeltaEncoding) + return target; +} + +size_t RiceDeltaEncoding::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.RiceDeltaEncoding) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional bytes encoded_data = 4; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_encoded_data()); + } + + // optional int64 first_value = 1; + if (cached_has_bits & 0x00000002u) { + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_first_value()); + } + + // optional int32 rice_parameter = 2; + if (cached_has_bits & 0x00000004u) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_rice_parameter()); + } + + // optional int32 num_entries = 3; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_num_entries()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void RiceDeltaEncoding::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const RiceDeltaEncoding*>( + &from)); +} + +void RiceDeltaEncoding::MergeFrom(const RiceDeltaEncoding& from) { + RiceDeltaEncoding* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.RiceDeltaEncoding) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_encoded_data(from._internal_encoded_data()); + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.first_value_ = from._impl_.first_value_; + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.rice_parameter_ = from._impl_.rice_parameter_; + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.num_entries_ = from._impl_.num_entries_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void RiceDeltaEncoding::CopyFrom(const RiceDeltaEncoding& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.RiceDeltaEncoding) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool RiceDeltaEncoding::IsInitialized() const { + return true; +} + +void RiceDeltaEncoding::InternalSwap(RiceDeltaEncoding* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.encoded_data_, lhs_arena, + &other->_impl_.encoded_data_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(RiceDeltaEncoding, _impl_.num_entries_) + + sizeof(RiceDeltaEncoding::_impl_.num_entries_) + - PROTOBUF_FIELD_OFFSET(RiceDeltaEncoding, _impl_.first_value_)>( + reinterpret_cast<char*>(&_impl_.first_value_), + reinterpret_cast<char*>(&other->_impl_.first_value_)); +} + +std::string RiceDeltaEncoding::GetTypeName() const { + return "mozilla.safebrowsing.RiceDeltaEncoding"; +} + + +// =================================================================== + +class ThreatEntryMetadata_MetadataEntry::_Internal { + public: + using HasBits = decltype(std::declval<ThreatEntryMetadata_MetadataEntry>()._impl_._has_bits_); + static void set_has_key(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_value(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +ThreatEntryMetadata_MetadataEntry::ThreatEntryMetadata_MetadataEntry(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) +} +ThreatEntryMetadata_MetadataEntry::ThreatEntryMetadata_MetadataEntry(const ThreatEntryMetadata_MetadataEntry& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatEntryMetadata_MetadataEntry* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.key_){} + , decltype(_impl_.value_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + _impl_.key_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.key_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_key()) { + _this->_impl_.key_.Set(from._internal_key(), + _this->GetArenaForAllocation()); + } + _impl_.value_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.value_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_value()) { + _this->_impl_.value_.Set(from._internal_value(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) +} + +inline void ThreatEntryMetadata_MetadataEntry::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.key_){} + , decltype(_impl_.value_){} + }; + _impl_.key_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.key_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.value_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.value_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ThreatEntryMetadata_MetadataEntry::~ThreatEntryMetadata_MetadataEntry() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatEntryMetadata_MetadataEntry::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.key_.Destroy(); + _impl_.value_.Destroy(); +} + +void ThreatEntryMetadata_MetadataEntry::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatEntryMetadata_MetadataEntry::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.key_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.value_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatEntryMetadata_MetadataEntry::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional bytes key = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + auto str = _internal_mutable_key(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes value = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) { + auto str = _internal_mutable_value(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatEntryMetadata_MetadataEntry::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional bytes key = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_key(), target); + } + + // optional bytes value = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_value(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + return target; +} + +size_t ThreatEntryMetadata_MetadataEntry::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional bytes key = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_key()); + } + + // optional bytes value = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_value()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatEntryMetadata_MetadataEntry::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatEntryMetadata_MetadataEntry*>( + &from)); +} + +void ThreatEntryMetadata_MetadataEntry::MergeFrom(const ThreatEntryMetadata_MetadataEntry& from) { + ThreatEntryMetadata_MetadataEntry* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_key(from._internal_key()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_value(from._internal_value()); + } + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatEntryMetadata_MetadataEntry::CopyFrom(const ThreatEntryMetadata_MetadataEntry& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatEntryMetadata_MetadataEntry::IsInitialized() const { + return true; +} + +void ThreatEntryMetadata_MetadataEntry::InternalSwap(ThreatEntryMetadata_MetadataEntry* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.key_, lhs_arena, + &other->_impl_.key_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.value_, lhs_arena, + &other->_impl_.value_, rhs_arena + ); +} + +std::string ThreatEntryMetadata_MetadataEntry::GetTypeName() const { + return "mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry"; +} + + +// =================================================================== + +class ThreatEntryMetadata::_Internal { + public: +}; + +ThreatEntryMetadata::ThreatEntryMetadata(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatEntryMetadata) +} +ThreatEntryMetadata::ThreatEntryMetadata(const ThreatEntryMetadata& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatEntryMetadata* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.entries_){from._impl_.entries_} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatEntryMetadata) +} + +inline void ThreatEntryMetadata::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.entries_){arena} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +ThreatEntryMetadata::~ThreatEntryMetadata() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatEntryMetadata) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatEntryMetadata::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.entries_.~RepeatedPtrField(); +} + +void ThreatEntryMetadata::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatEntryMetadata::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatEntryMetadata) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.entries_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatEntryMetadata::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry entries = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_entries(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatEntryMetadata::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatEntryMetadata) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry entries = 1; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_entries_size()); i < n; i++) { + const auto& repfield = this->_internal_entries(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatEntryMetadata) + return target; +} + +size_t ThreatEntryMetadata::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatEntryMetadata) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry entries = 1; + total_size += 1UL * this->_internal_entries_size(); + for (const auto& msg : this->_impl_.entries_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatEntryMetadata::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatEntryMetadata*>( + &from)); +} + +void ThreatEntryMetadata::MergeFrom(const ThreatEntryMetadata& from) { + ThreatEntryMetadata* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatEntryMetadata) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.entries_.MergeFrom(from._impl_.entries_); + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatEntryMetadata::CopyFrom(const ThreatEntryMetadata& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatEntryMetadata) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatEntryMetadata::IsInitialized() const { + return true; +} + +void ThreatEntryMetadata::InternalSwap(ThreatEntryMetadata* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.entries_.InternalSwap(&other->_impl_.entries_); +} + +std::string ThreatEntryMetadata::GetTypeName() const { + return "mozilla.safebrowsing.ThreatEntryMetadata"; +} + + +// =================================================================== + +class ThreatListDescriptor::_Internal { + public: + using HasBits = decltype(std::declval<ThreatListDescriptor>()._impl_._has_bits_); + static void set_has_threat_type(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_platform_type(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_threat_entry_type(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +ThreatListDescriptor::ThreatListDescriptor(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ThreatListDescriptor) +} +ThreatListDescriptor::ThreatListDescriptor(const ThreatListDescriptor& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ThreatListDescriptor* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.threat_type_){} + , decltype(_impl_.platform_type_){} + , decltype(_impl_.threat_entry_type_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + ::memcpy(&_impl_.threat_type_, &from._impl_.threat_type_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.threat_entry_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.threat_entry_type_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ThreatListDescriptor) +} + +inline void ThreatListDescriptor::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.threat_type_){0} + , decltype(_impl_.platform_type_){0} + , decltype(_impl_.threat_entry_type_){0} + }; +} + +ThreatListDescriptor::~ThreatListDescriptor() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ThreatListDescriptor) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ThreatListDescriptor::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void ThreatListDescriptor::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ThreatListDescriptor::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ThreatListDescriptor) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + ::memset(&_impl_.threat_type_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.threat_entry_type_) - + reinterpret_cast<char*>(&_impl_.threat_type_)) + sizeof(_impl_.threat_entry_type_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ThreatListDescriptor::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatType_IsValid(val))) { + _internal_set_threat_type(static_cast<::mozilla::safebrowsing::ThreatType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::PlatformType_IsValid(val))) { + _internal_set_platform_type(static_cast<::mozilla::safebrowsing::PlatformType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::safebrowsing::ThreatEntryType_IsValid(val))) { + _internal_set_threat_entry_type(static_cast<::mozilla::safebrowsing::ThreatEntryType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(3, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ThreatListDescriptor::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ThreatListDescriptor) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_threat_type(), target); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_platform_type(), target); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 3, this->_internal_threat_entry_type(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ThreatListDescriptor) + return target; +} + +size_t ThreatListDescriptor::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ThreatListDescriptor) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_type()); + } + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_platform_type()); + } + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_threat_entry_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ThreatListDescriptor::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ThreatListDescriptor*>( + &from)); +} + +void ThreatListDescriptor::MergeFrom(const ThreatListDescriptor& from) { + ThreatListDescriptor* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ThreatListDescriptor) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_impl_.threat_type_ = from._impl_.threat_type_; + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.platform_type_ = from._impl_.platform_type_; + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.threat_entry_type_ = from._impl_.threat_entry_type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ThreatListDescriptor::CopyFrom(const ThreatListDescriptor& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ThreatListDescriptor) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ThreatListDescriptor::IsInitialized() const { + return true; +} + +void ThreatListDescriptor::InternalSwap(ThreatListDescriptor* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ThreatListDescriptor, _impl_.threat_entry_type_) + + sizeof(ThreatListDescriptor::_impl_.threat_entry_type_) + - PROTOBUF_FIELD_OFFSET(ThreatListDescriptor, _impl_.threat_type_)>( + reinterpret_cast<char*>(&_impl_.threat_type_), + reinterpret_cast<char*>(&other->_impl_.threat_type_)); +} + +std::string ThreatListDescriptor::GetTypeName() const { + return "mozilla.safebrowsing.ThreatListDescriptor"; +} + + +// =================================================================== + +class ListThreatListsResponse::_Internal { + public: +}; + +ListThreatListsResponse::ListThreatListsResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.ListThreatListsResponse) +} +ListThreatListsResponse::ListThreatListsResponse(const ListThreatListsResponse& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ListThreatListsResponse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.threat_lists_){from._impl_.threat_lists_} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.ListThreatListsResponse) +} + +inline void ListThreatListsResponse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.threat_lists_){arena} + , /*decltype(_impl_._cached_size_)*/{} + }; +} + +ListThreatListsResponse::~ListThreatListsResponse() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.ListThreatListsResponse) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ListThreatListsResponse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.threat_lists_.~RepeatedPtrField(); +} + +void ListThreatListsResponse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ListThreatListsResponse::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.ListThreatListsResponse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.threat_lists_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* ListThreatListsResponse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .mozilla.safebrowsing.ThreatListDescriptor threat_lists = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_threat_lists(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ListThreatListsResponse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.ListThreatListsResponse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatListDescriptor threat_lists = 1; + for (unsigned i = 0, + n = static_cast<unsigned>(this->_internal_threat_lists_size()); i < n; i++) { + const auto& repfield = this->_internal_threat_lists(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.ListThreatListsResponse) + return target; +} + +size_t ListThreatListsResponse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.ListThreatListsResponse) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.safebrowsing.ThreatListDescriptor threat_lists = 1; + total_size += 1UL * this->_internal_threat_lists_size(); + for (const auto& msg : this->_impl_.threat_lists_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ListThreatListsResponse::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const ListThreatListsResponse*>( + &from)); +} + +void ListThreatListsResponse::MergeFrom(const ListThreatListsResponse& from) { + ListThreatListsResponse* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.ListThreatListsResponse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.threat_lists_.MergeFrom(from._impl_.threat_lists_); + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void ListThreatListsResponse::CopyFrom(const ListThreatListsResponse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.ListThreatListsResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ListThreatListsResponse::IsInitialized() const { + return true; +} + +void ListThreatListsResponse::InternalSwap(ListThreatListsResponse* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.threat_lists_.InternalSwap(&other->_impl_.threat_lists_); +} + +std::string ListThreatListsResponse::GetTypeName() const { + return "mozilla.safebrowsing.ListThreatListsResponse"; +} + + +// =================================================================== + +class Duration::_Internal { + public: + using HasBits = decltype(std::declval<Duration>()._impl_._has_bits_); + static void set_has_seconds(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_nanos(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +Duration::Duration(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:mozilla.safebrowsing.Duration) +} +Duration::Duration(const Duration& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + Duration* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.seconds_){} + , decltype(_impl_.nanos_){}}; + + _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); + ::memcpy(&_impl_.seconds_, &from._impl_.seconds_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.nanos_) - + reinterpret_cast<char*>(&_impl_.seconds_)) + sizeof(_impl_.nanos_)); + // @@protoc_insertion_point(copy_constructor:mozilla.safebrowsing.Duration) +} + +inline void Duration::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.seconds_){int64_t{0}} + , decltype(_impl_.nanos_){0} + }; +} + +Duration::~Duration() { + // @@protoc_insertion_point(destructor:mozilla.safebrowsing.Duration) + if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Duration::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void Duration::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Duration::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.safebrowsing.Duration) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + ::memset(&_impl_.seconds_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.nanos_) - + reinterpret_cast<char*>(&_impl_.seconds_)) + sizeof(_impl_.nanos_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<std::string>(); +} + +const char* Duration::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional int64 seconds = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) { + _Internal::set_has_seconds(&has_bits); + _impl_.seconds_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional int32 nanos = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) { + _Internal::set_has_nanos(&has_bits); + _impl_.nanos_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<std::string>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Duration::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.safebrowsing.Duration) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional int64 seconds = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt64ToArray(1, this->_internal_seconds(), target); + } + + // optional int32 nanos = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray(2, this->_internal_nanos(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.safebrowsing.Duration) + return target; +} + +size_t Duration::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.safebrowsing.Duration) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional int64 seconds = 1; + if (cached_has_bits & 0x00000001u) { + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_seconds()); + } + + // optional int32 nanos = 2; + if (cached_has_bits & 0x00000002u) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne(this->_internal_nanos()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void Duration::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast<const Duration*>( + &from)); +} + +void Duration::MergeFrom(const Duration& from) { + Duration* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.safebrowsing.Duration) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_impl_.seconds_ = from._impl_.seconds_; + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.nanos_ = from._impl_.nanos_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_); +} + +void Duration::CopyFrom(const Duration& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.safebrowsing.Duration) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Duration::IsInitialized() const { + return true; +} + +void Duration::InternalSwap(Duration* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Duration, _impl_.nanos_) + + sizeof(Duration::_impl_.nanos_) + - PROTOBUF_FIELD_OFFSET(Duration, _impl_.seconds_)>( + reinterpret_cast<char*>(&_impl_.seconds_), + reinterpret_cast<char*>(&other->_impl_.seconds_)); +} + +std::string Duration::GetTypeName() const { + return "mozilla.safebrowsing.Duration"; +} + + +// @@protoc_insertion_point(namespace_scope) +} // namespace safebrowsing +} // namespace mozilla +PROTOBUF_NAMESPACE_OPEN +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatInfo* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatInfo >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatInfo >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatMatch* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatMatch >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatMatch >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FindThreatMatchesRequest* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FindThreatMatchesRequest >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FindThreatMatchesRequest >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FindThreatMatchesResponse* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FindThreatMatchesResponse >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FindThreatMatchesResponse >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FetchThreatListUpdatesRequest* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FetchThreatListUpdatesResponse* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FindFullHashesRequest* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FindFullHashesRequest >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FindFullHashesRequest >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::FindFullHashesResponse* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::FindFullHashesResponse >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::FindFullHashesResponse >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatHit_ThreatSource* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatHit_ThreatSource >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatHit_ThreatSource >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatHit_UserInfo* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatHit_UserInfo >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatHit_UserInfo >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatHit* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatHit >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatHit >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ClientInfo* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ClientInfo >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ClientInfo >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ChromeClientInfo* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ChromeClientInfo >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ChromeClientInfo >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::Checksum* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::Checksum >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::Checksum >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatEntry* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatEntry >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatEntry >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatEntrySet* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatEntrySet >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatEntrySet >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::RawIndices* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::RawIndices >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::RawIndices >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::RawHashes* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::RawHashes >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::RawHashes >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::RiceDeltaEncoding* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::RiceDeltaEncoding >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::RiceDeltaEncoding >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatEntryMetadata* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatEntryMetadata >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatEntryMetadata >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ThreatListDescriptor* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ThreatListDescriptor >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ThreatListDescriptor >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::ListThreatListsResponse* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::ListThreatListsResponse >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::ListThreatListsResponse >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::safebrowsing::Duration* +Arena::CreateMaybeMessage< ::mozilla::safebrowsing::Duration >(Arena* arena) { + return Arena::CreateMessageInternal< ::mozilla::safebrowsing::Duration >(arena); +} +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) +#include <google/protobuf/port_undef.inc> diff --git a/toolkit/components/url-classifier/chromium/safebrowsing.pb.h b/toolkit/components/url-classifier/chromium/safebrowsing.pb.h new file mode 100644 index 0000000000..194b2c410f --- /dev/null +++ b/toolkit/components/url-classifier/chromium/safebrowsing.pb.h @@ -0,0 +1,10010 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: safebrowsing.proto + +#ifndef GOOGLE_PROTOBUF_INCLUDED_safebrowsing_2eproto +#define GOOGLE_PROTOBUF_INCLUDED_safebrowsing_2eproto + +#include <limits> +#include <string> + +#include <google/protobuf/port_def.inc> +#if PROTOBUF_VERSION < 3021000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 3021006 < PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include <google/protobuf/port_undef.inc> +#include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/arena.h> +#include <google/protobuf/arenastring.h> +#include <google/protobuf/generated_message_util.h> +#include <google/protobuf/metadata_lite.h> +#include <google/protobuf/message_lite.h> +#include <google/protobuf/repeated_field.h> // IWYU pragma: export +#include <google/protobuf/extension_set.h> // IWYU pragma: export +#include <google/protobuf/generated_enum_util.h> +// @@protoc_insertion_point(includes) +#include <google/protobuf/port_def.inc> +#define PROTOBUF_INTERNAL_EXPORT_safebrowsing_2eproto +PROTOBUF_NAMESPACE_OPEN +namespace internal { +class AnyMetadata; +} // namespace internal +PROTOBUF_NAMESPACE_CLOSE + +// Internal implementation detail -- do not use these members. +struct TableStruct_safebrowsing_2eproto { + static const uint32_t offsets[]; +}; +namespace mozilla { +namespace safebrowsing { +class Checksum; +struct ChecksumDefaultTypeInternal; +extern ChecksumDefaultTypeInternal _Checksum_default_instance_; +class ChromeClientInfo; +struct ChromeClientInfoDefaultTypeInternal; +extern ChromeClientInfoDefaultTypeInternal _ChromeClientInfo_default_instance_; +class ClientInfo; +struct ClientInfoDefaultTypeInternal; +extern ClientInfoDefaultTypeInternal _ClientInfo_default_instance_; +class Duration; +struct DurationDefaultTypeInternal; +extern DurationDefaultTypeInternal _Duration_default_instance_; +class FetchThreatListUpdatesRequest; +struct FetchThreatListUpdatesRequestDefaultTypeInternal; +extern FetchThreatListUpdatesRequestDefaultTypeInternal _FetchThreatListUpdatesRequest_default_instance_; +class FetchThreatListUpdatesRequest_ListUpdateRequest; +struct FetchThreatListUpdatesRequest_ListUpdateRequestDefaultTypeInternal; +extern FetchThreatListUpdatesRequest_ListUpdateRequestDefaultTypeInternal _FetchThreatListUpdatesRequest_ListUpdateRequest_default_instance_; +class FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints; +struct FetchThreatListUpdatesRequest_ListUpdateRequest_ConstraintsDefaultTypeInternal; +extern FetchThreatListUpdatesRequest_ListUpdateRequest_ConstraintsDefaultTypeInternal _FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints_default_instance_; +class FetchThreatListUpdatesResponse; +struct FetchThreatListUpdatesResponseDefaultTypeInternal; +extern FetchThreatListUpdatesResponseDefaultTypeInternal _FetchThreatListUpdatesResponse_default_instance_; +class FetchThreatListUpdatesResponse_ListUpdateResponse; +struct FetchThreatListUpdatesResponse_ListUpdateResponseDefaultTypeInternal; +extern FetchThreatListUpdatesResponse_ListUpdateResponseDefaultTypeInternal _FetchThreatListUpdatesResponse_ListUpdateResponse_default_instance_; +class FindFullHashesRequest; +struct FindFullHashesRequestDefaultTypeInternal; +extern FindFullHashesRequestDefaultTypeInternal _FindFullHashesRequest_default_instance_; +class FindFullHashesResponse; +struct FindFullHashesResponseDefaultTypeInternal; +extern FindFullHashesResponseDefaultTypeInternal _FindFullHashesResponse_default_instance_; +class FindThreatMatchesRequest; +struct FindThreatMatchesRequestDefaultTypeInternal; +extern FindThreatMatchesRequestDefaultTypeInternal _FindThreatMatchesRequest_default_instance_; +class FindThreatMatchesResponse; +struct FindThreatMatchesResponseDefaultTypeInternal; +extern FindThreatMatchesResponseDefaultTypeInternal _FindThreatMatchesResponse_default_instance_; +class ListThreatListsResponse; +struct ListThreatListsResponseDefaultTypeInternal; +extern ListThreatListsResponseDefaultTypeInternal _ListThreatListsResponse_default_instance_; +class RawHashes; +struct RawHashesDefaultTypeInternal; +extern RawHashesDefaultTypeInternal _RawHashes_default_instance_; +class RawIndices; +struct RawIndicesDefaultTypeInternal; +extern RawIndicesDefaultTypeInternal _RawIndices_default_instance_; +class RiceDeltaEncoding; +struct RiceDeltaEncodingDefaultTypeInternal; +extern RiceDeltaEncodingDefaultTypeInternal _RiceDeltaEncoding_default_instance_; +class ThreatEntry; +struct ThreatEntryDefaultTypeInternal; +extern ThreatEntryDefaultTypeInternal _ThreatEntry_default_instance_; +class ThreatEntryMetadata; +struct ThreatEntryMetadataDefaultTypeInternal; +extern ThreatEntryMetadataDefaultTypeInternal _ThreatEntryMetadata_default_instance_; +class ThreatEntryMetadata_MetadataEntry; +struct ThreatEntryMetadata_MetadataEntryDefaultTypeInternal; +extern ThreatEntryMetadata_MetadataEntryDefaultTypeInternal _ThreatEntryMetadata_MetadataEntry_default_instance_; +class ThreatEntrySet; +struct ThreatEntrySetDefaultTypeInternal; +extern ThreatEntrySetDefaultTypeInternal _ThreatEntrySet_default_instance_; +class ThreatHit; +struct ThreatHitDefaultTypeInternal; +extern ThreatHitDefaultTypeInternal _ThreatHit_default_instance_; +class ThreatHit_ThreatSource; +struct ThreatHit_ThreatSourceDefaultTypeInternal; +extern ThreatHit_ThreatSourceDefaultTypeInternal _ThreatHit_ThreatSource_default_instance_; +class ThreatHit_UserInfo; +struct ThreatHit_UserInfoDefaultTypeInternal; +extern ThreatHit_UserInfoDefaultTypeInternal _ThreatHit_UserInfo_default_instance_; +class ThreatInfo; +struct ThreatInfoDefaultTypeInternal; +extern ThreatInfoDefaultTypeInternal _ThreatInfo_default_instance_; +class ThreatListDescriptor; +struct ThreatListDescriptorDefaultTypeInternal; +extern ThreatListDescriptorDefaultTypeInternal _ThreatListDescriptor_default_instance_; +class ThreatMatch; +struct ThreatMatchDefaultTypeInternal; +extern ThreatMatchDefaultTypeInternal _ThreatMatch_default_instance_; +} // namespace safebrowsing +} // namespace mozilla +PROTOBUF_NAMESPACE_OPEN +template<> ::mozilla::safebrowsing::Checksum* Arena::CreateMaybeMessage<::mozilla::safebrowsing::Checksum>(Arena*); +template<> ::mozilla::safebrowsing::ChromeClientInfo* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ChromeClientInfo>(Arena*); +template<> ::mozilla::safebrowsing::ClientInfo* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ClientInfo>(Arena*); +template<> ::mozilla::safebrowsing::Duration* Arena::CreateMaybeMessage<::mozilla::safebrowsing::Duration>(Arena*); +template<> ::mozilla::safebrowsing::FetchThreatListUpdatesRequest* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FetchThreatListUpdatesRequest>(Arena*); +template<> ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest>(Arena*); +template<> ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints>(Arena*); +template<> ::mozilla::safebrowsing::FetchThreatListUpdatesResponse* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FetchThreatListUpdatesResponse>(Arena*); +template<> ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse>(Arena*); +template<> ::mozilla::safebrowsing::FindFullHashesRequest* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FindFullHashesRequest>(Arena*); +template<> ::mozilla::safebrowsing::FindFullHashesResponse* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FindFullHashesResponse>(Arena*); +template<> ::mozilla::safebrowsing::FindThreatMatchesRequest* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FindThreatMatchesRequest>(Arena*); +template<> ::mozilla::safebrowsing::FindThreatMatchesResponse* Arena::CreateMaybeMessage<::mozilla::safebrowsing::FindThreatMatchesResponse>(Arena*); +template<> ::mozilla::safebrowsing::ListThreatListsResponse* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ListThreatListsResponse>(Arena*); +template<> ::mozilla::safebrowsing::RawHashes* Arena::CreateMaybeMessage<::mozilla::safebrowsing::RawHashes>(Arena*); +template<> ::mozilla::safebrowsing::RawIndices* Arena::CreateMaybeMessage<::mozilla::safebrowsing::RawIndices>(Arena*); +template<> ::mozilla::safebrowsing::RiceDeltaEncoding* Arena::CreateMaybeMessage<::mozilla::safebrowsing::RiceDeltaEncoding>(Arena*); +template<> ::mozilla::safebrowsing::ThreatEntry* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntry>(Arena*); +template<> ::mozilla::safebrowsing::ThreatEntryMetadata* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntryMetadata>(Arena*); +template<> ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry>(Arena*); +template<> ::mozilla::safebrowsing::ThreatEntrySet* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntrySet>(Arena*); +template<> ::mozilla::safebrowsing::ThreatHit* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatHit>(Arena*); +template<> ::mozilla::safebrowsing::ThreatHit_ThreatSource* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatHit_ThreatSource>(Arena*); +template<> ::mozilla::safebrowsing::ThreatHit_UserInfo* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatHit_UserInfo>(Arena*); +template<> ::mozilla::safebrowsing::ThreatInfo* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatInfo>(Arena*); +template<> ::mozilla::safebrowsing::ThreatListDescriptor* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatListDescriptor>(Arena*); +template<> ::mozilla::safebrowsing::ThreatMatch* Arena::CreateMaybeMessage<::mozilla::safebrowsing::ThreatMatch>(Arena*); +PROTOBUF_NAMESPACE_CLOSE +namespace mozilla { +namespace safebrowsing { + +enum FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType : int { + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_RESPONSE_TYPE_UNSPECIFIED = 0, + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_PARTIAL_UPDATE = 1, + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_FULL_UPDATE = 2 +}; +bool FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_IsValid(int value); +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_MIN = FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_RESPONSE_TYPE_UNSPECIFIED; +constexpr FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_MAX = FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_FULL_UPDATE; +constexpr int FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_ARRAYSIZE = FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_MAX + 1; + +const std::string& FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Name(FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType value); +template<typename T> +inline const std::string& FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Name(T enum_t_value) { + static_assert(::std::is_same<T, FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Name."); + return FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Name(static_cast<FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType>(enum_t_value)); +} +bool FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType* value); +enum ThreatHit_ThreatSourceType : int { + ThreatHit_ThreatSourceType_THREAT_SOURCE_TYPE_UNSPECIFIED = 0, + ThreatHit_ThreatSourceType_MATCHING_URL = 1, + ThreatHit_ThreatSourceType_TAB_URL = 2, + ThreatHit_ThreatSourceType_TAB_REDIRECT = 3, + ThreatHit_ThreatSourceType_TAB_RESOURCE = 4 +}; +bool ThreatHit_ThreatSourceType_IsValid(int value); +constexpr ThreatHit_ThreatSourceType ThreatHit_ThreatSourceType_ThreatSourceType_MIN = ThreatHit_ThreatSourceType_THREAT_SOURCE_TYPE_UNSPECIFIED; +constexpr ThreatHit_ThreatSourceType ThreatHit_ThreatSourceType_ThreatSourceType_MAX = ThreatHit_ThreatSourceType_TAB_RESOURCE; +constexpr int ThreatHit_ThreatSourceType_ThreatSourceType_ARRAYSIZE = ThreatHit_ThreatSourceType_ThreatSourceType_MAX + 1; + +const std::string& ThreatHit_ThreatSourceType_Name(ThreatHit_ThreatSourceType value); +template<typename T> +inline const std::string& ThreatHit_ThreatSourceType_Name(T enum_t_value) { + static_assert(::std::is_same<T, ThreatHit_ThreatSourceType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ThreatHit_ThreatSourceType_Name."); + return ThreatHit_ThreatSourceType_Name(static_cast<ThreatHit_ThreatSourceType>(enum_t_value)); +} +bool ThreatHit_ThreatSourceType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ThreatHit_ThreatSourceType* value); +enum ChromeClientInfo_SafeBrowsingReportingPopulation : int { + ChromeClientInfo_SafeBrowsingReportingPopulation_UNSPECIFIED = 0, + ChromeClientInfo_SafeBrowsingReportingPopulation_OPT_OUT = 1, + ChromeClientInfo_SafeBrowsingReportingPopulation_EXTENDED = 2, + ChromeClientInfo_SafeBrowsingReportingPopulation_SCOUT = 3 +}; +bool ChromeClientInfo_SafeBrowsingReportingPopulation_IsValid(int value); +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_MIN = ChromeClientInfo_SafeBrowsingReportingPopulation_UNSPECIFIED; +constexpr ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_MAX = ChromeClientInfo_SafeBrowsingReportingPopulation_SCOUT; +constexpr int ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_ARRAYSIZE = ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_MAX + 1; + +const std::string& ChromeClientInfo_SafeBrowsingReportingPopulation_Name(ChromeClientInfo_SafeBrowsingReportingPopulation value); +template<typename T> +inline const std::string& ChromeClientInfo_SafeBrowsingReportingPopulation_Name(T enum_t_value) { + static_assert(::std::is_same<T, ChromeClientInfo_SafeBrowsingReportingPopulation>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ChromeClientInfo_SafeBrowsingReportingPopulation_Name."); + return ChromeClientInfo_SafeBrowsingReportingPopulation_Name(static_cast<ChromeClientInfo_SafeBrowsingReportingPopulation>(enum_t_value)); +} +bool ChromeClientInfo_SafeBrowsingReportingPopulation_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ChromeClientInfo_SafeBrowsingReportingPopulation* value); +enum ThreatType : int { + THREAT_TYPE_UNSPECIFIED = 0, + MALWARE_THREAT = 1, + SOCIAL_ENGINEERING_PUBLIC = 2, + UNWANTED_SOFTWARE = 3, + POTENTIALLY_HARMFUL_APPLICATION = 4, + SOCIAL_ENGINEERING = 5, + API_ABUSE = 6, + MALICIOUS_BINARY = 7, + CSD_WHITELIST = 8, + CSD_DOWNLOAD_WHITELIST = 9, + CLIENT_INCIDENT = 10, + SUBRESOURCE_FILTER = 13 +}; +bool ThreatType_IsValid(int value); +constexpr ThreatType ThreatType_MIN = THREAT_TYPE_UNSPECIFIED; +constexpr ThreatType ThreatType_MAX = SUBRESOURCE_FILTER; +constexpr int ThreatType_ARRAYSIZE = ThreatType_MAX + 1; + +const std::string& ThreatType_Name(ThreatType value); +template<typename T> +inline const std::string& ThreatType_Name(T enum_t_value) { + static_assert(::std::is_same<T, ThreatType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ThreatType_Name."); + return ThreatType_Name(static_cast<ThreatType>(enum_t_value)); +} +bool ThreatType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ThreatType* value); +enum PlatformType : int { + PLATFORM_TYPE_UNSPECIFIED = 0, + WINDOWS_PLATFORM = 1, + LINUX_PLATFORM = 2, + ANDROID_PLATFORM = 3, + OSX_PLATFORM = 4, + IOS_PLATFORM = 5, + ANY_PLATFORM = 6, + ALL_PLATFORMS = 7, + CHROME_PLATFORM = 8 +}; +bool PlatformType_IsValid(int value); +constexpr PlatformType PlatformType_MIN = PLATFORM_TYPE_UNSPECIFIED; +constexpr PlatformType PlatformType_MAX = CHROME_PLATFORM; +constexpr int PlatformType_ARRAYSIZE = PlatformType_MAX + 1; + +const std::string& PlatformType_Name(PlatformType value); +template<typename T> +inline const std::string& PlatformType_Name(T enum_t_value) { + static_assert(::std::is_same<T, PlatformType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function PlatformType_Name."); + return PlatformType_Name(static_cast<PlatformType>(enum_t_value)); +} +bool PlatformType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, PlatformType* value); +enum CompressionType : int { + COMPRESSION_TYPE_UNSPECIFIED = 0, + RAW = 1, + RICE = 2 +}; +bool CompressionType_IsValid(int value); +constexpr CompressionType CompressionType_MIN = COMPRESSION_TYPE_UNSPECIFIED; +constexpr CompressionType CompressionType_MAX = RICE; +constexpr int CompressionType_ARRAYSIZE = CompressionType_MAX + 1; + +const std::string& CompressionType_Name(CompressionType value); +template<typename T> +inline const std::string& CompressionType_Name(T enum_t_value) { + static_assert(::std::is_same<T, CompressionType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function CompressionType_Name."); + return CompressionType_Name(static_cast<CompressionType>(enum_t_value)); +} +bool CompressionType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, CompressionType* value); +enum ThreatEntryType : int { + THREAT_ENTRY_TYPE_UNSPECIFIED = 0, + URL = 1, + EXECUTABLE = 2, + IP_RANGE = 3, + CHROME_EXTENSION = 4, + FILENAME = 5, + CERT = 6 +}; +bool ThreatEntryType_IsValid(int value); +constexpr ThreatEntryType ThreatEntryType_MIN = THREAT_ENTRY_TYPE_UNSPECIFIED; +constexpr ThreatEntryType ThreatEntryType_MAX = CERT; +constexpr int ThreatEntryType_ARRAYSIZE = ThreatEntryType_MAX + 1; + +const std::string& ThreatEntryType_Name(ThreatEntryType value); +template<typename T> +inline const std::string& ThreatEntryType_Name(T enum_t_value) { + static_assert(::std::is_same<T, ThreatEntryType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ThreatEntryType_Name."); + return ThreatEntryType_Name(static_cast<ThreatEntryType>(enum_t_value)); +} +bool ThreatEntryType_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ThreatEntryType* value); +// =================================================================== + +class ThreatInfo final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatInfo) */ { + public: + inline ThreatInfo() : ThreatInfo(nullptr) {} + ~ThreatInfo() override; + explicit PROTOBUF_CONSTEXPR ThreatInfo(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatInfo(const ThreatInfo& from); + ThreatInfo(ThreatInfo&& from) noexcept + : ThreatInfo() { + *this = ::std::move(from); + } + + inline ThreatInfo& operator=(const ThreatInfo& from) { + CopyFrom(from); + return *this; + } + inline ThreatInfo& operator=(ThreatInfo&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatInfo& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatInfo* internal_default_instance() { + return reinterpret_cast<const ThreatInfo*>( + &_ThreatInfo_default_instance_); + } + static constexpr int kIndexInFileMessages = + 0; + + friend void swap(ThreatInfo& a, ThreatInfo& b) { + a.Swap(&b); + } + inline void Swap(ThreatInfo* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatInfo* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatInfo* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatInfo>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatInfo& from); + void MergeFrom(const ThreatInfo& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatInfo* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatInfo"; + } + protected: + explicit ThreatInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kThreatTypesFieldNumber = 1, + kPlatformTypesFieldNumber = 2, + kThreatEntriesFieldNumber = 3, + kThreatEntryTypesFieldNumber = 4, + }; + // repeated .mozilla.safebrowsing.ThreatType threat_types = 1; + int threat_types_size() const; + private: + int _internal_threat_types_size() const; + public: + void clear_threat_types(); + private: + ::mozilla::safebrowsing::ThreatType _internal_threat_types(int index) const; + void _internal_add_threat_types(::mozilla::safebrowsing::ThreatType value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* _internal_mutable_threat_types(); + public: + ::mozilla::safebrowsing::ThreatType threat_types(int index) const; + void set_threat_types(int index, ::mozilla::safebrowsing::ThreatType value); + void add_threat_types(::mozilla::safebrowsing::ThreatType value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& threat_types() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* mutable_threat_types(); + + // repeated .mozilla.safebrowsing.PlatformType platform_types = 2; + int platform_types_size() const; + private: + int _internal_platform_types_size() const; + public: + void clear_platform_types(); + private: + ::mozilla::safebrowsing::PlatformType _internal_platform_types(int index) const; + void _internal_add_platform_types(::mozilla::safebrowsing::PlatformType value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* _internal_mutable_platform_types(); + public: + ::mozilla::safebrowsing::PlatformType platform_types(int index) const; + void set_platform_types(int index, ::mozilla::safebrowsing::PlatformType value); + void add_platform_types(::mozilla::safebrowsing::PlatformType value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& platform_types() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* mutable_platform_types(); + + // repeated .mozilla.safebrowsing.ThreatEntry threat_entries = 3; + int threat_entries_size() const; + private: + int _internal_threat_entries_size() const; + public: + void clear_threat_entries(); + ::mozilla::safebrowsing::ThreatEntry* mutable_threat_entries(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntry >* + mutable_threat_entries(); + private: + const ::mozilla::safebrowsing::ThreatEntry& _internal_threat_entries(int index) const; + ::mozilla::safebrowsing::ThreatEntry* _internal_add_threat_entries(); + public: + const ::mozilla::safebrowsing::ThreatEntry& threat_entries(int index) const; + ::mozilla::safebrowsing::ThreatEntry* add_threat_entries(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntry >& + threat_entries() const; + + // repeated .mozilla.safebrowsing.ThreatEntryType threat_entry_types = 4; + int threat_entry_types_size() const; + private: + int _internal_threat_entry_types_size() const; + public: + void clear_threat_entry_types(); + private: + ::mozilla::safebrowsing::ThreatEntryType _internal_threat_entry_types(int index) const; + void _internal_add_threat_entry_types(::mozilla::safebrowsing::ThreatEntryType value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* _internal_mutable_threat_entry_types(); + public: + ::mozilla::safebrowsing::ThreatEntryType threat_entry_types(int index) const; + void set_threat_entry_types(int index, ::mozilla::safebrowsing::ThreatEntryType value); + void add_threat_entry_types(::mozilla::safebrowsing::ThreatEntryType value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& threat_entry_types() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* mutable_threat_entry_types(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatInfo) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int> threat_types_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int> platform_types_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntry > threat_entries_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int> threat_entry_types_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatMatch final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatMatch) */ { + public: + inline ThreatMatch() : ThreatMatch(nullptr) {} + ~ThreatMatch() override; + explicit PROTOBUF_CONSTEXPR ThreatMatch(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatMatch(const ThreatMatch& from); + ThreatMatch(ThreatMatch&& from) noexcept + : ThreatMatch() { + *this = ::std::move(from); + } + + inline ThreatMatch& operator=(const ThreatMatch& from) { + CopyFrom(from); + return *this; + } + inline ThreatMatch& operator=(ThreatMatch&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatMatch& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatMatch* internal_default_instance() { + return reinterpret_cast<const ThreatMatch*>( + &_ThreatMatch_default_instance_); + } + static constexpr int kIndexInFileMessages = + 1; + + friend void swap(ThreatMatch& a, ThreatMatch& b) { + a.Swap(&b); + } + inline void Swap(ThreatMatch* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatMatch* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatMatch* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatMatch>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatMatch& from); + void MergeFrom(const ThreatMatch& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatMatch* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatMatch"; + } + protected: + explicit ThreatMatch(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kThreatFieldNumber = 3, + kThreatEntryMetadataFieldNumber = 4, + kCacheDurationFieldNumber = 5, + kThreatTypeFieldNumber = 1, + kPlatformTypeFieldNumber = 2, + kThreatEntryTypeFieldNumber = 6, + }; + // optional .mozilla.safebrowsing.ThreatEntry threat = 3; + bool has_threat() const; + private: + bool _internal_has_threat() const; + public: + void clear_threat(); + const ::mozilla::safebrowsing::ThreatEntry& threat() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ThreatEntry* release_threat(); + ::mozilla::safebrowsing::ThreatEntry* mutable_threat(); + void set_allocated_threat(::mozilla::safebrowsing::ThreatEntry* threat); + private: + const ::mozilla::safebrowsing::ThreatEntry& _internal_threat() const; + ::mozilla::safebrowsing::ThreatEntry* _internal_mutable_threat(); + public: + void unsafe_arena_set_allocated_threat( + ::mozilla::safebrowsing::ThreatEntry* threat); + ::mozilla::safebrowsing::ThreatEntry* unsafe_arena_release_threat(); + + // optional .mozilla.safebrowsing.ThreatEntryMetadata threat_entry_metadata = 4; + bool has_threat_entry_metadata() const; + private: + bool _internal_has_threat_entry_metadata() const; + public: + void clear_threat_entry_metadata(); + const ::mozilla::safebrowsing::ThreatEntryMetadata& threat_entry_metadata() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ThreatEntryMetadata* release_threat_entry_metadata(); + ::mozilla::safebrowsing::ThreatEntryMetadata* mutable_threat_entry_metadata(); + void set_allocated_threat_entry_metadata(::mozilla::safebrowsing::ThreatEntryMetadata* threat_entry_metadata); + private: + const ::mozilla::safebrowsing::ThreatEntryMetadata& _internal_threat_entry_metadata() const; + ::mozilla::safebrowsing::ThreatEntryMetadata* _internal_mutable_threat_entry_metadata(); + public: + void unsafe_arena_set_allocated_threat_entry_metadata( + ::mozilla::safebrowsing::ThreatEntryMetadata* threat_entry_metadata); + ::mozilla::safebrowsing::ThreatEntryMetadata* unsafe_arena_release_threat_entry_metadata(); + + // optional .mozilla.safebrowsing.Duration cache_duration = 5; + bool has_cache_duration() const; + private: + bool _internal_has_cache_duration() const; + public: + void clear_cache_duration(); + const ::mozilla::safebrowsing::Duration& cache_duration() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::Duration* release_cache_duration(); + ::mozilla::safebrowsing::Duration* mutable_cache_duration(); + void set_allocated_cache_duration(::mozilla::safebrowsing::Duration* cache_duration); + private: + const ::mozilla::safebrowsing::Duration& _internal_cache_duration() const; + ::mozilla::safebrowsing::Duration* _internal_mutable_cache_duration(); + public: + void unsafe_arena_set_allocated_cache_duration( + ::mozilla::safebrowsing::Duration* cache_duration); + ::mozilla::safebrowsing::Duration* unsafe_arena_release_cache_duration(); + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + bool has_threat_type() const; + private: + bool _internal_has_threat_type() const; + public: + void clear_threat_type(); + ::mozilla::safebrowsing::ThreatType threat_type() const; + void set_threat_type(::mozilla::safebrowsing::ThreatType value); + private: + ::mozilla::safebrowsing::ThreatType _internal_threat_type() const; + void _internal_set_threat_type(::mozilla::safebrowsing::ThreatType value); + public: + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + bool has_platform_type() const; + private: + bool _internal_has_platform_type() const; + public: + void clear_platform_type(); + ::mozilla::safebrowsing::PlatformType platform_type() const; + void set_platform_type(::mozilla::safebrowsing::PlatformType value); + private: + ::mozilla::safebrowsing::PlatformType _internal_platform_type() const; + void _internal_set_platform_type(::mozilla::safebrowsing::PlatformType value); + public: + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 6; + bool has_threat_entry_type() const; + private: + bool _internal_has_threat_entry_type() const; + public: + void clear_threat_entry_type(); + ::mozilla::safebrowsing::ThreatEntryType threat_entry_type() const; + void set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + private: + ::mozilla::safebrowsing::ThreatEntryType _internal_threat_entry_type() const; + void _internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatMatch) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::mozilla::safebrowsing::ThreatEntry* threat_; + ::mozilla::safebrowsing::ThreatEntryMetadata* threat_entry_metadata_; + ::mozilla::safebrowsing::Duration* cache_duration_; + int threat_type_; + int platform_type_; + int threat_entry_type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FindThreatMatchesRequest final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FindThreatMatchesRequest) */ { + public: + inline FindThreatMatchesRequest() : FindThreatMatchesRequest(nullptr) {} + ~FindThreatMatchesRequest() override; + explicit PROTOBUF_CONSTEXPR FindThreatMatchesRequest(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FindThreatMatchesRequest(const FindThreatMatchesRequest& from); + FindThreatMatchesRequest(FindThreatMatchesRequest&& from) noexcept + : FindThreatMatchesRequest() { + *this = ::std::move(from); + } + + inline FindThreatMatchesRequest& operator=(const FindThreatMatchesRequest& from) { + CopyFrom(from); + return *this; + } + inline FindThreatMatchesRequest& operator=(FindThreatMatchesRequest&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FindThreatMatchesRequest& default_instance() { + return *internal_default_instance(); + } + static inline const FindThreatMatchesRequest* internal_default_instance() { + return reinterpret_cast<const FindThreatMatchesRequest*>( + &_FindThreatMatchesRequest_default_instance_); + } + static constexpr int kIndexInFileMessages = + 2; + + friend void swap(FindThreatMatchesRequest& a, FindThreatMatchesRequest& b) { + a.Swap(&b); + } + inline void Swap(FindThreatMatchesRequest* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FindThreatMatchesRequest* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FindThreatMatchesRequest* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FindThreatMatchesRequest>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FindThreatMatchesRequest& from); + void MergeFrom(const FindThreatMatchesRequest& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FindThreatMatchesRequest* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FindThreatMatchesRequest"; + } + protected: + explicit FindThreatMatchesRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kClientFieldNumber = 1, + kThreatInfoFieldNumber = 2, + }; + // optional .mozilla.safebrowsing.ClientInfo client = 1; + bool has_client() const; + private: + bool _internal_has_client() const; + public: + void clear_client(); + const ::mozilla::safebrowsing::ClientInfo& client() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ClientInfo* release_client(); + ::mozilla::safebrowsing::ClientInfo* mutable_client(); + void set_allocated_client(::mozilla::safebrowsing::ClientInfo* client); + private: + const ::mozilla::safebrowsing::ClientInfo& _internal_client() const; + ::mozilla::safebrowsing::ClientInfo* _internal_mutable_client(); + public: + void unsafe_arena_set_allocated_client( + ::mozilla::safebrowsing::ClientInfo* client); + ::mozilla::safebrowsing::ClientInfo* unsafe_arena_release_client(); + + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 2; + bool has_threat_info() const; + private: + bool _internal_has_threat_info() const; + public: + void clear_threat_info(); + const ::mozilla::safebrowsing::ThreatInfo& threat_info() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ThreatInfo* release_threat_info(); + ::mozilla::safebrowsing::ThreatInfo* mutable_threat_info(); + void set_allocated_threat_info(::mozilla::safebrowsing::ThreatInfo* threat_info); + private: + const ::mozilla::safebrowsing::ThreatInfo& _internal_threat_info() const; + ::mozilla::safebrowsing::ThreatInfo* _internal_mutable_threat_info(); + public: + void unsafe_arena_set_allocated_threat_info( + ::mozilla::safebrowsing::ThreatInfo* threat_info); + ::mozilla::safebrowsing::ThreatInfo* unsafe_arena_release_threat_info(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FindThreatMatchesRequest) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::mozilla::safebrowsing::ClientInfo* client_; + ::mozilla::safebrowsing::ThreatInfo* threat_info_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FindThreatMatchesResponse final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FindThreatMatchesResponse) */ { + public: + inline FindThreatMatchesResponse() : FindThreatMatchesResponse(nullptr) {} + ~FindThreatMatchesResponse() override; + explicit PROTOBUF_CONSTEXPR FindThreatMatchesResponse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FindThreatMatchesResponse(const FindThreatMatchesResponse& from); + FindThreatMatchesResponse(FindThreatMatchesResponse&& from) noexcept + : FindThreatMatchesResponse() { + *this = ::std::move(from); + } + + inline FindThreatMatchesResponse& operator=(const FindThreatMatchesResponse& from) { + CopyFrom(from); + return *this; + } + inline FindThreatMatchesResponse& operator=(FindThreatMatchesResponse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FindThreatMatchesResponse& default_instance() { + return *internal_default_instance(); + } + static inline const FindThreatMatchesResponse* internal_default_instance() { + return reinterpret_cast<const FindThreatMatchesResponse*>( + &_FindThreatMatchesResponse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 3; + + friend void swap(FindThreatMatchesResponse& a, FindThreatMatchesResponse& b) { + a.Swap(&b); + } + inline void Swap(FindThreatMatchesResponse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FindThreatMatchesResponse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FindThreatMatchesResponse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FindThreatMatchesResponse>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FindThreatMatchesResponse& from); + void MergeFrom(const FindThreatMatchesResponse& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FindThreatMatchesResponse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FindThreatMatchesResponse"; + } + protected: + explicit FindThreatMatchesResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kMatchesFieldNumber = 1, + }; + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + int matches_size() const; + private: + int _internal_matches_size() const; + public: + void clear_matches(); + ::mozilla::safebrowsing::ThreatMatch* mutable_matches(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >* + mutable_matches(); + private: + const ::mozilla::safebrowsing::ThreatMatch& _internal_matches(int index) const; + ::mozilla::safebrowsing::ThreatMatch* _internal_add_matches(); + public: + const ::mozilla::safebrowsing::ThreatMatch& matches(int index) const; + ::mozilla::safebrowsing::ThreatMatch* add_matches(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >& + matches() const; + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FindThreatMatchesResponse) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch > matches_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) */ { + public: + inline FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints() : FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(nullptr) {} + ~FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints() override; + explicit PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from); + FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints&& from) noexcept + : FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints() { + *this = ::std::move(from); + } + + inline FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& operator=(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from) { + CopyFrom(from); + return *this; + } + inline FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& operator=(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& default_instance() { + return *internal_default_instance(); + } + static inline const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* internal_default_instance() { + return reinterpret_cast<const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints*>( + &_FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints_default_instance_); + } + static constexpr int kIndexInFileMessages = + 4; + + friend void swap(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& a, FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& b) { + a.Swap(&b); + } + inline void Swap(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from); + void MergeFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints"; + } + protected: + explicit FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kSupportedCompressionsFieldNumber = 4, + kRegionFieldNumber = 3, + kMaxUpdateEntriesFieldNumber = 1, + kMaxDatabaseEntriesFieldNumber = 2, + }; + // repeated .mozilla.safebrowsing.CompressionType supported_compressions = 4; + int supported_compressions_size() const; + private: + int _internal_supported_compressions_size() const; + public: + void clear_supported_compressions(); + private: + ::mozilla::safebrowsing::CompressionType _internal_supported_compressions(int index) const; + void _internal_add_supported_compressions(::mozilla::safebrowsing::CompressionType value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* _internal_mutable_supported_compressions(); + public: + ::mozilla::safebrowsing::CompressionType supported_compressions(int index) const; + void set_supported_compressions(int index, ::mozilla::safebrowsing::CompressionType value); + void add_supported_compressions(::mozilla::safebrowsing::CompressionType value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& supported_compressions() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* mutable_supported_compressions(); + + // optional string region = 3; + bool has_region() const; + private: + bool _internal_has_region() const; + public: + void clear_region(); + const std::string& region() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_region(ArgT0&& arg0, ArgT... args); + std::string* mutable_region(); + PROTOBUF_NODISCARD std::string* release_region(); + void set_allocated_region(std::string* region); + private: + const std::string& _internal_region() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_region(const std::string& value); + std::string* _internal_mutable_region(); + public: + + // optional int32 max_update_entries = 1; + bool has_max_update_entries() const; + private: + bool _internal_has_max_update_entries() const; + public: + void clear_max_update_entries(); + int32_t max_update_entries() const; + void set_max_update_entries(int32_t value); + private: + int32_t _internal_max_update_entries() const; + void _internal_set_max_update_entries(int32_t value); + public: + + // optional int32 max_database_entries = 2; + bool has_max_database_entries() const; + private: + bool _internal_has_max_database_entries() const; + public: + void clear_max_database_entries(); + int32_t max_database_entries() const; + void set_max_database_entries(int32_t value); + private: + int32_t _internal_max_database_entries() const; + void _internal_set_max_database_entries(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField<int> supported_compressions_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr region_; + int32_t max_update_entries_; + int32_t max_database_entries_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FetchThreatListUpdatesRequest_ListUpdateRequest final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) */ { + public: + inline FetchThreatListUpdatesRequest_ListUpdateRequest() : FetchThreatListUpdatesRequest_ListUpdateRequest(nullptr) {} + ~FetchThreatListUpdatesRequest_ListUpdateRequest() override; + explicit PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest_ListUpdateRequest(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FetchThreatListUpdatesRequest_ListUpdateRequest(const FetchThreatListUpdatesRequest_ListUpdateRequest& from); + FetchThreatListUpdatesRequest_ListUpdateRequest(FetchThreatListUpdatesRequest_ListUpdateRequest&& from) noexcept + : FetchThreatListUpdatesRequest_ListUpdateRequest() { + *this = ::std::move(from); + } + + inline FetchThreatListUpdatesRequest_ListUpdateRequest& operator=(const FetchThreatListUpdatesRequest_ListUpdateRequest& from) { + CopyFrom(from); + return *this; + } + inline FetchThreatListUpdatesRequest_ListUpdateRequest& operator=(FetchThreatListUpdatesRequest_ListUpdateRequest&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FetchThreatListUpdatesRequest_ListUpdateRequest& default_instance() { + return *internal_default_instance(); + } + static inline const FetchThreatListUpdatesRequest_ListUpdateRequest* internal_default_instance() { + return reinterpret_cast<const FetchThreatListUpdatesRequest_ListUpdateRequest*>( + &_FetchThreatListUpdatesRequest_ListUpdateRequest_default_instance_); + } + static constexpr int kIndexInFileMessages = + 5; + + friend void swap(FetchThreatListUpdatesRequest_ListUpdateRequest& a, FetchThreatListUpdatesRequest_ListUpdateRequest& b) { + a.Swap(&b); + } + inline void Swap(FetchThreatListUpdatesRequest_ListUpdateRequest* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FetchThreatListUpdatesRequest_ListUpdateRequest* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FetchThreatListUpdatesRequest_ListUpdateRequest* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FetchThreatListUpdatesRequest_ListUpdateRequest>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest& from); + void MergeFrom(const FetchThreatListUpdatesRequest_ListUpdateRequest& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FetchThreatListUpdatesRequest_ListUpdateRequest* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest"; + } + protected: + explicit FetchThreatListUpdatesRequest_ListUpdateRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints Constraints; + + // accessors ------------------------------------------------------- + + enum : int { + kStateFieldNumber = 3, + kConstraintsFieldNumber = 4, + kThreatTypeFieldNumber = 1, + kPlatformTypeFieldNumber = 2, + kThreatEntryTypeFieldNumber = 5, + }; + // optional bytes state = 3; + bool has_state() const; + private: + bool _internal_has_state() const; + public: + void clear_state(); + const std::string& state() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_state(ArgT0&& arg0, ArgT... args); + std::string* mutable_state(); + PROTOBUF_NODISCARD std::string* release_state(); + void set_allocated_state(std::string* state); + private: + const std::string& _internal_state() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_state(const std::string& value); + std::string* _internal_mutable_state(); + public: + + // optional .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints constraints = 4; + bool has_constraints() const; + private: + bool _internal_has_constraints() const; + public: + void clear_constraints(); + const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& constraints() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* release_constraints(); + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* mutable_constraints(); + void set_allocated_constraints(::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* constraints); + private: + const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& _internal_constraints() const; + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* _internal_mutable_constraints(); + public: + void unsafe_arena_set_allocated_constraints( + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* constraints); + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* unsafe_arena_release_constraints(); + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + bool has_threat_type() const; + private: + bool _internal_has_threat_type() const; + public: + void clear_threat_type(); + ::mozilla::safebrowsing::ThreatType threat_type() const; + void set_threat_type(::mozilla::safebrowsing::ThreatType value); + private: + ::mozilla::safebrowsing::ThreatType _internal_threat_type() const; + void _internal_set_threat_type(::mozilla::safebrowsing::ThreatType value); + public: + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + bool has_platform_type() const; + private: + bool _internal_has_platform_type() const; + public: + void clear_platform_type(); + ::mozilla::safebrowsing::PlatformType platform_type() const; + void set_platform_type(::mozilla::safebrowsing::PlatformType value); + private: + ::mozilla::safebrowsing::PlatformType _internal_platform_type() const; + void _internal_set_platform_type(::mozilla::safebrowsing::PlatformType value); + public: + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 5; + bool has_threat_entry_type() const; + private: + bool _internal_has_threat_entry_type() const; + public: + void clear_threat_entry_type(); + ::mozilla::safebrowsing::ThreatEntryType threat_entry_type() const; + void set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + private: + ::mozilla::safebrowsing::ThreatEntryType _internal_threat_entry_type() const; + void _internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr state_; + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* constraints_; + int threat_type_; + int platform_type_; + int threat_entry_type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FetchThreatListUpdatesRequest final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FetchThreatListUpdatesRequest) */ { + public: + inline FetchThreatListUpdatesRequest() : FetchThreatListUpdatesRequest(nullptr) {} + ~FetchThreatListUpdatesRequest() override; + explicit PROTOBUF_CONSTEXPR FetchThreatListUpdatesRequest(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FetchThreatListUpdatesRequest(const FetchThreatListUpdatesRequest& from); + FetchThreatListUpdatesRequest(FetchThreatListUpdatesRequest&& from) noexcept + : FetchThreatListUpdatesRequest() { + *this = ::std::move(from); + } + + inline FetchThreatListUpdatesRequest& operator=(const FetchThreatListUpdatesRequest& from) { + CopyFrom(from); + return *this; + } + inline FetchThreatListUpdatesRequest& operator=(FetchThreatListUpdatesRequest&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FetchThreatListUpdatesRequest& default_instance() { + return *internal_default_instance(); + } + static inline const FetchThreatListUpdatesRequest* internal_default_instance() { + return reinterpret_cast<const FetchThreatListUpdatesRequest*>( + &_FetchThreatListUpdatesRequest_default_instance_); + } + static constexpr int kIndexInFileMessages = + 6; + + friend void swap(FetchThreatListUpdatesRequest& a, FetchThreatListUpdatesRequest& b) { + a.Swap(&b); + } + inline void Swap(FetchThreatListUpdatesRequest* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FetchThreatListUpdatesRequest* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FetchThreatListUpdatesRequest* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FetchThreatListUpdatesRequest>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FetchThreatListUpdatesRequest& from); + void MergeFrom(const FetchThreatListUpdatesRequest& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FetchThreatListUpdatesRequest* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FetchThreatListUpdatesRequest"; + } + protected: + explicit FetchThreatListUpdatesRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef FetchThreatListUpdatesRequest_ListUpdateRequest ListUpdateRequest; + + // accessors ------------------------------------------------------- + + enum : int { + kListUpdateRequestsFieldNumber = 3, + kClientFieldNumber = 1, + kChromeClientInfoFieldNumber = 4, + }; + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest list_update_requests = 3; + int list_update_requests_size() const; + private: + int _internal_list_update_requests_size() const; + public: + void clear_list_update_requests(); + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* mutable_list_update_requests(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest >* + mutable_list_update_requests(); + private: + const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest& _internal_list_update_requests(int index) const; + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* _internal_add_list_update_requests(); + public: + const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest& list_update_requests(int index) const; + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* add_list_update_requests(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest >& + list_update_requests() const; + + // optional .mozilla.safebrowsing.ClientInfo client = 1; + bool has_client() const; + private: + bool _internal_has_client() const; + public: + void clear_client(); + const ::mozilla::safebrowsing::ClientInfo& client() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ClientInfo* release_client(); + ::mozilla::safebrowsing::ClientInfo* mutable_client(); + void set_allocated_client(::mozilla::safebrowsing::ClientInfo* client); + private: + const ::mozilla::safebrowsing::ClientInfo& _internal_client() const; + ::mozilla::safebrowsing::ClientInfo* _internal_mutable_client(); + public: + void unsafe_arena_set_allocated_client( + ::mozilla::safebrowsing::ClientInfo* client); + ::mozilla::safebrowsing::ClientInfo* unsafe_arena_release_client(); + + // optional .mozilla.safebrowsing.ChromeClientInfo chrome_client_info = 4; + bool has_chrome_client_info() const; + private: + bool _internal_has_chrome_client_info() const; + public: + void clear_chrome_client_info(); + const ::mozilla::safebrowsing::ChromeClientInfo& chrome_client_info() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ChromeClientInfo* release_chrome_client_info(); + ::mozilla::safebrowsing::ChromeClientInfo* mutable_chrome_client_info(); + void set_allocated_chrome_client_info(::mozilla::safebrowsing::ChromeClientInfo* chrome_client_info); + private: + const ::mozilla::safebrowsing::ChromeClientInfo& _internal_chrome_client_info() const; + ::mozilla::safebrowsing::ChromeClientInfo* _internal_mutable_chrome_client_info(); + public: + void unsafe_arena_set_allocated_chrome_client_info( + ::mozilla::safebrowsing::ChromeClientInfo* chrome_client_info); + ::mozilla::safebrowsing::ChromeClientInfo* unsafe_arena_release_chrome_client_info(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FetchThreatListUpdatesRequest) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest > list_update_requests_; + ::mozilla::safebrowsing::ClientInfo* client_; + ::mozilla::safebrowsing::ChromeClientInfo* chrome_client_info_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FetchThreatListUpdatesResponse_ListUpdateResponse final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) */ { + public: + inline FetchThreatListUpdatesResponse_ListUpdateResponse() : FetchThreatListUpdatesResponse_ListUpdateResponse(nullptr) {} + ~FetchThreatListUpdatesResponse_ListUpdateResponse() override; + explicit PROTOBUF_CONSTEXPR FetchThreatListUpdatesResponse_ListUpdateResponse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FetchThreatListUpdatesResponse_ListUpdateResponse(const FetchThreatListUpdatesResponse_ListUpdateResponse& from); + FetchThreatListUpdatesResponse_ListUpdateResponse(FetchThreatListUpdatesResponse_ListUpdateResponse&& from) noexcept + : FetchThreatListUpdatesResponse_ListUpdateResponse() { + *this = ::std::move(from); + } + + inline FetchThreatListUpdatesResponse_ListUpdateResponse& operator=(const FetchThreatListUpdatesResponse_ListUpdateResponse& from) { + CopyFrom(from); + return *this; + } + inline FetchThreatListUpdatesResponse_ListUpdateResponse& operator=(FetchThreatListUpdatesResponse_ListUpdateResponse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FetchThreatListUpdatesResponse_ListUpdateResponse& default_instance() { + return *internal_default_instance(); + } + static inline const FetchThreatListUpdatesResponse_ListUpdateResponse* internal_default_instance() { + return reinterpret_cast<const FetchThreatListUpdatesResponse_ListUpdateResponse*>( + &_FetchThreatListUpdatesResponse_ListUpdateResponse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 7; + + friend void swap(FetchThreatListUpdatesResponse_ListUpdateResponse& a, FetchThreatListUpdatesResponse_ListUpdateResponse& b) { + a.Swap(&b); + } + inline void Swap(FetchThreatListUpdatesResponse_ListUpdateResponse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FetchThreatListUpdatesResponse_ListUpdateResponse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FetchThreatListUpdatesResponse_ListUpdateResponse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FetchThreatListUpdatesResponse_ListUpdateResponse>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FetchThreatListUpdatesResponse_ListUpdateResponse& from); + void MergeFrom(const FetchThreatListUpdatesResponse_ListUpdateResponse& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FetchThreatListUpdatesResponse_ListUpdateResponse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse"; + } + protected: + explicit FetchThreatListUpdatesResponse_ListUpdateResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType ResponseType; + static constexpr ResponseType RESPONSE_TYPE_UNSPECIFIED = + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_RESPONSE_TYPE_UNSPECIFIED; + static constexpr ResponseType PARTIAL_UPDATE = + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_PARTIAL_UPDATE; + static constexpr ResponseType FULL_UPDATE = + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_FULL_UPDATE; + static inline bool ResponseType_IsValid(int value) { + return FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_IsValid(value); + } + static constexpr ResponseType ResponseType_MIN = + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_MIN; + static constexpr ResponseType ResponseType_MAX = + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_MAX; + static constexpr int ResponseType_ARRAYSIZE = + FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_ResponseType_ARRAYSIZE; + template<typename T> + static inline const std::string& ResponseType_Name(T enum_t_value) { + static_assert(::std::is_same<T, ResponseType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ResponseType_Name."); + return FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Name(enum_t_value); + } + static inline bool ResponseType_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + ResponseType* value) { + return FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kAdditionsFieldNumber = 5, + kRemovalsFieldNumber = 6, + kNewClientStateFieldNumber = 7, + kChecksumFieldNumber = 8, + kThreatTypeFieldNumber = 1, + kThreatEntryTypeFieldNumber = 2, + kPlatformTypeFieldNumber = 3, + kResponseTypeFieldNumber = 4, + }; + // repeated .mozilla.safebrowsing.ThreatEntrySet additions = 5; + int additions_size() const; + private: + int _internal_additions_size() const; + public: + void clear_additions(); + ::mozilla::safebrowsing::ThreatEntrySet* mutable_additions(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >* + mutable_additions(); + private: + const ::mozilla::safebrowsing::ThreatEntrySet& _internal_additions(int index) const; + ::mozilla::safebrowsing::ThreatEntrySet* _internal_add_additions(); + public: + const ::mozilla::safebrowsing::ThreatEntrySet& additions(int index) const; + ::mozilla::safebrowsing::ThreatEntrySet* add_additions(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >& + additions() const; + + // repeated .mozilla.safebrowsing.ThreatEntrySet removals = 6; + int removals_size() const; + private: + int _internal_removals_size() const; + public: + void clear_removals(); + ::mozilla::safebrowsing::ThreatEntrySet* mutable_removals(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >* + mutable_removals(); + private: + const ::mozilla::safebrowsing::ThreatEntrySet& _internal_removals(int index) const; + ::mozilla::safebrowsing::ThreatEntrySet* _internal_add_removals(); + public: + const ::mozilla::safebrowsing::ThreatEntrySet& removals(int index) const; + ::mozilla::safebrowsing::ThreatEntrySet* add_removals(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >& + removals() const; + + // optional bytes new_client_state = 7; + bool has_new_client_state() const; + private: + bool _internal_has_new_client_state() const; + public: + void clear_new_client_state(); + const std::string& new_client_state() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_new_client_state(ArgT0&& arg0, ArgT... args); + std::string* mutable_new_client_state(); + PROTOBUF_NODISCARD std::string* release_new_client_state(); + void set_allocated_new_client_state(std::string* new_client_state); + private: + const std::string& _internal_new_client_state() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_new_client_state(const std::string& value); + std::string* _internal_mutable_new_client_state(); + public: + + // optional .mozilla.safebrowsing.Checksum checksum = 8; + bool has_checksum() const; + private: + bool _internal_has_checksum() const; + public: + void clear_checksum(); + const ::mozilla::safebrowsing::Checksum& checksum() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::Checksum* release_checksum(); + ::mozilla::safebrowsing::Checksum* mutable_checksum(); + void set_allocated_checksum(::mozilla::safebrowsing::Checksum* checksum); + private: + const ::mozilla::safebrowsing::Checksum& _internal_checksum() const; + ::mozilla::safebrowsing::Checksum* _internal_mutable_checksum(); + public: + void unsafe_arena_set_allocated_checksum( + ::mozilla::safebrowsing::Checksum* checksum); + ::mozilla::safebrowsing::Checksum* unsafe_arena_release_checksum(); + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + bool has_threat_type() const; + private: + bool _internal_has_threat_type() const; + public: + void clear_threat_type(); + ::mozilla::safebrowsing::ThreatType threat_type() const; + void set_threat_type(::mozilla::safebrowsing::ThreatType value); + private: + ::mozilla::safebrowsing::ThreatType _internal_threat_type() const; + void _internal_set_threat_type(::mozilla::safebrowsing::ThreatType value); + public: + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 2; + bool has_threat_entry_type() const; + private: + bool _internal_has_threat_entry_type() const; + public: + void clear_threat_entry_type(); + ::mozilla::safebrowsing::ThreatEntryType threat_entry_type() const; + void set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + private: + ::mozilla::safebrowsing::ThreatEntryType _internal_threat_entry_type() const; + void _internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + public: + + // optional .mozilla.safebrowsing.PlatformType platform_type = 3; + bool has_platform_type() const; + private: + bool _internal_has_platform_type() const; + public: + void clear_platform_type(); + ::mozilla::safebrowsing::PlatformType platform_type() const; + void set_platform_type(::mozilla::safebrowsing::PlatformType value); + private: + ::mozilla::safebrowsing::PlatformType _internal_platform_type() const; + void _internal_set_platform_type(::mozilla::safebrowsing::PlatformType value); + public: + + // optional .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.ResponseType response_type = 4; + bool has_response_type() const; + private: + bool _internal_has_response_type() const; + public: + void clear_response_type(); + ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType response_type() const; + void set_response_type(::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType value); + private: + ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType _internal_response_type() const; + void _internal_set_response_type(::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet > additions_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet > removals_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr new_client_state_; + ::mozilla::safebrowsing::Checksum* checksum_; + int threat_type_; + int threat_entry_type_; + int platform_type_; + int response_type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FetchThreatListUpdatesResponse final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FetchThreatListUpdatesResponse) */ { + public: + inline FetchThreatListUpdatesResponse() : FetchThreatListUpdatesResponse(nullptr) {} + ~FetchThreatListUpdatesResponse() override; + explicit PROTOBUF_CONSTEXPR FetchThreatListUpdatesResponse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FetchThreatListUpdatesResponse(const FetchThreatListUpdatesResponse& from); + FetchThreatListUpdatesResponse(FetchThreatListUpdatesResponse&& from) noexcept + : FetchThreatListUpdatesResponse() { + *this = ::std::move(from); + } + + inline FetchThreatListUpdatesResponse& operator=(const FetchThreatListUpdatesResponse& from) { + CopyFrom(from); + return *this; + } + inline FetchThreatListUpdatesResponse& operator=(FetchThreatListUpdatesResponse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FetchThreatListUpdatesResponse& default_instance() { + return *internal_default_instance(); + } + static inline const FetchThreatListUpdatesResponse* internal_default_instance() { + return reinterpret_cast<const FetchThreatListUpdatesResponse*>( + &_FetchThreatListUpdatesResponse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 8; + + friend void swap(FetchThreatListUpdatesResponse& a, FetchThreatListUpdatesResponse& b) { + a.Swap(&b); + } + inline void Swap(FetchThreatListUpdatesResponse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FetchThreatListUpdatesResponse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FetchThreatListUpdatesResponse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FetchThreatListUpdatesResponse>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FetchThreatListUpdatesResponse& from); + void MergeFrom(const FetchThreatListUpdatesResponse& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FetchThreatListUpdatesResponse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FetchThreatListUpdatesResponse"; + } + protected: + explicit FetchThreatListUpdatesResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef FetchThreatListUpdatesResponse_ListUpdateResponse ListUpdateResponse; + + // accessors ------------------------------------------------------- + + enum : int { + kListUpdateResponsesFieldNumber = 1, + kMinimumWaitDurationFieldNumber = 2, + }; + // repeated .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse list_update_responses = 1; + int list_update_responses_size() const; + private: + int _internal_list_update_responses_size() const; + public: + void clear_list_update_responses(); + ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* mutable_list_update_responses(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse >* + mutable_list_update_responses(); + private: + const ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse& _internal_list_update_responses(int index) const; + ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* _internal_add_list_update_responses(); + public: + const ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse& list_update_responses(int index) const; + ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* add_list_update_responses(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse >& + list_update_responses() const; + + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + bool has_minimum_wait_duration() const; + private: + bool _internal_has_minimum_wait_duration() const; + public: + void clear_minimum_wait_duration(); + const ::mozilla::safebrowsing::Duration& minimum_wait_duration() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::Duration* release_minimum_wait_duration(); + ::mozilla::safebrowsing::Duration* mutable_minimum_wait_duration(); + void set_allocated_minimum_wait_duration(::mozilla::safebrowsing::Duration* minimum_wait_duration); + private: + const ::mozilla::safebrowsing::Duration& _internal_minimum_wait_duration() const; + ::mozilla::safebrowsing::Duration* _internal_mutable_minimum_wait_duration(); + public: + void unsafe_arena_set_allocated_minimum_wait_duration( + ::mozilla::safebrowsing::Duration* minimum_wait_duration); + ::mozilla::safebrowsing::Duration* unsafe_arena_release_minimum_wait_duration(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FetchThreatListUpdatesResponse) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse > list_update_responses_; + ::mozilla::safebrowsing::Duration* minimum_wait_duration_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FindFullHashesRequest final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FindFullHashesRequest) */ { + public: + inline FindFullHashesRequest() : FindFullHashesRequest(nullptr) {} + ~FindFullHashesRequest() override; + explicit PROTOBUF_CONSTEXPR FindFullHashesRequest(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FindFullHashesRequest(const FindFullHashesRequest& from); + FindFullHashesRequest(FindFullHashesRequest&& from) noexcept + : FindFullHashesRequest() { + *this = ::std::move(from); + } + + inline FindFullHashesRequest& operator=(const FindFullHashesRequest& from) { + CopyFrom(from); + return *this; + } + inline FindFullHashesRequest& operator=(FindFullHashesRequest&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FindFullHashesRequest& default_instance() { + return *internal_default_instance(); + } + static inline const FindFullHashesRequest* internal_default_instance() { + return reinterpret_cast<const FindFullHashesRequest*>( + &_FindFullHashesRequest_default_instance_); + } + static constexpr int kIndexInFileMessages = + 9; + + friend void swap(FindFullHashesRequest& a, FindFullHashesRequest& b) { + a.Swap(&b); + } + inline void Swap(FindFullHashesRequest* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FindFullHashesRequest* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FindFullHashesRequest* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FindFullHashesRequest>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FindFullHashesRequest& from); + void MergeFrom(const FindFullHashesRequest& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FindFullHashesRequest* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FindFullHashesRequest"; + } + protected: + explicit FindFullHashesRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kClientStatesFieldNumber = 2, + kClientFieldNumber = 1, + kThreatInfoFieldNumber = 3, + }; + // repeated bytes client_states = 2; + int client_states_size() const; + private: + int _internal_client_states_size() const; + public: + void clear_client_states(); + const std::string& client_states(int index) const; + std::string* mutable_client_states(int index); + void set_client_states(int index, const std::string& value); + void set_client_states(int index, std::string&& value); + void set_client_states(int index, const char* value); + void set_client_states(int index, const void* value, size_t size); + std::string* add_client_states(); + void add_client_states(const std::string& value); + void add_client_states(std::string&& value); + void add_client_states(const char* value); + void add_client_states(const void* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<std::string>& client_states() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<std::string>* mutable_client_states(); + private: + const std::string& _internal_client_states(int index) const; + std::string* _internal_add_client_states(); + public: + + // optional .mozilla.safebrowsing.ClientInfo client = 1; + bool has_client() const; + private: + bool _internal_has_client() const; + public: + void clear_client(); + const ::mozilla::safebrowsing::ClientInfo& client() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ClientInfo* release_client(); + ::mozilla::safebrowsing::ClientInfo* mutable_client(); + void set_allocated_client(::mozilla::safebrowsing::ClientInfo* client); + private: + const ::mozilla::safebrowsing::ClientInfo& _internal_client() const; + ::mozilla::safebrowsing::ClientInfo* _internal_mutable_client(); + public: + void unsafe_arena_set_allocated_client( + ::mozilla::safebrowsing::ClientInfo* client); + ::mozilla::safebrowsing::ClientInfo* unsafe_arena_release_client(); + + // optional .mozilla.safebrowsing.ThreatInfo threat_info = 3; + bool has_threat_info() const; + private: + bool _internal_has_threat_info() const; + public: + void clear_threat_info(); + const ::mozilla::safebrowsing::ThreatInfo& threat_info() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ThreatInfo* release_threat_info(); + ::mozilla::safebrowsing::ThreatInfo* mutable_threat_info(); + void set_allocated_threat_info(::mozilla::safebrowsing::ThreatInfo* threat_info); + private: + const ::mozilla::safebrowsing::ThreatInfo& _internal_threat_info() const; + ::mozilla::safebrowsing::ThreatInfo* _internal_mutable_threat_info(); + public: + void unsafe_arena_set_allocated_threat_info( + ::mozilla::safebrowsing::ThreatInfo* threat_info); + ::mozilla::safebrowsing::ThreatInfo* unsafe_arena_release_threat_info(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FindFullHashesRequest) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<std::string> client_states_; + ::mozilla::safebrowsing::ClientInfo* client_; + ::mozilla::safebrowsing::ThreatInfo* threat_info_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class FindFullHashesResponse final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.FindFullHashesResponse) */ { + public: + inline FindFullHashesResponse() : FindFullHashesResponse(nullptr) {} + ~FindFullHashesResponse() override; + explicit PROTOBUF_CONSTEXPR FindFullHashesResponse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + FindFullHashesResponse(const FindFullHashesResponse& from); + FindFullHashesResponse(FindFullHashesResponse&& from) noexcept + : FindFullHashesResponse() { + *this = ::std::move(from); + } + + inline FindFullHashesResponse& operator=(const FindFullHashesResponse& from) { + CopyFrom(from); + return *this; + } + inline FindFullHashesResponse& operator=(FindFullHashesResponse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const FindFullHashesResponse& default_instance() { + return *internal_default_instance(); + } + static inline const FindFullHashesResponse* internal_default_instance() { + return reinterpret_cast<const FindFullHashesResponse*>( + &_FindFullHashesResponse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 10; + + friend void swap(FindFullHashesResponse& a, FindFullHashesResponse& b) { + a.Swap(&b); + } + inline void Swap(FindFullHashesResponse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(FindFullHashesResponse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + FindFullHashesResponse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<FindFullHashesResponse>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const FindFullHashesResponse& from); + void MergeFrom(const FindFullHashesResponse& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FindFullHashesResponse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.FindFullHashesResponse"; + } + protected: + explicit FindFullHashesResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kMatchesFieldNumber = 1, + kMinimumWaitDurationFieldNumber = 2, + kNegativeCacheDurationFieldNumber = 3, + }; + // repeated .mozilla.safebrowsing.ThreatMatch matches = 1; + int matches_size() const; + private: + int _internal_matches_size() const; + public: + void clear_matches(); + ::mozilla::safebrowsing::ThreatMatch* mutable_matches(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >* + mutable_matches(); + private: + const ::mozilla::safebrowsing::ThreatMatch& _internal_matches(int index) const; + ::mozilla::safebrowsing::ThreatMatch* _internal_add_matches(); + public: + const ::mozilla::safebrowsing::ThreatMatch& matches(int index) const; + ::mozilla::safebrowsing::ThreatMatch* add_matches(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >& + matches() const; + + // optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; + bool has_minimum_wait_duration() const; + private: + bool _internal_has_minimum_wait_duration() const; + public: + void clear_minimum_wait_duration(); + const ::mozilla::safebrowsing::Duration& minimum_wait_duration() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::Duration* release_minimum_wait_duration(); + ::mozilla::safebrowsing::Duration* mutable_minimum_wait_duration(); + void set_allocated_minimum_wait_duration(::mozilla::safebrowsing::Duration* minimum_wait_duration); + private: + const ::mozilla::safebrowsing::Duration& _internal_minimum_wait_duration() const; + ::mozilla::safebrowsing::Duration* _internal_mutable_minimum_wait_duration(); + public: + void unsafe_arena_set_allocated_minimum_wait_duration( + ::mozilla::safebrowsing::Duration* minimum_wait_duration); + ::mozilla::safebrowsing::Duration* unsafe_arena_release_minimum_wait_duration(); + + // optional .mozilla.safebrowsing.Duration negative_cache_duration = 3; + bool has_negative_cache_duration() const; + private: + bool _internal_has_negative_cache_duration() const; + public: + void clear_negative_cache_duration(); + const ::mozilla::safebrowsing::Duration& negative_cache_duration() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::Duration* release_negative_cache_duration(); + ::mozilla::safebrowsing::Duration* mutable_negative_cache_duration(); + void set_allocated_negative_cache_duration(::mozilla::safebrowsing::Duration* negative_cache_duration); + private: + const ::mozilla::safebrowsing::Duration& _internal_negative_cache_duration() const; + ::mozilla::safebrowsing::Duration* _internal_mutable_negative_cache_duration(); + public: + void unsafe_arena_set_allocated_negative_cache_duration( + ::mozilla::safebrowsing::Duration* negative_cache_duration); + ::mozilla::safebrowsing::Duration* unsafe_arena_release_negative_cache_duration(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.FindFullHashesResponse) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch > matches_; + ::mozilla::safebrowsing::Duration* minimum_wait_duration_; + ::mozilla::safebrowsing::Duration* negative_cache_duration_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatHit_ThreatSource final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatHit.ThreatSource) */ { + public: + inline ThreatHit_ThreatSource() : ThreatHit_ThreatSource(nullptr) {} + ~ThreatHit_ThreatSource() override; + explicit PROTOBUF_CONSTEXPR ThreatHit_ThreatSource(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatHit_ThreatSource(const ThreatHit_ThreatSource& from); + ThreatHit_ThreatSource(ThreatHit_ThreatSource&& from) noexcept + : ThreatHit_ThreatSource() { + *this = ::std::move(from); + } + + inline ThreatHit_ThreatSource& operator=(const ThreatHit_ThreatSource& from) { + CopyFrom(from); + return *this; + } + inline ThreatHit_ThreatSource& operator=(ThreatHit_ThreatSource&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatHit_ThreatSource& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatHit_ThreatSource* internal_default_instance() { + return reinterpret_cast<const ThreatHit_ThreatSource*>( + &_ThreatHit_ThreatSource_default_instance_); + } + static constexpr int kIndexInFileMessages = + 11; + + friend void swap(ThreatHit_ThreatSource& a, ThreatHit_ThreatSource& b) { + a.Swap(&b); + } + inline void Swap(ThreatHit_ThreatSource* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatHit_ThreatSource* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatHit_ThreatSource* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatHit_ThreatSource>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatHit_ThreatSource& from); + void MergeFrom(const ThreatHit_ThreatSource& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatHit_ThreatSource* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatHit.ThreatSource"; + } + protected: + explicit ThreatHit_ThreatSource(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUrlFieldNumber = 1, + kRemoteIpFieldNumber = 3, + kReferrerFieldNumber = 4, + kTypeFieldNumber = 2, + }; + // optional string url = 1; + bool has_url() const; + private: + bool _internal_has_url() const; + public: + void clear_url(); + const std::string& url() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_url(ArgT0&& arg0, ArgT... args); + std::string* mutable_url(); + PROTOBUF_NODISCARD std::string* release_url(); + void set_allocated_url(std::string* url); + private: + const std::string& _internal_url() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_url(const std::string& value); + std::string* _internal_mutable_url(); + public: + + // optional string remote_ip = 3; + bool has_remote_ip() const; + private: + bool _internal_has_remote_ip() const; + public: + void clear_remote_ip(); + const std::string& remote_ip() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_remote_ip(ArgT0&& arg0, ArgT... args); + std::string* mutable_remote_ip(); + PROTOBUF_NODISCARD std::string* release_remote_ip(); + void set_allocated_remote_ip(std::string* remote_ip); + private: + const std::string& _internal_remote_ip() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_remote_ip(const std::string& value); + std::string* _internal_mutable_remote_ip(); + public: + + // optional string referrer = 4; + bool has_referrer() const; + private: + bool _internal_has_referrer() const; + public: + void clear_referrer(); + const std::string& referrer() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_referrer(ArgT0&& arg0, ArgT... args); + std::string* mutable_referrer(); + PROTOBUF_NODISCARD std::string* release_referrer(); + void set_allocated_referrer(std::string* referrer); + private: + const std::string& _internal_referrer() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_referrer(const std::string& value); + std::string* _internal_mutable_referrer(); + public: + + // optional .mozilla.safebrowsing.ThreatHit.ThreatSourceType type = 2; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::mozilla::safebrowsing::ThreatHit_ThreatSourceType type() const; + void set_type(::mozilla::safebrowsing::ThreatHit_ThreatSourceType value); + private: + ::mozilla::safebrowsing::ThreatHit_ThreatSourceType _internal_type() const; + void _internal_set_type(::mozilla::safebrowsing::ThreatHit_ThreatSourceType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatHit.ThreatSource) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr url_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr remote_ip_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr referrer_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatHit_UserInfo final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatHit.UserInfo) */ { + public: + inline ThreatHit_UserInfo() : ThreatHit_UserInfo(nullptr) {} + ~ThreatHit_UserInfo() override; + explicit PROTOBUF_CONSTEXPR ThreatHit_UserInfo(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatHit_UserInfo(const ThreatHit_UserInfo& from); + ThreatHit_UserInfo(ThreatHit_UserInfo&& from) noexcept + : ThreatHit_UserInfo() { + *this = ::std::move(from); + } + + inline ThreatHit_UserInfo& operator=(const ThreatHit_UserInfo& from) { + CopyFrom(from); + return *this; + } + inline ThreatHit_UserInfo& operator=(ThreatHit_UserInfo&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatHit_UserInfo& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatHit_UserInfo* internal_default_instance() { + return reinterpret_cast<const ThreatHit_UserInfo*>( + &_ThreatHit_UserInfo_default_instance_); + } + static constexpr int kIndexInFileMessages = + 12; + + friend void swap(ThreatHit_UserInfo& a, ThreatHit_UserInfo& b) { + a.Swap(&b); + } + inline void Swap(ThreatHit_UserInfo* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatHit_UserInfo* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatHit_UserInfo* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatHit_UserInfo>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatHit_UserInfo& from); + void MergeFrom(const ThreatHit_UserInfo& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatHit_UserInfo* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatHit.UserInfo"; + } + protected: + explicit ThreatHit_UserInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRegionCodeFieldNumber = 1, + kUserIdFieldNumber = 2, + }; + // optional string region_code = 1; + bool has_region_code() const; + private: + bool _internal_has_region_code() const; + public: + void clear_region_code(); + const std::string& region_code() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_region_code(ArgT0&& arg0, ArgT... args); + std::string* mutable_region_code(); + PROTOBUF_NODISCARD std::string* release_region_code(); + void set_allocated_region_code(std::string* region_code); + private: + const std::string& _internal_region_code() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_region_code(const std::string& value); + std::string* _internal_mutable_region_code(); + public: + + // optional bytes user_id = 2; + bool has_user_id() const; + private: + bool _internal_has_user_id() const; + public: + void clear_user_id(); + const std::string& user_id() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_user_id(ArgT0&& arg0, ArgT... args); + std::string* mutable_user_id(); + PROTOBUF_NODISCARD std::string* release_user_id(); + void set_allocated_user_id(std::string* user_id); + private: + const std::string& _internal_user_id() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_user_id(const std::string& value); + std::string* _internal_mutable_user_id(); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatHit.UserInfo) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr region_code_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr user_id_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatHit final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatHit) */ { + public: + inline ThreatHit() : ThreatHit(nullptr) {} + ~ThreatHit() override; + explicit PROTOBUF_CONSTEXPR ThreatHit(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatHit(const ThreatHit& from); + ThreatHit(ThreatHit&& from) noexcept + : ThreatHit() { + *this = ::std::move(from); + } + + inline ThreatHit& operator=(const ThreatHit& from) { + CopyFrom(from); + return *this; + } + inline ThreatHit& operator=(ThreatHit&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatHit& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatHit* internal_default_instance() { + return reinterpret_cast<const ThreatHit*>( + &_ThreatHit_default_instance_); + } + static constexpr int kIndexInFileMessages = + 13; + + friend void swap(ThreatHit& a, ThreatHit& b) { + a.Swap(&b); + } + inline void Swap(ThreatHit* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatHit* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatHit* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatHit>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatHit& from); + void MergeFrom(const ThreatHit& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatHit* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatHit"; + } + protected: + explicit ThreatHit(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef ThreatHit_ThreatSource ThreatSource; + typedef ThreatHit_UserInfo UserInfo; + + typedef ThreatHit_ThreatSourceType ThreatSourceType; + static constexpr ThreatSourceType THREAT_SOURCE_TYPE_UNSPECIFIED = + ThreatHit_ThreatSourceType_THREAT_SOURCE_TYPE_UNSPECIFIED; + static constexpr ThreatSourceType MATCHING_URL = + ThreatHit_ThreatSourceType_MATCHING_URL; + static constexpr ThreatSourceType TAB_URL = + ThreatHit_ThreatSourceType_TAB_URL; + static constexpr ThreatSourceType TAB_REDIRECT = + ThreatHit_ThreatSourceType_TAB_REDIRECT; + static constexpr ThreatSourceType TAB_RESOURCE = + ThreatHit_ThreatSourceType_TAB_RESOURCE; + static inline bool ThreatSourceType_IsValid(int value) { + return ThreatHit_ThreatSourceType_IsValid(value); + } + static constexpr ThreatSourceType ThreatSourceType_MIN = + ThreatHit_ThreatSourceType_ThreatSourceType_MIN; + static constexpr ThreatSourceType ThreatSourceType_MAX = + ThreatHit_ThreatSourceType_ThreatSourceType_MAX; + static constexpr int ThreatSourceType_ARRAYSIZE = + ThreatHit_ThreatSourceType_ThreatSourceType_ARRAYSIZE; + template<typename T> + static inline const std::string& ThreatSourceType_Name(T enum_t_value) { + static_assert(::std::is_same<T, ThreatSourceType>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ThreatSourceType_Name."); + return ThreatHit_ThreatSourceType_Name(enum_t_value); + } + static inline bool ThreatSourceType_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + ThreatSourceType* value) { + return ThreatHit_ThreatSourceType_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kResourcesFieldNumber = 4, + kEntryFieldNumber = 3, + kClientInfoFieldNumber = 5, + kUserInfoFieldNumber = 6, + kThreatTypeFieldNumber = 1, + kPlatformTypeFieldNumber = 2, + }; + // repeated .mozilla.safebrowsing.ThreatHit.ThreatSource resources = 4; + int resources_size() const; + private: + int _internal_resources_size() const; + public: + void clear_resources(); + ::mozilla::safebrowsing::ThreatHit_ThreatSource* mutable_resources(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatHit_ThreatSource >* + mutable_resources(); + private: + const ::mozilla::safebrowsing::ThreatHit_ThreatSource& _internal_resources(int index) const; + ::mozilla::safebrowsing::ThreatHit_ThreatSource* _internal_add_resources(); + public: + const ::mozilla::safebrowsing::ThreatHit_ThreatSource& resources(int index) const; + ::mozilla::safebrowsing::ThreatHit_ThreatSource* add_resources(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatHit_ThreatSource >& + resources() const; + + // optional .mozilla.safebrowsing.ThreatEntry entry = 3; + bool has_entry() const; + private: + bool _internal_has_entry() const; + public: + void clear_entry(); + const ::mozilla::safebrowsing::ThreatEntry& entry() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ThreatEntry* release_entry(); + ::mozilla::safebrowsing::ThreatEntry* mutable_entry(); + void set_allocated_entry(::mozilla::safebrowsing::ThreatEntry* entry); + private: + const ::mozilla::safebrowsing::ThreatEntry& _internal_entry() const; + ::mozilla::safebrowsing::ThreatEntry* _internal_mutable_entry(); + public: + void unsafe_arena_set_allocated_entry( + ::mozilla::safebrowsing::ThreatEntry* entry); + ::mozilla::safebrowsing::ThreatEntry* unsafe_arena_release_entry(); + + // optional .mozilla.safebrowsing.ClientInfo client_info = 5; + bool has_client_info() const; + private: + bool _internal_has_client_info() const; + public: + void clear_client_info(); + const ::mozilla::safebrowsing::ClientInfo& client_info() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ClientInfo* release_client_info(); + ::mozilla::safebrowsing::ClientInfo* mutable_client_info(); + void set_allocated_client_info(::mozilla::safebrowsing::ClientInfo* client_info); + private: + const ::mozilla::safebrowsing::ClientInfo& _internal_client_info() const; + ::mozilla::safebrowsing::ClientInfo* _internal_mutable_client_info(); + public: + void unsafe_arena_set_allocated_client_info( + ::mozilla::safebrowsing::ClientInfo* client_info); + ::mozilla::safebrowsing::ClientInfo* unsafe_arena_release_client_info(); + + // optional .mozilla.safebrowsing.ThreatHit.UserInfo user_info = 6; + bool has_user_info() const; + private: + bool _internal_has_user_info() const; + public: + void clear_user_info(); + const ::mozilla::safebrowsing::ThreatHit_UserInfo& user_info() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::ThreatHit_UserInfo* release_user_info(); + ::mozilla::safebrowsing::ThreatHit_UserInfo* mutable_user_info(); + void set_allocated_user_info(::mozilla::safebrowsing::ThreatHit_UserInfo* user_info); + private: + const ::mozilla::safebrowsing::ThreatHit_UserInfo& _internal_user_info() const; + ::mozilla::safebrowsing::ThreatHit_UserInfo* _internal_mutable_user_info(); + public: + void unsafe_arena_set_allocated_user_info( + ::mozilla::safebrowsing::ThreatHit_UserInfo* user_info); + ::mozilla::safebrowsing::ThreatHit_UserInfo* unsafe_arena_release_user_info(); + + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + bool has_threat_type() const; + private: + bool _internal_has_threat_type() const; + public: + void clear_threat_type(); + ::mozilla::safebrowsing::ThreatType threat_type() const; + void set_threat_type(::mozilla::safebrowsing::ThreatType value); + private: + ::mozilla::safebrowsing::ThreatType _internal_threat_type() const; + void _internal_set_threat_type(::mozilla::safebrowsing::ThreatType value); + public: + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + bool has_platform_type() const; + private: + bool _internal_has_platform_type() const; + public: + void clear_platform_type(); + ::mozilla::safebrowsing::PlatformType platform_type() const; + void set_platform_type(::mozilla::safebrowsing::PlatformType value); + private: + ::mozilla::safebrowsing::PlatformType _internal_platform_type() const; + void _internal_set_platform_type(::mozilla::safebrowsing::PlatformType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatHit) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatHit_ThreatSource > resources_; + ::mozilla::safebrowsing::ThreatEntry* entry_; + ::mozilla::safebrowsing::ClientInfo* client_info_; + ::mozilla::safebrowsing::ThreatHit_UserInfo* user_info_; + int threat_type_; + int platform_type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ClientInfo final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ClientInfo) */ { + public: + inline ClientInfo() : ClientInfo(nullptr) {} + ~ClientInfo() override; + explicit PROTOBUF_CONSTEXPR ClientInfo(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ClientInfo(const ClientInfo& from); + ClientInfo(ClientInfo&& from) noexcept + : ClientInfo() { + *this = ::std::move(from); + } + + inline ClientInfo& operator=(const ClientInfo& from) { + CopyFrom(from); + return *this; + } + inline ClientInfo& operator=(ClientInfo&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ClientInfo& default_instance() { + return *internal_default_instance(); + } + static inline const ClientInfo* internal_default_instance() { + return reinterpret_cast<const ClientInfo*>( + &_ClientInfo_default_instance_); + } + static constexpr int kIndexInFileMessages = + 14; + + friend void swap(ClientInfo& a, ClientInfo& b) { + a.Swap(&b); + } + inline void Swap(ClientInfo* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ClientInfo* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ClientInfo* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ClientInfo>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ClientInfo& from); + void MergeFrom(const ClientInfo& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ClientInfo* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ClientInfo"; + } + protected: + explicit ClientInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kClientIdFieldNumber = 1, + kClientVersionFieldNumber = 2, + }; + // optional string client_id = 1; + bool has_client_id() const; + private: + bool _internal_has_client_id() const; + public: + void clear_client_id(); + const std::string& client_id() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_client_id(ArgT0&& arg0, ArgT... args); + std::string* mutable_client_id(); + PROTOBUF_NODISCARD std::string* release_client_id(); + void set_allocated_client_id(std::string* client_id); + private: + const std::string& _internal_client_id() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_client_id(const std::string& value); + std::string* _internal_mutable_client_id(); + public: + + // optional string client_version = 2; + bool has_client_version() const; + private: + bool _internal_has_client_version() const; + public: + void clear_client_version(); + const std::string& client_version() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_client_version(ArgT0&& arg0, ArgT... args); + std::string* mutable_client_version(); + PROTOBUF_NODISCARD std::string* release_client_version(); + void set_allocated_client_version(std::string* client_version); + private: + const std::string& _internal_client_version() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_client_version(const std::string& value); + std::string* _internal_mutable_client_version(); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ClientInfo) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr client_id_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr client_version_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ChromeClientInfo final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ChromeClientInfo) */ { + public: + inline ChromeClientInfo() : ChromeClientInfo(nullptr) {} + ~ChromeClientInfo() override; + explicit PROTOBUF_CONSTEXPR ChromeClientInfo(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ChromeClientInfo(const ChromeClientInfo& from); + ChromeClientInfo(ChromeClientInfo&& from) noexcept + : ChromeClientInfo() { + *this = ::std::move(from); + } + + inline ChromeClientInfo& operator=(const ChromeClientInfo& from) { + CopyFrom(from); + return *this; + } + inline ChromeClientInfo& operator=(ChromeClientInfo&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ChromeClientInfo& default_instance() { + return *internal_default_instance(); + } + static inline const ChromeClientInfo* internal_default_instance() { + return reinterpret_cast<const ChromeClientInfo*>( + &_ChromeClientInfo_default_instance_); + } + static constexpr int kIndexInFileMessages = + 15; + + friend void swap(ChromeClientInfo& a, ChromeClientInfo& b) { + a.Swap(&b); + } + inline void Swap(ChromeClientInfo* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ChromeClientInfo* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ChromeClientInfo* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ChromeClientInfo>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ChromeClientInfo& from); + void MergeFrom(const ChromeClientInfo& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ChromeClientInfo* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ChromeClientInfo"; + } + protected: + explicit ChromeClientInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef ChromeClientInfo_SafeBrowsingReportingPopulation SafeBrowsingReportingPopulation; + static constexpr SafeBrowsingReportingPopulation UNSPECIFIED = + ChromeClientInfo_SafeBrowsingReportingPopulation_UNSPECIFIED; + static constexpr SafeBrowsingReportingPopulation OPT_OUT = + ChromeClientInfo_SafeBrowsingReportingPopulation_OPT_OUT; + static constexpr SafeBrowsingReportingPopulation EXTENDED = + ChromeClientInfo_SafeBrowsingReportingPopulation_EXTENDED; + static constexpr SafeBrowsingReportingPopulation SCOUT = + ChromeClientInfo_SafeBrowsingReportingPopulation_SCOUT; + static inline bool SafeBrowsingReportingPopulation_IsValid(int value) { + return ChromeClientInfo_SafeBrowsingReportingPopulation_IsValid(value); + } + static constexpr SafeBrowsingReportingPopulation SafeBrowsingReportingPopulation_MIN = + ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_MIN; + static constexpr SafeBrowsingReportingPopulation SafeBrowsingReportingPopulation_MAX = + ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_MAX; + static constexpr int SafeBrowsingReportingPopulation_ARRAYSIZE = + ChromeClientInfo_SafeBrowsingReportingPopulation_SafeBrowsingReportingPopulation_ARRAYSIZE; + template<typename T> + static inline const std::string& SafeBrowsingReportingPopulation_Name(T enum_t_value) { + static_assert(::std::is_same<T, SafeBrowsingReportingPopulation>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function SafeBrowsingReportingPopulation_Name."); + return ChromeClientInfo_SafeBrowsingReportingPopulation_Name(enum_t_value); + } + static inline bool SafeBrowsingReportingPopulation_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + SafeBrowsingReportingPopulation* value) { + return ChromeClientInfo_SafeBrowsingReportingPopulation_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kReportingPopulationFieldNumber = 1, + }; + // optional .mozilla.safebrowsing.ChromeClientInfo.SafeBrowsingReportingPopulation reporting_population = 1; + bool has_reporting_population() const; + private: + bool _internal_has_reporting_population() const; + public: + void clear_reporting_population(); + ::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation reporting_population() const; + void set_reporting_population(::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation value); + private: + ::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation _internal_reporting_population() const; + void _internal_set_reporting_population(::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ChromeClientInfo) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + int reporting_population_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class Checksum final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.Checksum) */ { + public: + inline Checksum() : Checksum(nullptr) {} + ~Checksum() override; + explicit PROTOBUF_CONSTEXPR Checksum(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Checksum(const Checksum& from); + Checksum(Checksum&& from) noexcept + : Checksum() { + *this = ::std::move(from); + } + + inline Checksum& operator=(const Checksum& from) { + CopyFrom(from); + return *this; + } + inline Checksum& operator=(Checksum&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const Checksum& default_instance() { + return *internal_default_instance(); + } + static inline const Checksum* internal_default_instance() { + return reinterpret_cast<const Checksum*>( + &_Checksum_default_instance_); + } + static constexpr int kIndexInFileMessages = + 16; + + friend void swap(Checksum& a, Checksum& b) { + a.Swap(&b); + } + inline void Swap(Checksum* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Checksum* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Checksum* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<Checksum>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const Checksum& from); + void MergeFrom(const Checksum& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(Checksum* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.Checksum"; + } + protected: + explicit Checksum(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kSha256FieldNumber = 1, + }; + // optional bytes sha256 = 1; + bool has_sha256() const; + private: + bool _internal_has_sha256() const; + public: + void clear_sha256(); + const std::string& sha256() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_sha256(ArgT0&& arg0, ArgT... args); + std::string* mutable_sha256(); + PROTOBUF_NODISCARD std::string* release_sha256(); + void set_allocated_sha256(std::string* sha256); + private: + const std::string& _internal_sha256() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_sha256(const std::string& value); + std::string* _internal_mutable_sha256(); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.Checksum) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr sha256_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatEntry final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatEntry) */ { + public: + inline ThreatEntry() : ThreatEntry(nullptr) {} + ~ThreatEntry() override; + explicit PROTOBUF_CONSTEXPR ThreatEntry(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatEntry(const ThreatEntry& from); + ThreatEntry(ThreatEntry&& from) noexcept + : ThreatEntry() { + *this = ::std::move(from); + } + + inline ThreatEntry& operator=(const ThreatEntry& from) { + CopyFrom(from); + return *this; + } + inline ThreatEntry& operator=(ThreatEntry&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatEntry& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatEntry* internal_default_instance() { + return reinterpret_cast<const ThreatEntry*>( + &_ThreatEntry_default_instance_); + } + static constexpr int kIndexInFileMessages = + 17; + + friend void swap(ThreatEntry& a, ThreatEntry& b) { + a.Swap(&b); + } + inline void Swap(ThreatEntry* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatEntry* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatEntry* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatEntry>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatEntry& from); + void MergeFrom(const ThreatEntry& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatEntry* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatEntry"; + } + protected: + explicit ThreatEntry(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kHashFieldNumber = 1, + kUrlFieldNumber = 2, + }; + // optional bytes hash = 1; + bool has_hash() const; + private: + bool _internal_has_hash() const; + public: + void clear_hash(); + const std::string& hash() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_hash(ArgT0&& arg0, ArgT... args); + std::string* mutable_hash(); + PROTOBUF_NODISCARD std::string* release_hash(); + void set_allocated_hash(std::string* hash); + private: + const std::string& _internal_hash() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_hash(const std::string& value); + std::string* _internal_mutable_hash(); + public: + + // optional string url = 2; + bool has_url() const; + private: + bool _internal_has_url() const; + public: + void clear_url(); + const std::string& url() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_url(ArgT0&& arg0, ArgT... args); + std::string* mutable_url(); + PROTOBUF_NODISCARD std::string* release_url(); + void set_allocated_url(std::string* url); + private: + const std::string& _internal_url() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_url(const std::string& value); + std::string* _internal_mutable_url(); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatEntry) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr hash_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr url_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatEntrySet final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatEntrySet) */ { + public: + inline ThreatEntrySet() : ThreatEntrySet(nullptr) {} + ~ThreatEntrySet() override; + explicit PROTOBUF_CONSTEXPR ThreatEntrySet(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatEntrySet(const ThreatEntrySet& from); + ThreatEntrySet(ThreatEntrySet&& from) noexcept + : ThreatEntrySet() { + *this = ::std::move(from); + } + + inline ThreatEntrySet& operator=(const ThreatEntrySet& from) { + CopyFrom(from); + return *this; + } + inline ThreatEntrySet& operator=(ThreatEntrySet&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatEntrySet& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatEntrySet* internal_default_instance() { + return reinterpret_cast<const ThreatEntrySet*>( + &_ThreatEntrySet_default_instance_); + } + static constexpr int kIndexInFileMessages = + 18; + + friend void swap(ThreatEntrySet& a, ThreatEntrySet& b) { + a.Swap(&b); + } + inline void Swap(ThreatEntrySet* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatEntrySet* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatEntrySet* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatEntrySet>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatEntrySet& from); + void MergeFrom(const ThreatEntrySet& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatEntrySet* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatEntrySet"; + } + protected: + explicit ThreatEntrySet(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRawHashesFieldNumber = 2, + kRawIndicesFieldNumber = 3, + kRiceHashesFieldNumber = 4, + kRiceIndicesFieldNumber = 5, + kCompressionTypeFieldNumber = 1, + }; + // optional .mozilla.safebrowsing.RawHashes raw_hashes = 2; + bool has_raw_hashes() const; + private: + bool _internal_has_raw_hashes() const; + public: + void clear_raw_hashes(); + const ::mozilla::safebrowsing::RawHashes& raw_hashes() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::RawHashes* release_raw_hashes(); + ::mozilla::safebrowsing::RawHashes* mutable_raw_hashes(); + void set_allocated_raw_hashes(::mozilla::safebrowsing::RawHashes* raw_hashes); + private: + const ::mozilla::safebrowsing::RawHashes& _internal_raw_hashes() const; + ::mozilla::safebrowsing::RawHashes* _internal_mutable_raw_hashes(); + public: + void unsafe_arena_set_allocated_raw_hashes( + ::mozilla::safebrowsing::RawHashes* raw_hashes); + ::mozilla::safebrowsing::RawHashes* unsafe_arena_release_raw_hashes(); + + // optional .mozilla.safebrowsing.RawIndices raw_indices = 3; + bool has_raw_indices() const; + private: + bool _internal_has_raw_indices() const; + public: + void clear_raw_indices(); + const ::mozilla::safebrowsing::RawIndices& raw_indices() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::RawIndices* release_raw_indices(); + ::mozilla::safebrowsing::RawIndices* mutable_raw_indices(); + void set_allocated_raw_indices(::mozilla::safebrowsing::RawIndices* raw_indices); + private: + const ::mozilla::safebrowsing::RawIndices& _internal_raw_indices() const; + ::mozilla::safebrowsing::RawIndices* _internal_mutable_raw_indices(); + public: + void unsafe_arena_set_allocated_raw_indices( + ::mozilla::safebrowsing::RawIndices* raw_indices); + ::mozilla::safebrowsing::RawIndices* unsafe_arena_release_raw_indices(); + + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_hashes = 4; + bool has_rice_hashes() const; + private: + bool _internal_has_rice_hashes() const; + public: + void clear_rice_hashes(); + const ::mozilla::safebrowsing::RiceDeltaEncoding& rice_hashes() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::RiceDeltaEncoding* release_rice_hashes(); + ::mozilla::safebrowsing::RiceDeltaEncoding* mutable_rice_hashes(); + void set_allocated_rice_hashes(::mozilla::safebrowsing::RiceDeltaEncoding* rice_hashes); + private: + const ::mozilla::safebrowsing::RiceDeltaEncoding& _internal_rice_hashes() const; + ::mozilla::safebrowsing::RiceDeltaEncoding* _internal_mutable_rice_hashes(); + public: + void unsafe_arena_set_allocated_rice_hashes( + ::mozilla::safebrowsing::RiceDeltaEncoding* rice_hashes); + ::mozilla::safebrowsing::RiceDeltaEncoding* unsafe_arena_release_rice_hashes(); + + // optional .mozilla.safebrowsing.RiceDeltaEncoding rice_indices = 5; + bool has_rice_indices() const; + private: + bool _internal_has_rice_indices() const; + public: + void clear_rice_indices(); + const ::mozilla::safebrowsing::RiceDeltaEncoding& rice_indices() const; + PROTOBUF_NODISCARD ::mozilla::safebrowsing::RiceDeltaEncoding* release_rice_indices(); + ::mozilla::safebrowsing::RiceDeltaEncoding* mutable_rice_indices(); + void set_allocated_rice_indices(::mozilla::safebrowsing::RiceDeltaEncoding* rice_indices); + private: + const ::mozilla::safebrowsing::RiceDeltaEncoding& _internal_rice_indices() const; + ::mozilla::safebrowsing::RiceDeltaEncoding* _internal_mutable_rice_indices(); + public: + void unsafe_arena_set_allocated_rice_indices( + ::mozilla::safebrowsing::RiceDeltaEncoding* rice_indices); + ::mozilla::safebrowsing::RiceDeltaEncoding* unsafe_arena_release_rice_indices(); + + // optional .mozilla.safebrowsing.CompressionType compression_type = 1; + bool has_compression_type() const; + private: + bool _internal_has_compression_type() const; + public: + void clear_compression_type(); + ::mozilla::safebrowsing::CompressionType compression_type() const; + void set_compression_type(::mozilla::safebrowsing::CompressionType value); + private: + ::mozilla::safebrowsing::CompressionType _internal_compression_type() const; + void _internal_set_compression_type(::mozilla::safebrowsing::CompressionType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatEntrySet) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::mozilla::safebrowsing::RawHashes* raw_hashes_; + ::mozilla::safebrowsing::RawIndices* raw_indices_; + ::mozilla::safebrowsing::RiceDeltaEncoding* rice_hashes_; + ::mozilla::safebrowsing::RiceDeltaEncoding* rice_indices_; + int compression_type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class RawIndices final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.RawIndices) */ { + public: + inline RawIndices() : RawIndices(nullptr) {} + ~RawIndices() override; + explicit PROTOBUF_CONSTEXPR RawIndices(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RawIndices(const RawIndices& from); + RawIndices(RawIndices&& from) noexcept + : RawIndices() { + *this = ::std::move(from); + } + + inline RawIndices& operator=(const RawIndices& from) { + CopyFrom(from); + return *this; + } + inline RawIndices& operator=(RawIndices&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const RawIndices& default_instance() { + return *internal_default_instance(); + } + static inline const RawIndices* internal_default_instance() { + return reinterpret_cast<const RawIndices*>( + &_RawIndices_default_instance_); + } + static constexpr int kIndexInFileMessages = + 19; + + friend void swap(RawIndices& a, RawIndices& b) { + a.Swap(&b); + } + inline void Swap(RawIndices* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RawIndices* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RawIndices* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<RawIndices>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const RawIndices& from); + void MergeFrom(const RawIndices& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(RawIndices* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.RawIndices"; + } + protected: + explicit RawIndices(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kIndicesFieldNumber = 1, + }; + // repeated int32 indices = 1; + int indices_size() const; + private: + int _internal_indices_size() const; + public: + void clear_indices(); + private: + int32_t _internal_indices(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >& + _internal_indices() const; + void _internal_add_indices(int32_t value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >* + _internal_mutable_indices(); + public: + int32_t indices(int index) const; + void set_indices(int index, int32_t value); + void add_indices(int32_t value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >& + indices() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >* + mutable_indices(); + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.RawIndices) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > indices_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class RawHashes final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.RawHashes) */ { + public: + inline RawHashes() : RawHashes(nullptr) {} + ~RawHashes() override; + explicit PROTOBUF_CONSTEXPR RawHashes(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RawHashes(const RawHashes& from); + RawHashes(RawHashes&& from) noexcept + : RawHashes() { + *this = ::std::move(from); + } + + inline RawHashes& operator=(const RawHashes& from) { + CopyFrom(from); + return *this; + } + inline RawHashes& operator=(RawHashes&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const RawHashes& default_instance() { + return *internal_default_instance(); + } + static inline const RawHashes* internal_default_instance() { + return reinterpret_cast<const RawHashes*>( + &_RawHashes_default_instance_); + } + static constexpr int kIndexInFileMessages = + 20; + + friend void swap(RawHashes& a, RawHashes& b) { + a.Swap(&b); + } + inline void Swap(RawHashes* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RawHashes* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RawHashes* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<RawHashes>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const RawHashes& from); + void MergeFrom(const RawHashes& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(RawHashes* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.RawHashes"; + } + protected: + explicit RawHashes(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRawHashesFieldNumber = 2, + kPrefixSizeFieldNumber = 1, + }; + // optional bytes raw_hashes = 2; + bool has_raw_hashes() const; + private: + bool _internal_has_raw_hashes() const; + public: + void clear_raw_hashes(); + const std::string& raw_hashes() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_raw_hashes(ArgT0&& arg0, ArgT... args); + std::string* mutable_raw_hashes(); + PROTOBUF_NODISCARD std::string* release_raw_hashes(); + void set_allocated_raw_hashes(std::string* raw_hashes); + private: + const std::string& _internal_raw_hashes() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_raw_hashes(const std::string& value); + std::string* _internal_mutable_raw_hashes(); + public: + + // optional int32 prefix_size = 1; + bool has_prefix_size() const; + private: + bool _internal_has_prefix_size() const; + public: + void clear_prefix_size(); + int32_t prefix_size() const; + void set_prefix_size(int32_t value); + private: + int32_t _internal_prefix_size() const; + void _internal_set_prefix_size(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.RawHashes) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr raw_hashes_; + int32_t prefix_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class RiceDeltaEncoding final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.RiceDeltaEncoding) */ { + public: + inline RiceDeltaEncoding() : RiceDeltaEncoding(nullptr) {} + ~RiceDeltaEncoding() override; + explicit PROTOBUF_CONSTEXPR RiceDeltaEncoding(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + RiceDeltaEncoding(const RiceDeltaEncoding& from); + RiceDeltaEncoding(RiceDeltaEncoding&& from) noexcept + : RiceDeltaEncoding() { + *this = ::std::move(from); + } + + inline RiceDeltaEncoding& operator=(const RiceDeltaEncoding& from) { + CopyFrom(from); + return *this; + } + inline RiceDeltaEncoding& operator=(RiceDeltaEncoding&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const RiceDeltaEncoding& default_instance() { + return *internal_default_instance(); + } + static inline const RiceDeltaEncoding* internal_default_instance() { + return reinterpret_cast<const RiceDeltaEncoding*>( + &_RiceDeltaEncoding_default_instance_); + } + static constexpr int kIndexInFileMessages = + 21; + + friend void swap(RiceDeltaEncoding& a, RiceDeltaEncoding& b) { + a.Swap(&b); + } + inline void Swap(RiceDeltaEncoding* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(RiceDeltaEncoding* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + RiceDeltaEncoding* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<RiceDeltaEncoding>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const RiceDeltaEncoding& from); + void MergeFrom(const RiceDeltaEncoding& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(RiceDeltaEncoding* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.RiceDeltaEncoding"; + } + protected: + explicit RiceDeltaEncoding(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kEncodedDataFieldNumber = 4, + kFirstValueFieldNumber = 1, + kRiceParameterFieldNumber = 2, + kNumEntriesFieldNumber = 3, + }; + // optional bytes encoded_data = 4; + bool has_encoded_data() const; + private: + bool _internal_has_encoded_data() const; + public: + void clear_encoded_data(); + const std::string& encoded_data() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_encoded_data(ArgT0&& arg0, ArgT... args); + std::string* mutable_encoded_data(); + PROTOBUF_NODISCARD std::string* release_encoded_data(); + void set_allocated_encoded_data(std::string* encoded_data); + private: + const std::string& _internal_encoded_data() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_encoded_data(const std::string& value); + std::string* _internal_mutable_encoded_data(); + public: + + // optional int64 first_value = 1; + bool has_first_value() const; + private: + bool _internal_has_first_value() const; + public: + void clear_first_value(); + int64_t first_value() const; + void set_first_value(int64_t value); + private: + int64_t _internal_first_value() const; + void _internal_set_first_value(int64_t value); + public: + + // optional int32 rice_parameter = 2; + bool has_rice_parameter() const; + private: + bool _internal_has_rice_parameter() const; + public: + void clear_rice_parameter(); + int32_t rice_parameter() const; + void set_rice_parameter(int32_t value); + private: + int32_t _internal_rice_parameter() const; + void _internal_set_rice_parameter(int32_t value); + public: + + // optional int32 num_entries = 3; + bool has_num_entries() const; + private: + bool _internal_has_num_entries() const; + public: + void clear_num_entries(); + int32_t num_entries() const; + void set_num_entries(int32_t value); + private: + int32_t _internal_num_entries() const; + void _internal_set_num_entries(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.RiceDeltaEncoding) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr encoded_data_; + int64_t first_value_; + int32_t rice_parameter_; + int32_t num_entries_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatEntryMetadata_MetadataEntry final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) */ { + public: + inline ThreatEntryMetadata_MetadataEntry() : ThreatEntryMetadata_MetadataEntry(nullptr) {} + ~ThreatEntryMetadata_MetadataEntry() override; + explicit PROTOBUF_CONSTEXPR ThreatEntryMetadata_MetadataEntry(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatEntryMetadata_MetadataEntry(const ThreatEntryMetadata_MetadataEntry& from); + ThreatEntryMetadata_MetadataEntry(ThreatEntryMetadata_MetadataEntry&& from) noexcept + : ThreatEntryMetadata_MetadataEntry() { + *this = ::std::move(from); + } + + inline ThreatEntryMetadata_MetadataEntry& operator=(const ThreatEntryMetadata_MetadataEntry& from) { + CopyFrom(from); + return *this; + } + inline ThreatEntryMetadata_MetadataEntry& operator=(ThreatEntryMetadata_MetadataEntry&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatEntryMetadata_MetadataEntry& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatEntryMetadata_MetadataEntry* internal_default_instance() { + return reinterpret_cast<const ThreatEntryMetadata_MetadataEntry*>( + &_ThreatEntryMetadata_MetadataEntry_default_instance_); + } + static constexpr int kIndexInFileMessages = + 22; + + friend void swap(ThreatEntryMetadata_MetadataEntry& a, ThreatEntryMetadata_MetadataEntry& b) { + a.Swap(&b); + } + inline void Swap(ThreatEntryMetadata_MetadataEntry* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatEntryMetadata_MetadataEntry* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatEntryMetadata_MetadataEntry* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatEntryMetadata_MetadataEntry>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatEntryMetadata_MetadataEntry& from); + void MergeFrom(const ThreatEntryMetadata_MetadataEntry& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatEntryMetadata_MetadataEntry* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry"; + } + protected: + explicit ThreatEntryMetadata_MetadataEntry(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kKeyFieldNumber = 1, + kValueFieldNumber = 2, + }; + // optional bytes key = 1; + bool has_key() const; + private: + bool _internal_has_key() const; + public: + void clear_key(); + const std::string& key() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_key(ArgT0&& arg0, ArgT... args); + std::string* mutable_key(); + PROTOBUF_NODISCARD std::string* release_key(); + void set_allocated_key(std::string* key); + private: + const std::string& _internal_key() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_key(const std::string& value); + std::string* _internal_mutable_key(); + public: + + // optional bytes value = 2; + bool has_value() const; + private: + bool _internal_has_value() const; + public: + void clear_value(); + const std::string& value() const; + template <typename ArgT0 = const std::string&, typename... ArgT> + void set_value(ArgT0&& arg0, ArgT... args); + std::string* mutable_value(); + PROTOBUF_NODISCARD std::string* release_value(); + void set_allocated_value(std::string* value); + private: + const std::string& _internal_value() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_value(const std::string& value); + std::string* _internal_mutable_value(); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr key_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr value_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatEntryMetadata final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatEntryMetadata) */ { + public: + inline ThreatEntryMetadata() : ThreatEntryMetadata(nullptr) {} + ~ThreatEntryMetadata() override; + explicit PROTOBUF_CONSTEXPR ThreatEntryMetadata(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatEntryMetadata(const ThreatEntryMetadata& from); + ThreatEntryMetadata(ThreatEntryMetadata&& from) noexcept + : ThreatEntryMetadata() { + *this = ::std::move(from); + } + + inline ThreatEntryMetadata& operator=(const ThreatEntryMetadata& from) { + CopyFrom(from); + return *this; + } + inline ThreatEntryMetadata& operator=(ThreatEntryMetadata&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatEntryMetadata& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatEntryMetadata* internal_default_instance() { + return reinterpret_cast<const ThreatEntryMetadata*>( + &_ThreatEntryMetadata_default_instance_); + } + static constexpr int kIndexInFileMessages = + 23; + + friend void swap(ThreatEntryMetadata& a, ThreatEntryMetadata& b) { + a.Swap(&b); + } + inline void Swap(ThreatEntryMetadata* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatEntryMetadata* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatEntryMetadata* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatEntryMetadata>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatEntryMetadata& from); + void MergeFrom(const ThreatEntryMetadata& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatEntryMetadata* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatEntryMetadata"; + } + protected: + explicit ThreatEntryMetadata(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef ThreatEntryMetadata_MetadataEntry MetadataEntry; + + // accessors ------------------------------------------------------- + + enum : int { + kEntriesFieldNumber = 1, + }; + // repeated .mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry entries = 1; + int entries_size() const; + private: + int _internal_entries_size() const; + public: + void clear_entries(); + ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* mutable_entries(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry >* + mutable_entries(); + private: + const ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry& _internal_entries(int index) const; + ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* _internal_add_entries(); + public: + const ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry& entries(int index) const; + ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* add_entries(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry >& + entries() const; + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatEntryMetadata) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry > entries_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ThreatListDescriptor final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ThreatListDescriptor) */ { + public: + inline ThreatListDescriptor() : ThreatListDescriptor(nullptr) {} + ~ThreatListDescriptor() override; + explicit PROTOBUF_CONSTEXPR ThreatListDescriptor(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ThreatListDescriptor(const ThreatListDescriptor& from); + ThreatListDescriptor(ThreatListDescriptor&& from) noexcept + : ThreatListDescriptor() { + *this = ::std::move(from); + } + + inline ThreatListDescriptor& operator=(const ThreatListDescriptor& from) { + CopyFrom(from); + return *this; + } + inline ThreatListDescriptor& operator=(ThreatListDescriptor&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ThreatListDescriptor& default_instance() { + return *internal_default_instance(); + } + static inline const ThreatListDescriptor* internal_default_instance() { + return reinterpret_cast<const ThreatListDescriptor*>( + &_ThreatListDescriptor_default_instance_); + } + static constexpr int kIndexInFileMessages = + 24; + + friend void swap(ThreatListDescriptor& a, ThreatListDescriptor& b) { + a.Swap(&b); + } + inline void Swap(ThreatListDescriptor* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ThreatListDescriptor* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ThreatListDescriptor* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ThreatListDescriptor>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ThreatListDescriptor& from); + void MergeFrom(const ThreatListDescriptor& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ThreatListDescriptor* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ThreatListDescriptor"; + } + protected: + explicit ThreatListDescriptor(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kThreatTypeFieldNumber = 1, + kPlatformTypeFieldNumber = 2, + kThreatEntryTypeFieldNumber = 3, + }; + // optional .mozilla.safebrowsing.ThreatType threat_type = 1; + bool has_threat_type() const; + private: + bool _internal_has_threat_type() const; + public: + void clear_threat_type(); + ::mozilla::safebrowsing::ThreatType threat_type() const; + void set_threat_type(::mozilla::safebrowsing::ThreatType value); + private: + ::mozilla::safebrowsing::ThreatType _internal_threat_type() const; + void _internal_set_threat_type(::mozilla::safebrowsing::ThreatType value); + public: + + // optional .mozilla.safebrowsing.PlatformType platform_type = 2; + bool has_platform_type() const; + private: + bool _internal_has_platform_type() const; + public: + void clear_platform_type(); + ::mozilla::safebrowsing::PlatformType platform_type() const; + void set_platform_type(::mozilla::safebrowsing::PlatformType value); + private: + ::mozilla::safebrowsing::PlatformType _internal_platform_type() const; + void _internal_set_platform_type(::mozilla::safebrowsing::PlatformType value); + public: + + // optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 3; + bool has_threat_entry_type() const; + private: + bool _internal_has_threat_entry_type() const; + public: + void clear_threat_entry_type(); + ::mozilla::safebrowsing::ThreatEntryType threat_entry_type() const; + void set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + private: + ::mozilla::safebrowsing::ThreatEntryType _internal_threat_entry_type() const; + void _internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ThreatListDescriptor) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + int threat_type_; + int platform_type_; + int threat_entry_type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class ListThreatListsResponse final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.ListThreatListsResponse) */ { + public: + inline ListThreatListsResponse() : ListThreatListsResponse(nullptr) {} + ~ListThreatListsResponse() override; + explicit PROTOBUF_CONSTEXPR ListThreatListsResponse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ListThreatListsResponse(const ListThreatListsResponse& from); + ListThreatListsResponse(ListThreatListsResponse&& from) noexcept + : ListThreatListsResponse() { + *this = ::std::move(from); + } + + inline ListThreatListsResponse& operator=(const ListThreatListsResponse& from) { + CopyFrom(from); + return *this; + } + inline ListThreatListsResponse& operator=(ListThreatListsResponse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const ListThreatListsResponse& default_instance() { + return *internal_default_instance(); + } + static inline const ListThreatListsResponse* internal_default_instance() { + return reinterpret_cast<const ListThreatListsResponse*>( + &_ListThreatListsResponse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 25; + + friend void swap(ListThreatListsResponse& a, ListThreatListsResponse& b) { + a.Swap(&b); + } + inline void Swap(ListThreatListsResponse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ListThreatListsResponse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ListThreatListsResponse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<ListThreatListsResponse>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ListThreatListsResponse& from); + void MergeFrom(const ListThreatListsResponse& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ListThreatListsResponse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.ListThreatListsResponse"; + } + protected: + explicit ListThreatListsResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kThreatListsFieldNumber = 1, + }; + // repeated .mozilla.safebrowsing.ThreatListDescriptor threat_lists = 1; + int threat_lists_size() const; + private: + int _internal_threat_lists_size() const; + public: + void clear_threat_lists(); + ::mozilla::safebrowsing::ThreatListDescriptor* mutable_threat_lists(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatListDescriptor >* + mutable_threat_lists(); + private: + const ::mozilla::safebrowsing::ThreatListDescriptor& _internal_threat_lists(int index) const; + ::mozilla::safebrowsing::ThreatListDescriptor* _internal_add_threat_lists(); + public: + const ::mozilla::safebrowsing::ThreatListDescriptor& threat_lists(int index) const; + ::mozilla::safebrowsing::ThreatListDescriptor* add_threat_lists(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatListDescriptor >& + threat_lists() const; + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.ListThreatListsResponse) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatListDescriptor > threat_lists_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// ------------------------------------------------------------------- + +class Duration final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.safebrowsing.Duration) */ { + public: + inline Duration() : Duration(nullptr) {} + ~Duration() override; + explicit PROTOBUF_CONSTEXPR Duration(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Duration(const Duration& from); + Duration(Duration&& from) noexcept + : Duration() { + *this = ::std::move(from); + } + + inline Duration& operator=(const Duration& from) { + CopyFrom(from); + return *this; + } + inline Duration& operator=(Duration&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields<std::string>(); + } + + static const Duration& default_instance() { + return *internal_default_instance(); + } + static inline const Duration* internal_default_instance() { + return reinterpret_cast<const Duration*>( + &_Duration_default_instance_); + } + static constexpr int kIndexInFileMessages = + 26; + + friend void swap(Duration& a, Duration& b) { + a.Swap(&b); + } + inline void Swap(Duration* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Duration* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Duration* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage<Duration>(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const Duration& from); + void MergeFrom(const Duration& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(Duration* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.safebrowsing.Duration"; + } + protected: + explicit Duration(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kSecondsFieldNumber = 1, + kNanosFieldNumber = 2, + }; + // optional int64 seconds = 1; + bool has_seconds() const; + private: + bool _internal_has_seconds() const; + public: + void clear_seconds(); + int64_t seconds() const; + void set_seconds(int64_t value); + private: + int64_t _internal_seconds() const; + void _internal_set_seconds(int64_t value); + public: + + // optional int32 nanos = 2; + bool has_nanos() const; + private: + bool _internal_has_nanos() const; + public: + void clear_nanos(); + int32_t nanos() const; + void set_nanos(int32_t value); + private: + int32_t _internal_nanos() const; + void _internal_set_nanos(int32_t value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.safebrowsing.Duration) + private: + class _Internal; + + template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + int64_t seconds_; + int32_t nanos_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_safebrowsing_2eproto; +}; +// =================================================================== + + +// =================================================================== + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// ThreatInfo + +// repeated .mozilla.safebrowsing.ThreatType threat_types = 1; +inline int ThreatInfo::_internal_threat_types_size() const { + return _impl_.threat_types_.size(); +} +inline int ThreatInfo::threat_types_size() const { + return _internal_threat_types_size(); +} +inline void ThreatInfo::clear_threat_types() { + _impl_.threat_types_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatType ThreatInfo::_internal_threat_types(int index) const { + return static_cast< ::mozilla::safebrowsing::ThreatType >(_impl_.threat_types_.Get(index)); +} +inline ::mozilla::safebrowsing::ThreatType ThreatInfo::threat_types(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatInfo.threat_types) + return _internal_threat_types(index); +} +inline void ThreatInfo::set_threat_types(int index, ::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_.threat_types_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatInfo.threat_types) +} +inline void ThreatInfo::_internal_add_threat_types(::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_.threat_types_.Add(value); +} +inline void ThreatInfo::add_threat_types(::mozilla::safebrowsing::ThreatType value) { + _internal_add_threat_types(value); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ThreatInfo.threat_types) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& +ThreatInfo::threat_types() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ThreatInfo.threat_types) + return _impl_.threat_types_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +ThreatInfo::_internal_mutable_threat_types() { + return &_impl_.threat_types_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +ThreatInfo::mutable_threat_types() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ThreatInfo.threat_types) + return _internal_mutable_threat_types(); +} + +// repeated .mozilla.safebrowsing.PlatformType platform_types = 2; +inline int ThreatInfo::_internal_platform_types_size() const { + return _impl_.platform_types_.size(); +} +inline int ThreatInfo::platform_types_size() const { + return _internal_platform_types_size(); +} +inline void ThreatInfo::clear_platform_types() { + _impl_.platform_types_.Clear(); +} +inline ::mozilla::safebrowsing::PlatformType ThreatInfo::_internal_platform_types(int index) const { + return static_cast< ::mozilla::safebrowsing::PlatformType >(_impl_.platform_types_.Get(index)); +} +inline ::mozilla::safebrowsing::PlatformType ThreatInfo::platform_types(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatInfo.platform_types) + return _internal_platform_types(index); +} +inline void ThreatInfo::set_platform_types(int index, ::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_.platform_types_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatInfo.platform_types) +} +inline void ThreatInfo::_internal_add_platform_types(::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_.platform_types_.Add(value); +} +inline void ThreatInfo::add_platform_types(::mozilla::safebrowsing::PlatformType value) { + _internal_add_platform_types(value); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ThreatInfo.platform_types) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& +ThreatInfo::platform_types() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ThreatInfo.platform_types) + return _impl_.platform_types_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +ThreatInfo::_internal_mutable_platform_types() { + return &_impl_.platform_types_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +ThreatInfo::mutable_platform_types() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ThreatInfo.platform_types) + return _internal_mutable_platform_types(); +} + +// repeated .mozilla.safebrowsing.ThreatEntryType threat_entry_types = 4; +inline int ThreatInfo::_internal_threat_entry_types_size() const { + return _impl_.threat_entry_types_.size(); +} +inline int ThreatInfo::threat_entry_types_size() const { + return _internal_threat_entry_types_size(); +} +inline void ThreatInfo::clear_threat_entry_types() { + _impl_.threat_entry_types_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatEntryType ThreatInfo::_internal_threat_entry_types(int index) const { + return static_cast< ::mozilla::safebrowsing::ThreatEntryType >(_impl_.threat_entry_types_.Get(index)); +} +inline ::mozilla::safebrowsing::ThreatEntryType ThreatInfo::threat_entry_types(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatInfo.threat_entry_types) + return _internal_threat_entry_types(index); +} +inline void ThreatInfo::set_threat_entry_types(int index, ::mozilla::safebrowsing::ThreatEntryType value) { + assert(::mozilla::safebrowsing::ThreatEntryType_IsValid(value)); + _impl_.threat_entry_types_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatInfo.threat_entry_types) +} +inline void ThreatInfo::_internal_add_threat_entry_types(::mozilla::safebrowsing::ThreatEntryType value) { + assert(::mozilla::safebrowsing::ThreatEntryType_IsValid(value)); + _impl_.threat_entry_types_.Add(value); +} +inline void ThreatInfo::add_threat_entry_types(::mozilla::safebrowsing::ThreatEntryType value) { + _internal_add_threat_entry_types(value); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ThreatInfo.threat_entry_types) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& +ThreatInfo::threat_entry_types() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ThreatInfo.threat_entry_types) + return _impl_.threat_entry_types_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +ThreatInfo::_internal_mutable_threat_entry_types() { + return &_impl_.threat_entry_types_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +ThreatInfo::mutable_threat_entry_types() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ThreatInfo.threat_entry_types) + return _internal_mutable_threat_entry_types(); +} + +// repeated .mozilla.safebrowsing.ThreatEntry threat_entries = 3; +inline int ThreatInfo::_internal_threat_entries_size() const { + return _impl_.threat_entries_.size(); +} +inline int ThreatInfo::threat_entries_size() const { + return _internal_threat_entries_size(); +} +inline void ThreatInfo::clear_threat_entries() { + _impl_.threat_entries_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatInfo::mutable_threat_entries(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatInfo.threat_entries) + return _impl_.threat_entries_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntry >* +ThreatInfo::mutable_threat_entries() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ThreatInfo.threat_entries) + return &_impl_.threat_entries_; +} +inline const ::mozilla::safebrowsing::ThreatEntry& ThreatInfo::_internal_threat_entries(int index) const { + return _impl_.threat_entries_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatEntry& ThreatInfo::threat_entries(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatInfo.threat_entries) + return _internal_threat_entries(index); +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatInfo::_internal_add_threat_entries() { + return _impl_.threat_entries_.Add(); +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatInfo::add_threat_entries() { + ::mozilla::safebrowsing::ThreatEntry* _add = _internal_add_threat_entries(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ThreatInfo.threat_entries) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntry >& +ThreatInfo::threat_entries() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ThreatInfo.threat_entries) + return _impl_.threat_entries_; +} + +// ------------------------------------------------------------------- + +// ThreatMatch + +// optional .mozilla.safebrowsing.ThreatType threat_type = 1; +inline bool ThreatMatch::_internal_has_threat_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool ThreatMatch::has_threat_type() const { + return _internal_has_threat_type(); +} +inline void ThreatMatch::clear_threat_type() { + _impl_.threat_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline ::mozilla::safebrowsing::ThreatType ThreatMatch::_internal_threat_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatType >(_impl_.threat_type_); +} +inline ::mozilla::safebrowsing::ThreatType ThreatMatch::threat_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatMatch.threat_type) + return _internal_threat_type(); +} +inline void ThreatMatch::_internal_set_threat_type(::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.threat_type_ = value; +} +inline void ThreatMatch::set_threat_type(::mozilla::safebrowsing::ThreatType value) { + _internal_set_threat_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatMatch.threat_type) +} + +// optional .mozilla.safebrowsing.PlatformType platform_type = 2; +inline bool ThreatMatch::_internal_has_platform_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool ThreatMatch::has_platform_type() const { + return _internal_has_platform_type(); +} +inline void ThreatMatch::clear_platform_type() { + _impl_.platform_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline ::mozilla::safebrowsing::PlatformType ThreatMatch::_internal_platform_type() const { + return static_cast< ::mozilla::safebrowsing::PlatformType >(_impl_.platform_type_); +} +inline ::mozilla::safebrowsing::PlatformType ThreatMatch::platform_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatMatch.platform_type) + return _internal_platform_type(); +} +inline void ThreatMatch::_internal_set_platform_type(::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.platform_type_ = value; +} +inline void ThreatMatch::set_platform_type(::mozilla::safebrowsing::PlatformType value) { + _internal_set_platform_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatMatch.platform_type) +} + +// optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 6; +inline bool ThreatMatch::_internal_has_threat_entry_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool ThreatMatch::has_threat_entry_type() const { + return _internal_has_threat_entry_type(); +} +inline void ThreatMatch::clear_threat_entry_type() { + _impl_.threat_entry_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline ::mozilla::safebrowsing::ThreatEntryType ThreatMatch::_internal_threat_entry_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatEntryType >(_impl_.threat_entry_type_); +} +inline ::mozilla::safebrowsing::ThreatEntryType ThreatMatch::threat_entry_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatMatch.threat_entry_type) + return _internal_threat_entry_type(); +} +inline void ThreatMatch::_internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + assert(::mozilla::safebrowsing::ThreatEntryType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.threat_entry_type_ = value; +} +inline void ThreatMatch::set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + _internal_set_threat_entry_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatMatch.threat_entry_type) +} + +// optional .mozilla.safebrowsing.ThreatEntry threat = 3; +inline bool ThreatMatch::_internal_has_threat() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.threat_ != nullptr); + return value; +} +inline bool ThreatMatch::has_threat() const { + return _internal_has_threat(); +} +inline void ThreatMatch::clear_threat() { + if (_impl_.threat_ != nullptr) _impl_.threat_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::ThreatEntry& ThreatMatch::_internal_threat() const { + const ::mozilla::safebrowsing::ThreatEntry* p = _impl_.threat_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ThreatEntry&>( + ::mozilla::safebrowsing::_ThreatEntry_default_instance_); +} +inline const ::mozilla::safebrowsing::ThreatEntry& ThreatMatch::threat() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatMatch.threat) + return _internal_threat(); +} +inline void ThreatMatch::unsafe_arena_set_allocated_threat( + ::mozilla::safebrowsing::ThreatEntry* threat) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.threat_); + } + _impl_.threat_ = threat; + if (threat) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatMatch.threat) +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatMatch::release_threat() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ThreatEntry* temp = _impl_.threat_; + _impl_.threat_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatMatch::unsafe_arena_release_threat() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatMatch.threat) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ThreatEntry* temp = _impl_.threat_; + _impl_.threat_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatMatch::_internal_mutable_threat() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.threat_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntry>(GetArenaForAllocation()); + _impl_.threat_ = p; + } + return _impl_.threat_; +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatMatch::mutable_threat() { + ::mozilla::safebrowsing::ThreatEntry* _msg = _internal_mutable_threat(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatMatch.threat) + return _msg; +} +inline void ThreatMatch::set_allocated_threat(::mozilla::safebrowsing::ThreatEntry* threat) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.threat_; + } + if (threat) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(threat); + if (message_arena != submessage_arena) { + threat = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, threat, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.threat_ = threat; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatMatch.threat) +} + +// optional .mozilla.safebrowsing.ThreatEntryMetadata threat_entry_metadata = 4; +inline bool ThreatMatch::_internal_has_threat_entry_metadata() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.threat_entry_metadata_ != nullptr); + return value; +} +inline bool ThreatMatch::has_threat_entry_metadata() const { + return _internal_has_threat_entry_metadata(); +} +inline void ThreatMatch::clear_threat_entry_metadata() { + if (_impl_.threat_entry_metadata_ != nullptr) _impl_.threat_entry_metadata_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::ThreatEntryMetadata& ThreatMatch::_internal_threat_entry_metadata() const { + const ::mozilla::safebrowsing::ThreatEntryMetadata* p = _impl_.threat_entry_metadata_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ThreatEntryMetadata&>( + ::mozilla::safebrowsing::_ThreatEntryMetadata_default_instance_); +} +inline const ::mozilla::safebrowsing::ThreatEntryMetadata& ThreatMatch::threat_entry_metadata() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatMatch.threat_entry_metadata) + return _internal_threat_entry_metadata(); +} +inline void ThreatMatch::unsafe_arena_set_allocated_threat_entry_metadata( + ::mozilla::safebrowsing::ThreatEntryMetadata* threat_entry_metadata) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.threat_entry_metadata_); + } + _impl_.threat_entry_metadata_ = threat_entry_metadata; + if (threat_entry_metadata) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatMatch.threat_entry_metadata) +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata* ThreatMatch::release_threat_entry_metadata() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ThreatEntryMetadata* temp = _impl_.threat_entry_metadata_; + _impl_.threat_entry_metadata_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata* ThreatMatch::unsafe_arena_release_threat_entry_metadata() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatMatch.threat_entry_metadata) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ThreatEntryMetadata* temp = _impl_.threat_entry_metadata_; + _impl_.threat_entry_metadata_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata* ThreatMatch::_internal_mutable_threat_entry_metadata() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.threat_entry_metadata_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntryMetadata>(GetArenaForAllocation()); + _impl_.threat_entry_metadata_ = p; + } + return _impl_.threat_entry_metadata_; +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata* ThreatMatch::mutable_threat_entry_metadata() { + ::mozilla::safebrowsing::ThreatEntryMetadata* _msg = _internal_mutable_threat_entry_metadata(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatMatch.threat_entry_metadata) + return _msg; +} +inline void ThreatMatch::set_allocated_threat_entry_metadata(::mozilla::safebrowsing::ThreatEntryMetadata* threat_entry_metadata) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.threat_entry_metadata_; + } + if (threat_entry_metadata) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(threat_entry_metadata); + if (message_arena != submessage_arena) { + threat_entry_metadata = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, threat_entry_metadata, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.threat_entry_metadata_ = threat_entry_metadata; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatMatch.threat_entry_metadata) +} + +// optional .mozilla.safebrowsing.Duration cache_duration = 5; +inline bool ThreatMatch::_internal_has_cache_duration() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.cache_duration_ != nullptr); + return value; +} +inline bool ThreatMatch::has_cache_duration() const { + return _internal_has_cache_duration(); +} +inline void ThreatMatch::clear_cache_duration() { + if (_impl_.cache_duration_ != nullptr) _impl_.cache_duration_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::safebrowsing::Duration& ThreatMatch::_internal_cache_duration() const { + const ::mozilla::safebrowsing::Duration* p = _impl_.cache_duration_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::Duration&>( + ::mozilla::safebrowsing::_Duration_default_instance_); +} +inline const ::mozilla::safebrowsing::Duration& ThreatMatch::cache_duration() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatMatch.cache_duration) + return _internal_cache_duration(); +} +inline void ThreatMatch::unsafe_arena_set_allocated_cache_duration( + ::mozilla::safebrowsing::Duration* cache_duration) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.cache_duration_); + } + _impl_.cache_duration_ = cache_duration; + if (cache_duration) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatMatch.cache_duration) +} +inline ::mozilla::safebrowsing::Duration* ThreatMatch::release_cache_duration() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::mozilla::safebrowsing::Duration* temp = _impl_.cache_duration_; + _impl_.cache_duration_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::Duration* ThreatMatch::unsafe_arena_release_cache_duration() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatMatch.cache_duration) + _impl_._has_bits_[0] &= ~0x00000004u; + ::mozilla::safebrowsing::Duration* temp = _impl_.cache_duration_; + _impl_.cache_duration_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::Duration* ThreatMatch::_internal_mutable_cache_duration() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.cache_duration_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::Duration>(GetArenaForAllocation()); + _impl_.cache_duration_ = p; + } + return _impl_.cache_duration_; +} +inline ::mozilla::safebrowsing::Duration* ThreatMatch::mutable_cache_duration() { + ::mozilla::safebrowsing::Duration* _msg = _internal_mutable_cache_duration(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatMatch.cache_duration) + return _msg; +} +inline void ThreatMatch::set_allocated_cache_duration(::mozilla::safebrowsing::Duration* cache_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.cache_duration_; + } + if (cache_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(cache_duration); + if (message_arena != submessage_arena) { + cache_duration = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, cache_duration, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.cache_duration_ = cache_duration; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatMatch.cache_duration) +} + +// ------------------------------------------------------------------- + +// FindThreatMatchesRequest + +// optional .mozilla.safebrowsing.ClientInfo client = 1; +inline bool FindThreatMatchesRequest::_internal_has_client() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.client_ != nullptr); + return value; +} +inline bool FindThreatMatchesRequest::has_client() const { + return _internal_has_client(); +} +inline void FindThreatMatchesRequest::clear_client() { + if (_impl_.client_ != nullptr) _impl_.client_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::ClientInfo& FindThreatMatchesRequest::_internal_client() const { + const ::mozilla::safebrowsing::ClientInfo* p = _impl_.client_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ClientInfo&>( + ::mozilla::safebrowsing::_ClientInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ClientInfo& FindThreatMatchesRequest::client() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindThreatMatchesRequest.client) + return _internal_client(); +} +inline void FindThreatMatchesRequest::unsafe_arena_set_allocated_client( + ::mozilla::safebrowsing::ClientInfo* client) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.client_); + } + _impl_.client_ = client; + if (client) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FindThreatMatchesRequest.client) +} +inline ::mozilla::safebrowsing::ClientInfo* FindThreatMatchesRequest::release_client() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_; + _impl_.client_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* FindThreatMatchesRequest::unsafe_arena_release_client() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FindThreatMatchesRequest.client) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_; + _impl_.client_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* FindThreatMatchesRequest::_internal_mutable_client() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.client_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ClientInfo>(GetArenaForAllocation()); + _impl_.client_ = p; + } + return _impl_.client_; +} +inline ::mozilla::safebrowsing::ClientInfo* FindThreatMatchesRequest::mutable_client() { + ::mozilla::safebrowsing::ClientInfo* _msg = _internal_mutable_client(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindThreatMatchesRequest.client) + return _msg; +} +inline void FindThreatMatchesRequest::set_allocated_client(::mozilla::safebrowsing::ClientInfo* client) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.client_; + } + if (client) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(client); + if (message_arena != submessage_arena) { + client = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, client, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.client_ = client; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FindThreatMatchesRequest.client) +} + +// optional .mozilla.safebrowsing.ThreatInfo threat_info = 2; +inline bool FindThreatMatchesRequest::_internal_has_threat_info() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.threat_info_ != nullptr); + return value; +} +inline bool FindThreatMatchesRequest::has_threat_info() const { + return _internal_has_threat_info(); +} +inline void FindThreatMatchesRequest::clear_threat_info() { + if (_impl_.threat_info_ != nullptr) _impl_.threat_info_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::ThreatInfo& FindThreatMatchesRequest::_internal_threat_info() const { + const ::mozilla::safebrowsing::ThreatInfo* p = _impl_.threat_info_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ThreatInfo&>( + ::mozilla::safebrowsing::_ThreatInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ThreatInfo& FindThreatMatchesRequest::threat_info() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindThreatMatchesRequest.threat_info) + return _internal_threat_info(); +} +inline void FindThreatMatchesRequest::unsafe_arena_set_allocated_threat_info( + ::mozilla::safebrowsing::ThreatInfo* threat_info) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.threat_info_); + } + _impl_.threat_info_ = threat_info; + if (threat_info) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FindThreatMatchesRequest.threat_info) +} +inline ::mozilla::safebrowsing::ThreatInfo* FindThreatMatchesRequest::release_threat_info() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ThreatInfo* temp = _impl_.threat_info_; + _impl_.threat_info_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ThreatInfo* FindThreatMatchesRequest::unsafe_arena_release_threat_info() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FindThreatMatchesRequest.threat_info) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ThreatInfo* temp = _impl_.threat_info_; + _impl_.threat_info_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ThreatInfo* FindThreatMatchesRequest::_internal_mutable_threat_info() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.threat_info_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ThreatInfo>(GetArenaForAllocation()); + _impl_.threat_info_ = p; + } + return _impl_.threat_info_; +} +inline ::mozilla::safebrowsing::ThreatInfo* FindThreatMatchesRequest::mutable_threat_info() { + ::mozilla::safebrowsing::ThreatInfo* _msg = _internal_mutable_threat_info(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindThreatMatchesRequest.threat_info) + return _msg; +} +inline void FindThreatMatchesRequest::set_allocated_threat_info(::mozilla::safebrowsing::ThreatInfo* threat_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.threat_info_; + } + if (threat_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(threat_info); + if (message_arena != submessage_arena) { + threat_info = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, threat_info, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.threat_info_ = threat_info; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FindThreatMatchesRequest.threat_info) +} + +// ------------------------------------------------------------------- + +// FindThreatMatchesResponse + +// repeated .mozilla.safebrowsing.ThreatMatch matches = 1; +inline int FindThreatMatchesResponse::_internal_matches_size() const { + return _impl_.matches_.size(); +} +inline int FindThreatMatchesResponse::matches_size() const { + return _internal_matches_size(); +} +inline void FindThreatMatchesResponse::clear_matches() { + _impl_.matches_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatMatch* FindThreatMatchesResponse::mutable_matches(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindThreatMatchesResponse.matches) + return _impl_.matches_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >* +FindThreatMatchesResponse::mutable_matches() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FindThreatMatchesResponse.matches) + return &_impl_.matches_; +} +inline const ::mozilla::safebrowsing::ThreatMatch& FindThreatMatchesResponse::_internal_matches(int index) const { + return _impl_.matches_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatMatch& FindThreatMatchesResponse::matches(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindThreatMatchesResponse.matches) + return _internal_matches(index); +} +inline ::mozilla::safebrowsing::ThreatMatch* FindThreatMatchesResponse::_internal_add_matches() { + return _impl_.matches_.Add(); +} +inline ::mozilla::safebrowsing::ThreatMatch* FindThreatMatchesResponse::add_matches() { + ::mozilla::safebrowsing::ThreatMatch* _add = _internal_add_matches(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FindThreatMatchesResponse.matches) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >& +FindThreatMatchesResponse::matches() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FindThreatMatchesResponse.matches) + return _impl_.matches_; +} + +// ------------------------------------------------------------------- + +// FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints + +// optional int32 max_update_entries = 1; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_has_max_update_entries() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::has_max_update_entries() const { + return _internal_has_max_update_entries(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::clear_max_update_entries() { + _impl_.max_update_entries_ = 0; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline int32_t FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_max_update_entries() const { + return _impl_.max_update_entries_; +} +inline int32_t FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::max_update_entries() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.max_update_entries) + return _internal_max_update_entries(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_set_max_update_entries(int32_t value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.max_update_entries_ = value; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::set_max_update_entries(int32_t value) { + _internal_set_max_update_entries(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.max_update_entries) +} + +// optional int32 max_database_entries = 2; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_has_max_database_entries() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::has_max_database_entries() const { + return _internal_has_max_database_entries(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::clear_max_database_entries() { + _impl_.max_database_entries_ = 0; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline int32_t FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_max_database_entries() const { + return _impl_.max_database_entries_; +} +inline int32_t FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::max_database_entries() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.max_database_entries) + return _internal_max_database_entries(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_set_max_database_entries(int32_t value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.max_database_entries_ = value; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::set_max_database_entries(int32_t value) { + _internal_set_max_database_entries(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.max_database_entries) +} + +// optional string region = 3; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_has_region() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::has_region() const { + return _internal_has_region(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::clear_region() { + _impl_.region_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::region() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.region) + return _internal_region(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::set_region(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.region_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.region) +} +inline std::string* FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::mutable_region() { + std::string* _s = _internal_mutable_region(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.region) + return _s; +} +inline const std::string& FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_region() const { + return _impl_.region_.Get(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_set_region(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.region_.Set(value, GetArenaForAllocation()); +} +inline std::string* FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_mutable_region() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.region_.Mutable(GetArenaForAllocation()); +} +inline std::string* FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::release_region() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.region) + if (!_internal_has_region()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.region_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.region_.IsDefault()) { + _impl_.region_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::set_allocated_region(std::string* region) { + if (region != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.region_.SetAllocated(region, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.region_.IsDefault()) { + _impl_.region_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.region) +} + +// repeated .mozilla.safebrowsing.CompressionType supported_compressions = 4; +inline int FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_supported_compressions_size() const { + return _impl_.supported_compressions_.size(); +} +inline int FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::supported_compressions_size() const { + return _internal_supported_compressions_size(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::clear_supported_compressions() { + _impl_.supported_compressions_.Clear(); +} +inline ::mozilla::safebrowsing::CompressionType FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_supported_compressions(int index) const { + return static_cast< ::mozilla::safebrowsing::CompressionType >(_impl_.supported_compressions_.Get(index)); +} +inline ::mozilla::safebrowsing::CompressionType FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::supported_compressions(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.supported_compressions) + return _internal_supported_compressions(index); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::set_supported_compressions(int index, ::mozilla::safebrowsing::CompressionType value) { + assert(::mozilla::safebrowsing::CompressionType_IsValid(value)); + _impl_.supported_compressions_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.supported_compressions) +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_add_supported_compressions(::mozilla::safebrowsing::CompressionType value) { + assert(::mozilla::safebrowsing::CompressionType_IsValid(value)); + _impl_.supported_compressions_.Add(value); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::add_supported_compressions(::mozilla::safebrowsing::CompressionType value) { + _internal_add_supported_compressions(value); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.supported_compressions) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>& +FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::supported_compressions() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.supported_compressions) + return _impl_.supported_compressions_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::_internal_mutable_supported_compressions() { + return &_impl_.supported_compressions_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField<int>* +FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints::mutable_supported_compressions() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints.supported_compressions) + return _internal_mutable_supported_compressions(); +} + +// ------------------------------------------------------------------- + +// FetchThreatListUpdatesRequest_ListUpdateRequest + +// optional .mozilla.safebrowsing.ThreatType threat_type = 1; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_has_threat_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::has_threat_type() const { + return _internal_has_threat_type(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::clear_threat_type() { + _impl_.threat_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline ::mozilla::safebrowsing::ThreatType FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_threat_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatType >(_impl_.threat_type_); +} +inline ::mozilla::safebrowsing::ThreatType FetchThreatListUpdatesRequest_ListUpdateRequest::threat_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.threat_type) + return _internal_threat_type(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_set_threat_type(::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.threat_type_ = value; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::set_threat_type(::mozilla::safebrowsing::ThreatType value) { + _internal_set_threat_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.threat_type) +} + +// optional .mozilla.safebrowsing.PlatformType platform_type = 2; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_has_platform_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::has_platform_type() const { + return _internal_has_platform_type(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::clear_platform_type() { + _impl_.platform_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline ::mozilla::safebrowsing::PlatformType FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_platform_type() const { + return static_cast< ::mozilla::safebrowsing::PlatformType >(_impl_.platform_type_); +} +inline ::mozilla::safebrowsing::PlatformType FetchThreatListUpdatesRequest_ListUpdateRequest::platform_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.platform_type) + return _internal_platform_type(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_set_platform_type(::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.platform_type_ = value; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::set_platform_type(::mozilla::safebrowsing::PlatformType value) { + _internal_set_platform_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.platform_type) +} + +// optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 5; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_has_threat_entry_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::has_threat_entry_type() const { + return _internal_has_threat_entry_type(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::clear_threat_entry_type() { + _impl_.threat_entry_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline ::mozilla::safebrowsing::ThreatEntryType FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_threat_entry_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatEntryType >(_impl_.threat_entry_type_); +} +inline ::mozilla::safebrowsing::ThreatEntryType FetchThreatListUpdatesRequest_ListUpdateRequest::threat_entry_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.threat_entry_type) + return _internal_threat_entry_type(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + assert(::mozilla::safebrowsing::ThreatEntryType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.threat_entry_type_ = value; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + _internal_set_threat_entry_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.threat_entry_type) +} + +// optional bytes state = 3; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_has_state() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::has_state() const { + return _internal_has_state(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::clear_state() { + _impl_.state_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& FetchThreatListUpdatesRequest_ListUpdateRequest::state() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.state) + return _internal_state(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void FetchThreatListUpdatesRequest_ListUpdateRequest::set_state(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.state_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.state) +} +inline std::string* FetchThreatListUpdatesRequest_ListUpdateRequest::mutable_state() { + std::string* _s = _internal_mutable_state(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.state) + return _s; +} +inline const std::string& FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_state() const { + return _impl_.state_.Get(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_set_state(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.state_.Set(value, GetArenaForAllocation()); +} +inline std::string* FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_mutable_state() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.state_.Mutable(GetArenaForAllocation()); +} +inline std::string* FetchThreatListUpdatesRequest_ListUpdateRequest::release_state() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.state) + if (!_internal_has_state()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.state_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.state_.IsDefault()) { + _impl_.state_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::set_allocated_state(std::string* state) { + if (state != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.state_.SetAllocated(state, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.state_.IsDefault()) { + _impl_.state_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.state) +} + +// optional .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.Constraints constraints = 4; +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_has_constraints() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.constraints_ != nullptr); + return value; +} +inline bool FetchThreatListUpdatesRequest_ListUpdateRequest::has_constraints() const { + return _internal_has_constraints(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::clear_constraints() { + if (_impl_.constraints_ != nullptr) _impl_.constraints_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_constraints() const { + const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* p = _impl_.constraints_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints&>( + ::mozilla::safebrowsing::_FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints_default_instance_); +} +inline const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints& FetchThreatListUpdatesRequest_ListUpdateRequest::constraints() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.constraints) + return _internal_constraints(); +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::unsafe_arena_set_allocated_constraints( + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* constraints) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.constraints_); + } + _impl_.constraints_ = constraints; + if (constraints) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.constraints) +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* FetchThreatListUpdatesRequest_ListUpdateRequest::release_constraints() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* temp = _impl_.constraints_; + _impl_.constraints_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* FetchThreatListUpdatesRequest_ListUpdateRequest::unsafe_arena_release_constraints() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.constraints) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* temp = _impl_.constraints_; + _impl_.constraints_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* FetchThreatListUpdatesRequest_ListUpdateRequest::_internal_mutable_constraints() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.constraints_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints>(GetArenaForAllocation()); + _impl_.constraints_ = p; + } + return _impl_.constraints_; +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* FetchThreatListUpdatesRequest_ListUpdateRequest::mutable_constraints() { + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* _msg = _internal_mutable_constraints(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.constraints) + return _msg; +} +inline void FetchThreatListUpdatesRequest_ListUpdateRequest::set_allocated_constraints(::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints* constraints) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.constraints_; + } + if (constraints) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(constraints); + if (message_arena != submessage_arena) { + constraints = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, constraints, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.constraints_ = constraints; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest.constraints) +} + +// ------------------------------------------------------------------- + +// FetchThreatListUpdatesRequest + +// optional .mozilla.safebrowsing.ClientInfo client = 1; +inline bool FetchThreatListUpdatesRequest::_internal_has_client() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.client_ != nullptr); + return value; +} +inline bool FetchThreatListUpdatesRequest::has_client() const { + return _internal_has_client(); +} +inline void FetchThreatListUpdatesRequest::clear_client() { + if (_impl_.client_ != nullptr) _impl_.client_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::ClientInfo& FetchThreatListUpdatesRequest::_internal_client() const { + const ::mozilla::safebrowsing::ClientInfo* p = _impl_.client_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ClientInfo&>( + ::mozilla::safebrowsing::_ClientInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ClientInfo& FetchThreatListUpdatesRequest::client() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.client) + return _internal_client(); +} +inline void FetchThreatListUpdatesRequest::unsafe_arena_set_allocated_client( + ::mozilla::safebrowsing::ClientInfo* client) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.client_); + } + _impl_.client_ = client; + if (client) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.client) +} +inline ::mozilla::safebrowsing::ClientInfo* FetchThreatListUpdatesRequest::release_client() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_; + _impl_.client_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* FetchThreatListUpdatesRequest::unsafe_arena_release_client() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesRequest.client) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_; + _impl_.client_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* FetchThreatListUpdatesRequest::_internal_mutable_client() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.client_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ClientInfo>(GetArenaForAllocation()); + _impl_.client_ = p; + } + return _impl_.client_; +} +inline ::mozilla::safebrowsing::ClientInfo* FetchThreatListUpdatesRequest::mutable_client() { + ::mozilla::safebrowsing::ClientInfo* _msg = _internal_mutable_client(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesRequest.client) + return _msg; +} +inline void FetchThreatListUpdatesRequest::set_allocated_client(::mozilla::safebrowsing::ClientInfo* client) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.client_; + } + if (client) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(client); + if (message_arena != submessage_arena) { + client = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, client, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.client_ = client; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.client) +} + +// repeated .mozilla.safebrowsing.FetchThreatListUpdatesRequest.ListUpdateRequest list_update_requests = 3; +inline int FetchThreatListUpdatesRequest::_internal_list_update_requests_size() const { + return _impl_.list_update_requests_.size(); +} +inline int FetchThreatListUpdatesRequest::list_update_requests_size() const { + return _internal_list_update_requests_size(); +} +inline void FetchThreatListUpdatesRequest::clear_list_update_requests() { + _impl_.list_update_requests_.Clear(); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* FetchThreatListUpdatesRequest::mutable_list_update_requests(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesRequest.list_update_requests) + return _impl_.list_update_requests_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest >* +FetchThreatListUpdatesRequest::mutable_list_update_requests() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FetchThreatListUpdatesRequest.list_update_requests) + return &_impl_.list_update_requests_; +} +inline const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest& FetchThreatListUpdatesRequest::_internal_list_update_requests(int index) const { + return _impl_.list_update_requests_.Get(index); +} +inline const ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest& FetchThreatListUpdatesRequest::list_update_requests(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.list_update_requests) + return _internal_list_update_requests(index); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* FetchThreatListUpdatesRequest::_internal_add_list_update_requests() { + return _impl_.list_update_requests_.Add(); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* FetchThreatListUpdatesRequest::add_list_update_requests() { + ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest* _add = _internal_add_list_update_requests(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FetchThreatListUpdatesRequest.list_update_requests) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesRequest_ListUpdateRequest >& +FetchThreatListUpdatesRequest::list_update_requests() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FetchThreatListUpdatesRequest.list_update_requests) + return _impl_.list_update_requests_; +} + +// optional .mozilla.safebrowsing.ChromeClientInfo chrome_client_info = 4; +inline bool FetchThreatListUpdatesRequest::_internal_has_chrome_client_info() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.chrome_client_info_ != nullptr); + return value; +} +inline bool FetchThreatListUpdatesRequest::has_chrome_client_info() const { + return _internal_has_chrome_client_info(); +} +inline void FetchThreatListUpdatesRequest::clear_chrome_client_info() { + if (_impl_.chrome_client_info_ != nullptr) _impl_.chrome_client_info_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::ChromeClientInfo& FetchThreatListUpdatesRequest::_internal_chrome_client_info() const { + const ::mozilla::safebrowsing::ChromeClientInfo* p = _impl_.chrome_client_info_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ChromeClientInfo&>( + ::mozilla::safebrowsing::_ChromeClientInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ChromeClientInfo& FetchThreatListUpdatesRequest::chrome_client_info() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesRequest.chrome_client_info) + return _internal_chrome_client_info(); +} +inline void FetchThreatListUpdatesRequest::unsafe_arena_set_allocated_chrome_client_info( + ::mozilla::safebrowsing::ChromeClientInfo* chrome_client_info) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.chrome_client_info_); + } + _impl_.chrome_client_info_ = chrome_client_info; + if (chrome_client_info) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.chrome_client_info) +} +inline ::mozilla::safebrowsing::ChromeClientInfo* FetchThreatListUpdatesRequest::release_chrome_client_info() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ChromeClientInfo* temp = _impl_.chrome_client_info_; + _impl_.chrome_client_info_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ChromeClientInfo* FetchThreatListUpdatesRequest::unsafe_arena_release_chrome_client_info() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesRequest.chrome_client_info) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ChromeClientInfo* temp = _impl_.chrome_client_info_; + _impl_.chrome_client_info_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ChromeClientInfo* FetchThreatListUpdatesRequest::_internal_mutable_chrome_client_info() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.chrome_client_info_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ChromeClientInfo>(GetArenaForAllocation()); + _impl_.chrome_client_info_ = p; + } + return _impl_.chrome_client_info_; +} +inline ::mozilla::safebrowsing::ChromeClientInfo* FetchThreatListUpdatesRequest::mutable_chrome_client_info() { + ::mozilla::safebrowsing::ChromeClientInfo* _msg = _internal_mutable_chrome_client_info(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesRequest.chrome_client_info) + return _msg; +} +inline void FetchThreatListUpdatesRequest::set_allocated_chrome_client_info(::mozilla::safebrowsing::ChromeClientInfo* chrome_client_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.chrome_client_info_; + } + if (chrome_client_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(chrome_client_info); + if (message_arena != submessage_arena) { + chrome_client_info = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, chrome_client_info, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.chrome_client_info_ = chrome_client_info; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesRequest.chrome_client_info) +} + +// ------------------------------------------------------------------- + +// FetchThreatListUpdatesResponse_ListUpdateResponse + +// optional .mozilla.safebrowsing.ThreatType threat_type = 1; +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_has_threat_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::has_threat_type() const { + return _internal_has_threat_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_threat_type() { + _impl_.threat_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline ::mozilla::safebrowsing::ThreatType FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_threat_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatType >(_impl_.threat_type_); +} +inline ::mozilla::safebrowsing::ThreatType FetchThreatListUpdatesResponse_ListUpdateResponse::threat_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.threat_type) + return _internal_threat_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_set_threat_type(::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.threat_type_ = value; +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::set_threat_type(::mozilla::safebrowsing::ThreatType value) { + _internal_set_threat_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.threat_type) +} + +// optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 2; +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_has_threat_entry_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::has_threat_entry_type() const { + return _internal_has_threat_entry_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_threat_entry_type() { + _impl_.threat_entry_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline ::mozilla::safebrowsing::ThreatEntryType FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_threat_entry_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatEntryType >(_impl_.threat_entry_type_); +} +inline ::mozilla::safebrowsing::ThreatEntryType FetchThreatListUpdatesResponse_ListUpdateResponse::threat_entry_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.threat_entry_type) + return _internal_threat_entry_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + assert(::mozilla::safebrowsing::ThreatEntryType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.threat_entry_type_ = value; +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + _internal_set_threat_entry_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.threat_entry_type) +} + +// optional .mozilla.safebrowsing.PlatformType platform_type = 3; +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_has_platform_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::has_platform_type() const { + return _internal_has_platform_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_platform_type() { + _impl_.platform_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline ::mozilla::safebrowsing::PlatformType FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_platform_type() const { + return static_cast< ::mozilla::safebrowsing::PlatformType >(_impl_.platform_type_); +} +inline ::mozilla::safebrowsing::PlatformType FetchThreatListUpdatesResponse_ListUpdateResponse::platform_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.platform_type) + return _internal_platform_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_set_platform_type(::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.platform_type_ = value; +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::set_platform_type(::mozilla::safebrowsing::PlatformType value) { + _internal_set_platform_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.platform_type) +} + +// optional .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.ResponseType response_type = 4; +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_has_response_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::has_response_type() const { + return _internal_has_response_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_response_type() { + _impl_.response_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_response_type() const { + return static_cast< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType >(_impl_.response_type_); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType FetchThreatListUpdatesResponse_ListUpdateResponse::response_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.response_type) + return _internal_response_type(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_set_response_type(::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType value) { + assert(::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.response_type_ = value; +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::set_response_type(::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType value) { + _internal_set_response_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.response_type) +} + +// repeated .mozilla.safebrowsing.ThreatEntrySet additions = 5; +inline int FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_additions_size() const { + return _impl_.additions_.size(); +} +inline int FetchThreatListUpdatesResponse_ListUpdateResponse::additions_size() const { + return _internal_additions_size(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_additions() { + _impl_.additions_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatEntrySet* FetchThreatListUpdatesResponse_ListUpdateResponse::mutable_additions(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.additions) + return _impl_.additions_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >* +FetchThreatListUpdatesResponse_ListUpdateResponse::mutable_additions() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.additions) + return &_impl_.additions_; +} +inline const ::mozilla::safebrowsing::ThreatEntrySet& FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_additions(int index) const { + return _impl_.additions_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatEntrySet& FetchThreatListUpdatesResponse_ListUpdateResponse::additions(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.additions) + return _internal_additions(index); +} +inline ::mozilla::safebrowsing::ThreatEntrySet* FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_add_additions() { + return _impl_.additions_.Add(); +} +inline ::mozilla::safebrowsing::ThreatEntrySet* FetchThreatListUpdatesResponse_ListUpdateResponse::add_additions() { + ::mozilla::safebrowsing::ThreatEntrySet* _add = _internal_add_additions(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.additions) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >& +FetchThreatListUpdatesResponse_ListUpdateResponse::additions() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.additions) + return _impl_.additions_; +} + +// repeated .mozilla.safebrowsing.ThreatEntrySet removals = 6; +inline int FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_removals_size() const { + return _impl_.removals_.size(); +} +inline int FetchThreatListUpdatesResponse_ListUpdateResponse::removals_size() const { + return _internal_removals_size(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_removals() { + _impl_.removals_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatEntrySet* FetchThreatListUpdatesResponse_ListUpdateResponse::mutable_removals(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.removals) + return _impl_.removals_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >* +FetchThreatListUpdatesResponse_ListUpdateResponse::mutable_removals() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.removals) + return &_impl_.removals_; +} +inline const ::mozilla::safebrowsing::ThreatEntrySet& FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_removals(int index) const { + return _impl_.removals_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatEntrySet& FetchThreatListUpdatesResponse_ListUpdateResponse::removals(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.removals) + return _internal_removals(index); +} +inline ::mozilla::safebrowsing::ThreatEntrySet* FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_add_removals() { + return _impl_.removals_.Add(); +} +inline ::mozilla::safebrowsing::ThreatEntrySet* FetchThreatListUpdatesResponse_ListUpdateResponse::add_removals() { + ::mozilla::safebrowsing::ThreatEntrySet* _add = _internal_add_removals(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.removals) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntrySet >& +FetchThreatListUpdatesResponse_ListUpdateResponse::removals() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.removals) + return _impl_.removals_; +} + +// optional bytes new_client_state = 7; +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_has_new_client_state() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::has_new_client_state() const { + return _internal_has_new_client_state(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_new_client_state() { + _impl_.new_client_state_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& FetchThreatListUpdatesResponse_ListUpdateResponse::new_client_state() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.new_client_state) + return _internal_new_client_state(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void FetchThreatListUpdatesResponse_ListUpdateResponse::set_new_client_state(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.new_client_state_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.new_client_state) +} +inline std::string* FetchThreatListUpdatesResponse_ListUpdateResponse::mutable_new_client_state() { + std::string* _s = _internal_mutable_new_client_state(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.new_client_state) + return _s; +} +inline const std::string& FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_new_client_state() const { + return _impl_.new_client_state_.Get(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_set_new_client_state(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.new_client_state_.Set(value, GetArenaForAllocation()); +} +inline std::string* FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_mutable_new_client_state() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.new_client_state_.Mutable(GetArenaForAllocation()); +} +inline std::string* FetchThreatListUpdatesResponse_ListUpdateResponse::release_new_client_state() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.new_client_state) + if (!_internal_has_new_client_state()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.new_client_state_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.new_client_state_.IsDefault()) { + _impl_.new_client_state_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::set_allocated_new_client_state(std::string* new_client_state) { + if (new_client_state != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.new_client_state_.SetAllocated(new_client_state, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.new_client_state_.IsDefault()) { + _impl_.new_client_state_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.new_client_state) +} + +// optional .mozilla.safebrowsing.Checksum checksum = 8; +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_has_checksum() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.checksum_ != nullptr); + return value; +} +inline bool FetchThreatListUpdatesResponse_ListUpdateResponse::has_checksum() const { + return _internal_has_checksum(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::clear_checksum() { + if (_impl_.checksum_ != nullptr) _impl_.checksum_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::Checksum& FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_checksum() const { + const ::mozilla::safebrowsing::Checksum* p = _impl_.checksum_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::Checksum&>( + ::mozilla::safebrowsing::_Checksum_default_instance_); +} +inline const ::mozilla::safebrowsing::Checksum& FetchThreatListUpdatesResponse_ListUpdateResponse::checksum() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.checksum) + return _internal_checksum(); +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::unsafe_arena_set_allocated_checksum( + ::mozilla::safebrowsing::Checksum* checksum) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.checksum_); + } + _impl_.checksum_ = checksum; + if (checksum) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.checksum) +} +inline ::mozilla::safebrowsing::Checksum* FetchThreatListUpdatesResponse_ListUpdateResponse::release_checksum() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::Checksum* temp = _impl_.checksum_; + _impl_.checksum_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::Checksum* FetchThreatListUpdatesResponse_ListUpdateResponse::unsafe_arena_release_checksum() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.checksum) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::Checksum* temp = _impl_.checksum_; + _impl_.checksum_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::Checksum* FetchThreatListUpdatesResponse_ListUpdateResponse::_internal_mutable_checksum() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.checksum_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::Checksum>(GetArenaForAllocation()); + _impl_.checksum_ = p; + } + return _impl_.checksum_; +} +inline ::mozilla::safebrowsing::Checksum* FetchThreatListUpdatesResponse_ListUpdateResponse::mutable_checksum() { + ::mozilla::safebrowsing::Checksum* _msg = _internal_mutable_checksum(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.checksum) + return _msg; +} +inline void FetchThreatListUpdatesResponse_ListUpdateResponse::set_allocated_checksum(::mozilla::safebrowsing::Checksum* checksum) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.checksum_; + } + if (checksum) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(checksum); + if (message_arena != submessage_arena) { + checksum = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, checksum, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.checksum_ = checksum; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse.checksum) +} + +// ------------------------------------------------------------------- + +// FetchThreatListUpdatesResponse + +// repeated .mozilla.safebrowsing.FetchThreatListUpdatesResponse.ListUpdateResponse list_update_responses = 1; +inline int FetchThreatListUpdatesResponse::_internal_list_update_responses_size() const { + return _impl_.list_update_responses_.size(); +} +inline int FetchThreatListUpdatesResponse::list_update_responses_size() const { + return _internal_list_update_responses_size(); +} +inline void FetchThreatListUpdatesResponse::clear_list_update_responses() { + _impl_.list_update_responses_.Clear(); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* FetchThreatListUpdatesResponse::mutable_list_update_responses(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesResponse.list_update_responses) + return _impl_.list_update_responses_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse >* +FetchThreatListUpdatesResponse::mutable_list_update_responses() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FetchThreatListUpdatesResponse.list_update_responses) + return &_impl_.list_update_responses_; +} +inline const ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse& FetchThreatListUpdatesResponse::_internal_list_update_responses(int index) const { + return _impl_.list_update_responses_.Get(index); +} +inline const ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse& FetchThreatListUpdatesResponse::list_update_responses(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.list_update_responses) + return _internal_list_update_responses(index); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* FetchThreatListUpdatesResponse::_internal_add_list_update_responses() { + return _impl_.list_update_responses_.Add(); +} +inline ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* FetchThreatListUpdatesResponse::add_list_update_responses() { + ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse* _add = _internal_add_list_update_responses(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FetchThreatListUpdatesResponse.list_update_responses) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse >& +FetchThreatListUpdatesResponse::list_update_responses() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FetchThreatListUpdatesResponse.list_update_responses) + return _impl_.list_update_responses_; +} + +// optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; +inline bool FetchThreatListUpdatesResponse::_internal_has_minimum_wait_duration() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.minimum_wait_duration_ != nullptr); + return value; +} +inline bool FetchThreatListUpdatesResponse::has_minimum_wait_duration() const { + return _internal_has_minimum_wait_duration(); +} +inline void FetchThreatListUpdatesResponse::clear_minimum_wait_duration() { + if (_impl_.minimum_wait_duration_ != nullptr) _impl_.minimum_wait_duration_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::Duration& FetchThreatListUpdatesResponse::_internal_minimum_wait_duration() const { + const ::mozilla::safebrowsing::Duration* p = _impl_.minimum_wait_duration_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::Duration&>( + ::mozilla::safebrowsing::_Duration_default_instance_); +} +inline const ::mozilla::safebrowsing::Duration& FetchThreatListUpdatesResponse::minimum_wait_duration() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FetchThreatListUpdatesResponse.minimum_wait_duration) + return _internal_minimum_wait_duration(); +} +inline void FetchThreatListUpdatesResponse::unsafe_arena_set_allocated_minimum_wait_duration( + ::mozilla::safebrowsing::Duration* minimum_wait_duration) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.minimum_wait_duration_); + } + _impl_.minimum_wait_duration_ = minimum_wait_duration; + if (minimum_wait_duration) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesResponse.minimum_wait_duration) +} +inline ::mozilla::safebrowsing::Duration* FetchThreatListUpdatesResponse::release_minimum_wait_duration() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::Duration* temp = _impl_.minimum_wait_duration_; + _impl_.minimum_wait_duration_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::Duration* FetchThreatListUpdatesResponse::unsafe_arena_release_minimum_wait_duration() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FetchThreatListUpdatesResponse.minimum_wait_duration) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::Duration* temp = _impl_.minimum_wait_duration_; + _impl_.minimum_wait_duration_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::Duration* FetchThreatListUpdatesResponse::_internal_mutable_minimum_wait_duration() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.minimum_wait_duration_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::Duration>(GetArenaForAllocation()); + _impl_.minimum_wait_duration_ = p; + } + return _impl_.minimum_wait_duration_; +} +inline ::mozilla::safebrowsing::Duration* FetchThreatListUpdatesResponse::mutable_minimum_wait_duration() { + ::mozilla::safebrowsing::Duration* _msg = _internal_mutable_minimum_wait_duration(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FetchThreatListUpdatesResponse.minimum_wait_duration) + return _msg; +} +inline void FetchThreatListUpdatesResponse::set_allocated_minimum_wait_duration(::mozilla::safebrowsing::Duration* minimum_wait_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.minimum_wait_duration_; + } + if (minimum_wait_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(minimum_wait_duration); + if (message_arena != submessage_arena) { + minimum_wait_duration = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, minimum_wait_duration, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.minimum_wait_duration_ = minimum_wait_duration; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FetchThreatListUpdatesResponse.minimum_wait_duration) +} + +// ------------------------------------------------------------------- + +// FindFullHashesRequest + +// optional .mozilla.safebrowsing.ClientInfo client = 1; +inline bool FindFullHashesRequest::_internal_has_client() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.client_ != nullptr); + return value; +} +inline bool FindFullHashesRequest::has_client() const { + return _internal_has_client(); +} +inline void FindFullHashesRequest::clear_client() { + if (_impl_.client_ != nullptr) _impl_.client_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::ClientInfo& FindFullHashesRequest::_internal_client() const { + const ::mozilla::safebrowsing::ClientInfo* p = _impl_.client_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ClientInfo&>( + ::mozilla::safebrowsing::_ClientInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ClientInfo& FindFullHashesRequest::client() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindFullHashesRequest.client) + return _internal_client(); +} +inline void FindFullHashesRequest::unsafe_arena_set_allocated_client( + ::mozilla::safebrowsing::ClientInfo* client) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.client_); + } + _impl_.client_ = client; + if (client) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FindFullHashesRequest.client) +} +inline ::mozilla::safebrowsing::ClientInfo* FindFullHashesRequest::release_client() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_; + _impl_.client_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* FindFullHashesRequest::unsafe_arena_release_client() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FindFullHashesRequest.client) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_; + _impl_.client_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* FindFullHashesRequest::_internal_mutable_client() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.client_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ClientInfo>(GetArenaForAllocation()); + _impl_.client_ = p; + } + return _impl_.client_; +} +inline ::mozilla::safebrowsing::ClientInfo* FindFullHashesRequest::mutable_client() { + ::mozilla::safebrowsing::ClientInfo* _msg = _internal_mutable_client(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindFullHashesRequest.client) + return _msg; +} +inline void FindFullHashesRequest::set_allocated_client(::mozilla::safebrowsing::ClientInfo* client) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.client_; + } + if (client) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(client); + if (message_arena != submessage_arena) { + client = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, client, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.client_ = client; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FindFullHashesRequest.client) +} + +// repeated bytes client_states = 2; +inline int FindFullHashesRequest::_internal_client_states_size() const { + return _impl_.client_states_.size(); +} +inline int FindFullHashesRequest::client_states_size() const { + return _internal_client_states_size(); +} +inline void FindFullHashesRequest::clear_client_states() { + _impl_.client_states_.Clear(); +} +inline std::string* FindFullHashesRequest::add_client_states() { + std::string* _s = _internal_add_client_states(); + // @@protoc_insertion_point(field_add_mutable:mozilla.safebrowsing.FindFullHashesRequest.client_states) + return _s; +} +inline const std::string& FindFullHashesRequest::_internal_client_states(int index) const { + return _impl_.client_states_.Get(index); +} +inline const std::string& FindFullHashesRequest::client_states(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindFullHashesRequest.client_states) + return _internal_client_states(index); +} +inline std::string* FindFullHashesRequest::mutable_client_states(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindFullHashesRequest.client_states) + return _impl_.client_states_.Mutable(index); +} +inline void FindFullHashesRequest::set_client_states(int index, const std::string& value) { + _impl_.client_states_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline void FindFullHashesRequest::set_client_states(int index, std::string&& value) { + _impl_.client_states_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline void FindFullHashesRequest::set_client_states(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.client_states_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline void FindFullHashesRequest::set_client_states(int index, const void* value, size_t size) { + _impl_.client_states_.Mutable(index)->assign( + reinterpret_cast<const char*>(value), size); + // @@protoc_insertion_point(field_set_pointer:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline std::string* FindFullHashesRequest::_internal_add_client_states() { + return _impl_.client_states_.Add(); +} +inline void FindFullHashesRequest::add_client_states(const std::string& value) { + _impl_.client_states_.Add()->assign(value); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline void FindFullHashesRequest::add_client_states(std::string&& value) { + _impl_.client_states_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline void FindFullHashesRequest::add_client_states(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.client_states_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline void FindFullHashesRequest::add_client_states(const void* value, size_t size) { + _impl_.client_states_.Add()->assign(reinterpret_cast<const char*>(value), size); + // @@protoc_insertion_point(field_add_pointer:mozilla.safebrowsing.FindFullHashesRequest.client_states) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<std::string>& +FindFullHashesRequest::client_states() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FindFullHashesRequest.client_states) + return _impl_.client_states_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<std::string>* +FindFullHashesRequest::mutable_client_states() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FindFullHashesRequest.client_states) + return &_impl_.client_states_; +} + +// optional .mozilla.safebrowsing.ThreatInfo threat_info = 3; +inline bool FindFullHashesRequest::_internal_has_threat_info() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.threat_info_ != nullptr); + return value; +} +inline bool FindFullHashesRequest::has_threat_info() const { + return _internal_has_threat_info(); +} +inline void FindFullHashesRequest::clear_threat_info() { + if (_impl_.threat_info_ != nullptr) _impl_.threat_info_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::ThreatInfo& FindFullHashesRequest::_internal_threat_info() const { + const ::mozilla::safebrowsing::ThreatInfo* p = _impl_.threat_info_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ThreatInfo&>( + ::mozilla::safebrowsing::_ThreatInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ThreatInfo& FindFullHashesRequest::threat_info() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindFullHashesRequest.threat_info) + return _internal_threat_info(); +} +inline void FindFullHashesRequest::unsafe_arena_set_allocated_threat_info( + ::mozilla::safebrowsing::ThreatInfo* threat_info) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.threat_info_); + } + _impl_.threat_info_ = threat_info; + if (threat_info) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FindFullHashesRequest.threat_info) +} +inline ::mozilla::safebrowsing::ThreatInfo* FindFullHashesRequest::release_threat_info() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ThreatInfo* temp = _impl_.threat_info_; + _impl_.threat_info_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ThreatInfo* FindFullHashesRequest::unsafe_arena_release_threat_info() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FindFullHashesRequest.threat_info) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ThreatInfo* temp = _impl_.threat_info_; + _impl_.threat_info_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ThreatInfo* FindFullHashesRequest::_internal_mutable_threat_info() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.threat_info_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ThreatInfo>(GetArenaForAllocation()); + _impl_.threat_info_ = p; + } + return _impl_.threat_info_; +} +inline ::mozilla::safebrowsing::ThreatInfo* FindFullHashesRequest::mutable_threat_info() { + ::mozilla::safebrowsing::ThreatInfo* _msg = _internal_mutable_threat_info(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindFullHashesRequest.threat_info) + return _msg; +} +inline void FindFullHashesRequest::set_allocated_threat_info(::mozilla::safebrowsing::ThreatInfo* threat_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.threat_info_; + } + if (threat_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(threat_info); + if (message_arena != submessage_arena) { + threat_info = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, threat_info, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.threat_info_ = threat_info; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FindFullHashesRequest.threat_info) +} + +// ------------------------------------------------------------------- + +// FindFullHashesResponse + +// repeated .mozilla.safebrowsing.ThreatMatch matches = 1; +inline int FindFullHashesResponse::_internal_matches_size() const { + return _impl_.matches_.size(); +} +inline int FindFullHashesResponse::matches_size() const { + return _internal_matches_size(); +} +inline void FindFullHashesResponse::clear_matches() { + _impl_.matches_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatMatch* FindFullHashesResponse::mutable_matches(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindFullHashesResponse.matches) + return _impl_.matches_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >* +FindFullHashesResponse::mutable_matches() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.FindFullHashesResponse.matches) + return &_impl_.matches_; +} +inline const ::mozilla::safebrowsing::ThreatMatch& FindFullHashesResponse::_internal_matches(int index) const { + return _impl_.matches_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatMatch& FindFullHashesResponse::matches(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindFullHashesResponse.matches) + return _internal_matches(index); +} +inline ::mozilla::safebrowsing::ThreatMatch* FindFullHashesResponse::_internal_add_matches() { + return _impl_.matches_.Add(); +} +inline ::mozilla::safebrowsing::ThreatMatch* FindFullHashesResponse::add_matches() { + ::mozilla::safebrowsing::ThreatMatch* _add = _internal_add_matches(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.FindFullHashesResponse.matches) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatMatch >& +FindFullHashesResponse::matches() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.FindFullHashesResponse.matches) + return _impl_.matches_; +} + +// optional .mozilla.safebrowsing.Duration minimum_wait_duration = 2; +inline bool FindFullHashesResponse::_internal_has_minimum_wait_duration() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.minimum_wait_duration_ != nullptr); + return value; +} +inline bool FindFullHashesResponse::has_minimum_wait_duration() const { + return _internal_has_minimum_wait_duration(); +} +inline void FindFullHashesResponse::clear_minimum_wait_duration() { + if (_impl_.minimum_wait_duration_ != nullptr) _impl_.minimum_wait_duration_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::Duration& FindFullHashesResponse::_internal_minimum_wait_duration() const { + const ::mozilla::safebrowsing::Duration* p = _impl_.minimum_wait_duration_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::Duration&>( + ::mozilla::safebrowsing::_Duration_default_instance_); +} +inline const ::mozilla::safebrowsing::Duration& FindFullHashesResponse::minimum_wait_duration() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindFullHashesResponse.minimum_wait_duration) + return _internal_minimum_wait_duration(); +} +inline void FindFullHashesResponse::unsafe_arena_set_allocated_minimum_wait_duration( + ::mozilla::safebrowsing::Duration* minimum_wait_duration) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.minimum_wait_duration_); + } + _impl_.minimum_wait_duration_ = minimum_wait_duration; + if (minimum_wait_duration) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FindFullHashesResponse.minimum_wait_duration) +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::release_minimum_wait_duration() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::Duration* temp = _impl_.minimum_wait_duration_; + _impl_.minimum_wait_duration_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::unsafe_arena_release_minimum_wait_duration() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FindFullHashesResponse.minimum_wait_duration) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::Duration* temp = _impl_.minimum_wait_duration_; + _impl_.minimum_wait_duration_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::_internal_mutable_minimum_wait_duration() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.minimum_wait_duration_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::Duration>(GetArenaForAllocation()); + _impl_.minimum_wait_duration_ = p; + } + return _impl_.minimum_wait_duration_; +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::mutable_minimum_wait_duration() { + ::mozilla::safebrowsing::Duration* _msg = _internal_mutable_minimum_wait_duration(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindFullHashesResponse.minimum_wait_duration) + return _msg; +} +inline void FindFullHashesResponse::set_allocated_minimum_wait_duration(::mozilla::safebrowsing::Duration* minimum_wait_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.minimum_wait_duration_; + } + if (minimum_wait_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(minimum_wait_duration); + if (message_arena != submessage_arena) { + minimum_wait_duration = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, minimum_wait_duration, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.minimum_wait_duration_ = minimum_wait_duration; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FindFullHashesResponse.minimum_wait_duration) +} + +// optional .mozilla.safebrowsing.Duration negative_cache_duration = 3; +inline bool FindFullHashesResponse::_internal_has_negative_cache_duration() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.negative_cache_duration_ != nullptr); + return value; +} +inline bool FindFullHashesResponse::has_negative_cache_duration() const { + return _internal_has_negative_cache_duration(); +} +inline void FindFullHashesResponse::clear_negative_cache_duration() { + if (_impl_.negative_cache_duration_ != nullptr) _impl_.negative_cache_duration_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::Duration& FindFullHashesResponse::_internal_negative_cache_duration() const { + const ::mozilla::safebrowsing::Duration* p = _impl_.negative_cache_duration_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::Duration&>( + ::mozilla::safebrowsing::_Duration_default_instance_); +} +inline const ::mozilla::safebrowsing::Duration& FindFullHashesResponse::negative_cache_duration() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.FindFullHashesResponse.negative_cache_duration) + return _internal_negative_cache_duration(); +} +inline void FindFullHashesResponse::unsafe_arena_set_allocated_negative_cache_duration( + ::mozilla::safebrowsing::Duration* negative_cache_duration) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.negative_cache_duration_); + } + _impl_.negative_cache_duration_ = negative_cache_duration; + if (negative_cache_duration) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.FindFullHashesResponse.negative_cache_duration) +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::release_negative_cache_duration() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::Duration* temp = _impl_.negative_cache_duration_; + _impl_.negative_cache_duration_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::unsafe_arena_release_negative_cache_duration() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.FindFullHashesResponse.negative_cache_duration) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::Duration* temp = _impl_.negative_cache_duration_; + _impl_.negative_cache_duration_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::_internal_mutable_negative_cache_duration() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.negative_cache_duration_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::Duration>(GetArenaForAllocation()); + _impl_.negative_cache_duration_ = p; + } + return _impl_.negative_cache_duration_; +} +inline ::mozilla::safebrowsing::Duration* FindFullHashesResponse::mutable_negative_cache_duration() { + ::mozilla::safebrowsing::Duration* _msg = _internal_mutable_negative_cache_duration(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.FindFullHashesResponse.negative_cache_duration) + return _msg; +} +inline void FindFullHashesResponse::set_allocated_negative_cache_duration(::mozilla::safebrowsing::Duration* negative_cache_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.negative_cache_duration_; + } + if (negative_cache_duration) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(negative_cache_duration); + if (message_arena != submessage_arena) { + negative_cache_duration = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, negative_cache_duration, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.negative_cache_duration_ = negative_cache_duration; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.FindFullHashesResponse.negative_cache_duration) +} + +// ------------------------------------------------------------------- + +// ThreatHit_ThreatSource + +// optional string url = 1; +inline bool ThreatHit_ThreatSource::_internal_has_url() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ThreatHit_ThreatSource::has_url() const { + return _internal_has_url(); +} +inline void ThreatHit_ThreatSource::clear_url() { + _impl_.url_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ThreatHit_ThreatSource::url() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.ThreatSource.url) + return _internal_url(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatHit_ThreatSource::set_url(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.url_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.ThreatSource.url) +} +inline std::string* ThreatHit_ThreatSource::mutable_url() { + std::string* _s = _internal_mutable_url(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.ThreatSource.url) + return _s; +} +inline const std::string& ThreatHit_ThreatSource::_internal_url() const { + return _impl_.url_.Get(); +} +inline void ThreatHit_ThreatSource::_internal_set_url(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.url_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatHit_ThreatSource::_internal_mutable_url() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.url_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatHit_ThreatSource::release_url() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.ThreatSource.url) + if (!_internal_has_url()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.url_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatHit_ThreatSource::set_allocated_url(std::string* url) { + if (url != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.url_.SetAllocated(url, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.ThreatSource.url) +} + +// optional .mozilla.safebrowsing.ThreatHit.ThreatSourceType type = 2; +inline bool ThreatHit_ThreatSource::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool ThreatHit_ThreatSource::has_type() const { + return _internal_has_type(); +} +inline void ThreatHit_ThreatSource::clear_type() { + _impl_.type_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline ::mozilla::safebrowsing::ThreatHit_ThreatSourceType ThreatHit_ThreatSource::_internal_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatHit_ThreatSourceType >(_impl_.type_); +} +inline ::mozilla::safebrowsing::ThreatHit_ThreatSourceType ThreatHit_ThreatSource::type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.ThreatSource.type) + return _internal_type(); +} +inline void ThreatHit_ThreatSource::_internal_set_type(::mozilla::safebrowsing::ThreatHit_ThreatSourceType value) { + assert(::mozilla::safebrowsing::ThreatHit_ThreatSourceType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.type_ = value; +} +inline void ThreatHit_ThreatSource::set_type(::mozilla::safebrowsing::ThreatHit_ThreatSourceType value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.ThreatSource.type) +} + +// optional string remote_ip = 3; +inline bool ThreatHit_ThreatSource::_internal_has_remote_ip() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ThreatHit_ThreatSource::has_remote_ip() const { + return _internal_has_remote_ip(); +} +inline void ThreatHit_ThreatSource::clear_remote_ip() { + _impl_.remote_ip_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ThreatHit_ThreatSource::remote_ip() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.ThreatSource.remote_ip) + return _internal_remote_ip(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatHit_ThreatSource::set_remote_ip(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.remote_ip_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.ThreatSource.remote_ip) +} +inline std::string* ThreatHit_ThreatSource::mutable_remote_ip() { + std::string* _s = _internal_mutable_remote_ip(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.ThreatSource.remote_ip) + return _s; +} +inline const std::string& ThreatHit_ThreatSource::_internal_remote_ip() const { + return _impl_.remote_ip_.Get(); +} +inline void ThreatHit_ThreatSource::_internal_set_remote_ip(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.remote_ip_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatHit_ThreatSource::_internal_mutable_remote_ip() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.remote_ip_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatHit_ThreatSource::release_remote_ip() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.ThreatSource.remote_ip) + if (!_internal_has_remote_ip()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.remote_ip_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.remote_ip_.IsDefault()) { + _impl_.remote_ip_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatHit_ThreatSource::set_allocated_remote_ip(std::string* remote_ip) { + if (remote_ip != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.remote_ip_.SetAllocated(remote_ip, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.remote_ip_.IsDefault()) { + _impl_.remote_ip_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.ThreatSource.remote_ip) +} + +// optional string referrer = 4; +inline bool ThreatHit_ThreatSource::_internal_has_referrer() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool ThreatHit_ThreatSource::has_referrer() const { + return _internal_has_referrer(); +} +inline void ThreatHit_ThreatSource::clear_referrer() { + _impl_.referrer_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const std::string& ThreatHit_ThreatSource::referrer() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.ThreatSource.referrer) + return _internal_referrer(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatHit_ThreatSource::set_referrer(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.referrer_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.ThreatSource.referrer) +} +inline std::string* ThreatHit_ThreatSource::mutable_referrer() { + std::string* _s = _internal_mutable_referrer(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.ThreatSource.referrer) + return _s; +} +inline const std::string& ThreatHit_ThreatSource::_internal_referrer() const { + return _impl_.referrer_.Get(); +} +inline void ThreatHit_ThreatSource::_internal_set_referrer(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.referrer_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatHit_ThreatSource::_internal_mutable_referrer() { + _impl_._has_bits_[0] |= 0x00000004u; + return _impl_.referrer_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatHit_ThreatSource::release_referrer() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.ThreatSource.referrer) + if (!_internal_has_referrer()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000004u; + auto* p = _impl_.referrer_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.referrer_.IsDefault()) { + _impl_.referrer_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatHit_ThreatSource::set_allocated_referrer(std::string* referrer) { + if (referrer != nullptr) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.referrer_.SetAllocated(referrer, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.referrer_.IsDefault()) { + _impl_.referrer_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.ThreatSource.referrer) +} + +// ------------------------------------------------------------------- + +// ThreatHit_UserInfo + +// optional string region_code = 1; +inline bool ThreatHit_UserInfo::_internal_has_region_code() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ThreatHit_UserInfo::has_region_code() const { + return _internal_has_region_code(); +} +inline void ThreatHit_UserInfo::clear_region_code() { + _impl_.region_code_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ThreatHit_UserInfo::region_code() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.UserInfo.region_code) + return _internal_region_code(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatHit_UserInfo::set_region_code(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.region_code_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.UserInfo.region_code) +} +inline std::string* ThreatHit_UserInfo::mutable_region_code() { + std::string* _s = _internal_mutable_region_code(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.UserInfo.region_code) + return _s; +} +inline const std::string& ThreatHit_UserInfo::_internal_region_code() const { + return _impl_.region_code_.Get(); +} +inline void ThreatHit_UserInfo::_internal_set_region_code(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.region_code_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatHit_UserInfo::_internal_mutable_region_code() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.region_code_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatHit_UserInfo::release_region_code() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.UserInfo.region_code) + if (!_internal_has_region_code()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.region_code_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.region_code_.IsDefault()) { + _impl_.region_code_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatHit_UserInfo::set_allocated_region_code(std::string* region_code) { + if (region_code != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.region_code_.SetAllocated(region_code, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.region_code_.IsDefault()) { + _impl_.region_code_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.UserInfo.region_code) +} + +// optional bytes user_id = 2; +inline bool ThreatHit_UserInfo::_internal_has_user_id() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ThreatHit_UserInfo::has_user_id() const { + return _internal_has_user_id(); +} +inline void ThreatHit_UserInfo::clear_user_id() { + _impl_.user_id_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ThreatHit_UserInfo::user_id() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.UserInfo.user_id) + return _internal_user_id(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatHit_UserInfo::set_user_id(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.user_id_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.UserInfo.user_id) +} +inline std::string* ThreatHit_UserInfo::mutable_user_id() { + std::string* _s = _internal_mutable_user_id(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.UserInfo.user_id) + return _s; +} +inline const std::string& ThreatHit_UserInfo::_internal_user_id() const { + return _impl_.user_id_.Get(); +} +inline void ThreatHit_UserInfo::_internal_set_user_id(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.user_id_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatHit_UserInfo::_internal_mutable_user_id() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.user_id_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatHit_UserInfo::release_user_id() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.UserInfo.user_id) + if (!_internal_has_user_id()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.user_id_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.user_id_.IsDefault()) { + _impl_.user_id_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatHit_UserInfo::set_allocated_user_id(std::string* user_id) { + if (user_id != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.user_id_.SetAllocated(user_id, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.user_id_.IsDefault()) { + _impl_.user_id_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.UserInfo.user_id) +} + +// ------------------------------------------------------------------- + +// ThreatHit + +// optional .mozilla.safebrowsing.ThreatType threat_type = 1; +inline bool ThreatHit::_internal_has_threat_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool ThreatHit::has_threat_type() const { + return _internal_has_threat_type(); +} +inline void ThreatHit::clear_threat_type() { + _impl_.threat_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline ::mozilla::safebrowsing::ThreatType ThreatHit::_internal_threat_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatType >(_impl_.threat_type_); +} +inline ::mozilla::safebrowsing::ThreatType ThreatHit::threat_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.threat_type) + return _internal_threat_type(); +} +inline void ThreatHit::_internal_set_threat_type(::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.threat_type_ = value; +} +inline void ThreatHit::set_threat_type(::mozilla::safebrowsing::ThreatType value) { + _internal_set_threat_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.threat_type) +} + +// optional .mozilla.safebrowsing.PlatformType platform_type = 2; +inline bool ThreatHit::_internal_has_platform_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool ThreatHit::has_platform_type() const { + return _internal_has_platform_type(); +} +inline void ThreatHit::clear_platform_type() { + _impl_.platform_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline ::mozilla::safebrowsing::PlatformType ThreatHit::_internal_platform_type() const { + return static_cast< ::mozilla::safebrowsing::PlatformType >(_impl_.platform_type_); +} +inline ::mozilla::safebrowsing::PlatformType ThreatHit::platform_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.platform_type) + return _internal_platform_type(); +} +inline void ThreatHit::_internal_set_platform_type(::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.platform_type_ = value; +} +inline void ThreatHit::set_platform_type(::mozilla::safebrowsing::PlatformType value) { + _internal_set_platform_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatHit.platform_type) +} + +// optional .mozilla.safebrowsing.ThreatEntry entry = 3; +inline bool ThreatHit::_internal_has_entry() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.entry_ != nullptr); + return value; +} +inline bool ThreatHit::has_entry() const { + return _internal_has_entry(); +} +inline void ThreatHit::clear_entry() { + if (_impl_.entry_ != nullptr) _impl_.entry_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::ThreatEntry& ThreatHit::_internal_entry() const { + const ::mozilla::safebrowsing::ThreatEntry* p = _impl_.entry_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ThreatEntry&>( + ::mozilla::safebrowsing::_ThreatEntry_default_instance_); +} +inline const ::mozilla::safebrowsing::ThreatEntry& ThreatHit::entry() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.entry) + return _internal_entry(); +} +inline void ThreatHit::unsafe_arena_set_allocated_entry( + ::mozilla::safebrowsing::ThreatEntry* entry) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.entry_); + } + _impl_.entry_ = entry; + if (entry) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatHit.entry) +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatHit::release_entry() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ThreatEntry* temp = _impl_.entry_; + _impl_.entry_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatHit::unsafe_arena_release_entry() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.entry) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::ThreatEntry* temp = _impl_.entry_; + _impl_.entry_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatHit::_internal_mutable_entry() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.entry_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ThreatEntry>(GetArenaForAllocation()); + _impl_.entry_ = p; + } + return _impl_.entry_; +} +inline ::mozilla::safebrowsing::ThreatEntry* ThreatHit::mutable_entry() { + ::mozilla::safebrowsing::ThreatEntry* _msg = _internal_mutable_entry(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.entry) + return _msg; +} +inline void ThreatHit::set_allocated_entry(::mozilla::safebrowsing::ThreatEntry* entry) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.entry_; + } + if (entry) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(entry); + if (message_arena != submessage_arena) { + entry = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, entry, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.entry_ = entry; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.entry) +} + +// repeated .mozilla.safebrowsing.ThreatHit.ThreatSource resources = 4; +inline int ThreatHit::_internal_resources_size() const { + return _impl_.resources_.size(); +} +inline int ThreatHit::resources_size() const { + return _internal_resources_size(); +} +inline void ThreatHit::clear_resources() { + _impl_.resources_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatHit_ThreatSource* ThreatHit::mutable_resources(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.resources) + return _impl_.resources_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatHit_ThreatSource >* +ThreatHit::mutable_resources() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ThreatHit.resources) + return &_impl_.resources_; +} +inline const ::mozilla::safebrowsing::ThreatHit_ThreatSource& ThreatHit::_internal_resources(int index) const { + return _impl_.resources_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatHit_ThreatSource& ThreatHit::resources(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.resources) + return _internal_resources(index); +} +inline ::mozilla::safebrowsing::ThreatHit_ThreatSource* ThreatHit::_internal_add_resources() { + return _impl_.resources_.Add(); +} +inline ::mozilla::safebrowsing::ThreatHit_ThreatSource* ThreatHit::add_resources() { + ::mozilla::safebrowsing::ThreatHit_ThreatSource* _add = _internal_add_resources(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ThreatHit.resources) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatHit_ThreatSource >& +ThreatHit::resources() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ThreatHit.resources) + return _impl_.resources_; +} + +// optional .mozilla.safebrowsing.ClientInfo client_info = 5; +inline bool ThreatHit::_internal_has_client_info() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.client_info_ != nullptr); + return value; +} +inline bool ThreatHit::has_client_info() const { + return _internal_has_client_info(); +} +inline void ThreatHit::clear_client_info() { + if (_impl_.client_info_ != nullptr) _impl_.client_info_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::ClientInfo& ThreatHit::_internal_client_info() const { + const ::mozilla::safebrowsing::ClientInfo* p = _impl_.client_info_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ClientInfo&>( + ::mozilla::safebrowsing::_ClientInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ClientInfo& ThreatHit::client_info() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.client_info) + return _internal_client_info(); +} +inline void ThreatHit::unsafe_arena_set_allocated_client_info( + ::mozilla::safebrowsing::ClientInfo* client_info) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.client_info_); + } + _impl_.client_info_ = client_info; + if (client_info) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatHit.client_info) +} +inline ::mozilla::safebrowsing::ClientInfo* ThreatHit::release_client_info() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_info_; + _impl_.client_info_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* ThreatHit::unsafe_arena_release_client_info() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.client_info) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::ClientInfo* temp = _impl_.client_info_; + _impl_.client_info_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ClientInfo* ThreatHit::_internal_mutable_client_info() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.client_info_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ClientInfo>(GetArenaForAllocation()); + _impl_.client_info_ = p; + } + return _impl_.client_info_; +} +inline ::mozilla::safebrowsing::ClientInfo* ThreatHit::mutable_client_info() { + ::mozilla::safebrowsing::ClientInfo* _msg = _internal_mutable_client_info(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.client_info) + return _msg; +} +inline void ThreatHit::set_allocated_client_info(::mozilla::safebrowsing::ClientInfo* client_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.client_info_; + } + if (client_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(client_info); + if (message_arena != submessage_arena) { + client_info = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, client_info, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.client_info_ = client_info; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.client_info) +} + +// optional .mozilla.safebrowsing.ThreatHit.UserInfo user_info = 6; +inline bool ThreatHit::_internal_has_user_info() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.user_info_ != nullptr); + return value; +} +inline bool ThreatHit::has_user_info() const { + return _internal_has_user_info(); +} +inline void ThreatHit::clear_user_info() { + if (_impl_.user_info_ != nullptr) _impl_.user_info_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::safebrowsing::ThreatHit_UserInfo& ThreatHit::_internal_user_info() const { + const ::mozilla::safebrowsing::ThreatHit_UserInfo* p = _impl_.user_info_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::ThreatHit_UserInfo&>( + ::mozilla::safebrowsing::_ThreatHit_UserInfo_default_instance_); +} +inline const ::mozilla::safebrowsing::ThreatHit_UserInfo& ThreatHit::user_info() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatHit.user_info) + return _internal_user_info(); +} +inline void ThreatHit::unsafe_arena_set_allocated_user_info( + ::mozilla::safebrowsing::ThreatHit_UserInfo* user_info) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.user_info_); + } + _impl_.user_info_ = user_info; + if (user_info) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatHit.user_info) +} +inline ::mozilla::safebrowsing::ThreatHit_UserInfo* ThreatHit::release_user_info() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::mozilla::safebrowsing::ThreatHit_UserInfo* temp = _impl_.user_info_; + _impl_.user_info_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::ThreatHit_UserInfo* ThreatHit::unsafe_arena_release_user_info() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatHit.user_info) + _impl_._has_bits_[0] &= ~0x00000004u; + ::mozilla::safebrowsing::ThreatHit_UserInfo* temp = _impl_.user_info_; + _impl_.user_info_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::ThreatHit_UserInfo* ThreatHit::_internal_mutable_user_info() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.user_info_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::ThreatHit_UserInfo>(GetArenaForAllocation()); + _impl_.user_info_ = p; + } + return _impl_.user_info_; +} +inline ::mozilla::safebrowsing::ThreatHit_UserInfo* ThreatHit::mutable_user_info() { + ::mozilla::safebrowsing::ThreatHit_UserInfo* _msg = _internal_mutable_user_info(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatHit.user_info) + return _msg; +} +inline void ThreatHit::set_allocated_user_info(::mozilla::safebrowsing::ThreatHit_UserInfo* user_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.user_info_; + } + if (user_info) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(user_info); + if (message_arena != submessage_arena) { + user_info = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, user_info, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.user_info_ = user_info; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatHit.user_info) +} + +// ------------------------------------------------------------------- + +// ClientInfo + +// optional string client_id = 1; +inline bool ClientInfo::_internal_has_client_id() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ClientInfo::has_client_id() const { + return _internal_has_client_id(); +} +inline void ClientInfo::clear_client_id() { + _impl_.client_id_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ClientInfo::client_id() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ClientInfo.client_id) + return _internal_client_id(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ClientInfo::set_client_id(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.client_id_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ClientInfo.client_id) +} +inline std::string* ClientInfo::mutable_client_id() { + std::string* _s = _internal_mutable_client_id(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ClientInfo.client_id) + return _s; +} +inline const std::string& ClientInfo::_internal_client_id() const { + return _impl_.client_id_.Get(); +} +inline void ClientInfo::_internal_set_client_id(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.client_id_.Set(value, GetArenaForAllocation()); +} +inline std::string* ClientInfo::_internal_mutable_client_id() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.client_id_.Mutable(GetArenaForAllocation()); +} +inline std::string* ClientInfo::release_client_id() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ClientInfo.client_id) + if (!_internal_has_client_id()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.client_id_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.client_id_.IsDefault()) { + _impl_.client_id_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ClientInfo::set_allocated_client_id(std::string* client_id) { + if (client_id != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.client_id_.SetAllocated(client_id, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.client_id_.IsDefault()) { + _impl_.client_id_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ClientInfo.client_id) +} + +// optional string client_version = 2; +inline bool ClientInfo::_internal_has_client_version() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ClientInfo::has_client_version() const { + return _internal_has_client_version(); +} +inline void ClientInfo::clear_client_version() { + _impl_.client_version_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ClientInfo::client_version() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ClientInfo.client_version) + return _internal_client_version(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ClientInfo::set_client_version(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.client_version_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ClientInfo.client_version) +} +inline std::string* ClientInfo::mutable_client_version() { + std::string* _s = _internal_mutable_client_version(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ClientInfo.client_version) + return _s; +} +inline const std::string& ClientInfo::_internal_client_version() const { + return _impl_.client_version_.Get(); +} +inline void ClientInfo::_internal_set_client_version(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.client_version_.Set(value, GetArenaForAllocation()); +} +inline std::string* ClientInfo::_internal_mutable_client_version() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.client_version_.Mutable(GetArenaForAllocation()); +} +inline std::string* ClientInfo::release_client_version() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ClientInfo.client_version) + if (!_internal_has_client_version()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.client_version_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.client_version_.IsDefault()) { + _impl_.client_version_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ClientInfo::set_allocated_client_version(std::string* client_version) { + if (client_version != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.client_version_.SetAllocated(client_version, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.client_version_.IsDefault()) { + _impl_.client_version_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ClientInfo.client_version) +} + +// ------------------------------------------------------------------- + +// ChromeClientInfo + +// optional .mozilla.safebrowsing.ChromeClientInfo.SafeBrowsingReportingPopulation reporting_population = 1; +inline bool ChromeClientInfo::_internal_has_reporting_population() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ChromeClientInfo::has_reporting_population() const { + return _internal_has_reporting_population(); +} +inline void ChromeClientInfo::clear_reporting_population() { + _impl_.reporting_population_ = 0; + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline ::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::_internal_reporting_population() const { + return static_cast< ::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation >(_impl_.reporting_population_); +} +inline ::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation ChromeClientInfo::reporting_population() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ChromeClientInfo.reporting_population) + return _internal_reporting_population(); +} +inline void ChromeClientInfo::_internal_set_reporting_population(::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation value) { + assert(::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.reporting_population_ = value; +} +inline void ChromeClientInfo::set_reporting_population(::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation value) { + _internal_set_reporting_population(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ChromeClientInfo.reporting_population) +} + +// ------------------------------------------------------------------- + +// Checksum + +// optional bytes sha256 = 1; +inline bool Checksum::_internal_has_sha256() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool Checksum::has_sha256() const { + return _internal_has_sha256(); +} +inline void Checksum::clear_sha256() { + _impl_.sha256_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& Checksum::sha256() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.Checksum.sha256) + return _internal_sha256(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void Checksum::set_sha256(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.sha256_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.Checksum.sha256) +} +inline std::string* Checksum::mutable_sha256() { + std::string* _s = _internal_mutable_sha256(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.Checksum.sha256) + return _s; +} +inline const std::string& Checksum::_internal_sha256() const { + return _impl_.sha256_.Get(); +} +inline void Checksum::_internal_set_sha256(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.sha256_.Set(value, GetArenaForAllocation()); +} +inline std::string* Checksum::_internal_mutable_sha256() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.sha256_.Mutable(GetArenaForAllocation()); +} +inline std::string* Checksum::release_sha256() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.Checksum.sha256) + if (!_internal_has_sha256()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.sha256_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.sha256_.IsDefault()) { + _impl_.sha256_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void Checksum::set_allocated_sha256(std::string* sha256) { + if (sha256 != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.sha256_.SetAllocated(sha256, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.sha256_.IsDefault()) { + _impl_.sha256_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.Checksum.sha256) +} + +// ------------------------------------------------------------------- + +// ThreatEntry + +// optional bytes hash = 1; +inline bool ThreatEntry::_internal_has_hash() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ThreatEntry::has_hash() const { + return _internal_has_hash(); +} +inline void ThreatEntry::clear_hash() { + _impl_.hash_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ThreatEntry::hash() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntry.hash) + return _internal_hash(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatEntry::set_hash(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.hash_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatEntry.hash) +} +inline std::string* ThreatEntry::mutable_hash() { + std::string* _s = _internal_mutable_hash(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntry.hash) + return _s; +} +inline const std::string& ThreatEntry::_internal_hash() const { + return _impl_.hash_.Get(); +} +inline void ThreatEntry::_internal_set_hash(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.hash_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatEntry::_internal_mutable_hash() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.hash_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatEntry::release_hash() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntry.hash) + if (!_internal_has_hash()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.hash_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.hash_.IsDefault()) { + _impl_.hash_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatEntry::set_allocated_hash(std::string* hash) { + if (hash != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.hash_.SetAllocated(hash, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.hash_.IsDefault()) { + _impl_.hash_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntry.hash) +} + +// optional string url = 2; +inline bool ThreatEntry::_internal_has_url() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ThreatEntry::has_url() const { + return _internal_has_url(); +} +inline void ThreatEntry::clear_url() { + _impl_.url_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ThreatEntry::url() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntry.url) + return _internal_url(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatEntry::set_url(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.url_.Set(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatEntry.url) +} +inline std::string* ThreatEntry::mutable_url() { + std::string* _s = _internal_mutable_url(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntry.url) + return _s; +} +inline const std::string& ThreatEntry::_internal_url() const { + return _impl_.url_.Get(); +} +inline void ThreatEntry::_internal_set_url(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.url_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatEntry::_internal_mutable_url() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.url_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatEntry::release_url() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntry.url) + if (!_internal_has_url()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.url_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatEntry::set_allocated_url(std::string* url) { + if (url != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.url_.SetAllocated(url, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntry.url) +} + +// ------------------------------------------------------------------- + +// ThreatEntrySet + +// optional .mozilla.safebrowsing.CompressionType compression_type = 1; +inline bool ThreatEntrySet::_internal_has_compression_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool ThreatEntrySet::has_compression_type() const { + return _internal_has_compression_type(); +} +inline void ThreatEntrySet::clear_compression_type() { + _impl_.compression_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline ::mozilla::safebrowsing::CompressionType ThreatEntrySet::_internal_compression_type() const { + return static_cast< ::mozilla::safebrowsing::CompressionType >(_impl_.compression_type_); +} +inline ::mozilla::safebrowsing::CompressionType ThreatEntrySet::compression_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntrySet.compression_type) + return _internal_compression_type(); +} +inline void ThreatEntrySet::_internal_set_compression_type(::mozilla::safebrowsing::CompressionType value) { + assert(::mozilla::safebrowsing::CompressionType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.compression_type_ = value; +} +inline void ThreatEntrySet::set_compression_type(::mozilla::safebrowsing::CompressionType value) { + _internal_set_compression_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatEntrySet.compression_type) +} + +// optional .mozilla.safebrowsing.RawHashes raw_hashes = 2; +inline bool ThreatEntrySet::_internal_has_raw_hashes() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.raw_hashes_ != nullptr); + return value; +} +inline bool ThreatEntrySet::has_raw_hashes() const { + return _internal_has_raw_hashes(); +} +inline void ThreatEntrySet::clear_raw_hashes() { + if (_impl_.raw_hashes_ != nullptr) _impl_.raw_hashes_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::safebrowsing::RawHashes& ThreatEntrySet::_internal_raw_hashes() const { + const ::mozilla::safebrowsing::RawHashes* p = _impl_.raw_hashes_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::RawHashes&>( + ::mozilla::safebrowsing::_RawHashes_default_instance_); +} +inline const ::mozilla::safebrowsing::RawHashes& ThreatEntrySet::raw_hashes() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntrySet.raw_hashes) + return _internal_raw_hashes(); +} +inline void ThreatEntrySet::unsafe_arena_set_allocated_raw_hashes( + ::mozilla::safebrowsing::RawHashes* raw_hashes) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.raw_hashes_); + } + _impl_.raw_hashes_ = raw_hashes; + if (raw_hashes) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatEntrySet.raw_hashes) +} +inline ::mozilla::safebrowsing::RawHashes* ThreatEntrySet::release_raw_hashes() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::RawHashes* temp = _impl_.raw_hashes_; + _impl_.raw_hashes_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::RawHashes* ThreatEntrySet::unsafe_arena_release_raw_hashes() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntrySet.raw_hashes) + _impl_._has_bits_[0] &= ~0x00000001u; + ::mozilla::safebrowsing::RawHashes* temp = _impl_.raw_hashes_; + _impl_.raw_hashes_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::RawHashes* ThreatEntrySet::_internal_mutable_raw_hashes() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.raw_hashes_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::RawHashes>(GetArenaForAllocation()); + _impl_.raw_hashes_ = p; + } + return _impl_.raw_hashes_; +} +inline ::mozilla::safebrowsing::RawHashes* ThreatEntrySet::mutable_raw_hashes() { + ::mozilla::safebrowsing::RawHashes* _msg = _internal_mutable_raw_hashes(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntrySet.raw_hashes) + return _msg; +} +inline void ThreatEntrySet::set_allocated_raw_hashes(::mozilla::safebrowsing::RawHashes* raw_hashes) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.raw_hashes_; + } + if (raw_hashes) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(raw_hashes); + if (message_arena != submessage_arena) { + raw_hashes = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, raw_hashes, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.raw_hashes_ = raw_hashes; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntrySet.raw_hashes) +} + +// optional .mozilla.safebrowsing.RawIndices raw_indices = 3; +inline bool ThreatEntrySet::_internal_has_raw_indices() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.raw_indices_ != nullptr); + return value; +} +inline bool ThreatEntrySet::has_raw_indices() const { + return _internal_has_raw_indices(); +} +inline void ThreatEntrySet::clear_raw_indices() { + if (_impl_.raw_indices_ != nullptr) _impl_.raw_indices_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::safebrowsing::RawIndices& ThreatEntrySet::_internal_raw_indices() const { + const ::mozilla::safebrowsing::RawIndices* p = _impl_.raw_indices_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::RawIndices&>( + ::mozilla::safebrowsing::_RawIndices_default_instance_); +} +inline const ::mozilla::safebrowsing::RawIndices& ThreatEntrySet::raw_indices() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntrySet.raw_indices) + return _internal_raw_indices(); +} +inline void ThreatEntrySet::unsafe_arena_set_allocated_raw_indices( + ::mozilla::safebrowsing::RawIndices* raw_indices) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.raw_indices_); + } + _impl_.raw_indices_ = raw_indices; + if (raw_indices) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatEntrySet.raw_indices) +} +inline ::mozilla::safebrowsing::RawIndices* ThreatEntrySet::release_raw_indices() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::RawIndices* temp = _impl_.raw_indices_; + _impl_.raw_indices_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::RawIndices* ThreatEntrySet::unsafe_arena_release_raw_indices() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntrySet.raw_indices) + _impl_._has_bits_[0] &= ~0x00000002u; + ::mozilla::safebrowsing::RawIndices* temp = _impl_.raw_indices_; + _impl_.raw_indices_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::RawIndices* ThreatEntrySet::_internal_mutable_raw_indices() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.raw_indices_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::RawIndices>(GetArenaForAllocation()); + _impl_.raw_indices_ = p; + } + return _impl_.raw_indices_; +} +inline ::mozilla::safebrowsing::RawIndices* ThreatEntrySet::mutable_raw_indices() { + ::mozilla::safebrowsing::RawIndices* _msg = _internal_mutable_raw_indices(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntrySet.raw_indices) + return _msg; +} +inline void ThreatEntrySet::set_allocated_raw_indices(::mozilla::safebrowsing::RawIndices* raw_indices) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.raw_indices_; + } + if (raw_indices) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(raw_indices); + if (message_arena != submessage_arena) { + raw_indices = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, raw_indices, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.raw_indices_ = raw_indices; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntrySet.raw_indices) +} + +// optional .mozilla.safebrowsing.RiceDeltaEncoding rice_hashes = 4; +inline bool ThreatEntrySet::_internal_has_rice_hashes() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.rice_hashes_ != nullptr); + return value; +} +inline bool ThreatEntrySet::has_rice_hashes() const { + return _internal_has_rice_hashes(); +} +inline void ThreatEntrySet::clear_rice_hashes() { + if (_impl_.rice_hashes_ != nullptr) _impl_.rice_hashes_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::safebrowsing::RiceDeltaEncoding& ThreatEntrySet::_internal_rice_hashes() const { + const ::mozilla::safebrowsing::RiceDeltaEncoding* p = _impl_.rice_hashes_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::RiceDeltaEncoding&>( + ::mozilla::safebrowsing::_RiceDeltaEncoding_default_instance_); +} +inline const ::mozilla::safebrowsing::RiceDeltaEncoding& ThreatEntrySet::rice_hashes() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntrySet.rice_hashes) + return _internal_rice_hashes(); +} +inline void ThreatEntrySet::unsafe_arena_set_allocated_rice_hashes( + ::mozilla::safebrowsing::RiceDeltaEncoding* rice_hashes) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.rice_hashes_); + } + _impl_.rice_hashes_ = rice_hashes; + if (rice_hashes) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatEntrySet.rice_hashes) +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::release_rice_hashes() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::mozilla::safebrowsing::RiceDeltaEncoding* temp = _impl_.rice_hashes_; + _impl_.rice_hashes_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::unsafe_arena_release_rice_hashes() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntrySet.rice_hashes) + _impl_._has_bits_[0] &= ~0x00000004u; + ::mozilla::safebrowsing::RiceDeltaEncoding* temp = _impl_.rice_hashes_; + _impl_.rice_hashes_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::_internal_mutable_rice_hashes() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.rice_hashes_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::RiceDeltaEncoding>(GetArenaForAllocation()); + _impl_.rice_hashes_ = p; + } + return _impl_.rice_hashes_; +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::mutable_rice_hashes() { + ::mozilla::safebrowsing::RiceDeltaEncoding* _msg = _internal_mutable_rice_hashes(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntrySet.rice_hashes) + return _msg; +} +inline void ThreatEntrySet::set_allocated_rice_hashes(::mozilla::safebrowsing::RiceDeltaEncoding* rice_hashes) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.rice_hashes_; + } + if (rice_hashes) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(rice_hashes); + if (message_arena != submessage_arena) { + rice_hashes = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, rice_hashes, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.rice_hashes_ = rice_hashes; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntrySet.rice_hashes) +} + +// optional .mozilla.safebrowsing.RiceDeltaEncoding rice_indices = 5; +inline bool ThreatEntrySet::_internal_has_rice_indices() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + PROTOBUF_ASSUME(!value || _impl_.rice_indices_ != nullptr); + return value; +} +inline bool ThreatEntrySet::has_rice_indices() const { + return _internal_has_rice_indices(); +} +inline void ThreatEntrySet::clear_rice_indices() { + if (_impl_.rice_indices_ != nullptr) _impl_.rice_indices_->Clear(); + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline const ::mozilla::safebrowsing::RiceDeltaEncoding& ThreatEntrySet::_internal_rice_indices() const { + const ::mozilla::safebrowsing::RiceDeltaEncoding* p = _impl_.rice_indices_; + return p != nullptr ? *p : reinterpret_cast<const ::mozilla::safebrowsing::RiceDeltaEncoding&>( + ::mozilla::safebrowsing::_RiceDeltaEncoding_default_instance_); +} +inline const ::mozilla::safebrowsing::RiceDeltaEncoding& ThreatEntrySet::rice_indices() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntrySet.rice_indices) + return _internal_rice_indices(); +} +inline void ThreatEntrySet::unsafe_arena_set_allocated_rice_indices( + ::mozilla::safebrowsing::RiceDeltaEncoding* rice_indices) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.rice_indices_); + } + _impl_.rice_indices_ = rice_indices; + if (rice_indices) { + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.safebrowsing.ThreatEntrySet.rice_indices) +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::release_rice_indices() { + _impl_._has_bits_[0] &= ~0x00000008u; + ::mozilla::safebrowsing::RiceDeltaEncoding* temp = _impl_.rice_indices_; + _impl_.rice_indices_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::unsafe_arena_release_rice_indices() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntrySet.rice_indices) + _impl_._has_bits_[0] &= ~0x00000008u; + ::mozilla::safebrowsing::RiceDeltaEncoding* temp = _impl_.rice_indices_; + _impl_.rice_indices_ = nullptr; + return temp; +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::_internal_mutable_rice_indices() { + _impl_._has_bits_[0] |= 0x00000008u; + if (_impl_.rice_indices_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::safebrowsing::RiceDeltaEncoding>(GetArenaForAllocation()); + _impl_.rice_indices_ = p; + } + return _impl_.rice_indices_; +} +inline ::mozilla::safebrowsing::RiceDeltaEncoding* ThreatEntrySet::mutable_rice_indices() { + ::mozilla::safebrowsing::RiceDeltaEncoding* _msg = _internal_mutable_rice_indices(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntrySet.rice_indices) + return _msg; +} +inline void ThreatEntrySet::set_allocated_rice_indices(::mozilla::safebrowsing::RiceDeltaEncoding* rice_indices) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.rice_indices_; + } + if (rice_indices) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(rice_indices); + if (message_arena != submessage_arena) { + rice_indices = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, rice_indices, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + _impl_.rice_indices_ = rice_indices; + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntrySet.rice_indices) +} + +// ------------------------------------------------------------------- + +// RawIndices + +// repeated int32 indices = 1; +inline int RawIndices::_internal_indices_size() const { + return _impl_.indices_.size(); +} +inline int RawIndices::indices_size() const { + return _internal_indices_size(); +} +inline void RawIndices::clear_indices() { + _impl_.indices_.Clear(); +} +inline int32_t RawIndices::_internal_indices(int index) const { + return _impl_.indices_.Get(index); +} +inline int32_t RawIndices::indices(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RawIndices.indices) + return _internal_indices(index); +} +inline void RawIndices::set_indices(int index, int32_t value) { + _impl_.indices_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RawIndices.indices) +} +inline void RawIndices::_internal_add_indices(int32_t value) { + _impl_.indices_.Add(value); +} +inline void RawIndices::add_indices(int32_t value) { + _internal_add_indices(value); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.RawIndices.indices) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >& +RawIndices::_internal_indices() const { + return _impl_.indices_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >& +RawIndices::indices() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.RawIndices.indices) + return _internal_indices(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >* +RawIndices::_internal_mutable_indices() { + return &_impl_.indices_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t >* +RawIndices::mutable_indices() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.RawIndices.indices) + return _internal_mutable_indices(); +} + +// ------------------------------------------------------------------- + +// RawHashes + +// optional int32 prefix_size = 1; +inline bool RawHashes::_internal_has_prefix_size() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool RawHashes::has_prefix_size() const { + return _internal_has_prefix_size(); +} +inline void RawHashes::clear_prefix_size() { + _impl_.prefix_size_ = 0; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline int32_t RawHashes::_internal_prefix_size() const { + return _impl_.prefix_size_; +} +inline int32_t RawHashes::prefix_size() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RawHashes.prefix_size) + return _internal_prefix_size(); +} +inline void RawHashes::_internal_set_prefix_size(int32_t value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.prefix_size_ = value; +} +inline void RawHashes::set_prefix_size(int32_t value) { + _internal_set_prefix_size(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RawHashes.prefix_size) +} + +// optional bytes raw_hashes = 2; +inline bool RawHashes::_internal_has_raw_hashes() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool RawHashes::has_raw_hashes() const { + return _internal_has_raw_hashes(); +} +inline void RawHashes::clear_raw_hashes() { + _impl_.raw_hashes_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& RawHashes::raw_hashes() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RawHashes.raw_hashes) + return _internal_raw_hashes(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void RawHashes::set_raw_hashes(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.raw_hashes_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RawHashes.raw_hashes) +} +inline std::string* RawHashes::mutable_raw_hashes() { + std::string* _s = _internal_mutable_raw_hashes(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.RawHashes.raw_hashes) + return _s; +} +inline const std::string& RawHashes::_internal_raw_hashes() const { + return _impl_.raw_hashes_.Get(); +} +inline void RawHashes::_internal_set_raw_hashes(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.raw_hashes_.Set(value, GetArenaForAllocation()); +} +inline std::string* RawHashes::_internal_mutable_raw_hashes() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.raw_hashes_.Mutable(GetArenaForAllocation()); +} +inline std::string* RawHashes::release_raw_hashes() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.RawHashes.raw_hashes) + if (!_internal_has_raw_hashes()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.raw_hashes_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.raw_hashes_.IsDefault()) { + _impl_.raw_hashes_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void RawHashes::set_allocated_raw_hashes(std::string* raw_hashes) { + if (raw_hashes != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.raw_hashes_.SetAllocated(raw_hashes, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.raw_hashes_.IsDefault()) { + _impl_.raw_hashes_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.RawHashes.raw_hashes) +} + +// ------------------------------------------------------------------- + +// RiceDeltaEncoding + +// optional int64 first_value = 1; +inline bool RiceDeltaEncoding::_internal_has_first_value() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool RiceDeltaEncoding::has_first_value() const { + return _internal_has_first_value(); +} +inline void RiceDeltaEncoding::clear_first_value() { + _impl_.first_value_ = int64_t{0}; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline int64_t RiceDeltaEncoding::_internal_first_value() const { + return _impl_.first_value_; +} +inline int64_t RiceDeltaEncoding::first_value() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RiceDeltaEncoding.first_value) + return _internal_first_value(); +} +inline void RiceDeltaEncoding::_internal_set_first_value(int64_t value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.first_value_ = value; +} +inline void RiceDeltaEncoding::set_first_value(int64_t value) { + _internal_set_first_value(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RiceDeltaEncoding.first_value) +} + +// optional int32 rice_parameter = 2; +inline bool RiceDeltaEncoding::_internal_has_rice_parameter() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool RiceDeltaEncoding::has_rice_parameter() const { + return _internal_has_rice_parameter(); +} +inline void RiceDeltaEncoding::clear_rice_parameter() { + _impl_.rice_parameter_ = 0; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline int32_t RiceDeltaEncoding::_internal_rice_parameter() const { + return _impl_.rice_parameter_; +} +inline int32_t RiceDeltaEncoding::rice_parameter() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RiceDeltaEncoding.rice_parameter) + return _internal_rice_parameter(); +} +inline void RiceDeltaEncoding::_internal_set_rice_parameter(int32_t value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.rice_parameter_ = value; +} +inline void RiceDeltaEncoding::set_rice_parameter(int32_t value) { + _internal_set_rice_parameter(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RiceDeltaEncoding.rice_parameter) +} + +// optional int32 num_entries = 3; +inline bool RiceDeltaEncoding::_internal_has_num_entries() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool RiceDeltaEncoding::has_num_entries() const { + return _internal_has_num_entries(); +} +inline void RiceDeltaEncoding::clear_num_entries() { + _impl_.num_entries_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline int32_t RiceDeltaEncoding::_internal_num_entries() const { + return _impl_.num_entries_; +} +inline int32_t RiceDeltaEncoding::num_entries() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RiceDeltaEncoding.num_entries) + return _internal_num_entries(); +} +inline void RiceDeltaEncoding::_internal_set_num_entries(int32_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.num_entries_ = value; +} +inline void RiceDeltaEncoding::set_num_entries(int32_t value) { + _internal_set_num_entries(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RiceDeltaEncoding.num_entries) +} + +// optional bytes encoded_data = 4; +inline bool RiceDeltaEncoding::_internal_has_encoded_data() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool RiceDeltaEncoding::has_encoded_data() const { + return _internal_has_encoded_data(); +} +inline void RiceDeltaEncoding::clear_encoded_data() { + _impl_.encoded_data_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& RiceDeltaEncoding::encoded_data() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.RiceDeltaEncoding.encoded_data) + return _internal_encoded_data(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void RiceDeltaEncoding::set_encoded_data(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.encoded_data_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.RiceDeltaEncoding.encoded_data) +} +inline std::string* RiceDeltaEncoding::mutable_encoded_data() { + std::string* _s = _internal_mutable_encoded_data(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.RiceDeltaEncoding.encoded_data) + return _s; +} +inline const std::string& RiceDeltaEncoding::_internal_encoded_data() const { + return _impl_.encoded_data_.Get(); +} +inline void RiceDeltaEncoding::_internal_set_encoded_data(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.encoded_data_.Set(value, GetArenaForAllocation()); +} +inline std::string* RiceDeltaEncoding::_internal_mutable_encoded_data() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.encoded_data_.Mutable(GetArenaForAllocation()); +} +inline std::string* RiceDeltaEncoding::release_encoded_data() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.RiceDeltaEncoding.encoded_data) + if (!_internal_has_encoded_data()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.encoded_data_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.encoded_data_.IsDefault()) { + _impl_.encoded_data_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void RiceDeltaEncoding::set_allocated_encoded_data(std::string* encoded_data) { + if (encoded_data != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.encoded_data_.SetAllocated(encoded_data, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.encoded_data_.IsDefault()) { + _impl_.encoded_data_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.RiceDeltaEncoding.encoded_data) +} + +// ------------------------------------------------------------------- + +// ThreatEntryMetadata_MetadataEntry + +// optional bytes key = 1; +inline bool ThreatEntryMetadata_MetadataEntry::_internal_has_key() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ThreatEntryMetadata_MetadataEntry::has_key() const { + return _internal_has_key(); +} +inline void ThreatEntryMetadata_MetadataEntry::clear_key() { + _impl_.key_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ThreatEntryMetadata_MetadataEntry::key() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.key) + return _internal_key(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatEntryMetadata_MetadataEntry::set_key(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.key_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.key) +} +inline std::string* ThreatEntryMetadata_MetadataEntry::mutable_key() { + std::string* _s = _internal_mutable_key(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.key) + return _s; +} +inline const std::string& ThreatEntryMetadata_MetadataEntry::_internal_key() const { + return _impl_.key_.Get(); +} +inline void ThreatEntryMetadata_MetadataEntry::_internal_set_key(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.key_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatEntryMetadata_MetadataEntry::_internal_mutable_key() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.key_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatEntryMetadata_MetadataEntry::release_key() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.key) + if (!_internal_has_key()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.key_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.key_.IsDefault()) { + _impl_.key_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatEntryMetadata_MetadataEntry::set_allocated_key(std::string* key) { + if (key != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.key_.SetAllocated(key, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.key_.IsDefault()) { + _impl_.key_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.key) +} + +// optional bytes value = 2; +inline bool ThreatEntryMetadata_MetadataEntry::_internal_has_value() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ThreatEntryMetadata_MetadataEntry::has_value() const { + return _internal_has_value(); +} +inline void ThreatEntryMetadata_MetadataEntry::clear_value() { + _impl_.value_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ThreatEntryMetadata_MetadataEntry::value() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.value) + return _internal_value(); +} +template <typename ArgT0, typename... ArgT> +inline PROTOBUF_ALWAYS_INLINE +void ThreatEntryMetadata_MetadataEntry::set_value(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.value_.SetBytes(static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.value) +} +inline std::string* ThreatEntryMetadata_MetadataEntry::mutable_value() { + std::string* _s = _internal_mutable_value(); + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.value) + return _s; +} +inline const std::string& ThreatEntryMetadata_MetadataEntry::_internal_value() const { + return _impl_.value_.Get(); +} +inline void ThreatEntryMetadata_MetadataEntry::_internal_set_value(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.value_.Set(value, GetArenaForAllocation()); +} +inline std::string* ThreatEntryMetadata_MetadataEntry::_internal_mutable_value() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.value_.Mutable(GetArenaForAllocation()); +} +inline std::string* ThreatEntryMetadata_MetadataEntry::release_value() { + // @@protoc_insertion_point(field_release:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.value) + if (!_internal_has_value()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.value_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.value_.IsDefault()) { + _impl_.value_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ThreatEntryMetadata_MetadataEntry::set_allocated_value(std::string* value) { + if (value != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.value_.SetAllocated(value, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.value_.IsDefault()) { + _impl_.value_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry.value) +} + +// ------------------------------------------------------------------- + +// ThreatEntryMetadata + +// repeated .mozilla.safebrowsing.ThreatEntryMetadata.MetadataEntry entries = 1; +inline int ThreatEntryMetadata::_internal_entries_size() const { + return _impl_.entries_.size(); +} +inline int ThreatEntryMetadata::entries_size() const { + return _internal_entries_size(); +} +inline void ThreatEntryMetadata::clear_entries() { + _impl_.entries_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* ThreatEntryMetadata::mutable_entries(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ThreatEntryMetadata.entries) + return _impl_.entries_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry >* +ThreatEntryMetadata::mutable_entries() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ThreatEntryMetadata.entries) + return &_impl_.entries_; +} +inline const ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry& ThreatEntryMetadata::_internal_entries(int index) const { + return _impl_.entries_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry& ThreatEntryMetadata::entries(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatEntryMetadata.entries) + return _internal_entries(index); +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* ThreatEntryMetadata::_internal_add_entries() { + return _impl_.entries_.Add(); +} +inline ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* ThreatEntryMetadata::add_entries() { + ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry* _add = _internal_add_entries(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ThreatEntryMetadata.entries) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatEntryMetadata_MetadataEntry >& +ThreatEntryMetadata::entries() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ThreatEntryMetadata.entries) + return _impl_.entries_; +} + +// ------------------------------------------------------------------- + +// ThreatListDescriptor + +// optional .mozilla.safebrowsing.ThreatType threat_type = 1; +inline bool ThreatListDescriptor::_internal_has_threat_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ThreatListDescriptor::has_threat_type() const { + return _internal_has_threat_type(); +} +inline void ThreatListDescriptor::clear_threat_type() { + _impl_.threat_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline ::mozilla::safebrowsing::ThreatType ThreatListDescriptor::_internal_threat_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatType >(_impl_.threat_type_); +} +inline ::mozilla::safebrowsing::ThreatType ThreatListDescriptor::threat_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatListDescriptor.threat_type) + return _internal_threat_type(); +} +inline void ThreatListDescriptor::_internal_set_threat_type(::mozilla::safebrowsing::ThreatType value) { + assert(::mozilla::safebrowsing::ThreatType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.threat_type_ = value; +} +inline void ThreatListDescriptor::set_threat_type(::mozilla::safebrowsing::ThreatType value) { + _internal_set_threat_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatListDescriptor.threat_type) +} + +// optional .mozilla.safebrowsing.PlatformType platform_type = 2; +inline bool ThreatListDescriptor::_internal_has_platform_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ThreatListDescriptor::has_platform_type() const { + return _internal_has_platform_type(); +} +inline void ThreatListDescriptor::clear_platform_type() { + _impl_.platform_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline ::mozilla::safebrowsing::PlatformType ThreatListDescriptor::_internal_platform_type() const { + return static_cast< ::mozilla::safebrowsing::PlatformType >(_impl_.platform_type_); +} +inline ::mozilla::safebrowsing::PlatformType ThreatListDescriptor::platform_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatListDescriptor.platform_type) + return _internal_platform_type(); +} +inline void ThreatListDescriptor::_internal_set_platform_type(::mozilla::safebrowsing::PlatformType value) { + assert(::mozilla::safebrowsing::PlatformType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.platform_type_ = value; +} +inline void ThreatListDescriptor::set_platform_type(::mozilla::safebrowsing::PlatformType value) { + _internal_set_platform_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatListDescriptor.platform_type) +} + +// optional .mozilla.safebrowsing.ThreatEntryType threat_entry_type = 3; +inline bool ThreatListDescriptor::_internal_has_threat_entry_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool ThreatListDescriptor::has_threat_entry_type() const { + return _internal_has_threat_entry_type(); +} +inline void ThreatListDescriptor::clear_threat_entry_type() { + _impl_.threat_entry_type_ = 0; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline ::mozilla::safebrowsing::ThreatEntryType ThreatListDescriptor::_internal_threat_entry_type() const { + return static_cast< ::mozilla::safebrowsing::ThreatEntryType >(_impl_.threat_entry_type_); +} +inline ::mozilla::safebrowsing::ThreatEntryType ThreatListDescriptor::threat_entry_type() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ThreatListDescriptor.threat_entry_type) + return _internal_threat_entry_type(); +} +inline void ThreatListDescriptor::_internal_set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + assert(::mozilla::safebrowsing::ThreatEntryType_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.threat_entry_type_ = value; +} +inline void ThreatListDescriptor::set_threat_entry_type(::mozilla::safebrowsing::ThreatEntryType value) { + _internal_set_threat_entry_type(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.ThreatListDescriptor.threat_entry_type) +} + +// ------------------------------------------------------------------- + +// ListThreatListsResponse + +// repeated .mozilla.safebrowsing.ThreatListDescriptor threat_lists = 1; +inline int ListThreatListsResponse::_internal_threat_lists_size() const { + return _impl_.threat_lists_.size(); +} +inline int ListThreatListsResponse::threat_lists_size() const { + return _internal_threat_lists_size(); +} +inline void ListThreatListsResponse::clear_threat_lists() { + _impl_.threat_lists_.Clear(); +} +inline ::mozilla::safebrowsing::ThreatListDescriptor* ListThreatListsResponse::mutable_threat_lists(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.safebrowsing.ListThreatListsResponse.threat_lists) + return _impl_.threat_lists_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatListDescriptor >* +ListThreatListsResponse::mutable_threat_lists() { + // @@protoc_insertion_point(field_mutable_list:mozilla.safebrowsing.ListThreatListsResponse.threat_lists) + return &_impl_.threat_lists_; +} +inline const ::mozilla::safebrowsing::ThreatListDescriptor& ListThreatListsResponse::_internal_threat_lists(int index) const { + return _impl_.threat_lists_.Get(index); +} +inline const ::mozilla::safebrowsing::ThreatListDescriptor& ListThreatListsResponse::threat_lists(int index) const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.ListThreatListsResponse.threat_lists) + return _internal_threat_lists(index); +} +inline ::mozilla::safebrowsing::ThreatListDescriptor* ListThreatListsResponse::_internal_add_threat_lists() { + return _impl_.threat_lists_.Add(); +} +inline ::mozilla::safebrowsing::ThreatListDescriptor* ListThreatListsResponse::add_threat_lists() { + ::mozilla::safebrowsing::ThreatListDescriptor* _add = _internal_add_threat_lists(); + // @@protoc_insertion_point(field_add:mozilla.safebrowsing.ListThreatListsResponse.threat_lists) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::safebrowsing::ThreatListDescriptor >& +ListThreatListsResponse::threat_lists() const { + // @@protoc_insertion_point(field_list:mozilla.safebrowsing.ListThreatListsResponse.threat_lists) + return _impl_.threat_lists_; +} + +// ------------------------------------------------------------------- + +// Duration + +// optional int64 seconds = 1; +inline bool Duration::_internal_has_seconds() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool Duration::has_seconds() const { + return _internal_has_seconds(); +} +inline void Duration::clear_seconds() { + _impl_.seconds_ = int64_t{0}; + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline int64_t Duration::_internal_seconds() const { + return _impl_.seconds_; +} +inline int64_t Duration::seconds() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.Duration.seconds) + return _internal_seconds(); +} +inline void Duration::_internal_set_seconds(int64_t value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.seconds_ = value; +} +inline void Duration::set_seconds(int64_t value) { + _internal_set_seconds(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.Duration.seconds) +} + +// optional int32 nanos = 2; +inline bool Duration::_internal_has_nanos() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool Duration::has_nanos() const { + return _internal_has_nanos(); +} +inline void Duration::clear_nanos() { + _impl_.nanos_ = 0; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline int32_t Duration::_internal_nanos() const { + return _impl_.nanos_; +} +inline int32_t Duration::nanos() const { + // @@protoc_insertion_point(field_get:mozilla.safebrowsing.Duration.nanos) + return _internal_nanos(); +} +inline void Duration::_internal_set_nanos(int32_t value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.nanos_ = value; +} +inline void Duration::set_nanos(int32_t value) { + _internal_set_nanos(value); + // @@protoc_insertion_point(field_set:mozilla.safebrowsing.Duration.nanos) +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace safebrowsing +} // namespace mozilla + +PROTOBUF_NAMESPACE_OPEN + +template <> struct is_proto_enum< ::mozilla::safebrowsing::FetchThreatListUpdatesResponse_ListUpdateResponse_ResponseType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::safebrowsing::ThreatHit_ThreatSourceType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::safebrowsing::ChromeClientInfo_SafeBrowsingReportingPopulation> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::safebrowsing::ThreatType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::safebrowsing::PlatformType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::safebrowsing::CompressionType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::safebrowsing::ThreatEntryType> : ::std::true_type {}; + +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) + +#include <google/protobuf/port_undef.inc> +#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_safebrowsing_2eproto diff --git a/toolkit/components/url-classifier/chromium/safebrowsing.proto b/toolkit/components/url-classifier/chromium/safebrowsing.proto new file mode 100644 index 0000000000..207429bbd6 --- /dev/null +++ b/toolkit/components/url-classifier/chromium/safebrowsing.proto @@ -0,0 +1,540 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file includes Safe Browsing V4 API blacklist request and response +// protocol buffers. They should be kept in sync with the server implementation. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package mozilla.safebrowsing; + +message ThreatInfo { + // The threat types to be checked. + repeated ThreatType threat_types = 1; + + // The platform types to be checked. + repeated PlatformType platform_types = 2; + + // The entry types to be checked. + repeated ThreatEntryType threat_entry_types = 4; + + // The threat entries to be checked. + repeated ThreatEntry threat_entries = 3; +} + +// A match when checking a threat entry in the Safe Browsing threat lists. +message ThreatMatch { + // The threat type matching this threat. + optional ThreatType threat_type = 1; + + // The platform type matching this threat. + optional PlatformType platform_type = 2; + + // The threat entry type matching this threat. + optional ThreatEntryType threat_entry_type = 6; + + // The threat matching this threat. + optional ThreatEntry threat = 3; + + // Optional metadata associated with this threat. + optional ThreatEntryMetadata threat_entry_metadata = 4; + + // The cache lifetime for the returned match. Clients must not cache this + // response for more than this duration to avoid false positives. + optional Duration cache_duration = 5; +} + +// Request to check entries against lists. +message FindThreatMatchesRequest { + // The client metadata. + optional ClientInfo client = 1; + + // The lists and entries to be checked for matches. + optional ThreatInfo threat_info = 2; +} + +// Response type for requests to find threat matches. +message FindThreatMatchesResponse { + // The threat list matches. + repeated ThreatMatch matches = 1; +} + +// Describes a Safe Browsing API update request. Clients can request updates for +// multiple lists in a single request. +message FetchThreatListUpdatesRequest { + // The client metadata. + optional ClientInfo client = 1; + + // A single list update request. + message ListUpdateRequest { + // The type of threat posed by entries present in the list. + optional ThreatType threat_type = 1; + + // The type of platform at risk by entries present in the list. + optional PlatformType platform_type = 2; + + // The types of entries present in the list. + optional ThreatEntryType threat_entry_type = 5; + + // The current state of the client for the requested list (the encrypted + // ClientState that was sent to the client from the previous update + // request). + optional bytes state = 3; + + // The constraints for this update. + message Constraints { + // The maximum size in number of entries. The update will not contain more + // entries than this value. This should be a power of 2 between 2**10 and + // 2**20. If zero, no update size limit is set. + optional int32 max_update_entries = 1; + + // Sets the maxmimum number of entries that the client is willing to have + // in the local database. This should be a power of 2 between 2**10 and + // 2**20. If zero, no database size limit is set. + optional int32 max_database_entries = 2; + + // Requests the list for a specific geographic location. If not set the + // server may pick that value based on the user's IP address. Expects ISO + // 3166-1 alpha-2 format. + optional string region = 3; + + // The compression types supported by the client. + repeated CompressionType supported_compressions = 4; + } + + // The constraints associated with this request. + optional Constraints constraints = 4; + } + + // The requested threat list updates. + repeated ListUpdateRequest list_update_requests = 3; + + // Chrome-specific client information. + optional ChromeClientInfo chrome_client_info = 4; +} + +// Response type for threat list update requests. +message FetchThreatListUpdatesResponse { + // An update to an individual list. + message ListUpdateResponse { + // The threat type for which data is returned. + optional ThreatType threat_type = 1; + + // The format of the threats. + optional ThreatEntryType threat_entry_type = 2; + + // The platform type for which data is returned. + optional PlatformType platform_type = 3; + + // The type of response sent to the client. + enum ResponseType { + // Unknown. + RESPONSE_TYPE_UNSPECIFIED = 0; + + // Partial updates are applied to the client's existing local database. + PARTIAL_UPDATE = 1; + + // Full updates replace the client's entire local database. This means + // that either the client was seriously out-of-date or the client is + // believed to be corrupt. + FULL_UPDATE = 2; + } + + // The type of response. This may indicate that an action is required by the + // client when the response is received. + optional ResponseType response_type = 4; + + // A set of entries to add to a local threat type's list. Repeated to allow + // for a combination of compressed and raw data to be sent in a single + // response. + repeated ThreatEntrySet additions = 5; + + // A set of entries to remove from a local threat type's list. In practice, + // this field is empty or contains exactly one ThreatEntrySet. + repeated ThreatEntrySet removals = 6; + + // The new client state, in encrypted format. Opaque to clients. + optional bytes new_client_state = 7; + + // The expected SHA256 hash of the client state; that is, of the sorted list + // of all hashes present in the database after applying the provided update. + // If the client state doesn't match the expected state, the client must + // disregard this update and retry later. + optional Checksum checksum = 8; + } + + // The list updates requested by the clients. + repeated ListUpdateResponse list_update_responses = 1; + + // The minimum duration the client must wait before issuing any update + // request. If this field is not set clients may update as soon as they want. + optional Duration minimum_wait_duration = 2; +} + +// Request to return full hashes matched by the provided hash prefixes. +message FindFullHashesRequest { + // The client metadata. + optional ClientInfo client = 1; + + // The current client states for each of the client's local threat lists. + repeated bytes client_states = 2; + + // The lists and hashes to be checked. + optional ThreatInfo threat_info = 3; +} + +// Response type for requests to find full hashes. +message FindFullHashesResponse { + // The full hashes that matched the requested prefixes. + repeated ThreatMatch matches = 1; + + // The minimum duration the client must wait before issuing any find hashes + // request. If this field is not set, clients can issue a request as soon as + // they want. + optional Duration minimum_wait_duration = 2; + + // For requested entities that did not match the threat list, how long to + // cache the response. + optional Duration negative_cache_duration = 3; +} + +// A hit comprised of multiple resources; one is the threat list entry that was +// encountered by the client, while others give context as to how the client +// arrived at the unsafe entry. +message ThreatHit { + // The threat type reported. + optional ThreatType threat_type = 1; + + // The platform type reported. + optional PlatformType platform_type = 2; + + // The threat entry responsible for the hit. Full hash should be reported for + // hash-based hits. + optional ThreatEntry entry = 3; + + // Types of resources reported by the client as part of a single hit. + enum ThreatSourceType { + // Unknown. + THREAT_SOURCE_TYPE_UNSPECIFIED = 0; + // The URL that matched the threat list (for which GetFullHash returned a + // valid hash). + MATCHING_URL = 1; + // The final top-level URL of the tab that the client was browsing when the + // match occurred. + TAB_URL = 2; + // A redirect URL that was fetched before hitting the final TAB_URL. + TAB_REDIRECT = 3; + // A resource loaded within the final TAB_URL. + TAB_RESOURCE = 4; + } + + // A single resource related to a threat hit. + message ThreatSource { + // The URL of the resource. + optional string url = 1; + + // The type of source reported. + optional ThreatSourceType type = 2; + + // The remote IP of the resource in ASCII format. Either IPv4 or IPv6. + optional string remote_ip = 3; + + // Referrer of the resource. Only set if the referrer is available. + optional string referrer = 4; + } + + // The resources related to the threat hit. + repeated ThreatSource resources = 4; + + // Client-reported identification. + optional ClientInfo client_info = 5; + + // Details about the user that encountered the threat. + message UserInfo { + // The UN M.49 region code associated with the user's location. + optional string region_code = 1; + + // Unique ID stable over a week or two + optional bytes user_id = 2; + } + + // Details about the user that encountered the threat. + optional UserInfo user_info = 6; +} + +// Types of threats. +enum ThreatType { + // Unknown. + THREAT_TYPE_UNSPECIFIED = 0; + + // Malware threat type. + MALWARE_THREAT = 1; + + // Social engineering threat type. + SOCIAL_ENGINEERING_PUBLIC = 2; + + // Unwanted software threat type. + UNWANTED_SOFTWARE = 3; + + // Potentially harmful application threat type. + POTENTIALLY_HARMFUL_APPLICATION = 4; + + // Social engineering threat type for internal use. + SOCIAL_ENGINEERING = 5; + + // API abuse threat type. + API_ABUSE = 6; + + // Malicious binary threat type. + MALICIOUS_BINARY = 7; + + // Client side detection whitelist threat type. + CSD_WHITELIST = 8; + + // Client side download detection whitelist threat type. + CSD_DOWNLOAD_WHITELIST = 9; + + // Client incident threat type. + CLIENT_INCIDENT = 10; + + // Patterns to be used for activating the subresource filter. Interstitial + // will not be shown for patterns from this list. + SUBRESOURCE_FILTER = 13; +} + +// Types of platforms. +enum PlatformType { + // Unknown platform. + PLATFORM_TYPE_UNSPECIFIED = 0; + + // Threat posed to Windows. + WINDOWS_PLATFORM = 1; + + // Threat posed to Linux. + LINUX_PLATFORM = 2; + + // Threat posed to Android. + // This cannot be ANDROID because that symbol is defined for android builds + // here: build/config/android/BUILD.gn line21. + ANDROID_PLATFORM = 3; + + // Threat posed to OSX. + OSX_PLATFORM = 4; + + // Threat posed to iOS. + IOS_PLATFORM = 5; + + // Threat posed to at least one of the defined platforms. + ANY_PLATFORM = 6; + + // Threat posed to all defined platforms. + ALL_PLATFORMS = 7; + + // Threat posed to Chrome. + CHROME_PLATFORM = 8; +} + +// The client metadata associated with Safe Browsing API requests. +message ClientInfo { + // A client ID that (hopefully) uniquely identifies the client implementation + // of the Safe Browsing API. + optional string client_id = 1; + + // The version of the client implementation. + optional string client_version = 2; +} + +// The client metadata associated with Safe Browsing API requests specific to +// users of Chrome. +message ChromeClientInfo { + // Safe Browsing reporting populations in Chrome. + enum SafeBrowsingReportingPopulation { + // Unspecified reporting verbosity. + UNSPECIFIED = 0; + + // Client is opted out of reporting. + OPT_OUT = 1; + + // Legacy extended reporting population. + EXTENDED = 2; + + // Scout reporting population. + SCOUT = 3; + } + + // The reporting population of the user. + optional SafeBrowsingReportingPopulation reporting_population = 1; +} + +// The expected state of a client's local database. +message Checksum { + // The SHA256 hash of the client state; that is, of the sorted list of all + // hashes present in the database. + optional bytes sha256 = 1; +} + +// The ways in which threat entry sets can be compressed. +enum CompressionType { + // Unknown. + COMPRESSION_TYPE_UNSPECIFIED = 0; + + // Raw, uncompressed data. + RAW = 1; + + // Rice-Golomb encoded data. + RICE = 2; +} + +// An individual threat; for example, a malicious URL or its hash +// representation. Only one of these fields should be set. +message ThreatEntry { + // A variable-length SHA256 hash with size between 4 and 32 bytes inclusive. + optional bytes hash = 1; + + // A URL. + optional string url = 2; +} + +// Types of entries that pose threats. Threat lists are collections of entries +// of a single type. +enum ThreatEntryType { + // Unspecified. + THREAT_ENTRY_TYPE_UNSPECIFIED = 0; + + // A host-suffix/path-prefix URL expression; for example, "foo.bar.com/baz/". + URL = 1; + + // An executable program. + EXECUTABLE = 2; + + // An IP range. + IP_RANGE = 3; + + // Chrome extension. + CHROME_EXTENSION = 4; + + // Filename. + FILENAME = 5; + + // CERT. + CERT = 6; +} + +// A set of threats that should be added or removed from a client's local +// database. +message ThreatEntrySet { + // The compression type for the entries in this set. + optional CompressionType compression_type = 1; + + // At most one of the following fields should be set. + + // The raw SHA256-formatted entries. + optional RawHashes raw_hashes = 2; + + // The raw removal indices for a local list. + optional RawIndices raw_indices = 3; + + // The encoded 4-byte prefixes of SHA256-formatted entries, using a + // Golomb-Rice encoding. + optional RiceDeltaEncoding rice_hashes = 4; + + // The encoded local, lexicographically-sorted list indices, using a + // Golomb-Rice encoding. Used for sending compressed removal indicies. + optional RiceDeltaEncoding rice_indices = 5; +} + +// A set of raw indicies to remove from a local list. +message RawIndices { + // The indicies to remove from a lexicographically-sorted local list. + repeated int32 indices = 1; +} + +// The uncompressed threat entries in hash format of a particular prefix length. +// Hashes can be anywhere from 4 to 32 bytes in size. A large majority are 4 +// bytes, but some hashes are lengthened if they collide with the hash of a +// popular URL. +// +// Used for sending ThreatEntrySet to clients that do not support compression, +// or when sending non-4-byte hashes to clients that do support compression. +message RawHashes { + // The number of bytes for each prefix encoded below. This field can be + // anywhere from 4 (shortest prefix) to 32 (full SHA256 hash). + optional int32 prefix_size = 1; + + // The hashes, all concatenated into one long string. Each hash has a prefix + // size of |prefix_size| above. Hashes are sorted in lexicographic order. + optional bytes raw_hashes = 2; +} + +// The Rice-Golomb encoded data. Used for sending compressed 4-byte hashes or +// compressed removal indices. +message RiceDeltaEncoding { + // The offset of the first entry in the encoded data, or, if only a single + // integer was encoded, that single integer's value. + optional int64 first_value = 1; + + // The Golomb-Rice parameter which is a number between 2 and 28. This field + // is missing (that is, zero) if num_entries is zero. + optional int32 rice_parameter = 2; + + // The number of entries that are delta encoded in the encoded data. If only a + // single integer was encoded, this will be zero and the single value will be + // stored in first_value. + optional int32 num_entries = 3; + + // The encoded deltas that are encoded using the Golomb-Rice coder. + optional bytes encoded_data = 4; +} + +// The metadata associated with a specific threat entry. The client is expected +// to know the metadata key/value pairs associated with each threat type. +message ThreatEntryMetadata { + // A single metadata entry. + message MetadataEntry { + // The metadata entry key. + optional bytes key = 1; + + // The metadata entry value. + optional bytes value = 2; + } + + // The metadata entries. + repeated MetadataEntry entries = 1; +} + +// Describes an individual threat list. A list is defined by three parameters: +// the type of threat posed, the type of platform targeted by the threat, and +// the type of entries in the list. +message ThreatListDescriptor { + // The threat type posed by the list's entries. + optional ThreatType threat_type = 1; + + // The platform type targeted by the list's entries. + optional PlatformType platform_type = 2; + + // The entry types contained in the list. + optional ThreatEntryType threat_entry_type = 3; +} + +// A collection of lists available for download. +message ListThreatListsResponse { + // The lists available for download. + repeated ThreatListDescriptor threat_lists = 1; +} + +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. + optional int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + optional int32 nanos = 2; +} diff --git a/toolkit/components/url-classifier/components.conf b/toolkit/components/url-classifier/components.conf new file mode 100644 index 0000000000..1be90b11e7 --- /dev/null +++ b/toolkit/components/url-classifier/components.conf @@ -0,0 +1,32 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{26a4a019-2827-4a89-a85c-5931a678823a}', + 'contract_ids': ['@mozilla.org/url-classifier/jslib;1'], + 'esModule': 'resource://gre/modules/UrlClassifierLib.sys.mjs', + 'constructor': 'UrlClassifierLib', + }, + { + 'cid': '{ca168834-cc00-48f9-b83c-fd018e58cae3}', + 'contract_ids': ['@mozilla.org/url-classifier/listmanager;1'], + 'esModule': 'resource://gre/modules/UrlClassifierListManager.sys.mjs', + 'constructor': 'RegistrationData', + }, + { + 'cid': '{9111de73-9322-4bfc-8b65-2b727f3e6ec8}', + 'contract_ids': ['@mozilla.org/url-classifier/hashcompleter;1'], + 'esModule': 'resource://gre/modules/UrlClassifierHashCompleter.sys.mjs', + 'constructor': 'HashCompleter', + }, + { + 'cid': '{1980624c-c50b-4b46-a91c-dfaba7792706}', + 'contract_ids': ['@mozilla.org/url-classifier/list-service;1'], + 'esModule': 'resource://gre/modules/UrlClassifierRemoteSettingsService.sys.mjs', + 'constructor': 'UrlClassifierRemoteSettingsService', + }, +] diff --git a/toolkit/components/url-classifier/docs/flash-block-lists.rst b/toolkit/components/url-classifier/docs/flash-block-lists.rst new file mode 100644 index 0000000000..f24fc0d2c0 --- /dev/null +++ b/toolkit/components/url-classifier/docs/flash-block-lists.rst @@ -0,0 +1,38 @@ +========================= +List Based Flash Blocking +========================= + +List based Flash blocking currently uses six lists. +The lists specify what domains/subdomains Flash is allowed to or denied from loading on. +The domains specified by the lists indicate the domain of the document that the Flash is loaded in, not the domain hosting the Flash content itself. + +* Allow List +* Allow Exceptions List +* Deny List +* Deny Exceptions List +* Sub-Document Deny List +* Sub-Document Deny Exceptions List + +If a page is on a list and the corresponding "Exceptions List", it is treated as though it is not on that list. + +Classification +============== + +Documents can be classified as Allow, Deny or Unknown. +Documents with an Allow classification may load Flash normally. +Documents with a Deny classification may not load Flash at all. +A Deny classification overrides an Allow classification. +The Unknown classification is the fall-through classification; it essentially just means that the document did not receive an Allow or Deny classification. +Documents with an Unknown classification will have Flash set to Click To Activate. + +If the document is at the top level (its address is in the URL bar), then the Deny List is checked first followed by the Allow List to determine its classification. + +If the document is not at the top level, it will receive a Deny classification if the classification of the parent document is Deny or if the document is on the Deny List. +It will also receive a Deny classification if the sub-document is not same-origin and the document is on the Sub-Document Deny List. +If the document did not receive a Deny classification, it can receive an Allow classification if it is on the Allow List or if the parent document received an Allow classification. + +If for any reason, the document has a null principal, it will receive a Deny classification. +Some examples of documents that would have a null principal are: + +* Data URIs <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs> loaded directly from the URL bar. Data URIs loaded by a page should inherit the loading page's permissions. +* URIs that are rendered with the JSON viewer diff --git a/toolkit/components/url-classifier/docs/index.rst b/toolkit/components/url-classifier/docs/index.rst new file mode 100644 index 0000000000..01f0f58ba3 --- /dev/null +++ b/toolkit/components/url-classifier/docs/index.rst @@ -0,0 +1,8 @@ +============== +URL Classifier +============== + +.. toctree:: + :maxdepth: 1 + + flash-block-lists diff --git a/toolkit/components/url-classifier/moz.build b/toolkit/components/url-classifier/moz.build new file mode 100644 index 0000000000..3b1431dc2c --- /dev/null +++ b/toolkit/components/url-classifier/moz.build @@ -0,0 +1,101 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Safe Browsing") + +TEST_DIRS += ["tests"] + +XPIDL_SOURCES += [ + "IUrlClassifierUITelemetry.idl", + "nsIUrlClassifierDBService.idl", + "nsIUrlClassifierHashCompleter.idl", + "nsIUrlClassifierInfo.idl", + "nsIUrlClassifierPrefixSet.idl", + "nsIUrlClassifierRemoteSettingsService.idl", + "nsIUrlClassifierStreamUpdater.idl", + "nsIUrlClassifierUtils.idl", + "nsIUrlListManager.idl", +] + +XPIDL_MODULE = "url-classifier" + +DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True +DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True + +UNIFIED_SOURCES += [ + "chromium/safebrowsing.pb.cc", + "ChunkSet.cpp", + "Classifier.cpp", + "LookupCache.cpp", + "LookupCacheV4.cpp", + "nsCheckSummedOutputStream.cpp", + "nsUrlClassifierDBService.cpp", + "nsUrlClassifierInfo.cpp", + "nsUrlClassifierProxies.cpp", + "nsUrlClassifierUtils.cpp", + "ProtocolParser.cpp", + "RiceDeltaDecoder.cpp", + "UrlClassifierTelemetryUtils.cpp", +] + +# define conflicting LOG() macros +SOURCES += [ + "nsUrlClassifierPrefixSet.cpp", + "nsUrlClassifierStreamUpdater.cpp", + "VariableLengthPrefixSet.cpp", +] + +# contains variables that conflict with LookupCache.cpp +SOURCES += [ + "HashStore.cpp", +] + +EXTRA_JS_MODULES += [ + "SafeBrowsing.sys.mjs", + "UrlClassifierHashCompleter.sys.mjs", + "UrlClassifierLib.sys.mjs", + "UrlClassifierListManager.sys.mjs", + "UrlClassifierRemoteSettingsService.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "chromium/safebrowsing.pb.h", + "Entries.h", + "LookupCache.h", + "LookupCacheV4.h", + "nsUrlClassifierPrefixSet.h", + "VariableLengthPrefixSet.h", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "../build", + "/xpcom/io", +] + +CXXFLAGS += CONFIG["SQLITE_CFLAGS"] + +# Suppress warnings in third-party code. +if CONFIG["CC_TYPE"] == "gcc": + CXXFLAGS += [ + "-Wno-maybe-uninitialized", + ] + +if CONFIG["NIGHTLY_BUILD"] or CONFIG["MOZ_DEBUG"]: + DEFINES["MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES"] = True + +SPHINX_TREES["url-classifier"] = "docs" + +with Files("docs/**"): + SCHEDULES.exclusive = ["docs"] + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp b/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp new file mode 100644 index 0000000000..472dd71390 --- /dev/null +++ b/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp @@ -0,0 +1,87 @@ +//* -*- 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 "nsCRT.h" +#include "nsComponentManagerUtils.h" +#include "nsISupportsImpl.h" +#include "nsCheckSummedOutputStream.h" +#include "crc32c.h" + +//////////////////////////////////////////////////////////////////////////////// +// nsCheckSummedOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsCheckSummedOutputStream, nsBufferedOutputStream, + nsISafeOutputStream) + +NS_IMETHODIMP +nsCheckSummedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize) { + nsresult rv; + mHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mHash->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + return nsBufferedOutputStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsCheckSummedOutputStream::Finish() { + nsresult rv = mHash->Finish(false, mCheckSum); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t written; + rv = nsBufferedOutputStream::Write( + reinterpret_cast<const char*>(mCheckSum.BeginReading()), + mCheckSum.Length(), &written); + NS_ASSERTION(written == mCheckSum.Length(), "Error writing stream checksum"); + NS_ENSURE_SUCCESS(rv, rv); + + return nsBufferedOutputStream::Finish(); +} + +NS_IMETHODIMP +nsCheckSummedOutputStream::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = mHash->Update(reinterpret_cast<const uint8_t*>(buf), count); + NS_ENSURE_SUCCESS(rv, rv); + + return nsBufferedOutputStream::Write(buf, count, result); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsCrc32CheckSumedOutputStream +NS_IMPL_ISUPPORTS_INHERITED(nsCrc32CheckSumedOutputStream, + nsBufferedOutputStream, nsISafeOutputStream) + +NS_IMETHODIMP +nsCrc32CheckSumedOutputStream::Init(nsIOutputStream* stream, + uint32_t bufferSize) { + mCheckSum = ~0; + + return nsBufferedOutputStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsCrc32CheckSumedOutputStream::Finish() { + uint32_t written; + nsresult rv = nsBufferedOutputStream::Write( + reinterpret_cast<const char*>(&mCheckSum), sizeof(mCheckSum), &written); + NS_ASSERTION(written == sizeof(mCheckSum), "Error writing stream checksum"); + NS_ENSURE_SUCCESS(rv, rv); + + return nsBufferedOutputStream::Finish(); +} + +NS_IMETHODIMP +nsCrc32CheckSumedOutputStream::Write(const char* buf, uint32_t count, + uint32_t* result) { + mCheckSum = + ComputeCrc32c(mCheckSum, reinterpret_cast<const uint8_t*>(buf), count); + + return nsBufferedOutputStream::Write(buf, count, result); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/toolkit/components/url-classifier/nsCheckSummedOutputStream.h b/toolkit/components/url-classifier/nsCheckSummedOutputStream.h new file mode 100644 index 0000000000..0aa1324024 --- /dev/null +++ b/toolkit/components/url-classifier/nsCheckSummedOutputStream.h @@ -0,0 +1,88 @@ +//* -*- 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/. */ + +#ifndef nsCheckSummedOutputStream_h__ +#define nsCheckSummedOutputStream_h__ + +#include "nsIFile.h" +#include "nsIOutputStream.h" +#include "nsICryptoHash.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "../../../netwerk/base/nsBufferedStreams.h" +#include "prio.h" + +class nsCheckSummedOutputStream : public nsBufferedOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + + // Size of MD5 hash in bytes + static const uint32_t CHECKSUM_SIZE = 16; + static const uint32_t MAX_BUFFER_SIZE = 64 * 1024; + + nsCheckSummedOutputStream() = default; + + NS_IMETHOD Finish() override; + NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override; + NS_IMETHOD Init(nsIOutputStream* stream, uint32_t bufferSize) override; + + protected: + virtual ~nsCheckSummedOutputStream() { nsBufferedOutputStream::Close(); } + + nsCOMPtr<nsICryptoHash> mHash; + nsCString mCheckSum; +}; + +// returns a file output stream which can be QI'ed to nsIFileOutputStream. +inline nsresult NS_NewCheckSummedOutputStream(nsIOutputStream** result, + nsIFile* file) { + nsCOMPtr<nsIOutputStream> localOutFile; + nsresult rv = + NS_NewSafeLocalFileOutputStream(getter_AddRefs(localOutFile), file, + PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIBufferedOutputStream> out = new nsCheckSummedOutputStream(); + rv = out->Init(localOutFile, nsCheckSummedOutputStream::MAX_BUFFER_SIZE); + if (NS_SUCCEEDED(rv)) { + out.forget(result); + } + return rv; +} + +class nsCrc32CheckSumedOutputStream : public nsBufferedOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + + static const uint32_t CHECKSUM_SIZE = 4; + + nsCrc32CheckSumedOutputStream() = default; + + NS_IMETHOD Finish() override; + NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override; + NS_IMETHOD Init(nsIOutputStream* stream, uint32_t bufferSize) override; + + protected: + virtual ~nsCrc32CheckSumedOutputStream() { nsBufferedOutputStream::Close(); } + + uint32_t mCheckSum; +}; + +inline nsresult NS_NewCrc32OutputStream( + nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutput, + uint32_t aBufferSize) { + nsCOMPtr<nsIOutputStream> out = std::move(aOutput); + + nsCOMPtr<nsIBufferedOutputStream> bufferOutput = + new nsCrc32CheckSumedOutputStream(); + nsresult rv = bufferOutput->Init(out, aBufferSize); + if (NS_SUCCEEDED(rv)) { + bufferOutput.forget(aResult); + } + return rv; +} + +#endif diff --git a/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl new file mode 100644 index 0000000000..5a20360672 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl @@ -0,0 +1,245 @@ +/* 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 "nsISupports.idl" + +%{C++ +#include "Entries.h" +#include "LookupCache.h" +#include "mozilla/UniquePtr.h" +%} +native ResultArray(mozilla::UniquePtr<mozilla::safebrowsing::LookupResultArray>); + +interface nsIUrlClassifierHashCompleter; +interface nsIPrincipal; + +// Interface for JS function callbacks +[scriptable, function, uuid(4ca27b6b-a674-4b3d-ab30-d21e2da2dffb)] +interface nsIUrlClassifierCallback : nsISupports { + void handleEvent(in ACString value); +}; + +/** + * The nsIUrlClassifierUpdateObserver interface is implemented by + * clients streaming updates to the url-classifier (usually + * nsUrlClassifierStreamUpdater. + */ +[scriptable, uuid(9fa11561-5816-4e1b-bcc9-b629ca05cce6)] +interface nsIUrlClassifierUpdateObserver : nsISupports { + /** + * The update requested a new URL whose contents should be downloaded + * and sent to the classifier as a new stream. + * + * @param url The url that was requested. + * @param table The table name that this URL's contents will be associated + * with. This should be passed back to beginStream(). + */ + void updateUrlRequested(in ACString url, + in ACString table); + + /** + * A stream update has completed. + * + * @param status The state of the update process. + * @param delay The amount of time the updater should wait to fetch the + * next URL in ms. + */ + void streamFinished(in nsresult status, in unsigned long delay); + + /* The update has encountered an error and should be cancelled */ + void updateError(in nsresult error); + + /** + * The update has completed successfully. + * + * @param requestedTimeout The number of seconds that the caller should + * wait before trying to update again. + **/ + 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(7a258022-6765-11e5-b379-b37b1f2354be)] +interface nsIUrlClassifierDBService : nsISupports +{ + /** + * 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 their meta info in the following format: + * + * tablename;[metadata]\n + * tablename2;[metadata]\n + * + * For v2 tables, the metadata is the chunks info such as + * + * goog-phish-shavar;a:10,14,30-40s:56,67 + * goog-unwanted-shavar;a:1-3,5 + * + * For v4 tables, base64 encoded state is currently the only info in the + * metadata (can be extended whenever necessary). For exmaple, + * + * goog-phish-proto;Cg0IARAGGAEiAzAwMTABEKqTARoCGAjT1gDD:oCGAjT1gDD\n + * goog-malware-proto;Cg0IAhAGGAEiAzAwMTABENCQARoCGAjx5Yty:BENCQARoCGAj\n + * + * Note that the metadata is colon-separated. + * + */ + void getTables(in nsIUrlClassifierCallback c); + + /** + * Set the nsIUrlClassifierCompleter object for a given table. This + * object will be used to request complete versions of partial + * hashes. + */ + void setHashCompleter(in ACString tableName, + in nsIUrlClassifierHashCompleter completer); + + /** + * Forget the results that were used in the last DB update. + */ + void clearLastResults(); + + //////////////////////////////////////////////////////////////////////////// + // Incremental update methods. + // + // An update to the database has the following steps: + // + // 1) The update process is started with beginUpdate(). The client + // passes an nsIUrlClassifierUpdateObserver object which will be + // notified as the update is processed by the dbservice. + // 2) The client sends an initial update stream to the dbservice, + // using beginStream/updateStream/finishStream. + // 3) While reading this initial update stream, the dbservice may + // request additional streams from the client as requested by the + // update stream. + // 4) For each additional update stream, the client feeds the + // contents to the dbservice using beginStream/updateStream/endStream. + // 5) Once all streams have been processed, the client calls + // finishUpdate. When the dbservice has finished processing + // all streams, it will notify the observer that the update process + // is complete. + + /** + * Begin an update process. Will throw NS_ERROR_NOT_AVAILABLE if there + * is already an update in progress. + * + * @param updater The update observer tied to this update. + * @param tables A comma-separated list of tables included in this update. + */ + void beginUpdate(in nsIUrlClassifierUpdateObserver updater, + in ACString tables); + + /** + * Begin a stream update. This should be called once per url being + * fetched. + * + * @param table The table the contents of this stream will be associated + * with, or empty for the initial stream. + */ + void beginStream(in ACString table); + + /** + * Update the table incrementally. + */ + void updateStream(in ACString updateChunk); + + // It would be nice to have an updateFromStream method to round out the + // interface, but it's tricky because of XPCOM proxies. + + /** + * Finish an individual stream update. Must be called for every + * beginStream() call, before the next beginStream() or finishUpdate(). + * + * The update observer's streamFinished will be called once the + * stream has been processed. + */ + void finishStream(); + + /** + * Finish an incremental update. This will attempt to commit any + * pending changes and resets the update interface. + * + * The update observer's updateSucceeded or updateError methods + * will be called when the update has been processed. + */ + void finishUpdate(); + + /** + * Cancel an incremental update. This rolls back any pending changes. + * and resets the update interface. + * + * The update observer's updateError method will be called when the + * update has been rolled back. + */ + void cancelUpdate(); + + /** + * Reset the url-classifier database. This call will delete the existing + * database, emptying all tables. Mostly intended for use in unit tests. + */ + void resetDatabase(); + + /** + * Reload he url-classifier database. This will empty all cache for + * completions from gethash, and reload it from database. Mostly intended + * for use in tests. + */ + void reloadDatabase(); + + /** + * Empty all the caches. + */ + void clearCache(); +}; + +/** + * This is an internal helper interface for communication between the + * main thread and the dbservice worker thread. It is called for each + * lookup to provide a set of possible results, which the main thread + * may need to expand using an nsIUrlClassifierCompleter. + */ +[uuid(b903dc8f-dff1-42fe-894b-36e7a59bb801)] +interface nsIUrlClassifierLookupCallback : nsISupports +{ + /** + * The lookup process is complete. + * + * @param results + * If this parameter is null, there were no results found. + * If not, it contains an array of nsUrlClassifierEntry objects + * with possible matches. The callee is responsible for freeing + * this array. + */ + void lookupComplete(in ResultArray results); +}; + +/** + * This is an internal helper interface which is called after each + * classify completes to provide and handle a set of possible results, + * which the main thread may need to expand using an nsIURIClassifierCallback. + */ +[uuid(091adf98-28a5-473d-8dec-5b34b4e62496)] +interface nsIUrlClassifierClassifyCallback : nsISupports +{ + /** + * The function is called each time the URL matches a Safe Browsing list + * The function could be called multiple times if URL matches multiple lists + * + */ + void handleResult(in ACString aList, + in ACString aPrefix); +}; diff --git a/toolkit/components/url-classifier/nsIUrlClassifierHashCompleter.idl b/toolkit/components/url-classifier/nsIUrlClassifierHashCompleter.idl new file mode 100644 index 0000000000..64aaffae30 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierHashCompleter.idl @@ -0,0 +1,103 @@ +/* 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 "nsISupports.idl" + +interface nsIArray; + +/** + * This interface contains feilds in Matches object of FullHashResponse(V4). + * Reference from: + * https://developers.google.com/safe-browsing/v4/update-api#http-post-response_2 + */ +[scriptable, uuid(aabeb50e-d9f7-418e-9469-2cd9608958c0)] +interface nsIFullHashMatch : nsISupports +{ + readonly attribute ACString tableName; + + readonly attribute ACString fullHash; + + readonly attribute uint32_t cacheDuration; +}; + +/** + * This interface is implemented by nsIUrlClassifierHashCompleter clients. + */ +[scriptable, uuid(da16de40-df26-414d-bde7-c4faf4504868)] +interface nsIUrlClassifierHashCompleterCallback : nsISupports +{ + /** + * A complete hash has been found that matches the partial hash. + * This method may be called 0-n times for a given + * nsIUrlClassifierCompleter::complete() call. + * + * @param hash + * The 256-bit hash that was discovered. + * @param table + * The name of the table that this hash belongs to. + * @param chunkId + * The database chunk that this hash belongs to. + */ + void completionV2(in ACString hash, + in ACString table, + in uint32_t chunkId); + + /** + * This will be called when a fullhash response is received and parsed + * no matter if any full hash has been found. + * + * @param partialHash + * The hash that was sent for completion. + * @param table + * The name of the table that this hash belongs to. + * @param negativeCacheDuration + * The negative cache duration in millisecond. + * @param fullHashes + * Array of fullhashes that match the prefix. + */ + void completionV4(in ACString partialHash, + in ACString table, + in uint32_t negativeCacheDuration, + in nsIArray fullHashes); + + /** + * The completion is complete. This method is called once per + * nsIUrlClassifierCompleter::complete() call, after all completion() + * calls are finished. + * + * @param status + * NS_OK if the request completed successfully, or an error code. + */ + void completionFinished(in nsresult status); +}; + +/** + * Clients updating the url-classifier database have the option of sending + * partial (32-bit) hashes of URL fragments to be blocklisted. If the + * url-classifier encounters one of these truncated hashes, it will ask an + * nsIUrlClassifierCompleter instance to asynchronously provide the complete + * hash, along with some associated metadata. + * This is only ever used for testing and should absolutely be deleted (I + * think). + */ +[scriptable, uuid(231fb2ad-ea8a-4e63-a331-eafc3b434811)] +interface nsIUrlClassifierHashCompleter : nsISupports +{ + /** + * Request a completed hash from the given gethash url. + * + * @param partialHash + * The 32-bit hash encountered by the url-classifier. + * @param gethashUrl + * The gethash url to use. + * @param tableName + * The table where we matched the partial hash. + * @param callback + * An nsIUrlClassifierCompleterCallback instance. + */ + void complete(in ACString partialHash, + in ACString gethashUrl, + in ACString tableName, + in nsIUrlClassifierHashCompleterCallback callback); +}; diff --git a/toolkit/components/url-classifier/nsIUrlClassifierInfo.idl b/toolkit/components/url-classifier/nsIUrlClassifierInfo.idl new file mode 100644 index 0000000000..f57c66aee5 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierInfo.idl @@ -0,0 +1,80 @@ + /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + /* 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 "nsISupports.idl" +#include "nsIArray.idl" + +/** + * nsIUrlClassifierPositiveCacheEntry Represents a positive cache entry. + */ +[scriptable, uuid(b3c27f8c-7db8-4f3f-97a5-5a94d781e565)] +interface nsIUrlClassifierPositiveCacheEntry : nsISupports { + /** + * Fullhash for the positive cache entry. + */ + readonly attribute ACString fullhash; + + /** + * Positive cache expiry. + */ + readonly attribute long long expiry; +}; + +/** + * nsIUrlClassifierCacheEntry contains cache information for + * a given prefix. + */ +[scriptable, uuid(d6297907-8236-4126-adaf-c3aa239a0d40)] +interface nsIUrlClassifierCacheEntry : nsISupports { + /** + * Prefix for this cache entry. + */ + readonly attribute ACString prefix; + + /** + * Negative cache expiry. + */ + readonly attribute long long expiry; + + /** + * An array of nsIUrlClassifierPositiveCacheEntry, each item represents + * a positive cache entry with its fullhash and expiry. + */ + readonly attribute nsIArray matches; +}; + +/** + * Cache information for a given table. + */ +[scriptable, uuid(69384f24-d9c5-4462-b24e-351c69e3b46a)] +interface nsIUrlClassifierCacheInfo : nsISupports { + /** + * Table name. + */ + readonly attribute ACString table; + + /* + * An array of nsIUrlClassifierCacheEntry. + */ + readonly attribute nsIArray entries; +}; + +[scriptable, function, uuid(26e12ea4-14ff-4c77-858f-6745998b7659)] +interface nsIUrlClassifierGetCacheCallback : nsISupports { + + void onGetCacheComplete(in nsIUrlClassifierCacheInfo info); +}; + +/** + * Interface to query url-classifier information. + */ +[scriptable, uuid(411bbff4-1b88-4687-aa36-e2bbdd93f6e8)] +interface nsIUrlClassifierInfo : nsISupports { + /** + * An asynchronous call to return cache information for the table. + */ + void getCacheInfo(in ACString table, + in nsIUrlClassifierGetCacheCallback callback); +}; diff --git a/toolkit/components/url-classifier/nsIUrlClassifierPrefixSet.idl b/toolkit/components/url-classifier/nsIUrlClassifierPrefixSet.idl new file mode 100644 index 0000000000..6ccb5ae7d1 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierPrefixSet.idl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; tab-width: 2; 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 "nsISupports.idl" +#include "nsIFile.idl" + +// Note that the PrefixSet name is historical and we do properly support +// duplicated values, so it's really a Prefix Trie. +// All methods are thread-safe. +[scriptable, uuid(3d8579f0-75fa-4e00-ba41-38661d5b5d17)] +interface nsIUrlClassifierPrefixSet : nsISupports +{ + // Initialize the PrefixSet. Give it a name for memory reporting. + void init(in ACString aName); + // Fills the PrefixSet with the given array of prefixes. + // Can send an empty Array to clear the tree. + // Requires array to be sorted. + void setPrefixes([const, array, size_is(aLength)] in unsigned long aPrefixes, + in unsigned long aLength); + void getPrefixes(out unsigned long aCount, + [array, size_is(aCount), retval] out unsigned long aPrefixes); + // Do a lookup in the PrefixSet, return whether the value is present. + boolean contains(in unsigned long aPrefix); + boolean isEmpty(); +}; diff --git a/toolkit/components/url-classifier/nsIUrlClassifierRemoteSettingsService.idl b/toolkit/components/url-classifier/nsIUrlClassifierRemoteSettingsService.idl new file mode 100644 index 0000000000..dc38b8eba0 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierRemoteSettingsService.idl @@ -0,0 +1,32 @@ +/* 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 "nsISupports.idl" + +interface nsIStreamListener; + +/** + * A service that serves Safe Browsing list data (V2 protocol) via + * Remote Settings. + */ +[scriptable, uuid(26a445a4-0d00-4b20-ba5f-1297d3344a25)] +interface nsIUrlClassifierRemoteSettingsService : nsISupports +{ + /** + * Fetch the Safe Browsing list data from the service. The service + * returns the response data by simulating how the data is sent over a + * stream listener when a HTTP request is made. + * + * + * @param aPayload the request payload for list data request + * @param aListener An nsIStreamListener object + */ + void fetchList(in ACString aPayload, in nsIStreamListener aListener); + + /** + * Clear all data in the service. + * This API is for testing only. + */ + void clear(); +}; diff --git a/toolkit/components/url-classifier/nsIUrlClassifierStreamUpdater.idl b/toolkit/components/url-classifier/nsIUrlClassifierStreamUpdater.idl new file mode 100644 index 0000000000..50844d0e03 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierStreamUpdater.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" +#include "nsIUrlClassifierDBService.idl" + +/** + * This is a class to manage large table updates from the server. Rather than + * downloading the whole update and then updating the sqlite database, we + * update tables as the data is streaming in. + */ +[scriptable, uuid(e1797597-f4d6-4dd3-a1e1-745ad352cd80)] +interface nsIUrlClassifierStreamUpdater : nsISupports +{ + /** + * Try to download updates from updateUrl. If an update is already in + * progress, queues the requested update. This is used in nsIUrlListManager + * as well as in testing. + * @param aRequestTables Comma-separated list of tables included in this + * update. + * @param aRequestPayload The payload for the request. + * @param aIsPostRequest Whether the request should be sent by POST method. + * Should be 'true' for v2 usage. + * @param aUpdateUrl The plaintext url from which to request updates. + * @param aSuccessCallback Called after a successful update. + * @param aUpdateErrorCallback Called for problems applying the update + * @param aDownloadErrorCallback Called if we get an http error or a + * connection refused error. + */ + boolean downloadUpdates(in ACString aRequestTables, + in ACString aRequestPayload, + in boolean aIsPostRequest, + in ACString aUpdateUrl, + in nsIUrlClassifierCallback aSuccessCallback, + in nsIUrlClassifierCallback aUpdateErrorCallback, + in nsIUrlClassifierCallback aDownloadErrorCallback); +}; diff --git a/toolkit/components/url-classifier/nsIUrlClassifierUtils.idl b/toolkit/components/url-classifier/nsIUrlClassifierUtils.idl new file mode 100644 index 0000000000..d98340e9a7 --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlClassifierUtils.idl @@ -0,0 +1,158 @@ +/* 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 "nsISupports.idl" +/** + * Some utility methods used by the url classifier. + */ + +interface nsIURI; +interface nsIChannel; + +/** + * Interface for parseFindFullHashResponseV4 callback + */ +[scriptable, uuid(fbb9684a-a0aa-11e6-88b0-08606e456b8a)] +interface nsIUrlClassifierParseFindFullHashCallback : nsISupports { + /** + * Callback when a match is found in full hash response. This callback may be + * called multiple times when there are more than one matches in response. + * + * @param aCompleteHash A 32-byte complete hash string. + * @param aTableNames The table names that this complete hash is associated with. + * Since the server responded with a threat type, multiple + * list names can be returned. The caller is reponsible + * for filtering out the unrequested table names. + * See |convertThreatTypeToListNames| for the format. + * @param aPerHashCacheDuration See "FindFullHashesResponse" in safebrowsing.proto. + * + */ + void onCompleteHashFound(in ACString aCompleteHash, + in ACString aTableNames, + in unsigned long aPerHashCacheDuration); + + /** + * Callback when full hash response is received. + * + * @param aMinWaitDuration See "FindFullHashesResponse" in safebrowsing.proto. + * @param aNegCacheDuration See "FindFullHashesResponse" in safebrowsing.proto. + * + */ + void onResponseParsed(in unsigned long aMinWaitDuration, + in unsigned long aNegCacheDuration); +}; + +[scriptable, uuid(e4f0e59c-b922-48b0-a7b6-1735c1f96fed)] +interface nsIUrlClassifierUtils : nsISupports +{ + /** + * Get the lookup string for a given URI. This normalizes the hostname, + * url-decodes the string, and strips off the protocol. + * + * @param uri URI to get the lookup key for. + * + * @returns String containing the canonicalized URI. + */ + ACString getKeyForURI(in nsIURI uri); + + /** + * Get the provider by table name. + * + * @param tableName The table name that we want to lookup + * + * @returns the provider name that the given table belongs. + */ + ACString getProvider(in ACString tableName); + + /** + * Get the provider used for Telemetry. + * Because recording Telemetry will leak user-controlled strings, + * only built-in providers should be recorded. + * + * @param tableName The table name that we want to lookup + * + * @returns the filtered provider for telemetry. + * + */ + ACString getTelemetryProvider(in ACString tableName); + + /** + * Get the protocol version for the given provider. + * + * @param provider String the provider name. e.g. "google" + * + * @returns String to indicate the protocol version. e.g. "2.2" + */ + ACString getProtocolVersion(in ACString provider); + + /** + * Convert threat type to list name. + * + * @param Integer to indicate threat type. + * + * @returns The list names separated by ','. For example, + * 'goog-phish-proto,test-phish-proto'. + */ + ACString convertThreatTypeToListNames(in uint32_t threatType); + + /** + * Convert list name to threat type. + * + * @param The list name. + * + * @returns The threat type in integer. + */ + uint32_t convertListNameToThreatType(in ACString listName); + + /** + * Make update request for given lists and their states. + * + * @param aListNames An array of list name represented in string. + * @param aState An array of states (encoded in base64 format) for each list. + * + * The two argument arrays must be the same length. + * + * @returns A base64url encoded string. + */ + ACString makeUpdateRequestV4(in Array<ACString> aListNames, + in Array<ACString> aStatesBase64); + + /** + * Make "find full hash" request by for the given prefixes. + * + * @param aListNames An array of list names represented in string. + * @param aListStatesBase64 An array of list states represented in base64. + * @param aPrefixes An array of prefixes for which we'd like to find full hashes.. + * + * The aListNames and aListStatesBase64 arrays must be the same length. + * + * @returns A base64url encoded string. + */ + ACString makeFindFullHashRequestV4(in Array<ACString> aListNames, + in Array<ACString> aListStatesBase64, + in Array<ACString> aPrefixes); + + /** + * Make ThreatHit report request body. + * + * @param aChannel channel which encountered the threat. + * @param aListName listname represented in string. + * @param aHashBase64 hash-based hit represented in base64. + * + * @returns A base64 encoded string. + */ + ACString makeThreatHitReport(in nsIChannel aChannel, + in ACString aListName, + in ACString aHashBase64); + + /** + * Parse V4 FindFullHash response. + * + * @param aResponse Byte stream from the server. + * @param aCallback The callback function on each complete hash parsed. + * Can be called multiple times in one parsing. + */ + void parseFindFullHashResponseV4(in ACString aResponse, + in nsIUrlClassifierParseFindFullHashCallback aCallback); +}; diff --git a/toolkit/components/url-classifier/nsIUrlListManager.idl b/toolkit/components/url-classifier/nsIUrlListManager.idl new file mode 100644 index 0000000000..6bf01020ee --- /dev/null +++ b/toolkit/components/url-classifier/nsIUrlListManager.idl @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIPrincipal; + +/** + * Interface for a class that manages updates of the url classifier database. + */ + +[scriptable, uuid(d60a08ee-5c83-4eb6-bdfb-79fd0716501e)] +interface nsIUrlListManager : nsISupports +{ + /** + * Get the gethash url for this table + */ + ACString getGethashUrl(in ACString tableName); + + /** + * Get the update url for this table + */ + ACString getUpdateUrl(in ACString tableName); + + /** + * Add a table to the list of tables we are managing. The name is a + * string of the format provider_name-semantic_type-table_type. For + * @param tableName A string of the format + * provider_name-semantic_type-table_type. For example, + * goog-white-enchash or goog-black-url. + * @param providerName The name of the entity providing the list. + * @param updateUrl The URL from which to fetch updates. + * @param gethashUrl The URL from which to fetch hash completions. + */ + boolean registerTable(in ACString tableName, + in ACString providerName, + in ACString updateUrl, + in ACString gethashUrl); + + /** + * Unregister table from the list + */ + void unregisterTable(in ACString tableName); + + /** + * Turn on update checking for a table. I.e., during the next server + * check, download updates for this table. + */ + void enableUpdate(in ACString tableName); + + /** + * Turn off update checking for all tables. + */ + void disableAllUpdates(); + + /** + * Turn off update checking for a single table. Only used in tests. + */ + void disableUpdate(in ACString tableName); + + /** + * Toggle update checking, if necessary. + */ + void maybeToggleUpdateChecking(); + + /** + * This is currently used by about:url-classifier to force an update + * for the update url. Update may still fail because of backoff algorithm. + */ + boolean checkForUpdates(in ACString updateUrl); + + /** + * Force updates for the given tables, updates are still restricted to + * backoff algorithm. + * @param tables A string lists all the tables that we want to trigger updates. + * table names are separated with ','. + */ + boolean forceUpdates(in ACString tableNames); + + /** + * This is currently used by about:url-classifier to get back-off time + * (in millisecond since epoch) for the given provider. Return 0 if we + * are not in back-off mode. + */ + uint64_t getBackOffTime(in ACString provider); + + /** + * Return true if someone registers a table, this is used by testcase + * to figure out it SafeBrowsing.jsm is initialized. + */ + boolean isRegistered(); +}; diff --git a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp new file mode 100644 index 0000000000..929517aa6e --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp @@ -0,0 +1,2613 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsArrayUtils.h" +#include "nsCRT.h" +#include "nsIObserverService.h" +#include "nsIPermissionManager.h" +#include "nsIPrefBranch.h" +#include "nsIXULRuntime.h" +#include "nsToolkitCompsCID.h" +#include "nsUrlClassifierDBService.h" +#include "nsUrlClassifierUtils.h" +#include "nsUrlClassifierProxies.h" +#include "nsURILoader.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsTArray.h" +#include "nsNetCID.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" +#include "nsString.h" +#include "mozilla/Atomics.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Components.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Mutex.h" +#include "mozilla/Preferences.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/Logging.h" +#include "prnetdb.h" +#include "Entries.h" +#include "Classifier.h" +#include "ProtocolParser.h" +#include "mozilla/Attributes.h" +#include "nsIHttpChannel.h" +#include "nsIPrincipal.h" +#include "nsIUrlListManager.h" +#include "Classifier.h" +#include "ProtocolParser.h" +#include "nsContentUtils.h" +#include "mozilla/Components.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/URLClassifierChild.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "mozilla/net/UrlClassifierFeatureResult.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/SyncRunnable.h" +#include "UrlClassifierTelemetryUtils.h" +#include "nsIURLFormatter.h" +#include "nsIUploadChannel.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" +#include "nsToolkitCompsCID.h" + +namespace mozilla { +namespace safebrowsing { + +nsresult TablesToResponse(const nsACString& tables) { + if (tables.IsEmpty()) { + return NS_OK; + } + + // We don't check mCheckMalware and friends because disabled tables are + // never included + if (FindInReadable("-malware-"_ns, tables)) { + return NS_ERROR_MALWARE_URI; + } + if (FindInReadable("-harmful-"_ns, tables)) { + return NS_ERROR_HARMFUL_URI; + } + if (FindInReadable("-phish-"_ns, tables)) { + return NS_ERROR_PHISHING_URI; + } + if (FindInReadable("-unwanted-"_ns, tables)) { + return NS_ERROR_UNWANTED_URI; + } + if (FindInReadable("-track-"_ns, tables)) { + return NS_ERROR_TRACKING_URI; + } + if (FindInReadable("-block-"_ns, tables)) { + return NS_ERROR_BLOCKED_URI; + } + return NS_OK; +} + +} // namespace safebrowsing +} // namespace mozilla + +// This class holds a list of features, their tables, and it stores the lookup +// results. +class nsUrlClassifierDBService::FeatureHolder final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FeatureHolder); + + // In order to avoid multiple lookup for the same table, we have a special + // array for tables and their results. The Features are stored in a separate + // array together with the references to their tables. + + class TableData { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING( + nsUrlClassifierDBService::FeatureHolder::TableData); + + explicit TableData(const nsACString& aTable) : mTable(aTable) {} + + nsCString mTable; + LookupResultArray mResults; + + private: + ~TableData() = default; + }; + + struct FeatureData { + RefPtr<nsIUrlClassifierFeature> mFeature; + nsTArray<RefPtr<TableData>> mTables; + }; + + static already_AddRefed<FeatureHolder> Create( + nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures, + nsIUrlClassifierFeature::listType aListType) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI); + + RefPtr<FeatureHolder> holder = new FeatureHolder(aURI); + + for (nsIUrlClassifierFeature* feature : aFeatures) { + FeatureData* featureData = holder->mFeatureData.AppendElement(); + MOZ_ASSERT(featureData); + + featureData->mFeature = feature; + nsTArray<nsCString> tables; + nsresult rv = feature->GetTables(aListType, tables); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + for (const nsCString& table : tables) { + TableData* tableData = holder->GetOrCreateTableData(table); + MOZ_ASSERT(tableData); + + featureData->mTables.AppendElement(tableData); + } + } + + return holder.forget(); + } + + nsresult DoLocalLookup(const nsACString& aSpec, + nsUrlClassifierDBServiceWorker* aWorker) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aWorker); + + mozilla::Telemetry::AutoTimer< + mozilla::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); + + for (TableData* tableData : mTableData) { + rv = aWorker->DoSingleLocalLookupWithURIFragments( + fragments, tableData->mTable, tableData->mResults); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; + } + + // This method is used to convert the LookupResultArray from + // ::DoSingleLocalLookupWithURIFragments to nsIUrlClassifierFeatureResult + void GetResults(nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aResults) { + MOZ_ASSERT(NS_IsMainThread()); + + // For each table, we must concatenate the results of the corresponding + // tables. + + for (FeatureData& featureData : mFeatureData) { + nsAutoCString list; + for (TableData* tableData : featureData.mTables) { + for (uint32_t i = 0; i < tableData->mResults.Length(); ++i) { + if (!list.IsEmpty()) { + list.AppendLiteral(","); + } + list.Append(tableData->mResults[i]->mTableName); + } + } + + if (list.IsEmpty()) { + continue; + } + + RefPtr<mozilla::net::UrlClassifierFeatureResult> result = + new mozilla::net::UrlClassifierFeatureResult( + mURI, featureData.mFeature, list); + aResults.AppendElement(result); + } + } + + mozilla::UniquePtr<LookupResultArray> GetTableResults() const { + mozilla::UniquePtr<LookupResultArray> results = + mozilla::MakeUnique<LookupResultArray>(); + if (NS_WARN_IF(!results)) { + return nullptr; + } + + for (TableData* tableData : mTableData) { + results->AppendElements(tableData->mResults); + } + + return results; + } + + private: + explicit FeatureHolder(nsIURI* aURI) : mURI(aURI) { + MOZ_ASSERT(NS_IsMainThread()); + } + + ~FeatureHolder() { + for (FeatureData& featureData : mFeatureData) { + NS_ReleaseOnMainThread("FeatureHolder:mFeatureData", + featureData.mFeature.forget()); + } + + NS_ReleaseOnMainThread("FeatureHolder:mURI", mURI.forget()); + } + + TableData* GetOrCreateTableData(const nsACString& aTable) { + for (TableData* tableData : mTableData) { + if (tableData->mTable == aTable) { + return tableData; + } + } + + RefPtr<TableData> tableData = new TableData(aTable); + mTableData.AppendElement(tableData); + return tableData; + } + + nsCOMPtr<nsIURI> mURI; + nsTArray<FeatureData> mFeatureData; + nsTArray<RefPtr<TableData>> mTableData; +}; + +using namespace mozilla; +using namespace mozilla::safebrowsing; + +// MOZ_LOG=UrlClassifierDbService:5 +LazyLogModule gUrlClassifierDbServiceLog("UrlClassifierDbService"); +#define LOG(args) \ + MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug) + +#define GETHASH_NOISE_PREF "urlclassifier.gethashnoise" +#define GETHASH_NOISE_DEFAULT 4 + +// 30 minutes as the maximum negative cache duration. +#define MAXIMUM_NEGATIVE_CACHE_DURATION_SEC (30 * 60 * 1000) + +class nsUrlClassifierDBServiceWorker; + +// Singleton instance. +static nsUrlClassifierDBService* sUrlClassifierDBService; + +nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr; + +// Once we've committed to shutting down, don't do work in the background +// thread. +static Atomic<bool> gShuttingDownThread(false); + +NS_IMPL_ISUPPORTS(nsUrlClassifierDBServiceWorker, nsIUrlClassifierDBService) + +nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker() + : mUpdateObserverLock("nsUrlClassifierDBServerWorker.mUpdateObserverLock"), + mInStream(false), + mGethashNoise(0), + mPendingLookupLock("nsUrlClassifierDBServerWorker.mPendingLookupLock") {} + +nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker() { + NS_ASSERTION(!mClassifier, + "Db connection not closed, leaking memory! Call CloseDb " + "to close the connection."); +} + +nsresult nsUrlClassifierDBServiceWorker::Init( + uint32_t aGethashNoise, nsCOMPtr<nsIFile> aCacheDir, + nsUrlClassifierDBService* aDBService) { + mGethashNoise = aGethashNoise; + mCacheDir = aCacheDir; + mDBService = aDBService; + + ResetUpdate(); + + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::QueueLookup( + const nsACString& aKey, + nsUrlClassifierDBService::FeatureHolder* aFeatureHolder, + nsIUrlClassifierLookupCallback* aCallback) { + MOZ_ASSERT(aFeatureHolder); + MOZ_ASSERT(aCallback); + + MutexAutoLock lock(mPendingLookupLock); + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + PendingLookup* lookup = mPendingLookups.AppendElement(fallible); + if (NS_WARN_IF(!lookup)) return NS_ERROR_OUT_OF_MEMORY; + + lookup->mStartTime = TimeStamp::Now(); + lookup->mKey = aKey; + lookup->mCallback = aCallback; + lookup->mFeatureHolder = aFeatureHolder; + + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::DoSingleLocalLookupWithURIFragments( + const nsTArray<nsCString>& aSpecFragments, const nsACString& aTable, + LookupResultArray& aResults) { + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + MOZ_ASSERT( + !NS_IsMainThread(), + "DoSingleLocalLookupWithURIFragments must be on background thread"); + + // Bail if we haven't been initialized on the background thread. + if (!mClassifier) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = + mClassifier->CheckURIFragments(aSpecFragments, aTable, aResults); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("Found %zu results.", aResults.Length())); + 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 + * url. For each URL there are one or two possible domain names to check: + * 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, + nsUrlClassifierDBService::FeatureHolder* aFeatureHolder, + nsIUrlClassifierLookupCallback* c) { + // Make sure the callback is invoked when a failure occurs, + // otherwise we will not be able to load any url. + auto scopeExit = MakeScopeExit([&c]() { c->LookupComplete(nullptr); }); + + if (gShuttingDownThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRIntervalTime clockStart = 0; + if (LOG_ENABLED()) { + clockStart = PR_IntervalNow(); + } + + nsresult rv = aFeatureHolder->DoLocalLookup(spec, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (LOG_ENABLED()) { + PRIntervalTime clockEnd = PR_IntervalNow(); + LOG(("query took %dms\n", + PR_IntervalToMilliseconds(clockEnd - clockStart))); + } + + UniquePtr<LookupResultArray> results = aFeatureHolder->GetTableResults(); + if (NS_WARN_IF(!results)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + LOG(("Found %zu results.", results->Length())); + + for (const RefPtr<const LookupResult> lookupResult : *results) { + if (!lookupResult->Confirmed() && + mDBService->CanComplete(lookupResult->mTableName)) { + // We're going to be doing a gethash request, add some extra entries. + // Note that we cannot pass the first two by reference, because we + // add to completes, which can cause completes to reallocate and move. + AddNoise(lookupResult->hash.fixedLengthPrefix, lookupResult->mTableName, + mGethashNoise, *results); + break; + } + } + + // At this point ownership of 'results' is handed to the callback. + scopeExit.release(); + c->LookupComplete(std::move(results)); + + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::HandlePendingLookups() { + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + MutexAutoLock lock(mPendingLookupLock); + while (mPendingLookups.Length() > 0) { + PendingLookup lookup = mPendingLookups[0]; + mPendingLookups.RemoveElementAt(0); + { + MutexAutoUnlock unlock(mPendingLookupLock); + DoLookup(lookup.mKey, lookup.mFeatureHolder, lookup.mCallback); + } + double lookupTime = (TimeStamp::Now() - lookup.mStartTime).ToMilliseconds(); + Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LOOKUP_TIME_2, + static_cast<uint32_t>(lookupTime)); + } + + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::AddNoise(const Prefix aPrefix, + const nsCString tableName, + uint32_t aCount, + LookupResultArray& results) { + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + if (aCount < 1) { + return NS_OK; + } + + PrefixArray noiseEntries; + nsresult rv = + mClassifier->ReadNoiseEntries(aPrefix, tableName, aCount, noiseEntries); + NS_ENSURE_SUCCESS(rv, rv); + + for (const auto noiseEntry : noiseEntries) { + RefPtr<LookupResult> result = new LookupResult; + results.AppendElement(result); + + result->hash.fixedLengthPrefix = noiseEntry; + result->mNoise = true; + result->mPartialHashLength = PREFIX_SIZE; // Noise is always 4-byte, + result->mTableName.Assign(tableName); + } + + return NS_OK; +} + +// Lookup a key in the db. +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal, + const nsACString& aTables, + nsIUrlClassifierCallback* c) { + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + return HandlePendingLookups(); +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c) { + if (gShuttingDownThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = OpenDb(); + if (NS_FAILED(rv)) { + NS_ERROR("Unable to open SafeBrowsing database"); + return NS_ERROR_FAILURE; + } + + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString response; + mClassifier->TableRequest(response); + LOG(("GetTables: %s", response.get())); + c->HandleEvent(response); + + return rv; +} + +void nsUrlClassifierDBServiceWorker::ResetStream() { + LOG(("ResetStream")); + mInStream = false; + mProtocolParser = nullptr; +} + +void nsUrlClassifierDBServiceWorker::ResetUpdate() { + LOG(("ResetUpdate")); + mUpdateWaitSec = 0; + mUpdateStatus = NS_OK; + { + MutexAutoLock lock(mUpdateObserverLock); + mUpdateObserver = nullptr; + } +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::SetHashCompleter( + const nsACString& tableName, nsIUrlClassifierHashCompleter* completer) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::BeginUpdate( + nsIUrlClassifierUpdateObserver* observer, const nsACString& tables) { + LOG(("nsUrlClassifierDBServiceWorker::BeginUpdate [%s]", + PromiseFlatCString(tables).get())); + + if (gShuttingDownThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + { + MutexAutoLock lock(mUpdateObserverLock); + NS_ENSURE_STATE(!mUpdateObserver); + + nsresult rv = OpenDb(); + if (NS_FAILED(rv)) { + NS_ERROR("Unable to open SafeBrowsing database"); + return NS_ERROR_FAILURE; + } + + mUpdateStatus = NS_OK; + MOZ_ASSERT(mTableUpdates.IsEmpty(), + "mTableUpdates should have been cleared in FinishUpdate()"); + mUpdateObserver = observer; + } + Classifier::SplitTables(tables, mUpdateTables); + + return NS_OK; +} + +// Called from the stream updater. +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::BeginStream(const nsACString& table) { + LOG(("nsUrlClassifierDBServiceWorker::BeginStream")); + MOZ_ASSERT(!NS_IsMainThread(), "Streaming must be on the background thread"); + + if (gShuttingDownThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + { + MutexAutoLock lock(mUpdateObserverLock); + NS_ENSURE_STATE(mUpdateObserver); + } + NS_ENSURE_STATE(!mInStream); + + mInStream = true; + + NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser."); + + // Check if we should use protobuf to parse the update. + bool useProtobuf = false; + for (size_t i = 0; i < mUpdateTables.Length(); i++) { + bool isCurProtobuf = StringEndsWith(mUpdateTables[i], "-proto"_ns); + + if (0 == i) { + // Use the first table name to decice if all the subsequent tables + // should be '-proto'. + useProtobuf = isCurProtobuf; + continue; + } + + if (useProtobuf != isCurProtobuf) { + NS_WARNING( + "Cannot mix 'proto' tables with other types " + "within the same provider."); + break; + } + } + + if (useProtobuf) { + mProtocolParser.reset(new (fallible) ProtocolParserProtobuf()); + } else { + mProtocolParser.reset(new (fallible) ProtocolParserV2()); + } + if (!mProtocolParser) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return mProtocolParser->Begin(table, mUpdateTables); +} + +/** + * Updating the database: + * + * The Update() method takes a series of chunks separated with control data, + * as described in + * http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec + * + * It will iterate through the control data until it reaches a chunk. By + * the time it reaches a chunk, it should have received + * a) the table to which this chunk applies + * b) the type of chunk (add, delete, expire add, expire delete). + * c) the chunk ID + * d) the length of the chunk. + * + * For add and subtract chunks, it needs to read the chunk data (expires + * don't have any data). Chunk data is a list of URI fragments whose + * encoding depends on the type of table (which is indicated by the end + * of the table name): + * a) tables ending with -exp are a zlib-compressed list of URI fragments + * separated by newlines. + * b) tables ending with -sha128 have the form + * [domain][N][frag0]...[fragN] + * 16 1 16 16 + * If N is 0, the domain is reused as a fragment. + * c) any other tables are assumed to be a plaintext list of URI fragments + * separated by newlines. + * + * Update() can be fed partial data; It will accumulate data until there is + * enough to act on. Finish() should be called when there will be no more + * data. + */ +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::UpdateStream(const nsACString& chunk) { + if (gShuttingDownThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + MOZ_ASSERT(mProtocolParser); + + NS_ENSURE_STATE(mInStream); + + HandlePendingLookups(); + + // Feed the chunk to the parser. + return mProtocolParser->AppendStream(chunk); +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::FinishStream() { + if (gShuttingDownThread) { + LOG(("shutting down")); + return NS_ERROR_NOT_INITIALIZED; + } + + MutexAutoLock lock(mUpdateObserverLock); + + MOZ_ASSERT(mProtocolParser); + + NS_ENSURE_STATE(mInStream); + NS_ENSURE_STATE(mUpdateObserver); + + mInStream = false; + + mProtocolParser->End(); + + if (NS_SUCCEEDED(mProtocolParser->Status())) { + if (mProtocolParser->UpdateWaitSec()) { + mUpdateWaitSec = mProtocolParser->UpdateWaitSec(); + } + // XXX: Only allow forwards from the initial update? + const nsTArray<ProtocolParser::ForwardedUpdate>& forwards = + mProtocolParser->Forwards(); + for (uint32_t i = 0; i < forwards.Length(); i++) { + const ProtocolParser::ForwardedUpdate& forward = forwards[i]; + mUpdateObserver->UpdateUrlRequested(forward.url, forward.table); + } + // Hold on to any TableUpdate objects that were created by the + // parser. + mTableUpdates.AppendElements(mProtocolParser->GetTableUpdates()); + mProtocolParser->ForgetTableUpdates(); + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + // The assignment involves no string copy since the source string is + // sharable. + mRawTableUpdates = mProtocolParser->GetRawTableUpdates(); +#endif + } else { + LOG( + ("nsUrlClassifierDBService::FinishStream Failed to parse the stream " + "using mProtocolParser.")); + mUpdateStatus = mProtocolParser->Status(); + } + mUpdateObserver->StreamFinished(mProtocolParser->Status(), 0); + + if (NS_SUCCEEDED(mUpdateStatus)) { + if (mProtocolParser->ResetRequested()) { + mClassifier->ResetTables(Classifier::Clear_All, + mProtocolParser->TablesToReset()); + } + } + + mProtocolParser = nullptr; + + return mUpdateStatus; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::FinishUpdate() { + LOG(("nsUrlClassifierDBServiceWorker::FinishUpdate")); + + MOZ_ASSERT(!NS_IsMainThread(), + "nsUrlClassifierDBServiceWorker::FinishUpdate " + "NUST NOT be on the main thread."); + + if (gShuttingDownThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + MOZ_ASSERT(!mProtocolParser, + "Should have been nulled out in FinishStream() " + "or never created."); + + { + MutexAutoLock lock(mUpdateObserverLock); + NS_ENSURE_STATE(mUpdateObserver); + } + + if (NS_FAILED(mUpdateStatus)) { + LOG( + ("nsUrlClassifierDBServiceWorker::FinishUpdate() Not running " + "ApplyUpdate() since the update has already failed.")); + mTableUpdates.Clear(); + return NotifyUpdateObserver(mUpdateStatus); + } + + if (mTableUpdates.IsEmpty()) { + LOG(("Nothing to update. Just notify update observer.")); + return NotifyUpdateObserver(NS_OK); + } + + RefPtr<nsUrlClassifierDBServiceWorker> self = this; + nsresult rv = mClassifier->AsyncApplyUpdates( + mTableUpdates, [self](nsresult aRv) -> void { +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + if (NS_FAILED(aRv) && NS_ERROR_OUT_OF_MEMORY != aRv && + NS_ERROR_UC_UPDATE_SHUTDOWNING != aRv) { + self->mClassifier->DumpRawTableUpdates(self->mRawTableUpdates); + } + // Invalidate the raw table updates. + self->mRawTableUpdates.Truncate(); +#endif + + self->NotifyUpdateObserver(aRv); + }); + mTableUpdates.Clear(); // Classifier is working on its copy. + + if (NS_FAILED(rv)) { + LOG(("Failed to start async update. Notify immediately.")); + NotifyUpdateObserver(rv); + } + + return rv; +} + +nsresult nsUrlClassifierDBServiceWorker::NotifyUpdateObserver( + nsresult aUpdateStatus) { + MOZ_ASSERT(!NS_IsMainThread(), + "nsUrlClassifierDBServiceWorker::NotifyUpdateObserver " + "NUST NOT be on the main thread."); + + LOG(("nsUrlClassifierDBServiceWorker::NotifyUpdateObserver")); + + // We've either + // 1) failed starting a download stream + // 2) succeeded in starting a download stream but failed to obtain + // table updates + // 3) succeeded in obtaining table updates but failed to build new + // tables. + // 4) succeeded in building new tables but failed to take them. + // 5) succeeded in taking new tables. + + mUpdateStatus = aUpdateStatus; + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return NS_ERROR_FAILURE; + } + + nsCString provider; + // Assume that all the tables in update should have the same provider. + urlUtil->GetTelemetryProvider(mUpdateTables.SafeElementAt(0, ""_ns), + provider); + + nsresult updateStatus = mUpdateStatus; + if (NS_FAILED(mUpdateStatus)) { + updateStatus = + NS_ERROR_GET_MODULE(mUpdateStatus) == NS_ERROR_MODULE_URL_CLASSIFIER + ? mUpdateStatus + : NS_ERROR_UC_UPDATE_UNKNOWN; + } + + // Do not record telemetry for testing tables. + if (!provider.EqualsLiteral(TESTING_TABLE_PROVIDER_NAME)) { + Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR, provider, + NS_ERROR_GET_CODE(updateStatus)); + } + + MutexAutoLock lock(mUpdateObserverLock); + + if (!mUpdateObserver) { + // In the normal shutdown process, CancelUpdate() would NOT be + // called prior to NotifyUpdateObserver(). However, CancelUpdate() + // is a public API which can be called in the test case at any point. + // If the call sequence is FinishUpdate() then CancelUpdate(), the later + // might be executed before NotifyUpdateObserver() which is triggered + // by the update thread. In this case, we will get null mUpdateObserver. + NS_WARNING( + "CancelUpdate() is called before we asynchronously call " + "NotifyUpdateObserver() in FinishUpdate()."); + + // The DB cleanup will be done in CancelUpdate() so we can just return. + return NS_OK; + } + + // Null out mUpdateObserver before notifying so that BeginUpdate() + // becomes available prior to callback. + nsCOMPtr<nsIUrlClassifierUpdateObserver> updateObserver = nullptr; + updateObserver.swap(mUpdateObserver); + + if (NS_SUCCEEDED(mUpdateStatus)) { + LOG(("Notifying success: %d", mUpdateWaitSec)); + updateObserver->UpdateSuccess(mUpdateWaitSec); + } else { + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(mUpdateStatus, errorName); + LOG(("Notifying error: %s (%" PRIu32 ")", errorName.get(), + static_cast<uint32_t>(mUpdateStatus))); + } + + updateObserver->UpdateError(mUpdateStatus); + /* + * mark the tables as spoiled(clear cache in LookupCache), we don't want to + * block hosts longer than normal because our update failed + */ + mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::ResetDatabase() { + nsresult rv = OpenDb(); + + if (NS_SUCCEEDED(rv)) { + mClassifier->Reset(); + } + + rv = CloseDb(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::ReloadDatabase() { + // This will null out mClassifier + nsresult rv = CloseDb(); + NS_ENSURE_SUCCESS(rv, rv); + + // Create new mClassifier and load prefixset and completions from disk. + rv = OpenDb(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::ClearCache() { + nsTArray<nsCString> tables; + nsresult rv = mClassifier->ActiveTables(tables); + NS_ENSURE_SUCCESS(rv, rv); + + mClassifier->ResetTables(Classifier::Clear_Cache, tables); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::CancelUpdate() { + LOG(("nsUrlClassifierDBServiceWorker::CancelUpdate")); + + { + MutexAutoLock lock(mUpdateObserverLock); + if (!mUpdateObserver) { + LOG(("No UpdateObserver, nothing to cancel")); + + return NS_OK; + } + + LOG(("UpdateObserver exists, cancelling")); + + mUpdateStatus = NS_BINDING_ABORTED; + + mUpdateObserver->UpdateError(mUpdateStatus); + } + + /* + * mark the tables as spoiled(clear cache in LookupCache), we don't want to + * block hosts longer than normal because our update failed + */ + mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables); + + ResetStream(); + ResetUpdate(); + + return NS_OK; +} + +void nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate() { + LOG(("nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate()")); + + if (mClassifier) { + mClassifier->FlushAndDisableAsyncUpdate(); + } +} + +// Allows the main thread to delete the connection which may be in +// a background thread. +// XXX This could be turned into a single shutdown event so the logic +// is simpler in nsUrlClassifierDBService::Shutdown. +nsresult nsUrlClassifierDBServiceWorker::CloseDb() { + if (mClassifier) { + mClassifier->Close(); + mClassifier = nullptr; + } + + // Clear last completion result when close db so we will still cache + // completion result next time we re-open it. + mLastResults.Clear(); + + LOG(("urlclassifier db closed\n")); + + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::PreShutdown() { + if (mClassifier) { + // Classifier close will release all lookup caches which may be a + // time-consuming job. See Bug 1408631. + mClassifier->Close(); + } + + // WARNING: nothing we put here should affect an ongoing update thread. When + // in doubt, put things in Shutdown() instead. + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::CacheCompletions( + const ConstCacheResultArray& aResults) { + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + LOG(("nsUrlClassifierDBServiceWorker::CacheCompletions [%p]", this)); + if (!mClassifier) { + return NS_OK; + } + + if (aResults.Length() == 0) { + return NS_OK; + } + + if (IsSameAsLastResults(aResults)) { + LOG(("Skipping completions that have just been cached already.")); + return NS_OK; + } + + // Only cache results for tables that we have, don't take + // in tables we might accidentally have hit during a completion. + // This happens due to goog vs googpub lists existing. + nsTArray<nsCString> tables; + nsresult rv = mClassifier->ActiveTables(tables); + NS_ENSURE_SUCCESS(rv, rv); + if (LOG_ENABLED()) { + nsCString s; + for (size_t i = 0; i < tables.Length(); i++) { + if (!s.IsEmpty()) { + s += ","; + } + s += tables[i]; + } + LOG(("Active tables: %s", s.get())); + } + + ConstTableUpdateArray updates; + + for (const auto& result : aResults) { + bool activeTable = false; + + for (uint32_t table = 0; table < tables.Length(); table++) { + if (tables[table].Equals(result->table)) { + activeTable = true; + break; + } + } + if (activeTable) { + UniquePtr<ProtocolParser> pParse; + if (result->Ver() == CacheResult::V2) { + pParse.reset(new ProtocolParserV2()); + } else { + pParse.reset(new ProtocolParserProtobuf()); + } + + RefPtr<TableUpdate> tu = pParse->GetTableUpdate(result->table); + + rv = CacheResultToTableUpdate(result, tu); + if (NS_FAILED(rv)) { + // We can bail without leaking here because ForgetTableUpdates + // hasn't been called yet. + return rv; + } + updates.AppendElement(tu); + pParse->ForgetTableUpdates(); + } else { + LOG(("Completion received, but table %s is not active, so not caching.", + result->table.get())); + } + } + + rv = mClassifier->ApplyFullHashes(updates); + if (NS_SUCCEEDED(rv)) { + mLastResults = aResults.Clone(); + } + return rv; +} + +nsresult nsUrlClassifierDBServiceWorker::CacheResultToTableUpdate( + RefPtr<const CacheResult> aCacheResult, RefPtr<TableUpdate> aUpdate) { + RefPtr<TableUpdateV2> tuV2 = TableUpdate::Cast<TableUpdateV2>(aUpdate); + if (tuV2) { + RefPtr<const CacheResultV2> result = + CacheResult::Cast<const CacheResultV2>(aCacheResult); + MOZ_ASSERT(result); + + if (result->miss) { + return tuV2->NewMissPrefix(result->prefix); + } else { + LOG(("CacheCompletion hash %X, Addchunk %d", + result->completion.ToUint32(), result->addChunk)); + + nsresult rv = tuV2->NewAddComplete(result->addChunk, result->completion); + if (NS_FAILED(rv)) { + return rv; + } + return tuV2->NewAddChunk(result->addChunk); + } + } + + RefPtr<TableUpdateV4> tuV4 = TableUpdate::Cast<TableUpdateV4>(aUpdate); + if (tuV4) { + RefPtr<const CacheResultV4> result = + CacheResult::Cast<const CacheResultV4>(aCacheResult); + MOZ_ASSERT(result); + + if (LOG_ENABLED()) { + const FullHashExpiryCache& fullHashes = result->response.fullHashes; + for (const auto& entry : fullHashes) { + Completion completion; + completion.Assign(entry.GetKey()); + LOG(("CacheCompletion(v4) hash %X, CacheExpireTime %" PRId64, + completion.ToUint32(), entry.GetData())); + } + } + + tuV4->NewFullHashResponse(result->prefix, result->response); + return NS_OK; + } + + // tableUpdate object should be either V2 or V4. + return NS_ERROR_FAILURE; +} + +nsresult nsUrlClassifierDBServiceWorker::OpenDb() { + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread"); + // Connection already open, don't do anything. + if (mClassifier) { + return NS_OK; + } + + nsresult rv; + RefPtr<Classifier> classifier = new (fallible) Classifier(); + if (!classifier) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = classifier->Open(*mCacheDir); + NS_ENSURE_SUCCESS(rv, rv); + + mClassifier = classifier; + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBServiceWorker::ClearLastResults() { + MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread"); + mLastResults.Clear(); + return NS_OK; +} + +nsresult nsUrlClassifierDBServiceWorker::GetCacheInfo( + const nsACString& aTable, nsIUrlClassifierCacheInfo** aCache) { + MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread"); + if (!mClassifier) { + return NS_ERROR_NOT_AVAILABLE; + } + + mClassifier->GetCacheInfo(aTable, aCache); + return NS_OK; +} + +bool nsUrlClassifierDBServiceWorker::IsSameAsLastResults( + const ConstCacheResultArray& aResult) const { + if (mLastResults.Length() != aResult.Length()) { + return false; + } + + bool equal = true; + for (uint32_t i = 0; i < mLastResults.Length() && equal; i++) { + RefPtr<const CacheResult> lhs = mLastResults[i]; + RefPtr<const CacheResult> rhs = aResult[i]; + + if (lhs->Ver() != rhs->Ver()) { + return false; + } + + if (lhs->Ver() == CacheResult::V2) { + equal = *(CacheResult::Cast<const CacheResultV2>(lhs)) == + *(CacheResult::Cast<const CacheResultV2>(rhs)); + } else if (lhs->Ver() == CacheResult::V4) { + equal = *(CacheResult::Cast<const CacheResultV4>(lhs)) == + *(CacheResult::Cast<const CacheResultV4>(rhs)); + } + } + + return equal; +} + +// ------------------------------------------------------------------------- +// nsUrlClassifierLookupCallback +// +// This class takes the results of a lookup found on the worker thread +// and handles any necessary partial hash expansions before calling +// the client callback. + +class nsUrlClassifierLookupCallback final + : public nsIUrlClassifierLookupCallback, + public nsIUrlClassifierHashCompleterCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK + NS_DECL_NSIURLCLASSIFIERHASHCOMPLETERCALLBACK + + nsUrlClassifierLookupCallback(nsUrlClassifierDBService* dbservice, + nsIUrlClassifierCallback* c) + : mDBService(dbservice), + mResults(nullptr), + mPendingCompletions(0), + mCallback(c) {} + + private: + ~nsUrlClassifierLookupCallback(); + + nsresult HandleResults(); + nsresult ProcessComplete(RefPtr<CacheResult> aCacheResult); + nsresult CacheMisses(); + + RefPtr<nsUrlClassifierDBService> mDBService; + UniquePtr<LookupResultArray> mResults; + + // Completed results to send back to the worker for caching. + ConstCacheResultArray mCacheResults; + + uint32_t mPendingCompletions; + nsCOMPtr<nsIUrlClassifierCallback> mCallback; +}; + +NS_IMPL_ISUPPORTS(nsUrlClassifierLookupCallback, nsIUrlClassifierLookupCallback, + nsIUrlClassifierHashCompleterCallback) + +nsUrlClassifierLookupCallback::~nsUrlClassifierLookupCallback() { + if (mCallback) { + NS_ReleaseOnMainThread("nsUrlClassifierLookupCallback::mCallback", + mCallback.forget()); + } +} + +NS_IMETHODIMP +nsUrlClassifierLookupCallback::LookupComplete( + UniquePtr<LookupResultArray> results) { + NS_ASSERTION( + mResults == nullptr, + "Should only get one set of results per nsUrlClassifierLookupCallback!"); + + if (!results) { + HandleResults(); + return NS_OK; + } + + mResults = std::move(results); + + // Check the results entries that need to be completed. + for (const auto& result : *mResults) { + // We will complete partial matches and matches that are stale. + if (!result->Confirmed()) { + nsCOMPtr<nsIUrlClassifierHashCompleter> completer; + nsCString gethashUrl; + nsresult rv; + nsCOMPtr<nsIUrlListManager> listManager = + do_GetService("@mozilla.org/url-classifier/listmanager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = listManager->GetGethashUrl(result->mTableName, gethashUrl); + NS_ENSURE_SUCCESS(rv, rv); + LOG(("The match from %s needs to be completed at %s", + result->mTableName.get(), gethashUrl.get())); + // gethashUrls may be empty in 2 cases: test tables, and on startup where + // we may have found a prefix in an existing table before the listmanager + // has registered the table. In the second case we should not call + // complete. + if ((!gethashUrl.IsEmpty() || + nsUrlClassifierUtils::IsTestTable(result->mTableName)) && + mDBService->GetCompleter(result->mTableName, + getter_AddRefs(completer))) { + // Bug 1323953 - Send the first 4 bytes for completion no matter how + // long we matched the prefix. + nsresult rv = completer->Complete(result->PartialHash(), gethashUrl, + result->mTableName, this); + if (NS_SUCCEEDED(rv)) { + mPendingCompletions++; + } + } else { + // For tables with no hash completer, a complete hash match is + // good enough, we'll consider it is valid. + if (result->Complete()) { + result->mConfirmed = true; + LOG(("Skipping completion in a table without a valid completer (%s).", + result->mTableName.get())); + } else { + NS_WARNING( + "Partial match in a table without a valid completer, ignoring " + "partial match."); + } + } + } + } + + LOG( + ("nsUrlClassifierLookupCallback::LookupComplete [%p] " + "%u pending completions", + this, mPendingCompletions)); + if (mPendingCompletions == 0) { + // All results were complete, we're ready! + HandleResults(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierLookupCallback::CompletionFinished(nsresult status) { + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(status, errorName); + LOG(("nsUrlClassifierLookupCallback::CompletionFinished [%p, %s]", this, + errorName.get())); + } + + mPendingCompletions--; + if (mPendingCompletions == 0) { + HandleResults(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierLookupCallback::CompletionV2(const nsACString& aCompleteHash, + const nsACString& aTableName, + uint32_t aChunkId) { + LOG(("nsUrlClassifierLookupCallback::Completion [%p, %s, %d]", this, + PromiseFlatCString(aTableName).get(), aChunkId)); + + MOZ_ASSERT(!StringEndsWith(aTableName, "-proto"_ns)); + + RefPtr<CacheResultV2> result = new CacheResultV2(); + + result->table = aTableName; + result->prefix.Assign(aCompleteHash); + result->completion.Assign(aCompleteHash); + result->addChunk = aChunkId; + + return ProcessComplete(result); +} + +NS_IMETHODIMP +nsUrlClassifierLookupCallback::CompletionV4(const nsACString& aPartialHash, + const nsACString& aTableName, + uint32_t aNegativeCacheDuration, + nsIArray* aFullHashes) { + LOG(("nsUrlClassifierLookupCallback::CompletionV4 [%p, %s, %d]", this, + PromiseFlatCString(aTableName).get(), aNegativeCacheDuration)); + + MOZ_ASSERT(StringEndsWith(aTableName, "-proto"_ns)); + + if (!aFullHashes) { + return NS_ERROR_INVALID_ARG; + } + + if (aNegativeCacheDuration > MAXIMUM_NEGATIVE_CACHE_DURATION_SEC) { + LOG( + ("Negative cache duration too large, clamping it down to" + "a reasonable value.")); + aNegativeCacheDuration = MAXIMUM_NEGATIVE_CACHE_DURATION_SEC; + } + + RefPtr<CacheResultV4> result = new CacheResultV4(); + + int64_t nowSec = PR_Now() / PR_USEC_PER_SEC; + + result->table = aTableName; + result->prefix.Assign(aPartialHash); + result->response.negativeCacheExpirySec = nowSec + aNegativeCacheDuration; + + // Fill in positive cache entries. + uint32_t fullHashCount = 0; + nsresult rv = aFullHashes->GetLength(&fullHashCount); + if (NS_FAILED(rv)) { + return rv; + } + + for (uint32_t i = 0; i < fullHashCount; i++) { + nsCOMPtr<nsIFullHashMatch> match = do_QueryElementAt(aFullHashes, i); + + nsCString fullHash; + match->GetFullHash(fullHash); + + uint32_t duration; + match->GetCacheDuration(&duration); + + result->response.fullHashes.InsertOrUpdate(fullHash, nowSec + duration); + } + + return ProcessComplete(result); +} + +nsresult nsUrlClassifierLookupCallback::ProcessComplete( + RefPtr<CacheResult> aCacheResult) { + NS_ENSURE_ARG_POINTER(mResults); + + if (!mCacheResults.AppendElement(aCacheResult, fallible)) { + // OK if this failed, we just won't cache the item. + } + + // Check if this matched any of our results. + for (const auto& result : *mResults) { + // Now, see if it verifies a lookup + if (!result->mNoise && result->mTableName.Equals(aCacheResult->table) && + aCacheResult->findCompletion(result->CompleteHash())) { + result->mProtocolConfirmed = true; + } + } + + return NS_OK; +} + +nsresult nsUrlClassifierLookupCallback::HandleResults() { + if (!mResults) { + // No results, this URI is clean. + LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, no results]", + this)); + return mCallback->HandleEvent(""_ns); + } + MOZ_ASSERT(mPendingCompletions == 0, + "HandleResults() should never be " + "called while there are pending completions"); + + LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, %zu results]", this, + mResults->Length())); + + nsCOMPtr<nsIUrlClassifierClassifyCallback> classifyCallback = + do_QueryInterface(mCallback); + + nsTArray<nsCString> tables; + // Build a stringified list of result tables. + for (const auto& result : *mResults) { + // Leave out results that weren't confirmed, as their existence on + // the list can't be verified. Also leave out randomly-generated + // noise. + if (result->mNoise) { + LOG(("Skipping result %s from table %s (noise)", + result->PartialHashHex().get(), result->mTableName.get())); + continue; + } + + if (!result->Confirmed()) { + LOG(("Skipping result %s from table %s (not confirmed)", + result->PartialHashHex().get(), result->mTableName.get())); + continue; + } + + LOG(("Confirmed result %s from table %s", result->PartialHashHex().get(), + result->mTableName.get())); + + if (tables.IndexOf(result->mTableName) == nsTArray<nsCString>::NoIndex) { + tables.AppendElement(result->mTableName); + } + + if (classifyCallback) { + nsCString fullHashString; + result->hash.complete.ToString(fullHashString); + classifyCallback->HandleResult(result->mTableName, fullHashString); + } + } + + // Some parts of this gethash request generated no hits at all. + // Save the prefixes we checked to prevent repeated requests. + CacheMisses(); + + // This hands ownership of the cache results array back to the worker + // thread. + mDBService->CacheCompletions(mCacheResults); + mCacheResults.Clear(); + + return mCallback->HandleEvent(StringJoin(","_ns, tables)); +} + +nsresult nsUrlClassifierLookupCallback::CacheMisses() { + MOZ_ASSERT(mResults); + + for (const RefPtr<const LookupResult> result : *mResults) { + // Skip V4 because cache information is already included in the + // fullhash response so we don't need to manually add it here. + if (!result->mProtocolV2 || result->Confirmed() || result->mNoise) { + continue; + } + + RefPtr<CacheResultV2> cacheResult = new CacheResultV2(); + + cacheResult->table = result->mTableName; + cacheResult->prefix = result->hash.fixedLengthPrefix; + cacheResult->miss = true; + if (!mCacheResults.AppendElement(cacheResult, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + return NS_OK; +} + +struct Provider { + nsCString name; + uint8_t priority; +}; + +// Order matters +// Provider which is not included in this table has the lowest priority 0 +static const Provider kBuiltInProviders[] = { + {"mozilla"_ns, 1}, + {"google4"_ns, 2}, + {"google"_ns, 3}, +}; + +// ------------------------------------------------------------------------- +// Helper class for nsIURIClassifier implementation, handle classify result and +// send back to nsIURIClassifier + +class nsUrlClassifierClassifyCallback final + : public nsIUrlClassifierCallback, + public nsIUrlClassifierClassifyCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERCALLBACK + NS_DECL_NSIURLCLASSIFIERCLASSIFYCALLBACK + + explicit nsUrlClassifierClassifyCallback(nsIURIClassifierCallback* c) + : mCallback(c) {} + + private: + struct ClassifyMatchedInfo { + nsCString table; + nsCString fullhash; + Provider provider; + nsresult errorCode; + }; + + ~nsUrlClassifierClassifyCallback() = default; + + nsCOMPtr<nsIURIClassifierCallback> mCallback; + nsTArray<ClassifyMatchedInfo> mMatchedArray; +}; + +NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback, nsIUrlClassifierCallback, + nsIUrlClassifierClassifyCallback) + +NS_IMETHODIMP +nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables) { + nsresult response = TablesToResponse(tables); + ClassifyMatchedInfo* matchedInfo = nullptr; + + if (NS_FAILED(response)) { + // Filter all matched info which has correct response + // In the case multiple tables found, use the higher priority provider + nsTArray<ClassifyMatchedInfo> matches; + for (uint32_t i = 0; i < mMatchedArray.Length(); i++) { + if (mMatchedArray[i].errorCode == response && + (!matchedInfo || matchedInfo->provider.priority < + mMatchedArray[i].provider.priority)) { + matchedInfo = &mMatchedArray[i]; + } + } + } + + nsCString provider = matchedInfo ? matchedInfo->provider.name : ""_ns; + nsCString fullhash = matchedInfo ? matchedInfo->fullhash : ""_ns; + nsCString table = matchedInfo ? matchedInfo->table : ""_ns; + + mCallback->OnClassifyComplete(response, table, provider, fullhash); + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierClassifyCallback::HandleResult(const nsACString& aTable, + const nsACString& aFullHash) { + LOG( + ("nsUrlClassifierClassifyCallback::HandleResult [%p, table %s full hash " + "%s]", + this, PromiseFlatCString(aTable).get(), + PromiseFlatCString(aFullHash).get())); + + if (NS_WARN_IF(aTable.IsEmpty()) || NS_WARN_IF(aFullHash.IsEmpty())) { + return NS_ERROR_INVALID_ARG; + } + + ClassifyMatchedInfo* matchedInfo = mMatchedArray.AppendElement(); + matchedInfo->table = aTable; + matchedInfo->fullhash = aFullHash; + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return NS_ERROR_FAILURE; + } + + nsCString provider; + nsresult rv = urlUtil->GetProvider(aTable, provider); + + matchedInfo->provider.name = NS_SUCCEEDED(rv) ? provider : ""_ns; + matchedInfo->provider.priority = 0; + for (uint8_t i = 0; i < ArrayLength(kBuiltInProviders); i++) { + if (kBuiltInProviders[i].name.Equals(matchedInfo->provider.name)) { + matchedInfo->provider.priority = kBuiltInProviders[i].priority; + } + } + matchedInfo->errorCode = TablesToResponse(aTable); + + return NS_OK; +} + +// ------------------------------------------------------------------------- +// Proxy class implementation + +NS_IMPL_ADDREF(nsUrlClassifierDBService) +NS_IMPL_RELEASE(nsUrlClassifierDBService) +NS_INTERFACE_MAP_BEGIN(nsUrlClassifierDBService) + // Only nsIURIClassifier is supported in the content process! + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUrlClassifierDBService, + XRE_IsParentProcess()) + NS_INTERFACE_MAP_ENTRY(nsIURIClassifier) + NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierInfo) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIObserver, XRE_IsParentProcess()) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIClassifier) +NS_INTERFACE_MAP_END + +/* static */ +already_AddRefed<nsUrlClassifierDBService> +nsUrlClassifierDBService::GetInstance(nsresult* result) { + *result = NS_OK; + if (!sUrlClassifierDBService) { + sUrlClassifierDBService = new (fallible) nsUrlClassifierDBService(); + if (!sUrlClassifierDBService) { + *result = NS_ERROR_OUT_OF_MEMORY; + return nullptr; + } + + *result = sUrlClassifierDBService->Init(); + if (NS_FAILED(*result)) { + return nullptr; + } + } + return do_AddRef(sUrlClassifierDBService); +} + +nsUrlClassifierDBService::nsUrlClassifierDBService() : mInUpdate(false) {} + +nsUrlClassifierDBService::~nsUrlClassifierDBService() { + sUrlClassifierDBService = nullptr; +} + +nsresult nsUrlClassifierDBService::ReadDisallowCompletionsTablesFromPrefs() { + nsAutoCString tables; + + Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, tables); + Classifier::SplitTables(tables, mDisallowCompletionsTables); + + return NS_OK; +} + +nsresult nsUrlClassifierDBService::Init() { + MOZ_ASSERT(NS_IsMainThread(), "Must initialize DB service on main thread"); + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + // The parent process is supported. + break; + case GeckoProcessType_Content: + // In a content process, we simply forward all requests to the parent + // process, so we can skip the initialization steps here. Note that since + // we never register an observer, Shutdown() will also never be called in + // the content process. + return NS_OK; + default: + // No other process type is supported! + return NS_ERROR_NOT_AVAILABLE; + } + + uint32_t hashNoise = + Preferences::GetUint(GETHASH_NOISE_PREF, GETHASH_NOISE_DEFAULT); + ReadDisallowCompletionsTablesFromPrefs(); + + // Force nsUrlClassifierUtils loading on main thread. + if (NS_WARN_IF(!nsUrlClassifierUtils::GetInstance())) { + return NS_ERROR_FAILURE; + } + + // Directory providers must also be accessed on the main thread. + nsresult rv; + nsCOMPtr<nsIFile> cacheDir; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(cacheDir)); + if (NS_FAILED(rv)) { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(cacheDir)); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Start the background thread. + rv = NS_NewNamedThread("URL Classifier", &gDbBackgroundThread); + if (NS_FAILED(rv)) return rv; + + mWorker = new (fallible) nsUrlClassifierDBServiceWorker(); + if (!mWorker) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = mWorker->Init(hashNoise, cacheDir, this); + if (NS_FAILED(rv)) { + mWorker = nullptr; + return rv; + } + + // Proxy for calling the worker on the background thread + mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker); + rv = mWorkerProxy->OpenDb(); + if (NS_FAILED(rv)) { + return rv; + } + + // Add an observer for shutdown + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) return NS_ERROR_FAILURE; + + // The application is about to quit + observerService->AddObserver(this, "quit-application", false); + observerService->AddObserver(this, "profile-before-change", false); + + Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF); + + return NS_OK; +} + +// nsChannelClassifier is the only consumer of this interface. +NS_IMETHODIMP +nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal, + nsIURIClassifierCallback* c, bool* aResult) { + NS_ENSURE_ARG(aPrincipal); + MOZ_ASSERT(c); + NS_ENSURE_ARG(aResult); + + if (aPrincipal->IsSystemPrincipal()) { + *aResult = false; + return NS_OK; + } + + if (XRE_IsContentProcess()) { + using namespace mozilla::dom; + + ContentChild* content = ContentChild::GetSingleton(); + MOZ_ASSERT(content); + + auto actor = static_cast<URLClassifierChild*>( + content->AllocPURLClassifierChild(aPrincipal, aResult)); + MOZ_ASSERT(actor); + + if (!content->SendPURLClassifierConstructor(actor, aPrincipal, aResult)) { + *aResult = false; + return NS_ERROR_FAILURE; + } + + actor->SetCallback(c); + return NS_OK; + } + + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIPermissionManager> permissionManager = + components::PermissionManager::Service(); + if (NS_WARN_IF(!permissionManager)) { + return NS_ERROR_FAILURE; + } + + uint32_t perm; + nsresult rv = permissionManager->TestPermissionFromPrincipal( + aPrincipal, "safe-browsing"_ns, &perm); + NS_ENSURE_SUCCESS(rv, rv); + + if (perm == nsIPermissionManager::ALLOW_ACTION) { + *aResult = false; + return NS_OK; + } + + nsTArray<RefPtr<nsIUrlClassifierFeature>> features; + mozilla::net::UrlClassifierFeatureFactory::GetPhishingProtectionFeatures( + features); + if (features.IsEmpty()) { + *aResult = false; + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + // Casting to BasePrincipal, as we can't get InnerMost URI otherwise + auto* basePrincipal = BasePrincipal::Cast(aPrincipal); + rv = basePrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + // Let's keep the features alive and release them on the correct thread. + RefPtr<FeatureHolder> holder = + FeatureHolder::Create(uri, features, nsIUrlClassifierFeature::blocklist); + if (NS_WARN_IF(!holder)) { + return NS_ERROR_FAILURE; + } + + uri = NS_GetInnermostURI(uri); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!utilsService)) { + return NS_ERROR_FAILURE; + } + + // Canonicalize the url + nsAutoCString key; + rv = utilsService->GetKeyForURI(uri, key); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsUrlClassifierClassifyCallback> callback = + new (fallible) nsUrlClassifierClassifyCallback(c); + if (NS_WARN_IF(!callback)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // The rest is done async. + rv = LookupURI(key, holder, callback); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = true; + return NS_OK; +} + +class ThreatHitReportListener final : public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + ThreatHitReportListener() = default; + + private: + ~ThreatHitReportListener() = default; +}; + +NS_IMPL_ISUPPORTS(ThreatHitReportListener, nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +ThreatHitReportListener::OnStartRequest(nsIRequest* aRequest) { + if (!LOG_ENABLED()) { + return NS_OK; // Nothing to do! + } + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); + NS_ENSURE_TRUE(httpChannel, NS_OK); + + nsresult rv, status; + rv = httpChannel->GetStatus(&status); + NS_ENSURE_SUCCESS(rv, NS_OK); + nsAutoCString errorName; + mozilla::GetErrorName(status, errorName); + + uint32_t requestStatus; + rv = httpChannel->GetResponseStatus(&requestStatus); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoCString spec; + nsCOMPtr<nsIURI> uri; + rv = httpChannel->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + uri->GetAsciiSpec(spec); + } + nsCOMPtr<nsIURLFormatter> urlFormatter = + do_GetService("@mozilla.org/toolkit/URLFormatterService;1"); + nsAutoString trimmed; + rv = urlFormatter->TrimSensitiveURLs(NS_ConvertUTF8toUTF16(spec), trimmed); + NS_ENSURE_SUCCESS(rv, NS_OK); + + LOG( + ("ThreatHitReportListener::OnStartRequest " + "(status=%s, code=%d, uri=%s, this=%p)", + errorName.get(), requestStatus, NS_ConvertUTF16toUTF8(trimmed).get(), + this)); + + return NS_OK; +} + +NS_IMETHODIMP +ThreatHitReportListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + return NS_OK; +} + +NS_IMETHODIMP +ThreatHitReportListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); + NS_ENSURE_TRUE(httpChannel, aStatus); + + uint8_t netErrCode = + NS_FAILED(aStatus) ? mozilla::safebrowsing::NetworkErrorToBucket(aStatus) + : 0; + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_THREATHIT_NETWORK_ERROR, netErrCode); + + uint32_t requestStatus; + nsresult rv = httpChannel->GetResponseStatus(&requestStatus); + NS_ENSURE_SUCCESS(rv, aStatus); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_THREATHIT_REMOTE_STATUS, + mozilla::safebrowsing::HTTPStatusToBucket(requestStatus)); + + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(aStatus, errorName); + + nsAutoCString spec; + nsCOMPtr<nsIURI> uri; + rv = httpChannel->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + uri->GetAsciiSpec(spec); + } + nsCOMPtr<nsIURLFormatter> urlFormatter = + do_GetService("@mozilla.org/toolkit/URLFormatterService;1"); + nsString trimmed; + rv = urlFormatter->TrimSensitiveURLs(NS_ConvertUTF8toUTF16(spec), trimmed); + NS_ENSURE_SUCCESS(rv, aStatus); + + LOG( + ("ThreatHitReportListener::OnStopRequest " + "(status=%s, code=%d, uri=%s, this=%p)", + errorName.get(), requestStatus, NS_ConvertUTF16toUTF8(trimmed).get(), + this)); + } + + return aStatus; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::SendThreatHitReport(nsIChannel* aChannel, + const nsACString& aProvider, + const nsACString& aList, + const nsACString& aFullHash) { + NS_ENSURE_ARG_POINTER(aChannel); + + if (aProvider.IsEmpty()) { + LOG(("nsUrlClassifierDBService::SendThreatHitReport missing provider")); + return NS_ERROR_FAILURE; + } + if (aList.IsEmpty()) { + LOG(("nsUrlClassifierDBService::SendThreatHitReport missing list")); + return NS_ERROR_FAILURE; + } + if (aFullHash.IsEmpty()) { + LOG(("nsUrlClassifierDBService::SendThreatHitReport missing fullhash")); + return NS_ERROR_FAILURE; + } + + nsPrintfCString reportUrlPref( + "browser.safebrowsing.provider.%s.dataSharingURL", + PromiseFlatCString(aProvider).get()); + + nsCOMPtr<nsIURLFormatter> formatter( + do_GetService("@mozilla.org/toolkit/URLFormatterService;1")); + if (!formatter) { + return NS_ERROR_UNEXPECTED; + } + + nsString urlStr; + nsresult rv = + formatter->FormatURLPref(NS_ConvertUTF8toUTF16(reportUrlPref), urlStr); + NS_ENSURE_SUCCESS(rv, rv); + + if (urlStr.IsEmpty() || u"about:blank"_ns.Equals(urlStr)) { + LOG(("%s is missing a ThreatHit data reporting URL.", + PromiseFlatCString(aProvider).get())); + return NS_OK; + } + + nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!utilsService)) { + return NS_ERROR_FAILURE; + } + + nsAutoCString reportBody; + rv = + utilsService->MakeThreatHitReport(aChannel, aList, aFullHash, reportBody); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIStringInputStream> sis( + do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID)); + rv = sis->SetData(reportBody.get(), reportBody.Length()); + NS_ENSURE_SUCCESS(rv, rv); + + LOG(("Sending the following ThreatHit report to %s about %s: %s", + PromiseFlatCString(aProvider).get(), PromiseFlatCString(aList).get(), + reportBody.get())); + + nsCOMPtr<nsIURI> reportURI; + rv = NS_NewURI(getter_AddRefs(reportURI), urlStr); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t loadFlags = nsIRequest::LOAD_ANONYMOUS | // no cookies + nsIChannel::INHIBIT_CACHING | + nsIChannel::LOAD_BYPASS_CACHE; + + nsCOMPtr<nsIChannel> reportChannel; + rv = NS_NewChannel(getter_AddRefs(reportChannel), reportURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // aPerformanceStorage + nullptr, // aLoadGroup + nullptr, loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadInfo> loadInfo = reportChannel->LoadInfo(); + mozilla::OriginAttributes attrs; + attrs.mFirstPartyDomain.AssignLiteral(NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN); + loadInfo->SetOriginAttributes(attrs); + + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel)); + NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE); + rv = uploadChannel->SetUploadStream(sis, "application/x-protobuf"_ns, -1); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel)); + NS_ENSURE_TRUE(httpChannel, NS_ERROR_FAILURE); + rv = httpChannel->SetRequestMethod("POST"_ns); + NS_ENSURE_SUCCESS(rv, rv); + // Disable keepalive. + rv = httpChannel->SetRequestHeader("Connection"_ns, "close"_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<ThreatHitReportListener> listener = new ThreatHitReportListener(); + rv = reportChannel->AsyncOpen(listener); + if (NS_FAILED(rv)) { + LOG(("Failure to send Safe Browsing ThreatHit report")); + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal, + const nsACString& aTables, + nsIUrlClassifierCallback* aCallback) { + // We don't expect someone with SystemPrincipal calls this API(See Bug + // 813897). + MOZ_ASSERT(!aPrincipal->IsSystemPrincipal()); + + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + nsTArray<nsCString> tableArray; + Classifier::SplitTables(aTables, tableArray); + + nsCOMPtr<nsIUrlClassifierFeature> feature; + nsresult rv = CreateFeatureWithTables( + "lookup"_ns, tableArray, nsTArray<nsCString>(), getter_AddRefs(feature)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + // Casting to BasePrincipal, as we can't get InnerMost URI otherwise + auto* basePrincipal = BasePrincipal::Cast(aPrincipal); + rv = basePrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + nsTArray<RefPtr<nsIUrlClassifierFeature>> features; + features.AppendElement(feature.get()); + + // Let's keep the features alive and release them on the correct thread. + RefPtr<FeatureHolder> holder = + FeatureHolder::Create(uri, features, nsIUrlClassifierFeature::blocklist); + if (NS_WARN_IF(!holder)) { + return NS_ERROR_FAILURE; + } + + uri = NS_GetInnermostURI(uri); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!utilsService)) { + return NS_ERROR_FAILURE; + } + + nsAutoCString key; + // Canonicalize the url + rv = utilsService->GetKeyForURI(uri, key); + NS_ENSURE_SUCCESS(rv, rv); + + return LookupURI(key, holder, aCallback); +} + +nsresult nsUrlClassifierDBService::LookupURI( + const nsACString& aKey, FeatureHolder* aHolder, + nsIUrlClassifierCallback* aCallback) { + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aCallback); + + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + // Create an nsUrlClassifierLookupCallback object. This object will + // take care of confirming partial hash matches if necessary before + // calling the client's callback. + nsCOMPtr<nsIUrlClassifierLookupCallback> callback = + new nsUrlClassifierLookupCallback(this, aCallback); + + nsCOMPtr<nsIUrlClassifierLookupCallback> proxyCallback = + new UrlClassifierLookupCallbackProxy(callback); + + // Queue this lookup and call the lookup function to flush the queue if + // necessary. + nsresult rv = mWorker->QueueLookup(aKey, aHolder, proxyCallback); + NS_ENSURE_SUCCESS(rv, rv); + + // This seems to just call HandlePendingLookups. + 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. + nsCOMPtr<nsIUrlClassifierCallback> proxyCallback = + new UrlClassifierCallbackProxy(c); + + return mWorkerProxy->GetTables(proxyCallback); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::SetHashCompleter( + const nsACString& tableName, nsIUrlClassifierHashCompleter* completer) { + if (completer) { + mCompleters.InsertOrUpdate(tableName, completer); + } else { + mCompleters.Remove(tableName); + } + ClearLastResults(); + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::ClearLastResults() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->ClearLastResults(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::BeginUpdate(nsIUrlClassifierUpdateObserver* observer, + const nsACString& updateTables) { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + if (mInUpdate) { + LOG(("Already updating, not available")); + return NS_ERROR_NOT_AVAILABLE; + } + + if (mWorker->IsBusyUpdating()) { + // |mInUpdate| used to work well because "notifying update observer" + // is synchronously done in Worker::FinishUpdate(). Even if the + // update observer hasn't been notified at this point, we can still + // dispatch BeginUpdate() since it will NOT be run until the + // previous Worker::FinishUpdate() returns. + // + // However, some tasks in Worker::FinishUpdate() have been moved to + // another thread. The update observer will NOT be notified when + // Worker::FinishUpdate() returns. If we only check |mInUpdate|, + // the following sequence might happen on worker thread: + // + // Worker::FinishUpdate() // for update 1 + // Worker::BeginUpdate() // for update 2 + // Worker::NotifyUpdateObserver() // for update 1 + // + // So, we have to find out a way to reject BeginUpdate() right here + // if the previous update observer hasn't been notified. + // + // Directly probing the worker's state is the most lightweight solution. + // No lock is required since Worker::BeginUpdate() and + // Worker::NotifyUpdateObserver() are by nature mutual exclusive. + // (both run on worker thread.) + LOG(("The previous update observer hasn't been notified.")); + return NS_ERROR_NOT_AVAILABLE; + } + + mInUpdate = true; + + // The proxy observer uses the current thread + nsCOMPtr<nsIUrlClassifierUpdateObserver> proxyObserver = + new UrlClassifierUpdateObserverProxy(observer); + + return mWorkerProxy->BeginUpdate(proxyObserver, updateTables); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::BeginStream(const nsACString& table) { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->BeginStream(table); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::UpdateStream(const nsACString& aUpdateChunk) { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->UpdateStream(aUpdateChunk); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::FinishStream() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->FinishStream(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::FinishUpdate() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + mInUpdate = false; + + return mWorkerProxy->FinishUpdate(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::CancelUpdate() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + mInUpdate = false; + + return mWorkerProxy->CancelUpdate(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::ResetDatabase() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + if (mWorker->IsBusyUpdating()) { + LOG(("Failed to ResetDatabase because of the unfinished update.")); + return NS_ERROR_FAILURE; + } + + return mWorkerProxy->ResetDatabase(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::ReloadDatabase() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + if (mWorker->IsBusyUpdating()) { + LOG(("Failed to ReloadDatabase because of the unfinished update.")); + return NS_ERROR_FAILURE; + } + + return mWorkerProxy->ReloadDatabase(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::ClearCache() { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->ClearCache(); +} + +NS_IMETHODIMP +nsUrlClassifierDBService::GetCacheInfo( + const nsACString& aTable, nsIUrlClassifierGetCacheCallback* aCallback) { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->GetCacheInfo(aTable, aCallback); +} + +nsresult nsUrlClassifierDBService::CacheCompletions( + const ConstCacheResultArray& results) { + NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED); + + return mWorkerProxy->CacheCompletions(results); +} + +bool nsUrlClassifierDBService::CanComplete(const nsACString& aTableName) { + return !mDisallowCompletionsTables.Contains(aTableName); +} + +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 (!CanComplete(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) { + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + ReadDisallowCompletionsTablesFromPrefs(); + } else if (!strcmp(aTopic, "quit-application")) { + // Tell the update thread to finish as soon as possible. + gShuttingDownThread = true; + + // The code in ::Shutdown() is run on a 'profile-before-change' event and + // ensures that objects are freed by blocking on this freeing. + // We can however speed up the shutdown time by using the worker thread to + // release, in an earlier event, any objects that cannot affect an ongoing + // update on the update thread. + PreShutdown(); + } else if (!strcmp(aTopic, "profile-before-change")) { + gShuttingDownThread = true; + Shutdown(); + } else { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +// Post a PreShutdown task to worker thread to release objects without blocking +// main-thread. Notice that shutdown process may still be blocked by PreShutdown +// task when ::Shutdown() is executed and synchronously waits for worker thread +// to finish PreShutdown event. +nsresult nsUrlClassifierDBService::PreShutdown() { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (mWorkerProxy) { + mWorkerProxy->PreShutdown(); + } + + return NS_OK; +} + +// Join the background thread if it exists. +nsresult nsUrlClassifierDBService::Shutdown() { + LOG(("shutting down db service\n")); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!gDbBackgroundThread) { + return NS_OK; + } + + Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_SHUTDOWN_TIME> timer; + + mCompleters.Clear(); + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this); + } + + // 1. Synchronize with worker thread and update thread by + // *synchronously* dispatching an event to worker thread + // for shutting down the update thread. The reason not + // shutting down update thread directly from main thread + // is to avoid racing for Classifier::mUpdateThread + // between main thread and the worker thread. (Both threads + // would access Classifier::mUpdateThread.) + // This event is dispatched unconditionally to avoid + // accessing mWorker->mClassifier on the main thread, which + // would be a race. + using Worker = nsUrlClassifierDBServiceWorker; + RefPtr<nsIRunnable> r = NewRunnableMethod( + "nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate", mWorker, + &Worker::FlushAndDisableAsyncUpdate); + SyncRunnable::DispatchToThread(gDbBackgroundThread, r); + // At this point the update thread has been shut down and + // the worker thread should only have at most one event, + // which is the callback event. + + // 2. Send CancelUpdate() event to notify the dangling update. + // (i.e. BeginUpdate is called but FinishUpdate is not.) + // and CloseDb() to clear mClassifier. They will be the last two + // events on the worker thread in the shutdown process. + DebugOnly<nsresult> rv; + rv = mWorkerProxy->CancelUpdate(); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to post 'cancel update' event"); + rv = mWorkerProxy->CloseDb(); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to post 'close db' event"); + mWorkerProxy = nullptr; + + // 3. Invalidate XPCOM APIs by nulling out gDbBackgroundThread + // since every API checks gDbBackgroundThread first. This has + // to be done before calling nsIThread.shutdown because it + // will cause the pending events on the joining thread to + // be processed. + nsIThread* backgroundThread = nullptr; + std::swap(backgroundThread, gDbBackgroundThread); + + // 4. Wait until the worker thread is down. + if (backgroundThread) { + backgroundThread->Shutdown(); + NS_RELEASE(backgroundThread); + } + + mWorker = nullptr; + return NS_OK; +} + +nsIThread* nsUrlClassifierDBService::BackgroundThread() { + return gDbBackgroundThread; +} + +// static +bool nsUrlClassifierDBService::ShutdownHasStarted() { + return gShuttingDownThread; +} + +// static +nsUrlClassifierDBServiceWorker* nsUrlClassifierDBService::GetWorker() { + nsresult rv; + RefPtr<nsUrlClassifierDBService> service = + nsUrlClassifierDBService::GetInstance(&rv); + if (!service) { + return nullptr; + } + + return service->mWorker; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures( + nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeatureCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + if (gShuttingDownThread) { + return NS_ERROR_ABORT; + } + + nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + // Let's try to use the preferences. + if (AsyncClassifyLocalWithFeaturesUsingPreferences(uri, aFeatures, aListType, + aCallback)) { + return NS_OK; + } + + nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!utilsService)) { + return NS_ERROR_FAILURE; + } + + nsAutoCString key; + // Canonicalize the url + nsresult rv = utilsService->GetKeyForURI(uri, key); + NS_ENSURE_SUCCESS(rv, rv); + + if (XRE_IsContentProcess()) { + using namespace mozilla::dom; + using namespace mozilla::ipc; + + ContentChild* content = ContentChild::GetSingleton(); + if (NS_WARN_IF(!content || content->IsShuttingDown())) { + return NS_ERROR_FAILURE; + } + + auto actor = new URLClassifierLocalChild(); + + nsTArray<IPCURLClassifierFeature> ipcFeatures; + for (nsIUrlClassifierFeature* feature : aFeatures) { + nsAutoCString name; + rv = feature->GetName(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + nsTArray<nsCString> tables; + rv = feature->GetTables(aListType, tables); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + nsAutoCString exceptionHostList; + if (aListType == nsIUrlClassifierFeature::blocklist) { + rv = feature->GetExceptionHostList(exceptionHostList); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + } + + ipcFeatures.AppendElement( + IPCURLClassifierFeature(name, tables, exceptionHostList)); + } + + if (!content->SendPURLClassifierLocalConstructor(actor, aURI, + ipcFeatures)) { + return NS_ERROR_FAILURE; + } + + actor->SetFeaturesAndCallback(aFeatures, aCallback); + return NS_OK; + } + + using namespace mozilla::Telemetry; + auto startTime = TimeStamp::Now(); // For telemetry. + + // Let's keep the features alive and release them on the correct thread. + RefPtr<FeatureHolder> holder = + FeatureHolder::Create(aURI, aFeatures, aListType); + if (NS_WARN_IF(!holder)) { + return NS_ERROR_FAILURE; + } + + auto worker = mWorker; + + // Since aCallback will be passed around threads... + nsMainThreadPtrHandle<nsIUrlClassifierFeatureCallback> callback( + new nsMainThreadPtrHolder<nsIUrlClassifierFeatureCallback>( + "nsIURIClassifierFeatureCallback", aCallback)); + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures", + [worker, key, holder, callback, startTime]() -> void { + holder->DoLocalLookup(key, worker); + + nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction( + "nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures", + [callback, holder, startTime]() -> void { + // Measure the time diff between calling and callback. + AccumulateTimeDelta( + Telemetry::URLCLASSIFIER_ASYNC_CLASSIFYLOCAL_TIME, startTime); + + nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> results; + holder->GetResults(results); + + // |callback| is captured as const value so ... + auto cb = + const_cast<nsIUrlClassifierFeatureCallback*>(callback.get()); + cb->OnClassifyComplete(results); + }); + + NS_DispatchToMainThread(cbRunnable); + }); + + return gDbBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL); +} + +bool nsUrlClassifierDBService::AsyncClassifyLocalWithFeaturesUsingPreferences( + nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeatureCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + nsAutoCString host; + nsresult rv = aURI->GetHost(host); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> results; + + // Let's see if we have special entries set by prefs. + for (nsIUrlClassifierFeature* feature : aFeatures) { + bool found = false; + + nsAutoCString tableName; + rv = feature->HasHostInPreferences(host, aListType, tableName, &found); + NS_ENSURE_SUCCESS(rv, false); + + if (found) { + MOZ_ASSERT(!tableName.IsEmpty()); + LOG(("URI found in preferences. Table: %s", tableName.get())); + + RefPtr<mozilla::net::UrlClassifierFeatureResult> result = + new mozilla::net::UrlClassifierFeatureResult(aURI, feature, + tableName); + results.AppendElement(result); + } + } + + if (results.IsEmpty()) { + return false; + } + + // If we have some match using the preferences, we don't need to continue. + nsCOMPtr<nsIUrlClassifierFeatureCallback> callback(aCallback); + nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction( + "nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures", + [callback, results = std::move(results)]() { + callback->OnClassifyComplete(results); + }); + + NS_DispatchToMainThread(cbRunnable); + return true; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::GetFeatureByName(const nsACString& aFeatureName, + nsIUrlClassifierFeature** aFeature) { + NS_ENSURE_ARG_POINTER(aFeature); + nsCOMPtr<nsIUrlClassifierFeature> feature = + mozilla::net::UrlClassifierFeatureFactory::GetFeatureByName(aFeatureName); + if (NS_WARN_IF(!feature)) { + return NS_ERROR_FAILURE; + } + + feature.forget(aFeature); + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::GetFeatureNames(nsTArray<nsCString>& aArray) { + mozilla::net::UrlClassifierFeatureFactory::GetFeatureNames(aArray); + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierDBService::CreateFeatureWithTables( + const nsACString& aName, const nsTArray<nsCString>& aBlocklistTables, + const nsTArray<nsCString>& aEntitylistTables, + nsIUrlClassifierFeature** aFeature) { + NS_ENSURE_ARG_POINTER(aFeature); + nsCOMPtr<nsIUrlClassifierFeature> feature = + mozilla::net::UrlClassifierFeatureFactory::CreateFeatureWithTables( + aName, aBlocklistTables, aEntitylistTables); + if (NS_WARN_IF(!feature)) { + return NS_ERROR_FAILURE; + } + + feature.forget(aFeature); + return NS_OK; +} diff --git a/toolkit/components/url-classifier/nsUrlClassifierDBService.h b/toolkit/components/url-classifier/nsUrlClassifierDBService.h new file mode 100644 index 0000000000..462d9a7b9d --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h @@ -0,0 +1,284 @@ +//* -*- 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/. */ + +#ifndef nsUrlClassifierDBService_h_ +#define nsUrlClassifierDBService_h_ + +#include <nsISupportsUtils.h> + +#include "nsID.h" +#include "nsInterfaceHashtable.h" +#include "nsIObserver.h" +#include "nsUrlClassifierPrefixSet.h" +#include "nsIUrlClassifierHashCompleter.h" +#include "nsIUrlClassifierDBService.h" +#include "nsIUrlClassifierInfo.h" +#include "nsIURIClassifier.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +#include "Entries.h" +#include "LookupCache.h" +#include "HashStore.h" + +// The hash length for a domain key. +#define DOMAIN_LENGTH 4 + +// The hash length of a partial hash entry. +#define PARTIAL_LENGTH 4 + +// The hash length of a complete hash entry. +#define COMPLETE_LENGTH 32 + +// Comma-separated lists +#define DISALLOW_COMPLETION_TABLE_PREF "urlclassifier.disallow_completions" + +using namespace mozilla::safebrowsing; + +class nsUrlClassifierDBServiceWorker; +class nsIThread; +class nsIURI; +class UrlClassifierDBServiceWorkerProxy; + +namespace mozilla { + +namespace safebrowsing { +class Classifier; +class ProtocolParser; + +nsresult TablesToResponse(const nsACString& tables); + +} // namespace safebrowsing + +namespace net { +class AsyncUrlChannelClassifier; +} + +} // namespace mozilla + +// This is a proxy class that just creates a background thread and delegates +// calls to the background thread. +class nsUrlClassifierDBService final : public nsIUrlClassifierDBService, + public nsIURIClassifier, + public nsIUrlClassifierInfo, + public nsIObserver { + friend class mozilla::net::AsyncUrlChannelClassifier; + + public: + class FeatureHolder; + + // This is thread safe. It throws an exception if the thread is busy. + nsUrlClassifierDBService(); + + nsresult Init(); + + static already_AddRefed<nsUrlClassifierDBService> GetInstance( + nsresult* result); + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_URLCLASSIFIERDBSERVICE_CID) + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERDBSERVICE + NS_DECL_NSIURICLASSIFIER + NS_DECL_NSIURLCLASSIFIERINFO + NS_DECL_NSIOBSERVER + + bool CanComplete(const nsACString& tableName); + bool GetCompleter(const nsACString& tableName, + nsIUrlClassifierHashCompleter** completer); + nsresult CacheCompletions( + const mozilla::safebrowsing::ConstCacheResultArray& results); + + static nsIThread* BackgroundThread(); + + static bool ShutdownHasStarted(); + + private: + // This method is used only by AsyncUrlChannelClassifier. If you want to use + // it, please contact a safebrowsing/URL-Classifier peer. + static nsUrlClassifierDBServiceWorker* GetWorker(); + + // No subclassing + ~nsUrlClassifierDBService(); + + // Disallow copy constructor + nsUrlClassifierDBService(nsUrlClassifierDBService&); + + nsresult LookupURI(const nsACString& aKey, FeatureHolder* aHolder, + nsIUrlClassifierCallback* c); + + // Post an event to worker thread to release objects when receive + // 'quit-application' + nsresult PreShutdown(); + + // Close db connection and join the background thread if it exists. + nsresult Shutdown(); + + nsresult ReadDisallowCompletionsTablesFromPrefs(); + + // This method checks if the classification can be done just using + // preferences. It returns true if the operation has been completed. + bool AsyncClassifyLocalWithFeaturesUsingPreferences( + nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeatureCallback* aCallback); + + RefPtr<nsUrlClassifierDBServiceWorker> mWorker; + RefPtr<UrlClassifierDBServiceWorkerProxy> mWorkerProxy; + + nsInterfaceHashtable<nsCStringHashKey, nsIUrlClassifierHashCompleter> + mCompleters; + + // TRUE if a BeginUpdate() has been called without an accompanying + // 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 should never be hash completed. + nsTArray<nsCString> mDisallowCompletionsTables; + + // Thread that we do the updates on. + static nsIThread* gDbBackgroundThread; +}; + +class nsUrlClassifierDBServiceWorker final : public nsIUrlClassifierDBService { + public: + nsUrlClassifierDBServiceWorker(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERDBSERVICE + + nsresult Init(uint32_t aGethashNoise, nsCOMPtr<nsIFile> aCacheDir, + nsUrlClassifierDBService* aDBService); + + // Queue a lookup for the worker to perform, called in the main thread. + nsresult QueueLookup(const nsACString& aLookupKey, + nsUrlClassifierDBService::FeatureHolder* aFeatureHolder, + nsIUrlClassifierLookupCallback* aLallback); + + // Handle any queued-up lookups. We call this function during long-running + // update operations to prevent lookups from blocking for too long. + nsresult HandlePendingLookups(); + + // Perform a blocking classifier lookup for a given url fragments set. + // Can be called on either the main thread or the worker thread. + nsresult DoSingleLocalLookupWithURIFragments( + const nsTArray<nsCString>& aSpecFragments, const nsACString& aTable, + LookupResultArray& aResults); + + // Open the DB connection + nsresult OpenDb(); + + // Provide a way to forcibly close the db connection. + nsresult CloseDb(); + + nsresult PreShutdown(); + + nsresult CacheCompletions(const ConstCacheResultArray& aEntries); + + // Used to probe the state of the worker thread. When the update begins, + // mUpdateObserver will be set. When the update finished, mUpdateObserver + // will be nulled out in NotifyUpdateObserver. + bool IsBusyUpdating() { + mozilla::MutexAutoLock lock(mUpdateObserverLock); + return !!mUpdateObserver; + } + + // Delegate Classifier to disable async update. If there is an + // ongoing update on the update thread, we will be blocked until + // the background update is done and callback is fired. + // Should be called on the worker thread. + void FlushAndDisableAsyncUpdate(); + + // A synchronous call to get cache information for the given table. + // This is only used by about:url-classifier now. + nsresult GetCacheInfo(const nsACString& aTable, + nsIUrlClassifierCacheInfo** aCache); + + private: + // No subclassing + ~nsUrlClassifierDBServiceWorker(); + + // Disallow copy constructor + nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&); + + nsresult NotifyUpdateObserver(nsresult aUpdateStatus); + + // 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, + nsUrlClassifierDBService::FeatureHolder* aFeatureHolder, + nsIUrlClassifierLookupCallback* c); + + nsresult AddNoise(const Prefix aPrefix, const nsCString tableName, + uint32_t aCount, LookupResultArray& results); + + nsresult CacheResultToTableUpdate(RefPtr<const CacheResult> aCacheResult, + RefPtr<TableUpdate> aUpdate); + + bool IsSameAsLastResults(const ConstCacheResultArray& aResult) const; + + RefPtr<mozilla::safebrowsing::Classifier> mClassifier; + // The class that actually parses the update chunks. + mozilla::UniquePtr<ProtocolParser> mProtocolParser; + + // Directory where to store the SB databases. + nsCOMPtr<nsIFile> mCacheDir; + + RefPtr<nsUrlClassifierDBService> mDBService; + + TableUpdateArray mTableUpdates; + + uint32_t mUpdateWaitSec; + + // Stores the last results that triggered a table update. + ConstCacheResultArray mLastResults; + + nsresult mUpdateStatus; + nsTArray<nsCString> mUpdateTables; + + // The mUpdateObserver will be accessed by both the main thread and the worker + // thread. The lock is used to protect the mUpdateObserver. + mozilla::Mutex mUpdateObserverLock; + + nsCOMPtr<nsIUrlClassifierUpdateObserver> mUpdateObserver + MOZ_GUARDED_BY(mUpdateObserverLock); + bool mInStream; + + // The number of noise entries to add to the set of lookup results. + uint32_t mGethashNoise; + + // Pending lookups are stored in a queue for processing. The queue + // is protected by mPendingLookupLock. + mozilla::Mutex mPendingLookupLock MOZ_UNANNOTATED; + + class PendingLookup { + public: + mozilla::TimeStamp mStartTime; + nsCString mKey; + RefPtr<nsUrlClassifierDBService::FeatureHolder> mFeatureHolder; + nsCOMPtr<nsIUrlClassifierLookupCallback> mCallback; + }; + + // list of pending lookups + nsTArray<PendingLookup> mPendingLookups; + +#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES + // The raw update response for debugging. + nsCString mRawTableUpdates; +#endif +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, + NS_URLCLASSIFIERDBSERVICE_CID) + +#endif // nsUrlClassifierDBService_h_ diff --git a/toolkit/components/url-classifier/nsUrlClassifierInfo.cpp b/toolkit/components/url-classifier/nsUrlClassifierInfo.cpp new file mode 100644 index 0000000000..afc9d1d920 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierInfo.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsComponentManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsUrlClassifierInfo.h" + +NS_IMPL_ISUPPORTS(nsUrlClassifierPositiveCacheEntry, + nsIUrlClassifierPositiveCacheEntry) + +nsUrlClassifierPositiveCacheEntry::nsUrlClassifierPositiveCacheEntry() + : expirySec(-1) {} + +NS_IMETHODIMP +nsUrlClassifierPositiveCacheEntry::GetExpiry(int64_t* aExpiry) { + if (!aExpiry) { + return NS_ERROR_NULL_POINTER; + } + + *aExpiry = expirySec; + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierPositiveCacheEntry::GetFullhash(nsACString& aFullHash) { + aFullHash = fullhash; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsUrlClassifierCacheEntry, nsIUrlClassifierCacheEntry) + +nsUrlClassifierCacheEntry::nsUrlClassifierCacheEntry() : expirySec(-1) {} + +NS_IMETHODIMP +nsUrlClassifierCacheEntry::GetPrefix(nsACString& aPrefix) { + aPrefix = prefix; + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierCacheEntry::GetExpiry(int64_t* aExpiry) { + if (!aExpiry) { + return NS_ERROR_NULL_POINTER; + } + + *aExpiry = expirySec; + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierCacheEntry::GetMatches(nsIArray** aMatches) { + if (!aMatches) { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + for (uint32_t i = 0; i < matches.Length(); i++) { + array->AppendElement(matches[i]); + } + + NS_ADDREF(*aMatches = array); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsUrlClassifierCacheInfo, nsIUrlClassifierCacheInfo) + +nsUrlClassifierCacheInfo::nsUrlClassifierCacheInfo() = default; + +NS_IMETHODIMP +nsUrlClassifierCacheInfo::GetTable(nsACString& aTable) { + aTable = table; + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierCacheInfo::GetEntries(nsIArray** aEntries) { + if (!aEntries) { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + for (uint32_t i = 0; i < entries.Length(); i++) { + array->AppendElement(entries[i]); + } + + NS_ADDREF(*aEntries = array); + + return NS_OK; +} diff --git a/toolkit/components/url-classifier/nsUrlClassifierInfo.h b/toolkit/components/url-classifier/nsUrlClassifierInfo.h new file mode 100644 index 0000000000..3eec17bce8 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierInfo.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef nsUrlClassifierInfo_h_ +#define nsUrlClassifierInfo_h_ + +#include "nsIUrlClassifierInfo.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +class nsUrlClassifierPositiveCacheEntry final + : public nsIUrlClassifierPositiveCacheEntry { + public: + nsUrlClassifierPositiveCacheEntry(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERPOSITIVECACHEENTRY + + private: + ~nsUrlClassifierPositiveCacheEntry() = default; + + public: + nsCString fullhash; + + int64_t expirySec; +}; + +class nsUrlClassifierCacheEntry final : public nsIUrlClassifierCacheEntry { + public: + nsUrlClassifierCacheEntry(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERCACHEENTRY + + private: + ~nsUrlClassifierCacheEntry() = default; + + public: + nsCString prefix; + + int64_t expirySec; + + nsTArray<nsCOMPtr<nsIUrlClassifierPositiveCacheEntry>> matches; +}; + +class nsUrlClassifierCacheInfo final : public nsIUrlClassifierCacheInfo { + public: + nsUrlClassifierCacheInfo(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERCACHEINFO + + private: + ~nsUrlClassifierCacheInfo() = default; + + public: + nsCString table; + + nsTArray<nsCOMPtr<nsIUrlClassifierCacheEntry>> entries; +}; + +#endif // nsUrlClassifierInfo_h_ diff --git a/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp new file mode 100644 index 0000000000..20b55ea150 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp @@ -0,0 +1,597 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsUrlClassifierPrefixSet.h" +#include "nsIUrlClassifierPrefixSet.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsTArray.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "mozilla/StaticPrefs_browser.h" + +using namespace mozilla; + +// MOZ_LOG=UrlClassifierPrefixSet:5 +static LazyLogModule gUrlClassifierPrefixSetLog("UrlClassifierPrefixSet"); +#define LOG(args) \ + MOZ_LOG(gUrlClassifierPrefixSetLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gUrlClassifierPrefixSetLog, mozilla::LogLevel::Debug) + +NS_IMPL_ISUPPORTS(nsUrlClassifierPrefixSet, nsIUrlClassifierPrefixSet) + +nsUrlClassifierPrefixSet::nsUrlClassifierPrefixSet() + : mLock("nsUrlClassifierPrefixSet.mLock"), mTotalPrefixes(0) {} + +NS_IMETHODIMP +nsUrlClassifierPrefixSet::Init(const nsACString& aName) { + mName = aName; + + return NS_OK; +} + +nsUrlClassifierPrefixSet::~nsUrlClassifierPrefixSet() { + for (uint32_t i = 0; i < mIndexDeltas.Length(); i++) { + mIndexDeltas[i].Clear(); + } + mIndexDeltas.Clear(); + mIndexPrefixes.Clear(); +} + +void nsUrlClassifierPrefixSet::Clear() { + LOG(("[%s] Clearing PrefixSet", mName.get())); + mIndexDeltas.Clear(); + mIndexPrefixes.Clear(); + mTotalPrefixes = 0; +} + +NS_IMETHODIMP +nsUrlClassifierPrefixSet::SetPrefixes(const uint32_t* aArray, + uint32_t aLength) { + MutexAutoLock lock(mLock); + + nsresult rv = NS_OK; + Clear(); + + if (aLength > 0) { + rv = MakePrefixSet(aArray, aLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + Clear(); // clear out any leftovers + } + } + + MOZ_ASSERT_IF(mIndexDeltas.IsEmpty(), + mIndexPrefixes.Length() == mTotalPrefixes); + MOZ_ASSERT_IF(!mIndexDeltas.IsEmpty(), + mIndexPrefixes.Length() == mIndexDeltas.Length()); + return rv; +} + +nsresult nsUrlClassifierPrefixSet::MakePrefixSet(const uint32_t* aPrefixes, + uint32_t aLength) { + mLock.AssertCurrentThreadOwns(); + + MOZ_ASSERT(aPrefixes); + MOZ_ASSERT(aLength > 0); + +#ifdef DEBUG + for (uint32_t i = 1; i < aLength; i++) { + MOZ_ASSERT(aPrefixes[i] >= aPrefixes[i - 1]); + } +#endif + + uint32_t totalDeltas = 0; + + // Request one memory space to store all the prefixes may lead to + // memory allocation failure on certain platforms(See Bug 1046038). + // So if required size to store all the prefixes exceeds defined + // threshold(512k), we divide prefixes into delta chunks instead. Note that + // the potential overhead of this approach is that it may reuqire more memory + // compared to store all prefixes in one array because of jemalloc's + // implementation. + if (aLength * sizeof(uint32_t) < + StaticPrefs::browser_safebrowsing_prefixset_max_array_size()) { + // Not over the threshold, store all prefixes into mIndexPrefixes. + // mIndexDeltas is empty in this case. + mIndexPrefixes.SetCapacity(aLength); + for (uint32_t i = 0; i < aLength; i++) { + mIndexPrefixes.AppendElement(aPrefixes[i]); + } + } else { + // Apply delta algorithm to split prefixes into smaller delta chunk. + + // We estimate the capacity of mIndexPrefixes & mIndexDeltas by assuming + // each element in mIndexDeltas stores DELTAS_LIMITS deltas, so the + // number of indexed prefixes is round up of + // TotalPrefixes / (DELTA_LIMIT + 1) + // The estimation only works when the number of prefixes are over a + // certain limit, which means, arrays in mIndexDeltas are always full. + uint32_t estimateCapacity = + (aLength + (DELTAS_LIMIT + 1) - 1) / (DELTAS_LIMIT + 1); + mIndexPrefixes.SetCapacity(estimateCapacity); + mIndexDeltas.SetCapacity(estimateCapacity); + + mIndexPrefixes.AppendElement(aPrefixes[0]); + mIndexDeltas.AppendElement(); + mIndexDeltas.LastElement().SetCapacity(DELTAS_LIMIT); + + uint32_t numOfDeltas = 0; + uint32_t previousItem = aPrefixes[0]; + for (uint32_t i = 1; i < aLength; i++) { + if ((numOfDeltas >= DELTAS_LIMIT) || + (aPrefixes[i] - previousItem >= MAX_INDEX_DIFF)) { + // Compact the previous element. + // Note there is always at least one element when we get here, + // because we created the first element before the loop. + mIndexDeltas.LastElement().Compact(); + if (!mIndexDeltas.AppendElement(fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mIndexDeltas.LastElement().SetCapacity(DELTAS_LIMIT); + + if (!mIndexPrefixes.AppendElement(aPrefixes[i], fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + numOfDeltas = 0; + } else { + uint16_t delta = aPrefixes[i] - previousItem; + if (!mIndexDeltas.LastElement().AppendElement(delta, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + numOfDeltas++; + totalDeltas++; + } + previousItem = aPrefixes[i]; + } + + mIndexDeltas.LastElement().Compact(); + mIndexDeltas.Compact(); + mIndexPrefixes.Compact(); + + MOZ_ASSERT(mIndexPrefixes.Length() == mIndexDeltas.Length()); + } + + if (totalDeltas == 0) { + // We have to clear mIndexDeltas here because it is still possible + // that the delta generation algorithm produces no deltas at all. When that + // happens, mIndexDeltas is not empty, which conflicts with the assumption + // that when there is no delta, mIndexDeltas is empty. + mIndexDeltas.Clear(); + } + mTotalPrefixes = aLength; + + LOG(("Total number of indices: %d", aLength)); + LOG(("Total number of deltas: %d", totalDeltas)); + LOG(("Total number of delta chunks: %zu", mIndexDeltas.Length())); + + return NS_OK; +} + +nsresult nsUrlClassifierPrefixSet::GetPrefixesNative( + FallibleTArray<uint32_t>& aOutArray) { + MutexAutoLock lock(mLock); + + if (!aOutArray.SetLength(mTotalPrefixes, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t prefixIdxLength = mIndexPrefixes.Length(); + uint32_t prefixCnt = 0; + + for (uint32_t i = 0; i < prefixIdxLength; i++) { + uint32_t prefix = mIndexPrefixes[i]; + + if (prefixCnt >= mTotalPrefixes) { + return NS_ERROR_FAILURE; + } + aOutArray[prefixCnt++] = prefix; + + if (mIndexDeltas.IsEmpty()) { + continue; + } + + for (uint32_t j = 0; j < mIndexDeltas[i].Length(); j++) { + prefix += mIndexDeltas[i][j]; + if (prefixCnt >= mTotalPrefixes) { + return NS_ERROR_FAILURE; + } + aOutArray[prefixCnt++] = prefix; + } + } + + NS_ASSERTION(mTotalPrefixes == prefixCnt, "Lengths are inconsistent"); + return NS_OK; +} + +nsresult nsUrlClassifierPrefixSet::GetPrefixByIndex( + uint32_t aIndex, uint32_t* aOutPrefix) const { + NS_ENSURE_ARG_POINTER(aOutPrefix); + + MutexAutoLock lock(mLock); + MOZ_ASSERT(aIndex < mTotalPrefixes); + + // We can directly get the target index if the delta algorithm didn't apply. + if (mIndexDeltas.IsEmpty()) { + *aOutPrefix = mIndexPrefixes[aIndex]; + return NS_OK; + } + + // The prefix set was compressed by the delta algorithm, we have to iterate + // the delta index to find out the right bucket. + for (uint32_t i = 0; i < mIndexDeltas.Length(); i++) { + // The target index is in the current delta bucket. + if (aIndex <= mIndexDeltas[i].Length()) { + MOZ_ASSERT(aIndex <= DELTAS_LIMIT); + + uint32_t prefix = mIndexPrefixes[i]; + + for (uint32_t j = 0; j < aIndex; j++) { + prefix += mIndexDeltas[i][j]; + } + + *aOutPrefix = prefix; + break; + } + + aIndex -= mIndexDeltas[i].Length() + 1; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierPrefixSet::GetPrefixes(uint32_t* aCount, uint32_t** aPrefixes) { + // No need to get mLock here because this function does not directly touch + // the class's data members. (GetPrefixesNative() will get mLock, however.) + + NS_ENSURE_ARG_POINTER(aCount); + *aCount = 0; + NS_ENSURE_ARG_POINTER(aPrefixes); + *aPrefixes = nullptr; + + FallibleTArray<uint32_t> prefixes; + nsresult rv = GetPrefixesNative(prefixes); + if (NS_FAILED(rv)) { + return rv; + } + + uint64_t itemCount = prefixes.Length(); + uint32_t* prefixArray = + static_cast<uint32_t*>(moz_xmalloc(itemCount * sizeof(uint32_t))); + + memcpy(prefixArray, prefixes.Elements(), sizeof(uint32_t) * itemCount); + + *aCount = itemCount; + *aPrefixes = prefixArray; + + return NS_OK; +} + +uint32_t nsUrlClassifierPrefixSet::BinSearch(uint32_t start, uint32_t end, + uint32_t target) const { + mLock.AssertCurrentThreadOwns(); + + while (start != end && end >= start) { + uint32_t i = start + ((end - start) >> 1); + uint32_t value = mIndexPrefixes[i]; + if (value < target) { + start = i + 1; + } else if (value > target) { + end = i - 1; + } else { + return i; + } + } + return end; +} + +NS_IMETHODIMP +nsUrlClassifierPrefixSet::Contains(uint32_t aPrefix, bool* aFound) { + MutexAutoLock lock(mLock); + + *aFound = false; + + if (IsEmptyInternal()) { + return NS_OK; + } + + uint32_t target = aPrefix; + + // We want to do a "Price is Right" binary search, that is, we want to find + // the index of the value either equal to the target or the closest value + // that is less than the target. + // + if (target < mIndexPrefixes[0]) { + return NS_OK; + } + + // |binsearch| does not necessarily return the correct index (when the + // target is not found) but rather it returns an index at least one away + // from the correct index. + // Because of this, we need to check if the target lies before the beginning + // of the indices. + + uint32_t i = BinSearch(0, mIndexPrefixes.Length() - 1, target); + if (mIndexPrefixes[i] > target && i > 0) { + i--; + } + + // Now search through the deltas for the target. + uint32_t diff = target - mIndexPrefixes[i]; + + if (!mIndexDeltas.IsEmpty()) { + uint32_t deltaSize = mIndexDeltas[i].Length(); + uint32_t deltaIndex = 0; + + while (diff > 0 && deltaIndex < deltaSize) { + diff -= mIndexDeltas[i][deltaIndex]; + deltaIndex++; + } + } + + if (diff == 0) { + *aFound = true; + } + + return NS_OK; +} + +size_t nsUrlClassifierPrefixSet::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + MutexAutoLock lock(mLock); + + size_t n = 0; + n += aMallocSizeOf(this); + n += mIndexDeltas.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mIndexDeltas.Length(); i++) { + n += mIndexDeltas[i].ShallowSizeOfExcludingThis(aMallocSizeOf); + } + n += mIndexPrefixes.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +bool nsUrlClassifierPrefixSet::IsEmptyInternal() const { + if (mIndexPrefixes.IsEmpty()) { + MOZ_ASSERT(mIndexDeltas.IsEmpty() && mTotalPrefixes == 0, + "If we're empty, there should be no leftovers."); + return true; + } + + MOZ_ASSERT(mTotalPrefixes >= mIndexPrefixes.Length()); + return false; +} + +NS_IMETHODIMP +nsUrlClassifierPrefixSet::IsEmpty(bool* aEmpty) { + MutexAutoLock lock(mLock); + + *aEmpty = IsEmptyInternal(); + return NS_OK; +} + +nsresult nsUrlClassifierPrefixSet::LoadPrefixes(nsCOMPtr<nsIInputStream>& in) { + MutexAutoLock lock(mLock); + + mCanary.Check(); + Clear(); + + uint32_t magic; + uint32_t read; + + nsresult rv = + in->Read(reinterpret_cast<char*>(&magic), sizeof(uint32_t), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE); + + if (magic == PREFIXSET_VERSION_MAGIC) { + // Read the number of indexed prefixes + uint32_t indexSize; + rv = in->Read(reinterpret_cast<char*>(&indexSize), sizeof(uint32_t), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE); + + // Read the number of delta prefixes + uint32_t deltaSize; + rv = in->Read(reinterpret_cast<char*>(&deltaSize), sizeof(uint32_t), &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE); + + if (indexSize == 0) { + LOG(("[%s] Stored PrefixSet is empty!", mName.get())); + return NS_OK; + } + + if (!mIndexPrefixes.SetLength(indexSize, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mTotalPrefixes = indexSize; + if (deltaSize > (indexSize * DELTAS_LIMIT)) { + return NS_ERROR_FILE_CORRUPTED; + } + + // Read index prefixes + uint32_t toRead = indexSize * sizeof(uint32_t); + rv = in->Read(reinterpret_cast<char*>(mIndexPrefixes.Elements()), toRead, + &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == toRead, NS_ERROR_FAILURE); + + if (deltaSize) { + nsTArray<uint32_t> indexStarts; + + if (!indexStarts.SetLength(indexSize, fallible) || + !mIndexDeltas.SetLength(indexSize, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Read index start array to construct mIndexDeltas + rv = in->Read(reinterpret_cast<char*>(indexStarts.Elements()), toRead, + &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == toRead, NS_ERROR_FAILURE); + + if (indexStarts[0] != 0) { + return NS_ERROR_FILE_CORRUPTED; + } + + for (uint32_t i = 0; i < indexSize; i++) { + uint32_t numInDelta = i == indexSize - 1 + ? deltaSize - indexStarts[i] + : indexStarts[i + 1] - indexStarts[i]; + if (numInDelta > DELTAS_LIMIT) { + return NS_ERROR_FILE_CORRUPTED; + } + if (numInDelta > 0) { + if (!mIndexDeltas[i].SetLength(numInDelta, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mTotalPrefixes += numInDelta; + toRead = numInDelta * sizeof(uint16_t); + rv = in->Read(reinterpret_cast<char*>(mIndexDeltas[i].Elements()), + toRead, &read); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(read == toRead, NS_ERROR_FAILURE); + } + } + } else { + mIndexDeltas.Clear(); + } + } else { + LOG(("[%s] Version magic mismatch, not loading", mName.get())); + return NS_ERROR_FILE_CORRUPTED; + } + + MOZ_ASSERT_IF(mIndexDeltas.IsEmpty(), + mIndexPrefixes.Length() == mTotalPrefixes); + MOZ_ASSERT_IF(!mIndexDeltas.IsEmpty(), + mIndexPrefixes.Length() == mIndexDeltas.Length()); + LOG(("[%s] Loading PrefixSet successful (%u total prefixes)", mName.get(), + mTotalPrefixes)); + + return NS_OK; +} + +uint32_t nsUrlClassifierPrefixSet::CalculatePreallocateSize() const { + uint32_t fileSize = 4 * sizeof(uint32_t); + MutexAutoLock lock(mLock); + + MOZ_RELEASE_ASSERT(mTotalPrefixes >= mIndexPrefixes.Length()); + uint32_t deltas = mTotalPrefixes - mIndexPrefixes.Length(); + fileSize += mIndexPrefixes.Length() * sizeof(uint32_t); + if (deltas) { + MOZ_ASSERT(mIndexPrefixes.Length() == mIndexDeltas.Length()); + + fileSize += mIndexPrefixes.Length() * sizeof(uint32_t); + fileSize += mIndexDeltas.Length() * sizeof(uint32_t); + fileSize += deltas * sizeof(uint16_t); + } + return fileSize; +} + +uint32_t nsUrlClassifierPrefixSet::Length() const { + MutexAutoLock lock(mLock); + + return mTotalPrefixes; +} + +nsresult nsUrlClassifierPrefixSet::WritePrefixes( + nsCOMPtr<nsIOutputStream>& out) const { + MutexAutoLock lock(mLock); + + MOZ_ASSERT_IF(mIndexDeltas.IsEmpty(), + mIndexPrefixes.Length() == mTotalPrefixes); + MOZ_ASSERT_IF(!mIndexDeltas.IsEmpty(), + mIndexPrefixes.Length() == mIndexDeltas.Length()); + + mCanary.Check(); + + uint32_t written; + uint32_t writelen = sizeof(uint32_t); + const uint32_t magic = PREFIXSET_VERSION_MAGIC; + nsresult rv = + out->Write(reinterpret_cast<const char*>(&magic), writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + const uint32_t indexSize = mIndexPrefixes.Length(); + if (mIndexDeltas.IsEmpty()) { + if (NS_WARN_IF(mTotalPrefixes != indexSize)) { + LOG(("[%s] mIndexPrefixes doesn't have the same length as mTotalPrefixes", + mName.get())); + return NS_ERROR_FAILURE; + } + } else { + if (NS_WARN_IF(mIndexDeltas.Length() != indexSize)) { + LOG(("[%s] mIndexPrefixes doesn't have the same length as mIndexDeltas", + mName.get())); + return NS_ERROR_FAILURE; + } + } + uint32_t totalDeltas = 0; + + // Store the shape of mIndexDeltas by noting at which "count" of total + // indexes a new subarray starts. This is slightly cumbersome but keeps + // file format compatibility. + // If we ever update the format, we can gain space by storing the delta + // subarray sizes, which fit in bytes. + nsTArray<uint32_t> indexStarts; + if (!mIndexDeltas.IsEmpty()) { + if (!indexStarts.SetCapacity(indexSize + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + indexStarts.AppendElement(0); + + for (uint32_t i = 0; i < indexSize; i++) { + uint32_t deltaLength = mIndexDeltas[i].Length(); + totalDeltas += deltaLength; + indexStarts.AppendElement(totalDeltas); + } + indexStarts.RemoveLastElement(); // we don't use the last element + MOZ_ASSERT(indexStarts.Length() == indexSize); + } + + rv = + out->Write(reinterpret_cast<const char*>(&indexSize), writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + rv = out->Write(reinterpret_cast<const char*>(&totalDeltas), writelen, + &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + writelen = indexSize * sizeof(uint32_t); + rv = out->Write(reinterpret_cast<const char*>(mIndexPrefixes.Elements()), + writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + if (!mIndexDeltas.IsEmpty()) { + MOZ_ASSERT(!indexStarts.IsEmpty() && totalDeltas > 0); + rv = out->Write(reinterpret_cast<const char*>(indexStarts.Elements()), + writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + + for (uint32_t i = 0; i < indexSize; i++) { + writelen = mIndexDeltas[i].Length() * sizeof(uint16_t); + rv = out->Write(reinterpret_cast<const char*>(mIndexDeltas[i].Elements()), + writelen, &written); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE); + } + } + + LOG(("[%s] Writing PrefixSet successful", mName.get())); + + return NS_OK; +} diff --git a/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.h b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.h new file mode 100644 index 0000000000..dac631359e --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.h @@ -0,0 +1,90 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef nsUrlClassifierPrefixSet_h_ +#define nsUrlClassifierPrefixSet_h_ + +#include "nsIUrlClassifierPrefixSet.h" +#include "nsISupports.h" +#include "nsToolkitCompsCID.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/Poison.h" + +class nsIInputStream; +class nsIOutputStream; + +namespace mozilla { +namespace safebrowsing { + +class VariableLengthPrefixSet; + +} // namespace safebrowsing +} // namespace mozilla + +class nsUrlClassifierPrefixSet final : public nsIUrlClassifierPrefixSet { + public: + nsUrlClassifierPrefixSet(); + + NS_IMETHOD Init(const nsACString& aName) override; + NS_IMETHOD SetPrefixes(const uint32_t* aArray, uint32_t aLength) override; + NS_IMETHOD GetPrefixes(uint32_t* aCount, uint32_t** aPrefixes) override; + NS_IMETHOD Contains(uint32_t aPrefix, bool* aFound) override; + NS_IMETHOD IsEmpty(bool* aEmpty) override; + + nsresult GetPrefixesNative(FallibleTArray<uint32_t>& aOutArray); + nsresult GetPrefixByIndex(uint32_t aIndex, uint32_t* aOutPrefix) const; + nsresult WritePrefixes(nsCOMPtr<nsIOutputStream>& out) const; + nsresult LoadPrefixes(nsCOMPtr<nsIInputStream>& in); + uint32_t CalculatePreallocateSize() const; + uint32_t Length() const; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + NS_DECL_THREADSAFE_ISUPPORTS + + friend class mozilla::safebrowsing::VariableLengthPrefixSet; + + private: + virtual ~nsUrlClassifierPrefixSet(); + + static const uint32_t DELTAS_LIMIT = 120; + static const uint32_t MAX_INDEX_DIFF = (1 << 16); + static const uint32_t PREFIXSET_VERSION_MAGIC = 1; + + void Clear() MOZ_REQUIRES(mLock); + nsresult MakePrefixSet(const uint32_t* aArray, uint32_t aLength) + MOZ_REQUIRES(mLock); + uint32_t BinSearch(uint32_t start, uint32_t end, uint32_t target) const + MOZ_REQUIRES(mLock); + bool IsEmptyInternal() const MOZ_REQUIRES(mLock); + + // Lock to prevent races between the url-classifier thread (which does most + // of the operations) and the main thread (which does memory reporting). + // It should be held for all operations between Init() and destruction that + // touch this class's data members. + mutable mozilla::Mutex mLock; + // list of fully stored prefixes, that also form the + // start of a run of deltas in mIndexDeltas. + nsTArray<uint32_t> mIndexPrefixes MOZ_GUARDED_BY(mLock); + // array containing arrays of deltas from indices. + // Index to the place that matches the closest lower + // prefix from mIndexPrefix. Then every "delta" corresponds + // to a prefix in the PrefixSet. + // This array could be empty when we decide to store all the prefixes + // in mIndexPrefixes. + nsTArray<nsTArray<uint16_t> > mIndexDeltas MOZ_GUARDED_BY(mLock); + + // how many prefixes we have. + uint32_t mTotalPrefixes MOZ_GUARDED_BY(mLock); + + nsCString mName; // Set in Init() only + mozilla::CorruptionCanary mCanary; +}; + +#endif diff --git a/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp b/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp new file mode 100644 index 0000000000..85caeaa728 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp @@ -0,0 +1,302 @@ +/* -*- 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 "nsUrlClassifierProxies.h" +#include "nsUrlClassifierDBService.h" + +#include "mozilla/SyncRunnable.h" +#include "Classifier.h" + +using namespace mozilla; +using namespace mozilla::safebrowsing; +using mozilla::NewRunnableMethod; + +static nsresult DispatchToWorkerThread(nsIRunnable* r) { + nsIThread* t = nsUrlClassifierDBService::BackgroundThread(); + if (!t) return NS_ERROR_FAILURE; + + return t->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMPL_ISUPPORTS(UrlClassifierDBServiceWorkerProxy, nsIUrlClassifierDBService) + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::Lookup(nsIPrincipal* aPrincipal, + const nsACString& aTables, + nsIUrlClassifierCallback* aCB) { + nsCOMPtr<nsIRunnable> r = + new LookupRunnable(mTarget, aPrincipal, aTables, aCB); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::LookupRunnable::Run() { + (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); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::GetTablesRunnable::Run() { + mTarget->GetTables(mCB); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::SetHashCompleter( + const nsACString&, nsIUrlClassifierHashCompleter*) { + MOZ_ASSERT_UNREACHABLE("This method should not be called!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::BeginUpdate( + nsIUrlClassifierUpdateObserver* aUpdater, const nsACString& aTables) { + nsCOMPtr<nsIRunnable> r = new BeginUpdateRunnable(mTarget, aUpdater, aTables); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::BeginUpdateRunnable::Run() { + mTarget->BeginUpdate(mUpdater, mTables); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::BeginStream(const nsACString& aTable) { + nsCOMPtr<nsIRunnable> r = new BeginStreamRunnable(mTarget, aTable); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::BeginStreamRunnable::Run() { + mTarget->BeginStream(mTable); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::UpdateStream( + const nsACString& aUpdateChunk) { + nsCOMPtr<nsIRunnable> r = new UpdateStreamRunnable(mTarget, aUpdateChunk); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::UpdateStreamRunnable::Run() { + mTarget->UpdateStream(mUpdateChunk); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::FinishStream() { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::FinishStream", mTarget, + &nsUrlClassifierDBServiceWorker::FinishStream); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::FinishUpdate() { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::FinishUpdate", mTarget, + &nsUrlClassifierDBServiceWorker::FinishUpdate); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::CancelUpdate() { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::CancelUpdate", mTarget, + &nsUrlClassifierDBServiceWorker::CancelUpdate); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::ResetDatabase() { + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "nsUrlClassifierDBServiceWorker::ResetDatabase", mTarget, + &nsUrlClassifierDBServiceWorker::ResetDatabase); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::ReloadDatabase() { + nsCOMPtr<nsIRunnable> r = NewRunnableMethod( + "nsUrlClassifierDBServiceWorker::ReloadDatabase", mTarget, + &nsUrlClassifierDBServiceWorker::ReloadDatabase); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::ClearCache() { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::ClearCache", mTarget, + &nsUrlClassifierDBServiceWorker::ClearCache); + return DispatchToWorkerThread(r); +} + +nsresult UrlClassifierDBServiceWorkerProxy::OpenDb() const { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::OpenDb", mTarget, + &nsUrlClassifierDBServiceWorker::OpenDb); + return DispatchToWorkerThread(r); +} + +nsresult UrlClassifierDBServiceWorkerProxy::CloseDb() const { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::CloseDb", mTarget, + &nsUrlClassifierDBServiceWorker::CloseDb); + return DispatchToWorkerThread(r); +} + +nsresult UrlClassifierDBServiceWorkerProxy::PreShutdown() const { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUrlClassifierDBServiceWorker::PreShutdown", mTarget, + &nsUrlClassifierDBServiceWorker::PreShutdown); + return DispatchToWorkerThread(r); +} + +nsresult UrlClassifierDBServiceWorkerProxy::CacheCompletions( + const ConstCacheResultArray& aEntries) const { + nsCOMPtr<nsIRunnable> r = new CacheCompletionsRunnable(mTarget, aEntries); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::CacheCompletionsRunnable::Run() { + mTarget->CacheCompletions(mEntries); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::ClearLastResults() { + nsCOMPtr<nsIRunnable> r = new ClearLastResultsRunnable(mTarget); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::ClearLastResultsRunnable::Run() { + return mTarget->ClearLastResults(); +} + +nsresult UrlClassifierDBServiceWorkerProxy::GetCacheInfo( + const nsACString& aTable, + nsIUrlClassifierGetCacheCallback* aCallback) const { + nsCOMPtr<nsIRunnable> r = + new GetCacheInfoRunnable(mTarget, aTable, aCallback); + return DispatchToWorkerThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::GetCacheInfoRunnable::Run() { + MOZ_ASSERT(mCallback); + + mTarget->GetCacheInfo(mTable, getter_AddRefs(mCache)); + + nsCOMPtr<nsIRunnable> r = new GetCacheInfoCallbackRunnable(mCache, mCallback); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierDBServiceWorkerProxy::GetCacheInfoCallbackRunnable::Run() { + MOZ_ASSERT(NS_IsMainThread(), "Must be called on main thread"); + MOZ_ASSERT(mCallback); + + mCallback->OnGetCacheComplete(mCache); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UrlClassifierLookupCallbackProxy, + nsIUrlClassifierLookupCallback) + +NS_IMETHODIMP +UrlClassifierLookupCallbackProxy::LookupComplete( + UniquePtr<LookupResultArray> aResults) { + nsCOMPtr<nsIRunnable> r = + new LookupCompleteRunnable(mTarget, std::move(aResults)); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierLookupCallbackProxy::LookupCompleteRunnable::Run() { + mTarget->LookupComplete(std::move(mResults)); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UrlClassifierCallbackProxy, nsIUrlClassifierCallback) + +NS_IMETHODIMP +UrlClassifierCallbackProxy::HandleEvent(const nsACString& aValue) { + nsCOMPtr<nsIRunnable> r = new HandleEventRunnable(mTarget, aValue); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierCallbackProxy::HandleEventRunnable::Run() { + mTarget->HandleEvent(mValue); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UrlClassifierUpdateObserverProxy, + nsIUrlClassifierUpdateObserver) + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::UpdateUrlRequested(const nsACString& aURL, + const nsACString& aTable) { + nsCOMPtr<nsIRunnable> r = + new UpdateUrlRequestedRunnable(mTarget, aURL, aTable); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::UpdateUrlRequestedRunnable::Run() { + mTarget->UpdateUrlRequested(mURL, mTable); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::StreamFinished(nsresult aStatus, + uint32_t aDelay) { + nsCOMPtr<nsIRunnable> r = + new StreamFinishedRunnable(mTarget, aStatus, aDelay); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::StreamFinishedRunnable::Run() { + mTarget->StreamFinished(mStatus, mDelay); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::UpdateError(nsresult aError) { + nsCOMPtr<nsIRunnable> r = new UpdateErrorRunnable(mTarget, aError); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::UpdateErrorRunnable::Run() { + mTarget->UpdateError(mError); + return NS_OK; +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::UpdateSuccess(uint32_t aRequestedTimeout) { + nsCOMPtr<nsIRunnable> r = + new UpdateSuccessRunnable(mTarget, aRequestedTimeout); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +UrlClassifierUpdateObserverProxy::UpdateSuccessRunnable::Run() { + mTarget->UpdateSuccess(mRequestedTimeout); + return NS_OK; +} diff --git a/toolkit/components/url-classifier/nsUrlClassifierProxies.h b/toolkit/components/url-classifier/nsUrlClassifierProxies.h new file mode 100644 index 0000000000..5739d7bf21 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.h @@ -0,0 +1,359 @@ +/* -*- 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/. */ + +#ifndef nsUrlClassifierProxies_h +#define nsUrlClassifierProxies_h + +#include "nsIUrlClassifierDBService.h" +#include "nsUrlClassifierDBService.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" +#include "nsIPrincipal.h" +#include "LookupCache.h" + +/** + * Thread proxy from the main thread to the worker thread. + */ +class UrlClassifierDBServiceWorkerProxy final + : public nsIUrlClassifierDBService { + public: + explicit UrlClassifierDBServiceWorkerProxy( + nsUrlClassifierDBServiceWorker* aTarget) + : mTarget(aTarget) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERDBSERVICE + + class LookupRunnable : public mozilla::Runnable { + public: + LookupRunnable(nsUrlClassifierDBServiceWorker* aTarget, + nsIPrincipal* aPrincipal, const nsACString& aTables, + nsIUrlClassifierCallback* aCB) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::LookupRunnable"), + mTarget(aTarget), + mPrincipal(aPrincipal), + mLookupTables(aTables), + mCB(aCB) {} + + NS_DECL_NSIRUNNABLE + + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const nsCOMPtr<nsIPrincipal> mPrincipal; + const nsCString mLookupTables; + const nsCOMPtr<nsIUrlClassifierCallback> mCB; + }; + + class GetTablesRunnable : public mozilla::Runnable { + public: + GetTablesRunnable(nsUrlClassifierDBServiceWorker* aTarget, + nsIUrlClassifierCallback* aCB) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::GetTablesRunnable"), + mTarget(aTarget), + mCB(aCB) {} + + NS_DECL_NSIRUNNABLE + + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const nsCOMPtr<nsIUrlClassifierCallback> mCB; + }; + + class BeginUpdateRunnable : public mozilla::Runnable { + public: + BeginUpdateRunnable(nsUrlClassifierDBServiceWorker* aTarget, + nsIUrlClassifierUpdateObserver* aUpdater, + const nsACString& aTables) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::BeginUpdateRunnable"), + mTarget(aTarget), + mUpdater(aUpdater), + mTables(aTables) {} + + NS_DECL_NSIRUNNABLE + + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const nsCOMPtr<nsIUrlClassifierUpdateObserver> mUpdater; + const nsCString mTables; + }; + + class BeginStreamRunnable : public mozilla::Runnable { + public: + BeginStreamRunnable(nsUrlClassifierDBServiceWorker* aTarget, + const nsACString& aTable) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::BeginStreamRunnable"), + mTarget(aTarget), + mTable(aTable) {} + + NS_DECL_NSIRUNNABLE + + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const nsCString mTable; + }; + + class UpdateStreamRunnable : public mozilla::Runnable { + public: + UpdateStreamRunnable(nsUrlClassifierDBServiceWorker* aTarget, + const nsACString& aUpdateChunk) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::UpdateStreamRunnable"), + mTarget(aTarget), + mUpdateChunk(aUpdateChunk) {} + + NS_DECL_NSIRUNNABLE + + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const nsCString mUpdateChunk; + }; + + class CacheCompletionsRunnable : public mozilla::Runnable { + public: + CacheCompletionsRunnable( + nsUrlClassifierDBServiceWorker* aTarget, + const mozilla::safebrowsing::ConstCacheResultArray& aEntries) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::CacheCompletionsRunnable"), + mTarget(aTarget), + mEntries(aEntries.Clone()) {} + + NS_DECL_NSIRUNNABLE + + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const mozilla::safebrowsing::ConstCacheResultArray mEntries; + }; + + class ClearLastResultsRunnable : public mozilla::Runnable { + public: + explicit ClearLastResultsRunnable(nsUrlClassifierDBServiceWorker* aTarget) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::ClearLastResultsRunnable"), + mTarget(aTarget) {} + + NS_DECL_NSIRUNNABLE + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + }; + + class GetCacheInfoRunnable : public mozilla::Runnable { + public: + explicit GetCacheInfoRunnable(nsUrlClassifierDBServiceWorker* aTarget, + const nsACString& aTable, + nsIUrlClassifierGetCacheCallback* aCallback) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::GetCacheInfoRunnable"), + mTarget(aTarget), + mTable(aTable), + mCache(nullptr), + mCallback(new nsMainThreadPtrHolder<nsIUrlClassifierGetCacheCallback>( + "nsIUrlClassifierGetCacheCallback", aCallback)) {} + + NS_DECL_NSIRUNNABLE + private: + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; + const nsCString mTable; + nsCOMPtr<nsIUrlClassifierCacheInfo> mCache; + const nsMainThreadPtrHandle<nsIUrlClassifierGetCacheCallback> mCallback; + }; + + class GetCacheInfoCallbackRunnable : public mozilla::Runnable { + public: + explicit GetCacheInfoCallbackRunnable( + nsIUrlClassifierCacheInfo* aCache, + const nsMainThreadPtrHandle<nsIUrlClassifierGetCacheCallback>& + aCallback) + : mozilla::Runnable( + "UrlClassifierDBServiceWorkerProxy::" + "GetCacheInfoCallbackRunnable"), + mCache(aCache), + mCallback(aCallback) {} + + NS_DECL_NSIRUNNABLE + private: + nsCOMPtr<nsIUrlClassifierCacheInfo> mCache; + const nsMainThreadPtrHandle<nsIUrlClassifierGetCacheCallback> mCallback; + }; + + public: + nsresult OpenDb() const; + nsresult CloseDb() const; + nsresult PreShutdown() const; + + nsresult CacheCompletions( + const mozilla::safebrowsing::ConstCacheResultArray& aEntries) const; + + nsresult GetCacheInfo(const nsACString& aTable, + nsIUrlClassifierGetCacheCallback* aCallback) const; + + private: + ~UrlClassifierDBServiceWorkerProxy() = default; + + const RefPtr<nsUrlClassifierDBServiceWorker> mTarget; +}; + +// The remaining classes here are all proxies to the main thread + +class UrlClassifierLookupCallbackProxy final + : public nsIUrlClassifierLookupCallback { + public: + explicit UrlClassifierLookupCallbackProxy( + nsIUrlClassifierLookupCallback* aTarget) + : mTarget(new nsMainThreadPtrHolder<nsIUrlClassifierLookupCallback>( + "UrlClassifierLookupCallbackProxy::mTarget", aTarget)) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK + + class LookupCompleteRunnable : public mozilla::Runnable { + public: + LookupCompleteRunnable( + const nsMainThreadPtrHandle<nsIUrlClassifierLookupCallback>& aTarget, + mozilla::UniquePtr<mozilla::safebrowsing::LookupResultArray> aResults) + : mozilla::Runnable( + "UrlClassifierLookupCallbackProxy::LookupCompleteRunnable"), + mTarget(aTarget), + mResults(std::move(aResults)) {} + + NS_DECL_NSIRUNNABLE + + private: + const nsMainThreadPtrHandle<nsIUrlClassifierLookupCallback> mTarget; + mozilla::UniquePtr<mozilla::safebrowsing::LookupResultArray> mResults; + }; + + private: + ~UrlClassifierLookupCallbackProxy() = default; + + const nsMainThreadPtrHandle<nsIUrlClassifierLookupCallback> mTarget; +}; + +class UrlClassifierCallbackProxy final : public nsIUrlClassifierCallback { + public: + explicit UrlClassifierCallbackProxy(nsIUrlClassifierCallback* aTarget) + : mTarget(new nsMainThreadPtrHolder<nsIUrlClassifierCallback>( + "UrlClassifierCallbackProxy::mTarget", aTarget)) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERCALLBACK + + class HandleEventRunnable : public mozilla::Runnable { + public: + HandleEventRunnable( + const nsMainThreadPtrHandle<nsIUrlClassifierCallback>& aTarget, + const nsACString& aValue) + : mozilla::Runnable("UrlClassifierCallbackProxy::HandleEventRunnable"), + mTarget(aTarget), + mValue(aValue) {} + + NS_DECL_NSIRUNNABLE + + private: + const nsMainThreadPtrHandle<nsIUrlClassifierCallback> mTarget; + const nsCString mValue; + }; + + private: + ~UrlClassifierCallbackProxy() = default; + + const nsMainThreadPtrHandle<nsIUrlClassifierCallback> mTarget; +}; + +class UrlClassifierUpdateObserverProxy final + : public nsIUrlClassifierUpdateObserver { + public: + explicit UrlClassifierUpdateObserverProxy( + nsIUrlClassifierUpdateObserver* aTarget) + : mTarget(new nsMainThreadPtrHolder<nsIUrlClassifierUpdateObserver>( + "UrlClassifierUpdateObserverProxy::mTarget", aTarget)) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERUPDATEOBSERVER + + class UpdateUrlRequestedRunnable : public mozilla::Runnable { + public: + UpdateUrlRequestedRunnable( + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget, + const nsACString& aURL, const nsACString& aTable) + : mozilla::Runnable( + "UrlClassifierUpdateObserverProxy::UpdateUrlRequestedRunnable"), + mTarget(aTarget), + mURL(aURL), + mTable(aTable) {} + + NS_DECL_NSIRUNNABLE + + private: + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget; + const nsCString mURL; + const nsCString mTable; + }; + + class StreamFinishedRunnable : public mozilla::Runnable { + public: + StreamFinishedRunnable( + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget, + nsresult aStatus, uint32_t aDelay) + : mozilla::Runnable( + "UrlClassifierUpdateObserverProxy::StreamFinishedRunnable"), + mTarget(aTarget), + mStatus(aStatus), + mDelay(aDelay) {} + + NS_DECL_NSIRUNNABLE + + private: + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget; + const nsresult mStatus; + const uint32_t mDelay; + }; + + class UpdateErrorRunnable : public mozilla::Runnable { + public: + UpdateErrorRunnable( + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget, + nsresult aError) + : mozilla::Runnable( + "UrlClassifierUpdateObserverProxy::UpdateErrorRunnable"), + mTarget(aTarget), + mError(aError) {} + + NS_DECL_NSIRUNNABLE + + private: + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget; + const nsresult mError; + }; + + class UpdateSuccessRunnable : public mozilla::Runnable { + public: + UpdateSuccessRunnable( + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget, + uint32_t aRequestedTimeout) + : mozilla::Runnable( + "UrlClassifierUpdateObserverProxy::UpdateSuccessRunnable"), + mTarget(aTarget), + mRequestedTimeout(aRequestedTimeout) {} + + NS_DECL_NSIRUNNABLE + + private: + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget; + const uint32_t mRequestedTimeout; + }; + + private: + ~UrlClassifierUpdateObserverProxy() = default; + + const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget; +}; + +#endif // nsUrlClassifierProxies_h diff --git a/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp new file mode 100644 index 0000000000..641339c3ea --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp @@ -0,0 +1,881 @@ +//* -*- 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 "mozilla/Components.h" +#include "nsCRT.h" +#include "nsIHttpChannel.h" +#include "nsIObserverService.h" +#include "nsIStringStream.h" +#include "nsIUploadChannel.h" +#include "nsIURI.h" +#include "nsIUrlClassifierDBService.h" +#include "nsIUrlClassifierRemoteSettingsService.h" +#include "nsUrlClassifierUtils.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsUrlClassifierStreamUpdater.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "nsIInterfaceRequestor.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Try.h" +#include "nsContentUtils.h" +#include "nsIURLFormatter.h" +#include "Classifier.h" +#include "UrlClassifierTelemetryUtils.h" +#include "mozilla/StaticPrefs_urlclassifier.h" + +using namespace mozilla::safebrowsing; +using namespace mozilla; + +#define MIN_TIMEOUT_MS (60 * 1000) + +static const char* gQuitApplicationMessage = "quit-application"; + +// Limit the list file size to 32mb +const uint32_t MAX_FILE_SIZE = (32 * 1024 * 1024); + +// Retry delay when we failed to DownloadUpdate() if due to +// DBService busy. +const uint32_t FETCH_NEXT_REQUEST_RETRY_DELAY_MS = 1000; + +#undef LOG + +// MOZ_LOG=UrlClassifierStreamUpdater:5 +static mozilla::LazyLogModule gUrlClassifierStreamUpdaterLog( + "UrlClassifierStreamUpdater"); +#define LOG(args) TrimAndLog args +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Debug) + +// Calls nsIURLFormatter::TrimSensitiveURLs to remove sensitive +// info from the logging message. +static MOZ_FORMAT_PRINTF(1, 2) void TrimAndLog(const char* aFmt, ...) { + nsString raw; + + va_list ap; + va_start(ap, aFmt); + raw.AppendVprintf(aFmt, ap); + va_end(ap); + + nsCOMPtr<nsIURLFormatter> urlFormatter = + do_GetService("@mozilla.org/toolkit/URLFormatterService;1"); + + nsString trimmed; + nsresult rv = urlFormatter->TrimSensitiveURLs(raw, trimmed); + if (NS_FAILED(rv)) { + trimmed.Truncate(); + } + + // Use %s so we aren't exposing random strings to printf interpolation. + MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Debug, + ("%s", NS_ConvertUTF16toUTF8(trimmed).get())); +} + +// This class does absolutely nothing, except pass requests onto the DBService. + +/////////////////////////////////////////////////////////////////////////////// +// nsIUrlClassiferStreamUpdater implementation +// Handles creating/running the stream listener + +nsUrlClassifierStreamUpdater::nsUrlClassifierStreamUpdater() + : mIsUpdating(false), + mInitialized(false), + mDownloadError(false), + mBeganStream(false), + mChannel(nullptr), + mTelemetryClockStart(0) { + LOG(("nsUrlClassifierStreamUpdater init [this=%p]", this)); +} + +NS_IMPL_ISUPPORTS(nsUrlClassifierStreamUpdater, nsIUrlClassifierStreamUpdater, + nsIUrlClassifierUpdateObserver, nsIRequestObserver, + nsIStreamListener, nsIObserver, nsIInterfaceRequestor, + nsITimerCallback, nsINamed) + +/** + * Clear out the update. + */ +void nsUrlClassifierStreamUpdater::DownloadDone() { + LOG(("nsUrlClassifierStreamUpdater::DownloadDone [this=%p]", this)); + mIsUpdating = false; + + mPendingUpdates.Clear(); + mDownloadError = false; + mCurrentRequest = nullptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIUrlClassifierStreamUpdater implementation + +nsresult nsUrlClassifierStreamUpdater::FetchUpdate( + nsIURI* aUpdateUrl, const nsACString& aRequestPayload, bool aIsPostRequest, + const nsACString& aStreamTable) { + mBeganStream = false; + nsresult rv; + // moz-sbrs is a customed scheme used by Safe Browsing. When the scheme is + // present in the update url, we'll fetch the data from + // UrlClassifierRemoteSettingsService. + if (aUpdateUrl->SchemeIs("moz-sbrs")) { +#ifdef DEBUG + LOG(("Fetching update %s from RemoteSettings", aRequestPayload.Data())); +#endif + nsCOMPtr<nsIUrlClassifierRemoteSettingsService> rsService = + do_GetService("@mozilla.org/url-classifier/list-service;1"); + if (!rsService) { + return NS_ERROR_FAILURE; + } + + rv = rsService->FetchList(aRequestPayload, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { +#ifdef DEBUG + LOG(("Fetching update %s from %s", aRequestPayload.Data(), + aUpdateUrl->GetSpecOrDefault().get())); +#endif + uint32_t loadFlags = nsIChannel::INHIBIT_CACHING | + nsIChannel::LOAD_BYPASS_CACHE | + nsIChannel::LOAD_BYPASS_URL_CLASSIFIER; + + // SafeBrowsing update request should never be classified to make sure + // we can recover from a bad SafeBrowsing database. + rv = NS_NewChannel(getter_AddRefs(mChannel), aUpdateUrl, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // aPerformanceStorage + nullptr, // aLoadGroup + this, // aInterfaceRequestor + loadFlags); + + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); + mozilla::OriginAttributes attrs; + attrs.mFirstPartyDomain.AssignLiteral( + NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN); + loadInfo->SetOriginAttributes(attrs); + // allow deprecated HTTP request from SystemPrincipal + loadInfo->SetAllowDeprecatedSystemRequests(true); + + if (!aIsPostRequest) { + // We use POST method to send our request in v2. In v4, the request + // needs to be embedded to the URL and use GET method to send. + // However, from the Chromium source code, a extended HTTP header has + // to be sent along with the request to make the request succeed. + // The following description is from Chromium source code: + // + // "The following header informs the envelope server (which sits in + // front of Google's stubby server) that the received GET request should + // be interpreted as a POST." + // + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = httpChannel->SetRequestHeader("X-HTTP-Method-Override"_ns, "POST"_ns, + false); + NS_ENSURE_SUCCESS(rv, rv); + } else if (!aRequestPayload.IsEmpty()) { + rv = AddRequestBody(aRequestPayload); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Set the appropriate content type for file/data URIs, for unit testing + // purposes. + // This is only used for testing and should be deleted. + if (aUpdateUrl->SchemeIs("file") || aUpdateUrl->SchemeIs("data")) { + mChannel->SetContentType("application/vnd.google.safebrowsing-update"_ns); + } else { + // We assume everything else is an HTTP request. + + // Disable keepalive. + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = httpChannel->SetRequestHeader("Connection"_ns, "close"_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make the request. + rv = mChannel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + mTelemetryClockStart = PR_IntervalNow(); + mStreamTable = aStreamTable; + + if (StaticPrefs::urlclassifier_update_response_timeout_ms() > + StaticPrefs::urlclassifier_update_timeout_ms()) { + NS_WARNING( + "Safe Browsing response timeout is greater than the general " + "timeout. Disabling these update timeouts."); + return NS_OK; + } + MOZ_TRY_VAR(mResponseTimeoutTimer, + NS_NewTimerWithCallback( + this, StaticPrefs::urlclassifier_update_response_timeout_ms(), + nsITimer::TYPE_ONE_SHOT)); + + MOZ_TRY_VAR(mTimeoutTimer, + NS_NewTimerWithCallback( + this, StaticPrefs::urlclassifier_update_timeout_ms(), + nsITimer::TYPE_ONE_SHOT)); + + if (StaticPrefs::urlclassifier_update_timeout_ms() < MIN_TIMEOUT_MS) { + LOG(("Download update timeout %d ms (< %d ms) would be too small", + StaticPrefs::urlclassifier_update_timeout_ms(), MIN_TIMEOUT_MS)); + } + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::FetchUpdate( + const nsACString& aUpdateUrl, const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aStreamTable) { + LOG(("(pre) Fetching update from %s\n", + PromiseFlatCString(aUpdateUrl).get())); + + nsCString updateUrl(aUpdateUrl); + if (!aIsPostRequest) { + updateUrl.AppendPrintf("&$req=%s", nsCString(aRequestPayload).get()); + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), updateUrl); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString urlSpec; + uri->GetAsciiSpec(urlSpec); + + LOG(("(post) Fetching update from %s\n", urlSpec.get())); + + return FetchUpdate(uri, aRequestPayload, aIsPostRequest, aStreamTable); +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::DownloadUpdates( + const nsACString& aRequestTables, const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aUpdateUrl, + nsIUrlClassifierCallback* aSuccessCallback, + nsIUrlClassifierCallback* aUpdateErrorCallback, + nsIUrlClassifierCallback* aDownloadErrorCallback, bool* _retval) { + NS_ENSURE_ARG(aSuccessCallback); + NS_ENSURE_ARG(aUpdateErrorCallback); + NS_ENSURE_ARG(aDownloadErrorCallback); + + if (mIsUpdating) { + LOG(("Already updating, queueing update %s from %s", aRequestPayload.Data(), + aUpdateUrl.Data())); + *_retval = false; + UpdateRequest* request = mPendingRequests.AppendElement(fallible); + if (!request) { + return NS_ERROR_OUT_OF_MEMORY; + } + BuildUpdateRequest(aRequestTables, aRequestPayload, aIsPostRequest, + aUpdateUrl, aSuccessCallback, aUpdateErrorCallback, + aDownloadErrorCallback, request); + return NS_OK; + } + + if (aUpdateUrl.IsEmpty()) { + NS_ERROR("updateUrl not set"); + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv; + + if (!mInitialized) { + // Add an observer for shutdown so we can cancel any pending list + // downloads. quit-application is the same event that the download + // manager listens for and uses to cancel pending downloads. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) return NS_ERROR_FAILURE; + + observerService->AddObserver(this, gQuitApplicationMessage, false); + + mDBService = mozilla::components::UrlClassifierDB::Service(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + } + + rv = mDBService->BeginUpdate(this, aRequestTables); + if (rv == NS_ERROR_NOT_AVAILABLE) { + LOG(("Service busy, already updating, queuing update %s from %s", + aRequestPayload.Data(), aUpdateUrl.Data())); + *_retval = false; + UpdateRequest* request = mPendingRequests.AppendElement(fallible); + if (!request) { + return NS_ERROR_OUT_OF_MEMORY; + } + BuildUpdateRequest(aRequestTables, aRequestPayload, aIsPostRequest, + aUpdateUrl, aSuccessCallback, aUpdateErrorCallback, + aDownloadErrorCallback, request); + + // We cannot guarantee that we will be notified when DBService is done + // processing the current update, so we fire a retry timer on our own. + MOZ_TRY_VAR(mFetchNextRequestTimer, + NS_NewTimerWithCallback(this, FETCH_NEXT_REQUEST_RETRY_DELAY_MS, + nsITimer::TYPE_ONE_SHOT)); + + return NS_OK; + } + + if (NS_FAILED(rv)) { + return rv; + } + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return NS_ERROR_FAILURE; + } + + nsTArray<nsCString> tables; + mozilla::safebrowsing::Classifier::SplitTables(aRequestTables, tables); + urlUtil->GetTelemetryProvider(tables.SafeElementAt(0, ""_ns), + mTelemetryProvider); + + mCurrentRequest = MakeUnique<UpdateRequest>(); + BuildUpdateRequest(aRequestTables, aRequestPayload, aIsPostRequest, + aUpdateUrl, aSuccessCallback, aUpdateErrorCallback, + aDownloadErrorCallback, mCurrentRequest.get()); + + mIsUpdating = true; + *_retval = true; + + LOG(("FetchUpdate: %s", mCurrentRequest->mUrl.Data())); + + return FetchUpdate(aUpdateUrl, aRequestPayload, aIsPostRequest, ""_ns); +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIUrlClassifierUpdateObserver implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::UpdateUrlRequested(const nsACString& aUrl, + const nsACString& aTable) { + LOG(("Queuing requested update from %s\n", PromiseFlatCString(aUrl).get())); + + PendingUpdate* update = mPendingUpdates.AppendElement(fallible); + if (!update) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Allow data: and file: urls for unit testing purposes, otherwise assume http + if (StringBeginsWith(aUrl, "data:"_ns) || + StringBeginsWith(aUrl, "file:"_ns)) { + update->mUrl = aUrl; + } else { + // For unittesting update urls to localhost should use http, not https + // (otherwise the connection will fail silently, since there will be no + // cert available). + if (!StringBeginsWith(aUrl, "localhost"_ns)) { + update->mUrl = "https://"_ns + aUrl; + } else { + update->mUrl = "http://"_ns + aUrl; + } + } + update->mTable = aTable; + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::FetchNext() { + if (mPendingUpdates.Length() == 0) { + return NS_OK; + } + + PendingUpdate& update = mPendingUpdates[0]; + LOG(("Fetching update url: %s\n", update.mUrl.get())); + nsresult rv = + FetchUpdate(update.mUrl, ""_ns, + true, // This method is for v2 and v2 is always a POST. + update.mTable); + if (NS_FAILED(rv)) { + nsAutoCString errorName; + mozilla::GetErrorName(rv, errorName); + LOG(("Error (%s) fetching update url: %s\n", errorName.get(), + update.mUrl.get())); + // We can commit the urls that we've applied so far. This is + // probably a transient server problem, so trigger backoff. + mDownloadError = true; + mDBService->FinishUpdate(); + return rv; + } + + mPendingUpdates.RemoveElementAt(0); + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::FetchNextRequest() { + if (mPendingRequests.Length() == 0) { + LOG(("No more requests, returning")); + return NS_OK; + } + + UpdateRequest request = mPendingRequests[0]; + mPendingRequests.RemoveElementAt(0); + LOG(("Stream updater: fetching next request: %s, %s", request.mTables.get(), + request.mUrl.get())); + bool dummy; + DownloadUpdates(request.mTables, request.mRequestPayload, + request.mIsPostRequest, request.mUrl, + request.mSuccessCallback, request.mUpdateErrorCallback, + request.mDownloadErrorCallback, &dummy); + return NS_OK; +} + +void nsUrlClassifierStreamUpdater::BuildUpdateRequest( + const nsACString& aRequestTables, const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aUpdateUrl, + nsIUrlClassifierCallback* aSuccessCallback, + nsIUrlClassifierCallback* aUpdateErrorCallback, + nsIUrlClassifierCallback* aDownloadErrorCallback, UpdateRequest* aRequest) { + MOZ_ASSERT(aRequest); + + aRequest->mTables = aRequestTables; + aRequest->mRequestPayload = aRequestPayload; + aRequest->mIsPostRequest = aIsPostRequest; + aRequest->mUrl = aUpdateUrl; + aRequest->mSuccessCallback = aSuccessCallback; + aRequest->mUpdateErrorCallback = aUpdateErrorCallback; + aRequest->mDownloadErrorCallback = aDownloadErrorCallback; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::StreamFinished(nsresult status, + uint32_t requestedDelay) { + // We are a service and may not be reset with Init between calls, so reset + // mBeganStream manually. + mBeganStream = false; + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(status, errorName); + LOG(("nsUrlClassifierStreamUpdater::StreamFinished [%s, %d]", + errorName.get(), requestedDelay)); + } + if (NS_FAILED(status) || mPendingUpdates.Length() == 0) { + // We're done. + LOG(("nsUrlClassifierStreamUpdater::Done [this=%p]", this)); + mDBService->FinishUpdate(); + return NS_OK; + } + + // This timer is for fetching indirect updates ("forwards") from any "u:" + // lines that we encountered while processing the server response. It is NOT + // for scheduling the next time we pull the list from the server. That's a + // different timer in listmanager.js (see bug 1110891). + nsresult rv; + rv = NS_NewTimerWithCallback(getter_AddRefs(mFetchIndirectUpdatesTimer), this, + requestedDelay, nsITimer::TYPE_ONE_SHOT); + + if (NS_FAILED(rv)) { + NS_WARNING( + "Unable to initialize timer, fetching next safebrowsing item " + "immediately"); + return FetchNext(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::UpdateSuccess(uint32_t requestedTimeout) { + LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess [this=%p]", this)); + if (mPendingUpdates.Length() != 0) { + NS_WARNING("Didn't fetch all safebrowsing update redirects"); + } + + // DownloadDone() clears mSuccessCallback, so we save it off here. + nsCOMPtr<nsIUrlClassifierCallback> successCallback = + mDownloadError ? nullptr : mCurrentRequest->mSuccessCallback.get(); + nsCOMPtr<nsIUrlClassifierCallback> downloadErrorCallback = + mDownloadError ? mCurrentRequest->mDownloadErrorCallback.get() : nullptr; + + DownloadDone(); + + nsAutoCString strTimeout; + strTimeout.AppendInt(requestedTimeout); + if (successCallback) { + LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess callback [this=%p]", + this)); + successCallback->HandleEvent(strTimeout); + } else if (downloadErrorCallback) { + downloadErrorCallback->HandleEvent(mDownloadErrorStatusStr); + mDownloadErrorStatusStr.Truncate(); + LOG(("Notify download error callback in UpdateSuccess [this=%p]", this)); + } + // Now fetch the next request + LOG(("stream updater: calling into fetch next request")); + FetchNextRequest(); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::UpdateError(nsresult result) { + LOG(("nsUrlClassifierStreamUpdater::UpdateError [this=%p]", this)); + + // DownloadDone() clears mUpdateErrorCallback, so we save it off here. + nsCOMPtr<nsIUrlClassifierCallback> errorCallback = + mDownloadError ? nullptr : mCurrentRequest->mUpdateErrorCallback.get(); + nsCOMPtr<nsIUrlClassifierCallback> downloadErrorCallback = + mDownloadError ? mCurrentRequest->mDownloadErrorCallback.get() : nullptr; + DownloadDone(); + + if (errorCallback) { + nsAutoCString strResult; + mozilla::GetErrorName(result, strResult); + errorCallback->HandleEvent(strResult); + } else if (downloadErrorCallback) { + LOG(("Notify download error callback in UpdateError [this=%p]", this)); + downloadErrorCallback->HandleEvent(mDownloadErrorStatusStr); + mDownloadErrorStatusStr.Truncate(); + } + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::AddRequestBody( + const nsACString& aRequestBody) { + nsresult rv; + nsCOMPtr<nsIStringInputStream> strStream = + do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = strStream->SetData(aRequestBody.BeginReading(), aRequestBody.Length()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = uploadChannel->SetUploadStream(strStream, "text/plain"_ns, -1); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = httpChannel->SetRequestMethod("POST"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIStreamListenerObserver implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::OnStartRequest(nsIRequest* request) { + nsresult rv; + bool downloadError = false; + nsAutoCString strStatus; + nsresult status = NS_OK; + + // Only update if we got http success header + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); + if (httpChannel) { + rv = httpChannel->GetStatus(&status); + NS_ENSURE_SUCCESS(rv, rv); + + if (LOG_ENABLED()) { + nsAutoCString errorName, spec; + mozilla::GetErrorName(status, errorName); + nsCOMPtr<nsIURI> uri; + rv = httpChannel->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + uri->GetAsciiSpec(spec); + } + LOG( + ("nsUrlClassifierStreamUpdater::OnStartRequest " + "(status=%s, uri=%s, this=%p)", + errorName.get(), spec.get(), this)); + } + if (mTelemetryClockStart > 0) { + uint32_t msecs = + PR_IntervalToMilliseconds(PR_IntervalNow() - mTelemetryClockStart); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_SERVER_RESPONSE_TIME, + mTelemetryProvider, msecs); + } + + if (mResponseTimeoutTimer) { + mResponseTimeoutTimer->Cancel(); + mResponseTimeoutTimer = nullptr; + } + + uint8_t netErrCode = NS_FAILED(status) ? NetworkErrorToBucket(status) : 0; + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_REMOTE_NETWORK_ERROR, + mTelemetryProvider, netErrCode); + + if (NS_FAILED(status)) { + // Assume we're overloading the server and trigger backoff. + downloadError = true; + } else { + bool succeeded = false; + rv = httpChannel->GetRequestSucceeded(&succeeded); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t requestStatus; + rv = httpChannel->GetResponseStatus(&requestStatus); + NS_ENSURE_SUCCESS(rv, rv); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_REMOTE_STATUS2, + mTelemetryProvider, HTTPStatusToBucket(requestStatus)); + if (requestStatus == 400) { + printf_stderr( + "Safe Browsing server returned a 400 during update:" + "request url = %s, payload = %s\n", + mCurrentRequest->mUrl.get(), + mCurrentRequest->mRequestPayload.get()); + } + + LOG(("nsUrlClassifierStreamUpdater::OnStartRequest %s (%d)", + succeeded ? "succeeded" : "failed", requestStatus)); + if (!succeeded) { + // 404 or other error, pass error status back + strStatus.AppendInt(requestStatus); + downloadError = true; + } + } + } + + if (downloadError) { + LOG(("nsUrlClassifierStreamUpdater::Download error [this=%p]", this)); + mDownloadError = true; + mDownloadErrorStatusStr = strStatus; + status = NS_ERROR_ABORT; + } else if (NS_SUCCEEDED(status)) { + MOZ_ASSERT(mCurrentRequest->mDownloadErrorCallback); + mBeganStream = true; + LOG(("nsUrlClassifierStreamUpdater::Beginning stream [this=%p]", this)); + rv = mDBService->BeginStream(mStreamTable); + NS_ENSURE_SUCCESS(rv, rv); + } + + mStreamTable.Truncate(); + + return status; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::OnDataAvailable(nsIRequest* request, + nsIInputStream* aIStream, + uint64_t aSourceOffset, + uint32_t aLength) { + if (!mDBService) return NS_ERROR_NOT_INITIALIZED; + + LOG(("OnDataAvailable (%d bytes)", aLength)); + + if (aSourceOffset > MAX_FILE_SIZE) { + LOG(( + "OnDataAvailable::Abort because exceeded the maximum file size(%" PRIu64 + ")", + aSourceOffset)); + return NS_ERROR_FILE_TOO_BIG; + } + + nsresult rv; + + // Copy the data into a nsCString + nsCString chunk; + rv = NS_ConsumeStream(aIStream, aLength, chunk); + NS_ENSURE_SUCCESS(rv, rv); + + // LOG(("Chunk (%d): %s\n\n", chunk.Length(), chunk.get())); + rv = mDBService->UpdateStream(chunk); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + if (!mDBService) return NS_ERROR_NOT_INITIALIZED; + + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(aStatus, errorName); + LOG(("OnStopRequest (status %s, beganStream %s, this=%p)", errorName.get(), + mBeganStream ? "true" : "false", this)); + } + + nsresult rv; + + if (NS_SUCCEEDED(aStatus)) { + // Success, finish this stream and move on to the next. + rv = mDBService->FinishStream(); + } else if (mBeganStream) { + LOG(("OnStopRequest::Canceling update [this=%p]", this)); + // We began this stream and couldn't finish it. We have to cancel the + // update, it's not in a consistent state. + mDownloadError = true; + rv = mDBService->CancelUpdate(); + } else { + LOG(("OnStopRequest::Finishing update [this=%p]", this)); + // The fetch failed, but we didn't start the stream (probably a + // server or connection error). We can commit what we've applied + // so far, and request again later. + rv = mDBService->FinishUpdate(); + } + + if (mResponseTimeoutTimer) { + mResponseTimeoutTimer->Cancel(); + mResponseTimeoutTimer = nullptr; + } + + // mResponseTimeoutTimer may be cleared in OnStartRequest, so we check + // mTimeoutTimer to see whether the update was has timed out + if (mTimeoutTimer) { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT, mTelemetryProvider, + static_cast<uint8_t>(eNoTimeout)); + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + + mTelemetryProvider.Truncate(); + mTelemetryClockStart = 0; + mChannel = nullptr; + + // If the fetch failed, return the network status rather than NS_OK, the + // result of finishing a possibly-empty update + if (NS_SUCCEEDED(aStatus)) { + return rv; + } + return aStatus; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIObserver implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (nsCRT::strcmp(aTopic, gQuitApplicationMessage) == 0) { + if (mIsUpdating && mChannel) { + LOG(("Cancel download")); + nsresult rv; + rv = mChannel->Cancel(NS_ERROR_ABORT); + NS_ENSURE_SUCCESS(rv, rv); + mIsUpdating = false; + mChannel = nullptr; + mTelemetryClockStart = 0; + } + if (mFetchIndirectUpdatesTimer) { + mFetchIndirectUpdatesTimer->Cancel(); + mFetchIndirectUpdatesTimer = nullptr; + } + if (mFetchNextRequestTimer) { + mFetchNextRequestTimer->Cancel(); + mFetchNextRequestTimer = nullptr; + } + if (mResponseTimeoutTimer) { + mResponseTimeoutTimer->Cancel(); + mResponseTimeoutTimer = nullptr; + } + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + } + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIInterfaceRequestor implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::GetInterface(const nsIID& eventSinkIID, + void** _retval) { + return QueryInterface(eventSinkIID, _retval); +} + +/////////////////////////////////////////////////////////////////////////////// +// nsITimerCallback implementation +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::Notify(nsITimer* timer) { + LOG(("nsUrlClassifierStreamUpdater::Notify [%p]", this)); + + if (timer == mFetchNextRequestTimer) { + mFetchNextRequestTimer = nullptr; + FetchNextRequest(); + return NS_OK; + } + + if (timer == mFetchIndirectUpdatesTimer) { + mFetchIndirectUpdatesTimer = nullptr; + // Start the update process up again. + FetchNext(); + return NS_OK; + } + + bool updateFailed = false; + if (timer == mResponseTimeoutTimer) { + mResponseTimeoutTimer = nullptr; + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + mDownloadError = true; // Trigger backoff + updateFailed = true; + MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Error, + ("Safe Browsing timed out while waiting for the update server to " + "respond.")); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT, mTelemetryProvider, + static_cast<uint8_t>(eResponseTimeout)); + } + + if (timer == mTimeoutTimer) { + mTimeoutTimer = nullptr; + // No backoff since the connection may just be temporarily slow. + updateFailed = true; + MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Error, + ("Safe Browsing timed out while waiting for the update server to " + "finish.")); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT, mTelemetryProvider, + static_cast<uint8_t>(eDownloadTimeout)); + } + + if (updateFailed) { + // Cancelling the channel will trigger OnStopRequest. + if (mChannel) { + mozilla::Unused << mChannel->Cancel(NS_ERROR_ABORT); + mChannel = nullptr; + } + mTelemetryClockStart = 0; + + if (mFetchIndirectUpdatesTimer) { + mFetchIndirectUpdatesTimer->Cancel(); + mFetchIndirectUpdatesTimer = nullptr; + } + if (mFetchNextRequestTimer) { + mFetchNextRequestTimer->Cancel(); + mFetchNextRequestTimer = nullptr; + } + + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("A timer is fired from nowhere."); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +//// nsINamed + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::GetName(nsACString& aName) { + aName.AssignLiteral("nsUrlClassifierStreamUpdater"); + return NS_OK; +} diff --git a/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.h b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.h new file mode 100644 index 0000000000..cc5508c3e6 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.h @@ -0,0 +1,129 @@ +//* -*- 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/. */ + +#ifndef nsUrlClassifierStreamUpdater_h_ +#define nsUrlClassifierStreamUpdater_h_ + +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsINamed.h" +#include "nsIObserver.h" +#include "nsIUrlClassifierStreamUpdater.h" +#include "nsIStreamListener.h" +#include "nsIChannel.h" +#include "nsTArray.h" +#include "nsITimer.h" + +// Forward declare pointers +class nsIURI; + +class nsUrlClassifierStreamUpdater final + : public nsIUrlClassifierStreamUpdater, + public nsIUrlClassifierUpdateObserver, + public nsIStreamListener, + public nsIObserver, + public nsIInterfaceRequestor, + public nsITimerCallback, + public nsINamed { + public: + nsUrlClassifierStreamUpdater(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERSTREAMUPDATER + NS_DECL_NSIURLCLASSIFIERUPDATEOBSERVER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + private: + // No subclassing + ~nsUrlClassifierStreamUpdater() = default; + + // When the dbservice sends an UpdateComplete or UpdateFailure, we call this + // to reset the stream updater. + void DownloadDone(); + + // Disallow copy constructor + nsUrlClassifierStreamUpdater(nsUrlClassifierStreamUpdater&); + + nsresult AddRequestBody(const nsACString& aRequestBody); + + // Fetches an update for a single table. + nsresult FetchUpdate(nsIURI* aURI, const nsACString& aRequest, + bool aIsPostRequest, const nsACString& aTable); + // Dumb wrapper so we don't have to create URIs. + nsresult FetchUpdate(const nsACString& aURI, const nsACString& aRequest, + bool aIsPostRequest, const nsACString& aTable); + + // Fetches the next table, from mPendingUpdates. + nsresult FetchNext(); + // Fetches the next request, from mPendingRequests + nsresult FetchNextRequest(); + + struct UpdateRequest { + nsCString mTables; + nsCString mRequestPayload; + bool mIsPostRequest; + nsCString mUrl; + nsCOMPtr<nsIUrlClassifierCallback> mSuccessCallback; + nsCOMPtr<nsIUrlClassifierCallback> mUpdateErrorCallback; + nsCOMPtr<nsIUrlClassifierCallback> mDownloadErrorCallback; + }; + // Utility function to create an update request. + void BuildUpdateRequest(const nsACString& aRequestTables, + const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aUpdateUrl, + nsIUrlClassifierCallback* aSuccessCallback, + nsIUrlClassifierCallback* aUpdateErrorCallback, + nsIUrlClassifierCallback* aDownloadErrorCallback, + UpdateRequest* aRequest); + + bool mIsUpdating; + bool mInitialized; + bool mDownloadError; + bool mBeganStream; + + nsCString mDownloadErrorStatusStr; + + // Note that mStreamTable is only used by v2, it is empty for v4 update. + nsCString mStreamTable; + + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIUrlClassifierDBService> mDBService; + + // In v2, a update response might contain redirection and this + // timer is for fetching the redirected update. + nsCOMPtr<nsITimer> mFetchIndirectUpdatesTimer; + + // When we DownloadUpdate(), the DBService might be busy on processing + // request issused outside of StreamUpdater. We have to fire a timer to + // retry on our own. + nsCOMPtr<nsITimer> mFetchNextRequestTimer; + + // Timer to abort the download if the server takes too long to respond. + nsCOMPtr<nsITimer> mResponseTimeoutTimer; + + // Timer to abort the download if it takes too long. + nsCOMPtr<nsITimer> mTimeoutTimer; + + mozilla::UniquePtr<UpdateRequest> mCurrentRequest; + nsTArray<UpdateRequest> mPendingRequests; + + struct PendingUpdate { + nsCString mUrl; + nsCString mTable; + }; + nsTArray<PendingUpdate> mPendingUpdates; + + // The provider for current update request and should be only used by + // telemetry since it would show up as "other" for any other providers. + nsCString mTelemetryProvider; + PRIntervalTime mTelemetryClockStart; +}; + +#endif // nsUrlClassifierStreamUpdater_h_ diff --git a/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp b/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp new file mode 100644 index 0000000000..da5e9cd451 --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp @@ -0,0 +1,1125 @@ +/* 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 "chromium/safebrowsing.pb.h" +#include "nsEscape.h" +#include "nsString.h" +#include "nsIURI.h" +#include "nsIURIMutator.h" +#include "nsIURL.h" +#include "nsIXULRuntime.h" +#include "nsUrlClassifierUtils.h" +#include "nsTArray.h" +#include "nsReadableUtils.h" +#include "plbase64.h" +#include "nsPrintfCString.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Mutex.h" +#include "nsIRedirectHistoryEntry.h" +#include "nsIHttpChannelInternal.h" +#include "mozIThirdPartyUtil.h" +#include "nsIDocShell.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "nsNetUtil.h" +#include "nsIHttpChannel.h" +#include "nsIObserverService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadManager.h" +#include "nsTHashSet.h" +#include "Classifier.h" +#include "Entries.h" +#include "prprf.h" +#include "prtime.h" + +#define DEFAULT_PROTOCOL_VERSION "2.2" + +using namespace mozilla; +using namespace mozilla::safebrowsing; + +static mozilla::StaticRefPtr<nsUrlClassifierUtils> gUrlClassifierUtils; + +static char int_to_hex_digit(int32_t i) { + NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit"); + return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A'))); +} + +static bool IsDecimal(const nsACString& num) { + for (uint32_t i = 0; i < num.Length(); i++) { + if (!mozilla::IsAsciiDigit(num[i])) { + return false; + } + } + + return true; +} + +static bool IsHex(const nsACString& num) { + if (num.Length() < 3) { + return false; + } + + if (num[0] != '0' || !(num[1] == 'x' || num[1] == 'X')) { + return false; + } + + for (uint32_t i = 2; i < num.Length(); i++) { + if (!mozilla::IsAsciiHexDigit(num[i])) { + return false; + } + } + + return true; +} + +static bool IsOctal(const nsACString& num) { + if (num.Length() < 2) { + return false; + } + + if (num[0] != '0') { + return false; + } + + for (uint32_t i = 1; i < num.Length(); i++) { + if (!mozilla::IsAsciiDigit(num[i]) || num[i] == '8' || num[i] == '9') { + return false; + } + } + + return true; +} + +///////////////////////////////////////////////////////////////// +// SafeBrowsing V4 related utits. + +namespace mozilla { +namespace safebrowsing { + +static PlatformType GetPlatformType() { +#if defined(ANDROID) + return ANDROID_PLATFORM; +#elif defined(XP_MACOSX) + return OSX_PLATFORM; +#elif defined(XP_LINUX) + return LINUX_PLATFORM; +#elif defined(XP_WIN) + return WINDOWS_PLATFORM; +#else + // Default to Linux for other platforms (see bug 1362501). + return LINUX_PLATFORM; +#endif +} + +typedef FetchThreatListUpdatesRequest_ListUpdateRequest ListUpdateRequest; +typedef FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints Constraints; + +static void InitListUpdateRequest(ThreatType aThreatType, + const nsCString& aStateBase64, + ListUpdateRequest* aListUpdateRequest) { + aListUpdateRequest->set_threat_type(aThreatType); + PlatformType platform = GetPlatformType(); +#if defined(ANDROID) + // Temporary hack to fix bug 1441345. + if ((aThreatType == SOCIAL_ENGINEERING_PUBLIC) || + (aThreatType == SOCIAL_ENGINEERING)) { + platform = LINUX_PLATFORM; + } +#endif + aListUpdateRequest->set_platform_type(platform); + aListUpdateRequest->set_threat_entry_type(URL); + + Constraints* contraints = new Constraints(); + contraints->add_supported_compressions(RICE); + aListUpdateRequest->set_allocated_constraints(contraints); + + // Only set non-empty state. + if (!aStateBase64.IsEmpty()) { + nsCString stateBinary; + nsresult rv = Base64Decode(aStateBase64, stateBinary); + if (NS_SUCCEEDED(rv)) { + aListUpdateRequest->set_state(stateBinary.get(), stateBinary.Length()); + } + } +} + +static ClientInfo* CreateClientInfo() { + ClientInfo* c = new ClientInfo(); + + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + + nsAutoCString clientId; + nsresult rv = prefBranch->GetCharPref("browser.safebrowsing.id", clientId); + + if (NS_FAILED(rv)) { + clientId = "Firefox"; // Use "Firefox" as fallback. + } + + c->set_client_id(clientId.get()); + + return c; +} + +static bool IsAllowedOnCurrentPlatform(uint32_t aThreatType) { + PlatformType platform = GetPlatformType(); + + switch (aThreatType) { + case POTENTIALLY_HARMFUL_APPLICATION: + // Bug 1388582 - Google server would respond 404 error if the request + // contains PHA on non-mobile platform. + return ANDROID_PLATFORM == platform; + case MALICIOUS_BINARY: + case CSD_DOWNLOAD_WHITELIST: + // Bug 1392204 - 'goog-downloadwhite-proto' and 'goog-badbinurl-proto' + // are not available on android. + return ANDROID_PLATFORM != platform; + } + // We allow every threat type not listed in the switch cases. + return true; +} + +} // end of namespace safebrowsing. +} // end of namespace mozilla. + +// static +already_AddRefed<nsUrlClassifierUtils> +nsUrlClassifierUtils::GetXPCOMSingleton() { + if (gUrlClassifierUtils) { + return do_AddRef(gUrlClassifierUtils); + } + + RefPtr<nsUrlClassifierUtils> utils = new nsUrlClassifierUtils(); + if (NS_WARN_IF(NS_FAILED(utils->Init()))) { + return nullptr; + } + + // Note: This is cleared in the nsUrlClassifierUtils destructor. + gUrlClassifierUtils = utils.get(); + ClearOnShutdown(&gUrlClassifierUtils); + return utils.forget(); +} + +// static +nsUrlClassifierUtils* nsUrlClassifierUtils::GetInstance() { + if (!gUrlClassifierUtils) { + RefPtr<nsUrlClassifierUtils> utils = GetXPCOMSingleton(); + } + + return gUrlClassifierUtils; +} + +nsUrlClassifierUtils::nsUrlClassifierUtils() + : mProviderDictLock("nsUrlClassifierUtils.mProviderDictLock") {} + +nsUrlClassifierUtils::~nsUrlClassifierUtils() { + if (gUrlClassifierUtils) { + MOZ_ASSERT(gUrlClassifierUtils == this); + gUrlClassifierUtils = nullptr; + } +} + +nsresult nsUrlClassifierUtils::Init() { + // nsIUrlClassifierUtils is a thread-safe service so it's + // allowed to use on non-main threads. However, building + // the provider dictionary must be on the main thread. + // We forcefully load nsUrlClassifierUtils in + // nsUrlClassifierDBService::Init() to ensure we must + // now be on the main thread. + nsresult rv = ReadProvidersFromPrefs(mProviderDict); + NS_ENSURE_SUCCESS(rv, rv); + + // Add an observer for shutdown + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) return NS_ERROR_FAILURE; + + observerService->AddObserver(this, "xpcom-shutdown-threads", false); + mozilla::Preferences::AddStrongObserver(this, "browser.safebrowsing"); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsUrlClassifierUtils, nsIUrlClassifierUtils, nsIObserver) + +///////////////////////////////////////////////////////////////////////////// +// nsIUrlClassifierUtils + +NS_IMETHODIMP +nsUrlClassifierUtils::GetKeyForURI(nsIURI* uri, nsACString& _retval) { + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri); + if (!innerURI) innerURI = uri; + + nsAutoCString host; + innerURI->GetAsciiHost(host); + + if (host.IsEmpty()) { + return NS_ERROR_MALFORMED_URI; + } + + nsresult rv = CanonicalizeHostname(host, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString path; + rv = innerURI->GetPathQueryRef(path); + NS_ENSURE_SUCCESS(rv, rv); + + // Strip fragment and query because canonicalization only applies to path + int32_t ref = path.FindChar('#'); + if (ref != kNotFound) { + path.SetLength(ref); + } + + int32_t query = path.FindChar('?'); + if (query != kNotFound) { + path.SetLength(query); + } + + nsAutoCString temp; + rv = CanonicalizePath(path, temp); + NS_ENSURE_SUCCESS(rv, rv); + + _retval.Append(temp); + + if (query != kNotFound) { + nsAutoCString query; + rv = innerURI->GetQuery(query); + NS_ENSURE_SUCCESS(rv, rv); + + _retval.AppendPrintf("?%s", query.get()); + } + + return NS_OK; +} + +// We use "goog-*-proto" as the list name for v4, where "proto" indicates +// it's updated (as well as hash completion) via protobuf. +// +// In the mozilla official build, we are allowed to use the +// private phishing list (goog-phish-proto). See Bug 1288840. +static const struct { + const char* mListName; + uint32_t mThreatType; +} THREAT_TYPE_CONV_TABLE[] = { + {"goog-malware-proto", MALWARE_THREAT}, // 1 + {"googpub-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2 + {"goog-unwanted-proto", UNWANTED_SOFTWARE}, // 3 + {"goog-harmful-proto", POTENTIALLY_HARMFUL_APPLICATION}, // 4 + {"goog-phish-proto", SOCIAL_ENGINEERING}, // 5 + + // For application reputation + {"goog-badbinurl-proto", MALICIOUS_BINARY}, // 7 + {"goog-downloadwhite-proto", CSD_DOWNLOAD_WHITELIST}, // 9 + + // For testing purpose. + {"moztest-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2 + {"test-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2 + {"moztest-unwanted-proto", UNWANTED_SOFTWARE}, // 3 + {"test-unwanted-proto", UNWANTED_SOFTWARE}, // 3 +}; + +NS_IMETHODIMP +nsUrlClassifierUtils::ConvertThreatTypeToListNames(uint32_t aThreatType, + nsACString& aListNames) { + for (uint32_t i = 0; i < ArrayLength(THREAT_TYPE_CONV_TABLE); i++) { + if (aThreatType == THREAT_TYPE_CONV_TABLE[i].mThreatType) { + if (!aListNames.IsEmpty()) { + aListNames.AppendLiteral(","); + } + aListNames += THREAT_TYPE_CONV_TABLE[i].mListName; + } + } + + return aListNames.IsEmpty() ? NS_ERROR_FAILURE : NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::ConvertListNameToThreatType(const nsACString& aListName, + uint32_t* aThreatType) { + for (uint32_t i = 0; i < ArrayLength(THREAT_TYPE_CONV_TABLE); i++) { + if (aListName.EqualsASCII(THREAT_TYPE_CONV_TABLE[i].mListName)) { + *aThreatType = THREAT_TYPE_CONV_TABLE[i].mThreatType; + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::GetProvider(const nsACString& aTableName, + nsACString& aProvider) { + MutexAutoLock lock(mProviderDictLock); + nsCString* provider = nullptr; + + if (IsTestTable(aTableName)) { + aProvider = nsLiteralCString(TESTING_TABLE_PROVIDER_NAME); + } else if (mProviderDict.Get(aTableName, &provider)) { + aProvider = provider ? *provider : ""_ns; + } else { + aProvider.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::GetTelemetryProvider(const nsACString& aTableName, + nsACString& aProvider) { + GetProvider(aTableName, aProvider); + // Exceptionlist known providers to avoid reporting on private ones. + // An empty provider is treated as "other" + if (!"mozilla"_ns.Equals(aProvider) && !"google"_ns.Equals(aProvider) && + !"google4"_ns.Equals(aProvider) && !"baidu"_ns.Equals(aProvider) && + !"mozcn"_ns.Equals(aProvider) && !"yandex"_ns.Equals(aProvider) && + !nsLiteralCString(TESTING_TABLE_PROVIDER_NAME).Equals(aProvider)) { + aProvider.AssignLiteral("other"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::GetProtocolVersion(const nsACString& aProvider, + nsACString& aVersion) { + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefBranch) { + nsPrintfCString prefName("browser.safebrowsing.provider.%s.pver", + nsCString(aProvider).get()); + nsAutoCString version; + nsresult rv = prefBranch->GetCharPref(prefName.get(), version); + + aVersion = NS_SUCCEEDED(rv) ? version.get() : DEFAULT_PROTOCOL_VERSION; + } else { + aVersion = DEFAULT_PROTOCOL_VERSION; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::MakeUpdateRequestV4( + const nsTArray<nsCString>& aListNames, + const nsTArray<nsCString>& aStatesBase64, nsACString& aRequest) { + using namespace mozilla::safebrowsing; + + if (aListNames.Length() != aStatesBase64.Length()) { + return NS_ERROR_INVALID_ARG; + } + + FetchThreatListUpdatesRequest r; + r.set_allocated_client(CreateClientInfo()); + + for (uint32_t i = 0; i < aListNames.Length(); i++) { + uint32_t threatType; + nsresult rv = ConvertListNameToThreatType(aListNames[i], &threatType); + if (NS_FAILED(rv)) { + continue; // Unknown list name. + } + if (!IsAllowedOnCurrentPlatform(threatType)) { + NS_WARNING( + nsPrintfCString( + "Threat type %d (%s) is unsupported on current platform: %d", + threatType, aListNames[i].get(), GetPlatformType()) + .get()); + continue; // Some threat types are not available on some platforms. + } + auto lur = r.mutable_list_update_requests()->Add(); + InitListUpdateRequest(static_cast<ThreatType>(threatType), aStatesBase64[i], + lur); + } + + // Then serialize. + std::string s; + r.SerializeToString(&s); + + nsCString out; + nsresult rv = Base64URLEncode(s.size(), (const uint8_t*)s.c_str(), + Base64URLEncodePaddingPolicy::Include, out); + NS_ENSURE_SUCCESS(rv, rv); + + aRequest = out; + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::MakeFindFullHashRequestV4( + const nsTArray<nsCString>& aListNames, + const nsTArray<nsCString>& aListStatesBase64, + const nsTArray<nsCString>& aPrefixesBase64, nsACString& aRequest) { + if (aListNames.Length() != aListStatesBase64.Length()) { + return NS_ERROR_INVALID_ARG; + } + + FindFullHashesRequest r; + r.set_allocated_client(CreateClientInfo()); + + nsresult rv; + + //------------------------------------------------------------------- + // Set up FindFullHashesRequest.threat_info. + auto threatInfo = r.mutable_threat_info(); + + PlatformType platform = GetPlatformType(); + + // 1) Set threat types. + for (uint32_t i = 0; i < aListNames.Length(); i++) { + // Add threat types. + uint32_t threatType; + rv = ConvertListNameToThreatType(aListNames[i], &threatType); + NS_ENSURE_SUCCESS(rv, rv); + if (!IsAllowedOnCurrentPlatform(threatType)) { + NS_WARNING( + nsPrintfCString( + "Threat type %d (%s) is unsupported on current platform: %d", + threatType, aListNames[i].get(), GetPlatformType()) + .get()); + continue; + } + threatInfo->add_threat_types((ThreatType)threatType); + +#if defined(ANDROID) + // Temporary hack to fix bug 1441345. + if (((ThreatType)threatType == SOCIAL_ENGINEERING_PUBLIC) || + ((ThreatType)threatType == SOCIAL_ENGINEERING)) { + platform = LINUX_PLATFORM; + } +#endif + + // Add client states for index 'i' only when the threat type is available + // on current platform. + nsCString stateBinary; + rv = Base64Decode(aListStatesBase64[i], stateBinary); + NS_ENSURE_SUCCESS(rv, rv); + r.add_client_states(stateBinary.get(), stateBinary.Length()); + } + + // 2) Set platform type. + threatInfo->add_platform_types(platform); + + // 3) Set threat entry type. + threatInfo->add_threat_entry_types(URL); + + // 4) Set threat entries. + for (const nsCString& prefix : aPrefixesBase64) { + nsCString prefixBinary; + rv = Base64Decode(prefix, prefixBinary); + threatInfo->add_threat_entries()->set_hash(prefixBinary.get(), + prefixBinary.Length()); + } + //------------------------------------------------------------------- + + // Then serialize. + std::string s; + r.SerializeToString(&s); + + nsCString out; + rv = Base64URLEncode(s.size(), (const uint8_t*)s.c_str(), + Base64URLEncodePaddingPolicy::Include, out); + NS_ENSURE_SUCCESS(rv, rv); + + aRequest = out; + + return NS_OK; +} + +// Remove ref, query, userpass, anypart which may contain sensitive data +static nsresult GetSpecWithoutSensitiveData(nsIURI* aUri, nsACString& aSpec) { + if (NS_WARN_IF(!aUri)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + nsCOMPtr<nsIURL> url(do_QueryInterface(aUri)); + if (url) { + nsCOMPtr<nsIURI> clone; + rv = NS_MutateURI(url) + .SetQuery(""_ns) + .SetRef(""_ns) + .SetUserPass(""_ns) + .Finalize(clone); + NS_ENSURE_SUCCESS(rv, rv); + rv = clone->GetAsciiSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +static nsresult AddThreatSourceFromChannel(ThreatHit& aHit, + nsIChannel* aChannel, + ThreatHit_ThreatSourceType aType) { + if (NS_WARN_IF(!aChannel)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + + auto matchingSource = aHit.add_resources(); + matchingSource->set_type(aType); + + nsCOMPtr<nsIURI> uri; + rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spec; + rv = GetSpecWithoutSensitiveData(uri, spec); + NS_ENSURE_SUCCESS(rv, rv); + matchingSource->set_url(spec.get()); + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); + if (referrerInfo) { + nsAutoCString referrerSpec; + nsCOMPtr<nsIURI> referrer = referrerInfo->GetComputedReferrer(); + if (referrer) { + rv = GetSpecWithoutSensitiveData(referrer, referrerSpec); + NS_ENSURE_SUCCESS(rv, rv); + matchingSource->set_referrer(referrerSpec.get()); + } + } + } + + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(aChannel); + if (httpChannelInternal) { + nsCString remoteIp; + rv = httpChannelInternal->GetRemoteAddress(remoteIp); + if (NS_SUCCEEDED(rv) && !remoteIp.IsEmpty()) { + matchingSource->set_remote_ip(remoteIp.get()); + } + } + return NS_OK; +} +static nsresult AddThreatSourceFromRedirectEntry( + ThreatHit& aHit, nsIRedirectHistoryEntry* aRedirectEntry, + ThreatHit_ThreatSourceType aType) { + if (NS_WARN_IF(!aRedirectEntry)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + + nsCOMPtr<nsIPrincipal> principal; + rv = aRedirectEntry->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString spec; + rv = principal->GetExposableSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + auto source = aHit.add_resources(); + source->set_url(spec.get()); + source->set_type(aType); + + nsCOMPtr<nsIURI> referrer; + rv = aRedirectEntry->GetReferrerURI(getter_AddRefs(referrer)); + if (NS_SUCCEEDED(rv) && referrer) { + nsCString referrerSpec; + rv = GetSpecWithoutSensitiveData(referrer, referrerSpec); + NS_ENSURE_SUCCESS(rv, rv); + source->set_referrer(referrerSpec.get()); + } + + nsCString remoteIp; + rv = aRedirectEntry->GetRemoteAddress(remoteIp); + if (NS_SUCCEEDED(rv) && !remoteIp.IsEmpty()) { + source->set_remote_ip(remoteIp.get()); + } + return NS_OK; +} + +// Add top level tab url and redirect threatsources to threatHit message +static nsresult AddTabThreatSources(ThreatHit& aHit, nsIChannel* aChannel) { + if (NS_WARN_IF(!aChannel)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + nsCOMPtr<mozIDOMWindowProxy> win; + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, nullptr, + getter_AddRefs(win)); + NS_ENSURE_SUCCESS(rv, rv); + + auto* pwin = nsPIDOMWindowOuter::From(win); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + if (!docShell) { + return NS_OK; + } + + nsCOMPtr<nsIChannel> topChannel; + docShell->GetCurrentDocumentChannel(getter_AddRefs(topChannel)); + if (!topChannel) { + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> topUri; + rv = topChannel->GetURI(getter_AddRefs(topUri)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isTopUri = false; + rv = topUri->Equals(uri, &isTopUri); + if (NS_SUCCEEDED(rv) && !isTopUri) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + if (loadInfo->RedirectChain().Length()) { + AddThreatSourceFromRedirectEntry(aHit, loadInfo->RedirectChain()[0], + ThreatHit_ThreatSourceType_TAB_RESOURCE); + } + } + + // Set top level tab_url threat source + rv = AddThreatSourceFromChannel(aHit, topChannel, + ThreatHit_ThreatSourceType_TAB_URL); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + // Set tab_redirect threat sources if there's any + nsCOMPtr<nsILoadInfo> topLoadInfo = topChannel->LoadInfo(); + nsIRedirectHistoryEntry* redirectEntry; + size_t length = topLoadInfo->RedirectChain().Length(); + for (size_t i = 0; i < length; i++) { + redirectEntry = topLoadInfo->RedirectChain()[i]; + AddThreatSourceFromRedirectEntry(aHit, redirectEntry, + ThreatHit_ThreatSourceType_TAB_REDIRECT); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::MakeThreatHitReport(nsIChannel* aChannel, + const nsACString& aListName, + const nsACString& aHashBase64, + nsACString& aRequest) { + if (NS_WARN_IF(aListName.IsEmpty()) || NS_WARN_IF(aHashBase64.IsEmpty()) || + NS_WARN_IF(!aChannel)) { + return NS_ERROR_INVALID_ARG; + } + + ThreatHit hit; + nsresult rv; + + uint32_t threatType; + rv = ConvertListNameToThreatType(aListName, &threatType); + NS_ENSURE_SUCCESS(rv, rv); + hit.set_threat_type(static_cast<ThreatType>(threatType)); + + hit.set_platform_type(GetPlatformType()); + + nsCString hash; + rv = Base64Decode(aHashBase64, hash); + if (NS_FAILED(rv) || hash.Length() != COMPLETE_SIZE) { + return NS_ERROR_FAILURE; + } + + auto threatEntry = hit.mutable_entry(); + threatEntry->set_hash(hash.get(), hash.Length()); + + // Set matching source + rv = AddThreatSourceFromChannel(hit, aChannel, + ThreatHit_ThreatSourceType_MATCHING_URL); + Unused << NS_WARN_IF(NS_FAILED(rv)); + // Set tab url, tab resource url and redirect sources + rv = AddTabThreatSources(hit, aChannel); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + hit.set_allocated_client_info(CreateClientInfo()); + + std::string s; + hit.SerializeToString(&s); + + nsCString out; + rv = Base64URLEncode(s.size(), reinterpret_cast<const uint8_t*>(s.c_str()), + Base64URLEncodePaddingPolicy::Include, out); + NS_ENSURE_SUCCESS(rv, rv); + + aRequest = out; + + return NS_OK; +} + +static uint32_t DurationToMs(const Duration& aDuration) { + // Seconds precision is good enough. Ignore nanoseconds like Chrome does. + return aDuration.seconds() * 1000; +} + +NS_IMETHODIMP +nsUrlClassifierUtils::ParseFindFullHashResponseV4( + const nsACString& aResponse, + nsIUrlClassifierParseFindFullHashCallback* aCallback) { + enum CompletionErrorType { + SUCCESS = 0, + PARSING_FAILURE = 1, + UNKNOWN_THREAT_TYPE = 2, + }; + + FindFullHashesResponse r; + if (!r.ParseFromArray(aResponse.BeginReading(), aResponse.Length())) { + NS_WARNING("Invalid response"); + Telemetry::Accumulate(Telemetry::URLCLASSIFIER_COMPLETION_ERROR, + PARSING_FAILURE); + return NS_ERROR_FAILURE; + } + + bool hasUnknownThreatType = false; + + for (auto& m : r.matches()) { + nsCString tableNames; + nsresult rv = ConvertThreatTypeToListNames(m.threat_type(), tableNames); + if (NS_FAILED(rv)) { + hasUnknownThreatType = true; + continue; // Ignore un-convertable threat type. + } + auto& hash = m.threat().hash(); + auto cacheDurationSec = m.cache_duration().seconds(); + aCallback->OnCompleteHashFound( + nsDependentCString(hash.c_str(), hash.length()), tableNames, + cacheDurationSec); + } + + auto minWaitDuration = DurationToMs(r.minimum_wait_duration()); + auto negCacheDurationSec = r.negative_cache_duration().seconds(); + + aCallback->OnResponseParsed(minWaitDuration, negCacheDurationSec); + + Telemetry::Accumulate(Telemetry::URLCLASSIFIER_COMPLETION_ERROR, + hasUnknownThreatType ? UNKNOWN_THREAT_TYPE : SUCCESS); + return NS_OK; +} + +////////////////////////////////////////////////////////// +// nsIObserver + +NS_IMETHODIMP +nsUrlClassifierUtils::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (0 == strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + MutexAutoLock lock(mProviderDictLock); + return ReadProvidersFromPrefs(mProviderDict); + } + + if (0 == strcmp(aTopic, "xpcom-shutdown-threads")) { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE); + return prefs->RemoveObserver("browser.safebrowsing", this); + } + + return NS_ERROR_UNEXPECTED; +} + +///////////////////////////////////////////////////////////////////////////// +// non-interface methods + +nsresult nsUrlClassifierUtils::ReadProvidersFromPrefs(ProviderDictType& aDict) { + MOZ_ASSERT(NS_IsMainThread(), + "ReadProvidersFromPrefs must be on main thread"); + + 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. + nsTArray<nsCString> childArray; + rv = prefBranch->GetChildList("", childArray); + NS_ENSURE_SUCCESS(rv, rv); + + // Collect providers from childArray. + nsTHashSet<nsCString> providers; + for (auto& child : childArray) { + auto dotPos = child.FindChar('.'); + if (dotPos < 0) { + continue; + } + + nsDependentCSubstring provider = Substring(child, 0, dotPos); + + providers.Insert(provider); + } + + // 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 (const auto& provider : providers) { + nsPrintfCString owninListsPref("%s.lists", + nsPromiseFlatCString{provider}.get()); + + nsAutoCString owningLists; + nsresult rv = prefBranch->GetCharPref(owninListsPref.get(), owningLists); + if (NS_FAILED(rv)) { + continue; + } + + // We've got the owning lists (represented as string) of |provider|. + // Build the dictionary for the owning list and the current provider. + nsTArray<nsCString> tables; + Classifier::SplitTables(owningLists, tables); + for (auto tableName : tables) { + aDict.InsertOrUpdate(tableName, MakeUnique<nsCString>(provider)); + } + } + + return NS_OK; +} + +nsresult nsUrlClassifierUtils::CanonicalizeHostname(const nsACString& hostname, + nsACString& _retval) { + nsAutoCString unescaped; + if (!NS_UnescapeURL(PromiseFlatCString(hostname).get(), + PromiseFlatCString(hostname).Length(), 0, unescaped)) { + unescaped.Assign(hostname); + } + + nsAutoCString cleaned; + CleanupHostname(unescaped, cleaned); + + nsAutoCString temp; + ParseIPAddress(cleaned, temp); + if (!temp.IsEmpty()) { + cleaned.Assign(temp); + } + + ToLowerCase(cleaned); + SpecialEncode(cleaned, false, _retval); + + return NS_OK; +} + +nsresult nsUrlClassifierUtils::CanonicalizePath(const nsACString& path, + nsACString& _retval) { + _retval.Truncate(); + + nsAutoCString decodedPath(path); + nsAutoCString temp; + while (NS_UnescapeURL(decodedPath.get(), decodedPath.Length(), 0, temp)) { + decodedPath.Assign(temp); + temp.Truncate(); + } + + SpecialEncode(decodedPath, true, _retval); + // XXX: lowercase the path? + + return NS_OK; +} + +void nsUrlClassifierUtils::CleanupHostname(const nsACString& hostname, + nsACString& _retval) { + _retval.Truncate(); + + const char* curChar = hostname.BeginReading(); + const char* end = hostname.EndReading(); + char lastChar = '\0'; + while (curChar != end) { + unsigned char c = static_cast<unsigned char>(*curChar); + if (c == '.' && (lastChar == '\0' || lastChar == '.')) { + // skip + } else { + _retval.Append(*curChar); + } + lastChar = c; + ++curChar; + } + + // cut off trailing dots + while (_retval.Length() > 0 && _retval[_retval.Length() - 1] == '.') { + _retval.SetLength(_retval.Length() - 1); + } +} + +void nsUrlClassifierUtils::ParseIPAddress(const nsACString& host, + nsACString& _retval) { + _retval.Truncate(); + nsACString::const_iterator iter, end; + host.BeginReading(iter); + host.EndReading(end); + + if (host.Length() <= 15) { + // The Windows resolver allows a 4-part dotted decimal IP address to + // have a space followed by any old rubbish, so long as the total length + // of the string doesn't get above 15 characters. So, "10.192.95.89 xy" + // is resolved to 10.192.95.89. + // If the string length is greater than 15 characters, e.g. + // "10.192.95.89 xy.wildcard.example.com", it will be resolved through + // DNS. + + if (FindCharInReadable(' ', iter, end)) { + end = iter; + } + } + + for (host.BeginReading(iter); iter != end; iter++) { + if (!(mozilla::IsAsciiHexDigit(*iter) || *iter == 'x' || *iter == 'X' || + *iter == '.')) { + // not an IP + return; + } + } + + host.BeginReading(iter); + nsTArray<nsCString> parts; + ParseString(PromiseFlatCString(Substring(iter, end)), '.', parts); + if (parts.Length() > 4) { + return; + } + + // If any potentially-octal numbers (start with 0 but not hex) have + // non-octal digits, no part of the ip can be in octal + // XXX: this came from the old javascript implementation, is it really + // supposed to be like this? + bool allowOctal = true; + uint32_t i; + + for (i = 0; i < parts.Length(); i++) { + const nsCString& part = parts[i]; + if (part[0] == '0') { + for (uint32_t j = 1; j < part.Length(); j++) { + if (part[j] == 'x') { + break; + } + if (part[j] == '8' || part[j] == '9') { + allowOctal = false; + break; + } + } + } + } + + for (i = 0; i < parts.Length(); i++) { + nsAutoCString canonical; + + if (i == parts.Length() - 1) { + CanonicalNum(parts[i], 5 - parts.Length(), allowOctal, canonical); + } else { + CanonicalNum(parts[i], 1, allowOctal, canonical); + } + + if (canonical.IsEmpty()) { + _retval.Truncate(); + return; + } + + if (_retval.IsEmpty()) { + _retval.Assign(canonical); + } else { + _retval.Append('.'); + _retval.Append(canonical); + } + } +} + +void nsUrlClassifierUtils::CanonicalNum(const nsACString& num, uint32_t bytes, + bool allowOctal, nsACString& _retval) { + _retval.Truncate(); + + if (num.Length() < 1) { + return; + } + + uint32_t val; + if (allowOctal && IsOctal(num)) { + if (PR_sscanf(PromiseFlatCString(num).get(), "%o", &val) != 1) { + return; + } + } else if (IsDecimal(num)) { + if (PR_sscanf(PromiseFlatCString(num).get(), "%u", &val) != 1) { + return; + } + } else if (IsHex(num)) { + if (PR_sscanf(PromiseFlatCString(num).get(), + num[1] == 'X' ? "0X%x" : "0x%x", &val) != 1) { + return; + } + } else { + return; + } + + while (bytes--) { + char buf[20]; + SprintfLiteral(buf, "%u", val & 0xff); + if (_retval.IsEmpty()) { + _retval.Assign(buf); + } else { + _retval = nsDependentCString(buf) + "."_ns + _retval; + } + val >>= 8; + } +} + +// This function will encode all "special" characters in typical url +// encoding, that is %hh where h is a valid hex digit. It will also fold +// any duplicated slashes. +bool nsUrlClassifierUtils::SpecialEncode(const nsACString& url, + bool foldSlashes, + nsACString& _retval) { + bool changed = false; + const char* curChar = url.BeginReading(); + const char* end = url.EndReading(); + + unsigned char lastChar = '\0'; + while (curChar != end) { + unsigned char c = static_cast<unsigned char>(*curChar); + if (ShouldURLEscape(c)) { + _retval.Append('%'); + _retval.Append(int_to_hex_digit(c / 16)); + _retval.Append(int_to_hex_digit(c % 16)); + + changed = true; + } else if (foldSlashes && (c == '/' && lastChar == '/')) { + // skip + } else { + _retval.Append(*curChar); + } + lastChar = c; + curChar++; + } + return changed; +} + +bool nsUrlClassifierUtils::ShouldURLEscape(const unsigned char c) const { + return c <= 32 || c == '%' || c == '#' || c >= 127; +} + +// moztest- tables are built-in created in LookupCache, they contain hardcoded +// url entries in it. moztest tables don't support updates. +// static +bool nsUrlClassifierUtils::IsMozTestTable(const nsACString& aTableName) { + return StringBeginsWith(aTableName, "moztest-"_ns); +} + +// test- tables are used by testcases and can add custom test entries +// through update API. +// static +bool nsUrlClassifierUtils::IsTestTable(const nsACString& aTableName) { + return IsMozTestTable(aTableName) || StringBeginsWith(aTableName, "test"_ns); +} + +bool nsUrlClassifierUtils::IsInSafeMode() { + static Maybe<bool> sIsInSafeMode; + + if (!sIsInSafeMode.isSome()) { + nsCOMPtr<nsIXULRuntime> appInfo = + do_GetService("@mozilla.org/xre/runtime;1"); + if (appInfo) { + bool inSafeMode = false; + appInfo->GetInSafeMode(&inSafeMode); + sIsInSafeMode.emplace(inSafeMode); + } + } + + return sIsInSafeMode.value(); +} diff --git a/toolkit/components/url-classifier/nsUrlClassifierUtils.h b/toolkit/components/url-classifier/nsUrlClassifierUtils.h new file mode 100644 index 0000000000..5ff9d97fdc --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierUtils.h @@ -0,0 +1,72 @@ +/* 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/. */ + +#ifndef nsUrlClassifierUtils_h_ +#define nsUrlClassifierUtils_h_ + +#include "mozilla/Mutex.h" +#include "mozilla/Base64.h" +#include "nsIUrlClassifierUtils.h" +#include "nsClassHashtable.h" +#include "nsIObserver.h" + +#define TESTING_TABLE_PROVIDER_NAME "test" + +class nsUrlClassifierUtils final : public nsIUrlClassifierUtils, + public nsIObserver { + public: + typedef nsClassHashtable<nsCStringHashKey, nsCString> ProviderDictType; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURLCLASSIFIERUTILS + NS_DECL_NSIOBSERVER + + static already_AddRefed<nsUrlClassifierUtils> GetXPCOMSingleton(); + static nsUrlClassifierUtils* GetInstance(); + + nsresult CanonicalizeHostname(const nsACString& hostname, + nsACString& _retval); + nsresult CanonicalizePath(const nsACString& url, nsACString& _retval); + + // This function will encode all "special" characters in typical url encoding, + // that is %hh where h is a valid hex digit. The characters which are encoded + // by this function are any ascii characters under 32(control characters and + // space), 37(%), and anything 127 or above (special characters). Url is the + // string to encode, ret is the encoded string. Function returns true if + // ret != url. + bool SpecialEncode(const nsACString& url, bool foldSlashes, + nsACString& _retval); + + void ParseIPAddress(const nsACString& host, nsACString& _retval); + void CanonicalNum(const nsACString& num, uint32_t bytes, bool allowOctal, + nsACString& _retval); + + static bool IsMozTestTable(const nsACString& aTableName); + + static bool IsTestTable(const nsACString& aTableName); + + static bool IsInSafeMode(); + + private: + nsUrlClassifierUtils(); + ~nsUrlClassifierUtils(); + + nsresult Init(); + + // Disallow copy constructor + nsUrlClassifierUtils(const nsUrlClassifierUtils&); + + // Function to tell if we should encode a character. + bool ShouldURLEscape(const unsigned char c) const; + + void CleanupHostname(const nsACString& host, nsACString& _retval); + + nsresult ReadProvidersFromPrefs(ProviderDictType& aDict); + + // The provider lookup table and its mutex. + ProviderDictType mProviderDict; + mozilla::Mutex mProviderDictLock MOZ_UNANNOTATED; +}; + +#endif // nsUrlClassifierUtils_h_ diff --git a/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs b/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs new file mode 100644 index 0000000000..c69d0c24b4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs @@ -0,0 +1,307 @@ +const ANNOTATION_TABLE_NAME = "mochitest1-track-simple"; +const ANNOTATION_TABLE_PREF = "urlclassifier.trackingAnnotationTable"; +const ANNOTATION_ENTITYLIST_TABLE_NAME = "mochitest1-trackwhite-simple"; +const ANNOTATION_ENTITYLIST_TABLE_PREF = + "urlclassifier.trackingAnnotationWhitelistTable"; + +const TRACKING_TABLE_NAME = "mochitest2-track-simple"; +const TRACKING_TABLE_PREF = "urlclassifier.trackingTable"; +const ENTITYLIST_TABLE_NAME = "mochitest2-trackwhite-simple"; +const ENTITYLIST_TABLE_PREF = "urlclassifier.trackingWhitelistTable"; + +const SOCIAL_ANNOTATION_TABLE_NAME = "mochitest3-track-simple"; +const SOCIAL_ANNOTATION_TABLE_PREF = + "urlclassifier.features.socialtracking.annotate.blacklistTables"; +const SOCIAL_TRACKING_TABLE_NAME = "mochitest4-track-simple"; +const SOCIAL_TRACKING_TABLE_PREF = + "urlclassifier.features.socialtracking.blacklistTables"; +const EMAIL_TRACKING_TABLE_NAME = "mochitest5-track-simple"; +const EMAIL_TRACKING_TABLE_PREF = + "urlclassifier.features.emailtracking.blocklistTables"; + +let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + +export var UrlClassifierTestUtils = { + addTestTrackers() { + // Add some URLs to the tracking databases + let annotationURL1 = "tracking.example.org/"; // only for annotations + let annotationURL2 = "itisatracker.org/"; + let annotationURL3 = "trackertest.org/"; + let annotationURL4 = "another-tracking.example.net/"; + let annotationURL5 = "tlsresumptiontest.example.org/"; + let annotationEntitylistedURL = "itisatrap.org/?resource=example.org"; + let trackingURL1 = "tracking.example.com/"; // only for TP + let trackingURL2 = "itisatracker.org/"; + let trackingURL3 = "trackertest.org/"; + let entitylistedURL = "itisatrap.org/?resource=itisatracker.org"; + let socialTrackingURL = "social-tracking.example.org/"; + let emailTrackingURL = "email-tracking.example.org/"; + + let annotationUpdate = + "n:1000\ni:" + + ANNOTATION_TABLE_NAME + + "\nad:5\n" + + "a:1:32:" + + annotationURL1.length + + "\n" + + annotationURL1 + + "\n" + + "a:2:32:" + + annotationURL2.length + + "\n" + + annotationURL2 + + "\n" + + "a:3:32:" + + annotationURL3.length + + "\n" + + annotationURL3 + + "\n" + + "a:4:32:" + + annotationURL4.length + + "\n" + + annotationURL4 + + "\n" + + "a:5:32:" + + annotationURL5.length + + "\n" + + annotationURL5 + + "\n"; + let socialAnnotationUpdate = + "n:1000\ni:" + + SOCIAL_ANNOTATION_TABLE_NAME + + "\nad:1\n" + + "a:1:32:" + + socialTrackingURL.length + + "\n" + + socialTrackingURL + + "\n"; + let annotationEntitylistUpdate = + "n:1000\ni:" + + ANNOTATION_ENTITYLIST_TABLE_NAME + + "\nad:1\n" + + "a:1:32:" + + annotationEntitylistedURL.length + + "\n" + + annotationEntitylistedURL + + "\n"; + let trackingUpdate = + "n:1000\ni:" + + TRACKING_TABLE_NAME + + "\nad:3\n" + + "a:1:32:" + + trackingURL1.length + + "\n" + + trackingURL1 + + "\n" + + "a:2:32:" + + trackingURL2.length + + "\n" + + trackingURL2 + + "\n" + + "a:3:32:" + + trackingURL3.length + + "\n" + + trackingURL3 + + "\n"; + let socialTrackingUpdate = + "n:1000\ni:" + + SOCIAL_TRACKING_TABLE_NAME + + "\nad:1\n" + + "a:1:32:" + + socialTrackingURL.length + + "\n" + + socialTrackingURL + + "\n"; + let emailTrackingUpdate = + "n:1000\ni:" + + EMAIL_TRACKING_TABLE_NAME + + "\nad:1\n" + + "a:1:32:" + + emailTrackingURL.length + + "\n" + + emailTrackingURL + + "\n"; + let entitylistUpdate = + "n:1000\ni:" + + ENTITYLIST_TABLE_NAME + + "\nad:1\n" + + "a:1:32:" + + entitylistedURL.length + + "\n" + + entitylistedURL + + "\n"; + + var tables = [ + { + pref: ANNOTATION_TABLE_PREF, + name: ANNOTATION_TABLE_NAME, + update: annotationUpdate, + }, + { + pref: SOCIAL_ANNOTATION_TABLE_PREF, + name: SOCIAL_ANNOTATION_TABLE_NAME, + update: socialAnnotationUpdate, + }, + { + pref: ANNOTATION_ENTITYLIST_TABLE_PREF, + name: ANNOTATION_ENTITYLIST_TABLE_NAME, + update: annotationEntitylistUpdate, + }, + { + pref: TRACKING_TABLE_PREF, + name: TRACKING_TABLE_NAME, + update: trackingUpdate, + }, + { + pref: SOCIAL_TRACKING_TABLE_PREF, + name: SOCIAL_TRACKING_TABLE_NAME, + update: socialTrackingUpdate, + }, + { + pref: EMAIL_TRACKING_TABLE_PREF, + name: EMAIL_TRACKING_TABLE_NAME, + update: emailTrackingUpdate, + }, + { + pref: ENTITYLIST_TABLE_PREF, + name: ENTITYLIST_TABLE_NAME, + update: entitylistUpdate, + }, + ]; + + let tableIndex = 0; + let doOneUpdate = () => { + if (tableIndex == tables.length) { + return Promise.resolve(); + } + return this.useTestDatabase(tables[tableIndex]).then( + () => { + tableIndex++; + return doOneUpdate(); + }, + aErrMsg => { + dump("Rejected: " + aErrMsg + ". Retry later.\n"); + return new Promise(resolve => { + timer.initWithCallback(resolve, 100, Ci.nsITimer.TYPE_ONE_SHOT); + }).then(doOneUpdate); + } + ); + }; + + return doOneUpdate(); + }, + + cleanupTestTrackers() { + Services.prefs.clearUserPref(ANNOTATION_TABLE_PREF); + Services.prefs.clearUserPref(SOCIAL_ANNOTATION_TABLE_PREF); + Services.prefs.clearUserPref(ANNOTATION_ENTITYLIST_TABLE_PREF); + Services.prefs.clearUserPref(TRACKING_TABLE_PREF); + Services.prefs.clearUserPref(SOCIAL_TRACKING_TABLE_PREF); + Services.prefs.clearUserPref(EMAIL_TRACKING_TABLE_PREF); + Services.prefs.clearUserPref(ENTITYLIST_TABLE_PREF); + }, + + /** + * Add some entries to a test tracking protection database, and resets + * back to the default database after the test ends. + * + * @return {Promise} + */ + useTestDatabase(table) { + Services.prefs.setCharPref(table.pref, table.name); + + return new Promise((resolve, reject) => { + let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIUrlClassifierDBService + ); + let listener = { + QueryInterface: iid => { + if ( + iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIUrlClassifierUpdateObserver) + ) { + return listener; + } + + throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); + }, + updateUrlRequested: url => {}, + streamFinished: status => {}, + updateError: errorCode => { + reject("Got updateError when updating " + table.name); + }, + updateSuccess: requestedTimeout => { + resolve(); + }, + }; + + try { + dbService.beginUpdate(listener, table.name, ""); + dbService.beginStream("", ""); + dbService.updateStream(table.update); + dbService.finishStream(); + dbService.finishUpdate(); + } catch (e) { + reject("Failed to update with dbService: " + table.name); + } + }); + }, + + /** + * Handle the next "urlclassifier-before-block-channel" event. + * @param {Object} options + * @param {String} [options.filterOrigin] - Only handle event for channels + * with matching origin. + * @param {function} [options.onBeforeBlockChannel] - Optional callback for + * the event. Called before acting on the channel. + * @param {("allow"|"replace")} [options.action] - Whether to allow or replace + * the channel. + * @returns {Promise} - Resolves once event has been handled. + */ + handleBeforeBlockChannel({ + filterOrigin = null, + onBeforeBlockChannel, + action, + }) { + if (action && action != "allow" && action != "replace") { + throw new Error("Invalid action " + action); + } + let channelClassifierService = Cc[ + "@mozilla.org/url-classifier/channel-classifier-service;1" + ].getService(Ci.nsIChannelClassifierService); + + let resolver; + let promise = new Promise(resolve => { + resolver = resolve; + }); + + let observer = { + observe(subject, topic) { + if (topic != "urlclassifier-before-block-channel") { + return; + } + let channel = subject.QueryInterface(Ci.nsIUrlClassifierBlockedChannel); + + if (filterOrigin) { + let { url } = channel; + let { origin } = new URL(url); + if (filterOrigin != origin) { + return; + } + } + + if (onBeforeBlockChannel) { + onBeforeBlockChannel(channel); + } + if (action) { + channel[action](); + } + + channelClassifierService.removeListener(observer); + resolver(); + }, + }; + channelClassifierService.addListener(observer); + return promise; + }, +}; diff --git a/toolkit/components/url-classifier/tests/browser/browser.toml b/toolkit/components/url-classifier/tests/browser/browser.toml new file mode 100644 index 0000000000..cd6be59720 --- /dev/null +++ b/toolkit/components/url-classifier/tests/browser/browser.toml @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = [ + "page.html", + "raptor.jpg", +] + +["browser_emailtracking_telemetry.js"] diff --git a/toolkit/components/url-classifier/tests/browser/browser_emailtracking_telemetry.js b/toolkit/components/url-classifier/tests/browser/browser_emailtracking_telemetry.js new file mode 100644 index 0000000000..086ca49afe --- /dev/null +++ b/toolkit/components/url-classifier/tests/browser/browser_emailtracking_telemetry.js @@ -0,0 +1,423 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +let { UrlClassifierTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); + +const TEST_DOMAIN = "https://example.com/"; +const TEST_EMAIL_WEBAPP_DOMAIN = "https://test1.example.com/"; +const EMAIL_TRACKER_DOMAIN = "https://email-tracking.example.org/"; +const TEST_PATH = "browser/toolkit/components/url-classifier/tests/browser/"; + +const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "page.html"; +const TEST_EMAIL_WEBAPP_PAGE = + TEST_EMAIL_WEBAPP_DOMAIN + TEST_PATH + "page.html"; + +const EMAIL_TRACKER_PAGE = EMAIL_TRACKER_DOMAIN + TEST_PATH + "page.html"; +const EMAIL_TRACKER_IMAGE = EMAIL_TRACKER_DOMAIN + TEST_PATH + "raptor.jpg"; + +const TELEMETRY_EMAIL_TRACKER_COUNT = "EMAIL_TRACKER_COUNT"; +const TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB = + "EMAIL_TRACKER_EMBEDDED_PER_TAB"; + +const LABEL_BASE_NORMAL = 0; +const LABEL_CONTENT_NORMAL = 1; +const LABEL_BASE_EMAIL_WEBAPP = 2; +const LABEL_CONTENT_EMAIL_WEBAPP = 3; + +const KEY_BASE_NORMAL = "base_normal"; +const KEY_CONTENT_NORMAL = "content_normal"; +const KEY_ALL_NORMAL = "all_normal"; +const KEY_BASE_EMAILAPP = "base_emailapp"; +const KEY_CONTENT_EMAILAPP = "content_emailapp"; +const KEY_ALL_EMAILAPP = "all_emailapp"; + +async function clearTelemetry() { + Services.telemetry.getSnapshotForHistograms("main", true /* clear */); + Services.telemetry.getHistogramById(TELEMETRY_EMAIL_TRACKER_COUNT).clear(); + Services.telemetry + .getKeyedHistogramById(TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB) + .clear(); +} + +async function loadImage(browser, url) { + return SpecialPowers.spawn(browser, [url], page => { + return new Promise(resolve => { + let image = new content.Image(); + image.src = page + "?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + }); +} + +async function getTelemetryProbe(key, label, checkCntFn) { + let histogram; + + // Wait until the telemetry probe appears. + await TestUtils.waitForCondition(() => { + let histograms = Services.telemetry.getSnapshotForHistograms( + "main", + false /* clear */ + ).parent; + + histogram = histograms[key]; + + let checkRes = false; + + if (histogram) { + checkRes = checkCntFn ? checkCntFn(histogram.values[label]) : true; + } + + return checkRes; + }); + + return histogram.values[label] || 0; +} + +async function getKeyedHistogram(histogram_id, key, bucket, checkCntFn) { + let histogram; + + // Wait until the telemetry probe appears. + await TestUtils.waitForCondition(() => { + let histograms = Services.telemetry.getSnapshotForKeyedHistograms( + "main", + false /* clear */ + ).parent; + + histogram = histograms[histogram_id]; + + let checkRes = false; + + if (histogram && histogram[key]) { + checkRes = checkCntFn ? checkCntFn(histogram[key].values[bucket]) : true; + } + + return checkRes; + }); + + return histogram[key].values[bucket] || 0; +} + +async function checkTelemetryProbe(key, label, expectedCnt) { + let cnt = await getTelemetryProbe(key, label, cnt => { + if (cnt === undefined) { + cnt = 0; + } + + return cnt == expectedCnt; + }); + + is(cnt, expectedCnt, "There should be expected count in telemetry."); +} + +async function checkKeyedHistogram(histogram_id, key, bucket, expectedCnt) { + let cnt = await getKeyedHistogram(histogram_id, key, bucket, cnt => { + if (cnt === undefined) { + cnt = 0; + } + + return cnt == expectedCnt; + }); + + is(cnt, expectedCnt, "There should be expected count in keyed telemetry."); +} + +function checkNoTelemetryProbe(key) { + let histograms = Services.telemetry.getSnapshotForHistograms( + "main", + false /* clear */ + ).parent; + + let histogram = histograms[key]; + + ok(!histogram, `No Telemetry has been recorded for ${key}`); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "urlclassifier.features.emailtracking.datacollection.blocklistTables", + "mochitest5-track-simple", + ], + [ + "urlclassifier.features.emailtracking.datacollection.allowlistTables", + "", + ], + [ + "urlclassifier.features.emailtracking.blocklistTables", + "mochitest5-track-simple", + ], + ["urlclassifier.features.emailtracking.allowlistTables", ""], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", false], + ["privacy.trackingprotection.cryptomining.enabled", false], + ["privacy.trackingprotection.emailtracking.enabled", true], + [ + "privacy.trackingprotection.emailtracking.data_collection.enabled", + true, + ], + ["privacy.trackingprotection.fingerprinting.enabled", false], + ["privacy.trackingprotection.socialtracking.enabled", false], + [ + "privacy.trackingprotection.emailtracking.webapp.domains", + "test1.example.com", + ], + ], + }); + + await UrlClassifierTestUtils.addTestTrackers(); + + registerCleanupFunction(function () { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); + + await clearTelemetry(); +}); + +add_task(async function test_email_tracking_telemetry() { + // Open a non email webapp tab. + await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => { + // Load a image from the email tracker + let res = await loadImage(browser, EMAIL_TRACKER_IMAGE); + + is(res, false, "The image is blocked."); + + // Verify the telemetry of the email tracker count. + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_BASE_NORMAL, + 1 + ); + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_CONTENT_NORMAL, + 0 + ); + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_BASE_EMAIL_WEBAPP, + 0 + ); + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_CONTENT_EMAIL_WEBAPP, + 0 + ); + }); + + // Open an email webapp tab. + await BrowserTestUtils.withNewTab(TEST_EMAIL_WEBAPP_PAGE, async browser => { + // Load a image from the email tracker + let res = await loadImage(browser, EMAIL_TRACKER_IMAGE); + + is(res, false, "The image is blocked."); + + // Verify the telemetry of the email tracker count. + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_BASE_NORMAL, + 1 + ); + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_CONTENT_NORMAL, + 0 + ); + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_BASE_EMAIL_WEBAPP, + 1 + ); + await checkTelemetryProbe( + TELEMETRY_EMAIL_TRACKER_COUNT, + LABEL_CONTENT_EMAIL_WEBAPP, + 0 + ); + }); + // Make sure the tab was closed properly before clearing Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + await clearTelemetry(); +}); + +add_task(async function test_no_telemetry_for_first_party_email_tracker() { + // Open a email tracker tab. + await BrowserTestUtils.withNewTab(EMAIL_TRACKER_PAGE, async browser => { + // Load a image from the first-party email tracker + let res = await loadImage(browser, EMAIL_TRACKER_IMAGE); + + is(res, true, "The image is loaded."); + + // Verify that there was no telemetry recorded. + checkNoTelemetryProbe(TELEMETRY_EMAIL_TRACKER_COUNT); + }); + // Make sure the tab was closed properly before clearing Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + await clearTelemetry(); +}); + +add_task(async function test_disable_email_data_collection() { + // Disable Email Tracking Data Collection. + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "privacy.trackingprotection.emailtracking.data_collection.enabled", + false, + ], + ], + }); + + // Open an email webapp tab. + await BrowserTestUtils.withNewTab(TEST_EMAIL_WEBAPP_PAGE, async browser => { + // Load a image from the email tracker + let res = await loadImage(browser, EMAIL_TRACKER_IMAGE); + + is(res, false, "The image is blocked."); + + // Verify that there was no telemetry recorded. + checkNoTelemetryProbe(TELEMETRY_EMAIL_TRACKER_COUNT); + }); + // Make sure the tab was closed properly before clearing Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + await SpecialPowers.popPrefEnv(); + await clearTelemetry(); +}); + +add_task(async function test_email_tracker_embedded_telemetry() { + // First, we open a page without loading any email trackers. + await BrowserTestUtils.withNewTab(TEST_PAGE, async _ => {}); + // Make sure the tab was closed properly before checking Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + // Check that the telemetry has been record properly for normal page. The + // telemetry should show there was no email tracker loaded. + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_BASE_NORMAL, + 0, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_CONTENT_NORMAL, + 0, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_ALL_NORMAL, + 0, + 1 + ); + + // Second, Open a email webapp tab that doesn't a load email tracker. + await BrowserTestUtils.withNewTab(TEST_EMAIL_WEBAPP_PAGE, async _ => {}); + // Make sure the tab was closed properly before checking Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + // Check that the telemetry has been record properly for the email webapp. The + // telemetry should show there was no email tracker loaded. + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_BASE_EMAILAPP, + 0, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_CONTENT_EMAILAPP, + 0, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_ALL_EMAILAPP, + 0, + 1 + ); + + // Third, open a page with one email tracker loaded. + await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => { + // Load a image from the email tracker + let res = await loadImage(browser, EMAIL_TRACKER_IMAGE); + + is(res, false, "The image is blocked."); + }); + // Make sure the tab was closed properly before checking Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + // Verify that the telemetry has been record properly, The telemetry should + // show there was one base email tracker loaded. + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_BASE_NORMAL, + 1, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_CONTENT_NORMAL, + 0, + 2 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_ALL_NORMAL, + 0, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_ALL_NORMAL, + 1, + 1 + ); + + // Open a page and load the same email tracker multiple times. There + // should be only one count for the same tracker. + await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => { + // Load a image from the email tracker two times. + await loadImage(browser, EMAIL_TRACKER_IMAGE); + await loadImage(browser, EMAIL_TRACKER_IMAGE); + }); + // Make sure the tab was closed properly before checking Telemetry. + await BrowserUtils.promiseObserved("window-global-destroyed"); + + // Verify that there is still only one count when loading the same tracker + // multiple times. + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_BASE_NORMAL, + 1, + 2 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_CONTENT_NORMAL, + 0, + 3 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_ALL_NORMAL, + 0, + 1 + ); + await checkKeyedHistogram( + TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB, + KEY_ALL_NORMAL, + 1, + 2 + ); + + await clearTelemetry(); +}); diff --git a/toolkit/components/url-classifier/tests/browser/page.html b/toolkit/components/url-classifier/tests/browser/page.html new file mode 100644 index 0000000000..a99e8be179 --- /dev/null +++ b/toolkit/components/url-classifier/tests/browser/page.html @@ -0,0 +1,8 @@ +<html> +<head> + <title>Just a top-level page</title> +</head> +<body> + <h1>This is the top-level page</h1> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/browser/raptor.jpg b/toolkit/components/url-classifier/tests/browser/raptor.jpg Binary files differnew file mode 100644 index 0000000000..243ba9e2d4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/browser/raptor.jpg diff --git a/toolkit/components/url-classifier/tests/gtest/Common.cpp b/toolkit/components/url-classifier/tests/gtest/Common.cpp new file mode 100644 index 0000000000..172ded051f --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/Common.cpp @@ -0,0 +1,219 @@ +/* -*- 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 "Common.h" + +#include "Classifier.h" +#include "HashStore.h" +#include "mozilla/Components.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIThread.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsUrlClassifierUtils.h" + +using namespace mozilla; +using namespace mozilla::safebrowsing; + +nsresult SyncApplyUpdates(TableUpdateArray& aUpdates) { + // We need to spin a new thread specifically because the callback + // will be on the caller thread. If we call Classifier::AsyncApplyUpdates + // and wait on the same thread, this function will never return. + + nsresult ret = NS_ERROR_FAILURE; + bool done = false; + auto onUpdateComplete = [&done, &ret](nsresult rv) { + // We are on the "ApplyUpdate" thread. Post an event to main thread + // so that we can avoid busy waiting on the main thread. + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("SyncApplyUpdates", [&done, &ret, rv] { + ret = rv; + done = true; + }); + NS_DispatchToMainThread(r); + }; + + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("SyncApplyUpdates", [&]() { + RefPtr<Classifier> classifier = new Classifier(); + classifier->Open(*file); + + nsresult rv = classifier->AsyncApplyUpdates(aUpdates, onUpdateComplete); + if (NS_FAILED(rv)) { + onUpdateComplete(rv); + } + }); + + nsCOMPtr<nsIThread> testingThread; + NS_NewNamedThread("ApplyUpdates", getter_AddRefs(testingThread)); + if (!testingThread) { + return NS_ERROR_FAILURE; + } + + testingThread->Dispatch(r, NS_DISPATCH_NORMAL); + + // NS_NewCheckSummedOutputStream in HashStore::WriteFile + // will synchronously init NS_CRYPTO_HASH_CONTRACTID on + // the main thread. As a result we have to keep processing + // pending event until |done| becomes true. If there's no + // more pending event, what we only can do is wait. + MOZ_ALWAYS_TRUE(SpinEventLoopUntil("url-classifier:SyncApplyUpdates"_ns, + [&]() { return done; })); + + return ret; +} + +already_AddRefed<nsIFile> GetFile(const nsTArray<nsString>& path) { + nsCOMPtr<nsIFile> file; + nsresult rv = + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + for (uint32_t i = 0; i < path.Length(); i++) { + file->Append(path[i]); + } + return file.forget(); +} + +void ApplyUpdate(TableUpdateArray& updates) { + // Force nsUrlClassifierUtils loading on main thread + // because nsIUrlClassifierDBService will not run in advance + // in gtest. + nsUrlClassifierUtils::GetInstance(); + + SyncApplyUpdates(updates); +} + +void ApplyUpdate(TableUpdate* update) { + TableUpdateArray updates = {update}; + ApplyUpdate(updates); +} + +nsresult PrefixArrayToPrefixStringMap(const _PrefixArray& aPrefixArray, + PrefixStringMap& aOut) { + aOut.Clear(); + + // Buckets are keyed by prefix length and contain an array of + // all prefixes of that length. + nsClassHashtable<nsUint32HashKey, _PrefixArray> table; + for (const auto& prefix : aPrefixArray) { + _PrefixArray* array = table.GetOrInsertNew(prefix.Length()); + array->AppendElement(prefix); + } + + // The resulting map entries will be a concatenation of all + // prefix data for the prefixes of a given size. + for (const auto& entry : table) { + uint32_t size = entry.GetKey(); + uint32_t count = entry.GetData()->Length(); + + auto str = MakeUnique<_Prefix>(); + str->SetLength(size * count); + + char* dst = str->BeginWriting(); + + entry.GetData()->Sort(); + for (uint32_t i = 0; i < count; i++) { + memcpy(dst, entry.GetData()->ElementAt(i).get(), size); + dst += size; + } + + aOut.InsertOrUpdate(size, std::move(str)); + } + + return NS_OK; +} + +nsresult PrefixArrayToAddPrefixArray(const _PrefixArray& aPrefixArray, + AddPrefixArray& aOut) { + aOut.Clear(); + + for (const auto& prefix : aPrefixArray) { + // Create prefix hash from string + AddPrefix* add = aOut.AppendElement(fallible); + if (!add) { + return NS_ERROR_OUT_OF_MEMORY; + } + + add->addChunk = 1; + add->prefix.Assign(prefix); + } + + EntrySort(aOut); + + return NS_OK; +} + +_Prefix CreatePrefixFromURL(const char* aURL, uint8_t aPrefixSize) { + return CreatePrefixFromURL(nsCString(aURL), aPrefixSize); +} + +_Prefix CreatePrefixFromURL(const nsCString& aURL, uint8_t aPrefixSize) { + Completion complete; + complete.FromPlaintext(aURL); + + _Prefix prefix; + prefix.Assign((const char*)complete.buf, aPrefixSize); + return prefix; +} + +void CheckContent(LookupCacheV4* aCache, const _PrefixArray& aPrefixArray) { + PrefixStringMap vlPSetMap; + aCache->GetPrefixes(vlPSetMap); + + PrefixStringMap expected; + PrefixArrayToPrefixStringMap(aPrefixArray, expected); + + for (const auto& entry : vlPSetMap) { + nsCString* expectedPrefix = expected.Get(entry.GetKey()); + nsCString* resultPrefix = entry.GetWeak(); + + ASSERT_TRUE(resultPrefix->Equals(*expectedPrefix)); + } +} + +nsresult BuildLookupCache(const RefPtr<Classifier>& classifier, + const nsACString& aTable, + _PrefixArray& aPrefixArray) { + RefPtr<LookupCache> cache = classifier->GetLookupCache(aTable, false); + if (!cache) { + return NS_ERROR_FAILURE; + } + + if (LookupCache::Cast<LookupCacheV4>(cache)) { + // V4 + RefPtr<LookupCacheV4> cacheV4 = LookupCache::Cast<LookupCacheV4>(cache); + + PrefixStringMap map; + PrefixArrayToPrefixStringMap(aPrefixArray, map); + return cacheV4->Build(map); + } else { + // V2 + RefPtr<LookupCacheV2> cacheV2 = LookupCache::Cast<LookupCacheV2>(cache); + + AddPrefixArray addPrefixes; + AddCompleteArray addComples; + + PrefixArrayToAddPrefixArray(aPrefixArray, addPrefixes); + return cacheV2->Build(addPrefixes, addComples); + } +} + +RefPtr<Classifier> GetClassifier() { + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + + RefPtr<Classifier> classifier = new Classifier(); + nsresult rv = classifier->Open(*file); + EXPECT_TRUE(rv == NS_OK); + + return classifier; +} diff --git a/toolkit/components/url-classifier/tests/gtest/Common.h b/toolkit/components/url-classifier/tests/gtest/Common.h new file mode 100644 index 0000000000..9c196abafa --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/Common.h @@ -0,0 +1,147 @@ +/* -*- 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/. */ + +#ifndef nsUrlClassifierGTestCommon_h__ +#define nsUrlClassifierGTestCommon_h__ + +#include "Entries.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsTArray.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "HashStore.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "LookupCacheV4.h" + +using namespace mozilla::safebrowsing; + +namespace mozilla { +namespace safebrowsing { +class Classifier; +class LookupCacheV4; +class TableUpdate; +} // namespace safebrowsing +} // namespace mozilla + +#define GTEST_SAFEBROWSING_DIR "safebrowsing"_ns +#define GTEST_TABLE_V4 "gtest-malware-proto"_ns +#define GTEST_TABLE_V2 "gtest-malware-simple"_ns + +template <typename Function> +void RunTestInNewThread(Function&& aFunction) { + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "RunTestInNewThread", std::forward<Function>(aFunction)); + nsCOMPtr<nsIThread> testingThread; + nsresult rv = + NS_NewNamedThread("Testing Thread", getter_AddRefs(testingThread), r); + ASSERT_EQ(rv, NS_OK); + testingThread->Shutdown(); +} + +// Synchronously apply updates by calling Classifier::AsyncApplyUpdates. +nsresult SyncApplyUpdates(Classifier* aClassifier, + nsTArray<TableUpdate*>* aUpdates); +nsresult SyncApplyUpdates(TableUpdateArray& aUpdates); + +// Return nsIFile with root directory - NS_APP_USER_PROFILE_50_DIR +// Sub-directories are passed in path argument. +already_AddRefed<nsIFile> GetFile(const nsTArray<nsString>& aPath); + +// ApplyUpdate will call |ApplyUpdates| of Classifier within a new thread +void ApplyUpdate(nsTArray<TableUpdate*>& aUpdates); + +void ApplyUpdate(TableUpdate* aUpdate); + +/** + * Prefix processing utility functions + */ + +typedef nsCString _Prefix; +typedef nsTArray<nsCString> _PrefixArray; + +// This function converts a lexigraphic-sorted prefixes array +// to a hash table keyed by prefix size(PrefixStringMap is defined in Entries.h) +nsresult PrefixArrayToPrefixStringMap(const _PrefixArray& aPrefixArray, + PrefixStringMap& aOut); + +// This function converts a lexigraphic-sorted prefixes array +// to an array containing AddPrefix(AddPrefix is defined in Entries.h) +nsresult PrefixArrayToAddPrefixArray(const _PrefixArray& aPrefixArray, + AddPrefixArray& aOut); + +_Prefix CreatePrefixFromURL(const char* aURL, uint8_t aPrefixSize); + +_Prefix CreatePrefixFromURL(const nsCString& aURL, uint8_t aPrefixSize); + +// To test if the content is equal +void CheckContent(LookupCacheV4* cache, const _PrefixArray& aPrefixArray); + +/** + * Utility function to generate safebrowsing internal structure + */ + +static inline nsresult BuildCache(LookupCacheV2* cache, + const _PrefixArray& aPrefixArray) { + AddPrefixArray prefixes; + AddCompleteArray completions; + nsresult rv = PrefixArrayToAddPrefixArray(aPrefixArray, prefixes); + if (NS_FAILED(rv)) { + return rv; + } + + return cache->Build(prefixes, completions); +} + +static inline nsresult BuildCache(LookupCacheV4* cache, + const _PrefixArray& aPrefixArray) { + PrefixStringMap map; + PrefixArrayToPrefixStringMap(aPrefixArray, map); + return cache->Build(map); +} + +// Create a LookupCacheV4 object with sepecified prefix array. +template <typename T> +RefPtr<T> SetupLookupCache(const _PrefixArray& aPrefixArray, + nsCOMPtr<nsIFile>& aFile) { + RefPtr<T> cache = new T(GTEST_TABLE_V4, ""_ns, aFile); + + nsresult rv = cache->Init(); + EXPECT_EQ(rv, NS_OK); + + rv = BuildCache(cache, aPrefixArray); + EXPECT_EQ(rv, NS_OK); + + return cache; +} + +template <typename T> +RefPtr<T> SetupLookupCache(const _PrefixArray& aPrefixArray) { + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + + file->AppendNative(GTEST_SAFEBROWSING_DIR); + + RefPtr<T> cache = new T(GTEST_TABLE_V4, ""_ns, file); + nsresult rv = cache->Init(); + EXPECT_EQ(rv, NS_OK); + + rv = BuildCache(cache, aPrefixArray); + EXPECT_EQ(rv, NS_OK); + + return cache; +} + +/** + * Retrieve Classifer class + */ +RefPtr<Classifier> GetClassifier(); + +nsresult BuildLookupCache(const RefPtr<Classifier>& aClassifier, + const nsACString& aTable, _PrefixArray& aPrefixArray); + +#endif // nsUrlClassifierGTestCommon_h__ diff --git a/toolkit/components/url-classifier/tests/gtest/TestCaching.cpp b/toolkit/components/url-classifier/tests/gtest/TestCaching.cpp new file mode 100644 index 0000000000..520eb35dcb --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestCaching.cpp @@ -0,0 +1,271 @@ +/* -*- 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 "Common.h" +#include "LookupCacheV4.h" + +#define EXPIRED_TIME_SEC (PR_Now() / PR_USEC_PER_SEC - 3600) +#define NOTEXPIRED_TIME_SEC (PR_Now() / PR_USEC_PER_SEC + 3600) + +#define CACHED_URL "cache.com/"_ns +#define NEG_CACHE_EXPIRED_URL "cache.negExpired.com/"_ns +#define POS_CACHE_EXPIRED_URL "cache.posExpired.com/"_ns +#define BOTH_CACHE_EXPIRED_URL "cache.negAndposExpired.com/"_ns + +static void SetupCacheEntry(LookupCacheV2* aLookupCache, + const nsCString& aCompletion, + bool aNegExpired = false, + bool aPosExpired = false) { + AddCompleteArray completes; + AddCompleteArray emptyCompletes; + MissPrefixArray misses; + MissPrefixArray emptyMisses; + + AddComplete* add = completes.AppendElement(mozilla::fallible); + add->complete.FromPlaintext(aCompletion); + + Prefix* prefix = misses.AppendElement(mozilla::fallible); + prefix->FromPlaintext(aCompletion); + + // Setup positive cache first otherwise negative cache expiry will be + // overwritten. + int64_t posExpirySec = aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC; + aLookupCache->AddGethashResultToCache(completes, emptyMisses, posExpirySec); + + int64_t negExpirySec = aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC; + aLookupCache->AddGethashResultToCache(emptyCompletes, misses, negExpirySec); +} + +static void SetupCacheEntry(LookupCacheV4* aLookupCache, + const nsCString& aCompletion, + bool aNegExpired = false, + bool aPosExpired = false) { + FullHashResponseMap map; + + Prefix prefix; + prefix.FromPlaintext(aCompletion); + + CachedFullHashResponse* response = map.GetOrInsertNew(prefix.ToUint32()); + + response->negativeCacheExpirySec = + aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC; + response->fullHashes.InsertOrUpdate( + CreatePrefixFromURL(aCompletion, COMPLETE_SIZE), + aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC); + + aLookupCache->AddFullHashResponseToCache(map); +} + +template <typename T> +static void TestCache(const Completion aCompletion, bool aExpectedHas, + bool aExpectedConfirmed, bool aExpectedInCache, + T* aCache = nullptr) { + bool has, inCache, confirmed; + uint32_t matchLength; + + if (aCache) { + aCache->Has(aCompletion, &has, &matchLength, &confirmed); + inCache = aCache->IsInCache(aCompletion.ToUint32()); + } else { + _PrefixArray array = {CreatePrefixFromURL("cache.notexpired.com/", 10), + CreatePrefixFromURL("cache.expired.com/", 8), + CreatePrefixFromURL("gound.com/", 5), + CreatePrefixFromURL("small.com/", 4)}; + + RefPtr<T> cache = SetupLookupCache<T>(array); + + // Create an expired entry and a non-expired entry + SetupCacheEntry(cache, "cache.notexpired.com/"_ns); + SetupCacheEntry(cache, "cache.expired.com/"_ns, true, true); + + cache->Has(aCompletion, &has, &matchLength, &confirmed); + inCache = cache->IsInCache(aCompletion.ToUint32()); + } + + EXPECT_EQ(has, aExpectedHas); + EXPECT_EQ(confirmed, aExpectedConfirmed); + EXPECT_EQ(inCache, aExpectedInCache); +} + +template <typename T> +static void TestCache(const nsCString& aURL, bool aExpectedHas, + bool aExpectedConfirmed, bool aExpectedInCache, + T* aCache = nullptr) { + Completion lookupHash; + lookupHash.FromPlaintext(aURL); + + TestCache<T>(lookupHash, aExpectedHas, aExpectedConfirmed, aExpectedInCache, + aCache); +} + +// This testcase check the returned result of |Has| API if fullhash cannot match +// any prefix in the local database. +TEST(UrlClassifierCaching, NotFound) +{ + TestCache<LookupCacheV2>("nomatch.com/"_ns, false, false, false); + TestCache<LookupCacheV4>("nomatch.com/"_ns, false, false, false); +} + +// This testcase check the returned result of |Has| API if fullhash find a match +// in the local database but not in the cache. +TEST(UrlClassifierCaching, NotInCache) +{ + TestCache<LookupCacheV2>("gound.com/"_ns, true, false, false); + TestCache<LookupCacheV4>("gound.com/"_ns, true, false, false); +} + +// This testcase check the returned result of |Has| API if fullhash matches +// a cache entry in positive cache. +TEST(UrlClassifierCaching, InPositiveCacheNotExpired) +{ + TestCache<LookupCacheV2>("cache.notexpired.com/"_ns, true, true, true); + TestCache<LookupCacheV4>("cache.notexpired.com/"_ns, true, true, true); +} + +// This testcase check the returned result of |Has| API if fullhash matches +// a cache entry in positive cache but that it is expired. +TEST(UrlClassifierCaching, InPositiveCacheExpired) +{ + TestCache<LookupCacheV2>("cache.expired.com/"_ns, true, false, true); + TestCache<LookupCacheV4>("cache.expired.com/"_ns, true, false, true); +} + +// This testcase check the returned result of |Has| API if fullhash matches +// a cache entry in negative cache. +TEST(UrlClassifierCaching, InNegativeCacheNotExpired) +{ + // Create a fullhash whose prefix matches the prefix in negative cache + // but completion doesn't match any fullhash in positive cache. + + Completion prefix; + prefix.FromPlaintext("cache.notexpired.com/"_ns); + + Completion fullhash; + fullhash.FromPlaintext("firefox.com/"_ns); + + // Overwrite the 4-byte prefix of `fullhash` so that it conflicts with + // `prefix`. Since "cache.notexpired.com" is added to database in TestCache as + // a 10-byte prefix, we should copy more than 10 bytes to fullhash to ensure + // it can match the prefix in database. + memcpy(fullhash.buf, prefix.buf, 10); + + TestCache<LookupCacheV2>(fullhash, false, false, true); + TestCache<LookupCacheV4>(fullhash, false, false, true); +} + +// This testcase check the returned result of |Has| API if fullhash matches +// a cache entry in negative cache but that entry is expired. +TEST(UrlClassifierCaching, InNegativeCacheExpired) +{ + // Create a fullhash whose prefix is in the cache. + + Completion prefix; + prefix.FromPlaintext("cache.expired.com/"_ns); + + Completion fullhash; + fullhash.FromPlaintext("firefox.com/"_ns); + + memcpy(fullhash.buf, prefix.buf, 10); + + TestCache<LookupCacheV2>(fullhash, true, false, true); + TestCache<LookupCacheV4>(fullhash, true, false, true); +} + +// This testcase create 4 cache entries. +// 1. unexpired entry. +// 2. an entry whose negative cache time is expired but whose positive cache +// is not expired. +// 3. an entry whose positive cache time is expired +// 4. an entry whose negative cache time and positive cache time are expired +// After calling |InvalidateExpiredCacheEntry| API, entries with expired +// negative time should be removed from cache(2 & 4) +template <typename T> +void TestInvalidateExpiredCacheEntry() { + _PrefixArray array = {CreatePrefixFromURL(CACHED_URL, 10), + CreatePrefixFromURL(NEG_CACHE_EXPIRED_URL, 8), + CreatePrefixFromURL(POS_CACHE_EXPIRED_URL, 5), + CreatePrefixFromURL(BOTH_CACHE_EXPIRED_URL, 4)}; + RefPtr<T> cache = SetupLookupCache<T>(array); + + SetupCacheEntry(cache, CACHED_URL, false, false); + SetupCacheEntry(cache, NEG_CACHE_EXPIRED_URL, true, false); + SetupCacheEntry(cache, POS_CACHE_EXPIRED_URL, false, true); + SetupCacheEntry(cache, BOTH_CACHE_EXPIRED_URL, true, true); + + // Before invalidate + TestCache<T>(CACHED_URL, true, true, true, cache.get()); + TestCache<T>(NEG_CACHE_EXPIRED_URL, true, true, true, cache.get()); + TestCache<T>(POS_CACHE_EXPIRED_URL, true, false, true, cache.get()); + TestCache<T>(BOTH_CACHE_EXPIRED_URL, true, false, true, cache.get()); + + // Call InvalidateExpiredCacheEntry to remove cache entries whose negative + // cache time is expired + cache->InvalidateExpiredCacheEntries(); + + // After invalidate, NEG_CACHE_EXPIRED_URL & BOTH_CACHE_EXPIRED_URL should + // not be found in cache. + TestCache<T>(NEG_CACHE_EXPIRED_URL, true, false, false, cache.get()); + TestCache<T>(BOTH_CACHE_EXPIRED_URL, true, false, false, cache.get()); + + // Other entries should remain the same result. + TestCache<T>(CACHED_URL, true, true, true, cache.get()); + TestCache<T>(POS_CACHE_EXPIRED_URL, true, false, true, cache.get()); +} + +TEST(UrlClassifierCaching, InvalidateExpiredCacheEntryV2) +{ TestInvalidateExpiredCacheEntry<LookupCacheV2>(); } + +TEST(UrlClassifierCaching, InvalidateExpiredCacheEntryV4) +{ TestInvalidateExpiredCacheEntry<LookupCacheV4>(); } + +// This testcase check if an cache entry whose negative cache time is expired +// and it doesn't have any postive cache entries in it, it should be removed +// from cache after calling |Has|. +TEST(UrlClassifierCaching, NegativeCacheExpireV2) +{ + _PrefixArray array = {CreatePrefixFromURL(NEG_CACHE_EXPIRED_URL, 8)}; + RefPtr<LookupCacheV2> cache = SetupLookupCache<LookupCacheV2>(array); + + nsCOMPtr<nsICryptoHash> cryptoHash = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID); + + MissPrefixArray misses; + Prefix* prefix = misses.AppendElement(mozilla::fallible); + prefix->FromPlaintext(NEG_CACHE_EXPIRED_URL); + + AddCompleteArray dummy; + cache->AddGethashResultToCache(dummy, misses, EXPIRED_TIME_SEC); + + // Ensure it is in cache in the first place. + EXPECT_EQ(cache->IsInCache(prefix->ToUint32()), true); + + // It should be removed after calling Has API. + TestCache<LookupCacheV2>(NEG_CACHE_EXPIRED_URL, true, false, false, + cache.get()); +} + +TEST(UrlClassifierCaching, NegativeCacheExpireV4) +{ + _PrefixArray array = {CreatePrefixFromURL(NEG_CACHE_EXPIRED_URL, 8)}; + RefPtr<LookupCacheV4> cache = SetupLookupCache<LookupCacheV4>(array); + + FullHashResponseMap map; + Prefix prefix; + nsCOMPtr<nsICryptoHash> cryptoHash = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID); + prefix.FromPlaintext(NEG_CACHE_EXPIRED_URL); + CachedFullHashResponse* response = map.GetOrInsertNew(prefix.ToUint32()); + + response->negativeCacheExpirySec = EXPIRED_TIME_SEC; + + cache->AddFullHashResponseToCache(map); + + // Ensure it is in cache in the first place. + EXPECT_EQ(cache->IsInCache(prefix.ToUint32()), true); + + // It should be removed after calling Has API. + TestCache<LookupCacheV4>(NEG_CACHE_EXPIRED_URL, true, false, false, + cache.get()); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestChunkSet.cpp b/toolkit/components/url-classifier/tests/gtest/TestChunkSet.cpp new file mode 100644 index 0000000000..6835103b30 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestChunkSet.cpp @@ -0,0 +1,281 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> + +#include <set> + +#include "ChunkSet.h" +#include "mozilla/ArrayUtils.h" + +#include "Common.h" + +TEST(UrlClassifierChunkSet, Empty) +{ + mozilla::safebrowsing::ChunkSet chunkSet; + mozilla::safebrowsing::ChunkSet removeSet; + + removeSet.Set(0); + + ASSERT_FALSE(chunkSet.Has(0)); + ASSERT_FALSE(chunkSet.Has(1)); + ASSERT_TRUE(chunkSet.Remove(removeSet) == NS_OK); + ASSERT_TRUE(chunkSet.Length() == 0); + + chunkSet.Set(0); + + ASSERT_TRUE(chunkSet.Has(0)); + ASSERT_TRUE(chunkSet.Length() == 1); + ASSERT_TRUE(chunkSet.Remove(removeSet) == NS_OK); + ASSERT_FALSE(chunkSet.Has(0)); + ASSERT_TRUE(chunkSet.Length() == 0); +} + +TEST(UrlClassifierChunkSet, Main) +{ + static int testVals[] = {2, 1, 5, 6, 8, 7, 14, 10, 12, 13}; + + mozilla::safebrowsing::ChunkSet chunkSet; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + chunkSet.Set(testVals[i]); + } + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + ASSERT_TRUE(chunkSet.Has(testVals[i])); + } + + ASSERT_FALSE(chunkSet.Has(3)); + ASSERT_FALSE(chunkSet.Has(4)); + ASSERT_FALSE(chunkSet.Has(9)); + ASSERT_FALSE(chunkSet.Has(11)); + + ASSERT_TRUE(chunkSet.Length() == MOZ_ARRAY_LENGTH(testVals)); +} + +TEST(UrlClassifierChunkSet, Merge) +{ + static int testVals[] = {2, 1, 5, 6, 8, 7, 14, 10, 12, 13}; + static int mergeVals[] = {9, 3, 4, 20, 14, 16}; + + mozilla::safebrowsing::ChunkSet chunkSet; + mozilla::safebrowsing::ChunkSet mergeSet; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + chunkSet.Set(testVals[i]); + } + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + mergeSet.Set(mergeVals[i]); + } + + chunkSet.Merge(mergeSet); + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + ASSERT_TRUE(chunkSet.Has(testVals[i])); + } + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + ASSERT_TRUE(chunkSet.Has(mergeVals[i])); + } + + // -1 because 14 is duplicated in both sets + ASSERT_TRUE(chunkSet.Length() == + MOZ_ARRAY_LENGTH(testVals) + MOZ_ARRAY_LENGTH(mergeVals) - 1); + + ASSERT_FALSE(chunkSet.Has(11)); + ASSERT_FALSE(chunkSet.Has(15)); + ASSERT_FALSE(chunkSet.Has(17)); + ASSERT_FALSE(chunkSet.Has(18)); + ASSERT_FALSE(chunkSet.Has(19)); +} + +TEST(UrlClassifierChunkSet, Merge2) +{ + static int testVals[] = {2, 1, 5, 6, 8, 7, 14, 10, 12, 13}; + static int mergeVals[] = {9, 3, 4, 20, 14, 16}; + static int mergeVals2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + + mozilla::safebrowsing::ChunkSet chunkSet; + mozilla::safebrowsing::ChunkSet mergeSet; + mozilla::safebrowsing::ChunkSet mergeSet2; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + chunkSet.Set(testVals[i]); + } + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + mergeSet.Set(mergeVals[i]); + } + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals2); i++) { + mergeSet2.Set(mergeVals2[i]); + } + + chunkSet.Merge(mergeSet); + chunkSet.Merge(mergeSet2); + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + ASSERT_TRUE(chunkSet.Has(testVals[i])); + } + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + ASSERT_TRUE(chunkSet.Has(mergeVals[i])); + } + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals2); i++) { + ASSERT_TRUE(chunkSet.Has(mergeVals2[i])); + } + + ASSERT_FALSE(chunkSet.Has(15)); + ASSERT_FALSE(chunkSet.Has(17)); + ASSERT_FALSE(chunkSet.Has(18)); + ASSERT_FALSE(chunkSet.Has(19)); +} + +TEST(UrlClassifierChunkSet, Stress) +{ + mozilla::safebrowsing::ChunkSet chunkSet; + mozilla::safebrowsing::ChunkSet mergeSet; + std::set<int> refSet; + std::set<int> refMergeSet; + static const int TEST_ITERS = 7000; + static const int REMOVE_ITERS = 3000; + static const int TEST_RANGE = 10000; + + // Construction by Set + for (int i = 0; i < TEST_ITERS; i++) { + int chunk = rand() % TEST_RANGE; + chunkSet.Set(chunk); + refSet.insert(chunk); + } + + // Same elements as reference set + for (auto it = refSet.begin(); it != refSet.end(); ++it) { + ASSERT_TRUE(chunkSet.Has(*it)); + } + + // Hole punching via Remove + for (int i = 0; i < REMOVE_ITERS; i++) { + int chunk = rand() % TEST_RANGE; + mozilla::safebrowsing::ChunkSet helpChunk; + helpChunk.Set(chunk); + + chunkSet.Remove(helpChunk); + refSet.erase(chunk); + + ASSERT_FALSE(chunkSet.Has(chunk)); + } + + // Should have chunks present in reference set + // Should not have chunks absent in reference set + for (int it = 0; it < TEST_RANGE; ++it) { + auto found = refSet.find(it); + if (chunkSet.Has(it)) { + ASSERT_FALSE(found == refSet.end()); + } else { + ASSERT_TRUE(found == refSet.end()); + } + } + + // Construct set to merge with + for (int i = 0; i < TEST_ITERS; i++) { + int chunk = rand() % TEST_RANGE; + mergeSet.Set(chunk); + refMergeSet.insert(chunk); + } + + // Merge set constructed correctly + for (auto it = refMergeSet.begin(); it != refMergeSet.end(); ++it) { + ASSERT_TRUE(mergeSet.Has(*it)); + } + + mozilla::safebrowsing::ChunkSet origSet; + origSet = chunkSet.InfallibleClone(); + + chunkSet.Merge(mergeSet); + refSet.insert(refMergeSet.begin(), refMergeSet.end()); + + // Check for presence of elements from both source + // Should not have chunks absent in reference set + for (int it = 0; it < TEST_RANGE; ++it) { + auto found = refSet.find(it); + if (chunkSet.Has(it)) { + ASSERT_FALSE(found == refSet.end()); + } else { + ASSERT_TRUE(found == refSet.end()); + } + } + + // Unmerge + chunkSet.Remove(origSet); + for (int it = 0; it < TEST_RANGE; ++it) { + if (origSet.Has(it)) { + ASSERT_FALSE(chunkSet.Has(it)); + } else if (mergeSet.Has(it)) { + ASSERT_TRUE(chunkSet.Has(it)); + } + } +} + +TEST(UrlClassifierChunkSet, RemoveClear) +{ + static int testVals[] = {2, 1, 5, 6, 8, 7, 14, 10, 12, 13}; + static int mergeVals[] = {3, 4, 9, 16, 20}; + + mozilla::safebrowsing::ChunkSet chunkSet; + mozilla::safebrowsing::ChunkSet mergeSet; + mozilla::safebrowsing::ChunkSet removeSet; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + chunkSet.Set(testVals[i]); + removeSet.Set(testVals[i]); + } + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + mergeSet.Set(mergeVals[i]); + } + + ASSERT_TRUE(chunkSet.Merge(mergeSet) == NS_OK); + ASSERT_TRUE(chunkSet.Remove(removeSet) == NS_OK); + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + ASSERT_TRUE(chunkSet.Has(mergeVals[i])); + } + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + ASSERT_FALSE(chunkSet.Has(testVals[i])); + } + + chunkSet.Clear(); + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + ASSERT_FALSE(chunkSet.Has(mergeVals[i])); + } +} + +TEST(UrlClassifierChunkSet, Serialize) +{ + static int testVals[] = {2, 1, 5, 6, 8, 7, 14, 10, 12, 13}; + static int mergeVals[] = {3, 4, 9, 16, 20}; + + mozilla::safebrowsing::ChunkSet chunkSet; + mozilla::safebrowsing::ChunkSet mergeSet; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(testVals); i++) { + chunkSet.Set(testVals[i]); + } + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mergeVals); i++) { + mergeSet.Set(mergeVals[i]); + } + + chunkSet.Merge(mergeSet); + + nsAutoCString mergeResult; + chunkSet.Serialize(mergeResult); + + printf("mergeResult: %s\n", mergeResult.get()); + + nsAutoCString expected("1-10,12-14,16,20"_ns); + + ASSERT_TRUE(mergeResult.Equals(expected)); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestClassifier.cpp b/toolkit/components/url-classifier/tests/gtest/TestClassifier.cpp new file mode 100644 index 0000000000..4000d6b32e --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestClassifier.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "Common.h" + +static void TestReadNoiseEntries(RefPtr<Classifier> classifier, + const nsCString& aTable, const nsCString& aURL, + const _PrefixArray& aPrefixArray) { + Completion lookupHash; + lookupHash.FromPlaintext(aURL); + RefPtr<LookupResult> result = new LookupResult; + result->hash.complete = lookupHash; + + PrefixArray noiseEntries; + uint32_t noiseCount = 3; + nsresult rv; + rv = classifier->ReadNoiseEntries(result->hash.fixedLengthPrefix, aTable, + noiseCount, noiseEntries); + ASSERT_TRUE(rv == NS_OK) + << "ReadNoiseEntries returns an error"; + EXPECT_TRUE(noiseEntries.Length() > 0) + << "Number of noise entries is less than 0"; + + for (uint32_t i = 0; i < noiseEntries.Length(); i++) { + // Test the noise entry should not equal the "real" hash request + EXPECT_NE(noiseEntries[i], result->hash.fixedLengthPrefix) + << "Noise entry is the same as real hash request"; + // Test the noise entry should exist in the cached prefix array + nsAutoCString partialHash; + partialHash.Assign(reinterpret_cast<char*>(&noiseEntries[i]), PREFIX_SIZE); + EXPECT_TRUE(aPrefixArray.Contains(partialHash)) + << "Noise entry is not in the cached prefix array"; + } +} + +TEST(UrlClassifierClassifier, ReadNoiseEntriesV4) +{ + RefPtr<Classifier> classifier = GetClassifier(); + _PrefixArray array = { + CreatePrefixFromURL("bravo.com/", 5), + CreatePrefixFromURL("browsing.com/", 9), + CreatePrefixFromURL("gound.com/", 4), + CreatePrefixFromURL("small.com/", 4), + CreatePrefixFromURL("gdfad.com/", 4), + CreatePrefixFromURL("afdfound.com/", 4), + CreatePrefixFromURL("dffa.com/", 4), + }; + + nsresult rv; + rv = BuildLookupCache(classifier, GTEST_TABLE_V4, array); + ASSERT_TRUE(rv == NS_OK) + << "Fail to build LookupCache"; + + TestReadNoiseEntries(classifier, GTEST_TABLE_V4, "gound.com/"_ns, array); +} + +TEST(UrlClassifierClassifier, ReadNoiseEntriesV2) +{ + RefPtr<Classifier> classifier = GetClassifier(); + _PrefixArray array = { + CreatePrefixFromURL("helloworld.com/", 4), + CreatePrefixFromURL("firefox.com/", 4), + CreatePrefixFromURL("chrome.com/", 4), + CreatePrefixFromURL("safebrowsing.com/", 4), + CreatePrefixFromURL("opera.com/", 4), + CreatePrefixFromURL("torbrowser.com/", 4), + CreatePrefixFromURL("gfaads.com/", 4), + CreatePrefixFromURL("qggdsas.com/", 4), + CreatePrefixFromURL("nqtewq.com/", 4), + }; + + nsresult rv; + rv = BuildLookupCache(classifier, GTEST_TABLE_V2, array); + ASSERT_TRUE(rv == NS_OK) + << "Fail to build LookupCache"; + + TestReadNoiseEntries(classifier, GTEST_TABLE_V2, "helloworld.com/"_ns, array); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestFailUpdate.cpp b/toolkit/components/url-classifier/tests/gtest/TestFailUpdate.cpp new file mode 100644 index 0000000000..f94852a821 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestFailUpdate.cpp @@ -0,0 +1,109 @@ +/* -*- 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 "HashStore.h" +#include "mozilla/Unused.h" +#include "nsPrintfCString.h" +#include "string.h" + +#include "Common.h" + +static const char* kFilesInV2[] = {".vlpset", ".sbstore"}; +static const char* kFilesInV4[] = {".vlpset", ".metadata"}; + +#define GTEST_MALWARE_TABLE_V4 "goog-malware-proto"_ns +#define GTEST_PHISH_TABLE_V4 "goog-phish-proto"_ns + +#define ROOT_DIR u"safebrowsing"_ns +#define SB_FILE(x, y) NS_ConvertUTF8toUTF16(nsPrintfCString("%s%s", x, y)) + +template <typename T, size_t N> +static void CheckFileExist(const nsCString& aTable, const T (&aFiles)[N], + bool aExpectExists, const char* aMsg = nullptr) { + for (uint32_t i = 0; i < N; i++) { + // This is just a quick way to know if this is v4 table + NS_ConvertUTF8toUTF16 SUB_DIR(strstr(aTable.get(), "-proto") ? "google4" + : ""); + nsCOMPtr<nsIFile> file = GetFile(nsTArray<nsString>{ + ROOT_DIR, SUB_DIR, SB_FILE(aTable.get(), aFiles[i])}); + + bool exists; + file->Exists(&exists); + + if (aMsg) { + ASSERT_EQ(aExpectExists, exists) + << file->HumanReadablePath().get() << " " << aMsg; + } else { + ASSERT_EQ(aExpectExists, exists) << file->HumanReadablePath().get(); + } + } +} + +TEST(UrlClassifierFailUpdate, CheckTableReset) +{ + const bool FULL_UPDATE = true; + const bool PARTIAL_UPDATE = false; + + // Apply V2 update + { + RefPtr<TableUpdateV2> update = new TableUpdateV2(GTEST_TABLE_V2); + mozilla::Unused << update->NewAddChunk(1); + + ApplyUpdate(update); + + // A successful V2 update should create .vlpset & .sbstore files + CheckFileExist(GTEST_TABLE_V2, kFilesInV2, true, + "V2 update doesn't create vlpset or sbstore"); + } + + // Helper function to generate table update data + auto func = [](RefPtr<TableUpdateV4> update, bool full, const char* str) { + update->SetFullUpdate(full); + nsCString prefix(str); + update->NewPrefixes(prefix.Length(), prefix); + }; + + // Apply V4 update for table1 + { + RefPtr<TableUpdateV4> update = new TableUpdateV4(GTEST_MALWARE_TABLE_V4); + func(update, FULL_UPDATE, "test_prefix"); + + ApplyUpdate(update); + + // A successful V4 update should create .vlpset & .metadata files + CheckFileExist(GTEST_MALWARE_TABLE_V4, kFilesInV4, true, + "v4 update doesn't create vlpset or metadata"); + } + + // Apply V4 update for table2 + { + RefPtr<TableUpdateV4> update = new TableUpdateV4(GTEST_PHISH_TABLE_V4); + func(update, FULL_UPDATE, "test_prefix"); + + ApplyUpdate(update); + + CheckFileExist(GTEST_PHISH_TABLE_V4, kFilesInV4, true, + "v4 update doesn't create vlpset or metadata"); + } + + // Apply V4 update with the same prefix in previous full udpate + // This should cause an update error. + { + RefPtr<TableUpdateV4> update = new TableUpdateV4(GTEST_MALWARE_TABLE_V4); + func(update, PARTIAL_UPDATE, "test_prefix"); + + ApplyUpdate(update); + + // A fail update should remove files for that table + CheckFileExist(GTEST_MALWARE_TABLE_V4, kFilesInV4, false, + "a fail v4 update doesn't remove the tables"); + + // A fail update should NOT remove files for the other tables + CheckFileExist(GTEST_TABLE_V2, kFilesInV2, true, + "a fail v4 update removes a v2 table"); + CheckFileExist(GTEST_PHISH_TABLE_V4, kFilesInV4, true, + "a fail v4 update removes the other v4 table"); + } +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestFindFullHash.cpp b/toolkit/components/url-classifier/tests/gtest/TestFindFullHash.cpp new file mode 100644 index 0000000000..f0f5785017 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestFindFullHash.cpp @@ -0,0 +1,216 @@ +/* -*- 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 "mozilla/Base64.h" +#include "nsUrlClassifierUtils.h" +#include "safebrowsing.pb.h" + +#include "Common.h" + +using namespace mozilla; + +template <size_t N> +static void ToBase64EncodedStringArray(nsCString (&aInput)[N], + nsTArray<nsCString>& aEncodedArray) { + for (size_t i = 0; i < N; i++) { + nsCString encoded; + nsresult rv = mozilla::Base64Encode(aInput[i], encoded); + NS_ENSURE_SUCCESS_VOID(rv); + aEncodedArray.AppendElement(std::move(encoded)); + } +} + +TEST(UrlClassifierFindFullHash, Request) +{ + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + + nsTArray<nsCString> listNames; + listNames.AppendElement("moztest-phish-proto"); + listNames.AppendElement("moztest-unwanted-proto"); + + nsCString listStates[] = {nsCString("sta\x00te1", 7), + nsCString("sta\x00te2", 7)}; + nsTArray<nsCString> listStateArray; + ToBase64EncodedStringArray(listStates, listStateArray); + + nsCString prefixes[] = {nsCString("\x00\x00\x00\x01", 4), + nsCString("\x00\x00\x00\x00\x01", 5), + nsCString("\x00\xFF\x00\x01", 4), + nsCString("\x00\xFF\x00\x01\x11\x23\xAA\xBC", 8), + nsCString("\x00\x00\x00\x01\x00\x01\x98", 7)}; + nsTArray<nsCString> prefixArray; + ToBase64EncodedStringArray(prefixes, prefixArray); + + nsCString requestBase64; + nsresult rv; + rv = urlUtil->MakeFindFullHashRequestV4(listNames, listStateArray, + prefixArray, requestBase64); + ASSERT_NS_SUCCEEDED(rv); + + // Base64 URL decode first. + FallibleTArray<uint8_t> requestBinary; + rv = Base64URLDecode(requestBase64, Base64URLDecodePaddingPolicy::Require, + requestBinary); + ASSERT_NS_SUCCEEDED(rv); + + // Parse the FindFullHash binary and compare with the expected values. + FindFullHashesRequest r; + ASSERT_TRUE(r.ParseFromArray(&requestBinary[0], requestBinary.Length())); + + // Compare client states. + ASSERT_EQ(r.client_states_size(), (int)ArrayLength(listStates)); + for (int i = 0; i < r.client_states_size(); i++) { + auto s = r.client_states(i); + ASSERT_TRUE(listStates[i].Equals(nsCString(s.c_str(), s.size()))); + } + + auto threatInfo = r.threat_info(); + + // Compare threat types. + ASSERT_EQ(threatInfo.threat_types_size(), (int)ArrayLength(listStates)); + for (int i = 0; i < threatInfo.threat_types_size(); i++) { + uint32_t expectedThreatType; + rv = + urlUtil->ConvertListNameToThreatType(listNames[i], &expectedThreatType); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(threatInfo.threat_types(i), (int)expectedThreatType); + } + + // Compare prefixes. + ASSERT_EQ(threatInfo.threat_entries_size(), (int)ArrayLength(prefixes)); + for (int i = 0; i < threatInfo.threat_entries_size(); i++) { + auto p = threatInfo.threat_entries(i).hash(); + ASSERT_TRUE(prefixes[i].Equals(nsCString(p.c_str(), p.size()))); + } +} + +///////////////////////////////////////////////////////////// +// Following is to test parsing the gethash response. + +namespace { + +// safebrowsing::Duration manipulation. +struct MyDuration { + uint32_t mSecs; + uint32_t mNanos; +}; +void PopulateDuration(Duration& aDest, const MyDuration& aSrc) { + aDest.set_seconds(aSrc.mSecs); + aDest.set_nanos(aSrc.mNanos); +} + +// The expected match data. +static MyDuration EXPECTED_MIN_WAIT_DURATION = {12, 10}; +static MyDuration EXPECTED_NEG_CACHE_DURATION = {120, 9}; +static const struct ExpectedMatch { + nsCString mCompleteHash; + ThreatType mThreatType; + MyDuration mPerHashCacheDuration; +} EXPECTED_MATCH[] = { + {nsCString("01234567890123456789012345678901"), + SOCIAL_ENGINEERING_PUBLIC, + {8, 500}}, + {nsCString("12345678901234567890123456789012"), + SOCIAL_ENGINEERING_PUBLIC, + {7, 100}}, + {nsCString("23456789012345678901234567890123"), + SOCIAL_ENGINEERING_PUBLIC, + {1, 20}}, +}; + +class MyParseCallback final : public nsIUrlClassifierParseFindFullHashCallback { + public: + NS_DECL_ISUPPORTS + + explicit MyParseCallback(uint32_t& aCallbackCount) + : mCallbackCount(aCallbackCount) {} + + NS_IMETHOD + OnCompleteHashFound(const nsACString& aCompleteHash, + const nsACString& aTableNames, + uint32_t aPerHashCacheDuration) override { + Verify(aCompleteHash, aTableNames, aPerHashCacheDuration); + + return NS_OK; + } + + NS_IMETHOD + OnResponseParsed(uint32_t aMinWaitDuration, + uint32_t aNegCacheDuration) override { + VerifyDuration(aMinWaitDuration / 1000, EXPECTED_MIN_WAIT_DURATION); + VerifyDuration(aNegCacheDuration, EXPECTED_NEG_CACHE_DURATION); + + return NS_OK; + } + + private: + void Verify(const nsACString& aCompleteHash, const nsACString& aTableNames, + uint32_t aPerHashCacheDuration) { + auto expected = EXPECTED_MATCH[mCallbackCount]; + + ASSERT_TRUE(aCompleteHash.Equals(expected.mCompleteHash)); + + // Verify aTableNames + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + + nsCString tableNames; + nsresult rv = + urlUtil->ConvertThreatTypeToListNames(expected.mThreatType, tableNames); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(aTableNames.Equals(tableNames)); + + VerifyDuration(aPerHashCacheDuration, expected.mPerHashCacheDuration); + + mCallbackCount++; + } + + void VerifyDuration(uint32_t aToVerify, const MyDuration& aExpected) { + ASSERT_TRUE(aToVerify == aExpected.mSecs); + } + + ~MyParseCallback() = default; + + uint32_t& mCallbackCount; +}; + +NS_IMPL_ISUPPORTS(MyParseCallback, nsIUrlClassifierParseFindFullHashCallback) + +} // end of unnamed namespace. + +TEST(UrlClassifierFindFullHash, ParseRequest) +{ + // Build response. + FindFullHashesResponse r; + + // Init response-wise durations. + auto minWaitDuration = r.mutable_minimum_wait_duration(); + PopulateDuration(*minWaitDuration, EXPECTED_MIN_WAIT_DURATION); + auto negCacheDuration = r.mutable_negative_cache_duration(); + PopulateDuration(*negCacheDuration, EXPECTED_NEG_CACHE_DURATION); + + // Init matches. + for (uint32_t i = 0; i < ArrayLength(EXPECTED_MATCH); i++) { + auto expected = EXPECTED_MATCH[i]; + auto match = r.mutable_matches()->Add(); + match->set_threat_type(expected.mThreatType); + match->mutable_threat()->set_hash(expected.mCompleteHash.BeginReading(), + expected.mCompleteHash.Length()); + auto perHashCacheDuration = match->mutable_cache_duration(); + PopulateDuration(*perHashCacheDuration, expected.mPerHashCacheDuration); + } + std::string s; + r.SerializeToString(&s); + + uint32_t callbackCount = 0; + nsCOMPtr<nsIUrlClassifierParseFindFullHashCallback> callback = + new MyParseCallback(callbackCount); + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + nsresult rv = urlUtil->ParseFindFullHashResponseV4( + nsCString(s.c_str(), s.size()), callback); + NS_ENSURE_SUCCESS_VOID(rv); + + ASSERT_EQ(callbackCount, ArrayLength(EXPECTED_MATCH)); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestLookupCacheV4.cpp b/toolkit/components/url-classifier/tests/gtest/TestLookupCacheV4.cpp new file mode 100644 index 0000000000..e1482c1416 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestLookupCacheV4.cpp @@ -0,0 +1,152 @@ +/* -*- 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 "nsAppDirectoryServiceDefs.h" + +#include "Common.h" + +#define GTEST_SAFEBROWSING_DIR "safebrowsing"_ns + +static void TestHasPrefix(const nsCString& aURL, bool aExpectedHas, + bool aExpectedComplete) { + _PrefixArray array = {CreatePrefixFromURL("bravo.com/", 32), + CreatePrefixFromURL("browsing.com/", 8), + CreatePrefixFromURL("gound.com/", 5), + CreatePrefixFromURL("small.com/", 4)}; + + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->AppendNative(GTEST_SAFEBROWSING_DIR); + + RunTestInNewThread([&]() -> void { + RefPtr<LookupCache> cache = SetupLookupCache<LookupCacheV4>(array, file); + + Completion lookupHash; + lookupHash.FromPlaintext(aURL); + + bool has, confirmed; + uint32_t matchLength; + // Freshness is not used in V4 so we just put dummy values here. + TableFreshnessMap dummy; + nsresult rv = cache->Has(lookupHash, &has, &matchLength, &confirmed); + + EXPECT_EQ(rv, NS_OK); + EXPECT_EQ(has, aExpectedHas); + EXPECT_EQ(matchLength == COMPLETE_SIZE, aExpectedComplete); + EXPECT_EQ(confirmed, false); + + cache->ClearAll(); + }); +} + +TEST(UrlClassifierLookupCacheV4, HasComplete) +{ TestHasPrefix("bravo.com/"_ns, true, true); } + +TEST(UrlClassifierLookupCacheV4, HasPrefix) +{ TestHasPrefix("browsing.com/"_ns, true, false); } + +TEST(UrlClassifierLookupCacheV4, Nomatch) +{ TestHasPrefix("nomatch.com/"_ns, false, false); } + +// Test an existing .pset should be removed after .vlpset is written +TEST(UrlClassifierLookupCacheV4, RemoveOldPset) +{ + nsCOMPtr<nsIFile> oldPsetFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(oldPsetFile)); + oldPsetFile->AppendNative("safebrowsing"_ns); + oldPsetFile->AppendNative(GTEST_TABLE_V4 + ".pset"_ns); + + nsCOMPtr<nsIFile> newPsetFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(newPsetFile)); + newPsetFile->AppendNative("safebrowsing"_ns); + newPsetFile->AppendNative(GTEST_TABLE_V4 + ".vlpset"_ns); + + // Create the legacy .pset file + nsresult rv = oldPsetFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + EXPECT_EQ(rv, NS_OK); + + bool exists; + rv = oldPsetFile->Exists(&exists); + EXPECT_EQ(rv, NS_OK); + EXPECT_EQ(exists, true); + + // Setup the data in lookup cache and write its data to disk + RefPtr<Classifier> classifier = GetClassifier(); + _PrefixArray array = {CreatePrefixFromURL("entry.com/", 4)}; + rv = BuildLookupCache(classifier, GTEST_TABLE_V4, array); + EXPECT_EQ(rv, NS_OK); + + RefPtr<LookupCache> cache = classifier->GetLookupCache(GTEST_TABLE_V4, false); + rv = cache->WriteFile(); + EXPECT_EQ(rv, NS_OK); + + // .vlpset should exist while .pset should be removed + rv = newPsetFile->Exists(&exists); + EXPECT_EQ(rv, NS_OK); + EXPECT_EQ(exists, true); + + rv = oldPsetFile->Exists(&exists); + EXPECT_EQ(rv, NS_OK); + EXPECT_EQ(exists, false); + + newPsetFile->Remove(false); +} + +// Test the legacy load +TEST(UrlClassifierLookupCacheV4, LoadOldPset) +{ + nsCOMPtr<nsIFile> oldPsetFile; + + _PrefixArray array = {CreatePrefixFromURL("entry.com/", 4)}; + PrefixStringMap map; + PrefixArrayToPrefixStringMap(array, map); + + // Prepare .pset file on disk + { + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(oldPsetFile)); + oldPsetFile->AppendNative("safebrowsing"_ns); + oldPsetFile->AppendNative(GTEST_TABLE_V4 + ".pset"_ns); + + RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet; + pset->SetPrefixes(map); + + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = + NS_NewLocalFileOutputStream(getter_AddRefs(stream), oldPsetFile); + EXPECT_EQ(rv, NS_OK); + + rv = pset->WritePrefixes(stream); + EXPECT_EQ(rv, NS_OK); + } + + // Load data from disk + RefPtr<Classifier> classifier = GetClassifier(); + RefPtr<LookupCache> cache = classifier->GetLookupCache(GTEST_TABLE_V4, false); + + RefPtr<LookupCacheV4> cacheV4 = LookupCache::Cast<LookupCacheV4>(cache); + CheckContent(cacheV4, array); + + oldPsetFile->Remove(false); +} + +TEST(UrlClassifierLookupCacheV4, BuildAPI) +{ + _PrefixArray init = {_Prefix("alph")}; + RefPtr<LookupCacheV4> cache = SetupLookupCache<LookupCacheV4>(init); + + _PrefixArray update = {_Prefix("beta")}; + PrefixStringMap map; + PrefixArrayToPrefixStringMap(update, map); + + cache->Build(map); + EXPECT_TRUE(map.IsEmpty()); + + CheckContent(cache, update); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestPerProviderDirectory.cpp b/toolkit/components/url-classifier/tests/gtest/TestPerProviderDirectory.cpp new file mode 100644 index 0000000000..a79ab643f4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestPerProviderDirectory.cpp @@ -0,0 +1,127 @@ +/* -*- 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 "HashStore.h" +#include "LookupCache.h" +#include "LookupCacheV4.h" +#include "nsAppDirectoryServiceDefs.h" + +#include "Common.h" + +namespace mozilla { +namespace safebrowsing { + +class PerProviderDirectoryTestUtils { + public: + template <typename T> + static nsIFile* InspectStoreDirectory(const T& aT) { + return aT.mStoreDirectory; + } +}; + +} // end of namespace safebrowsing +} // end of namespace mozilla + +template <typename T> +static void VerifyPrivateStorePath(T* target, const nsCString& aTableName, + const nsCString& aProvider, + const nsCOMPtr<nsIFile>& aRootDir, + bool aUsePerProviderStore) { + nsString rootStorePath; + nsresult rv = aRootDir->GetPath(rootStorePath); + EXPECT_EQ(rv, NS_OK); + + nsIFile* privateStoreDirectory = + PerProviderDirectoryTestUtils::InspectStoreDirectory(*target); + + nsString privateStorePath; + rv = privateStoreDirectory->GetPath(privateStorePath); + ASSERT_EQ(rv, NS_OK); + + nsString expectedPrivateStorePath = rootStorePath; + + if (aUsePerProviderStore) { + // Use API to append "provider" to the root directoy path + nsCOMPtr<nsIFile> expectedPrivateStoreDir; + rv = aRootDir->Clone(getter_AddRefs(expectedPrivateStoreDir)); + ASSERT_EQ(rv, NS_OK); + + expectedPrivateStoreDir->AppendNative(aProvider); + rv = expectedPrivateStoreDir->GetPath(expectedPrivateStorePath); + ASSERT_EQ(rv, NS_OK); + } + + printf("table: %s\nprovider: %s\nroot path: %s\nprivate path: %s\n\n", + aTableName.get(), aProvider.get(), + NS_ConvertUTF16toUTF8(rootStorePath).get(), + NS_ConvertUTF16toUTF8(privateStorePath).get()); + + ASSERT_TRUE(privateStorePath == expectedPrivateStorePath); +} + +TEST(UrlClassifierPerProviderDirectory, LookupCache) +{ + nsCOMPtr<nsIFile> rootDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(rootDir)); + + RunTestInNewThread([&]() -> void { + // For V2 tables (NOT ending with '-proto'), root directory should be + // used as the private store. + { + nsAutoCString table("goog-phish-shavar"); + nsAutoCString provider("google"); + RefPtr<LookupCacheV2> lc = new LookupCacheV2(table, provider, rootDir); + VerifyPrivateStorePath<LookupCacheV2>(lc, table, provider, rootDir, + false); + } + + // For V4 tables, if provider is found, use per-provider subdirectory; + // If not found, use root directory. + { + nsAutoCString table("goog-noprovider-proto"); + nsAutoCString provider(""); + RefPtr<LookupCacheV4> lc = new LookupCacheV4(table, provider, rootDir); + VerifyPrivateStorePath<LookupCacheV4>(lc, table, provider, rootDir, + false); + } + { + nsAutoCString table("goog-phish-proto"); + nsAutoCString provider("google4"); + RefPtr<LookupCacheV4> lc = new LookupCacheV4(table, provider, rootDir); + VerifyPrivateStorePath<LookupCacheV4>(lc, table, provider, rootDir, true); + } + }); +} + +TEST(UrlClassifierPerProviderDirectory, HashStore) +{ + nsCOMPtr<nsIFile> rootDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(rootDir)); + + RunTestInNewThread([&]() -> void { + // For V2 tables (NOT ending with '-proto'), root directory should be + // used as the private store. + { + nsAutoCString table("goog-phish-shavar"); + nsAutoCString provider("google"); + HashStore hs(table, provider, rootDir); + VerifyPrivateStorePath(&hs, table, provider, rootDir, false); + } + // For V4 tables, if provider is found, use per-provider subdirectory; + // If not found, use root directory. + { + nsAutoCString table("goog-noprovider-proto"); + nsAutoCString provider(""); + HashStore hs(table, provider, rootDir); + VerifyPrivateStorePath(&hs, table, provider, rootDir, false); + } + { + nsAutoCString table("goog-phish-proto"); + nsAutoCString provider("google4"); + HashStore hs(table, provider, rootDir); + VerifyPrivateStorePath(&hs, table, provider, rootDir, true); + } + }); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestPrefixSet.cpp b/toolkit/components/url-classifier/tests/gtest/TestPrefixSet.cpp new file mode 100644 index 0000000000..6d83cd02f6 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestPrefixSet.cpp @@ -0,0 +1,87 @@ +/* -*- 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 "mozilla/Preferences.h" +#include "nsString.h" +#include "nsUrlClassifierPrefixSet.h" + +#include "Common.h" + +// This function generate N 4 byte prefixes. +static void RandomPrefixes(uint32_t N, nsTArray<uint32_t>& array) { + array.Clear(); + array.SetCapacity(N); + + for (uint32_t i = 0; i < N; i++) { + bool added = false; + + while (!added) { + nsAutoCString prefix; + char* dst = prefix.BeginWriting(); + for (uint32_t j = 0; j < 4; j++) { + dst[j] = static_cast<char>(rand() % 256); + } + + const char* src = prefix.BeginReading(); + uint32_t data = 0; + memcpy(&data, src, sizeof(data)); + + if (!array.Contains(data)) { + array.AppendElement(data); + added = true; + } + } + } + + struct Comparator { + bool LessThan(const uint32_t& aA, const uint32_t& aB) const { + return aA < aB; + } + + bool Equals(const uint32_t& aA, const uint32_t& aB) const { + return aA == aB; + } + }; + + array.Sort(Comparator()); +} + +void RunTest(uint32_t aTestSize) { + RefPtr<nsUrlClassifierPrefixSet> prefixSet = new nsUrlClassifierPrefixSet(); + nsTArray<uint32_t> array; + + RandomPrefixes(aTestSize, array); + + nsresult rv = prefixSet->SetPrefixes(array.Elements(), array.Length()); + ASSERT_NS_SUCCEEDED(rv); + + for (uint32_t i = 0; i < array.Length(); i++) { + uint32_t value = 0; + rv = prefixSet->GetPrefixByIndex(i, &value); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(value == array[i]); + } +} + +TEST(URLClassifierPrefixSet, GetTargetPrefixWithLargeSet) +{ + // Make sure the delta algorithm will be used. + static const char prefKey[] = "browser.safebrowsing.prefixset_max_array_size"; + mozilla::Preferences::SetUint(prefKey, 10000); + + // Ideally, we should test more than 512 * 1024 entries. But, it will make the + // test too long. So, we test 100k entries instead. + RunTest(100000); +} + +TEST(URLClassifierPrefixSet, GetTargetPrefixWithSmallSet) +{ + // Make sure the delta algorithm won't be used. + static const char prefKey[] = "browser.safebrowsing.prefixset_max_array_size"; + mozilla::Preferences::SetUint(prefKey, 10000); + + RunTest(1000); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestProtocolParser.cpp b/toolkit/components/url-classifier/tests/gtest/TestProtocolParser.cpp new file mode 100644 index 0000000000..2155140b4d --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestProtocolParser.cpp @@ -0,0 +1,140 @@ +/* -*- 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 "mozilla/EndianUtils.h" +#include "ProtocolParser.h" + +#include "Common.h" + +typedef FetchThreatListUpdatesResponse_ListUpdateResponse ListUpdateResponse; + +static bool InitUpdateResponse(ListUpdateResponse* aUpdateResponse, + ThreatType aThreatType, const nsACString& aState, + const nsACString& aChecksum, bool isFullUpdate, + const nsTArray<uint32_t>& aFixedLengthPrefixes, + bool aDoPrefixEncoding) { + aUpdateResponse->set_threat_type(aThreatType); + aUpdateResponse->set_new_client_state(aState.BeginReading(), aState.Length()); + aUpdateResponse->mutable_checksum()->set_sha256(aChecksum.BeginReading(), + aChecksum.Length()); + aUpdateResponse->set_response_type(isFullUpdate + ? ListUpdateResponse::FULL_UPDATE + : ListUpdateResponse::PARTIAL_UPDATE); + + auto additions = aUpdateResponse->mutable_additions()->Add(); + + if (!aDoPrefixEncoding) { + additions->set_compression_type(RAW); + auto rawHashes = additions->mutable_raw_hashes(); + rawHashes->set_prefix_size(4); + auto prefixes = rawHashes->mutable_raw_hashes(); + for (auto p : aFixedLengthPrefixes) { + char buffer[4]; + mozilla::NativeEndian::copyAndSwapToBigEndian(buffer, &p, 1); + prefixes->append(buffer, 4); + } + return true; + } + + if (1 != aFixedLengthPrefixes.Length()) { + printf("This function only supports single value encoding.\n"); + return false; + } + + uint32_t firstValue = aFixedLengthPrefixes[0]; + additions->set_compression_type(RICE); + auto riceHashes = additions->mutable_rice_hashes(); + riceHashes->set_first_value(firstValue); + riceHashes->set_num_entries(0); + + return true; +} + +static void DumpBinary(const nsACString& aBinary) { + nsCString s; + for (size_t i = 0; i < aBinary.Length(); i++) { + s.AppendPrintf("\\x%.2X", (uint8_t)aBinary[i]); + } + printf("%s\n", s.get()); +} + +TEST(UrlClassifierProtocolParser, UpdateWait) +{ + // Top level response which contains a list of update response + // for different lists. + FetchThreatListUpdatesResponse response; + + auto r = response.mutable_list_update_responses()->Add(); + InitUpdateResponse(r, SOCIAL_ENGINEERING_PUBLIC, nsCString("sta\x00te", 6), + nsCString("check\x0sum", 9), true, {0, 1, 2, 3}, + false /* aDoPrefixEncoding */); + + // Set min wait duration. + auto minWaitDuration = response.mutable_minimum_wait_duration(); + minWaitDuration->set_seconds(8); + minWaitDuration->set_nanos(1 * 1000000000); + + std::string s; + response.SerializeToString(&s); + + DumpBinary(nsCString(s.c_str(), s.length())); + + ProtocolParser* p = new ProtocolParserProtobuf(); + p->AppendStream(nsCString(s.c_str(), s.length())); + p->End(); + ASSERT_EQ(p->UpdateWaitSec(), 9u); + delete p; +} + +TEST(UrlClassifierProtocolParser, SingleValueEncoding) +{ + // Top level response which contains a list of update response + // for different lists. + FetchThreatListUpdatesResponse response; + + auto r = response.mutable_list_update_responses()->Add(); + + const char* expectedPrefix = "\x00\x01\x02\x00"; + if (!InitUpdateResponse(r, SOCIAL_ENGINEERING_PUBLIC, + nsCString("sta\x00te", 6), + nsCString("check\x0sum", 9), true, + // As per spec, we should interpret the prefix as + // uint32 in little endian before encoding. + {mozilla::LittleEndian::readUint32(expectedPrefix)}, + true /* aDoPrefixEncoding */)) { + printf("Failed to initialize update response."); + ASSERT_TRUE(false); + return; + } + + // Set min wait duration. + auto minWaitDuration = response.mutable_minimum_wait_duration(); + minWaitDuration->set_seconds(8); + minWaitDuration->set_nanos(1 * 1000000000); + + std::string s; + response.SerializeToString(&s); + + // Feed data to the protocol parser. + ProtocolParser* p = new ProtocolParserProtobuf(); + p->SetRequestedTables({nsCString("googpub-phish-proto")}); + p->AppendStream(nsCString(s.c_str(), s.length())); + p->End(); + + const TableUpdateArray& tus = p->GetTableUpdates(); + RefPtr<const TableUpdateV4> tuv4 = TableUpdate::Cast<TableUpdateV4>(tus[0]); + auto& prefixMap = tuv4->Prefixes(); + for (const auto& entry : prefixMap) { + // This prefix map should contain only a single 4-byte prefixe. + ASSERT_EQ(entry.GetKey(), 4u); + + // The fixed-length prefix string from ProtocolParser should + // exactly match the expected prefix string. + nsCString* prefix = entry.GetWeak(); + ASSERT_TRUE(prefix->Equals(nsCString(expectedPrefix, 4))); + } + + delete p; +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestRiceDeltaDecoder.cpp b/toolkit/components/url-classifier/tests/gtest/TestRiceDeltaDecoder.cpp new file mode 100644 index 0000000000..9130cfe385 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestRiceDeltaDecoder.cpp @@ -0,0 +1,173 @@ +/* -*- 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 "mozilla/ArrayUtils.h" +#include "RiceDeltaDecoder.h" + +#include "Common.h" + +namespace { + +struct TestingData { + std::vector<uint32_t> mExpectedDecoded; + std::vector<uint8_t> mEncoded; + uint32_t mRiceParameter; +}; + +} // namespace + +static bool runOneTest(TestingData& aData) { + RiceDeltaDecoder decoder(&aData.mEncoded[0], aData.mEncoded.size()); + + std::vector<uint32_t> decoded(aData.mExpectedDecoded.size()); + + uint32_t firstValue = aData.mExpectedDecoded[0]; + bool rv = decoder.Decode( + aData.mRiceParameter, firstValue, + decoded.size() - 1, // # of entries (first value not included). + &decoded[0]); + + return rv && decoded == aData.mExpectedDecoded; +} + +TEST(UrlClassifierRiceDeltaDecoder, SingleEncodedValue) +{ + TestingData td = {{99}, {99}, 0}; + + ASSERT_TRUE(runOneTest(td)); +} + +// In this batch of tests, the encoded data would be like +// what we originally receive from the network. See comment +// in |runOneTest| for more detail. +TEST(UrlClassifierRiceDeltaDecoder, Empty) +{ + // The following structure and testing data is copied from Chromium source + // code: + // + // https://chromium.googlesource.com/chromium/src.git/+/950f9975599768b6a08c7146cb4befa161be87aa/components/safe_browsing_db/v4_rice_unittest.cc#75 + // + // and will be translated to our own testing format. + + struct RiceDecodingTestInfo { + uint32_t mRiceParameter; + std::vector<uint32_t> mDeltas; + std::string mEncoded; + + RiceDecodingTestInfo(uint32_t aRiceParameter, + const std::vector<uint32_t>& aDeltas, + const std::string& aEncoded) + : mRiceParameter(aRiceParameter), + mDeltas(aDeltas), + mEncoded(aEncoded) {} + }; + + // Copyright 2016 The Chromium Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be + // found in the media/webrtc/trunk/webrtc/LICENSE. + + // ----- Start of Chromium test code ---- + const std::vector<RiceDecodingTestInfo> TESTING_DATA_CHROMIUM = { + RiceDecodingTestInfo(2, {15, 9}, "\xf7\x2"), + RiceDecodingTestInfo( + 28, {1777762129, 2093280223, 924369848}, + "\xbf\xa8\x3f\xfb\xfc\xfb\x5e\x27\xe6\xc3\x1d\xc6\x38"), + RiceDecodingTestInfo( + 28, {62763050, 1046523781, 192522171, 1800511020, 4442775, 582142548}, + "\x54\x60\x7b\xe7\x0a\x5f\xc1\xdc\xee\x69\xde" + "\xfe\x58\x3c\xa3\xd6\xa5\xf2\x10\x8c\x4a\x59" + "\x56\x00"), + RiceDecodingTestInfo( + 28, + {26067715, 344823336, 8420095, 399843890, 95029378, 731622412, + 35811335, 1047558127, 1117722715, 78698892}, + "\x06\x86\x1b\x23\x14\xcb\x46\xf2\xaf\x07\x08\xc9\x88\x54\x1f\x41\x04" + "\xd5\x1a\x03\xeb\xe6\x3a\x80\x13\x91\x7b\xbf\x83\xf3\xb7\x85\xf1\x29" + "\x18\xb3\x61\x09"), + RiceDecodingTestInfo( + 27, + {225846818, 328287420, 166748623, 29117720, 552397365, 350353215, + 558267528, 4738273, 567093445, 28563065, 55077698, 73091685, + 339246010, 98242620, 38060941, 63917830, 206319759, 137700744}, + "\x89\x98\xd8\x75\xbc\x44\x91\xeb\x39\x0c\x3e\x30\x9a\x78\xf3\x6a\xd4" + "\xd9\xb1\x9f\xfb\x70\x3e\x44\x3e\xa3\x08\x67\x42\xc2\x2b\x46\x69\x8e" + "\x3c\xeb\xd9\x10\x5a\x43\x9a\x32\xa5\x2d\x4e\x77\x0f\x87\x78\x20\xb6" + "\xab\x71\x98\x48\x0c\x9e\x9e\xd7\x23\x0c\x13\x43\x2c\xa9\x01"), + RiceDecodingTestInfo( + 28, + {339784008, 263128563, 63871877, 69723256, 826001074, 797300228, + 671166008, 207712688}, + std::string("\x21\xc5\x02\x91\xf9\x82\xd7\x57\xb8\xe9\x3c\xf0\xc8\x4f" + "\xe8\x64\x8d\x77\x62\x04\xd6\x85\x3f\x1c\x97\x00\x04\x1b" + "\x17\xc6", + 30)), + RiceDecodingTestInfo( + 28, + {471820069, 196333855, 855579133, 122737976, 203433838, 85354544, + 1307949392, 165938578, 195134475, 553930435, 49231136}, + "\x95\x9c\x7d\xb0\x8f\xe8\xd9\xbd\xfe\x8c\x7f\x81\x53\x0d\x75\xdc\x4e" + "\x40\x18\x0c\x9a\x45\x3d\xa8\xdc\xfa\x26\x59\x40\x9e\x16\x08\x43\x77" + "\xc3\x4e\x04\x01\xa4\xe6\x5d\x00"), + RiceDecodingTestInfo( + 27, + {87336845, 129291033, 30906211, 433549264, 30899891, 53207875, + 11959529, 354827862, 82919275, 489637251, 53561020, 336722992, + 408117728, 204506246, 188216092, 9047110, 479817359, 230317256}, + "\x1a\x4f\x69\x2a\x63\x9a\xf6\xc6\x2e\xaf\x73\xd0\x6f\xd7\x31\xeb\x77" + "\x1d\x43\xe3\x2b\x93\xce\x67\x8b\x59\xf9\x98\xd4\xda\x4f\x3c\x6f\xb0" + "\xe8\xa5\x78\x8d\x62\x36\x18\xfe\x08\x1e\x78\xd8\x14\x32\x24\x84\x61" + "\x1c\xf3\x37\x63\xc4\xa0\x88\x7b\x74\xcb\x64\xc8\x5c\xba\x05"), + RiceDecodingTestInfo( + 28, + {297968956, 19709657, 259702329, 76998112, 1023176123, 29296013, + 1602741145, 393745181, 177326295, 55225536, 75194472}, + "\xf1\x94\x0a\x87\x6c\x5f\x96\x90\xe3\xab\xf7\xc0\xcb\x2d\xe9\x76\xdb" + "\xf8\x59\x63\xc1\x6f\x7c\x99\xe3\x87\x5f\xc7\x04\xde\xb9\x46\x8e\x54" + "\xc0\xac\x4a\x03\x0d\x6c\x8f\x00"), + RiceDecodingTestInfo( + 28, + {532220688, 780594691, 436816483, 163436269, 573044456, 1069604, + 39629436, 211410997, 227714491, 381562898, 75610008, 196754597, + 40310339, 15204118, 99010842}, + "\x41\x2c\xe4\xfe\x06\xdc\x0d\xbd\x31\xa5\x04\xd5\x6e\xdd\x9b\x43\xb7" + "\x3f\x11\x24\x52\x10\x80\x4f\x96\x4b\xd4\x80\x67\xb2\xdd\x52\xc9\x4e" + "\x02\xc6\xd7\x60\xde\x06\x92\x52\x1e\xdd\x35\x64\x71\x26\x2c\xfe\xcf" + "\x81\x46\xb2\x79\x01"), + RiceDecodingTestInfo( + 28, + {219354713, 389598618, 750263679, 554684211, 87381124, 4523497, + 287633354, 801308671, 424169435, 372520475, 277287849}, + "\xb2\x2c\x26\x3a\xcd\x66\x9c\xdb\x5f\x07\x2e\x6f\xe6\xf9\x21\x10\x52" + "\xd5\x94\xf4\x82\x22\x48\xf9\x9d\x24\xf6\xff\x2f\xfc\x6d\x3f\x21\x65" + "\x1b\x36\x34\x56\xea\xc4\x21\x00"), + }; + + // ----- End of Chromium test code ---- + + for (auto tdc : TESTING_DATA_CHROMIUM) { + // Populate chromium testing data to our native testing data struct. + TestingData d; + + d.mRiceParameter = tdc.mRiceParameter; // Populate rice parameter. + + // Populate encoded data from std::string to vector<uint8>. + d.mEncoded.resize(tdc.mEncoded.size()); + memcpy(&d.mEncoded[0], tdc.mEncoded.c_str(), tdc.mEncoded.size()); + + // Populate deltas to expected decoded data. The first value would be just + // set to an arbitrary value, say 7, to avoid any assumption to the + // first value in the implementation. + d.mExpectedDecoded.resize(tdc.mDeltas.size() + 1); + for (size_t i = 0; i < d.mExpectedDecoded.size(); i++) { + if (0 == i) { + d.mExpectedDecoded[i] = 7; // "7" is an arbitrary starting value + } else { + d.mExpectedDecoded[i] = d.mExpectedDecoded[i - 1] + tdc.mDeltas[i - 1]; + } + } + + ASSERT_TRUE(runOneTest(d)); + } +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestSafeBrowsingProtobuf.cpp b/toolkit/components/url-classifier/tests/gtest/TestSafeBrowsingProtobuf.cpp new file mode 100644 index 0000000000..aa15344324 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestSafeBrowsingProtobuf.cpp @@ -0,0 +1,30 @@ +/* -*- 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 "safebrowsing.pb.h" + +#include "Common.h" + +TEST(UrlClassifierProtobuf, Empty) +{ + using namespace mozilla::safebrowsing; + + const std::string CLIENT_ID = "firefox"; + + // Construct a simple update request. + FetchThreatListUpdatesRequest r; + r.set_allocated_client(new ClientInfo()); + r.mutable_client()->set_client_id(CLIENT_ID); + + // Then serialize. + std::string s; + r.SerializeToString(&s); + + // De-serialize. + FetchThreatListUpdatesRequest r2; + r2.ParseFromString(s); + + ASSERT_EQ(r2.client().client_id(), CLIENT_ID); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestSafebrowsingHash.cpp b/toolkit/components/url-classifier/tests/gtest/TestSafebrowsingHash.cpp new file mode 100644 index 0000000000..2fc4c793f7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestSafebrowsingHash.cpp @@ -0,0 +1,58 @@ +/* -*- 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 "mozilla/EndianUtils.h" + +#include "Common.h" + +TEST(UrlClassifierHash, ToFromUint32) +{ + using namespace mozilla::safebrowsing; + + // typedef SafebrowsingHash<PREFIX_SIZE, PrefixComparator> Prefix; + // typedef nsTArray<Prefix> PrefixArray; + + const char PREFIX_RAW[4] = {0x1, 0x2, 0x3, 0x4}; + uint32_t PREFIX_UINT32; + memcpy(&PREFIX_UINT32, PREFIX_RAW, 4); + + Prefix p; + p.Assign(nsCString(PREFIX_RAW, 4)); + ASSERT_EQ(p.ToUint32(), PREFIX_UINT32); + + p.FromUint32(PREFIX_UINT32); + ASSERT_EQ(memcmp(PREFIX_RAW, p.buf, 4), 0); +} + +TEST(UrlClassifierHash, Compare) +{ + using namespace mozilla; + using namespace mozilla::safebrowsing; + + Prefix p1, p2, p3; + + // The order of p1,p2,p3 is "p1 == p3 < p2" +#if MOZ_LITTLE_ENDIAN() + p1.Assign(nsCString("\x01\x00\x00\x00", 4)); + p2.Assign(nsCString("\x00\x00\x00\x01", 4)); + p3.Assign(nsCString("\x01\x00\x00\x00", 4)); +#else + p1.Assign(nsCString("\x00\x00\x00\x01", 4)); + p2.Assign(nsCString("\x01\x00\x00\x00", 4)); + p3.Assign(nsCString("\x00\x00\x00\x01", 4)); +#endif + + // Make sure "p1 == p3 < p2" is true + // on both little and big endian machine. + + ASSERT_EQ(p1.Compare(p2), -1); + ASSERT_EQ(p1.Compare(p1), 0); + ASSERT_EQ(p2.Compare(p1), 1); + ASSERT_EQ(p1.Compare(p3), 0); + + ASSERT_TRUE(p1 < p2); + ASSERT_TRUE(p1 == p1); + ASSERT_TRUE(p1 == p3); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestTable.cpp b/toolkit/components/url-classifier/tests/gtest/TestTable.cpp new file mode 100644 index 0000000000..796f40b265 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestTable.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "nsUrlClassifierDBService.h" + +#include "Common.h" + +static void TestResponseCode(const char* table, nsresult result) { + nsCString tableName(table); + ASSERT_EQ(TablesToResponse(tableName), result); +} + +TEST(UrlClassifierTable, ResponseCode) +{ + // malware URIs. + TestResponseCode("goog-malware-shavar", NS_ERROR_MALWARE_URI); + TestResponseCode("test-malware-simple", NS_ERROR_MALWARE_URI); + TestResponseCode("goog-phish-shavar,test-malware-simple", + NS_ERROR_MALWARE_URI); + TestResponseCode( + "test-malware-simple,mozstd-track-digest256,mozplugin-block-digest256", + NS_ERROR_MALWARE_URI); + + // phish URIs. + TestResponseCode("goog-phish-shavar", NS_ERROR_PHISHING_URI); + TestResponseCode("test-phish-simple", NS_ERROR_PHISHING_URI); + TestResponseCode("test-phish-simple,mozplugin-block-digest256", + NS_ERROR_PHISHING_URI); + TestResponseCode( + "mozstd-track-digest256,test-phish-simple,goog-unwanted-shavar", + NS_ERROR_PHISHING_URI); + + // unwanted URIs. + TestResponseCode("goog-unwanted-shavar", NS_ERROR_UNWANTED_URI); + TestResponseCode("test-unwanted-simple", NS_ERROR_UNWANTED_URI); + TestResponseCode("mozplugin-unwanted-digest256,mozfull-track-digest256", + NS_ERROR_UNWANTED_URI); + TestResponseCode( + "test-block-simple,mozfull-track-digest256,test-unwanted-simple", + NS_ERROR_UNWANTED_URI); + + // track URIs. + TestResponseCode("test-track-simple", NS_ERROR_TRACKING_URI); + TestResponseCode("mozstd-track-digest256", NS_ERROR_TRACKING_URI); + TestResponseCode("test-block-simple,mozstd-track-digest256", + NS_ERROR_TRACKING_URI); + + // block URIs + TestResponseCode("test-block-simple", NS_ERROR_BLOCKED_URI); + TestResponseCode("mozplugin-block-digest256", NS_ERROR_BLOCKED_URI); + TestResponseCode("mozplugin2-block-digest256", NS_ERROR_BLOCKED_URI); + + TestResponseCode("test-trackwhite-simple", NS_OK); + TestResponseCode("mozstd-trackwhite-digest256", NS_OK); + TestResponseCode("goog-badbinurl-shavar", NS_OK); + TestResponseCode("goog-downloadwhite-digest256", NS_OK); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestURLsAndHashing.cpp b/toolkit/components/url-classifier/tests/gtest/TestURLsAndHashing.cpp new file mode 100644 index 0000000000..0563c6a776 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestURLsAndHashing.cpp @@ -0,0 +1,71 @@ +/* -*- 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 "LookupCache.h" + +#include "Common.h" + +static void VerifyFragments(const nsACString& aURL, + const nsTArray<nsCString>& aExpected) { + nsTArray<nsCString> fragments; + nsresult rv = LookupCache::GetLookupFragments(aURL, &fragments); + ASSERT_EQ(rv, NS_OK) << "GetLookupFragments should not fail"; + + ASSERT_EQ(aExpected.Length(), fragments.Length()) + << "Fragments generated from " << aURL.BeginReading() + << " are not the same as expected"; + + for (const auto& fragment : fragments) { + ASSERT_TRUE(aExpected.Contains(fragment)) + << "Fragments generated from " << aURL.BeginReading() + << " are not the same as expected"; + } +} + +// This testcase references SafeBrowsing spec: +// https://developers.google.com/safe-browsing/v4/urls-hashing#suffixprefix-expressions +TEST(URLsAndHashing, FragmentURLWithQuery) +{ + const nsLiteralCString url("a.b.c/1/2.html?param=1"); + nsTArray<nsCString> expect = { + "a.b.c/1/2.html?param=1"_ns, + "a.b.c/1/2.html"_ns, + "a.b.c/"_ns, + "a.b.c/1/"_ns, + "b.c/1/2.html?param=1"_ns, + "b.c/1/2.html"_ns, + "b.c/"_ns, + "b.c/1/"_ns, + }; + + VerifyFragments(url, expect); +} + +// This testcase references SafeBrowsing spec: +// https://developers.google.com/safe-browsing/v4/urls-hashing#suffixprefix-expressions +TEST(URLsAndHashing, FragmentURLWithoutQuery) +{ + const nsLiteralCString url("a.b.c.d.e.f.g/1.html"); + nsTArray<nsCString> expect = { + "a.b.c.d.e.f.g/1.html"_ns, "a.b.c.d.e.f.g/"_ns, + "c.d.e.f.g/1.html"_ns, "c.d.e.f.g/"_ns, + "d.e.f.g/1.html"_ns, "d.e.f.g/"_ns, + "e.f.g/1.html"_ns, "e.f.g/"_ns, + "f.g/1.html"_ns, "f.g/"_ns, + }; + + VerifyFragments(url, expect); +} + +TEST(URLsAndHashing, FragmentURLEndWithoutPath) +{ + const nsLiteralCString url("1.2.3.4/?query=string"); + nsTArray<nsCString> expect = { + "1.2.3.4/?query=string"_ns, + "1.2.3.4/"_ns, + }; + + VerifyFragments(url, expect); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp new file mode 100644 index 0000000000..d4dec867bd --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp @@ -0,0 +1,785 @@ +/* -*- 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 "HashStore.h" +#include "mozilla/Components.h" +#include "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsThreadUtils.h" +#include "string.h" +#include "LookupCacheV4.h" +#include "nsUrlClassifierUtils.h" + +#include "Common.h" + +#define GTEST_SAFEBROWSING_DIR "safebrowsing"_ns +#define GTEST_TABLE "gtest-malware-proto"_ns +#define GTEST_PREFIXFILE "gtest-malware-proto.vlpset"_ns + +// This function removes common elements of inArray and outArray from +// outArray. This is used by partial update testcase to ensure partial update +// data won't contain prefixes we already have. +static void RemoveIntersection(const _PrefixArray& inArray, + _PrefixArray& outArray) { + for (uint32_t i = 0; i < inArray.Length(); i++) { + int32_t idx = outArray.BinaryIndexOf(inArray[i]); + if (idx >= 0) { + outArray.RemoveElementAt(idx); + } + } +} + +// This fucntion removes elements from outArray by index specified in +// removal array. +static void RemoveElements(const nsTArray<uint32_t>& removal, + _PrefixArray& outArray) { + for (int32_t i = removal.Length() - 1; i >= 0; i--) { + outArray.RemoveElementAt(removal[i]); + } +} + +static void MergeAndSortArray(const _PrefixArray& array1, + const _PrefixArray& array2, + _PrefixArray& output) { + output.Clear(); + output.AppendElements(array1); + output.AppendElements(array2); + output.Sort(); +} + +static void CalculateSHA256(_PrefixArray& prefixArray, nsCString& sha256) { + prefixArray.Sort(); + + nsresult rv; + nsCOMPtr<nsICryptoHash> cryptoHash = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + + cryptoHash->Init(nsICryptoHash::SHA256); + for (uint32_t i = 0; i < prefixArray.Length(); i++) { + const _Prefix& prefix = prefixArray[i]; + cryptoHash->Update( + reinterpret_cast<uint8_t*>(const_cast<char*>(prefix.get())), + prefix.Length()); + } + cryptoHash->Finish(false, sha256); +} + +// N: Number of prefixes, MIN/MAX: minimum/maximum prefix size +// This function will append generated prefixes to outArray. +static void CreateRandomSortedPrefixArray(uint32_t N, uint32_t MIN, + uint32_t MAX, + _PrefixArray& outArray) { + outArray.SetCapacity(outArray.Length() + N); + + const uint32_t range = (MAX - MIN + 1); + + for (uint32_t i = 0; i < N; i++) { + uint32_t prefixSize = (rand() % range) + MIN; + _Prefix prefix; + prefix.SetLength(prefixSize); + + while (true) { + char* dst = prefix.BeginWriting(); + for (uint32_t j = 0; j < prefixSize; j++) { + dst[j] = rand() % 256; + } + + if (!outArray.Contains(prefix)) { + outArray.AppendElement(prefix); + break; + } + } + } + + outArray.Sort(); +} + +// N: Number of removal indices, MAX: maximum index +static void CreateRandomRemovalIndices(uint32_t N, uint32_t MAX, + nsTArray<uint32_t>& outArray) { + for (uint32_t i = 0; i < N; i++) { + uint32_t idx = rand() % MAX; + if (!outArray.Contains(idx)) { + outArray.InsertElementSorted(idx); + } + } +} + +// Function to generate TableUpdateV4. +static void GenerateUpdateData(bool fullUpdate, PrefixStringMap& add, + nsTArray<uint32_t>* removal, nsCString* sha256, + TableUpdateArray& tableUpdates) { + RefPtr<TableUpdateV4> tableUpdate = new TableUpdateV4(GTEST_TABLE); + tableUpdate->SetFullUpdate(fullUpdate); + + for (const auto& entry : add) { + nsCString* pstring = entry.GetWeak(); + tableUpdate->NewPrefixes(entry.GetKey(), *pstring); + } + + if (removal) { + tableUpdate->NewRemovalIndices(removal->Elements(), removal->Length()); + } + + if (sha256) { + std::string stdSHA256; + stdSHA256.assign(const_cast<char*>(sha256->BeginReading()), + sha256->Length()); + + tableUpdate->SetSHA256(stdSHA256); + } + + tableUpdates.AppendElement(tableUpdate); +} + +static void VerifyPrefixSet(PrefixStringMap& expected) { + // Verify the prefix set is written to disk. + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->AppendNative(GTEST_SAFEBROWSING_DIR); + + RefPtr<LookupCacheV4> lookup = + new LookupCacheV4(GTEST_TABLE, "test"_ns, file); + lookup->Init(); + + file->AppendNative(GTEST_PREFIXFILE); + lookup->LoadFromFile(file); + + PrefixStringMap prefixesInFile; + lookup->GetPrefixes(prefixesInFile); + + for (const auto& entry : expected) { + nsCString* expectedPrefix = entry.GetWeak(); + nsCString* resultPrefix = prefixesInFile.Get(entry.GetKey()); + + ASSERT_TRUE(*resultPrefix == *expectedPrefix); + } +} + +static void Clear() { + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + + RefPtr<Classifier> classifier = new Classifier(); + classifier->Open(*file); + classifier->Reset(); +} + +static void testUpdateFail(TableUpdateArray& tableUpdates) { + nsresult rv = SyncApplyUpdates(tableUpdates); + ASSERT_NS_FAILED(rv); +} + +static void testUpdate(TableUpdateArray& tableUpdates, + PrefixStringMap& expected) { + // Force nsUrlClassifierUtils loading on main thread + // because nsIUrlClassifierDBService will not run in advance + // in gtest. + nsUrlClassifierUtils::GetInstance(); + + nsresult rv = SyncApplyUpdates(tableUpdates); + ASSERT_TRUE(rv == NS_OK); + VerifyPrefixSet(expected); +} + +static void testFullUpdate(PrefixStringMap& add, nsCString* sha256) { + TableUpdateArray tableUpdates; + + GenerateUpdateData(true, add, nullptr, sha256, tableUpdates); + + testUpdate(tableUpdates, add); +} + +static void testPartialUpdate(PrefixStringMap& add, nsTArray<uint32_t>* removal, + nsCString* sha256, PrefixStringMap& expected) { + TableUpdateArray tableUpdates; + GenerateUpdateData(false, add, removal, sha256, tableUpdates); + + testUpdate(tableUpdates, expected); +} + +static void testOpenLookupCache() { + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->AppendNative(GTEST_SAFEBROWSING_DIR); + + RunTestInNewThread([&]() -> void { + RefPtr<LookupCacheV4> cache = + new LookupCacheV4(nsCString(GTEST_TABLE), ""_ns, file); + nsresult rv = cache->Init(); + ASSERT_EQ(rv, NS_OK); + + rv = cache->Open(); + ASSERT_EQ(rv, NS_OK); + }); +} + +// Tests start from here. +TEST(UrlClassifierTableUpdateV4, FixLengthPSetFullUpdate) +{ + srand(time(NULL)); + + _PrefixArray array; + PrefixStringMap map; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 4, 4, array); + PrefixArrayToPrefixStringMap(array, map); + CalculateSHA256(array, sha256); + + testFullUpdate(map, &sha256); + + Clear(); +} + +TEST(UrlClassifierTableUpdateV4, VariableLengthPSetFullUpdate) +{ + _PrefixArray array; + PrefixStringMap map; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 5, 32, array); + PrefixArrayToPrefixStringMap(array, map); + CalculateSHA256(array, sha256); + + testFullUpdate(map, &sha256); + + Clear(); +} + +// This test contain both variable length prefix set and fixed-length prefix set +TEST(UrlClassifierTableUpdateV4, MixedPSetFullUpdate) +{ + _PrefixArray array; + PrefixStringMap map; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 4, 4, array); + CreateRandomSortedPrefixArray(1000, 5, 32, array); + PrefixArrayToPrefixStringMap(array, map); + CalculateSHA256(array, sha256); + + testFullUpdate(map, &sha256); + + Clear(); +} + +TEST(UrlClassifierTableUpdateV4, PartialUpdateWithRemoval) +{ + _PrefixArray fArray; + + // Apply a full update first. + { + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(10000, 4, 4, fArray); + CreateRandomSortedPrefixArray(2000, 5, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply a partial update with removal. + { + _PrefixArray pArray, mergedArray; + PrefixStringMap pMap, mergedMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + // Remove 1/5 of elements of original prefix set. + nsTArray<uint32_t> removal; + CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal); + RemoveElements(removal, fArray); + + // Calculate the expected prefix map. + MergeAndSortArray(fArray, pArray, mergedArray); + PrefixArrayToPrefixStringMap(mergedArray, mergedMap); + CalculateSHA256(mergedArray, sha256); + + testPartialUpdate(pMap, &removal, &sha256, mergedMap); + } + + Clear(); +} + +TEST(UrlClassifierTableUpdateV4, PartialUpdateWithoutRemoval) +{ + _PrefixArray fArray; + + // Apply a full update first. + { + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(10000, 4, 4, fArray); + CreateRandomSortedPrefixArray(2000, 5, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply a partial update without removal + { + _PrefixArray pArray, mergedArray; + PrefixStringMap pMap, mergedMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + // Calculate the expected prefix map. + MergeAndSortArray(fArray, pArray, mergedArray); + PrefixArrayToPrefixStringMap(mergedArray, mergedMap); + CalculateSHA256(mergedArray, sha256); + + testPartialUpdate(pMap, nullptr, &sha256, mergedMap); + } + + Clear(); +} + +// Expect failure because partial update contains prefix already +// in old prefix set. +TEST(UrlClassifierTableUpdateV4, PartialUpdatePrefixAlreadyExist) +{ + _PrefixArray fArray; + + // Apply a full update fist. + { + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(1000, 4, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply a partial update which contains a prefix in previous full update. + // This should cause an update error. + { + _PrefixArray pArray; + PrefixStringMap pMap; + TableUpdateArray tableUpdates; + + // Pick one prefix from full update prefix and add it to partial update. + // This should result a failure when call ApplyUpdates. + pArray.AppendElement(fArray[rand() % fArray.Length()]); + CreateRandomSortedPrefixArray(200, 4, 32, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + GenerateUpdateData(false, pMap, nullptr, nullptr, tableUpdates); + testUpdateFail(tableUpdates); + } + + Clear(); +} + +// Test apply partial update directly without applying an full update first. +TEST(UrlClassifierTableUpdateV4, OnlyPartialUpdate) +{ + _PrefixArray pArray; + PrefixStringMap pMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + CalculateSHA256(pArray, sha256); + + testPartialUpdate(pMap, nullptr, &sha256, pMap); + + Clear(); +} + +// Test partial update without any ADD prefixes, only removalIndices. +TEST(UrlClassifierTableUpdateV4, PartialUpdateOnlyRemoval) +{ + _PrefixArray fArray; + + // Apply a full update first. + { + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(5000, 4, 4, fArray); + CreateRandomSortedPrefixArray(1000, 5, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply a partial update without add prefix, only contain removal indices. + { + _PrefixArray pArray; + PrefixStringMap pMap, mergedMap; + nsCString sha256; + + // Remove 1/5 of elements of original prefix set. + nsTArray<uint32_t> removal; + CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal); + RemoveElements(removal, fArray); + + PrefixArrayToPrefixStringMap(fArray, mergedMap); + CalculateSHA256(fArray, sha256); + + testPartialUpdate(pMap, &removal, &sha256, mergedMap); + } + + Clear(); +} + +// Test one tableupdate array contains full update and multiple partial updates. +TEST(UrlClassifierTableUpdateV4, MultipleTableUpdates) +{ + _PrefixArray fArray, pArray, mergedArray; + PrefixStringMap fMap, pMap, mergedMap; + nsCString sha256; + + TableUpdateArray tableUpdates; + + // Generate first full udpate + CreateRandomSortedPrefixArray(10000, 4, 4, fArray); + CreateRandomSortedPrefixArray(2000, 5, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + GenerateUpdateData(true, fMap, nullptr, &sha256, tableUpdates); + + // Generate second partial update + CreateRandomSortedPrefixArray(3000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + MergeAndSortArray(fArray, pArray, mergedArray); + CalculateSHA256(mergedArray, sha256); + + GenerateUpdateData(false, pMap, nullptr, &sha256, tableUpdates); + + // Generate thrid partial update + fArray.AppendElements(pArray); + fArray.Sort(); + pArray.Clear(); + CreateRandomSortedPrefixArray(3000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + // Remove 1/5 of elements of original prefix set. + nsTArray<uint32_t> removal; + CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal); + RemoveElements(removal, fArray); + + MergeAndSortArray(fArray, pArray, mergedArray); + PrefixArrayToPrefixStringMap(mergedArray, mergedMap); + CalculateSHA256(mergedArray, sha256); + + GenerateUpdateData(false, pMap, &removal, &sha256, tableUpdates); + + testUpdate(tableUpdates, mergedMap); + + Clear(); +} + +// Test apply full update first, and then apply multiple partial updates +// in one tableupdate array. +TEST(UrlClassifierTableUpdateV4, MultiplePartialUpdateTableUpdates) +{ + _PrefixArray fArray; + + // Apply a full update first + { + PrefixStringMap fMap; + nsCString sha256; + + // Generate first full udpate + CreateRandomSortedPrefixArray(10000, 4, 4, fArray); + CreateRandomSortedPrefixArray(3000, 5, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply multiple partial updates in one table update + { + _PrefixArray pArray, mergedArray; + PrefixStringMap pMap, mergedMap; + nsCString sha256; + nsTArray<uint32_t> removal; + TableUpdateArray tableUpdates; + + // Generate first partial update + CreateRandomSortedPrefixArray(3000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + // Remove 1/5 of elements of original prefix set. + CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal); + RemoveElements(removal, fArray); + + MergeAndSortArray(fArray, pArray, mergedArray); + CalculateSHA256(mergedArray, sha256); + + GenerateUpdateData(false, pMap, &removal, &sha256, tableUpdates); + + fArray.AppendElements(pArray); + fArray.Sort(); + pArray.Clear(); + removal.Clear(); + + // Generate second partial update. + CreateRandomSortedPrefixArray(2000, 4, 4, pArray); + CreateRandomSortedPrefixArray(1000, 5, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + // Remove 1/5 of elements of original prefix set. + CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal); + RemoveElements(removal, fArray); + + MergeAndSortArray(fArray, pArray, mergedArray); + PrefixArrayToPrefixStringMap(mergedArray, mergedMap); + CalculateSHA256(mergedArray, sha256); + + GenerateUpdateData(false, pMap, &removal, &sha256, tableUpdates); + + testUpdate(tableUpdates, mergedMap); + } + + Clear(); +} + +// Test removal indices are larger than the original prefix set. +TEST(UrlClassifierTableUpdateV4, RemovalIndexTooLarge) +{ + _PrefixArray fArray; + + // Apply a full update first + { + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(1000, 4, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply a partial update with removal indice array larger than + // old prefix set(fArray). This should cause an error. + { + _PrefixArray pArray; + PrefixStringMap pMap; + nsTArray<uint32_t> removal; + TableUpdateArray tableUpdates; + + CreateRandomSortedPrefixArray(200, 4, 32, pArray); + RemoveIntersection(fArray, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + for (uint32_t i = 0; i < fArray.Length() + 1; i++) { + removal.AppendElement(i); + } + + GenerateUpdateData(false, pMap, &removal, nullptr, tableUpdates); + testUpdateFail(tableUpdates); + } + + Clear(); +} + +TEST(UrlClassifierTableUpdateV4, ChecksumMismatch) +{ + // Apply a full update first + { + _PrefixArray fArray; + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(1000, 4, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + } + + // Apply a partial update with incorrect sha256 + { + _PrefixArray pArray; + PrefixStringMap pMap; + nsCString sha256; + TableUpdateArray tableUpdates; + + CreateRandomSortedPrefixArray(200, 4, 32, pArray); + PrefixArrayToPrefixStringMap(pArray, pMap); + + // sha256 should be calculated with both old prefix set and add prefix + // set, here we only calculate sha256 with add prefix set to check if + // applyUpdate will return failure. + CalculateSHA256(pArray, sha256); + + GenerateUpdateData(false, pMap, nullptr, &sha256, tableUpdates); + testUpdateFail(tableUpdates); + } + + Clear(); +} + +TEST(UrlClassifierTableUpdateV4, ApplyUpdateThenLoad) +{ + // Apply update with sha256 + { + _PrefixArray fArray; + PrefixStringMap fMap; + nsCString sha256; + + CreateRandomSortedPrefixArray(1000, 4, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + CalculateSHA256(fArray, sha256); + + testFullUpdate(fMap, &sha256); + + // Open lookup cache will load prefix set and verify the sha256 + testOpenLookupCache(); + } + + Clear(); + + // Apply update without sha256 + { + _PrefixArray fArray; + PrefixStringMap fMap; + + CreateRandomSortedPrefixArray(1000, 4, 32, fArray); + PrefixArrayToPrefixStringMap(fArray, fMap); + + testFullUpdate(fMap, nullptr); + + testOpenLookupCache(); + } + + Clear(); +} + +// This test is used to avoid an eror from nsICryptoHash +TEST(UrlClassifierTableUpdateV4, ApplyUpdateWithFixedChecksum) +{ + _PrefixArray fArray = {_Prefix("enus"), + _Prefix("apollo"), + _Prefix("mars"), + _Prefix("Hecatonchires cyclopes"), + _Prefix("vesta"), + _Prefix("neptunus"), + _Prefix("jupiter"), + _Prefix("diana"), + _Prefix("minerva"), + _Prefix("ceres"), + _Prefix("Aidos,Adephagia,Adikia,Aletheia"), + _Prefix("hecatonchires"), + _Prefix("alcyoneus"), + _Prefix("hades"), + _Prefix("vulcanus"), + _Prefix("juno"), + _Prefix("mercury"), + _Prefix("Stheno, Euryale and Medusa")}; + fArray.Sort(); + + PrefixStringMap fMap; + PrefixArrayToPrefixStringMap(fArray, fMap); + + nsCString sha256( + "\xae\x18\x94\xd7\xd0\x83\x5f\xc1" + "\x58\x59\x5c\x2c\x72\xb9\x6e\x5e" + "\xf4\xe8\x0a\x6b\xff\x5e\x6b\x81" + "\x65\x34\x06\x16\x06\x59\xa0\x67"); + + testFullUpdate(fMap, &sha256); + + // Open lookup cache will load prefix set and verify the sha256 + testOpenLookupCache(); + + Clear(); +} + +// This test ensure that an empty update works correctly. Empty update +// should be skipped by CheckValidUpdate in Classifier::UpdateTableV4. +TEST(UrlClassifierTableUpdateV4, EmptyUpdate) +{ + PrefixStringMap emptyAddition; + nsTArray<uint32_t> emptyRemoval; + + _PrefixArray array; + PrefixStringMap map; + nsCString sha256; + + CalculateSHA256(array, sha256); + + // Test apply empty full/partial update before we already + // have data in DB. + testFullUpdate(emptyAddition, &sha256); + testPartialUpdate(emptyAddition, &emptyRemoval, &sha256, map); + + // Apply an full update. + CreateRandomSortedPrefixArray(100, 4, 4, array); + CreateRandomSortedPrefixArray(10, 5, 32, array); + PrefixArrayToPrefixStringMap(array, map); + CalculateSHA256(array, sha256); + + testFullUpdate(map, &sha256); + + // Test apply empty full/partial update when we already + // have data in DB + testPartialUpdate(emptyAddition, &emptyRemoval, &sha256, map); + testFullUpdate(emptyAddition, &sha256); + + Clear(); +} + +// This test ensure applying an empty update directly through update algorithm +// should be correct. +TEST(UrlClassifierTableUpdateV4, EmptyUpdate2) +{ + // Setup LookupCache with initial data + _PrefixArray array; + CreateRandomSortedPrefixArray(100, 4, 4, array); + CreateRandomSortedPrefixArray(10, 5, 32, array); + RefPtr<LookupCacheV4> cache = SetupLookupCache<LookupCacheV4>(array); + + // Setup TableUpdate object with only sha256 from previous update(initial + // data). + nsCString sha256; + CalculateSHA256(array, sha256); + std::string stdSHA256; + stdSHA256.assign(const_cast<char*>(sha256.BeginReading()), sha256.Length()); + + RefPtr<TableUpdateV4> tableUpdate = new TableUpdateV4(GTEST_TABLE); + tableUpdate->SetSHA256(stdSHA256); + + // Apply update directly through LookupCache interface + PrefixStringMap input, output; + PrefixArrayToPrefixStringMap(array, input); + nsresult rv = cache->ApplyUpdate(tableUpdate.get(), input, output); + + ASSERT_TRUE(rv == NS_OK); + + Clear(); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierUtils.cpp b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierUtils.cpp new file mode 100644 index 0000000000..354a47f07d --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierUtils.cpp @@ -0,0 +1,254 @@ +/* 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 <ctype.h> +#include <stdio.h> + +#include <mozilla/RefPtr.h> +#include "nsEscape.h" +#include "nsString.h" +#include "nsUrlClassifierUtils.h" +#include "stdlib.h" + +#include "Common.h" + +static char int_to_hex_digit(int32_t i) { + NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit"); + return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A'))); +} + +static void CheckEquals(nsCString& expected, nsCString& actual) { + ASSERT_TRUE((expected).Equals((actual))) + << "Expected:" << expected.get() << ", Actual:" << actual.get(); +} + +static void TestUnescapeHelper(const char* in, const char* expected) { + nsCString out, strIn(in), strExp(expected); + + NS_UnescapeURL(strIn.get(), strIn.Length(), esc_AlwaysCopy, out); + CheckEquals(strExp, out); +} + +static void TestEncodeHelper(const char* in, const char* expected) { + nsCString out, strIn(in), strExp(expected); + nsUrlClassifierUtils::GetInstance()->SpecialEncode(strIn, true, out); + CheckEquals(strExp, out); +} + +static void TestCanonicalizeHelper(const char* in, const char* expected) { + nsCString out, strIn(in), strExp(expected); + nsUrlClassifierUtils::GetInstance()->CanonicalizePath(strIn, out); + CheckEquals(strExp, out); +} + +static void TestCanonicalNumHelper(const char* in, uint32_t bytes, + bool allowOctal, const char* expected) { + nsCString out, strIn(in), strExp(expected); + nsUrlClassifierUtils::GetInstance()->CanonicalNum(strIn, bytes, allowOctal, + out); + CheckEquals(strExp, out); +} + +void TestHostnameHelper(const char* in, const char* expected) { + nsCString out, strIn(in), strExp(expected); + nsUrlClassifierUtils::GetInstance()->CanonicalizeHostname(strIn, out); + CheckEquals(strExp, out); +} + +// Make sure Unescape from nsEncode.h's unescape does what the server does. +TEST(UrlClassifierUtils, Unescape) +{ + // test empty string + TestUnescapeHelper("\0", "\0"); + + // Test docoding of all characters. + nsCString allCharsEncoded, allCharsEncodedLowercase, allCharsAsString; + for (int32_t i = 1; i < 256; ++i) { + allCharsEncoded.Append('%'); + allCharsEncoded.Append(int_to_hex_digit(i / 16)); + allCharsEncoded.Append((int_to_hex_digit(i % 16))); + + allCharsEncodedLowercase.Append('%'); + allCharsEncodedLowercase.Append(tolower(int_to_hex_digit(i / 16))); + allCharsEncodedLowercase.Append(tolower(int_to_hex_digit(i % 16))); + + allCharsAsString.Append(static_cast<char>(i)); + } + + nsCString out; + NS_UnescapeURL(allCharsEncoded.get(), allCharsEncoded.Length(), + esc_AlwaysCopy, out); + + CheckEquals(allCharsAsString, out); + + out.Truncate(); + NS_UnescapeURL(allCharsEncodedLowercase.get(), + allCharsEncodedLowercase.Length(), esc_AlwaysCopy, out); + CheckEquals(allCharsAsString, out); + + // Test %-related edge cases + TestUnescapeHelper("%", "%"); + TestUnescapeHelper("%xx", "%xx"); + TestUnescapeHelper("%%", "%%"); + TestUnescapeHelper("%%%", "%%%"); + TestUnescapeHelper("%%%%", "%%%%"); + TestUnescapeHelper("%1", "%1"); + TestUnescapeHelper("%1z", "%1z"); + TestUnescapeHelper("a%1z", "a%1z"); + TestUnescapeHelper("abc%d%e%fg%hij%klmno%", "abc%d%e%fg%hij%klmno%"); + + // A few more tests + TestUnescapeHelper("%25", "%"); + TestUnescapeHelper("%25%32%35", "%25"); +} + +TEST(UrlClassifierUtils, Enc) +{ + // Test empty string + TestEncodeHelper("", ""); + + // Test that all characters we shouldn't encode ([33-34],36,[38,126]) are not. + nsCString noenc; + for (int32_t i = 33; i < 127; i++) { + if (i != 35 && i != 37) { // skip % + noenc.Append(static_cast<char>(i)); + } + } + nsCString out; + nsUrlClassifierUtils::GetInstance()->SpecialEncode(noenc, false, out); + CheckEquals(noenc, out); + + // Test that all the chars that we should encode [0,32],35, 37,[127,255] are + nsCString yesAsString, yesExpectedString; + for (int32_t i = 1; i < 256; i++) { + if (i < 33 || i == 35 || i == 37 || i > 126) { + yesAsString.Append(static_cast<char>(i)); + yesExpectedString.Append('%'); + yesExpectedString.Append(int_to_hex_digit(i / 16)); + yesExpectedString.Append(int_to_hex_digit(i % 16)); + } + } + + out.Truncate(); + nsUrlClassifierUtils::GetInstance()->SpecialEncode(yesAsString, false, out); + CheckEquals(yesExpectedString, out); + + TestEncodeHelper("blah//blah", "blah/blah"); +} + +TEST(UrlClassifierUtils, Canonicalize) +{ + // Test repeated %-decoding. Note: %25 --> %, %32 --> 2, %35 --> 5 + TestCanonicalizeHelper("%25", "%25"); + TestCanonicalizeHelper("%25%32%35", "%25"); + TestCanonicalizeHelper("asdf%25%32%35asd", "asdf%25asd"); + TestCanonicalizeHelper("%%%25%32%35asd%%", "%25%25%25asd%25%25"); + TestCanonicalizeHelper("%25%32%35%25%32%35%25%32%35", "%25%25%25"); + TestCanonicalizeHelper("%25", "%25"); + TestCanonicalizeHelper( + "%257Ea%2521b%2540c%2523d%2524e%25f%255E00%252611%252A22%252833%252944_" + "55%252B", + "~a!b@c%23d$e%25f^00&11*22(33)44_55+"); + + TestCanonicalizeHelper("", ""); + TestCanonicalizeHelper( + "%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/" + "%77%77%77%2E%65%62%61%79%2E%63%6F%6D/", + "168.188.99.26/.secure/www.ebay.com/"); + TestCanonicalizeHelper( + "195.127.0.11/uploads/%20%20%20%20/.verify/" + ".eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/", + "195.127.0.11/uploads/%20%20%20%20/.verify/" + ".eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/"); + // Added in bug 489455. %00 should no longer be changed to %01. + TestCanonicalizeHelper("%00", "%00"); +} + +void TestParseIPAddressHelper(const char* in, const char* expected) { + nsCString out, strIn(in), strExp(expected); + nsUrlClassifierUtils::GetInstance()->ParseIPAddress(strIn, out); + CheckEquals(strExp, out); +} + +TEST(UrlClassifierUtils, ParseIPAddress) +{ + TestParseIPAddressHelper("123.123.0.0.1", ""); + TestParseIPAddressHelper("255.0.0.1", "255.0.0.1"); + TestParseIPAddressHelper("12.0x12.01234", "12.18.2.156"); + TestParseIPAddressHelper("276.2.3", "20.2.0.3"); + TestParseIPAddressHelper("012.034.01.055", "10.28.1.45"); + TestParseIPAddressHelper("0x12.0x43.0x44.0x01", "18.67.68.1"); + TestParseIPAddressHelper("167838211", "10.1.2.3"); + TestParseIPAddressHelper("3279880203", "195.127.0.11"); + TestParseIPAddressHelper("0x12434401", "18.67.68.1"); + TestParseIPAddressHelper("413960661", "24.172.137.213"); + TestParseIPAddressHelper("03053104725", "24.172.137.213"); + TestParseIPAddressHelper("030.0254.0x89d5", "24.172.137.213"); + TestParseIPAddressHelper("1.234.4.0377", "1.234.4.255"); + TestParseIPAddressHelper("1.2.3.00x0", ""); + TestParseIPAddressHelper("10.192.95.89 xy", "10.192.95.89"); + TestParseIPAddressHelper("10.192.95.89 xyz", ""); + TestParseIPAddressHelper("1.2.3.0x0", "1.2.3.0"); + TestParseIPAddressHelper("1.2.3.4", "1.2.3.4"); +} + +TEST(UrlClassifierUtils, CanonicalNum) +{ + TestCanonicalNumHelper("", 1, true, ""); + TestCanonicalNumHelper("10", 0, true, ""); + TestCanonicalNumHelper("45", 1, true, "45"); + TestCanonicalNumHelper("0x10", 1, true, "16"); + TestCanonicalNumHelper("367", 2, true, "1.111"); + TestCanonicalNumHelper("012345", 3, true, "0.20.229"); + TestCanonicalNumHelper("0173", 1, true, "123"); + TestCanonicalNumHelper("09", 1, false, "9"); + TestCanonicalNumHelper("0x120x34", 2, true, ""); + TestCanonicalNumHelper("0x12fc", 2, true, "18.252"); + TestCanonicalNumHelper("3279880203", 4, true, "195.127.0.11"); + TestCanonicalNumHelper("0x0000059", 1, true, "89"); + TestCanonicalNumHelper("0x00000059", 1, true, "89"); + TestCanonicalNumHelper("0x0000067", 1, true, "103"); +} + +TEST(UrlClassifierUtils, Hostname) +{ + TestHostnameHelper("abcd123;[]", "abcd123;[]"); + TestHostnameHelper("abc.123", "abc.123"); + TestHostnameHelper("abc..123", "abc.123"); + TestHostnameHelper("trailing.", "trailing"); + TestHostnameHelper("i love trailing dots....", "i%20love%20trailing%20dots"); + TestHostnameHelper(".leading", "leading"); + TestHostnameHelper("..leading", "leading"); + TestHostnameHelper(".dots.", "dots"); + TestHostnameHelper(".both.", "both"); + TestHostnameHelper(".both..", "both"); + TestHostnameHelper("..both.", "both"); + TestHostnameHelper("..both..", "both"); + TestHostnameHelper("..a.b.c.d..", "a.b.c.d"); + TestHostnameHelper("..127.0.0.1..", "127.0.0.1"); + TestHostnameHelper("AB CD 12354", "ab%20cd%2012354"); + TestHostnameHelper("\1\2\3\4\112\177", "%01%02%03%04j%7F"); + TestHostnameHelper("<>.AS/-+", "<>.as/-+"); + // Added in bug 489455. %00 should no longer be changed to %01. + TestHostnameHelper("%00", "%00"); +} + +TEST(UrlClassifierUtils, LongHostname) +{ + static const int kTestSize = 1024 * 150; + char* str = static_cast<char*>(malloc(kTestSize + 1)); + memset(str, 'x', kTestSize); + str[kTestSize] = '\0'; + + nsAutoCString out; + nsDependentCString in(str); + PRIntervalTime clockStart = PR_IntervalNow(); + nsUrlClassifierUtils::GetInstance()->CanonicalizeHostname(in, out); + PRIntervalTime clockEnd = PR_IntervalNow(); + + CheckEquals(in, out); + + printf("CanonicalizeHostname on long string (%dms)\n", + PR_IntervalToMilliseconds(clockEnd - clockStart)); +} diff --git a/toolkit/components/url-classifier/tests/gtest/TestVariableLengthPrefixSet.cpp b/toolkit/components/url-classifier/tests/gtest/TestVariableLengthPrefixSet.cpp new file mode 100644 index 0000000000..6eb36a12d2 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/TestVariableLengthPrefixSet.cpp @@ -0,0 +1,486 @@ +/* -*- 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 "LookupCacheV4.h" +#include "mozilla/Preferences.h" +#include <mozilla/RefPtr.h> +#include "nsAppDirectoryServiceDefs.h" +#include "nsClassHashtable.h" +#include "nsString.h" +#include "VariableLengthPrefixSet.h" + +#include "Common.h" + +// Create fullhash by appending random characters. +static nsCString CreateFullHash(const nsACString& in) { + nsCString out(in); + out.SetLength(32); + for (size_t i = in.Length(); i < 32; i++) { + out.SetCharAt(char(rand() % 256), i); + } + + return out; +} + +// This function generate N prefixes with size between MIN and MAX. +// The output array will not be cleared, random result will append to it +static void RandomPrefixes(uint32_t N, uint32_t MIN, uint32_t MAX, + _PrefixArray& array) { + array.SetCapacity(array.Length() + N); + + uint32_t range = (MAX - MIN + 1); + + for (uint32_t i = 0; i < N; i++) { + uint32_t prefixSize = (rand() % range) + MIN; + _Prefix prefix; + prefix.SetLength(prefixSize); + + bool added = false; + while (!added) { + char* dst = prefix.BeginWriting(); + for (uint32_t j = 0; j < prefixSize; j++) { + dst[j] = rand() % 256; + } + + if (!array.Contains(prefix)) { + array.AppendElement(prefix); + added = true; + } + } + } +} + +// This test loops through all the prefixes and converts each prefix to +// fullhash by appending random characters, each converted fullhash +// should at least match its original length in the prefixSet. +static void DoExpectedLookup(LookupCacheV4* cache, _PrefixArray& array) { + uint32_t matchLength = 0; + for (uint32_t i = 0; i < array.Length(); i++) { + const nsCString& prefix = array[i]; + Completion complete; + complete.Assign(CreateFullHash(prefix)); + + // Find match for prefix-generated full hash + bool has, confirmed; + cache->Has(complete, &has, &matchLength, &confirmed); + MOZ_ASSERT(matchLength != 0); + + if (matchLength != prefix.Length()) { + // Return match size is not the same as prefix size. + // In this case it could be because the generated fullhash match other + // prefixes, check if this prefix exist. + bool found = false; + + for (uint32_t j = 0; j < array.Length(); j++) { + if (array[j].Length() != matchLength) { + continue; + } + + if (0 == memcmp(complete.buf, array[j].BeginReading(), matchLength)) { + found = true; + break; + } + } + ASSERT_TRUE(found); + } + } +} + +static void DoRandomLookup(LookupCacheV4* cache, uint32_t N, + _PrefixArray& array) { + for (uint32_t i = 0; i < N; i++) { + // Random 32-bytes test fullhash + char buf[32]; + for (uint32_t j = 0; j < 32; j++) { + buf[j] = (char)(rand() % 256); + } + + // Get the expected result. + nsTArray<uint32_t> expected; + for (uint32_t j = 0; j < array.Length(); j++) { + const nsACString& str = array[j]; + if (0 == memcmp(buf, str.BeginReading(), str.Length())) { + expected.AppendElement(str.Length()); + } + } + + Completion complete; + complete.Assign(nsDependentCSubstring(buf, 32)); + bool has, confirmed; + uint32_t matchLength = 0; + cache->Has(complete, &has, &matchLength, &confirmed); + + ASSERT_TRUE(expected.IsEmpty() ? !matchLength + : expected.Contains(matchLength)); + } +} + +static already_AddRefed<LookupCacheV4> SetupLookupCache( + const nsACString& aName) { + nsCOMPtr<nsIFile> rootDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(rootDir)); + + nsAutoCString provider("test"); + RefPtr<LookupCacheV4> lookup = new LookupCacheV4(aName, provider, rootDir); + lookup->Init(); + + return lookup.forget(); +} + +class UrlClassifierPrefixSetTest : public ::testing::TestWithParam<uint32_t> { + protected: + void SetUp() override { + // max_array_size to 0 means we are testing delta algorithm here. + static const char prefKey[] = + "browser.safebrowsing.prefixset.max_array_size"; + mozilla::Preferences::SetUint(prefKey, GetParam()); + + mCache = SetupLookupCache("test"_ns); + } + + void TearDown() override { + mCache = nullptr; + mArray.Clear(); + mMap.Clear(); + } + + nsresult SetupPrefixes(_PrefixArray&& aArray) { + mArray = std::move(aArray); + PrefixArrayToPrefixStringMap(mArray, mMap); + return mCache->Build(mMap); + } + + void SetupPrefixesAndVerify(_PrefixArray& aArray) { + mArray = aArray.Clone(); + PrefixArrayToPrefixStringMap(mArray, mMap); + + ASSERT_NS_SUCCEEDED(mCache->Build(mMap)); + Verify(); + } + + void SetupPrefixesAndVerify(_PrefixArray&& aArray) { + nsresult rv = SetupPrefixes(std::move(aArray)); + ASSERT_NS_SUCCEEDED(rv); + Verify(); + } + + void SetupRandomPrefixesAndVerify(uint32_t N, uint32_t MIN, uint32_t MAX) { + srand(time(nullptr)); + RandomPrefixes(N, MIN, MAX, mArray); + PrefixArrayToPrefixStringMap(mArray, mMap); + + ASSERT_NS_SUCCEEDED(mCache->Build(mMap)); + Verify(); + } + + void Verify() { + DoExpectedLookup(mCache, mArray); + DoRandomLookup(mCache, 1000, mArray); + CheckContent(mCache, mArray); + } + + RefPtr<LookupCacheV4> mCache; + _PrefixArray mArray; + PrefixStringMap mMap; +}; + +// Test setting prefix set with only 4-bytes prefixes +TEST_P(UrlClassifierPrefixSetTest, FixedLengthSet) { + SetupPrefixesAndVerify({ + _Prefix("alph"), + _Prefix("brav"), + _Prefix("char"), + _Prefix("delt"), + _Prefix("echo"), + _Prefix("foxt"), + }); +} + +TEST_P(UrlClassifierPrefixSetTest, FixedLengthRandomSet) { + SetupRandomPrefixesAndVerify(1500, 4, 4); +} + +TEST_P(UrlClassifierPrefixSetTest, FixedLengthRandomLargeSet) { + SetupRandomPrefixesAndVerify(15000, 4, 4); +} + +TEST_P(UrlClassifierPrefixSetTest, FixedLengthTinySet) { + SetupPrefixesAndVerify({ + _Prefix("tiny"), + }); +} + +// Test setting prefix set with only 5~32 bytes prefixes +TEST_P(UrlClassifierPrefixSetTest, VariableLengthSet) { + SetupPrefixesAndVerify( + {_Prefix("bravo"), _Prefix("charlie"), _Prefix("delta"), + _Prefix("EchoEchoEchoEchoEcho"), _Prefix("foxtrot"), + _Prefix("GolfGolfGolfGolfGolfGolfGolfGolf"), _Prefix("hotel"), + _Prefix("november"), _Prefix("oscar"), _Prefix("quebec"), + _Prefix("romeo"), _Prefix("sierrasierrasierrasierrasierra"), + _Prefix("Tango"), _Prefix("whiskey"), _Prefix("yankee"), + _Prefix("ZuluZuluZuluZulu")}); +} + +TEST_P(UrlClassifierPrefixSetTest, VariableLengthRandomSet) { + SetupRandomPrefixesAndVerify(1500, 5, 32); +} + +// Test setting prefix set with both 4-bytes prefixes and 5~32 bytes prefixes +TEST_P(UrlClassifierPrefixSetTest, MixedPrefixSet) { + SetupPrefixesAndVerify( + {_Prefix("enus"), _Prefix("apollo"), _Prefix("mars"), + _Prefix("Hecatonchires cyclopes"), _Prefix("vesta"), _Prefix("neptunus"), + _Prefix("jupiter"), _Prefix("diana"), _Prefix("minerva"), + _Prefix("ceres"), _Prefix("Aidos,Adephagia,Adikia,Aletheia"), + _Prefix("hecatonchires"), _Prefix("alcyoneus"), _Prefix("hades"), + _Prefix("vulcanus"), _Prefix("juno"), _Prefix("mercury"), + _Prefix("Stheno, Euryale and Medusa")}); +} + +TEST_P(UrlClassifierPrefixSetTest, MixedRandomPrefixSet) { + SetupRandomPrefixesAndVerify(1500, 4, 32); +} + +// Test resetting prefix set +TEST_P(UrlClassifierPrefixSetTest, ResetPrefix) { + // Base prefix set + _PrefixArray oldArray = { + _Prefix("Iceland"), _Prefix("Peru"), _Prefix("Mexico"), + _Prefix("Australia"), _Prefix("Japan"), _Prefix("Egypt"), + _Prefix("America"), _Prefix("Finland"), _Prefix("Germany"), + _Prefix("Italy"), _Prefix("France"), _Prefix("Taiwan"), + }; + SetupPrefixesAndVerify(oldArray); + + // New prefix set + _PrefixArray newArray = { + _Prefix("Pikachu"), _Prefix("Bulbasaur"), _Prefix("Charmander"), + _Prefix("Blastoise"), _Prefix("Pidgey"), _Prefix("Mewtwo"), + _Prefix("Jigglypuff"), _Prefix("Persian"), _Prefix("Tentacool"), + _Prefix("Onix"), _Prefix("Eevee"), _Prefix("Jynx"), + }; + SetupPrefixesAndVerify(newArray); + + // Should not match any of the first prefix set + uint32_t matchLength = 0; + for (uint32_t i = 0; i < oldArray.Length(); i++) { + Completion complete; + complete.Assign(CreateFullHash(oldArray[i])); + + // Find match for prefix-generated full hash + bool has, confirmed; + mCache->Has(complete, &has, &matchLength, &confirmed); + + ASSERT_TRUE(matchLength == 0); + } +} + +// Test only set one 4-bytes prefix and one full-length prefix +TEST_P(UrlClassifierPrefixSetTest, TinyPrefixSet) { + SetupPrefixesAndVerify({ + _Prefix("AAAA"), + _Prefix("11112222333344445555666677778888"), + }); +} + +// Test empty prefix set and IsEmpty function +TEST_P(UrlClassifierPrefixSetTest, EmptyFixedPrefixSet) { + ASSERT_TRUE(mCache->IsEmpty()); + + SetupPrefixesAndVerify({}); + + // Insert an 4-bytes prefix, then IsEmpty should return false + SetupPrefixesAndVerify({_Prefix("test")}); + + ASSERT_TRUE(!mCache->IsEmpty()); +} + +TEST_P(UrlClassifierPrefixSetTest, EmptyVariableLengthPrefixSet) { + ASSERT_TRUE(mCache->IsEmpty()); + + SetupPrefixesAndVerify({}); + + // Insert an 5~32 bytes prefix, then IsEmpty should return false + SetupPrefixesAndVerify({_Prefix("test variable length")}); + + ASSERT_TRUE(!mCache->IsEmpty()); +} + +// Test prefix size should only between 4~32 bytes +TEST_P(UrlClassifierPrefixSetTest, MinMaxPrefixSet) { + // Test prefix set between 4-32 bytes, should success + SetupPrefixesAndVerify({_Prefix("1234"), _Prefix("ABCDEFGHIJKKMNOP"), + _Prefix("1aaa2bbb3ccc4ddd5eee6fff7ggg8hhh")}); + + // Prefix size less than 4-bytes should fail + nsresult rv = SetupPrefixes({_Prefix("123")}); + ASSERT_NS_FAILED(rv); + + // Prefix size greater than 32-bytes should fail + rv = SetupPrefixes({_Prefix("1aaa2bbb3ccc4ddd5eee6fff7ggg8hhh9")}); + ASSERT_NS_FAILED(rv); +} + +// Test save then load prefix set with only 4-bytes prefixes +TEST_P(UrlClassifierPrefixSetTest, LoadSaveFixedLengthPrefixSet) { + nsCOMPtr<nsIFile> file; + _PrefixArray array; + PrefixStringMap map; + + // Save + { + RefPtr<LookupCacheV4> save = SetupLookupCache("test-save"_ns); + + RandomPrefixes(10000, 4, 4, array); + + PrefixArrayToPrefixStringMap(array, map); + save->Build(map); + + DoExpectedLookup(save, array); + DoRandomLookup(save, 1000, array); + CheckContent(save, array); + + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->Append(u"test.vlpset"_ns); + save->StoreToFile(file); + } + + // Load + { + RefPtr<LookupCacheV4> load = SetupLookupCache("test-load"_ns); + load->LoadFromFile(file); + + DoExpectedLookup(load, array); + DoRandomLookup(load, 1000, array); + CheckContent(load, array); + } + + file->Remove(false); +} + +// Test save then load prefix set with only 5~32 bytes prefixes +TEST_P(UrlClassifierPrefixSetTest, LoadSaveVariableLengthPrefixSet) { + nsCOMPtr<nsIFile> file; + _PrefixArray array; + PrefixStringMap map; + + // Save + { + RefPtr<LookupCacheV4> save = SetupLookupCache("test-save"_ns); + + RandomPrefixes(10000, 5, 32, array); + + PrefixArrayToPrefixStringMap(array, map); + save->Build(map); + + DoExpectedLookup(save, array); + DoRandomLookup(save, 1000, array); + CheckContent(save, array); + + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->Append(u"test.vlpset"_ns); + save->StoreToFile(file); + } + + // Load + { + RefPtr<LookupCacheV4> load = SetupLookupCache("test-load"_ns); + load->LoadFromFile(file); + + DoExpectedLookup(load, array); + DoRandomLookup(load, 1000, array); + CheckContent(load, array); + } + + file->Remove(false); +} + +// Test save then load prefix with both 4 bytes prefixes and 5~32 bytes prefixes +TEST_P(UrlClassifierPrefixSetTest, LoadSavePrefixSet) { + nsCOMPtr<nsIFile> file; + _PrefixArray array; + PrefixStringMap map; + + // Save + { + RefPtr<LookupCacheV4> save = SetupLookupCache("test-save"_ns); + + // Try to simulate the real case that most prefixes are 4bytes + RandomPrefixes(20000, 4, 4, array); + RandomPrefixes(1000, 5, 32, array); + + PrefixArrayToPrefixStringMap(array, map); + save->Build(map); + + DoExpectedLookup(save, array); + DoRandomLookup(save, 1000, array); + CheckContent(save, array); + + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->Append(u"test.vlpset"_ns); + save->StoreToFile(file); + } + + // Load + { + RefPtr<LookupCacheV4> load = SetupLookupCache("test-load"_ns); + load->LoadFromFile(file); + + DoExpectedLookup(load, array); + DoRandomLookup(load, 1000, array); + CheckContent(load, array); + } + + file->Remove(false); +} + +// This is for fixed-length prefixset +TEST_P(UrlClassifierPrefixSetTest, LoadSaveNoDelta) { + nsCOMPtr<nsIFile> file; + _PrefixArray array; + PrefixStringMap map; + + for (uint32_t i = 0; i < 100; i++) { + // construct a tree without deltas by making the distance + // between entries larger than 16 bits + uint32_t v = ((1 << 16) + 1) * i; + nsCString* ele = array.AppendElement(); + ele->AppendASCII(reinterpret_cast<const char*>(&v), 4); + } + + // Save + { + RefPtr<LookupCacheV4> save = SetupLookupCache("test-save"_ns); + + PrefixArrayToPrefixStringMap(array, map); + save->Build(map); + + DoExpectedLookup(save, array); + DoRandomLookup(save, 1000, array); + CheckContent(save, array); + + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file)); + file->Append(u"test.vlpset"_ns); + save->StoreToFile(file); + } + + // Load + { + RefPtr<LookupCacheV4> load = SetupLookupCache("test-load"_ns); + load->LoadFromFile(file); + + DoExpectedLookup(load, array); + DoRandomLookup(load, 1000, array); + CheckContent(load, array); + } + + file->Remove(false); +} + +// To run the same test for different configurations of +// "browser_safebrowsing_prefixset_max_array_size" +INSTANTIATE_TEST_SUITE_P(UrlClassifierPrefixSetTest, UrlClassifierPrefixSetTest, + ::testing::Values(0, UINT32_MAX)); diff --git a/toolkit/components/url-classifier/tests/gtest/moz.build b/toolkit/components/url-classifier/tests/gtest/moz.build new file mode 100644 index 0000000000..070012de77 --- /dev/null +++ b/toolkit/components/url-classifier/tests/gtest/moz.build @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +LOCAL_INCLUDES += [ + "../..", +] + +DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True +DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True + +UNIFIED_SOURCES += [ + "Common.cpp", + "TestCaching.cpp", + "TestChunkSet.cpp", + "TestClassifier.cpp", + "TestFailUpdate.cpp", + "TestFindFullHash.cpp", + "TestLookupCacheV4.cpp", + "TestPerProviderDirectory.cpp", + "TestPrefixSet.cpp", + "TestProtocolParser.cpp", + "TestRiceDeltaDecoder.cpp", + "TestSafebrowsingHash.cpp", + "TestSafeBrowsingProtobuf.cpp", + "TestTable.cpp", + "TestUrlClassifierTableUpdateV4.cpp", + "TestUrlClassifierUtils.cpp", + "TestURLsAndHashing.cpp", + "TestVariableLengthPrefixSet.cpp", +] + +# Required to have the same MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES +# as non-testing code. +if CONFIG["NIGHTLY_BUILD"] or CONFIG["MOZ_DEBUG"]: + DEFINES["MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES"] = True + +FINAL_LIBRARY = "xul-gtest" diff --git a/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html b/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html new file mode 100644 index 0000000000..1096274260 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html @@ -0,0 +1,143 @@ +<html> +<head> +<title></title> + +<script type="text/javascript"> + +// Modified by evil.js +var scriptItem; + +var scriptItem1 = "untouched"; +var imageItem1 = "untouched"; +var frameItem1 = "untouched"; +var scriptItem2 = "untouched"; +var imageItem2 = "untouched"; +var frameItem2 = "untouched"; +var xhrItem = "untouched"; +var fetchItem = "untouched"; +var mediaItem1 = "untouched"; + +async function checkLoads() { + window.parent.is(scriptItem1, "spoiled", "Should not block tracking js 1"); + window.parent.is(scriptItem2, "spoiled", "Should not block tracking js 2"); + window.parent.is(imageItem1, "spoiled", "Should not block tracking img 1"); + window.parent.is(imageItem2, "spoiled", "Should not block tracking img 2"); + window.parent.is(frameItem1, "spoiled", "Should not block tracking iframe 1"); + window.parent.is(frameItem2, "spoiled", "Should not block tracking iframe 2"); + window.parent.is(mediaItem1, "loaded", "Should not block tracking video"); + window.parent.is(xhrItem, "loaded", "Should not block tracking XHR"); + window.parent.is(fetchItem, "loaded", "Should not block fetches from tracking domains"); + window.parent.is(window.document.blockedNodeByClassifierCount, 0, + "No elements should be blocked"); + + // End (parent) test. + await window.parent.clearPermissions(); + window.parent.SimpleTest.finish(); +} + +var onloadCalled = false; +var xhrFinished = false; +var fetchFinished = false; +var videoLoaded = false; +function loaded(type) { + if (type === "onload") { + onloadCalled = true; + } else if (type === "xhr") { + xhrFinished = true; + } else if (type === "fetch") { + fetchFinished = true; + } else if (type === "video") { + videoLoaded = true; + } + + if (onloadCalled && xhrFinished && fetchFinished && videoLoaded) { + checkLoads(); + } +} +</script> + +</head> + +<body onload="loaded('onload')"> + +<!-- Try loading from a tracking script URI (1) --> +<script id="badscript1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="scriptItem1 = 'spoiled';"></script> + +<!-- Try loading from a tracking image URI (1) --> +<img id="badimage1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg" onload="imageItem1 = 'spoiled';"/> + +<!-- Try loading from a tracking frame URI (1) --> +<iframe id="badframe1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html" onload="frameItem1 = 'spoiled';"></iframe> + +<!-- Try loading from a tracking video URI --> +<video id="badmedia1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/vp9.webm"></video> + +<script> +var v = document.getElementById("badmedia1"); +v.addEventListener("loadedmetadata", function() { + mediaItem1 = "loaded"; + loaded("video"); +}, true); +v.addEventListener("error", function() { + mediaItem1 = "error"; + loaded("video"); +}, true); + +// Try loading from a tracking script URI (2) - The loader may follow a +// different path depending on whether the resource is loaded from JS or HTML. +var newScript = document.createElement("script"); +newScript.id = "badscript2"; +newScript.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"; +newScript.addEventListener("load", function onload() { scriptItem2 = "spoiled"; }); +document.body.appendChild(newScript); + +// / Try loading from a tracking image URI (2) +var newImage = document.createElement("img"); +newImage.id = "badimage2"; +newImage.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg"; +newImage.addEventListener("load", function onload() { imageItem2 = "spoiled"; }); +document.body.appendChild(newImage); + +// Try loading from a tracking iframe URI (2) +var newFrame = document.createElement("iframe"); +newFrame.id = "badframe2"; +newFrame.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html"; +newFrame.addEventListener("load", function onload() { frameItem2 = "spoiled"; }); +document.body.appendChild(newFrame); + +// Try doing an XHR against a tracking domain (bug 1216793) +function reqListener() { + xhrItem = "loaded"; + loaded("xhr"); +} +function transferFailed() { + xhrItem = "failed"; + loaded("xhr"); +} +function transferCanceled() { + xhrItem = "canceled"; + loaded("xhr"); +} +var oReq = new XMLHttpRequest(); +oReq.addEventListener("load", reqListener); +oReq.addEventListener("error", transferFailed); +oReq.addEventListener("abort", transferCanceled); +oReq.open("GET", "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"); +oReq.send(); + +// Fetch from a tracking domain +fetch("http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js").then(function(response) { + if (response.ok) { + fetchItem = "loaded"; + loaded("fetch"); + } else { + fetchItem = "badresponse"; + loaded("fetch"); + } + }).catch(function(error) { + fetchItem = "error"; + loaded("fetch"); +}); +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/bad.css b/toolkit/components/url-classifier/tests/mochitest/bad.css new file mode 100644 index 0000000000..f57b36a778 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bad.css @@ -0,0 +1 @@ +#styleBad { visibility: hidden; } diff --git a/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ b/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/basic.vtt b/toolkit/components/url-classifier/tests/mochitest/basic.vtt new file mode 100644 index 0000000000..7781790d04 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/basic.vtt @@ -0,0 +1,27 @@ +WEBVTT +Region: id=testOne lines=2 width=30% +Region: id=testTwo lines=4 width=20% + +1 +00:00.500 --> 00:00.700 region:testOne +This + +2 +00:01.200 --> 00:02.400 region:testTwo +Is + +2.5 +00:02.000 --> 00:03.500 region:testOne +(Over here?!) + +3 +00:02.710 --> 00:02.910 +A + +4 +00:03.217 --> 00:03.989 +Test + +5 +00:03.217 --> 00:03.989 +And more! diff --git a/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ b/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ new file mode 100644 index 0000000000..23de552c1a --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: *
\ No newline at end of file diff --git a/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html b/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html new file mode 100644 index 0000000000..80124da3cc --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html @@ -0,0 +1,11 @@ +<html> +<head> +<title></title> +</head> +<body> + +<!-- Try loading from a malware javascript URI --> +<script id="badscript" data-touched="not sure" src="http://bug1281083.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script> + +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/bug_1580416.html b/toolkit/components/url-classifier/tests/mochitest/bug_1580416.html new file mode 100644 index 0000000000..ae305bbb47 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bug_1580416.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> +<title></title> +</head> +<body> + +<script id="goodscript" data-touched="not sure" src="http://mochitest.apps.fbsbx.com/tests/toolkit/components/url-classifier/tests/mochitest/good.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script> + +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/cache.sjs b/toolkit/components/url-classifier/tests/mochitest/cache.sjs new file mode 100644 index 0000000000..84fb0e9089 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/cache.sjs @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function handleRequest(request, response) { + var query = {}; + request.queryString.split("&").forEach(function (val) { + var idx = val.indexOf("="); + query[val.slice(0, idx)] = unescape(val.slice(idx + 1)); + }); + + var responseBody; + + // Store fullhash in the server side. + if ("list" in query && "fullhash" in query) { + // In the server side we will store: + // 1. All the full hashes for a given list + // 2. All the lists we have right now + // data is separate by '\n' + let list = query.list; + let hashes = getState(list); + + let hash = atob(query.fullhash); + hashes += hash + "\n"; + setState(list, hashes); + + let lists = getState("lists"); + if (!lists.includes(list)) { + lists += list + "\n"; + setState("lists", lists); + } + + return; + // gethash count return how many gethash request received. + // This is used by client to know if a gethash request is triggered by gecko + } else if ("gethashcount" == request.queryString) { + let counter = getState("counter"); + responseBody = counter == "" ? "0" : counter; + } else { + let body = new BinaryInputStream(request.bodyInputStream); + let avail; + let bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + + let counter = getState("counter"); + counter = counter == "" ? "1" : (parseInt(counter) + 1).toString(); + setState("counter", counter); + + responseBody = parseV2Request(bytes); + } + + response.setHeader("Content-Type", "text/plain", false); + response.write(responseBody); +} + +function parseV2Request(bytes) { + var request = String.fromCharCode.apply(this, bytes); + var [HEADER, PREFIXES] = request.split("\n"); + var [PREFIXSIZE, LENGTH] = HEADER.split(":").map(val => { + return parseInt(val); + }); + + var ret = ""; + for (var start = 0; start < LENGTH; start += PREFIXSIZE) { + getState("lists") + .split("\n") + .forEach(function (list) { + var completions = getState(list).split("\n"); + + for (var completion of completions) { + if (completion.indexOf(PREFIXES.substr(start, PREFIXSIZE)) == 0) { + ret += list + ":1:32\n"; + ret += completion; + } + } + }); + } + + return ret; +} diff --git a/toolkit/components/url-classifier/tests/mochitest/chrome.toml b/toolkit/components/url-classifier/tests/mochitest/chrome.toml new file mode 100644 index 0000000000..cbd0e475e3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/chrome.toml @@ -0,0 +1,83 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "allowlistAnnotatedFrame.html", + "classifiedAnnotatedFrame.html", + "classifiedAnnotatedPBFrame.html", + "trackingRequest.html", + "bug_1281083.html", + "bug_1580416.html", + "report.sjs", + "gethash.sjs", + "classifierCommon.js", + "classifierHelper.js", + "head.js", + "threathit.sjs", + "redirect_tracker.sjs", + "!/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html", + "!/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js", + "!/toolkit/components/url-classifier/tests/mochitest/good.js", + "!/toolkit/components/url-classifier/tests/mochitest/evil.css", + "!/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^", + "!/toolkit/components/url-classifier/tests/mochitest/evil.js", + "!/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^", + "!/toolkit/components/url-classifier/tests/mochitest/evilWorker.js", + "!/toolkit/components/url-classifier/tests/mochitest/import.css", + "!/toolkit/components/url-classifier/tests/mochitest/raptor.jpg", + "!/toolkit/components/url-classifier/tests/mochitest/track.html", + "!/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js", + "!/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js^headers^", + "!/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js", + "!/toolkit/components/url-classifier/tests/mochitest/vp9.webm", + "!/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html", + "!/toolkit/components/url-classifier/tests/mochitest/workerFrame.html", + "!/toolkit/components/url-classifier/tests/mochitest/ping.sjs", + "!/toolkit/components/url-classifier/tests/mochitest/basic.vtt", + "!/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^", + "!/toolkit/components/url-classifier/tests/mochitest/dnt.html", + "!/toolkit/components/url-classifier/tests/mochitest/dnt.sjs", + "!/toolkit/components/url-classifier/tests/mochitest/update.sjs", + "!/toolkit/components/url-classifier/tests/mochitest/bad.css", + "!/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^", + "!/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html", + "!/toolkit/components/url-classifier/tests/mochitest/seek.webm", + "!/toolkit/components/url-classifier/tests/mochitest/cache.sjs", +] + +["test_advisory_link.html"] + +["test_allowlisted_annotations.html"] +tags = "trackingprotection" + +["test_classified_annotations.html"] +tags = "trackingprotection" +skip-if = ["os == 'linux' && asan"] # Bug 1202548 + +["test_classifier_changetablepref.html"] +skip-if = ["verify"] + +["test_classifier_changetablepref_bug1395411.html"] + +["test_donottrack.html"] + +["test_privatebrowsing_trackingprotection.html"] +tags = "trackingprotection" + +["test_reporturl.html"] +skip-if = ["verify"] + +["test_safebrowsing_bug1272239.html"] + +["test_threathit_report.html"] +skip-if = ["verify"] + +["test_trackingprotection_bug1157081.html"] +tags = "trackingprotection" + +["test_trackingprotection_bug1312515.html"] + +["test_trackingprotection_bug1580416.html"] +tags = "trackingprotection" + +["test_trackingprotection_whitelist.html"] +tags = "trackingprotection" diff --git a/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html new file mode 100644 index 0000000000..9b12f529cb --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html @@ -0,0 +1,154 @@ +<html> +<head> +<title></title> + +<script type="text/javascript"> +"use strict"; + +var scriptItem = "untouched"; +var scriptItem1 = "untouched"; +var scriptItem2 = "untouched"; +var imageItem1 = "untouched"; +var imageItem2 = "untouched"; +var frameItem1 = "untouched"; +var frameItem2 = "untouched"; +var xhrItem = "untouched"; +var fetchItem = "untouched"; +var mediaItem1 = "untouched"; + +var badids = [ + "badscript1", + "badscript2", + "badimage1", + "badimage2", + "badframe1", + "badframe2", + "badmedia1", + "badcss", +]; + +var onloadCalled = false; +var xhrFinished = false; +var fetchFinished = false; +var videoLoaded = false; +function loaded(type) { + if (type === "onload") { + onloadCalled = true; + } else if (type === "xhr") { + xhrFinished = true; + } else if (type === "fetch") { + fetchFinished = true; + } else if (type === "video") { + videoLoaded = true; + } + if (onloadCalled && xhrFinished && fetchFinished && videoLoaded) { + var msg = new window.CustomEvent("OnLoadComplete", { + detail: JSON.stringify({ + scriptItem, + scriptItem1, + scriptItem2, + imageItem1, + imageItem2, + frameItem1, + frameItem2, + xhrItem, + fetchItem, + mediaItem1, + }), + }); + window.dispatchEvent(msg); + } +} +</script> + +<!-- Try loading from a tracking CSS URI --> +<link id="badcss" rel="stylesheet" type="text/css" href="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link> + +</head> + +<body onload="loaded('onload')"> + +<!-- Try loading from a tracking script URI (1): evil.js onload will have updated the scriptItem variable --> +<script id="badscript1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="scriptItem1 = scriptItem;"></script> + +<!-- Try loading from a tracking image URI (1) --> +<img id="badimage1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?reload=true" onload="imageItem1 = 'spoiled';"/> + +<!-- Try loading from a tracking frame URI (1) --> +<iframe id="badframe1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html" onload="frameItem1 = 'spoiled';"></iframe> + +<!-- Try loading from a tracking video URI --> +<video id="badmedia1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/vp9.webm?reload=true"></video> + +<script> +var v = document.getElementById("badmedia1"); +v.addEventListener("loadedmetadata", function() { + mediaItem1 = "loaded"; + loaded("video"); +}, true); +v.addEventListener("error", function() { + mediaItem1 = "error"; + loaded("video"); +}, true); + +// Try loading from a tracking script URI (2) - The loader may follow a different path depending on whether the resource is loaded from JS or HTML. +var newScript = document.createElement("script"); +newScript.id = "badscript2"; +newScript.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"; +newScript.addEventListener("load", function() { scriptItem2 = scriptItem; }); +document.body.appendChild(newScript); + +// Try loading from a tracking image URI (2) +var newImage = document.createElement("img"); +newImage.id = "badimage2"; +newImage.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?reload=true"; +newImage.addEventListener("load", function() { imageItem2 = "spoiled"; }); +document.body.appendChild(newImage); + +// Try loading from a tracking iframe URI (2) +var newFrame = document.createElement("iframe"); +newFrame.id = "badframe2"; +newFrame.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html"; +newFrame.addEventListener("load", function() { frameItem2 = "spoiled"; }); +document.body.appendChild(newFrame); + +// Try doing an XHR against a tracking domain (bug 1216793) +function reqListener() { + xhrItem = "loaded"; + loaded("xhr"); +} +function transferFailed() { + xhrItem = "failed"; + loaded("xhr"); +} +function transferCanceled() { + xhrItem = "canceled"; + loaded("xhr"); +} +var oReq = new XMLHttpRequest(); +oReq.addEventListener("load", reqListener); +oReq.addEventListener("error", transferFailed); +oReq.addEventListener("abort", transferCanceled); +oReq.open("GET", "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"); +oReq.send(); + +// Fetch from a tracking domain +fetch("http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js").then(function(response) { + if (response.ok) { + fetchItem = "loaded"; + loaded("fetch"); + } else { + fetchItem = "badresponse"; + loaded("fetch"); + } + }).catch(function(error) { + fetchItem = "error"; + loaded("fetch"); +}); +</script> + +The following should not be hidden: +<div id="styleCheck">STYLE TEST</div> + +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html new file mode 100644 index 0000000000..ecd89e91d6 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> +<title></title> + +<link id="badcss" rel="stylesheet" type="text/css" href="https://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link> + +</head> +<body> + +<script id="badscript" data-touched="not sure" src="https://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script> + +<script id="goodscript" data-touched="not sure" src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/good.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script> + +<!-- The image cache can cache JS handlers, so make sure we use a different URL for raptor.jpg each time --> +<img id="badimage" data-touched="not sure" src="https://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?pbmode=test" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"/> + +<img id="goodimage" data-touched="not sure" src="https://tracking.example.org/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?pbmode=test2" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"/> + +The following should not be hidden: +<div id="styleCheck">STYLE TEST</div> + +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js new file mode 100644 index 0000000000..ca288986e3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIUrlClassifierDBService +); +var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"].getService( + Ci.nsIUrlListManager +); + +var timer; +function setTimeout(callback, delay) { + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + { notify: callback }, + delay, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} + +function doUpdate(update) { + let listener = { + QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierUpdateObserver"]), + updateUrlRequested(url) {}, + streamFinished(status) {}, + updateError(errorCode) { + sendAsyncMessage("updateError", errorCode); + }, + updateSuccess(requestedTimeout) { + sendAsyncMessage("updateSuccess"); + }, + }; + + try { + dbService.beginUpdate( + listener, + "test-malware-simple,test-unwanted-simple", + "" + ); + dbService.beginStream("", ""); + dbService.updateStream(update); + dbService.finishStream(); + dbService.finishUpdate(); + } catch (e) { + // beginUpdate may fail if there's an existing update in progress + // retry until success or testcase timeout. + setTimeout(() => { + doUpdate(update); + }, 1000); + } +} + +function doReload() { + try { + dbService.reloadDatabase(); + sendAsyncMessage("reloadSuccess"); + } catch (e) { + setTimeout(() => { + doReload(); + }, 1000); + } +} + +// SafeBrowsing.jsm is initialized after mozEntries are added. Add observer +// to receive "finished" event. For the case when this function is called +// after the event had already been notified, we lookup entries to see if +// they are already added to database. +function waitForInit() { + if (listmanager.isRegistered()) { + sendAsyncMessage("safeBrowsingInited"); + } else { + setTimeout(() => { + waitForInit(); + }, 1000); + } +} + +function doGetTables() { + const callback = tables => { + sendAsyncMessage("GetTableSuccess", tables); + }; + + try { + dbService.getTables(callback); + } catch (e) { + setTimeout(() => { + doGetTables(); + }, 1000); + } +} + +addMessageListener("doUpdate", ({ testUpdate }) => { + doUpdate(testUpdate); +}); + +addMessageListener("doReload", () => { + doReload(); +}); + +addMessageListener("waitForInit", () => { + waitForInit(); +}); + +addMessageListener("doGetTables", () => { + doGetTables(); +}); diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html new file mode 100644 index 0000000000..1e3617b9d8 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html @@ -0,0 +1,57 @@ +<html> +<head> +<title></title> + +<script type="text/javascript"> + +var scriptItem = "untouched"; + +function checkLoads() { + // Make sure the javascript did not load. + window.parent.is(scriptItem, "untouched", "Should not load bad javascript"); + + // Make sure the css did not load. + var elt = document.getElementById("styleCheck"); + var style = document.defaultView.getComputedStyle(elt); + window.parent.isnot(style.visibility, "hidden", "Should not load bad css"); + + elt = document.getElementById("styleBad"); + style = document.defaultView.getComputedStyle(elt); + window.parent.isnot(style.visibility, "hidden", "Should not load bad css"); + + elt = document.getElementById("styleImport"); + style = document.defaultView.getComputedStyle(elt); + window.parent.isnot(style.visibility, "visible", "Should import clean css"); + + // Call parent.loadTestFrame again to test classification metadata in HTTP + // cache entries. + if (window.parent.firstLoad) { + window.parent.info("Reloading from cache..."); + window.parent.firstLoad = false; + window.parent.loadTestFrame(); + return; + } + + // End (parent) test. + window.parent.SimpleTest.finish(); +} + +</script> + +<!-- Try loading from a malware javascript URI --> +<script type="text/javascript" src="http://malware.mochi.test/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script> + +<!-- Try loading from an uwanted software css URI --> +<link rel="stylesheet" type="text/css" href="http://unwanted.mochi.test/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link> + +<!-- Try loading a marked-as-malware css through an @import from a clean URI --> +<link rel="stylesheet" type="text/css" href="import2.css"></link> +</head> + +<body onload="checkLoads()"> +The following should not be hidden: +<div id="styleCheck">STYLE TEST</div> +<div id="styleBad">STYLE BAD</div> +<div id="styleImport">STYLE IMPORT</div> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js new file mode 100644 index 0000000000..157e5d54f7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js @@ -0,0 +1,204 @@ +if (typeof classifierHelper == "undefined") { + var classifierHelper = {}; +} + +const CLASSIFIER_COMMON_URL = SimpleTest.getTestFileURL("classifierCommon.js"); +var gScript = SpecialPowers.loadChromeScript(CLASSIFIER_COMMON_URL); +var gOriginalGetHashURL; + +const PREFS = { + PROVIDER_LISTS: "browser.safebrowsing.provider.mozilla.lists", + DISALLOW_COMPLETIONS: "urlclassifier.disallow_completions", + PROVIDER_GETHASHURL: "browser.safebrowsing.provider.mozilla.gethashURL", +}; + +classifierHelper._curAddChunkNum = 1; + +// addUrlToDB is asynchronous, queue the task to ensure +// the callback follow correct order. +classifierHelper._updates = []; + +// Keep urls added to database, those urls should be automatically +// removed after test complete. +classifierHelper._updatesToCleanup = []; + +classifierHelper._initsCB = []; + +// This function return a Promise, promise is resolved when SafeBrowsing.jsm +// is initialized. +classifierHelper.waitForInit = function () { + return new Promise(function (resolve, reject) { + classifierHelper._initsCB.push(resolve); + gScript.sendAsyncMessage("waitForInit"); + }); +}; + +// This function is used to allow completion for specific "list", +// some lists like "test-malware-simple" is default disabled to ask for complete. +// "list" is the db we would like to allow it +// "url" is the completion server +classifierHelper.allowCompletion = async function (lists, url) { + for (var list of lists) { + // Add test db to provider + var pref = await SpecialPowers.getParentCharPref(PREFS.PROVIDER_LISTS); + pref += "," + list; + await SpecialPowers.setCharPref(PREFS.PROVIDER_LISTS, pref); + + // Rename test db so we will not disallow it from completions + pref = await SpecialPowers.getParentCharPref(PREFS.DISALLOW_COMPLETIONS); + pref = pref.replace(list, list + "-backup"); + await SpecialPowers.setCharPref(PREFS.DISALLOW_COMPLETIONS, pref); + } + + // Store the original get hash URL in order to reset it back during clean up. + gOriginalGetHashURL = SpecialPowers.getCharPref(PREFS.PROVIDER_GETHASHURL); + + // Set get hash url + await SpecialPowers.setCharPref(PREFS.PROVIDER_GETHASHURL, url); +}; + +// Pass { url: ..., db: ... } to add url to database, +// onsuccess/onerror will be called when update complete. +classifierHelper.addUrlToDB = function (updateData) { + return new Promise(function (resolve, reject) { + var testUpdate = ""; + for (var update of updateData) { + var LISTNAME = update.db; + var CHUNKDATA = update.url; + var CHUNKLEN = CHUNKDATA.length; + var HASHLEN = update.len ? update.len : 32; + + update.addChunk = classifierHelper._curAddChunkNum; + classifierHelper._curAddChunkNum += 1; + + classifierHelper._updatesToCleanup.push(update); + testUpdate += + "n:1000\n" + + "i:" + + LISTNAME + + "\n" + + "ad:1\n" + + "a:" + + update.addChunk + + ":" + + HASHLEN + + ":" + + CHUNKLEN + + "\n" + + CHUNKDATA; + } + + classifierHelper._update(testUpdate, resolve, reject); + }); +}; + +// This API is used to expire all add/sub chunks we have updated +// by using addUrlToDB. +classifierHelper.resetDatabase = function () { + function removeDatabase() { + return new Promise(function (resolve, reject) { + var testUpdate = ""; + for (var update of classifierHelper._updatesToCleanup) { + testUpdate += + "n:1000\ni:" + update.db + "\nad:" + update.addChunk + "\n"; + } + + classifierHelper._update(testUpdate, resolve, reject); + }); + } + + // Remove and then reload will ensure both database and cache will + // be cleared. + return Promise.resolve() + .then(removeDatabase) + .then(classifierHelper.reloadDatabase); +}; + +classifierHelper.reloadDatabase = function () { + return new Promise(function (resolve, reject) { + gScript.addMessageListener("reloadSuccess", function handler() { + gScript.removeMessageListener("reloadSuccess", handler); + resolve(); + }); + + gScript.sendAsyncMessage("doReload"); + }); +}; + +classifierHelper.getTables = function () { + return new Promise(resolve => { + gScript.addMessageListener("GetTableSuccess", function handler(tables) { + gScript.removeMessageListener("GetTableSuccess", handler); + resolve(tables); + }); + + gScript.sendAsyncMessage("doGetTables"); + }); +}; + +classifierHelper._update = function (testUpdate, onsuccess, onerror) { + // Queue the task if there is still an on-going update + classifierHelper._updates.push({ + data: testUpdate, + onsuccess, + onerror, + }); + if (classifierHelper._updates.length != 1) { + return; + } + + gScript.sendAsyncMessage("doUpdate", { testUpdate }); +}; + +classifierHelper._updateSuccess = function () { + var update = classifierHelper._updates.shift(); + update.onsuccess(); + + if (classifierHelper._updates.length) { + var testUpdate = classifierHelper._updates[0].data; + gScript.sendAsyncMessage("doUpdate", { testUpdate }); + } +}; + +classifierHelper._updateError = function (errorCode) { + var update = classifierHelper._updates.shift(); + update.onerror(errorCode); + + if (classifierHelper._updates.length) { + var testUpdate = classifierHelper._updates[0].data; + gScript.sendAsyncMessage("doUpdate", { testUpdate }); + } +}; + +classifierHelper._inited = function () { + classifierHelper._initsCB.forEach(function (cb) { + cb(); + }); + classifierHelper._initsCB = []; +}; + +classifierHelper._setup = function () { + gScript.addMessageListener("updateSuccess", classifierHelper._updateSuccess); + gScript.addMessageListener("updateError", classifierHelper._updateError); + gScript.addMessageListener("safeBrowsingInited", classifierHelper._inited); + + // cleanup will be called at end of each testcase to remove all the urls added to database. + SimpleTest.registerCleanupFunction(classifierHelper._cleanup); +}; + +classifierHelper._cleanup = function () { + // clean all the preferences may touch by helper + Object.values(PREFS).map(pref => SpecialPowers.clearUserPref(pref)); + + // Set the getHashURL back, the original value isn't the same as the default + // pref value. + SpecialPowers.setCharPref(PREFS.PROVIDER_GETHASHURL, gOriginalGetHashURL); + + if (!classifierHelper._updatesToCleanup) { + return Promise.resolve(); + } + + return classifierHelper.resetDatabase(); +}; + +classifierHelper._setup(); diff --git a/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js b/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js new file mode 100644 index 0000000000..e7361119b6 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js @@ -0,0 +1,12 @@ +/* eslint-env worker */ + +onmessage = function () { + try { + importScripts("evilWorker.js"); + } catch (ex) { + postMessage("success"); + return; + } + + postMessage("failure"); +}; diff --git a/toolkit/components/url-classifier/tests/mochitest/dnt.html b/toolkit/components/url-classifier/tests/mochitest/dnt.html new file mode 100644 index 0000000000..2246263688 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/dnt.html @@ -0,0 +1,31 @@ +<html> +<head> +<title></title> + +<script type="text/javascript"> + +function makeXHR(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.onload = function() { + callback(xhr.response); + }; + xhr.send(); +} + +function loaded(type) { + window.parent.postMessage("navigator.doNotTrack=" + navigator.doNotTrack, "*"); + + makeXHR("dnt.sjs", (res) => { + window.parent.postMessage("DNT=" + res, "*"); + window.parent.postMessage("finish", "*"); + }); +} + +</script> +</head> + +<body onload="loaded('onload')"> +</body> + +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/dnt.sjs b/toolkit/components/url-classifier/tests/mochitest/dnt.sjs new file mode 100644 index 0000000000..bbb836482a --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/dnt.sjs @@ -0,0 +1,9 @@ +function handleRequest(request, response) { + var dnt = "unspecified"; + if (request.hasHeader("DNT")) { + dnt = "1"; + } + + response.setHeader("Content-Type", "text/plain", false); + response.write(dnt); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.css b/toolkit/components/url-classifier/tests/mochitest/evil.css new file mode 100644 index 0000000000..62d506c899 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.css @@ -0,0 +1 @@ +#styleCheck { visibility: hidden; } diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ b/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.js b/toolkit/components/url-classifier/tests/mochitest/evil.js new file mode 100644 index 0000000000..3e8b165587 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.js @@ -0,0 +1,3 @@ +/* global scriptItem:true */ + +scriptItem = "loaded malware javascript!"; diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ b/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ new file mode 100644 index 0000000000..3eced96143 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ @@ -0,0 +1,2 @@ +Access-Control-Allow-Origin: * +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/evilWorker.js b/toolkit/components/url-classifier/tests/mochitest/evilWorker.js new file mode 100644 index 0000000000..b4e8a47602 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evilWorker.js @@ -0,0 +1,5 @@ +/* eslint-env worker */ + +onmessage = function () { + postMessage("loaded bad file"); +}; diff --git a/toolkit/components/url-classifier/tests/mochitest/features.js b/toolkit/components/url-classifier/tests/mochitest/features.js new file mode 100644 index 0000000000..0004693d81 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/features.js @@ -0,0 +1,291 @@ +var tests = [ + // Config is an array with 4 elements: + // - annotation blocklist + // - annotation entitylist + // - tracking blocklist + // - tracking entitylist + + // All disabled. + { + config: [false, false, false, false], + loadExpected: true, + annotationExpected: false, + }, + + // Just entitylisted. + { + config: [false, false, false, true], + loadExpected: true, + annotationExpected: false, + }, + + // Just blocklisted. + { + config: [false, false, true, false], + loadExpected: false, + annotationExpected: false, + }, + + // entitylist + blocklist => entitylist wins + { + config: [false, false, true, true], + loadExpected: true, + annotationExpected: false, + }, + + // just annotated in entitylist. + { + config: [false, true, false, false], + loadExpected: true, + annotationExpected: false, + }, + + // TP and annotation entitylisted. + { + config: [false, true, false, true], + loadExpected: true, + annotationExpected: false, + }, + + // Annotation entitylisted, but TP blocklisted. + { + config: [false, true, true, false], + loadExpected: false, + annotationExpected: false, + }, + + // Annotation entitylisted. TP blocklisted and entitylisted: entitylist wins. + { + config: [false, true, true, true], + loadExpected: true, + annotationExpected: false, + }, + + // Just blocklist annotated. + { + config: [true, false, false, false], + loadExpected: true, + annotationExpected: true, + }, + + // annotated but TP entitylisted. + { + config: [true, false, false, true], + loadExpected: true, + annotationExpected: true, + }, + + // annotated and blocklisted. + { + config: [true, false, true, false], + loadExpected: false, + annotationExpected: false, + }, + + // annotated, TP blocklisted and entitylisted: entitylist wins. + { + config: [true, false, true, true], + loadExpected: true, + annotationExpected: true, + }, + + // annotated in white and blocklist. + { + config: [true, true, false, false], + loadExpected: true, + annotationExpected: false, + }, + + // annotated in white and blocklist. TP Whiteslited + { + config: [true, true, false, true], + loadExpected: true, + annotationExpected: false, + }, + + // everywhere. TP entitylist wins. + { + config: [true, true, true, true], + loadExpected: true, + annotationExpected: false, + }, +]; + +function prefBlacklistValue(value) { + return value ? "example.com" : ""; +} + +function prefWhitelistValue(value) { + return value ? "mochi.test,mochi.xorigin-test" : ""; +} + +async function runTest(test, expectedFlag, expectedTrackingResource, prefs) { + let config = [ + [ + "urlclassifier.trackingAnnotationTable.testEntries", + prefBlacklistValue(test.config[0]), + ], + [ + "urlclassifier.features.fingerprinting.annotate.blacklistHosts", + prefBlacklistValue(test.config[0]), + ], + [ + "urlclassifier.features.cryptomining.annotate.blacklistHosts", + prefBlacklistValue(test.config[0]), + ], + [ + "urlclassifier.features.socialtracking.annotate.blacklistHosts", + prefBlacklistValue(test.config[0]), + ], + + [ + "urlclassifier.trackingAnnotationWhitelistTable.testEntries", + prefWhitelistValue(test.config[1]), + ], + [ + "urlclassifier.features.fingerprinting.annotate.whitelistHosts", + prefWhitelistValue(test.config[1]), + ], + [ + "urlclassifier.features.cryptomining.annotate.whitelistHosts", + prefWhitelistValue(test.config[1]), + ], + [ + "urlclassifier.features.socialtracking.annotate.whitelistHosts", + prefWhitelistValue(test.config[1]), + ], + + [ + "urlclassifier.trackingTable.testEntries", + prefBlacklistValue(test.config[2]), + ], + [ + "urlclassifier.features.fingerprinting.blacklistHosts", + prefBlacklistValue(test.config[2]), + ], + [ + "urlclassifier.features.cryptomining.blacklistHosts", + prefBlacklistValue(test.config[2]), + ], + [ + "urlclassifier.features.socialtracking.blacklistHosts", + prefBlacklistValue(test.config[2]), + ], + + [ + "urlclassifier.trackingWhitelistTable.testEntries", + prefWhitelistValue(test.config[3]), + ], + [ + "urlclassifier.features.fingerprinting.whitelistHosts", + prefWhitelistValue(test.config[3]), + ], + [ + "urlclassifier.features.cryptomining.whitelistHosts", + prefWhitelistValue(test.config[3]), + ], + [ + "urlclassifier.features.socialtracking.whitelistHosts", + prefWhitelistValue(test.config[3]), + ], + ]; + + info("Testing: " + JSON.stringify(config) + "\n"); + + await SpecialPowers.pushPrefEnv({ set: config.concat(prefs) }); + + // This promise will be resolved when the chromeScript knows if the channel + // is annotated or not. + let annotationPromise; + if (test.loadExpected) { + info("We want to have annotation information"); + annotationPromise = new Promise(resolve => { + chromeScript.addMessageListener( + "last-channel-flags", + data => resolve(data), + { once: true } + ); + }); + } + + // Let's load a script with a random query string to avoid network cache. + // Using a script as the fingerprinting feature does not block display content + let result = await new Promise(resolve => { + let script = document.createElement("script"); + script.setAttribute( + "src", + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js?" + + Math.random() + ); + script.onload = _ => resolve(true); + script.onerror = _ => resolve(false); + document.body.appendChild(script); + }); + + is(result, test.loadExpected, "The loading happened correctly"); + + if (annotationPromise) { + let data = await annotationPromise; + is( + !!data.classificationFlags, + test.annotationExpected, + "The annotation happened correctly" + ); + if (test.annotationExpected) { + is(data.classificationFlags, expectedFlag, "Correct flag"); + is( + data.isThirdPartyTrackingResource, + expectedTrackingResource, + "Tracking resource flag matches" + ); + } + } +} + +var chromeScript; + +function runTests(flag, prefs, trackingResource) { + chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let classifiedChannel = subject.QueryInterface(Ci.nsIClassifiedChannel); + if ( + !channel || + !classifiedChannel || + !channel.URI.spec.startsWith( + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" + ) + ) { + return; + } + + sendAsyncMessage("last-channel-flags", { + classificationFlags: classifiedChannel.classificationFlags, + isThirdPartyTrackingResource: + classifiedChannel.isThirdPartyTrackingResource(), + }); + } + + addMessageListener("done", __ => { + Services.obs.removeObserver(onExamResp, "http-on-examine-response"); + }); + + Services.obs.addObserver(onExamResp, "http-on-examine-response"); + + sendAsyncMessage("start-test"); + }); + + chromeScript.addMessageListener( + "start-test", + async _ => { + for (let test in tests) { + await runTest(tests[test], flag, trackingResource, prefs); + } + + chromeScript.sendAsyncMessage("done"); + SimpleTest.finish(); + }, + { once: true } + ); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/gethash.sjs b/toolkit/components/url-classifier/tests/mochitest/gethash.sjs new file mode 100644 index 0000000000..12e8c11835 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/gethash.sjs @@ -0,0 +1,86 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function handleRequest(request, response) { + var query = {}; + request.queryString.split("&").forEach(function (val) { + var idx = val.indexOf("="); + query[val.slice(0, idx)] = unescape(val.slice(idx + 1)); + }); + + var responseBody; + + // Store fullhash in the server side. + if ("list" in query && "fullhash" in query) { + // In the server side we will store: + // 1. All the full hashes for a given list + // 2. All the lists we have right now + // data is separate by '\n' + let list = query.list; + let hashes = getState(list); + + let hash = atob(query.fullhash); + hashes += hash + "\n"; + setState(list, hashes); + + let lists = getState("lists"); + if (!lists.includes(list)) { + lists += list + "\n"; + setState("lists", lists); + } + + return; + // gethash count return how many gethash request received. + // This is used by client to know if a gethash request is triggered by gecko + } else if ("gethashcount" == request.queryString) { + let counter = getState("counter"); + responseBody = counter == "" ? "0" : counter; + } else { + let body = new BinaryInputStream(request.bodyInputStream); + let avail; + let bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + + let counter = getState("counter"); + counter = counter == "" ? "1" : (parseInt(counter) + 1).toString(); + setState("counter", counter); + + responseBody = parseV2Request(bytes); + } + + response.setHeader("Content-Type", "text/plain", false); + response.write(responseBody); +} + +function parseV2Request(bytes) { + var request = String.fromCharCode.apply(this, bytes); + var [HEADER, PREFIXES] = request.split("\n"); + var [PREFIXSIZE, LENGTH] = HEADER.split(":").map(val => { + return parseInt(val); + }); + + var ret = ""; + for (var start = 0; start < LENGTH; start += PREFIXSIZE) { + getState("lists") + .split("\n") + .forEach(function (list) { + var completions = getState(list).split("\n"); + + for (var completion of completions) { + if (completion.indexOf(PREFIXES.substr(start, PREFIXSIZE)) == 0) { + ret += list + ":1:32\n"; + ret += completion; + } + } + }); + } + + return ret; +} diff --git a/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html b/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html new file mode 100644 index 0000000000..4f518dabe0 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html @@ -0,0 +1,61 @@ +<html> +<head> +<title></title> + +<script type="text/javascript"> + +var scriptItem = "untouched"; + +function checkLoads() { + var title = document.getElementById("title"); + title.textContent = window.parent.shouldLoad ? + "The following should be hidden:" : + "The following should not be hidden:"; + + if (window.parent.shouldLoad) { + window.parent.is(scriptItem, "loaded malware javascript!", "Should load bad javascript"); + } else { + window.parent.is(scriptItem, "untouched", "Should not load bad javascript"); + } + + var elt = document.getElementById("styleImport"); + var style = document.defaultView.getComputedStyle(elt); + window.parent.isnot(style.visibility, "visible", "Should load clean css"); + + // Make sure the css did not load. + elt = document.getElementById("styleCheck"); + style = document.defaultView.getComputedStyle(elt); + if (window.parent.shouldLoad) { + window.parent.isnot(style.visibility, "visible", "Should load bad css"); + } else { + window.parent.isnot(style.visibility, "hidden", "Should not load bad css"); + } + + elt = document.getElementById("styleBad"); + style = document.defaultView.getComputedStyle(elt); + if (window.parent.shouldLoad) { + window.parent.isnot(style.visibility, "visible", "Should import bad css"); + } else { + window.parent.isnot(style.visibility, "hidden", "Should not import bad css"); + } +} + +</script> + +<!-- Try loading from a malware javascript URI --> +<script type="text/javascript" src="http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script> + +<!-- Try loading from an uwanted software css URI --> +<link rel="stylesheet" type="text/css" href="http://unwanted.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link> + +<!-- Try loading a marked-as-malware css through an @import from a clean URI --> +<link rel="stylesheet" type="text/css" href="import.css"></link> +</head> + +<body onload="checkLoads()"> +<div id="title"></div> +<div id="styleCheck">STYLE EVIL</div> +<div id="styleBad">STYLE BAD</div> +<div id="styleImport">STYLE IMPORT</div> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/good.js b/toolkit/components/url-classifier/tests/mochitest/good.js new file mode 100644 index 0000000000..a3c8fcb0c8 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/good.js @@ -0,0 +1,3 @@ +/* global scriptItem:true */ + +scriptItem = "loaded whitelisted javascript!"; diff --git a/toolkit/components/url-classifier/tests/mochitest/head.js b/toolkit/components/url-classifier/tests/mochitest/head.js new file mode 100644 index 0000000000..dca6abe279 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/head.js @@ -0,0 +1,40 @@ +// calculate the fullhash and send it to gethash server +function addCompletionToServer(list, url, mochitestUrl) { + return new Promise(function (resolve, reject) { + var listParam = "list=" + list; + var fullhashParam = "fullhash=" + hash(url); + + var xhr = new XMLHttpRequest(); + xhr.open("PUT", mochitestUrl + "?" + listParam + "&" + fullhashParam, true); + xhr.setRequestHeader("Content-Type", "text/plain"); + xhr.onreadystatechange = function () { + if (this.readyState == this.DONE) { + resolve(); + } + }; + xhr.send(); + }); +} + +function hash(str) { + var hasher = SpecialPowers.Cc["@mozilla.org/security/hash;1"].createInstance( + SpecialPowers.Ci.nsICryptoHash + ); + + var data = new TextEncoder().encode(str); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + + return hasher.finish(true); +} + +var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({ set: p }); + +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + setTimeout(aCallback, 0); + } + }, "browser-delayed-startup-finished"); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/import.css b/toolkit/components/url-classifier/tests/mochitest/import.css new file mode 100644 index 0000000000..9b86c82169 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/import.css @@ -0,0 +1,3 @@ +/* malware.example.com is in the malware database. */ +@import url("http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/bad.css"); +#styleImport { visibility: hidden; } diff --git a/toolkit/components/url-classifier/tests/mochitest/import2.css b/toolkit/components/url-classifier/tests/mochitest/import2.css new file mode 100644 index 0000000000..55de698e0c --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/import2.css @@ -0,0 +1,3 @@ +/* malware.mochi.test is in the malware database. */ +@import url("http://malware.mochi.test/tests/toolkit/components/url-classifier/tests/mochitest/bad.css"); +#styleImport { visibility: hidden; } diff --git a/toolkit/components/url-classifier/tests/mochitest/mochitest.toml b/toolkit/components/url-classifier/tests/mochitest/mochitest.toml new file mode 100644 index 0000000000..20ebb3bf34 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.toml @@ -0,0 +1,91 @@ +[DEFAULT] +tags = "condprof" +support-files = [ + "classifierCommon.js", + "classifierFrame.html", + "classifierHelper.js", + "cleanWorker.js", + "good.js", + "head.js", + "evil.css", + "evil.css^headers^", + "evil.js", + "evil.js^headers^", + "evilWorker.js", + "import.css", + "import2.css", + "raptor.jpg", + "track.html", + "trackerFrame.html", + "trackerFrame.sjs", + "trackingRequest.js", + "trackingRequest.js^headers^", + "unwantedWorker.js", + "vp9.webm", + "whitelistFrame.html", + "workerFrame.html", + "ping.sjs", + "basic.vtt", + "basic.vtt^headers^", + "dnt.html", + "dnt.sjs", + "update.sjs", + "bad.css", + "bad.css^headers^", + "gethash.sjs", + "gethashFrame.html", + "seek.webm", + "cache.sjs", + "features.js", + "sw_register.html", + "sw_unregister.html", + "sw_worker.js", + "sandboxed.html", + "sandboxed.html^headers^", +] +skip-if = [ + "http3", + "http2", +] + +["test_annotation_vs_TP.html"] + +["test_bug1254766.html"] + +["test_cachemiss.html"] +skip-if = ["verify"] + +["test_classifier.html"] + +["test_classifier_match.html"] + +["test_classifier_worker.html"] + +["test_classify_by_default.html"] +skip-if = [ + "http3", + "http2", +] + +["test_classify_ping.html"] +skip-if = ["verify && debug && (os == 'win' || os == 'mac')"] + +["test_classify_top_sandboxed.html"] + +["test_classify_track.html"] + +["test_cryptomining.html"] + +["test_cryptomining_annotate.html"] + +["test_emailtracking.html"] + +["test_fingerprinting.html"] + +["test_fingerprinting_annotate.html"] + +["test_gethash.html"] + +["test_socialtracking.html"] + +["test_socialtracking_annotate.html"] diff --git a/toolkit/components/url-classifier/tests/mochitest/ping.sjs b/toolkit/components/url-classifier/tests/mochitest/ping.sjs new file mode 100644 index 0000000000..09a5f17356 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/ping.sjs @@ -0,0 +1,15 @@ +function handleRequest(request, response) { + var query = {}; + request.queryString.split("&").forEach(function (val) { + var [name, value] = val.split("="); + query[name] = unescape(value); + }); + + if (request.method == "POST") { + setState(query.id, "ping"); + } else { + var value = getState(query.id); + response.setHeader("Content-Type", "text/plain", false); + response.write(value); + } +} diff --git a/toolkit/components/url-classifier/tests/mochitest/raptor.jpg b/toolkit/components/url-classifier/tests/mochitest/raptor.jpg Binary files differnew file mode 100644 index 0000000000..243ba9e2d4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/raptor.jpg diff --git a/toolkit/components/url-classifier/tests/mochitest/redirect_tracker.sjs b/toolkit/components/url-classifier/tests/mochitest/redirect_tracker.sjs new file mode 100644 index 0000000000..563abae5cb --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/redirect_tracker.sjs @@ -0,0 +1,7 @@ +const gURL = + "http://trackertest.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"; + +function handleRequest(request, response) { + response.setStatusLine("1.1", 302, "found"); + response.setHeader("Location", gURL, false); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/report.sjs b/toolkit/components/url-classifier/tests/mochitest/report.sjs new file mode 100644 index 0000000000..b5844eb7a4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/report.sjs @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const SJS = "report.sjs?"; +const REDIRECT = + "mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/" + + SJS; + +function createBlockedIframePage() { + return `<!DOCTYPE HTML> + <html> + <head> + <title></title> + </head> + <body> + <iframe id="phishingFrame" ></iframe> + </body> + </html>`; +} + +function createPage() { + return `<!DOCTYPE HTML> + <html> + <head> + <title>Hello World</title> + </head> + <body> + <script></script> + </body> + </html>`; +} + +function handleRequest(request, response) { + var params = new URLSearchParams(request.queryString); + var action = params.get("action"); + + if (action === "create-blocked-iframe") { + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html; charset=utf-8", false); + response.write(createBlockedIframePage()); + return; + } + + if (action === "redirect") { + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html; charset=utf-8", false); + response.write(createPage()); + return; + } + + if (action === "reporturl") { + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html; charset=utf-8", false); + response.write(createPage()); + return; + } + + if (action === "create-blocked-redirect") { + params.delete("action"); + params.append("action", "redirect"); + response.setStatusLine("1.1", 302, "found"); + response.setHeader( + "Location", + "https://" + REDIRECT + params.toString(), + false + ); + return; + } + + response.write("I don't know action " + action); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/sandboxed.html b/toolkit/components/url-classifier/tests/mochitest/sandboxed.html new file mode 100644 index 0000000000..3eb8870edd --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/sandboxed.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<body> +<script src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/sandboxed.html^headers^ b/toolkit/components/url-classifier/tests/mochitest/sandboxed.html^headers^ new file mode 100644 index 0000000000..4705ce9ded --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/sandboxed.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts; diff --git a/toolkit/components/url-classifier/tests/mochitest/seek.webm b/toolkit/components/url-classifier/tests/mochitest/seek.webm Binary files differnew file mode 100644 index 0000000000..72b0297233 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/seek.webm diff --git a/toolkit/components/url-classifier/tests/mochitest/sw_register.html b/toolkit/components/url-classifier/tests/mochitest/sw_register.html new file mode 100644 index 0000000000..2a536128fa --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/sw_register.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<script> +function waitForState(worker, state, context) { + return new Promise(resolve => { + function onStateChange() { + if (worker.state === state) { + worker.removeEventListener("statechange", onStateChange); + resolve(context); + } + } + + // First add an event listener, so we won't miss any change that happens + // before we check the current state. + worker.addEventListener("statechange", onStateChange); + + // Now check if the worker is already in the desired state. + onStateChange(); + }); +} + +(async function () { + dump("[Dimi]register sw...\n"); + let reg = await navigator.serviceWorker.register("sw_worker.js", {scope: "."}); + await waitForState(reg.installing, "activated", reg); + window.parent.postMessage({status: "registrationdone"}, "*"); +})(); + +</script> +</head> +<html> diff --git a/toolkit/components/url-classifier/tests/mochitest/sw_unregister.html b/toolkit/components/url-classifier/tests/mochitest/sw_unregister.html new file mode 100644 index 0000000000..b94fa0baac --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/sw_unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.opener.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/toolkit/components/url-classifier/tests/mochitest/sw_worker.js b/toolkit/components/url-classifier/tests/mochitest/sw_worker.js new file mode 100644 index 0000000000..bad16608b4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/sw_worker.js @@ -0,0 +1,10 @@ +self.addEventListener("fetch", function (event) { + let sep = "synth.html?"; + let idx = event.request.url.indexOf(sep); + if (idx > 0) { + let url = event.request.url.substring(idx + sep.length); + event.respondWith(fetch(url, { credentials: "include" })); + } else { + event.respondWith(fetch(event.request)); + } +}); diff --git a/toolkit/components/url-classifier/tests/mochitest/test_advisory_link.html b/toolkit/components/url-classifier/tests/mochitest/test_advisory_link.html new file mode 100644 index 0000000000..5fa49f628e --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_advisory_link.html @@ -0,0 +1,138 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test advisory link (Bug #1366384)</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); + +var mainWindow = window.browsingContext.topChromeWindow; + +var testDatas = [ + { url: "itisaphishingsite.org/phishing.html", + list: "mochi1-phish-simple", + provider: "google", + }, + + { url: "fakeitisaphishingsite.org/phishing.html", + list: "fake1-phish-simple", + provider: "mozilla", + }, + + { url: "phishing.example.com/test.html", + list: "mochi2-phish-simple", + provider: "google4", + }, +]; + +let pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p}); + +function addUrlToDB(list, url) { + let testData = [{ db: list, url}]; + + return classifierHelper.addUrlToDB(testData) + .catch(function(err) { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +function setupTestData(data) { + let promises = []; + let providerList = "browser.safebrowsing.provider." + data.provider + ".lists"; + promises.push(pushPrefs([providerList, data.list])); + + let activeTablePref = "urlclassifier.phishTable"; + let activeTable = SpecialPowers.getCharPref(activeTablePref); + activeTable += "," + data.list; + promises.push(pushPrefs([activeTablePref, activeTable])); + + promises.push(addUrlToDB(data.list, data.url)); + return Promise.all(promises); +} + +function testOnWindow(aTestData) { + return new Promise(resolve => { + let win = mainWindow.OpenBrowserWindow(); + + (async function() { + await TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win); + + let browser = win.gBrowser.selectedBrowser; + BrowserTestUtils.startLoadingURIString(browser, aTestData.url); + await BrowserTestUtils.waitForContentEvent(browser, "DOMContentLoaded"); + + let doc = win.gBrowser.contentDocument; + + // This test works on a document which uses Fluent. + // Fluent localization may finish later than DOMContentLoaded, + // so let's wait for `document.l10n.ready` promise to resolve. + await doc.l10n.ready; + let advisoryEl = doc.getElementById("advisory_provider"); + if (aTestData.provider != "google" && aTestData.provider != "google4") { + ok(!advisoryEl, "Advisory should not be shown"); + } else { + ok(advisoryEl, "Advisory element should exist"); + let expectedUrl = + SpecialPowers.getCharPref("browser.safebrowsing.provider." + + aTestData.provider + + ".advisoryURL"); + is(advisoryEl.href, expectedUrl, "Correct advisory url"); + let expectedText = + SpecialPowers.getCharPref("browser.safebrowsing.provider." + + aTestData.provider + + ".advisoryName"); + is(advisoryEl.text, expectedText, "correct advisory text"); + } + + win.close(); + resolve(); + })(); + }); +} + +SpecialPowers.pushPrefEnv( + {"set": [["browser.safebrowsing.phishing.enabled", true]]}, + test); + +function test() { + (async function() { + await classifierHelper.waitForInit(); + + for (let testData of testDatas) { + await setupTestData(testData); + await testOnWindow(testData); + await classifierHelper._cleanup(); + } + + SimpleTest.finish(); + })(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html b/toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html new file mode 100644 index 0000000000..bb512e7cdc --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the URI Classifier</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); + +// Add https://allowlisted.example.com to the permissions manager +SpecialPowers.addPermission("trackingprotection", + Ci.nsIPermissionManager.ALLOW_ACTION, + { url: "https://allowlisted.example.com" }); + +async function clearPermissions() { + await SpecialPowers.removePermission("trackingprotection", + { url: "https://allowlisted.example.com" }); + ok(!await SpecialPowers.testPermission("trackingprotection", + Ci.nsIPermissionManager.ALLOW_ACTION, + { url: "https://allowlisted.example.com" })); +} + +SpecialPowers.pushPrefEnv( + {"set": [["urlclassifier.trackingTable", "moztest-track-simple"], + ["privacy.trackingprotection.enabled", true], + ["privacy.trackingprotection.testing.report_blocked_node", true], + ["channelclassifier.allowlist_example", true], + ["dom.security.skip_remote_script_assertion_in_system_priv_context", true]]}, + test); + +function test() { + SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers); + UrlClassifierTestUtils.addTestTrackers().then(() => { + document.getElementById("testFrame").src = "allowlistAnnotatedFrame.html"; + }); +} + +// Expected finish() call is in "allowlistedAnnotatedFrame.html". +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_annotation_vs_TP.html b/toolkit/components/url-classifier/tests/mochitest/test_annotation_vs_TP.html new file mode 100644 index 0000000000..ad8e98005b --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_annotation_vs_TP.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the relationship between annotation vs TP</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="features.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +runTests(SpecialPowers.Ci.nsIClassifiedChannel.CLASSIFIED_TRACKING, + [ + ["privacy.trackingprotection.enabled", true], + ["privacy.trackingprotection.annotate_channels", true], + ["urlclassifier.features.fingerprinting.annotate.blacklistTables", ""], + ["urlclassifier.features.fingerprinting.annotate.blacklistHosts", ""], + ["privacy.trackingprotection.fingerprinting.enabled", false], + ["urlclassifier.features.cryptomining.annotate.blacklistTables", ""], + ["urlclassifier.features.cryptomining.annotate.blacklistHosts", ""], + ["privacy.trackingprotection.cryptomining.enabled", false], + ["urlclassifier.features.socialtracking.annotate.blacklistTables", ""], + ["urlclassifier.features.socialtracking.annotate.blacklistHosts", ""], + ], + true /* a tracking resource */); +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html new file mode 100644 index 0000000000..2e528a7242 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html @@ -0,0 +1,265 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1272239 - Test gethash.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script src="head.js"></script> +<script class="testbody" type="text/javascript"> +const MALWARE_LIST = "mochi-malware-simple"; +const MALWARE_HOST1 = "malware.example.com/"; +const MALWARE_HOST2 = "test1.example.com/"; + +const UNWANTED_LIST = "mochi-unwanted-simple"; +const UNWANTED_HOST1 = "unwanted.example.com/"; +const UNWANTED_HOST2 = "test2.example.com/"; + + +const UNUSED_MALWARE_HOST = "unused.malware.com/"; +const UNUSED_UNWANTED_HOST = "unused.unwanted.com/"; + +const GETHASH_URL = + "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/gethash.sjs"; + +var gPreGethashCounter = 0; +var gCurGethashCounter = 0; + +var expectLoad = false; + +function loadTestFrame() { + return new Promise(function(resolve, reject) { + var iframe = document.createElement("iframe"); + iframe.setAttribute("src", "gethashFrame.html"); + document.body.appendChild(iframe); + + iframe.onload = function() { + document.body.removeChild(iframe); + resolve(); + }; + }).then(getGethashCounter); +} + +function getGethashCounter() { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest; + xhr.open("PUT", GETHASH_URL + "?gethashcount"); + xhr.setRequestHeader("Content-Type", "text/plain"); + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + gPreGethashCounter = gCurGethashCounter; + gCurGethashCounter = parseInt(xhr.response); + resolve(); + } + }; + xhr.send(); + }); +} + +// setup function allows classifier send gethash request for test database +// also it calculate to fullhash for url and store those hashes in gethash sjs. +async function setup() { + await classifierHelper.allowCompletion([MALWARE_LIST, UNWANTED_LIST], GETHASH_URL); + + return Promise.all([ + addCompletionToServer(MALWARE_LIST, MALWARE_HOST1, GETHASH_URL), + addCompletionToServer(MALWARE_LIST, MALWARE_HOST2, GETHASH_URL), + addCompletionToServer(UNWANTED_LIST, UNWANTED_HOST1, GETHASH_URL), + addCompletionToServer(UNWANTED_LIST, UNWANTED_HOST2, GETHASH_URL), + ]); +} + +// Reset function in helper try to simulate the behavior we restart firefox +function reset() { + return classifierHelper.resetDatabase() + .catch(err => { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +function updateUnusedUrl() { + var testData = [ + { url: UNUSED_MALWARE_HOST, db: MALWARE_LIST }, + { url: UNUSED_UNWANTED_HOST, db: UNWANTED_LIST }, + ]; + + return classifierHelper.addUrlToDB(testData) + .catch(err => { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +function addPrefixToDB() { + return update(true); +} + +function addCompletionToDB() { + return update(false); +} + +function update(prefix = false) { + var length = prefix ? 4 : 32; + var testData = [ + { url: MALWARE_HOST1, db: MALWARE_LIST, len: length }, + { url: MALWARE_HOST2, db: MALWARE_LIST, len: length }, + { url: UNWANTED_HOST1, db: UNWANTED_LIST, len: length }, + { url: UNWANTED_HOST2, db: UNWANTED_LIST, len: length }, + ]; + + return classifierHelper.addUrlToDB(testData) + .catch(err => { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +// This testcase is to make sure gethash works: +// 1. Add prefixes to DB. +// 2. Load test frame contains malware & unwanted url, those urls should be blocked. +// 3. The second step should also trigger a gethash request since completions is not in +// either cache or DB. +// 4. Load test frame again, since completions is stored in cache now, no gethash +// request should be triggered. +function testGethash() { + return Promise.resolve() + .then(addPrefixToDB) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter > gPreGethashCounter, "Gethash request is triggered."); +}) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is not triggered."); +}) + .then(reset); +} + +// This testcae is to make sure completions in update works: +// 1. Add completions to DB. +// 2. Load test frame, since completions is stored in DB, gethash request should +// not be triggered. +function testUpdate() { + return Promise.resolve() + .then(addCompletionToDB) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is not triggered."); +}) + .then(reset); +} + +// This testcase is to make sure an update request will not clear completions in DB: +// 1. Add completions to DB. +// 2. Load test frame to make sure completions is stored in database, in this case, gethash +// should not be triggered. +// 3. Trigger an update, cache is cleared, but completions in DB should still remain. +// 4. Load test frame again, since completions is in DB, gethash request should not be triggered. +function testUpdateNotClearCompletions() { + return Promise.resolve() + .then(addCompletionToDB) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is not triggered."); +}) + .then(updateUnusedUrl) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is not triggered."); +}) + .then(reset); +} + +// This testcase is to make sure completion store in DB will properly load after restarting. +// 1. Add completions to DB. +// 2. Simulate firefox restart by calling reloadDatabase. +// 3. Load test frame, since completions should be loaded from DB, no gethash request should +// be triggered. +function testUpdateCompletionsAfterReload() { + return Promise.resolve() + .then(addCompletionToDB) + .then(classifierHelper.reloadDatabase) + // Call getTables to ensure the DB is fully reloaded before we load the test + // frame. + .then(classifierHelper.getTables) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is not triggered."); +}) + .then(reset); +} + +// This testcase is to make sure cache will be cleared after restarting +// 1. Add prefixes to DB. +// 2. Load test frame, this should trigger a gethash request and completions will be stored in +// cache. +// 3. Load test frame again, no gethash should be triggered because of cache. +// 4. Simulate firefox restart by calling reloadDatabase. +// 5. Load test frame again, since cache is cleared, gethash request should be triggered. +function testGethashCompletionsAfterReload() { + return Promise.resolve() + .then(addPrefixToDB) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter > gPreGethashCounter, "Gethash request is triggered."); +}) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is not triggered."); +}) + .then(classifierHelper.reloadDatabase) + // Call getTables to ensure the DB is fully reloaded before we load the test + // frame. + .then(classifierHelper.getTables) + .then(loadTestFrame) + .then(() => { + ok(gCurGethashCounter > gPreGethashCounter, "Gethash request is triggered."); +}) + .then(reset); +} + +function runTest() { + Promise.resolve() + .then(classifierHelper.waitForInit) + .then(setup) + .then(testGethash) + .then(testUpdate) + .then(testUpdateNotClearCompletions) + .then(testUpdateCompletionsAfterReload) + .then(testGethashCompletionsAfterReload) + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some test failed with error " + e); + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); + +// 'network.predictor.enabled' is disabled because if other testcase load +// evil.js, evil.css ...etc resources, it may cause we load them from cache +// directly and bypass classifier check +SpecialPowers.pushPrefEnv({"set": [ + ["browser.safebrowsing.malware.enabled", true], + ["urlclassifier.malwareTable", "mochi-malware-simple,mochi-unwanted-simple"], + ["network.predictor.enabled", false], + ["urlclassifier.gethash.timeout_ms", 30000], +]}, runTest); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html b/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html new file mode 100644 index 0000000000..2af0285884 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html @@ -0,0 +1,167 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1272239 - Test gethash.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script src="head.js"></script> +<script class="testbody" type="text/javascript"> +const MALWARE_LIST = "test-malware-simple"; +const MALWARE_HOST = "malware.example.com/"; + +const UNWANTED_LIST = "test-unwanted-simple"; +const UNWANTED_HOST = "unwanted.example.com/"; + +const GETHASH_URL = "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/cache.sjs"; + +var shouldLoad = false; + +var gPreGethashCounter = 0; +var gCurGethashCounter = 0; + +function loadTestFrame() { + return new Promise(function(resolve, reject) { + var iframe = document.createElement("iframe"); + iframe.setAttribute("src", "gethashFrame.html"); + document.body.appendChild(iframe); + + iframe.onload = function() { + document.body.removeChild(iframe); + resolve(); + }; + }).then(getGethashCounter); +} + +function getGethashCounter() { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest; + xhr.open("PUT", GETHASH_URL + "?gethashcount"); + xhr.setRequestHeader("Content-Type", "text/plain"); + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + gPreGethashCounter = gCurGethashCounter; + gCurGethashCounter = parseInt(xhr.response); + resolve(); + } + }; + xhr.send(); + }); +} + +// add 4-bytes prefixes to local database, so when we access the url, +// it will trigger gethash request. +function addPrefixToDB(list, url) { + var testData = [{ db: list, url, len: 4 }]; + + return classifierHelper.addUrlToDB(testData) + .catch(function(err) { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +// manually reset DB to make sure next test won't be affected by cache. +function reset() { + return classifierHelper.resetDatabase(); +} + +// This test has to come before testPositiveCache to ensure gethash server doesn't +// contain completions. +function testNegativeCache() { + SpecialPowers.DOMWindowUtils.clearSharedStyleSheetCache(); + shouldLoad = true; + + async function setup() { + await classifierHelper.allowCompletion([MALWARE_LIST, UNWANTED_LIST], GETHASH_URL); + + // Only add prefix to database. not server, so gethash will not return + // result. + return Promise.all([ + addPrefixToDB(MALWARE_LIST, MALWARE_HOST), + addPrefixToDB(UNWANTED_LIST, UNWANTED_HOST), + ]); + } + + return Promise.resolve() + .then(setup) + .then(() => loadTestFrame()) + .then(() => { + ok(gCurGethashCounter > gPreGethashCounter, "Gethash request is triggered."); +}) + // Second load should not trigger gethash request because cache. + .then(() => loadTestFrame()) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is nottriggered."); +}) + .then(reset); +} + +function testPositiveCache() { + SpecialPowers.DOMWindowUtils.clearSharedStyleSheetCache(); + shouldLoad = false; + + async function setup() { + await classifierHelper.allowCompletion([MALWARE_LIST, UNWANTED_LIST], GETHASH_URL); + + return Promise.all([ + addPrefixToDB(MALWARE_LIST, MALWARE_HOST), + addPrefixToDB(UNWANTED_LIST, UNWANTED_HOST), + addCompletionToServer(MALWARE_LIST, MALWARE_HOST, GETHASH_URL), + addCompletionToServer(UNWANTED_LIST, UNWANTED_HOST, GETHASH_URL), + ]); + } + + return Promise.resolve() + .then(setup) + .then(() => loadTestFrame()) + .then(() => { + ok(gCurGethashCounter > gPreGethashCounter, "Gethash request is triggered."); +}) + // Second load should not trigger gethash request because cache. + .then(() => loadTestFrame()) + .then(() => { + ok(gCurGethashCounter == gPreGethashCounter, "Gethash request is nottriggered."); +}) + .then(reset); +} + +function runTest() { + Promise.resolve() + // This test resources get blocked when gethash returns successfully + .then(classifierHelper.waitForInit) + .then(testNegativeCache) + .then(testPositiveCache) + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some test failed with error " + e); + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); + +// 'network.predictor.enabled' is disabled because if other testcase load +// evil.js, evil.css ...etc resources, it may cause we load them from cache +// directly and bypass classifier check +SpecialPowers.pushPrefEnv({"set": [ + ["browser.safebrowsing.malware.enabled", true], + ["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"], + ["network.predictor.enabled", false], + ["urlclassifier.gethash.timeout_ms", 30000], +]}, runTest); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html b/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html new file mode 100644 index 0000000000..44a0c92549 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html @@ -0,0 +1,171 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the URI Classifier</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; +var contentPage = "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html"; + +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function testOnWindow() { + return new Promise((resolve, reject) => { + let win = mainWindow.OpenBrowserWindow(); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("OnLoadComplete", function innerLoad2(e) { + win.content.removeEventListener("OnLoadComplete", innerLoad2); + SimpleTest.executeSoon(function() { + checkLoads(win, JSON.parse(e.detail)); + resolve(win); + }); + }, false, true); + }, true); + SimpleTest.executeSoon(function() { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + }); + }); + }, {capture: true, once: true}); + }); +} + +var badids = [ + "badscript1", + "badscript2", + "badimage1", + "badimage2", + "badframe1", + "badframe2", + "badmedia1", + "badcss", +]; + +function checkLoads(aWindow, aData) { + var win = aWindow.content; + + is(aData.scriptItem1, "untouched", "Should not load tracking javascript"); + is(aData.scriptItem2, "untouched", "Should not load tracking javascript (2)"); + + is(aData.imageItem1, "untouched", "Should not load tracking images"); + is(aData.imageItem2, "untouched", "Should not load tracking images (2)"); + + is(aData.frameItem1, "untouched", "Should not load tracking iframes"); + is(aData.frameItem2, "untouched", "Should not load tracking iframes (2)"); + is(aData.mediaItem1, "error", "Should not load tracking videos"); + is(aData.xhrItem, "failed", "Should not load tracking XHRs"); + is(aData.fetchItem, "error", "Should not fetch from tracking URLs"); + + var elt = win.document.getElementById("styleCheck"); + var style = win.document.defaultView.getComputedStyle(elt); + isnot(style.visibility, "hidden", "Should not load tracking css"); + + is(win.document.blockedNodeByClassifierCount, badids.length, + "Should identify all tracking elements"); + + var blockedNodes = win.document.blockedNodesByClassifier; + + // Make sure that every node in blockedNodes exists in the tree + // (that may not always be the case but do not expect any nodes to disappear + // from the tree here) + var allNodeMatch = true; + for (let i = 0; i < blockedNodes.length; i++) { + let nodeMatch = false; + for (let j = 0; j < badids.length && !nodeMatch; j++) { + nodeMatch = nodeMatch || + (blockedNodes[i] == win.document.getElementById(badids[j])); + } + + allNodeMatch = allNodeMatch && nodeMatch; + } + ok(allNodeMatch, "All annotated nodes are expected in the tree"); + + // Make sure that every node with a badid (see badids) is found in the + // blockedNodes. This tells us if we are neglecting to annotate + // some nodes + allNodeMatch = true; + for (let j = 0; j < badids.length; j++) { + let nodeMatch = false; + for (let i = 0; i < blockedNodes.length && !nodeMatch; i++) { + nodeMatch = nodeMatch || + (blockedNodes[i] == win.document.getElementById(badids[j])); + } + + if (!nodeMatch) { + console.log(badids[j] + " was not found in blockedNodes"); + } + allNodeMatch = allNodeMatch && nodeMatch; + } + ok(allNodeMatch, "All tracking nodes are expected to be annotated as such"); + + // End (parent) test. +} + +function cleanup() { + SpecialPowers.clearUserPref("privacy.trackingprotection.enabled"); + SpecialPowers.clearUserPref("channelclassifier.allowlist_example"); + SpecialPowers.clearUserPref("privacy.trackingprotection.testing.report_blocked_node"); + + UrlClassifierTestUtils.cleanupTestTrackers(); +} + +SpecialPowers.pushPrefEnv( + {"set": [["urlclassifier.trackingTable", "moztest-track-simple"]]}, + test); + +async function test() { + SimpleTest.registerCleanupFunction(cleanup); + await UrlClassifierTestUtils.addTestTrackers(); + + await SpecialPowers.setBoolPref("privacy.trackingprotection.enabled", true); + // Make sure chrome:// URIs are processed. This does not white-list + // any URIs unless 'https://allowlisted.example.com' is added in the + // permission manager (see test_allowlisted_annotations.html) + await SpecialPowers.setBoolPref("channelclassifier.allowlist_example", true); + await SpecialPowers.setBoolPref("privacy.trackingprotection.testing.report_blocked_node", true); + + await testOnWindow().then(function(aWindow) { + aWindow.close(); + }); + + SimpleTest.finish(); +} + +// Expected finish() call is in "classifiedAnnotatedFrame.html". +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html new file mode 100644 index 0000000000..787b70798a --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the URI Classifier</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> +var firstLoad = true; + +// Add some URLs to the malware database. +var testData = [ + { url: "malware.mochi.test/", + db: "test-malware-simple", + }, + { url: "unwanted.mochi.test/", + db: "test-unwanted-simple", + }, + { url: "blocked.mochi.test/", + db: "test-block-simple", + }, + { url: "harmful.mochi.test/", + db: "test-harmful-simple", + }, + { url: "phish.mochi.test/firefox/its-a-trap.html", + db: "test-phish-simple", + }, +]; + +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; +const Cr = SpecialPowers.Cr; + +var testURLs = [ + { url: "http://mochi.test", + table: "", + result: Cr.NS_OK, + }, + { url: "http://malware.mochi.test", + table: "test-malware-simple", + result: Cr.NS_ERROR_MALWARE_URI, + }, + { url: "http://unwanted.mochi.test", + table: "test-unwanted-simple", + result: Cr.NS_ERROR_UNWANTED_URI, + }, + { url: "http://phish.mochi.test/firefox/its-a-trap.html", + table: "test-phish-simple", + result: Cr.NS_ERROR_PHISHING_URI, + }, + { url: "http://harmful.mochi.test", + table: "test-harmful-simple", + result: Cr.NS_ERROR_HARMFUL_URI, + }, + { url: "http://blocked.mochi.test", + table: "test-block-simple", + result: Cr.NS_ERROR_BLOCKED_URI, + }, +]; + +function loadTestFrame() { + document.getElementById("testFrame").src = "classifierFrame.html"; +} + +// Expected finish() call is in "classifierFrame.html". +SimpleTest.waitForExplicitFinish(); + +function updateSuccess() { + return SpecialPowers.pushPrefEnv( + {"set": [["browser.safebrowsing.malware.enabled", true]]}); +} + +function updateError(errorCode) { + ok(false, "Couldn't update classifier. Error code: " + errorCode); + // Abort test. + SimpleTest.finish(); +} + +function testService() { + return new Promise(resolve => { + function runNextTest() { + if (!testURLs.length) { + resolve(); + return; + } + let test = testURLs.shift(); + let tables = "test-malware-simple,test-unwanted-simple,test-phish-simple,test-block-simple,test-harmful-simple"; + let uri = SpecialPowers.Services.io.newURI(test.url); + let prin = SpecialPowers.Services.scriptSecurityManager.createContentPrincipal(uri, {}); + SpecialPowers.doUrlClassify(prin, function(errorCode) { + is(errorCode, test.result, + `Successful asynchronous classification of ${test.url}`); + SpecialPowers.doUrlClassifyLocal(uri, tables, function(results) { + if (!results.length) { + is(test.table, "", + `Successful asynchronous local classification of ${test.url}`); + } else { + let result = results[0].QueryInterface(Ci.nsIUrlClassifierFeatureResult); + is(result.list, test.table, + `Successful asynchronous local classification of ${test.url}`); + } + runNextTest(); + }); + }); + } + runNextTest(resolve); + }); +} + +SpecialPowers.pushPrefEnv( + {"set": [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple,test-harmful-simple"], + ["urlclassifier.blockedTable", "test-block-simple"], + ["urlclassifier.phishTable", "test-phish-simple"], + ["browser.safebrowsing.phishing.enabled", true], + ["browser.safebrowsing.malware.enabled", true], + ["browser.safebrowsing.blockedURIs.enabled", true], + ["browser.safebrowsing.debug", true]]}, + function() { + classifierHelper.waitForInit() + .then(() => classifierHelper.addUrlToDB(testData)) + .then(updateSuccess) + .catch(err => { + updateError(err); + }) + .then(testService) + .then(loadTestFrame); + }); + +</script> + +</pre> +<iframe id="testFrame" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html new file mode 100644 index 0000000000..ea16c14e74 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html @@ -0,0 +1,157 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1281083 - Changing the urlclassifier.*Table prefs doesn't take effect before the next browser restart.</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script src="head.js"></script> +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; +var contentPage = "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html"; + +const testTable = "moz-track-digest256"; +const UPDATE_URL = "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/update.sjs"; + +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + +// If default preference contain the table we want to test, +// We should change test table to a different one. +var trackingTables = SpecialPowers.getCharPref("urlclassifier.trackingTable").split(","); +ok(!trackingTables.includes(testTable), "test table should not be in the preference"); + +var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]. + getService(Ci.nsIUrlListManager); + +is(listmanager.getGethashUrl(testTable), "", + "gethash url for test table should be empty before setting to preference"); + +function checkLoads(aWindow, aBlocked) { + var win = aWindow.content; + is(win.document.getElementById("badscript").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking javascript"); +} + +function testOnWindow() { + return new Promise((resolve, reject) => { + let win = mainWindow.OpenBrowserWindow(); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("load", function innerLoad2(e) { + win.content.removeEventListener("load", innerLoad2); + SimpleTest.executeSoon(function() { + resolve(win); + }); + }, false, true); + }, true); + SimpleTest.executeSoon(function() { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + }); + }); + }, {capture: true, once: true}); + }); +} + +function setup() { + return new Promise(function(resolve, reject) { + // gethash url of test table "moz-track-digest256" should be updated + // after setting preference. + var url = listmanager.getGethashUrl(testTable); + var expected = SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.gethashURL"); + + is(url, expected, testTable + " matches its gethash url"); + + // Trigger update + listmanager.disableUpdate(testTable); + listmanager.enableUpdate(testTable); + listmanager.maybeToggleUpdateChecking(); + + // We wait until "nextupdattime" was set as a signal that update is complete. + waitForUpdateSuccess(function() { + resolve(); + }); + }); +} + +function waitForUpdateSuccess(callback) { + let nextupdatetime = + SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.nextupdatetime"); + + if (nextupdatetime !== "1") { + callback(); + return; + } + + timer.initWithCallback(function() { + waitForUpdateSuccess(callback); + }, 10, Ci.nsITimer.TYPE_ONE_SHOT); +} + +async function runTest() { + // To make sure url is not blocked by an already blocked url. + // Here we use non-tracking.example.com as a tracked url. + // Since this table is only used in this bug, so it won't affect other testcases. + await addCompletionToServer(testTable, "bug1281083.example.com/", UPDATE_URL); + + /** + * In this test we try to modify only urlclassifier.*Table preference to see if + * url specified in the table will be blocked after update. + */ + await SpecialPowers.pushPrefEnv( + {"set": [["urlclassifier.trackingTable", testTable]]}); + + await setup(); + + await testOnWindow().then(function(aWindow) { + checkLoads(aWindow, true); + aWindow.close(); + }); + + SimpleTest.finish(); +} + +// Set nextupdatetime to 1 to trigger an update +SpecialPowers.pushPrefEnv( + {"set": [["privacy.trackingprotection.enabled", true], + ["channelclassifier.allowlist_example", true], + ["browser.safebrowsing.provider.mozilla.nextupdatetime", "1"], + ["browser.safebrowsing.provider.mozilla.lists", testTable], + ["browser.safebrowsing.provider.mozilla.updateURL", UPDATE_URL]]}, + runTest); + +// Expected finish() call is in "bug_1281083.html". +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref_bug1395411.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref_bug1395411.html new file mode 100644 index 0000000000..df3ac79d8a --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref_bug1395411.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Bug 1395411 - Changing the urlclassifier.*Table prefs doesn't remove them from the update checker.</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +const testTableV2 = "mochi1-phish-simple"; +const testTableV4 = "mochi1-phish-proto"; +const UPDATE_URL_V2 = "http://mochi.test:8888/tests/toolkit/components/url-classifier/dummyV2"; +const UPDATE_URL_V4 = "http://mochi.test:8888/tests/toolkit/components/url-classifier/dummyV4"; + +let listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]. + getService(Ci.nsIUrlListManager); + +let pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p}); + +SpecialPowers.pushPrefEnv( + {"set": [["browser.safebrowsing.phishing.enabled", true], + ["browser.safebrowsing.provider.mozilla.lists", testTableV2], + ["browser.safebrowsing.provider.mozilla4.lists", testTableV4], + ["browser.safebrowsing.provider.mozilla4.updateURL", UPDATE_URL_V4], + ["browser.safebrowsing.provider.mozilla.updateURL", UPDATE_URL_V2]]}, + runTest); + +function runTest() { + (async function() { + await classifierHelper.waitForInit(); + + await pushPrefs(["urlclassifier.phishTable", testTableV2 + "," + testTableV4]); + is(listmanager.getUpdateUrl(testTableV4), UPDATE_URL_V4, "Correct update url v4"); + is(listmanager.getUpdateUrl(testTableV2), UPDATE_URL_V2, "Correct update url v2"); + + await pushPrefs(["urlclassifier.phishTable", testTableV2]); + is(listmanager.getUpdateUrl(testTableV4), "", "Correct empty update url v4"); + is(listmanager.getUpdateUrl(testTableV2), UPDATE_URL_V2, "Correct update url v2"); + + await pushPrefs(["urlclassifier.phishTable", testTableV4]); + is(listmanager.getUpdateUrl(testTableV4), UPDATE_URL_V4, "Correct update url v4"); + is(listmanager.getUpdateUrl(testTableV2), "", "Correct empty update url v2"); + + await pushPrefs(["urlclassifier.phishTable", ""]); + is(listmanager.getUpdateUrl(testTableV4), "", "Correct empty update url v4"); + is(listmanager.getUpdateUrl(testTableV2), "", "Correct empty update url v2"); + + await classifierHelper._cleanup(); + + SimpleTest.finish(); + })(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_match.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_match.html new file mode 100644 index 0000000000..f6ea460be1 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_match.html @@ -0,0 +1,179 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the URI Classifier Matched Info (bug 1288633) </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="application/javascript"> +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; +var Cr = SpecialPowers.Cr; + +var inputDatas = [ + { url: "malware.mochi.test/", + db: "mochi-block-simple", + }, + { url: "malware1.mochi.test/", + db: "mochi1-block-simple", + }, + { url: "malware1.mochi.test/", + db: "mochi1-malware-simple", + provider: "mozilla", + }, + { url: "malware2.mochi.test/", + db: "mochi2-unwanted-simple", + provider: "mozilla", + }, + { url: "malware2.mochi.test/", + db: "mochi2-malware-simple", + provider: "mozilla", + }, + { url: "malware3.mochi.test/", + db: "mochig3-malware-simple", + provider: "google", + }, + { url: "malware3.mochi.test/", + db: "mochim3-malware-simple", + provider: "mozilla", + }, +]; + +function hash(str) { + let hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + + let data = new TextEncoder().encode(str); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + + return hasher.finish(false); +} + +var testDatas = [ + // Match empty provider + { url: "http://malware.mochi.test", + expect: { error: Cr.NS_ERROR_BLOCKED_URI, + table: "mochi-block-simple", + provider: "", + fullhash: (function() { + return hash("malware.mochi.test/"); + })(), + }, + }, + // Match multiple tables, only one has valid provider + { url: "http://malware1.mochi.test", + expect: { error: Cr.NS_ERROR_MALWARE_URI, + table: "mochi1-malware-simple", + provider: "mozilla", + fullhash: (function() { + return hash("malware1.mochi.test/"); + })(), + }, + }, + // Match multiple tables, handle order + { url: "http://malware2.mochi.test", + expect: { error: Cr.NS_ERROR_MALWARE_URI, + table: "mochi2-malware-simple", + provider: "mozilla", + fullhash: (function() { + return hash("malware2.mochi.test/"); + })(), + }, + }, + // Match multiple tables, handle order + { url: "http://malware3.mochi.test", + expect: { error: Cr.NS_ERROR_MALWARE_URI, + table: "mochig3-malware-simple", + provider: "google", + fullhash: (function() { + return hash("malware3.mochi.test/"); + })(), + }, + }, + +]; + +SimpleTest.waitForExplicitFinish(); + +function setupTestData(datas) { + let prefValues = {}; + for (let data of datas) { + if (!data.provider) { + continue; + } + let providerPref = "browser.safebrowsing.provider." + data.provider + ".lists"; + let prefValue; + if (!prefValues[providerPref]) { + prefValue = data.db; + } else { + prefValue = prefValues[providerPref] + "," + data.db; + } + prefValues[providerPref] = prefValue; + } + + // Convert map to array + let prefArray = []; + for (var pref in prefValues) { + prefArray.push([pref, prefValues[pref]]); + } + + let activeTablePref = "urlclassifier.malwareTable"; + let activeTable = SpecialPowers.getCharPref(activeTablePref); + for (let data of datas) { + activeTable += "," + data.db; + } + prefArray.push([activeTablePref, activeTable]); + + return SpecialPowers.pushPrefEnv({set: prefArray}); +} + +function runTest() { + return new Promise(resolve => { + function runNextTest() { + if (!testDatas.length) { + resolve(); + return; + } + let test = testDatas.shift(); + let uri = SpecialPowers.Services.io.newURI(test.url); + let prin = SpecialPowers.Services.scriptSecurityManager.createContentPrincipal(uri, {}); + SpecialPowers.doUrlClassify(prin, function(errorCode, table, provider, fullhash) { + is(errorCode, test.expect.error, `Test url ${test.url} correct error`); + is(table, test.expect.table, `Test url ${test.url} correct table`); + is(provider, test.expect.provider, `Test url ${test.url} correct provider`); + is(fullhash, btoa(test.expect.fullhash), `Test url ${test.url} correct full hash`); + runNextTest(); + }); + } + runNextTest(); + }); +} + +SpecialPowers.pushPrefEnv( + {"set": [["browser.safebrowsing.malware.enabled", true]]}, + function() { + classifierHelper.waitForInit() + .then(() => setupTestData(inputDatas)) + .then(() => classifierHelper.addUrlToDB(inputDatas)) + .then(runTest) + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some tests failed with error " + e); + SimpleTest.finish(); + }); + }); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html new file mode 100644 index 0000000000..0e984c2a64 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the URI Classifier</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> +// Add some URLs to the malware database. +var testData = [ + { url: "example.com/tests/toolkit/components/url-classifier/tests/mochitest/evilWorker.js", + db: "test-malware-simple", + }, + { url: "example.com/tests/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js", + db: "test-unwanted-simple", + }, +]; + +function loadTestFrame() { + document.getElementById("testFrame").src = + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/workerFrame.html"; +} + +function onmessage(event) { + var pieces = event.data.split(":"); + if (pieces[0] == "finish") { + SimpleTest.finish(); + return; + } + + is(pieces[0], "success", pieces[1]); +} + +function updateSuccess() { + SpecialPowers.pushPrefEnv( + {"set": [["browser.safebrowsing.malware.enabled", true]]}, + loadTestFrame); +} + +function updateError(errorCode) { + ok(false, "Couldn't update classifier. Error code: " + errorCode); + // Abort test. + SimpleTest.finish(); +} + +SpecialPowers.pushPrefEnv( + {"set": [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"]]}, + function() { + classifierHelper.waitForInit() + .then(() => classifierHelper.addUrlToDB(testData)) + .then(updateSuccess) + .catch(err => { + updateError(err); + }); + }); + +window.addEventListener("message", onmessage); + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html new file mode 100644 index 0000000000..c10a0c62f9 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html @@ -0,0 +1,156 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1442496</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> + +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1442496">Mozilla Bug 1442496</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script type="text/javascript" src="classifierHelper.js"></script> +<script class="testbody" type="text/javascript"> + +// To add a request to test, add the request in trackerFrame.html +// and the id of query string "?id=xxx" here. +const trackersAll = [ + "img-src", + "object-data", + "script-src", + "iframe-src", + "link-rel-stylesheet", + "video-src", + "track-src", + "ping", + "fetch", + "xmlhttprequest", + "send-beacon", + "fetch-in-sw", +]; + +const TRACKER_DOMAIN = "itisatracker.org"; +const TEST_TOP_DOMAIN = "example.com"; + +const TEST_TOP_PAGE = "trackerFrame.html"; +const TRACKER_SERVER = "trackerFrame.sjs"; + +const TEST_PATH = "/tests/toolkit/components/url-classifier/tests/mochitest/"; + +const TEST_TOP_SITE = "https:///" + TEST_TOP_DOMAIN + TEST_PATH; +const TRACKER_SITE = "https://" + TRACKER_DOMAIN + TEST_PATH; +const TRACKER_SJS = "https://" + TRACKER_DOMAIN + TEST_PATH + TRACKER_SERVER; + +// This function ask the server to set the cookie +async function setupAndRun(hasCookie, topLevelSite = TEST_TOP_SITE) { + // In order to apply the correct cookieBehavior, we need to first open a new + // window to setup cookies. So, the new window will use the correct + // cookieBehavior. Otherwise, it will use the default cookieBehavior. + let setup_win = window.open(); + await setup_win.fetch(TRACKER_SJS + "?init=" + trackersAll.length, { + credentials: "include", + }); + setup_win.close(); + + return new Promise(resolve => { + let win; + let query = hasCookie ? "with-cookie" : "without-cookie"; + fetch(TRACKER_SJS + "?callback=" + query).then(r => { + r.text().then((body) => { + let trackers_found = body.split(","); + for (let tracker of trackersAll) { + let description = "Tracker request " + tracker + "received " + + (hasCookie ? "with" : "without") + " cookie"; + ok(trackers_found.includes(tracker), description); + } + win.close(); + resolve(); + }); + }); + + win = window.open(topLevelSite + TEST_TOP_PAGE); + }); +} + +async function cleanup(topLevelSite = TEST_TOP_SITE) { + function clearServiceWorker() { + return new Promise(resolve => { + let w; + window.onmessage = function(e) { + if (e.data.status == "unregistrationdone") { + w.close(); + resolve(); + } + } + w = window.open(TEST_TOP_SITE + "sw_unregister.html"); + }); + } + + // Ensure we clear the stylesheet cache so that we re-classify. + SpecialPowers.DOMWindowUtils.clearSharedStyleSheetCache(); + + await clearServiceWorker(); +} + +async function runTests() { + await SpecialPowers.pushPrefEnv({set: [ + ["urlclassifier.trackingAnnotationTable.testEntries", TRACKER_DOMAIN], + ["urlclassifier.trackingAnnotationWhitelistTable", "test-trackwhite-simple"], + ["network.cookie.cookieBehavior", 4], + ["privacy.trackingprotection.enabled", false ], + ["privacy.trackingprotection.annotate_channels", false], + ["browser.send_pings", true], + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ]}); + + await classifierHelper.waitForInit(); + + let hasCookie; + + info("Test all requests should be sent with cookies when ETP is off"); + hasCookie = true; + await setupAndRun(hasCookie); + await cleanup(); + + info("Test all requests should be sent without cookies when ETP is on"); + await SpecialPowers.pushPrefEnv({set: [ + [ "privacy.trackingprotection.annotate_channels", true], + ]}); + + hasCookie = false; + await setupAndRun(hasCookie); + await cleanup(); + + info("Test all requests should be sent with cookies when top-level is in the whitelist"); + await classifierHelper.addUrlToDB([{ + url: TEST_TOP_DOMAIN + "/?resource=" + TRACKER_DOMAIN, + db: "test-trackwhite-simple", + }]); + + hasCookie = true; + await setupAndRun(hasCookie); + await cleanup(); + + info("Test all requests should be sent with cookies when the tracker is a first-party"); + hasCookie = true; + await setupAndRun(hasCookie, TRACKER_SITE); + await cleanup(TRACKER_SITE); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> + +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html new file mode 100644 index 0000000000..6a8a189ed2 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html @@ -0,0 +1,131 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1233914 - ping doesn't honor the TP list here.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + SimpleTest.requestFlakyTimeout("Delay to make sure ping is made prior than XHR"); + + const timeout = 200; + const host_nottrack = "http://not-tracking.example.com/"; + const host_track = "http://trackertest.org/"; + const path_ping = "tests/toolkit/components/url-classifier/tests/mochitest/ping.sjs"; + const TP_ENABLE_PREF = "privacy.trackingprotection.enabled"; + const RETRY_TIMEOUT_MS = 200; + + async function testPingNonBlocklist() { + await SpecialPowers.setBoolPref(TP_ENABLE_PREF, true); + + var msg = "ping should reach page not in blacklist"; + var expectPing = true; + var id = "1111"; + ping(id, host_nottrack); + + return new Promise(function(resolve, reject) { + // Retry at most 30 seconds. + isPingedWithRetry(id, expectPing, msg, resolve, 30 * 1000 / RETRY_TIMEOUT_MS); + }); + } + + async function testPingBlocklistSafebrowsingOff() { + await SpecialPowers.setBoolPref(TP_ENABLE_PREF, false); + + var msg = "ping should reach page in blacklist when tracking protection is off"; + var expectPing = true; + var id = "2222"; + ping(id, host_track); + + return new Promise(function(resolve, reject) { + // Retry at most 30 seconds. + isPingedWithRetry(id, expectPing, msg, resolve, 30 * 1000 / RETRY_TIMEOUT_MS); + }); + } + + async function testPingBlocklistSafebrowsingOn() { + await SpecialPowers.setBoolPref(TP_ENABLE_PREF, true); + + var msg = "ping should not reach page in blacklist when tracking protection is on"; + var expectPing = false; + var id = "3333"; + ping(id, host_track); + + return new Promise(function(resolve, reject) { + setTimeout(function() { + isPinged(id, expectPing, msg, resolve); + }, timeout); + }); + } + + function ping(id, host) { + var elm = document.createElement("a"); + elm.setAttribute("ping", host + path_ping + "?id=" + id); + elm.setAttribute("href", "#"); + document.body.appendChild(elm); + + // Trigger ping. + elm.click(); + + document.body.removeChild(elm); + } + + function isPingedWithRetry(id, expected, msg, callback, retryCnt) { + var url = "http://mochi.test:8888/" + path_ping; + var xhr = new XMLHttpRequest(); + xhr.open("GET", url + "?id=" + id); + xhr.onload = function() { + let pinged = xhr.response === "ping"; + let success = pinged === expected; + if (success || 0 === retryCnt) { + is(expected, pinged, msg); + callback(); + return; + } + // Retry on failure. + setTimeout(() => { + isPingedWithRetry(id, expected, msg, callback, retryCnt - 1); + }, RETRY_TIMEOUT_MS); + }; + xhr.send(); + } + + function isPinged(id, expected, msg, callback) { + isPingedWithRetry(id, expected, msg, callback, 0); + } + + function cleanup() { + SpecialPowers.clearUserPref(TP_ENABLE_PREF); + } + + function runTest() { + classifierHelper.waitForInit() + .then(testPingNonBlocklist) + .then(testPingBlocklistSafebrowsingOff) + .then(testPingBlocklistSafebrowsingOn) + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some test failed with error " + e); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.registerCleanupFunction(cleanup); + SpecialPowers.pushPrefEnv({"set": [ + ["browser.send_pings", true], + ]}, runTest); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html new file mode 100644 index 0000000000..7e3ae97751 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1647681</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> + <script> + +async function runTests() { + await SpecialPowers.pushPrefEnv({set: [ + ["privacy.trackingprotection.annotate_channels", true], + ]}); + + var chromeScript; + chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let classifiedChannel = subject.QueryInterface(Ci.nsIClassifiedChannel); + if ( + !channel || + !classifiedChannel || + !channel.URI.spec.startsWith( + "https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" + ) + ) { + return; + } + + sendAsyncMessage("last-channel-flags", { + classified: Ci.nsIClassifiedChannel.CLASSIFIED_TRACKING == classifiedChannel.classificationFlags, + }); + } + + addMessageListener("done", __ => { + Services.obs.removeObserver(onExamResp, "http-on-examine-response"); + }); + + Services.obs.addObserver(onExamResp, "http-on-examine-response"); + + sendAsyncMessage("start-test"); + }); + + chromeScript.addMessageListener( + "start-test", + async _ => { + window.open("https://example.org/tests/toolkit/components/url-classifier/tests/mochitest/sandboxed.html") + }, + { once: true } + ); + + chromeScript.addMessageListener( + "last-channel-flags", + data => { + ok(data.classified, "tracker script should be classified when the top-level is sandboxed"); + chromeScript.sendAsyncMessage("done"); + SimpleTest.finish(); + }, + { once: true } + ); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html new file mode 100644 index 0000000000..cbb6e67c78 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html @@ -0,0 +1,163 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1262406 - Track element doesn't use the URL classifier.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + const PREF = "browser.safebrowsing.malware.enabled"; + const track_path = "tests/toolkit/components/url-classifier/tests/mochitest/basic.vtt"; + const malware_url = "http://malware.example.com/" + track_path; + const validtrack_url = "http://mochi.test:8888/" + track_path; + + var video = document.createElement("video"); + video.src = "seek.webm"; + video.crossOrigin = "anonymous"; + + document.body.appendChild(video); + + function testValidTrack() { + SpecialPowers.setBoolPref(PREF, true); + + return new Promise(function(resolve, reject) { + var track = document.createElement("track"); + track.track.mode = "hidden"; + track.src = validtrack_url; + video.appendChild(track); + + function onload() { + ok(true, "Track should be loaded when url is not in blocklist"); + finish(); + } + + function onerror() { + ok(false, "Error while loading track"); + finish(); + } + + function finish() { + track.removeEventListener("load", onload); + track.removeEventListener("error", onerror); + resolve(); + } + + track.addEventListener("load", onload); + track.addEventListener("error", onerror); + }); + } + + function testBlocklistTrackSafebrowsingOff() { + SpecialPowers.setBoolPref(PREF, false); + + return new Promise(function(resolve, reject) { + var track = document.createElement("track"); + track.track.mode = "hidden"; + track.src = malware_url; + video.appendChild(track); + + function onload() { + ok(true, "Track should be loaded when url is in blocklist and safebrowsing is off"); + finish(); + } + + function onerror() { + ok(false, "Error while loading track"); + finish(); + } + + function finish() { + track.removeEventListener("load", onload); + track.removeEventListener("error", onerror); + resolve(); + } + + track.addEventListener("load", onload); + track.addEventListener("error", onerror); + }); + } + + function testBlocklistTrackSafebrowsingOn() { + SpecialPowers.setBoolPref(PREF, true); + + return new Promise(function(resolve, reject) { + var track = document.createElement("track"); + track.track.mode = "hidden"; + // Add a query string parameter here to avoid url classifier bypass classify + // because of cache. + track.src = malware_url + "?testsbon"; + video.appendChild(track); + + function onload() { + ok(false, "Unexpected result while loading track in blocklist"); + finish(); + } + + function onerror() { + ok(true, "Track should not be loaded when url is in blocklist and safebrowsing is on"); + finish(); + } + + function finish() { + track.removeEventListener("load", onload); + track.removeEventListener("error", onerror); + resolve(); + } + + track.addEventListener("load", onload); + track.addEventListener("error", onerror); + }); + } + + function cleanup() { + SpecialPowers.clearUserPref(PREF); + } + + function setup() { + var testData = [ + { url: "malware.example.com/", + db: "test-malware-simple", + }, + ]; + + return classifierHelper.addUrlToDB(testData) + .catch(function(err) { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); + } + + function runTest() { + Promise.resolve() + .then(classifierHelper.waitForInit) + .then(setup) + .then(testValidTrack) + .then(testBlocklistTrackSafebrowsingOff) + .then(testBlocklistTrackSafebrowsingOn) + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some test failed with error " + e); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.registerCleanupFunction(cleanup); + SpecialPowers.pushPrefEnv({"set": [ + ["urlclassifier.malwareTable", "test-malware-simple"], + ]}, runTest); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html b/toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html new file mode 100644 index 0000000000..0098d6c489 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the cryptomining classifier</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +var tests = [ + // All disabled. + { config: [ false, false ], loadExpected: true }, + + // Just entitylisted. + { config: [ false, true ], loadExpected: true }, + + // Just blocklisted. + { config: [ true, false ], loadExpected: false }, + + // entitylist + blocklist: entitylist wins + { config: [ true, true ], loadExpected: true }, +]; + +function prefValue(value, what) { + return value ? what : ""; +} + +async function runTest(test) { + await SpecialPowers.pushPrefEnv({set: [ + [ "urlclassifier.features.cryptomining.blacklistHosts", prefValue(test.config[0], "example.com") ], + [ "urlclassifier.features.cryptomining.whitelistHosts", prefValue(test.config[1], "mochi.test,mochi.xorigin-test") ], + [ "urlclassifier.features.cryptomining.blacklistTables", prefValue(test.config[0], "mochitest1-track-simple") ], + [ "urlclassifier.features.cryptomining.whitelistTables", "" ], + [ "privacy.trackingprotection.enabled", false ], + [ "privacy.trackingprotection.annotate_channels", false ], + [ "privacy.trackingprotection.cryptomining.enabled", true ], + [ "privacy.trackingprotection.emailtracking.enabled", false ], + [ "privacy.trackingprotection.fingerprinting.enabled", false ], + [ "privacy.trackingprotection.socialtracking.enabled", false ], + ]}); + + info("Testing: " + JSON.stringify(test.config) + "\n"); + + // Let's load an image with a random query string, just to avoid network cache. + let result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.loadExpected, "The loading happened correctly"); + + // Let's load an image with a random query string, just to avoid network cache. + result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://tracking.example.org/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.loadExpected, "The loading happened correctly (by table)"); +} + +async function runTests() { + let chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" + ); + + addMessageListener("loadTrackers", __ => { + return UrlClassifierTestUtils.addTestTrackers(); + }); + + addMessageListener("unloadTrackers", __ => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); + }); + + await chromeScript.sendQuery("loadTrackers"); + + for (let test in tests) { + await runTest(tests[test]); + } + + await chromeScript.sendQuery("unloadTrackers"); + + chromeScript.destroy(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_cryptomining_annotate.html b/toolkit/components/url-classifier/tests/mochitest/test_cryptomining_annotate.html new file mode 100644 index 0000000000..ecf5cd02a4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_cryptomining_annotate.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the relationship between annotation vs blocking - cryptomining</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="features.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +runTests(SpecialPowers.Ci.nsIClassifiedChannel.CLASSIFIED_CRYPTOMINING, + [ + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", false], + ["urlclassifier.features.fingerprinting.annotate.blacklistHosts", ""], + ["urlclassifier.features.fingerprinting.annotate.blacklistTables", ""], + ["privacy.trackingprotection.fingerprinting.enabled", false], + ["privacy.trackingprotection.cryptomining.enabled", true], + ["urlclassifier.features.socialtracking.annotate.blacklistHosts", ""], + ["urlclassifier.features.socialtracking.annotate.blacklistTables", ""], + ["privacy.trackingprotection.socialtracking.enabled", false], + ["privacy.trackingprotection.emailtracking.enabled", false], + ], + false /* a tracking resource */); +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html b/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html new file mode 100644 index 0000000000..96278ae49d --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1258033 - Fix the DNT loophole for tracking protection</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; + +const tests = [ + // DNT turned on and TP turned off, DNT signal sent in both private browsing + // and normal mode. + { + setting: {dntPref: true, tpPref: false, tppbPref: false, pbMode: true}, + expected: {dnt: "1"}, + }, + { + setting: {dntPref: true, tpPref: false, tppbPref: false, pbMode: false}, + expected: {dnt: "1"}, + }, + // DNT turned off and TP turned on globally, DNT signal sent in both private + // browsing and normal mode. + { + setting: {dntPref: false, tpPref: true, tppbPref: false, pbMode: true}, + expected: {dnt: "1"}, + }, + { + setting: {dntPref: false, tpPref: true, tppbPref: false, pbMode: false}, + expected: {dnt: "1"}, + }, + // DNT turned off and TP in Private Browsing only, DNT signal sent in private + // browsing mode only. + { + setting: {dntPref: false, tpPref: false, tppbPref: true, pbMode: true}, + expected: {dnt: "1"}, + }, + { + setting: {dntPref: false, tpPref: false, tppbPref: true, pbMode: false}, + expected: {dnt: "unspecified"}, + }, + // DNT turned off and TP turned off, DNT signal is never sent. + { + setting: {dntPref: false, tpPref: false, tppbPref: false, pbMode: true}, + expected: {dnt: "unspecified"}, + }, + { + setting: {dntPref: false, tpPref: false, tppbPref: false, pbMode: false}, + expected: {dnt: "unspecified"}, + }, +]; + +const DNT_PREF = "privacy.donottrackheader.enabled"; +const TP_PREF = "privacy.trackingprotection.enabled"; +const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled"; + +const contentPage = + "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/dnt.html"; + +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function executeTest(test) { + SpecialPowers.pushPrefEnv({"set": [ + [DNT_PREF, test.setting.dntPref], + [TP_PREF, test.setting.tpPref], + [TP_PB_PREF, test.setting.tppbPref], + ]}); + + var win = mainWindow.OpenBrowserWindow({private: test.setting.pbMode}); + + return new Promise(function(resolve, reject) { + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + return; + } + + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("message", function(event) { + let [key, value] = event.data.split("="); + if (key == "finish") { + win.close(); + resolve(); + } else if (key == "navigator.doNotTrack") { + is(value, test.expected.dnt, "navigator.doNotTrack should be " + test.expected.dnt); + } else if (key == "DNT") { + let msg = test.expected.dnt == "1" ? "" : "not "; + is(value, test.expected.dnt, "DNT header should " + msg + "be sent"); + } else { + ok(false, "unexpected message"); + } + }); + }, true); + SimpleTest.executeSoon(function() { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + }); + }); + }, {capture: true, once: true}); + }); +} + +let loop = function loop(index) { + if (index >= tests.length) { + SimpleTest.finish(); + return; + } + + let test = tests[index]; + let next = function next() { + loop(index + 1); + }; + let result = executeTest(test); + result.then(next, next); +}; + +SimpleTest.waitForExplicitFinish(); +loop(0); + +</script> + +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_emailtracking.html b/toolkit/components/url-classifier/tests/mochitest/test_emailtracking.html new file mode 100644 index 0000000000..fd2996997c --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_emailtracking.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the email tracking classifier</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +var tests = [ + // All disabled. + { config: [ false, false ], loadExpected: true }, + + // Just entitylisted. + { config: [ false, true ], loadExpected: true }, + + // Just blocklisted. + { config: [ true, false ], loadExpected: false }, + + // entitylist + blocklist: entitylist wins + { config: [ true, true ], loadExpected: true }, +]; + +function prefValue(value, what) { + return value ? what : ""; +} + +async function runTest(test) { + await SpecialPowers.pushPrefEnv({set: [ + [ "urlclassifier.features.emailtracking.blocklistHosts", prefValue(test.config[0], "example.com") ], + [ "urlclassifier.features.emailtracking.allowlistHosts", prefValue(test.config[1], "mochi.test,mochi.xorigin-test") ], + [ "urlclassifier.features.emailtracking.blocklistTables", prefValue(test.config[0], "mochitest5-track-simple") ], + [ "urlclassifier.features.emailtracking.allowlistTables", "" ], + [ "privacy.trackingprotection.enabled", false ], + [ "privacy.trackingprotection.annotate_channels", false ], + [ "privacy.trackingprotection.cryptomining.enabled", false ], + [ "privacy.trackingprotection.emailtracking.enabled", true ], + [ "privacy.trackingprotection.fingerprinting.enabled", false ], + [ "privacy.trackingprotection.socialtracking.enabled", false ], + ]}); + + info("Testing: " + JSON.stringify(test.config) + "\n"); + + // Let's load an image with a random query string to avoid network cache. + let result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.loadExpected, "Image loading happened correctly"); + + // Let's load an image with a random query string to avoid network cache. + result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://email-tracking.example.org/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.loadExpected, "Image loading happened correctly (by table)"); + + // Let's load a script with a random query string to avoid network cache. + result = await new Promise(resolve => { + let script = document.createElement("script"); + script.setAttribute( + "src", + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js?" + + Math.random() + ); + script.onload = _ => resolve(true); + script.onerror = _ => resolve(false); + document.body.appendChild(script); + }); + + is(result, test.loadExpected, "Script loading happened correctly"); + + // Let's load a script with a random query string to avoid network cache. + result = await new Promise(resolve => { + let script = document.createElement("script"); + script.setAttribute( + "src", + "http://email-tracking.example.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js?" + + Math.random() + ); + script.onload = _ => resolve(true); + script.onerror = _ => resolve(false); + document.body.appendChild(script); + }); + + is(result, test.loadExpected, "Script loading happened correctly (by table)"); +} + +async function runTests() { + let chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" + ); + + addMessageListener("loadTrackers", __ => { + return UrlClassifierTestUtils.addTestTrackers(); + }); + + addMessageListener("unloadTrackers", __ => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); + }); + + await chromeScript.sendQuery("loadTrackers"); + + for (let test in tests) { + await runTest(tests[test]); + } + + await chromeScript.sendQuery("unloadTrackers"); + + chromeScript.destroy(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html b/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html new file mode 100644 index 0000000000..81e6f9a466 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the fingerprinting classifier</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +var tests = [ + // All disabled. + { config: [ false, false ], imgLoadExpected: true, scriptLoadExpected: true }, + + // Just entitylisted. + { config: [ false, true ], imgLoadExpected: true, scriptLoadExpected: true }, + + // Just blocklisted. + { config: [ true, false ], imgLoadExpected: true, scriptLoadExpected: false }, + + // entitylist + blocklist: entitylist wins + { config: [ true, true ], imgLoadExpected: true, scriptLoadExpected: true }, +]; + +function prefValue(value, what) { + return value ? what : ""; +} + +async function runTest(test) { + await SpecialPowers.pushPrefEnv({set: [ + [ "urlclassifier.features.fingerprinting.blacklistHosts", prefValue(test.config[0], "example.com") ], + [ "urlclassifier.features.fingerprinting.whitelistHosts", prefValue(test.config[1], "mochi.test,mochi.xorigin-test") ], + [ "urlclassifier.features.fingerprinting.blacklistTables", prefValue(test.config[0], "mochitest1-track-simple") ], + [ "urlclassifier.features.fingerprinting.whitelistTables", "" ], + [ "privacy.trackingprotection.enabled", false ], + [ "privacy.trackingprotection.annotate_channels", false ], + [ "privacy.trackingprotection.cryptomining.enabled", false ], + [ "privacy.trackingprotection.emailtracking.enabled", false ], + [ "privacy.trackingprotection.fingerprinting.enabled", true ], + [ "privacy.trackingprotection.socialtracking.enabled", false ], + ]}); + + info("Testing: " + JSON.stringify(test.config) + "\n"); + + // Let's load an image with a random query string to avoid network cache. + let result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.imgLoadExpected, "Image loading happened correctly"); + + // Let's load an image with a random query string to avoid network cache. + result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://tracking.example.org/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.imgLoadExpected, "Image loading happened correctly (by table)"); + + // Let's load a script with a random query string to avoid network cache. + result = await new Promise(resolve => { + let script = document.createElement("script"); + script.setAttribute( + "src", + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js?" + + Math.random() + ); + script.onload = _ => resolve(true); + script.onerror = _ => resolve(false); + document.body.appendChild(script); + }); + + is(result, test.scriptLoadExpected, "Script loading happened correctly"); + + // Let's load a script with a random query string to avoid network cache. + result = await new Promise(resolve => { + let script = document.createElement("script"); + script.setAttribute( + "src", + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js?" + + Math.random() + ); + script.onload = _ => resolve(true); + script.onerror = _ => resolve(false); + document.body.appendChild(script); + }); + + is(result, test.scriptLoadExpected, "Script loading happened correctly"); +} + +async function runTests() { + let chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" + ); + + addMessageListener("loadTrackers", __ => { + return UrlClassifierTestUtils.addTestTrackers(); + }); + + addMessageListener("unloadTrackers", __ => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); + }); + + await chromeScript.sendQuery("loadTrackers"); + + for (let test in tests) { + await runTest(tests[test]); + } + + await chromeScript.sendQuery("unloadTrackers"); + + chromeScript.destroy(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting_annotate.html b/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting_annotate.html new file mode 100644 index 0000000000..0bc01645b1 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting_annotate.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the relationship between annotation vs blocking - fingerprinting</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="features.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +runTests(SpecialPowers.Ci.nsIClassifiedChannel.CLASSIFIED_FINGERPRINTING, + [ + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", false], + ["privacy.trackingprotection.fingerprinting.enabled", true], + ["urlclassifier.features.cryptomining.annotate.blacklistHosts", ""], + ["urlclassifier.features.cryptomining.annotate.blacklistTables", ""], + ["privacy.trackingprotection.cryptomining.enabled", false], + ["urlclassifier.features.socialtracking.annotate.blacklistHosts", ""], + ["urlclassifier.features.socialtracking.annotate.blacklistTables", ""], + ["privacy.trackingprotection.socialtracking.enabled", false], + ["privacy.trackingprotection.emailtracking.enabled", false], + ], + true /* a tracking resource */); +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_gethash.html b/toolkit/components/url-classifier/tests/mochitest/test_gethash.html new file mode 100644 index 0000000000..b9d3a2fd44 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_gethash.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1272239 - Test gethash.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<iframe id="testFrame1" onload=""></iframe> +<iframe id="testFrame2" onload=""></iframe> + +<script src="head.js"></script> +<script class="testbody" type="text/javascript"> +const MALWARE_LIST = "test-malware-simple"; +const MALWARE_HOST = "malware.example.com/"; + +const UNWANTED_LIST = "test-unwanted-simple"; +const UNWANTED_HOST = "unwanted.example.com/"; + +const GETHASH_URL = "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/gethash.sjs"; +const NOTEXIST_URL = "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/nonexistserver.sjs"; + +var shouldLoad = false; + +// In this testcase we store prefixes to localdb and send the fullhash to gethash server. +// When access the test page gecko should trigger gethash request to server and +// get the completion response. +function loadTestFrame(id) { + return new Promise(function(resolve, reject) { + var iframe = document.getElementById(id); + iframe.setAttribute("src", "gethashFrame.html"); + + iframe.onload = function() { + resolve(); + }; + }); +} + +// add 4-bytes prefixes to local database, so when we access the url, +// it will trigger gethash request. +function addPrefixToDB(list, url) { + var testData = [{ db: list, url, len: 4 }]; + + return classifierHelper.addUrlToDB(testData) + .catch(function(err) { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +function setup404() { + shouldLoad = true; + + return Promise.all([ + classifierHelper.allowCompletion( + [MALWARE_LIST, UNWANTED_LIST], NOTEXIST_URL), + addPrefixToDB(MALWARE_LIST, MALWARE_HOST), + addPrefixToDB(UNWANTED_LIST, UNWANTED_HOST), + ]); +} + +function setup() { + return Promise.all([ + classifierHelper.allowCompletion( + [MALWARE_LIST, UNWANTED_LIST], GETHASH_URL), + addPrefixToDB(MALWARE_LIST, MALWARE_HOST), + addPrefixToDB(UNWANTED_LIST, UNWANTED_HOST), + addCompletionToServer(MALWARE_LIST, MALWARE_HOST, GETHASH_URL), + addCompletionToServer(UNWANTED_LIST, UNWANTED_HOST, GETHASH_URL), + ]); +} + +// manually reset DB to make sure next test won't be affected by cache. +function reset() { + return classifierHelper.resetDatabase(); +} + +function runTest() { + Promise.resolve() + // This test resources get blocked when gethash returns successfully + .then(classifierHelper.waitForInit) + .then(setup) + .then(() => loadTestFrame("testFrame1")) + .then(reset) + // This test resources are not blocked when gethash returns an error + .then(setup404) + .then(() => loadTestFrame("testFrame2")) + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some test failed with error " + e); + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); + +// 'network.predictor.enabled' is disabled because if other testcase load +// evil.js, evil.css ...etc resources, it may cause we load them from cache +// directly and bypass classifier check +SpecialPowers.pushPrefEnv({"set": [ + ["browser.safebrowsing.malware.enabled", true], + ["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"], + ["network.predictor.enabled", false], + ["urlclassifier.gethash.timeout_ms", 30000], +]}, runTest); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html new file mode 100644 index 0000000000..0959ecf42e --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html @@ -0,0 +1,153 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test Tracking Protection in Private Browsing mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; +var contentPage = "https://www.itisatrap.org/chrome/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html"; + +const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function testOnWindow(aPrivate) { + return new Promise((resolve, reject) => { + let win = mainWindow.OpenBrowserWindow({private: aPrivate}); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("load", function innerLoad2() { + win.content.removeEventListener("load", innerLoad2); + SimpleTest.executeSoon(function() { + resolve(win); + }); + }, false, true); + }, true); + SimpleTest.executeSoon(function() { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + }); + }); + }, {capture: true, once: true}); + }); +} + +var badids = [ + "badscript", + "badimage", + "badcss", +]; + +function checkLoads(aWindow, aBlocked) { + var win = aWindow.content; + is(win.document.getElementById("badscript").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking javascript"); + is(win.document.getElementById("badimage").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking images"); + is(win.document.getElementById("goodscript").dataset.touched, "yes", "Should load entitylisted tracking javascript"); + is(win.document.getElementById("goodimage").dataset.touched, "yes", "Should load non-blocklisted image"); + + var elt = win.document.getElementById("styleCheck"); + var style = win.document.defaultView.getComputedStyle(elt); + isnot(style.visibility, aBlocked ? "hidden" : "", "Should not load tracking css"); + + is(win.document.blockedNodeByClassifierCount, aBlocked ? badids.length : 0, "Should identify all tracking elements"); + + var blockedNodes = win.document.blockedNodesByClassifier; + + // Make sure that every node in blockedNodes exists in the tree + // (that may not always be the case but do not expect any nodes to disappear + // from the tree here) + var allNodeMatch = true; + for (let i = 0; i < blockedNodes.length; i++) { + let nodeMatch = false; + for (let j = 0; j < badids.length && !nodeMatch; j++) { + nodeMatch = nodeMatch || + (blockedNodes[i] == win.document.getElementById(badids[j])); + } + + allNodeMatch = allNodeMatch && nodeMatch; + } + is(allNodeMatch, true, "All annotated nodes are expected in the tree"); + + // Make sure that every node with a badid (see badids) is found in the + // blockedNodes. This tells us if we are neglecting to annotate + // some nodes + allNodeMatch = true; + for (let j = 0; j < badids.length; j++) { + let nodeMatch = false; + for (let i = 0; i < blockedNodes.length && !nodeMatch; i++) { + nodeMatch = nodeMatch || + (blockedNodes[i] == win.document.getElementById(badids[j])); + } + + allNodeMatch = allNodeMatch && nodeMatch; + } + is(allNodeMatch, aBlocked, "All tracking nodes are expected to be annotated as such"); +} + +SpecialPowers.pushPrefEnv( + {"set": [ + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.pbmode.enabled", true], + ["privacy.trackingprotection.testing.report_blocked_node", true], + ]}, test); + +async function test() { + SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers); + await UrlClassifierTestUtils.addTestTrackers(); + + // Normal mode, with the pref (trackers should be loaded) + await testOnWindow(false).then(function(aWindow) { + checkLoads(aWindow, false); + aWindow.close(); + }); + + // Private Browsing, with the pref (trackers should be blocked) + await testOnWindow(true).then(function(aWindow) { + checkLoads(aWindow, true); + aWindow.close(); + }); + + // Private Browsing, without the pref (trackers should be loaded) + await SpecialPowers.setBoolPref("privacy.trackingprotection.pbmode.enabled", false); + await testOnWindow(true).then(function(aWindow) { + checkLoads(aWindow, false); + aWindow.close(); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html b/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html new file mode 100644 index 0000000000..36d128cdf7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html @@ -0,0 +1,217 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test report matched URL info (Bug #1288633)</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +; +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +var mainWindow = window.browsingContext.topChromeWindow; +const SJS = "mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs"; +const BASE_URL = "http://" + SJS + "?"; + +var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p}); + +function addUrlToDB(list, url) { + let testData = [{ db: list, url}]; + + return classifierHelper.addUrlToDB(testData) + .catch(function(err) { + ok(false, "Couldn't update classifier. Error code: " + err); + // Abort test. + SimpleTest.finish(); + }); +} + +function setupTestData(data) { + let promises = []; + let providerList = "browser.safebrowsing.provider." + data.provider + ".lists"; + if (!Services.prefs.prefHasUserValue(providerList)) { + promises.push(pushPrefs([providerList, data.list])); + } else { + let pref = SpecialPowers.getCharPref(providerList); + pref += "," + data.list; + promises.push(pushPrefs([providerList, pref])); + } + + let activeTablePref = "urlclassifier.phishTable"; + let activeTable = SpecialPowers.getCharPref(activeTablePref); + activeTable += "," + data.list; + promises.push(pushPrefs([activeTablePref, activeTable])); + + promises.push(addUrlToDB(data.list, data.testUrl)); + return Promise.all(promises); +} + +function testOnWindow(aTestData, aCallback, aTestCreater) { + return new Promise(resolve => { + let win = mainWindow.OpenBrowserWindow(); + + (async function() { + await TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win); + + let browser = win.gBrowser.selectedBrowser; + aTestCreater(win, browser, aTestData.topUrl, aTestData.testUrl); + + let notification = await BrowserTestUtils.waitForNotificationBar(win.gBrowser, browser, "blocked-badware-page"); + ok(notification, "Notification box should be displayed"); + + let buttons = notification.buttonContainer.getElementsByTagName("button"); + let button = buttons[1]; + if (aTestData.provider != "google" && aTestData.provider != "google4") { + is(button, undefined, "Report button should not be showed"); + win.close(); + resolve(); + return; + } + + button.click(); + + let newTabBrowser = win.gBrowser.selectedTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(newTabBrowser); + + aCallback(newTabBrowser); + win.close(); + resolve(); + })(); + }); +} + +var createBlockedIframe = function(aWindow, aBrowser, aTopUrl, aUrl) { + (async function() { + BrowserTestUtils.startLoadingURIString(aBrowser, aTopUrl); + await BrowserTestUtils.browserLoaded(aBrowser); + + await SpecialPowers.spawn(aBrowser, [aUrl], async function(url) { + return new Promise(resolve => { + let listener = e => { + docShell.chromeEventHandler.removeEventListener("AboutBlockedLoaded", listener, false, true); + resolve(); + }; + docShell.chromeEventHandler.addEventListener("AboutBlockedLoaded", listener, false, true); + let frame = content.document.getElementById("phishingFrame"); + frame.setAttribute("src", "http://" + url); + }); + }); + + let doc = aWindow.gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument; + let ignoreWarningLink = doc.getElementById("ignore_warning_link"); + ok(ignoreWarningLink, "Ignore warning link should exist"); + ignoreWarningLink.click(); + })(); +}; + +var createBlockedPage = function(aWindow, aBrowser, aTopUrl, aUrl) { + (async function() { + BrowserTestUtils.startLoadingURIString(aBrowser, aTopUrl); + await BrowserTestUtils.waitForContentEvent(aBrowser, "DOMContentLoaded"); + + let doc = aWindow.gBrowser.contentDocument; + let ignoreWarningLink = doc.getElementById("ignore_warning_link"); + ok(ignoreWarningLink, "Ignore warning link should exist"); + ignoreWarningLink.click(); + })(); +}; + +function checkReportURL(aReportBrowser, aUrl) { + let expectedReportUrl = BASE_URL + "action=reporturl&reporturl=" + encodeURIComponent(aUrl); + is(aReportBrowser.contentDocument.location.href, expectedReportUrl, "Correct report URL"); +} + +var testDatas = [ + { topUrl: "http://itisaphishingsite.org/phishing.html", + testUrl: "itisaphishingsite.org/phishing.html", + list: "mochi1-phish-simple", + provider: "google", + blockCreater: createBlockedPage, + expectedReportUri: "http://itisaphishingsite.org/phishing.html", + }, + + // Non-google provider, no report button is showed. + // Test provider needs a valid update URL (mozilla for example) otherwise + // the updates inserting the test data will fail. + { topUrl: "http://fakeitisaphishingsite.org/phishing.html", + testUrl: "fakeitisaphishingsite.org/phishing.html", + list: "fake-phish-simple", + provider: "mozilla", + blockCreater: createBlockedPage, + }, + + // Iframe case: + // A top level page at + // http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-iframe + // contains an iframe to http://phishing.example.com/test.html (blocked). + + { topUrl: "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-iframe", + testUrl: "phishing.example.com/test.html", + list: "mochi2-phish-simple", + provider: "google4", + blockCreater: createBlockedIframe, + expectedReportUri: "http://phishing.example.com/test.html", + }, + + // Redirect case: + // A top level page at + // http://prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect (blocked) + // will get redirected to + // https://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect. + { topUrl: "http://prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect", + testUrl: "prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect", + list: "mochi3-phish-simple", + provider: "google4", + blockCreater: createBlockedPage, + expectedReportUri: "http://prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs", + }, + +]; + +SpecialPowers.pushPrefEnv( + {"set": [["browser.safebrowsing.provider.google.reportPhishMistakeURL", BASE_URL + "action=reporturl&reporturl="], + ["browser.safebrowsing.provider.google4.reportPhishMistakeURL", BASE_URL + "action=reporturl&reporturl="], + ["browser.safebrowsing.phishing.enabled", true]]}, + test); + +function test() { + (async function() { + await classifierHelper.waitForInit(); + + for (let testData of testDatas) { + await setupTestData(testData); + await testOnWindow(testData, function(browser) { + checkReportURL(browser, testData.expectedReportUri); + }, testData.blockCreater); + + await classifierHelper._cleanup(); + } + + SimpleTest.finish(); + })(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html b/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html new file mode 100644 index 0000000000..475145591d --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1272239 - Only tables with provider could register gethash url in listmanager.</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +// List all the tables +const prefs = [ + "urlclassifier.phishTable", + "urlclassifier.malwareTable", + "urlclassifier.downloadBlockTable", + "urlclassifier.downloadAllowTable", + "urlclassifier.trackingTable", + "urlclassifier.trackingWhitelistTable", + "urlclassifier.blockedTable", +]; + +// Get providers +var providers = {}; + +var branch = SpecialPowers.Services.prefs.getBranch("browser.safebrowsing.provider."); +var children = branch.getChildList(""); + +for (var child of children) { + var prefComponents = child.split("."); + var providerName = prefComponents[0]; + providers[providerName] = {}; +} + +// Get lists from |browser.safebrowsing.provider.PROVIDER_NAME.lists| preference. +var listsWithProvider = []; +var listsToProvider = []; +for (let provider in providers) { + let pref = "browser.safebrowsing.provider." + provider + ".lists"; + let list = SpecialPowers.getCharPref(pref).split(","); + + listsToProvider = listsToProvider.concat(list.map( () => { return provider; })); + listsWithProvider = listsWithProvider.concat(list); +} + +// Get all the lists +var lists = []; +for (let pref of prefs) { + lists = lists.concat(SpecialPowers.getCharPref(pref).split(",")); +} + +var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]. + getService(Ci.nsIUrlListManager); + +let googleKey = SpecialPowers.Services.urlFormatter.formatURL("%GOOGLE_SAFEBROWSING_API_KEY%").trim(); + +for (let list of lists) { + if (!list) + continue; + + // For lists having a provider, it should have a correct gethash url + // For lists without a provider, for example, moztest-malware-simple, it should not + // have a gethash url. + var url = listmanager.getGethashUrl(list); + var index = listsWithProvider.indexOf(list); + if (index >= 0) { + let provider = listsToProvider[index]; + let pref = "browser.safebrowsing.provider." + provider + ".gethashURL"; + if ((provider == "google" || provider == "google4") && + (!googleKey || googleKey == "no-google-safebrowsing-api-key")) { + is(url, "", "getHash url of " + list + " should be empty"); + } else { + is(url, SpecialPowers.getCharPref(pref), list + " matches its gethash url"); + } + } else { + is(url, "", list + " should not have a gethash url"); + } +} + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html b/toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html new file mode 100644 index 0000000000..ef1114686f --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the socialtracking classifier</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +var tests = [ + // All disabled. + { config: [ false, false ], loadExpected: true }, + + // Just entitylisted. + { config: [ false, true ], loadExpected: true }, + + // Just blocklisted. + { config: [ true, false ], loadExpected: false }, + + // entitylist + blocklist: entitylist wins + { config: [ true, true ], loadExpected: true }, +]; + +function prefValue(value, what) { + return value ? what : ""; +} + +async function runTest(test) { + await SpecialPowers.pushPrefEnv({set: [ + [ "urlclassifier.features.socialtracking.blacklistHosts", prefValue(test.config[0], "example.com") ], + [ "urlclassifier.features.socialtracking.whitelistHosts", prefValue(test.config[1], "mochi.test,mochi.xorigin-test") ], + [ "urlclassifier.features.socialtracking.blacklistTables", prefValue(test.config[0], "mochitest1-track-simple") ], + [ "urlclassifier.features.socialtracking.whitelistTables", "" ], + [ "privacy.trackingprotection.enabled", false ], + [ "privacy.trackingprotection.annotate_channels", false ], + [ "privacy.trackingprotection.cryptomining.enabled", false ], + [ "privacy.trackingprotection.emailtracking.enabled", false ], + [ "privacy.trackingprotection.fingerprinting.enabled", false ], + [ "privacy.trackingprotection.socialtracking.enabled", true ], + ]}); + + info("Testing: " + JSON.stringify(test.config) + "\n"); + + // Let's load an image with a random query string, just to avoid network cache. + let result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.loadExpected, "The loading happened correctly"); + + // Let's load an image with a random query string, just to avoid network cache. + result = await new Promise(resolve => { + let image = new Image(); + image.src = "http://tracking.example.org/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?" + Math.random(); + image.onload = _ => resolve(true); + image.onerror = _ => resolve(false); + }); + + is(result, test.loadExpected, "The loading happened correctly (by table)"); +} + +async function runTests() { + let chromeScript = SpecialPowers.loadChromeScript(_ => { + /* eslint-env mozilla/chrome-script */ + const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" + ); + + addMessageListener("loadTrackers", __ => { + UrlClassifierTestUtils.addTestTrackers().then(___ => { + sendAsyncMessage("trackersLoaded"); + }); + }); + + addMessageListener("unloadTrackers", __ => { + UrlClassifierTestUtils.cleanupTestTrackers(); + sendAsyncMessage("trackersUnloaded"); + }); + }); + + await new Promise(resolve => { + chromeScript.addMessageListener("trackersLoaded", resolve); + chromeScript.sendAsyncMessage("loadTrackers"); + }); + + for (let test in tests) { + await runTest(tests[test]); + } + + await new Promise(resolve => { + chromeScript.addMessageListener("trackersUnloaded", resolve); + chromeScript.sendAsyncMessage("unloadTrackers"); + }); + + chromeScript.destroy(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_socialtracking_annotate.html b/toolkit/components/url-classifier/tests/mochitest/test_socialtracking_annotate.html new file mode 100644 index 0000000000..f12dcf11ec --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_socialtracking_annotate.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the relationship between annotation vs blocking - socialtracking</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="features.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<script class="testbody" type="text/javascript"> + +runTests(SpecialPowers.Ci.nsIClassifiedChannel.CLASSIFIED_SOCIALTRACKING, + [ + ["privacy.socialtracking.block_cookies.enabled", false], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", false], + ["urlclassifier.features.fingerprinting.annotate.blacklistHosts", ""], + ["urlclassifier.features.fingerprinting.annotate.blacklistTables", ""], + ["privacy.trackingprotection.fingerprinting.enabled", false], + ["urlclassifier.features.cryptomining.annotate.blacklistHosts", ""], + ["urlclassifier.features.cryptomining.annotate.blacklistTables", ""], + ["privacy.trackingprotection.cryptomining.enabled", false], + ["privacy.trackingprotection.socialtracking.enabled", true], + ["privacy.trackingprotection.emailtracking.enabled", false], + ], + false /* a tracking resource */); +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html b/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html new file mode 100644 index 0000000000..341d26fa3f --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html @@ -0,0 +1,235 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test threathit repoty </title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script src="head.js"></script> +<script class="testbody" type="text/javascript"> +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); + +var mainWindow = window.browsingContext.topChromeWindow; + +var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]. + getService(Ci.nsIUrlListManager); +const SJS = "mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/threathit.sjs"; + +function hash(str) { + let hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + + let data = new TextEncoder().encode(str); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + + return hasher.finish(false); +} + +var testDatas = [ + { url: "itisaphishingsite1.org/phishing.html", + list: "test-phish-proto", + provider: "test", + // The base64 of binary protobuf representation of response: + // + // [ + // { + // 'threat_type': 2, // SOCIAL_ENGINEERING_PUBLIC + // 'response_type': 2, // FULL_UPDATE + // 'new_client_state': 'sta\x0te', // NEW_CLIENT_STATE + // 'additions': { 'compression_type': RAW, + // 'prefix_size': 1, + // 'raw_hashes': "xxxx"} // hash prefix of url itisaphishingsite.org/phishing.html + // 'minimumWaitDuration': "8.1s", + // } + // ] + // + updateProtobuf: "ChoIAiACKgwIARIICAQSBM9UdYs6BnN0YQB0ZRIECAwQCg==", + // The base64 of binary protobuf representation of response: + // { + // "matches": [ + // { + // "threat_type": 2, // SOCIAL_ENGINEERING_PUBLIC + // "threat": { + // "hash": string, + // }, + // "cacheDuration": "8.1", + // } + // ], + // "minimumWaitDuration": 12.0..1, + // "negativeCacheDuration": 12.0..1, + // } + fullhashProtobuf: "CiwIAhoiCiDPVHWLptJSc/UYiabk1/wo5OkJqbggiylVKISK28bfeSoECAwQChIECAwQChoECAwQCg==", + }, +]; + +function addDataV4ToServer(list, type, data) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest; + let params = new URLSearchParams(); + params.append("action", "store"); + params.append("list", list); + params.append("type", type); + params.append("data", data); + + xhr.open("PUT", "http://" + SJS + "?" + params.toString(), true); + xhr.setRequestHeader("Content-Type", "text/plain"); + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + resolve(); + } + }; + xhr.send(); + }); +} +/** + * Grabs the results via XHR + */ +function checkResults(aTestdata, aExpected) { + let xhr = new XMLHttpRequest(); + xhr.responseType = "text"; + xhr.onload = function() { + is(aExpected, xhr.response, "Correct report request"); + SimpleTest.finish(); + }; + xhr.onerror = function() { + ok(false, "Can't get results from server."); + SimpleTest.finish(); + }; + let params = new URLSearchParams(); + params.append("action", "getreport"); + params.append("list", aTestdata.list); + let url = "http://" + SJS + "?" + params.toString(); + + xhr.open("GET", url, true); + xhr.setRequestHeader("Content-Type", "text/plain"); + xhr.send(); +} + +function waitForUpdate(data) { + listmanager.checkForUpdates(data.updateUrl); + return new Promise(resolve => { + Services.obs.addObserver(function observer(aSubject, aTopic) { + Services.obs.removeObserver(observer, aTopic); + resolve(); + }, "safebrowsing-update-finished"); + }); +} + +function addUpdateDataV4ToServer(list, data) { + return addDataV4ToServer(list, "update", data); +} + +function addFullhashV4DataToServer(list, data) { + return addDataV4ToServer(list, "fullhash", data); +} + +function setupTestData(data) { + let updateParams = new URLSearchParams(); + updateParams.append("action", "get"); + updateParams.append("list", data.list); + updateParams.append("type", "update"); + data.updateUrl = "http://" + SJS + "?" + updateParams.toString(); + + let gethashParams = new URLSearchParams(); + gethashParams.append("action", "get"); + gethashParams.append("list", data.list); + gethashParams.append("type", "fullhash"); + data.gethashUrl = "http://" + SJS + "?" + gethashParams.toString(); + + listmanager.registerTable(data.list, + data.provider, + data.updateUrl, + data.gethashUrl); + + let promises = []; + let activeTablePref = "urlclassifier.phishTable"; + let activeTable = SpecialPowers.getCharPref(activeTablePref); + activeTable += "," + data.list; + + let reportPref = "browser.safebrowsing.provider." + data.provider + ".dataSharingURL"; + let reportParams = new URLSearchParams(); + reportParams.append("action", "report"); + reportParams.append("list", data.list); + data.reportUrl = "http://" + SJS + "?" + reportParams.toString(); + + let reportEnabledPref = "browser.safebrowsing.provider." + data.provider + ".dataSharing.enabled"; + + promises.push(pushPrefs([reportPref, data.reportUrl])); + promises.push(pushPrefs([reportEnabledPref, true])); + promises.push(pushPrefs([activeTablePref, activeTable])); + promises.push(addUpdateDataV4ToServer(data.list, data.updateProtobuf)); + promises.push(addFullhashV4DataToServer(data.list, data.fullhashProtobuf)); + return Promise.all(promises); +} + +function testOnWindow(aTestData) { + return new Promise(resolve => { + let win = mainWindow.OpenBrowserWindow(); + + (async function() { + await new Promise(rs => whenDelayedStartupFinished(win, rs)); + + let expected; + let browser = win.gBrowser.selectedBrowser; + let progressListener = { + onContentBlockingEvent(aWebProgress, aRequest, aEvent) { + expected = aTestData.reportUrl; + }, + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + }; + win.gBrowser.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_CONTENT_BLOCKING); + + BrowserTestUtils.startLoadingURIString(browser, aTestData.url); + await BrowserTestUtils.browserLoaded( + browser, + false, + `http://${aTestData.url}`, + true + ); + checkResults(aTestData, expected); + win.close(); + resolve(); + })(); + }); +} +SpecialPowers.pushPrefEnv( + {"set": [ + ["browser.safebrowsing.phishing.enabled", true], + ["dom.testing.sync-content-blocking-notifications", true], + ]}, + test); + +function test() { + (async function() { + await classifierHelper.waitForInit(); + + for (let testData of testDatas) { + await setupTestData(testData); + await waitForUpdate(testData); + await testOnWindow(testData); + await classifierHelper._cleanup(); + } + })(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html new file mode 100644 index 0000000000..098d6098d9 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test Tracking Protection with and without Safe Browsing (Bug #1157081)</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; +var contentPage = "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html"; + +const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function testOnWindow(aCallback) { + var win = mainWindow.OpenBrowserWindow(); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("load", function innerLoad2() { + win.content.removeEventListener("load", innerLoad2); + SimpleTest.executeSoon(function() { aCallback(win); }); + }, false, true); + }, true); + SimpleTest.executeSoon(function() { + win.gBrowser.loadURI(Services.io.newURI(contentPage), { + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + }); + }); + }); + }, {capture: true, once: true}); +} + +var badids = [ + "badscript", +]; + +function checkLoads(aWindow, aBlocked) { + var win = aWindow.content; + is(win.document.getElementById("badscript").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking javascript"); +} + +SpecialPowers.pushPrefEnv( + {"set": [["urlclassifier.trackingTable", "moztest-track-simple"], + ["privacy.trackingprotection.enabled", true], + ["browser.safebrowsing.malware.enabled", false], + ["browser.safebrowsing.phishing.enabled", false], + ["channelclassifier.allowlist_example", true]]}, + test); + +function test() { + SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers); + UrlClassifierTestUtils.addTestTrackers().then(() => { + // Safe Browsing turned OFF, tracking protection should work nevertheless + testOnWindow(function(aWindow) { + checkLoads(aWindow, true); + aWindow.close(); + + // Safe Browsing turned ON, tracking protection should still work + SpecialPowers.setBoolPref("browser.safebrowsing.phishing.enabled", true); + testOnWindow(function(aWindow1) { + checkLoads(aWindow1, true); + aWindow1.close(); + SimpleTest.finish(); + }); + }); + }); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html new file mode 100644 index 0000000000..31c69ac293 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html @@ -0,0 +1,164 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test Bug 1312515</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> + +<p><b>To see more of what is happening: <code>export MOZ_LOG=nsChannelClassifier:3</code></b></p> + +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; +var contentPage = "http://www.itisatrap.org/chrome/toolkit/components/url-classifier/tests/mochitest/trackingRequest.html"; + +const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function testOnWindow(aPrivate, aCallback) { + var win = mainWindow.OpenBrowserWindow({private: aPrivate}); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("load", function innerLoad2() { + win.content.removeEventListener("load", innerLoad2); + SimpleTest.executeSoon(function() { aCallback(win); }); + }, false, true); + }, true); + SimpleTest.executeSoon(function () { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + }); + }); + }, {capture: true, once: true}); +} + +const topic = "http-on-before-connect"; +var testUrl; +var testWindow; +var resolve; + +function checkLowestPriority(aSubject) { + checkPriority(aSubject, checkLowestPriority, Ci.nsISupportsPriority.PRIORITY_LOWEST, "Priority should be lowest."); +} + +function checkNormalPriority(aSubject) { + checkPriority(aSubject, checkNormalPriority, Ci.nsISupportsPriority.PRIORITY_NORMAL, "Priority should be normal."); +} + +function checkPriority(aSubject, aCallback, aPriority, aMessage) { + var channel = aSubject.QueryInterface(Ci.nsIChannel); + info("Channel classified: " + channel.name); + if (channel.name !== testUrl) { + return; + } + + SpecialPowers.removeObserver(aCallback, topic); + + var p = aSubject.QueryInterface(Ci.nsISupportsPriority); + is(p.priority, aPriority, aMessage); + + info("Resolving promise for " + channel.name); + resolve(); +} + +function testXHR1() { + return new Promise(function(aResolve, aReject) { + testUrl = "http://tracking.example.com/"; + info("Not blocklisted: " + testUrl); + resolve = aResolve; + SpecialPowers.addObserver(checkNormalPriority, topic); + testWindow.content.postMessage({type: "doXHR", url: testUrl}, "*"); + }); +} + +function testXHR2() { + return new Promise(function(aResolve, aReject) { + testUrl = "http://trackertest.org/"; + info("Blocklisted and not entitylisted: " + testUrl); + resolve = aResolve; + SpecialPowers.addObserver(checkLowestPriority, topic); + testWindow.content.postMessage({type: "doXHR", url: testUrl}, "*"); + }); +} + +function testFetch1() { + return new Promise(function(aResolve, aReject) { + testUrl = "http://itisatracker.org/"; // only entitylisted in TP, not for annotations + info("Blocklisted and not entitylisted: " + testUrl); + resolve = aResolve; + SpecialPowers.addObserver(checkLowestPriority, topic); + testWindow.content.postMessage({type: "doFetch", url: testUrl}, "*"); + }); +} + +function testFetch2() { + return new Promise(function(aResolve, aReject) { + testUrl = "http://tracking.example.org/"; // only entitylisted for annotations, not in TP + info("Blocklisted but also entitylisted: " + testUrl); + resolve = aResolve; + SpecialPowers.addObserver(checkNormalPriority, topic); + testWindow.content.postMessage({type: "doFetch", url: testUrl}, "*"); + }); +} + +function endTest() { + info("Finishing up..."); + testWindow.close(); + testWindow = null; + SimpleTest.finish(); +} + +async function test() { + await SpecialPowers.pushPrefEnv( + {"set": [["network.http.tailing.enabled", false], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", true], + ["privacy.trackingprotection.lower_network_priority", true], + ["dom.security.https_first", false]]}); + await UrlClassifierTestUtils.addTestTrackers(); + testOnWindow(false, async function(aWindow) { + testWindow = aWindow; + await testXHR1(); + await testXHR2(); + await testFetch1(); + await testFetch2(); + await endTest(); + }); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.registerCleanupFunction(function() { + info("Cleaning up prefs..."); + UrlClassifierTestUtils.cleanupTestTrackers(); +}); +test(); + +</script> + +</pre> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html new file mode 100644 index 0000000000..75d69b2596 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test Tracking Protection in Private Browsing mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="classifierHelper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.window.browsingContext.topChromeWindow; +var contentPage1 = "http://www.example.com/chrome/toolkit/components/url-classifier/tests/mochitest/bug_1580416.html"; + +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function testOnWindow(contentPage) { + return new Promise((resolve, reject) => { + var win = mainWindow.OpenBrowserWindow(); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("load", function innerLoad2() { + win.content.removeEventListener("load", innerLoad2); + SimpleTest.executeSoon(function() { + resolve(win); + }); + }, false, true); + }, true); + SimpleTest.executeSoon(function() { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + }); + }); + }, {capture: true, once: true}); + }); +} + +var testData = [ + { url: "apps.fbsbx.com/", + db: "test-track-simple", + }, + { url: "www.example.com/?resource=apps.fbsbx.com", + db: "test-trackwhite-simple", + }, +]; + +function checkLoads(aWindow, aWhitelisted) { + var win = aWindow.content; + + is(win.document.getElementById("goodscript").dataset.touched, aWhitelisted ? "yes" : "no", "Should load whitelisted tracking javascript"); +} + +SpecialPowers.pushPrefEnv( + // Disable STS preloadlist because apps.fbsbx.com is in the list. + {"set": [["privacy.trackingprotection.enabled", true], + ["urlclassifier.trackingTable", "test-track-simple"], + ["urlclassifier.trackingWhitelistTable", "test-trackwhite-simple"], + ["dom.security.https_first", false], + ["network.stricttransportsecurity.preloadlist", false]]}, + test); + +async function test() { + await classifierHelper.waitForInit(); + await classifierHelper.addUrlToDB(testData); + + // Load the test from a URL on the whitelist + await testOnWindow(contentPage1).then(function(aWindow) { + checkLoads(aWindow, true); + aWindow.close(); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html new file mode 100644 index 0000000000..69ca337c33 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html @@ -0,0 +1,165 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>Test Tracking Protection in Private Browsing mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script class="testbody" type="text/javascript"> + +var mainWindow = window.browsingContext.topChromeWindow; +var contentPage1 = "http://www.itisatrap.org/tests/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html"; +var contentPage2 = "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html"; + +const {UrlClassifierTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +const {TestUtils} = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function testOnWindow(contentPage) { + return new Promise((resolve, reject) => { + var win = mainWindow.OpenBrowserWindow(); + win.addEventListener("load", function() { + TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win).then(() => { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + + win.content.addEventListener("load", function innerLoad2() { + win.content.removeEventListener("load", innerLoad2); + SimpleTest.executeSoon(function() { + resolve(win); + }); + }, false, true); + }, true); + SimpleTest.executeSoon(function() { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + }); + }); + }, {capture: true, once: true}); + }); +} + +var alwaysbadids = [ + "badscript", +]; + +function checkLoads(aWindow, aWhitelisted, tpEnabled) { + var win = aWindow.content; + if (!tpEnabled) { + is(win.document.getElementById("badscript").dataset.touched, "yes", "Should load tracking javascript"); + is(win.document.blockedNodeByClassifierCount, 0, "Should not identify any tracking elements"); + return; + } + + is(win.document.getElementById("badscript").dataset.touched, "no", "Should not load tracking javascript"); + is(win.document.getElementById("goodscript").dataset.touched, aWhitelisted ? "yes" : "no", "Should load whitelisted tracking javascript"); + + var badids = alwaysbadids.slice(); + if (!aWhitelisted) { + badids.push("goodscript"); + } + is(win.document.blockedNodeByClassifierCount, badids.length, "Should identify all tracking elements"); + + var blockedNodes = win.document.blockedNodesByClassifier; + + // Make sure that every node in blockedNodes exists in the tree + // (that may not always be the case but do not expect any nodes to disappear + // from the tree here) + var allNodeMatch = true; + for (let i = 0; i < blockedNodes.length; i++) { + let nodeMatch = false; + for (let j = 0; j < badids.length && !nodeMatch; j++) { + nodeMatch = nodeMatch || + (blockedNodes[i] == win.document.getElementById(badids[j])); + } + + allNodeMatch = allNodeMatch && nodeMatch; + } + is(allNodeMatch, true, "All annotated nodes are expected in the tree"); + + // Make sure that every node with a badid (see badids) is found in the + // blockedNodes. This tells us if we are neglecting to annotate + // some nodes + allNodeMatch = true; + for (let j = 0; j < badids.length; j++) { + let nodeMatch = false; + for (let i = 0; i < blockedNodes.length && !nodeMatch; i++) { + nodeMatch = nodeMatch || + (blockedNodes[i] == win.document.getElementById(badids[j])); + } + + allNodeMatch = allNodeMatch && nodeMatch; + } + is(allNodeMatch, true, "All tracking nodes are expected to be annotated as such"); +} + +SpecialPowers.pushPrefEnv( + {"set": [["privacy.trackingprotection.enabled", true], + ["privacy.trackingprotection.testing.report_blocked_node", true], + ["dom.security.https_first", false], + ["channelclassifier.allowlist_example", true]]}, + test); + +async function test() { + SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers); + await UrlClassifierTestUtils.addTestTrackers(); + + // Load the test from a URL that's NOT on the whitelist with tracking protection disabled + await SpecialPowers.setBoolPref("privacy.trackingprotection.enabled", false); + await testOnWindow(contentPage2).then(function(aWindow) { + checkLoads(aWindow, false, false); + aWindow.close(); + }); + await SpecialPowers.setBoolPref("privacy.trackingprotection.enabled", true); + + // Load the test from a URL that's NOT on the whitelist + await testOnWindow(contentPage2).then(function(aWindow) { + checkLoads(aWindow, false, true); + aWindow.close(); + }); + + // Load the test from a URL on the whitelist + await testOnWindow(contentPage1).then(function(aWindow) { + checkLoads(aWindow, true, true); + aWindow.close(); + }); + + // Load the test from a URL on the whitelist but without the whitelist + await SpecialPowers.setCharPref("urlclassifier.trackingWhitelistTable", ""); + await testOnWindow(contentPage1).then(function(aWindow) { + checkLoads(aWindow, false, true); + aWindow.close(); + }); + await SpecialPowers.clearUserPref("urlclassifier.trackingWhitelistTable"); + await SpecialPowers.clearUserPref("privacy.trackingprotection.enabled"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> + +</pre> +<iframe id="testFrame" width="100%" height="100%" onload=""></iframe> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/threathit.sjs b/toolkit/components/url-classifier/tests/mochitest/threathit.sjs new file mode 100644 index 0000000000..a083a2e247 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/threathit.sjs @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function handleRequest(request, response) { + let params = new URLSearchParams(request.queryString); + var action = params.get("action"); + + var responseBody; + + // Store data in the server side. + if (action == "store") { + // In the server side we will store: + // All the full hashes or update for a given list + let state = params.get("list") + params.get("type"); + let dataStr = params.get("data"); + setState(state, dataStr); + } else if (action == "get") { + let state = params.get("list") + params.get("type"); + responseBody = atob(getState(state)); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(responseBody, responseBody.length); + } else if (action == "report") { + let state = params.get("list") + "report"; + let requestUrl = + request.scheme + + "://" + + request.host + + ":" + + request.port + + request.path + + "?" + + request.queryString; + setState(state, requestUrl); + } else if (action == "getreport") { + let state = params.get("list") + "report"; + responseBody = getState(state); + response.setHeader("Content-Type", "text/plain", false); + response.write(responseBody); + } +} diff --git a/toolkit/components/url-classifier/tests/mochitest/track.html b/toolkit/components/url-classifier/tests/mochitest/track.html new file mode 100644 index 0000000000..8785e7c5b1 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/track.html @@ -0,0 +1,7 @@ +<html> + <head> + </head> + <body> + <h1>Tracking Works!</h1> + </body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/trackerFrame.html b/toolkit/components/url-classifier/tests/mochitest/trackerFrame.html new file mode 100644 index 0000000000..73409b5cda --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/trackerFrame.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> +<meta charset="utf-8"> +<title></title> +</head> +<body> +<div id="content" style="display: none"> + +<img src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=img-src"> + +<!--nsObjectLoadingContent::OpenChannel--> +<object data="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=object-data"></object> + +<!--ScriptLoader::StartLoad--> +<script src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=script-src"></script> + +<!--nsDocShell::DoURILoad--> +<iframe src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=iframe-src"></iframe> + +<!--Loader::LoadSheet--> +<link rel="stylesheet" href="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=link-rel-stylesheet" /> + +<!--nsPrefetchNode::OpenChannel--> +<!-- Temporarily disable this because it doesn't work in fission when the scheme is https --> +<!--<link rel="prefetch" href="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=link-rel-prefetch" />--> + +<!--HTMLMediaElement::ChannelLoader::LoadInternal--> +<video src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=video-src"> +</video> + +<video src="https://mochi.test:8888/basic.vtt", crossorigin=use-credentials> + <track default src="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=track-src" ></track> +</video> + +<!--SendPing--> +<a ping="https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=ping" id="a-ping" href="#"></a> +<script> + (function() { + document.getElementById("a-ping").click(); + })(); +</script> + +<script> + +// FetchDriver::HttpFetch +(function() { + try { + fetch(new Request("https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=fetch"), { + credentials: "include", + }); + } catch (err) { + console.log(err); + } +})(); + +// XMLHttpRequestMainThread::CreateChannel +(function() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=xmlhttprequest"); + xhr.withCredentials = true; + xhr.send(); +})(); + +// Navigator::SendBeaconInternal +(function() { + navigator.sendBeacon("https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=send-beacon"); +})(); + +</script> + +// Fetch inside service worker's script +<iframe id="sw" src="https://example.com/tests/toolkit/components/url-classifier/tests/mochitest/sw_register.html"></iframe> +<script> + let iframe = document.getElementById("sw"); + window.onmessage = function(e) { + if (e.data.status == "registrationdone") { + iframe.remove(); + iframe = document.createElement("iframe"); + document.getElementById("content").appendChild(iframe); + iframe.src = "https://example.com/tests/toolkit/components/url-classifier/tests/mochitest/synth.html?" + + "https://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs?id=fetch-in-sw"; + } + }; +</script> + +</div> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs b/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs new file mode 100644 index 0000000000..be31e666b0 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/trackerFrame.sjs @@ -0,0 +1,80 @@ +/* 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/. */ + +const stateTotalRequests = "total-request"; +const stateCallback = "callback-response"; +const stateTrackersWithCookie = "trackers-with-cookie"; +const stateTrackersWithoutCookie = "trackers-without-cookie"; +const stateReceivedTrackers = "received-trackers"; +const stateResponseType = "response-tracker-with-cookie"; + +function reset() { + setState(stateCallback, ""); + setState(stateTrackersWithCookie, ""); + setState(stateTrackersWithoutCookie, ""); + setState(stateReceivedTrackers, ""); + setState(stateResponseType, ""); +} + +function handleRequest(aRequest, aResponse) { + let params = new URLSearchParams(aRequest.queryString); + + // init the server and tell the server the total number requests to process + // server set the cookie + if (params.has("init")) { + setState(stateTotalRequests, params.get("init")); + + aResponse.setStatusLine(aRequest.httpVersion, 200); + aResponse.setHeader("Content-Type", "text/plain", false); + + // Prepare the cookie + aResponse.setHeader("Set-Cookie", "cookie=1234; SameSite=None; Secure"); + aResponse.setHeader( + "Access-Control-Allow-Origin", + aRequest.getHeader("Origin"), + false + ); + aResponse.setHeader("Access-Control-Allow-Credentials", "true", false); + aResponse.write("begin-test"); + // register the callback response, the response will be fired after receiving + // all the request + } else if (params.has("callback")) { + aResponse.processAsync(); + aResponse.setHeader("Content-Type", "text/plain", false); + aResponse.setHeader( + "Access-Control-Allow-Origin", + aRequest.getHeader("Origin"), + false + ); + aResponse.setHeader("Access-Control-Allow-Credentials", "true", false); + + setState(stateResponseType, params.get("callback")); + setObjectState(stateCallback, aResponse); + } else { + let count = parseInt(getState(stateReceivedTrackers) || 0) + 1; + setState(stateReceivedTrackers, count.toString()); + + let state = ""; + if (aRequest.hasHeader("Cookie")) { + state = stateTrackersWithCookie; + } else { + state = stateTrackersWithoutCookie; + } + + let ids = params.get("id").concat(",", getState(state)); + setState(state, ids); + + if (getState(stateTotalRequests) == getState(stateReceivedTrackers)) { + getObjectState(stateCallback, r => { + if (getState(stateResponseType) == "with-cookie") { + r.write(getState(stateTrackersWithCookie)); + } else { + r.write(getState(stateTrackersWithoutCookie)); + } + r.finish(); + reset(); + }); + } + } +} diff --git a/toolkit/components/url-classifier/tests/mochitest/trackingRequest.html b/toolkit/components/url-classifier/tests/mochitest/trackingRequest.html new file mode 100644 index 0000000000..ea0f92c481 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/trackingRequest.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> +<title></title> + +</head> +<body> + +<!-- + This domain is not blocklisted for annotations but it is for tracking protection. + Therefore if tracking protection is accidentally enabled, this test will fail. On + the other hand, tracking.example.com will not be used in any of the same-origin + comparisons since we always look for the top window URI when there is one and + that's set to be www.itisatrap.org. +--> +<script id="badscript" data-touched="not sure" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js"></script> + +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js b/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js new file mode 100644 index 0000000000..2720578eed --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js @@ -0,0 +1,9 @@ +window.addEventListener("message", function onMessage(evt) { + if (evt.data.type === "doXHR") { + var request = new XMLHttpRequest(); + request.open("GET", evt.data.url, true); + request.send(null); + } else if (evt.data.type === "doFetch") { + fetch(evt.data.url); + } +}); diff --git a/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js^headers^ b/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js^headers^ new file mode 100644 index 0000000000..3eced96143 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/trackingRequest.js^headers^ @@ -0,0 +1,2 @@ +Access-Control-Allow-Origin: * +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js b/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js new file mode 100644 index 0000000000..b4e8a47602 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js @@ -0,0 +1,5 @@ +/* eslint-env worker */ + +onmessage = function () { + postMessage("loaded bad file"); +}; diff --git a/toolkit/components/url-classifier/tests/mochitest/update.sjs b/toolkit/components/url-classifier/tests/mochitest/update.sjs new file mode 100644 index 0000000000..f3984b2a6f --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/update.sjs @@ -0,0 +1,71 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function handleRequest(request, response) { + var query = {}; + request.queryString.split("&").forEach(function (val) { + var idx = val.indexOf("="); + query[val.slice(0, idx)] = unescape(val.slice(idx + 1)); + }); + + // Store fullhash in the server side. + if ("list" in query && "fullhash" in query) { + // In the server side we will store: + // 1. All the full hashes for a given list + // 2. All the lists we have right now + // data is separate by '\n' + let list = query.list; + let hashes = getState(list); + + let hash = atob(query.fullhash); + hashes += hash + "\n"; + setState(list, hashes); + + let lists = getState("lists"); + if (!lists.includes(list)) { + lists += list + "\n"; + setState("lists", lists); + } + + return; + } + + var body = new BinaryInputStream(request.bodyInputStream); + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + + var responseBody = parseV2Request(bytes); + + response.setHeader("Content-Type", "text/plain", false); + response.write(responseBody); +} + +function parseV2Request(bytes) { + var table = String.fromCharCode.apply(this, bytes).slice(0, -2); + + var ret = ""; + getState("lists") + .split("\n") + .forEach(function (list) { + if (list == table) { + var completions = getState(list).split("\n"); + ret += "n:1000\n"; + ret += "i:" + list + "\n"; + ret += "a:1:32:" + 32 * (completions.length - 1) + "\n"; + + for (var completion of completions) { + ret += completion; + } + } + }); + + return ret; +} diff --git a/toolkit/components/url-classifier/tests/mochitest/vp9.webm b/toolkit/components/url-classifier/tests/mochitest/vp9.webm Binary files differnew file mode 100644 index 0000000000..221877e303 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/vp9.webm diff --git a/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html b/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html new file mode 100644 index 0000000000..620416fc74 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> +<title></title> +</head> +<body> + +<script id="badscript" data-touched="not sure" src="http://trackertest.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script> + +<script id="goodscript" data-touched="not sure" src="http://itisatracker.org/tests/toolkit/components/url-classifier/tests/mochitest/good.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script> + +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/mochitest/workerFrame.html b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html new file mode 100644 index 0000000000..69e8dd0074 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html @@ -0,0 +1,65 @@ +<html> +<head> +<title></title> + +<script type="text/javascript"> + +function startCleanWorker() { + var worker = new Worker("cleanWorker.js"); + + worker.onmessage = function(event) { + if (event.data == "success") { + window.parent.postMessage("success:blocked importScripts('evilWorker.js')", "*"); + } else { + window.parent.postMessage("failure:failed to block importScripts('evilWorker.js')", "*"); + } + window.parent.postMessage("finish", "*"); + }; + + worker.onerror = function(event) { + window.parent.postmessage("failure:failed to load cleanWorker.js", "*"); + window.parent.postMessage("finish", "*"); + }; + + worker.postMessage(""); +} + +function startEvilWorker() { + var worker = new Worker("evilWorker.js"); + + worker.onmessage = function(event) { + window.parent.postMessage("failure:failed to block evilWorker.js", "*"); + startUnwantedWorker(); + }; + + worker.onerror = function(event) { + window.parent.postMessage("success:blocked evilWorker.js", "*"); + startUnwantedWorker(); + }; + + worker.postMessage(""); +} + +function startUnwantedWorker() { + var worker = new Worker("unwantedWorker.js"); + + worker.onmessage = function(event) { + window.parent.postMessage("failure:failed to block unwantedWorker.js", "*"); + startCleanWorker(); + }; + + worker.onerror = function(event) { + window.parent.postMessage("success:blocked unwantedWorker.js", "*"); + startCleanWorker(); + }; + + worker.postMessage(""); +} + +</script> + +</head> + +<body onload="startEvilWorker()"> +</body> +</html> diff --git a/toolkit/components/url-classifier/tests/moz.build b/toolkit/components/url-classifier/tests/moz.build new file mode 100644 index 0000000000..79364db097 --- /dev/null +++ b/toolkit/components/url-classifier/tests/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +MOCHITEST_MANIFESTS += ["mochitest/mochitest.toml"] +MOCHITEST_CHROME_MANIFESTS += ["mochitest/chrome.toml"] +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"] +BROWSER_CHROME_MANIFESTS += ["browser/browser.toml"] + +TESTING_JS_MODULES += [ + "UrlClassifierTestUtils.sys.mjs", +] + +if CONFIG["ENABLE_TESTS"]: + DIRS += ["gtest"] diff --git a/toolkit/components/url-classifier/tests/unit/data/content-fingerprinting-track-digest256 b/toolkit/components/url-classifier/tests/unit/data/content-fingerprinting-track-digest256 Binary files differnew file mode 100644 index 0000000000..cf95b25ac3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/data/content-fingerprinting-track-digest256 diff --git a/toolkit/components/url-classifier/tests/unit/data/digest1.chunk b/toolkit/components/url-classifier/tests/unit/data/digest1.chunk Binary files differnew file mode 100644 index 0000000000..3850373c19 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/data/digest1.chunk diff --git a/toolkit/components/url-classifier/tests/unit/data/digest2.chunk b/toolkit/components/url-classifier/tests/unit/data/digest2.chunk new file mode 100644 index 0000000000..738c96f6ba --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/data/digest2.chunk @@ -0,0 +1,2 @@ +a:5:32:32 +“Ê_Há^˜aÍ7ÂÙ]´=#ÌnmåÃøún‹æo—ÌQ‰
\ No newline at end of file diff --git a/toolkit/components/url-classifier/tests/unit/data/google-trackwhite-digest256 b/toolkit/components/url-classifier/tests/unit/data/google-trackwhite-digest256 Binary files differnew file mode 100644 index 0000000000..81d28053ff --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/data/google-trackwhite-digest256 diff --git a/toolkit/components/url-classifier/tests/unit/data/invalid.chunk b/toolkit/components/url-classifier/tests/unit/data/invalid.chunk new file mode 100644 index 0000000000..7911ca4963 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/data/invalid.chunk @@ -0,0 +1,2 @@ +a:5:32 +“Ê_Há^˜aÃ7ÂÙ]´=#ÃŒnmåÃøún‹æo—ÌQ‰ diff --git a/toolkit/components/url-classifier/tests/unit/data/mozplugin-block-digest256 b/toolkit/components/url-classifier/tests/unit/data/mozplugin-block-digest256 Binary files differnew file mode 100644 index 0000000000..40f64f3cbf --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/data/mozplugin-block-digest256 diff --git a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js new file mode 100644 index 0000000000..0ed731b564 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js @@ -0,0 +1,570 @@ +//* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- * +function dumpn(s) { + dump(s + "\n"); +} + +const NS_APP_USER_PROFILE_50_DIR = "ProfD"; +const NS_APP_USER_PROFILE_LOCAL_50_DIR = "ProfLD"; + +var { + HTTP_400, + HTTP_401, + HTTP_402, + HTTP_403, + HTTP_404, + HTTP_405, + HTTP_406, + HTTP_407, + HTTP_408, + HTTP_409, + HTTP_410, + HTTP_411, + HTTP_412, + HTTP_413, + HTTP_414, + HTTP_415, + HTTP_417, + HTTP_500, + HTTP_501, + HTTP_502, + HTTP_503, + HTTP_504, + HTTP_505, + HttpError, + HttpServer, +} = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs"); + +do_get_profile(); + +// Ensure PSM is initialized before the test +Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); + +// Disable hashcompleter noise for tests +Services.prefs.setIntPref("urlclassifier.gethashnoise", 0); + +// Enable malware/phishing checking for tests +Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true); +Services.prefs.setBoolPref("browser.safebrowsing.blockedURIs.enabled", true); +Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", true); +Services.prefs.setBoolPref( + "browser.safebrowsing.provider.test.disableBackoff", + true +); + +// Add testing tables, we don't use moztest-* here because it doesn't support update +Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple"); +Services.prefs.setCharPref( + "urlclassifier.malwareTable", + "test-harmful-simple,test-malware-simple,test-unwanted-simple" +); +Services.prefs.setCharPref("urlclassifier.blockedTable", "test-block-simple"); +Services.prefs.setCharPref("urlclassifier.trackingTable", "test-track-simple"); +Services.prefs.setCharPref( + "urlclassifier.trackingWhitelistTable", + "test-trackwhite-simple" +); + +// Enable all completions for tests +Services.prefs.setCharPref("urlclassifier.disallow_completions", ""); + +// Hash completion timeout +Services.prefs.setIntPref("urlclassifier.gethash.timeout_ms", 5000); + +function delFile(name) { + try { + // Delete a previously created sqlite file + var file = Services.dirsvc.get("ProfLD", Ci.nsIFile); + file.append(name); + if (file.exists()) { + file.remove(false); + } + } catch (e) {} +} + +function cleanUp() { + delFile("urlclassifier3.sqlite"); + delFile("safebrowsing/classifier.hashkey"); + delFile("safebrowsing/test-phish-simple.sbstore"); + delFile("safebrowsing/test-malware-simple.sbstore"); + delFile("safebrowsing/test-unwanted-simple.sbstore"); + delFile("safebrowsing/test-block-simple.sbstore"); + delFile("safebrowsing/test-harmful-simple.sbstore"); + delFile("safebrowsing/test-track-simple.sbstore"); + delFile("safebrowsing/test-trackwhite-simple.sbstore"); + delFile("safebrowsing/test-phish-simple.pset"); + delFile("safebrowsing/test-malware-simple.pset"); + delFile("safebrowsing/test-unwanted-simple.pset"); + delFile("safebrowsing/test-block-simple.pset"); + delFile("safebrowsing/test-harmful-simple.pset"); + delFile("safebrowsing/test-track-simple.pset"); + delFile("safebrowsing/test-trackwhite-simple.pset"); + delFile("safebrowsing/moz-phish-simple.sbstore"); + delFile("safebrowsing/moz-phish-simple.pset"); + delFile("testLarge.pset"); + delFile("testNoDelta.pset"); +} + +// Update uses allTables by default +var allTables = + "test-phish-simple,test-malware-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple"; +var mozTables = "moz-phish-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" : [{ + * "chunkType" : "a", // 'a' is assumed if not specified + * "chunkNum" : 1, // numerically-increasing chunk numbers are assumed + * // if not specified + * "urls" : [ "foo.com/a", "foo.com/b", "bar.com/" ] + * } + */ + +function buildUpdate(update, hashSize) { + if (!hashSize) { + hashSize = 32; + } + var updateStr = "n:1000\n"; + + for (var tableName in update) { + if (tableName != "") { + updateStr += "i:" + tableName + "\n"; + } + var chunks = update[tableName]; + for (var j = 0; j < chunks.length; j++) { + var chunk = chunks[j]; + var chunkType = chunk.chunkType ? chunk.chunkType : "a"; + var chunkNum = chunk.chunkNum ? chunk.chunkNum : j; + updateStr += chunkType + ":" + chunkNum + ":" + hashSize; + + if (chunk.urls) { + var chunkData = chunk.urls.join("\n"); + updateStr += ":" + chunkData.length + "\n" + chunkData; + } + + updateStr += "\n"; + } + } + + return updateStr; +} + +function buildPhishingUpdate(chunks, hashSize) { + return buildUpdate({ "test-phish-simple": chunks }, hashSize); +} + +function buildMalwareUpdate(chunks, hashSize) { + return buildUpdate({ "test-malware-simple": chunks }, hashSize); +} + +function buildUnwantedUpdate(chunks, hashSize) { + return buildUpdate({ "test-unwanted-simple": chunks }, hashSize); +} + +function buildBlockedUpdate(chunks, hashSize) { + return buildUpdate({ "test-block-simple": chunks }, hashSize); +} + +function buildMozPhishingUpdate(chunks, hashSize) { + return buildUpdate({ "moz-phish-simple": chunks }, hashSize); +} + +function buildBareUpdate(chunks, hashSize) { + return buildUpdate({ "": chunks }, hashSize); +} + +/** + * Performs an update of the dbservice manually, bypassing the stream updater + */ +function doSimpleUpdate(updateText, success, failure) { + var listener = { + QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierUpdateObserver"]), + + updateUrlRequested(url) {}, + streamFinished(status) {}, + updateError(errorCode) { + failure(errorCode); + }, + updateSuccess(requestedTimeout) { + success(requestedTimeout); + }, + }; + + dbservice.beginUpdate(listener, allTables); + dbservice.beginStream("", ""); + dbservice.updateStream(updateText); + dbservice.finishStream(); + dbservice.finishUpdate(); +} + +/** + * Simulates a failed database update. + */ +function doErrorUpdate(tables, success, failure) { + var listener = { + QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierUpdateObserver"]), + + updateUrlRequested(url) {}, + streamFinished(status) {}, + updateError(errorCode) { + success(errorCode); + }, + updateSuccess(requestedTimeout) { + failure(requestedTimeout); + }, + }; + + dbservice.beginUpdate(listener, tables, null); + dbservice.beginStream("", ""); + dbservice.cancelUpdate(); +} + +/** + * Performs an update of the dbservice using the stream updater and a + * data: uri + */ +function doStreamUpdate(updateText, success, failure, downloadFailure) { + var dataUpdate = "data:," + encodeURIComponent(updateText); + + if (!downloadFailure) { + downloadFailure = failure; + } + + streamUpdater.downloadUpdates( + allTables, + "", + true, + dataUpdate, + success, + failure, + downloadFailure + ); +} + +var gAssertions = { + tableData(expectedTables, cb) { + dbservice.getTables(function (tables) { + // rebuild the tables in a predictable order. + var parts = tables.split("\n"); + while (parts[parts.length - 1] == "") { + parts.pop(); + } + parts.sort(); + tables = parts.join("\n"); + + Assert.equal(tables, expectedTables); + cb(); + }); + }, + + checkUrls(urls, expected, cb, useMoz = false) { + // work with a copy of the list. + urls = urls.slice(0); + var doLookup = function () { + if (urls.length) { + var tables = useMoz ? mozTables : allTables; + var fragment = urls.shift(); + var principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + fragment), + {} + ); + dbservice.lookup( + principal, + tables, + function (arg) { + Assert.equal(expected, arg); + doLookup(); + }, + true + ); + } else { + cb(); + } + }; + doLookup(); + }, + + checkTables(url, expected, cb) { + var principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + url), + {} + ); + dbservice.lookup( + principal, + allTables, + function (tables) { + // Rebuild tables in a predictable order. + var parts = tables.split(","); + while (parts[parts.length - 1] == "") { + parts.pop(); + } + parts.sort(); + tables = parts.join(","); + Assert.equal(tables, expected); + cb(); + }, + true + ); + }, + + urlsDontExist(urls, cb) { + this.checkUrls(urls, "", cb); + }, + + urlsExist(urls, cb) { + this.checkUrls(urls, "test-phish-simple", cb); + }, + + malwareUrlsExist(urls, cb) { + this.checkUrls(urls, "test-malware-simple", cb); + }, + + unwantedUrlsExist(urls, cb) { + this.checkUrls(urls, "test-unwanted-simple", cb); + }, + + blockedUrlsExist(urls, cb) { + this.checkUrls(urls, "test-block-simple", cb); + }, + + mozPhishingUrlsExist(urls, cb) { + this.checkUrls(urls, "moz-phish-simple", cb, true); + }, + + subsDontExist(urls, cb) { + // XXX: there's no interface for checking items in the subs table + cb(); + }, + + subsExist(urls, cb) { + // XXX: there's no interface for checking items in the subs table + cb(); + }, + + urlExistInMultipleTables(data, cb) { + this.checkTables(data.url, data.tables, cb); + }, +}; + +/** + * Check a set of assertions against the gAssertions table. + */ +function checkAssertions(assertions, doneCallback) { + var checkAssertion = function () { + for (var i in assertions) { + var data = assertions[i]; + delete assertions[i]; + gAssertions[i](data, checkAssertion); + return; + } + + doneCallback(); + }; + + checkAssertion(); +} + +function updateError(arg) { + do_throw(arg); +} + +/** + * Utility functions + */ +ChromeUtils.defineESModuleGetters(this, { + NetUtil: "resource://gre/modules/NetUtil.sys.mjs", +}); + +function readFileToString(aFilename) { + let f = do_get_file(aFilename); + let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + stream.init(f, -1, 0, 0); + let buf = NetUtil.readInputStreamToString(stream, stream.available()); + return buf; +} + +// Runs a set of updates, and then checks a set of assertions. +function doUpdateTest(updates, assertions, successCallback, errorCallback) { + var errorUpdate = function () { + checkAssertions(assertions, errorCallback); + }; + + var runUpdate = function () { + if (updates.length) { + var update = updates.shift(); + doStreamUpdate(update, runUpdate, errorUpdate, null); + } else { + checkAssertions(assertions, successCallback); + } + }; + + runUpdate(); +} + +var gTests; +var gNextTest = 0; + +function runNextTest() { + if (gNextTest >= gTests.length) { + do_test_finished(); + return; + } + + dbservice.resetDatabase(); + dbservice.setHashCompleter("test-phish-simple", null); + + let test = gTests[gNextTest++]; + dump("running " + test.name + "\n"); + test(); +} + +function runTests(tests) { + gTests = tests; + runNextTest(); +} + +var timerArray = []; + +function Timer(delay, cb) { + this.cb = cb; + var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback(this, delay, timer.TYPE_ONE_SHOT); + timerArray.push(timer); +} + +Timer.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]), + notify(timer) { + this.cb(); + }, +}; + +// LFSRgenerator is a 32-bit linear feedback shift register random number +// generator. It is highly predictable and is not intended to be used for +// cryptography but rather to allow easier debugging than a test that uses +// Math.random(). +function LFSRgenerator(seed) { + // Force |seed| to be a number. + seed = +seed; + // LFSR generators do not work with a value of 0. + if (seed == 0) { + seed = 1; + } + + this._value = seed; +} +LFSRgenerator.prototype = { + // nextNum returns a random unsigned integer of in the range [0,2^|bits|]. + nextNum(bits) { + if (!bits) { + bits = 32; + } + + let val = this._value; + // Taps are 32, 22, 2 and 1. + let bit = ((val >>> 0) ^ (val >>> 10) ^ (val >>> 30) ^ (val >>> 31)) & 1; + val = (val >>> 1) | (bit << 31); + this._value = val; + + return val >>> (32 - bits); + }, +}; + +function waitUntilMetaDataSaved(expectedState, expectedChecksum, callback) { + let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIUrlClassifierDBService + ); + + dbService.getTables(metaData => { + info("metadata: " + metaData); + let didCallback = false; + metaData.split("\n").some(line => { + // Parse [tableName];[stateBase64] + let p = line.indexOf(";"); + if (-1 === p) { + return false; // continue. + } + let tableName = line.substring(0, p); + let metadata = line.substring(p + 1).split(":"); + let stateBase64 = metadata[0]; + let checksumBase64 = metadata[1]; + + if (tableName !== "test-phish-proto") { + return false; // continue. + } + + if ( + stateBase64 === btoa(expectedState) && + checksumBase64 === btoa(expectedChecksum) + ) { + info("State has been saved to disk!"); + + // We slightly defer the callback to see if the in-memory + // |getTables| caching works correctly. + dbService.getTables(cachedMetadata => { + equal(cachedMetadata, metaData); + callback(); + }); + + // Even though we haven't done callback at this moment + // but we still claim "we have" in order to stop repeating + // a new timer. + didCallback = true; + } + + return true; // break no matter whether the state is matching. + }); + + if (!didCallback) { + do_timeout( + 1000, + waitUntilMetaDataSaved.bind( + null, + expectedState, + expectedChecksum, + callback + ) + ); + } + }); +} + +var gUpdateFinishedObserverEnabled = false; +var gUpdateFinishedObserver = function (aSubject, aTopic, aData) { + info("[" + aTopic + "] " + aData); + if (aData != "success") { + updateError(aData); + } +}; + +function throwOnUpdateErrors() { + Services.obs.addObserver( + gUpdateFinishedObserver, + "safebrowsing-update-finished" + ); + gUpdateFinishedObserverEnabled = true; +} + +function stopThrowingOnUpdateErrors() { + if (gUpdateFinishedObserverEnabled) { + Services.obs.removeObserver( + gUpdateFinishedObserver, + "safebrowsing-update-finished" + ); + gUpdateFinishedObserverEnabled = false; + } +} + +cleanUp(); + +registerCleanupFunction(function () { + cleanUp(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_addsub.js b/toolkit/components/url-classifier/tests/unit/test_addsub.js new file mode 100644 index 0000000000..f58a02506f --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_addsub.js @@ -0,0 +1,329 @@ +function doTest(updates, assertions) { + doUpdateTest(updates, assertions, runNextTest, updateError); +} + +// Test an add of two urls to a fresh database +function testSimpleAdds() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }]); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: addUrls, + }; + + doTest([update], assertions); +} + +// Same as testSimpleAdds, but make the same-domain URLs come from different +// chunks. +function testMultipleAdds() { + var add1Urls = ["foo.com/a", "bar.com/c"]; + var add2Urls = ["foo.com/b"]; + + var update = buildPhishingUpdate([ + { chunkNum: 1, urls: add1Urls }, + { chunkNum: 2, urls: add2Urls }, + ]); + var assertions = { + tableData: "test-phish-simple;a:1-2", + urlsExist: add1Urls.concat(add2Urls), + }; + + doTest([update], assertions); +} + +// Test that a sub will remove an existing add +function testSimpleSub() { + var addUrls = ["foo.com/a", "bar.com/b"]; + var subUrls = ["1:foo.com/a"]; + + var addUpdate = buildPhishingUpdate([ + { + chunkNum: 1, // adds and subtracts don't share a chunk numbering space + urls: addUrls, + }, + ]); + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 50, chunkType: "s", urls: subUrls }, + ]); + + var assertions = { + tableData: "test-phish-simple;a:1:s:50", + urlsExist: ["bar.com/b"], + urlsDontExist: ["foo.com/a"], + subsDontExist: ["foo.com/a"], + }; + + doTest([addUpdate, subUpdate], assertions); +} + +// Same as testSimpleSub(), but the sub comes in before the add. +function testSubEmptiesAdd() { + var subUrls = ["1:foo.com/a"]; + var addUrls = ["foo.com/a", "bar.com/b"]; + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 50, chunkType: "s", urls: subUrls }, + ]); + + var addUpdate = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }]); + + var assertions = { + tableData: "test-phish-simple;a:1:s:50", + urlsExist: ["bar.com/b"], + urlsDontExist: ["foo.com/a"], + subsDontExist: ["foo.com/a"], // this sub was found, it shouldn't exist anymore + }; + + doTest([subUpdate, addUpdate], assertions); +} + +// Very similar to testSubEmptiesAdd, except that the domain entry will +// still have an item left over that needs to be synced. +function testSubPartiallyEmptiesAdd() { + var subUrls = ["1:foo.com/a"]; + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/b"]; + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: subUrls }, + ]); + + var addUpdate = buildPhishingUpdate([ + { + chunkNum: 1, // adds and subtracts don't share a chunk numbering space + urls: addUrls, + }, + ]); + + var assertions = { + tableData: "test-phish-simple;a:1:s:1", + urlsExist: ["foo.com/b", "bar.com/b"], + urlsDontExist: ["foo.com/a"], + subsDontExist: ["foo.com/a"], // this sub was found, it shouldn't exist anymore + }; + + doTest([subUpdate, addUpdate], assertions); +} + +// We SHOULD be testing that pending subs are removed using +// subsDontExist assertions. Since we don't have a good interface for getting +// at sub entries, we'll verify it by side-effect. Subbing a url once +// then adding it twice should leave the url intact. +function testPendingSubRemoved() { + var subUrls = ["1:foo.com/a", "2:foo.com/b"]; + var addUrls = ["foo.com/a", "foo.com/b"]; + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: subUrls }, + ]); + + var addUpdate1 = buildPhishingUpdate([ + { + chunkNum: 1, // adds and subtracts don't share a chunk numbering space + urls: addUrls, + }, + ]); + + var addUpdate2 = buildPhishingUpdate([{ chunkNum: 2, urls: addUrls }]); + + var assertions = { + tableData: "test-phish-simple;a:1-2:s:1", + urlsExist: ["foo.com/a", "foo.com/b"], + subsDontExist: ["foo.com/a", "foo.com/b"], // this sub was found, it shouldn't exist anymore + }; + + doTest([subUpdate, addUpdate1, addUpdate2], assertions); +} + +// Make sure that a saved sub is removed when the sub chunk is expired. +function testPendingSubExpire() { + var subUrls = ["1:foo.com/a", "1:foo.com/b"]; + var addUrls = ["foo.com/a", "foo.com/b"]; + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: subUrls }, + ]); + + var expireUpdate = buildPhishingUpdate([{ chunkNum: 1, chunkType: "sd" }]); + + var addUpdate = buildPhishingUpdate([ + { + chunkNum: 1, // adds and subtracts don't share a chunk numbering space + urls: addUrls, + }, + ]); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: ["foo.com/a", "foo.com/b"], + subsDontExist: ["foo.com/a", "foo.com/b"], // this sub was expired + }; + + doTest([subUpdate, expireUpdate, addUpdate], assertions); +} + +// Make sure that the sub url removes from only the chunk that it specifies +function testDuplicateAdds() { + var urls = ["foo.com/a"]; + + var addUpdate1 = buildPhishingUpdate([{ chunkNum: 1, urls }]); + var addUpdate2 = buildPhishingUpdate([{ chunkNum: 2, urls }]); + var subUpdate = buildPhishingUpdate([ + { chunkNum: 3, chunkType: "s", urls: ["2:foo.com/a"] }, + ]); + + var assertions = { + tableData: "test-phish-simple;a:1-2:s:3", + urlsExist: ["foo.com/a"], + subsDontExist: ["foo.com/a"], + }; + + doTest([addUpdate1, addUpdate2, subUpdate], assertions); +} + +// Tests a sub which matches some existing adds but leaves others. +function testSubPartiallyMatches() { + var addUrls = ["1:foo.com/a", "2:foo.com/b"]; + + var addUpdate = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }]); + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: addUrls }, + ]); + + var assertions = { + tableData: "test-phish-simple;a:1:s:1", + urlsDontExist: ["foo.com/a"], + subsDontExist: ["foo.com/a"], + subsExist: ["foo.com/b"], + }; + + doTest([addUpdate, subUpdate], assertions); +} + +// XXX: because subsExist isn't actually implemented, this is the same +// test as above but with a second add chunk that should fail to be added +// because of a pending sub chunk. +function testSubPartiallyMatches2() { + var addUrls = ["foo.com/a"]; + var subUrls = ["1:foo.com/a", "2:foo.com/b"]; + var addUrls2 = ["foo.com/b"]; + + var addUpdate = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }]); + + var subUpdate = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: subUrls }, + ]); + + var addUpdate2 = buildPhishingUpdate([{ chunkNum: 2, urls: addUrls2 }]); + + var assertions = { + tableData: "test-phish-simple;a:1-2:s:1", + urlsDontExist: ["foo.com/a", "foo.com/b"], + subsDontExist: ["foo.com/a", "foo.com/b"], + }; + + doTest([addUpdate, subUpdate, addUpdate2], assertions); +} + +// Verify that two subs for the same domain but from different chunks +// match (tests that existing sub entries are properly updated) +function testSubsDifferentChunks() { + var subUrls1 = ["3:foo.com/a"]; + var subUrls2 = ["3:foo.com/b"]; + + var addUrls = ["foo.com/a", "foo.com/b", "foo.com/c"]; + + var subUpdate1 = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: subUrls1 }, + ]); + var subUpdate2 = buildPhishingUpdate([ + { chunkNum: 2, chunkType: "s", urls: subUrls2 }, + ]); + var addUpdate = buildPhishingUpdate([{ chunkNum: 3, urls: addUrls }]); + + var assertions = { + tableData: "test-phish-simple;a:3:s:1-2", + urlsExist: ["foo.com/c"], + urlsDontExist: ["foo.com/a", "foo.com/b"], + subsDontExist: ["foo.com/a", "foo.com/b"], + }; + + doTest([subUpdate1, subUpdate2, addUpdate], assertions); +} + +// for bug 534079 +function testSubsDifferentChunksSameHostId() { + var subUrls1 = ["1:foo.com/a"]; + var subUrls2 = ["1:foo.com/b", "2:foo.com/c"]; + + var addUrls = ["foo.com/a", "foo.com/b"]; + var addUrls2 = ["foo.com/c"]; + + var subUpdate1 = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: subUrls1 }, + ]); + var subUpdate2 = buildPhishingUpdate([ + { chunkNum: 2, chunkType: "s", urls: subUrls2 }, + ]); + + var addUpdate = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }]); + var addUpdate2 = buildPhishingUpdate([{ chunkNum: 2, urls: addUrls2 }]); + + var assertions = { + tableData: "test-phish-simple;a:1-2:s:1-2", + urlsDontExist: ["foo.com/c", "foo.com/b", "foo.com/a"], + }; + + doTest([addUpdate, addUpdate2, subUpdate1, subUpdate2], assertions); +} + +// Test lists of expired chunks +function testExpireLists() { + var addUpdate = buildPhishingUpdate([ + { chunkNum: 1, urls: ["foo.com/a"] }, + { chunkNum: 3, urls: ["bar.com/a"] }, + { chunkNum: 4, urls: ["baz.com/a"] }, + { chunkNum: 5, urls: ["blah.com/a"] }, + ]); + var subUpdate = buildPhishingUpdate([ + { chunkNum: 1, chunkType: "s", urls: ["50:foo.com/1"] }, + { chunkNum: 2, chunkType: "s", urls: ["50:bar.com/1"] }, + { chunkNum: 3, chunkType: "s", urls: ["50:baz.com/1"] }, + { chunkNum: 5, chunkType: "s", urls: ["50:blah.com/1"] }, + ]); + + var expireUpdate = buildPhishingUpdate([ + { chunkType: "ad:1,3-5" }, + { chunkType: "sd:1-3,5" }, + ]); + + var assertions = { + // "tableData" : "test-phish-simple;" + tableData: "", + }; + + doTest([addUpdate, subUpdate, expireUpdate], assertions); +} + +function run_test() { + runTests([ + testSimpleAdds, + testMultipleAdds, + testSimpleSub, + testSubEmptiesAdd, + testSubPartiallyEmptiesAdd, + testPendingSubRemoved, + testPendingSubExpire, + testDuplicateAdds, + testSubPartiallyMatches, + testSubPartiallyMatches2, + testSubsDifferentChunks, + testSubsDifferentChunksSameHostId, + testExpireLists, + ]); +} + +do_test_pending(); diff --git a/toolkit/components/url-classifier/tests/unit/test_backoff.js b/toolkit/components/url-classifier/tests/unit/test_backoff.js new file mode 100644 index 0000000000..e78a3ee23c --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_backoff.js @@ -0,0 +1,92 @@ +// Some unittests (e.g., paste into JS shell) +var jslib = + Cc["@mozilla.org/url-classifier/jslib;1"].getService().wrappedJSObject; + +var jslibDate = Cu.getGlobalForObject(jslib).Date; + +var _Datenow = jslibDate.now; +function setNow(time) { + jslibDate.now = function () { + return time; + }; +} + +function run_test() { + // 3 errors, 1ms retry period, max 3 requests per ten milliseconds, + // 5ms backoff interval, 19ms max delay + var rb = new jslib.RequestBackoff(3, 1, 3, 10, 5, 19, 0); + setNow(1); + rb.noteServerResponse(200); + Assert.ok(rb.canMakeRequest()); + setNow(2); + Assert.ok(rb.canMakeRequest()); + + // First error should trigger a 1ms delay + rb.noteServerResponse(500); + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 3); + setNow(3); + Assert.ok(rb.canMakeRequest()); + + // Second error should also trigger a 1ms delay + rb.noteServerResponse(500); + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 4); + setNow(4); + Assert.ok(rb.canMakeRequest()); + + // Third error should trigger a 5ms backoff + rb.noteServerResponse(500); + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 9); + setNow(9); + Assert.ok(rb.canMakeRequest()); + + // Trigger backoff again + rb.noteServerResponse(503); + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 19); + setNow(19); + Assert.ok(rb.canMakeRequest()); + + // Trigger backoff a third time and hit max timeout + rb.noteServerResponse(302); + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 38); + setNow(38); + Assert.ok(rb.canMakeRequest()); + + // One more backoff, should still be at the max timeout + rb.noteServerResponse(400); + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 57); + setNow(57); + Assert.ok(rb.canMakeRequest()); + + // Request goes through + rb.noteServerResponse(200); + Assert.ok(rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 0); + setNow(58); + rb.noteServerResponse(500); + + // Another error, should trigger a 1ms backoff + Assert.ok(!rb.canMakeRequest()); + Assert.equal(rb.nextRequestTime_, 59); + + setNow(59); + Assert.ok(rb.canMakeRequest()); + + setNow(200); + rb.noteRequest(); + setNow(201); + rb.noteRequest(); + setNow(202); + Assert.ok(rb.canMakeRequest()); + rb.noteRequest(); + Assert.ok(!rb.canMakeRequest()); + setNow(211); + Assert.ok(rb.canMakeRequest()); + + jslibDate.now = _Datenow; +} diff --git a/toolkit/components/url-classifier/tests/unit/test_bug1274685_unowned_list.js b/toolkit/components/url-classifier/tests/unit/test_bug1274685_unowned_list.js new file mode 100644 index 0000000000..4668a901eb --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_bug1274685_unowned_list.js @@ -0,0 +1,65 @@ +const { SafeBrowsing } = ChromeUtils.importESModule( + "resource://gre/modules/SafeBrowsing.sys.mjs" +); +const { updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); + +add_setup(async () => { + // 'Cc["@mozilla.org/xre/app-info;1"]' for xpcshell has no nsIXULAppInfo + // so that we have to update it to make nsURLFormatter.js happy. + // (SafeBrowsing.init() will indirectly use nsURLFormatter.js) + updateAppInfo(); + + // This test should not actually try to create a connection to any real + // endpoint. But a background request could try that while the test is in + // progress before we've actually shut down networking, and would cause a + // crash due to connecting to a non-local IP. + Services.prefs.setCharPref( + "browser.safebrowsing.provider.mozilla.updateURL", + `http://localhost:4444/safebrowsing/update` + ); + registerCleanupFunction(() => { + Services.prefs.clearUserPref( + "browser.safebrowsing.provider.mozilla.updateURL" + ); + Services.prefs.clearUserPref("browser.safebrowsing.provider.google.lists"); + Services.prefs.clearUserPref("browser.safebrowsing.provider.google4.lists"); + }); +}); + +add_task(async function test() { + SafeBrowsing.init(); + + let origListV2 = Services.prefs.getCharPref( + "browser.safebrowsing.provider.google.lists" + ); + let origListV4 = Services.prefs.getCharPref( + "browser.safebrowsing.provider.google4.lists" + ); + + // Ensure there's a list missing in both Safe Browsing V2 and V4. + let trimmedListV2 = origListV2.replace("goog-malware-shavar,", ""); + Services.prefs.setCharPref( + "browser.safebrowsing.provider.google.lists", + trimmedListV2 + ); + let trimmedListV4 = origListV4.replace("goog-malware-proto,", ""); + Services.prefs.setCharPref( + "browser.safebrowsing.provider.google4.lists", + trimmedListV4 + ); + + try { + // Bug 1274685 - Unowned Safe Browsing tables break list updates + // + // If SafeBrowsing.registerTableWithURLs() doesn't check if + // a provider is found before registering table, an exception + // will be thrown while accessing a null object. + // + SafeBrowsing.registerTables(); + ok(true, "SafeBrowsing.registerTables() did not throw."); + } catch (e) { + ok(false, "Exception thrown due to " + e.toString()); + } +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_canonicalization.js b/toolkit/components/url-classifier/tests/unit/test_canonicalization.js new file mode 100644 index 0000000000..e26bb5d84a --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_canonicalization.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function canonicalize(url) { + let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils + ); + + let uri = Services.io.newURI(url); + return uri.scheme + "://" + urlUtils.getKeyForURI(uri); +} + +function run_test() { + // These testcases are from + // https://developers.google.com/safe-browsing/v4/urls-hashing + equal(canonicalize("http://host/%25%32%35"), "http://host/%25"); + equal(canonicalize("http://host/%25%32%35%25%32%35"), "http://host/%25%25"); + equal(canonicalize("http://host/%2525252525252525"), "http://host/%25"); + equal(canonicalize("http://host/asdf%25%32%35asd"), "http://host/asdf%25asd"); + equal( + canonicalize("http://host/%%%25%32%35asd%%"), + "http://host/%25%25%25asd%25%25" + ); + equal(canonicalize("http://www.google.com/"), "http://www.google.com/"); + equal( + canonicalize( + "http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/" + ), + "http://168.188.99.26/.secure/www.ebay.com/" + ); + equal( + canonicalize( + "http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/" + ), + "http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/" + ); + equal(canonicalize("http://3279880203/blah"), "http://195.127.0.11/blah"); + equal( + canonicalize("http://www.google.com/blah/.."), + "http://www.google.com/" + ); + equal( + canonicalize("http://www.evil.com/blah#frag"), + "http://www.evil.com/blah" + ); + equal(canonicalize("http://www.GOOgle.com/"), "http://www.google.com/"); + equal(canonicalize("http://www.google.com.../"), "http://www.google.com/"); + equal( + canonicalize("http://www.google.com/foo\tbar\rbaz\n2"), + "http://www.google.com/foobarbaz2" + ); + equal(canonicalize("http://www.google.com/q?"), "http://www.google.com/q?"); + equal( + canonicalize("http://www.google.com/q?r?"), + "http://www.google.com/q?r?" + ); + equal( + canonicalize("http://www.google.com/q?r?s"), + "http://www.google.com/q?r?s" + ); + equal(canonicalize("http://evil.com/foo#bar#baz"), "http://evil.com/foo"); + equal(canonicalize("http://evil.com/foo;"), "http://evil.com/foo;"); + equal(canonicalize("http://evil.com/foo?bar;"), "http://evil.com/foo?bar;"); + equal( + canonicalize("http://notrailingslash.com"), + "http://notrailingslash.com/" + ); + equal( + canonicalize("http://www.gotaport.com:1234/"), + "http://www.gotaport.com/" + ); + equal( + canonicalize("https://www.securesite.com/"), + "https://www.securesite.com/" + ); + equal(canonicalize("http://host.com/ab%23cd"), "http://host.com/ab%23cd"); + equal( + canonicalize("http://host.com//twoslashes?more//slashes"), + "http://host.com/twoslashes?more//slashes" + ); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js b/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js new file mode 100644 index 0000000000..9d5f5edb65 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js @@ -0,0 +1,225 @@ +/* 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/. */ + +"use strict"; + +/* Unit tests for the nsIUrlClassifierSkipListService implementation. */ + +var httpserver = new HttpServer(); + +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); +const { UrlClassifierTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); + +const FEATURE_STP_PREF = "privacy.trackingprotection.socialtracking.enabled"; +const TOP_LEVEL_DOMAIN = "http://www.example.com/"; +const TRACKER_DOMAIN = "http://social-tracking.example.org/"; + +function setupChannel(uri, topUri = TOP_LEVEL_DOMAIN) { + httpserver.registerPathHandler("/", null); + httpserver.start(-1); + + let channel = NetUtil.newChannel({ + uri: uri + ":" + httpserver.identity.primaryPort, + loadingPrincipal: Services.scriptSecurityManager.createContentPrincipal( + NetUtil.newURI(topUri), + {} + ), + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + + channel + .QueryInterface(Ci.nsIHttpChannelInternal) + .setTopWindowURIIfUnknown(Services.io.newURI(topUri)); + + return channel; +} + +function waitForBeforeBlockEvent(expected, callback) { + return new Promise(function (resolve) { + let observer = function observe(aSubject, aTopic, aData) { + switch (aTopic) { + case "urlclassifier-before-block-channel": + let channel = aSubject.QueryInterface( + Ci.nsIUrlClassifierBlockedChannel + ); + Assert.equal( + channel.reason, + expected.reason, + "verify blocked reason" + ); + Assert.equal( + channel.url, + expected.url, + "verify url of blocked channel" + ); + + if (callback) { + callback(channel); + } + + service.removeListener(observer); + resolve(channel); + break; + } + }; + + let service = Cc[ + "@mozilla.org/url-classifier/channel-classifier-service;1" + ].getService(Ci.nsIChannelClassifierService); + service.addListener(observer); + }); +} + +add_task(async function test_block_channel() { + Services.prefs.setBoolPref(FEATURE_STP_PREF, true); + await UrlClassifierTestUtils.addTestTrackers(); + + let channel = setupChannel(TRACKER_DOMAIN); + + let blockPromise = waitForBeforeBlockEvent( + { + reason: Ci.nsIUrlClassifierBlockedChannel.SOCIAL_TRACKING_PROTECTION, + url: channel.URI.spec, + }, + null + ); + + let openPromise = new Promise((resolve, reject) => { + channel.asyncOpen({ + onStartRequest: (request, context) => {}, + onDataAvailable: (request, context, stream, offset, count) => {}, + onStopRequest: (request, status) => { + dump("status = " + status + "\n"); + if (status == 200) { + Assert.ok(false, "Should not successfully open the channel"); + } else { + Assert.equal( + status, + Cr.NS_ERROR_SOCIALTRACKING_URI, + "Should fail to open the channel" + ); + } + resolve(); + }, + }); + }); + + // wait for block event from url-classifier + await blockPromise; + + // wait for onStopRequest callback from AsyncOpen + await openPromise; + + // clean up + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(FEATURE_STP_PREF); + httpserver.stop(); +}); + +add_task(async function test_unblock_channel() { + Services.prefs.setBoolPref(FEATURE_STP_PREF, true); + //Services.prefs.setBoolPref("network.dns.native-is-localhost", true); + + await UrlClassifierTestUtils.addTestTrackers(); + + let channel = setupChannel(TRACKER_DOMAIN); + + let blockPromise = waitForBeforeBlockEvent( + { + reason: Ci.nsIUrlClassifierBlockedChannel.SOCIAL_TRACKING_PROTECTION, + url: channel.URI.spec, + }, + ch => { + ch.replace(); + } + ); + + let openPromise = new Promise((resolve, reject) => { + channel.asyncOpen({ + onStartRequest: (request, context) => {}, + onDataAvailable: (request, context, stream, offset, count) => {}, + onStopRequest: (request, status) => { + if (status == Cr.NS_ERROR_SOCIALTRACKING_URI) { + Assert.ok(false, "Classifier should not cancel this channel"); + } else { + // This request is supposed to fail, but we need to ensure it + // is not canceled by url-classifier + Assert.equal( + status, + Cr.NS_ERROR_UNKNOWN_HOST, + "Not cancel by classifier" + ); + } + resolve(); + }, + }); + }); + + // wait for block event from url-classifier + await blockPromise; + + // wait for onStopRequest callback from AsyncOpen + await openPromise; + + // clean up + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(FEATURE_STP_PREF); + httpserver.stop(); +}); + +add_task(async function test_allow_channel() { + Services.prefs.setBoolPref(FEATURE_STP_PREF, true); + //Services.prefs.setBoolPref("network.dns.native-is-localhost", true); + + await UrlClassifierTestUtils.addTestTrackers(); + + let channel = setupChannel(TRACKER_DOMAIN); + + let blockPromise = waitForBeforeBlockEvent( + { + reason: Ci.nsIUrlClassifierBlockedChannel.SOCIAL_TRACKING_PROTECTION, + url: channel.URI.spec, + }, + ch => { + ch.allow(); + } + ); + + let openPromise = new Promise((resolve, reject) => { + channel.asyncOpen({ + onStartRequest: (request, context) => {}, + onDataAvailable: (request, context, stream, offset, count) => {}, + onStopRequest: (request, status) => { + if (status == Cr.NS_ERROR_SOCIALTRACKING_URI) { + Assert.ok(false, "Classifier should not cancel this channel"); + } else { + // This request is supposed to fail, but we need to ensure it + // is not canceled by url-classifier + Assert.equal( + status, + Cr.NS_ERROR_UNKNOWN_HOST, + "Not cancel by classifier" + ); + } + resolve(); + }, + }); + }); + + // wait for block event from url-classifier + await blockPromise; + + // wait for onStopRequest callback from AsyncOpen + await openPromise; + + // clean up + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(FEATURE_STP_PREF); + httpserver.stop(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_dbservice.js b/toolkit/components/url-classifier/tests/unit/test_dbservice.js new file mode 100644 index 0000000000..70ac02021a --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_dbservice.js @@ -0,0 +1,329 @@ +var chunk1Urls = ["test.com/aba", "test.com/foo/bar", "foo.bar.com/a/b/c"]; +var chunk1 = chunk1Urls.join("\n"); + +var chunk2Urls = [ + "blah.com/a", + "baz.com/", + "255.255.0.1/", + "www.foo.com/test2?param=1", +]; +var chunk2 = chunk2Urls.join("\n"); + +var chunk3Urls = ["test.com/a", "foo.bar.com/a", "blah.com/a"]; +var chunk3 = chunk3Urls.join("\n"); + +var chunk3SubUrls = ["1:test.com/a", "1:foo.bar.com/a", "2:blah.com/a"]; +var chunk3Sub = chunk3SubUrls.join("\n"); + +var chunk4Urls = ["a.com/b", "b.com/c"]; +var chunk4 = chunk4Urls.join("\n"); + +var chunk5Urls = ["d.com/e", "f.com/g"]; +var chunk5 = chunk5Urls.join("\n"); + +var chunk6Urls = ["h.com/i", "j.com/k"]; +var chunk6 = chunk6Urls.join("\n"); + +var chunk7Urls = ["l.com/m", "n.com/o"]; +var chunk7 = chunk7Urls.join("\n"); + +// we are going to add chunks 1, 2, 4, 5, and 6 to phish-simple, +// chunk 2 to malware-simple, and chunk 3 to unwanted-simple, +// and chunk 7 to block-simple. +// Then we'll remove the urls in chunk3 from phish-simple, then +// expire chunk 1 and chunks 4-7 from phish-simple. +var phishExpected = {}; +var phishUnexpected = {}; +var malwareExpected = {}; +var unwantedExpected = {}; +var blockedExpected = {}; +for (let i = 0; i < chunk2Urls.length; i++) { + phishExpected[chunk2Urls[i]] = true; + malwareExpected[chunk2Urls[i]] = true; +} +for (let i = 0; i < chunk3Urls.length; i++) { + unwantedExpected[chunk3Urls[i]] = true; + delete phishExpected[chunk3Urls[i]]; + phishUnexpected[chunk3Urls[i]] = true; +} +for (let i = 0; i < chunk1Urls.length; i++) { + // chunk1 urls are expired + phishUnexpected[chunk1Urls[i]] = true; +} +for (let i = 0; i < chunk4Urls.length; i++) { + // chunk4 urls are expired + phishUnexpected[chunk4Urls[i]] = true; +} +for (let i = 0; i < chunk5Urls.length; i++) { + // chunk5 urls are expired + phishUnexpected[chunk5Urls[i]] = true; +} +for (let i = 0; i < chunk6Urls.length; i++) { + // chunk6 urls are expired + phishUnexpected[chunk6Urls[i]] = true; +} +for (let i = 0; i < chunk7Urls.length; i++) { + blockedExpected[chunk7Urls[i]] = true; + // chunk7 urls are expired + phishUnexpected[chunk7Urls[i]] = true; +} + +// Check that the entries hit based on sub-parts +phishExpected["baz.com/foo/bar"] = true; +phishExpected["foo.bar.baz.com/foo"] = true; +phishExpected["bar.baz.com/"] = true; + +var numExpecting; + +function testFailure(arg) { + do_throw(arg); +} + +function checkNoHost() { + // Looking up a no-host uri such as a data: uri should throw an exception. + var exception; + try { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("data:text/html,<b>test</b>"), + {} + ); + dbservice.lookup(principal, allTables); + + exception = false; + } catch (e) { + exception = true; + } + Assert.ok(exception); + + do_test_finished(); +} + +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 + Assert.equal( + parts.join("\n"), + "\ntest-block-simple;a:1\ntest-malware-simple;a:1\ntest-phish-simple;a:2\ntest-unwanted-simple;a:1" + ); + + checkNoHost(); +} + +function expireSubSuccess(result) { + dbservice.getTables(tablesCallbackWithoutSub); +} + +function tablesCallbackWithSub(tables) { + var parts = tables.split("\n"); + + let expectedChunks = [ + "test-block-simple;a:1", + "test-malware-simple;a:1", + "test-phish-simple;a:2:s:3", + "test-unwanted-simple;a:1", + ]; + for (let chunk of expectedChunks) { + Assert.ok(parts.includes(chunk)); + } + + // verify that expiring a sub chunk removes its name from the list + var data = "n:1000\ni:test-phish-simple\nsd:3\n"; + + doSimpleUpdate(data, expireSubSuccess, testFailure); +} + +function checkChunksWithSub() { + dbservice.getTables(tablesCallbackWithSub); +} + +function checkDone() { + if (--numExpecting == 0) { + checkChunksWithSub(); + } +} + +function phishExists(result) { + dumpn("phishExists: " + result); + try { + Assert.ok(result.includes("test-phish-simple")); + } finally { + checkDone(); + } +} + +function phishDoesntExist(result) { + dumpn("phishDoesntExist: " + result); + try { + Assert.ok(!result.includes("test-phish-simple")); + } finally { + checkDone(); + } +} + +function malwareExists(result) { + dumpn("malwareExists: " + result); + + try { + Assert.ok(result.includes("test-malware-simple")); + } finally { + checkDone(); + } +} + +function unwantedExists(result) { + dumpn("unwantedExists: " + result); + + try { + Assert.ok(result.includes("test-unwanted-simple")); + } finally { + checkDone(); + } +} + +function blockedExists(result) { + dumpn("blockedExists: " + result); + + try { + Assert.ok(result.includes("test-block-simple")); + } finally { + checkDone(); + } +} + +function checkState() { + numExpecting = 0; + + for (let key in phishExpected) { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + key), + {} + ); + dbservice.lookup(principal, allTables, phishExists, true); + numExpecting++; + } + + for (let key in phishUnexpected) { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + key), + {} + ); + dbservice.lookup(principal, allTables, phishDoesntExist, true); + numExpecting++; + } + + for (let key in malwareExpected) { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + key), + {} + ); + dbservice.lookup(principal, allTables, malwareExists, true); + numExpecting++; + } + + for (let key in unwantedExpected) { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + key), + {} + ); + dbservice.lookup(principal, allTables, unwantedExists, true); + numExpecting++; + } + + for (let key in blockedExpected) { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://" + key), + {} + ); + dbservice.lookup(principal, allTables, blockedExists, true); + numExpecting++; + } +} + +function testSubSuccess(result) { + Assert.equal(result, "1000"); + checkState(); +} + +function do_subs() { + var data = + "n:1000\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); +} + +function testAddSuccess(arg) { + Assert.equal(arg, "1000"); + + do_subs(); +} + +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: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:test-malware-simple\n" + + "a:1:32:" + + chunk2.length + + "\n" + + chunk2 + + "\n" + + "i:test-unwanted-simple\n" + + "a:1:32:" + + chunk3.length + + "\n" + + chunk3 + + "\n" + + "i:test-block-simple\n" + + "a:1:32:" + + chunk7.length + + "\n" + + chunk7 + + "\n"; + + doSimpleUpdate(data, testAddSuccess, testFailure); +} + +function run_test() { + do_adds(); + do_test_pending(); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_digest256.js b/toolkit/components/url-classifier/tests/unit/test_digest256.js new file mode 100644 index 0000000000..f96f13f7d1 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_digest256.js @@ -0,0 +1,143 @@ +// Global test server for serving safebrowsing updates. +var gHttpServ = null; +// Global nsIUrlClassifierDBService +var gDbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIUrlClassifierDBService +); + +// A map of tables to arrays of update redirect urls. +var gTables = {}; + +// Registers a table for which to serve update chunks. Returns a promise that +// resolves when that chunk has been downloaded. +function registerTableUpdate(aTable, aFilename) { + return new Promise(resolve => { + // If we haven't been given an update for this table yet, add it to the map + if (!(aTable in gTables)) { + gTables[aTable] = []; + } + + // The number of chunks associated with this table. + let numChunks = gTables[aTable].length + 1; + let redirectPath = "/" + aTable + "-" + numChunks; + let redirectUrl = "localhost:4444" + redirectPath; + + // Store redirect url for that table so we can return it later when we + // process an update request. + gTables[aTable].push(redirectUrl); + + gHttpServ.registerPathHandler(redirectPath, function (request, response) { + info("Mock safebrowsing server handling request for " + redirectPath); + let contents = readFileToString(aFilename); + response.setHeader( + "Content-Type", + "application/vnd.google.safebrowsing-update", + false + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(contents, contents.length); + resolve(contents); + }); + }); +} + +// Construct a response with redirect urls. +function processUpdateRequest() { + let response = "n:1000\n"; + for (let table in gTables) { + response += "i:" + table + "\n"; + for (let i = 0; i < gTables[table].length; ++i) { + response += "u:" + gTables[table][i] + "\n"; + } + } + info("Returning update response: " + response); + return response; +} + +// Set up our test server to handle update requests. +function run_test() { + gHttpServ = new HttpServer(); + gHttpServ.registerDirectory("/", do_get_cwd()); + + gHttpServ.registerPathHandler("/downloads", function (request, response) { + let blob = processUpdateRequest(); + response.setHeader( + "Content-Type", + "application/vnd.google.safebrowsing-update", + false + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(blob, blob.length); + }); + + gHttpServ.start(4444); + run_next_test(); +} + +// Just throw if we ever get an update or download error. +function handleError(aEvent) { + do_throw("We didn't download or update correctly: " + aEvent); +} + +add_test(function test_update() { + let streamUpdater = Cc[ + "@mozilla.org/url-classifier/streamupdater;1" + ].getService(Ci.nsIUrlClassifierStreamUpdater); + + // Load up some update chunks for the safebrowsing server to serve. + registerTableUpdate("goog-downloadwhite-digest256", "data/digest1.chunk"); + registerTableUpdate("goog-downloadwhite-digest256", "data/digest2.chunk"); + + // Download some updates, and don't continue until the downloads are done. + function updateSuccess(aEvent) { + // Timeout of n:1000 is constructed in processUpdateRequest above and + // passed back in the callback in nsIUrlClassifierStreamUpdater on success. + Assert.equal("1000", aEvent); + info("All data processed"); + run_next_test(); + } + streamUpdater.downloadUpdates( + "goog-downloadwhite-digest256", + "goog-downloadwhite-digest256;\n", + true, + "http://localhost:4444/downloads", + updateSuccess, + handleError, + handleError + ); +}); + +add_test(function test_url_not_whitelisted() { + let uri = Services.io.newURI("http://example.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + gDbService.lookup( + principal, + "goog-downloadwhite-digest256", + function handleEvent(aEvent) { + // This URI is not on any lists. + Assert.equal("", aEvent); + run_next_test(); + } + ); +}); + +add_test(function test_url_whitelisted() { + // Hash of "whitelisted.com/" (canonicalized URL) is: + // 93CA5F48E15E9861CD37C2D95DB43D23CC6E6DE5C3F8FA6E8BE66F97CC518907 + let uri = Services.io.newURI("http://whitelisted.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + gDbService.lookup( + principal, + "goog-downloadwhite-digest256", + function handleEvent(aEvent) { + Assert.equal("goog-downloadwhite-digest256", aEvent); + run_next_test(); + } + ); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_exceptionListService.js b/toolkit/components/url-classifier/tests/unit/test_exceptionListService.js new file mode 100644 index 0000000000..218aec4934 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_exceptionListService.js @@ -0,0 +1,285 @@ +/* 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/. */ + +"use strict"; + +/* Unit tests for the nsIUrlClassifierExceptionListService implementation. */ + +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); + +const COLLECTION_NAME = "url-classifier-skip-urls"; +const FEATURE_TRACKING_NAME = "tracking-annotation-test"; +const FEATURE_TRACKING_PREF_NAME = "urlclassifier.tracking-annotation-test"; +const FEATURE_SOCIAL_NAME = "socialtracking-annotation-test"; +const FEATURE_SOCIAL_PREF_NAME = "urlclassifier.socialtracking-annotation-test"; +const FEATURE_FINGERPRINTING_NAME = "fingerprinting-annotation-test"; +const FEATURE_FINGERPRINTING_PREF_NAME = + "urlclassifier.fingerprinting-annotation-test"; + +do_get_profile(); + +class UpdateEvent extends EventTarget {} +function waitForEvent(element, eventName) { + return new Promise(function (resolve) { + element.addEventListener(eventName, e => resolve(e.detail), { once: true }); + }); +} + +add_task(async function test_list_changes() { + let exceptionListService = Cc[ + "@mozilla.org/url-classifier/exception-list-service;1" + ].getService(Ci.nsIUrlClassifierExceptionListService); + + // Make sure we have a pref initially, since the exception list service + // requires it. + Services.prefs.setStringPref(FEATURE_TRACKING_PREF_NAME, ""); + + let updateEvent = new UpdateEvent(); + let obs = data => { + let event = new CustomEvent("update", { detail: data }); + updateEvent.dispatchEvent(event); + }; + + let records = [ + { + id: "1", + last_modified: 1000000000000001, + feature: FEATURE_TRACKING_NAME, + pattern: "example.com", + }, + ]; + + // Add some initial data. + let db = RemoteSettings(COLLECTION_NAME).db; + await db.importChanges({}, Date.now(), records); + let promise = waitForEvent(updateEvent, "update"); + + exceptionListService.registerAndRunExceptionListObserver( + FEATURE_TRACKING_NAME, + FEATURE_TRACKING_PREF_NAME, + obs + ); + + Assert.equal(await promise, "", "No items in the list"); + + // Second event is from the RemoteSettings record. + let list = await waitForEvent(updateEvent, "update"); + Assert.equal(list, "example.com", "Has one item in the list"); + + records.push( + { + id: "2", + last_modified: 1000000000000002, + feature: FEATURE_TRACKING_NAME, + pattern: "MOZILLA.ORG", + }, + { + id: "3", + last_modified: 1000000000000003, + feature: "some-other-feature", + pattern: "noinclude.com", + }, + { + last_modified: 1000000000000004, + feature: FEATURE_TRACKING_NAME, + pattern: "*.example.org", + } + ); + + promise = waitForEvent(updateEvent, "update"); + + await RemoteSettings(COLLECTION_NAME).emit("sync", { + data: { current: records }, + }); + + list = await promise; + + Assert.equal( + list, + "example.com,mozilla.org,*.example.org", + "Has several items in the list" + ); + + promise = waitForEvent(updateEvent, "update"); + + Services.prefs.setStringPref(FEATURE_TRACKING_PREF_NAME, "test.com"); + + list = await promise; + + Assert.equal( + list, + "test.com,example.com,mozilla.org,*.example.org", + "Has several items in the list" + ); + + promise = waitForEvent(updateEvent, "update"); + + Services.prefs.setStringPref( + FEATURE_TRACKING_PREF_NAME, + "test.com,whatever.com,*.abc.com" + ); + + list = await promise; + + Assert.equal( + list, + "test.com,whatever.com,*.abc.com,example.com,mozilla.org,*.example.org", + "Has several items in the list" + ); + + exceptionListService.unregisterExceptionListObserver( + FEATURE_TRACKING_NAME, + obs + ); + exceptionListService.clear(); + + await db.clear(); +}); + +/** + * This test make sure when a feature registers itself to exceptionlist service, + * it can get the correct initial data. + */ +add_task(async function test_list_init_data() { + let exceptionListService = Cc[ + "@mozilla.org/url-classifier/exception-list-service;1" + ].getService(Ci.nsIUrlClassifierExceptionListService); + + // Make sure we have a pref initially, since the exception list service + // requires it. + Services.prefs.setStringPref(FEATURE_TRACKING_PREF_NAME, ""); + + let updateEvent = new UpdateEvent(); + + let records = [ + { + id: "1", + last_modified: 1000000000000001, + feature: FEATURE_TRACKING_NAME, + pattern: "tracking.example.com", + }, + { + id: "2", + last_modified: 1000000000000002, + feature: FEATURE_SOCIAL_NAME, + pattern: "social.example.com", + }, + { + id: "3", + last_modified: 1000000000000003, + feature: FEATURE_TRACKING_NAME, + pattern: "*.tracking.org", + }, + { + id: "4", + last_modified: 1000000000000004, + feature: FEATURE_SOCIAL_NAME, + pattern: "MOZILLA.ORG", + }, + ]; + + // Add some initial data. + let db = RemoteSettings(COLLECTION_NAME).db; + await db.importChanges({}, Date.now(), records); + + // The first registered feature make ExceptionListService get the initial data + // from remote setting. + let promise = waitForEvent(updateEvent, "update"); + + let obs = data => { + let event = new CustomEvent("update", { detail: data }); + updateEvent.dispatchEvent(event); + }; + exceptionListService.registerAndRunExceptionListObserver( + FEATURE_TRACKING_NAME, + FEATURE_TRACKING_PREF_NAME, + obs + ); + + let list = await promise; + Assert.equal(list, "", "Empty list initially"); + + Assert.equal( + await waitForEvent(updateEvent, "update"), + "tracking.example.com,*.tracking.org", + "Has several items in the list" + ); + + // Register another feature after ExceptionListService got the initial data. + promise = waitForEvent(updateEvent, "update"); + + exceptionListService.registerAndRunExceptionListObserver( + FEATURE_SOCIAL_NAME, + FEATURE_SOCIAL_PREF_NAME, + obs + ); + + list = await promise; + + Assert.equal( + list, + "social.example.com,mozilla.org", + "Has several items in the list" + ); + + // Test registering a feature after ExceptionListService recieved the synced data. + records.push( + { + id: "5", + last_modified: 1000000000000002, + feature: FEATURE_FINGERPRINTING_NAME, + pattern: "fingerprinting.example.com", + }, + { + id: "6", + last_modified: 1000000000000002, + feature: "other-fature", + pattern: "not-a-fingerprinting.example.com", + }, + { + id: "7", + last_modified: 1000000000000002, + feature: FEATURE_FINGERPRINTING_NAME, + pattern: "*.fingerprinting.org", + } + ); + + await RemoteSettings(COLLECTION_NAME).emit("sync", { + data: { current: records }, + }); + + promise = waitForEvent(updateEvent, "update"); + + exceptionListService.registerAndRunExceptionListObserver( + FEATURE_FINGERPRINTING_NAME, + FEATURE_FINGERPRINTING_PREF_NAME, + obs + ); + + list = await promise; + + Assert.equal( + list, + "fingerprinting.example.com,*.fingerprinting.org", + "Has several items in the list" + ); + + exceptionListService.unregisterExceptionListObserver( + FEATURE_TRACKING_NAME, + obs + ); + exceptionListService.unregisterExceptionListObserver( + FEATURE_SOCIAL_NAME, + obs + ); + exceptionListService.unregisterExceptionListObserver( + FEATURE_FINGERPRINTING_NAME, + obs + ); + exceptionListService.clear(); + + await db.clear(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_features.js b/toolkit/components/url-classifier/tests/unit/test_features.js new file mode 100644 index 0000000000..07b22b1b21 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_features.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +*/ + +"use strict"; + +add_test(async _ => { + ok( + Services.cookies, + "Force the cookie service to be initialized to avoid issues later. " + + "See https://bugzilla.mozilla.org/show_bug.cgi?id=1621759#c3" + ); + + let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIURIClassifier + ); + ok(!!classifier, "We have the URI-Classifier"); + + var tests = [ + { name: "a", expectedResult: false }, + { name: "tracking-annotation", expectedResult: true }, + { name: "tracking-protection", expectedResult: true }, + ]; + + tests.forEach(test => { + let feature; + try { + feature = classifier.getFeatureByName(test.name); + } catch (e) {} + + equal( + !!feature, + test.expectedResult, + "Exceptected result for: " + test.name + ); + if (feature) { + equal(feature.name, test.name, "Feature name matches"); + } + }); + + let uri = Services.io.newURI("https://example.com"); + + let feature = classifier.getFeatureByName("tracking-protection"); + + let results = await new Promise(resolve => { + classifier.asyncClassifyLocalWithFeatures( + uri, + [feature], + Ci.nsIUrlClassifierFeature.blocklist, + r => { + resolve(r); + } + ); + }); + equal(results.length, 0, "No tracker"); + + Services.prefs.setCharPref( + "urlclassifier.trackingTable.testEntries", + "example.com" + ); + + feature = classifier.getFeatureByName("tracking-protection"); + + results = await new Promise(resolve => { + classifier.asyncClassifyLocalWithFeatures( + uri, + [feature], + Ci.nsIUrlClassifierFeature.blocklist, + r => { + resolve(r); + } + ); + }); + equal(results.length, 1, "Tracker"); + let result = results[0]; + equal(result.feature.name, "tracking-protection", "Correct feature"); + equal(result.list, "tracking-blocklist-pref", "Correct list"); + + Services.prefs.clearUserPref("browser.safebrowsing.password.enabled"); + run_next_test(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js b/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js new file mode 100644 index 0000000000..b8d6c7b128 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js @@ -0,0 +1,438 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test ensures that the nsIUrlClassifierHashCompleter works as expected +// and simulates an HTTP server to provide completions. +// +// In order to test completions, each group of completions sent as one request +// to the HTTP server is called a completion set. There is currently not +// support for multiple requests being sent to the server at once, in this test. +// This tests makes a request for each element of |completionSets|, waits for +// a response and then moves to the next element. +// Each element of |completionSets| is an array of completions, and each +// completion is an object with the properties: +// hash: complete hash for the completion. Automatically right-padded +// to be COMPLETE_LENGTH. +// expectCompletion: boolean indicating whether the server should respond +// with a full hash. +// forceServerError: boolean indicating whether the server should respond +// with a 503. +// table: name of the table that the hash corresponds to. Only needs to be set +// if a completion is expected. +// chunkId: positive integer corresponding to the chunk that the hash belongs +// to. Only needs to be set if a completion is expected. +// multipleCompletions: boolean indicating whether the server should respond +// with more than one full hash. If this is set to true +// then |expectCompletion| must also be set to true and +// |hash| must have the same prefix as all |completions|. +// completions: an array of completions (objects with a hash, table and +// chunkId property as described above). This property is only +// used when |multipleCompletions| is set to true. + +// Basic prefixes with 2/3 completions. +var basicCompletionSet = [ + { + hash: "abcdefgh", + expectCompletion: true, + table: "test", + chunkId: 1234, + }, + { + hash: "1234", + expectCompletion: false, + }, + { + hash: "\u0000\u0000\u000012312", + expectCompletion: true, + table: "test", + chunkId: 1234, + }, +]; + +// 3 prefixes with 0 completions to test HashCompleter handling a 204 status. +var falseCompletionSet = [ + { + hash: "1234", + expectCompletion: false, + }, + { + hash: "", + expectCompletion: false, + }, + { + hash: "abc", + expectCompletion: false, + }, +]; + +// The current implementation (as of Mar 2011) sometimes sends duplicate +// entries to HashCompleter and even expects responses for duplicated entries. +var dupedCompletionSet = [ + { + hash: "1234", + expectCompletion: true, + table: "test", + chunkId: 1, + }, + { + hash: "5678", + expectCompletion: false, + table: "test2", + chunkId: 2, + }, + { + hash: "1234", + expectCompletion: true, + table: "test", + chunkId: 1, + }, + { + hash: "5678", + expectCompletion: false, + table: "test2", + chunkId: 2, + }, +]; + +// It is possible for a hash completion request to return with multiple +// completions, the HashCompleter should return all of these. +var multipleResponsesCompletionSet = [ + { + hash: "1234", + expectCompletion: true, + multipleCompletions: true, + completions: [ + { + hash: "123456", + table: "test1", + chunkId: 3, + }, + { + hash: "123478", + table: "test2", + chunkId: 4, + }, + ], + }, +]; + +function buildCompletionRequest(aCompletionSet) { + let prefixes = []; + let prefixSet = new Set(); + aCompletionSet.forEach(s => { + let prefix = s.hash.substring(0, 4); + if (prefixSet.has(prefix)) { + return; + } + prefixSet.add(prefix); + prefixes.push(prefix); + }); + return 4 + ":" + 4 * prefixes.length + "\n" + prefixes.join(""); +} + +function parseCompletionRequest(aRequest) { + // Format: [partial_length]:[num_of_prefix * partial_length]\n[prefixes_data] + + let tokens = /(\d):(\d+)/.exec(aRequest); + if (tokens.length < 3) { + dump("Request format error."); + return null; + } + + let partialLength = parseInt(tokens[1]); + + let payloadStart = + tokens[1].length + // partial length + 1 + // ':' + tokens[2].length + // payload length + 1; // '\n' + + let prefixSet = []; + for (let i = payloadStart; i < aRequest.length; i += partialLength) { + let prefix = aRequest.substr(i, partialLength); + if (prefix.length !== partialLength) { + dump("Header info not correct: " + aRequest.substr(0, payloadStart)); + return null; + } + prefixSet.push(prefix); + } + prefixSet.sort(); + + return prefixSet; +} + +// Compare the requests in string format. +function compareCompletionRequest(aRequest1, aRequest2) { + let prefixSet1 = parseCompletionRequest(aRequest1); + let prefixSet2 = parseCompletionRequest(aRequest2); + + return equal(JSON.stringify(prefixSet1), JSON.stringify(prefixSet2)); +} + +// The fifth completion set is added at runtime by getRandomCompletionSet. +// Each completion in the set only has one response and its purpose is to +// provide an easy way to test the HashCompleter handling an arbitrarily large +// completion set (determined by SIZE_OF_RANDOM_SET). +const SIZE_OF_RANDOM_SET = 16; +function getRandomCompletionSet(forceServerError) { + let completionSet = []; + let hashPrefixes = []; + + let seed = Math.floor(Math.random() * Math.pow(2, 32)); + dump("Using seed of " + seed + " for random completion set.\n"); + let rand = new LFSRgenerator(seed); + + for (let i = 0; i < SIZE_OF_RANDOM_SET; i++) { + let completion = { + expectCompletion: false, + forceServerError: false, + _finished: false, + }; + + // Generate a random 256 bit hash. First we get a random number and then + // convert it to a string. + let hash; + let prefix; + do { + hash = ""; + let length = 1 + rand.nextNum(5); + for (let j = 0; j < length; j++) { + hash += String.fromCharCode(rand.nextNum(8)); + } + prefix = hash.substring(0, 4); + } while (hashPrefixes.includes(prefix)); + + hashPrefixes.push(prefix); + completion.hash = hash; + + if (!forceServerError) { + completion.expectCompletion = rand.nextNum(1) == 1; + } else { + completion.forceServerError = true; + } + if (completion.expectCompletion) { + // Generate a random alpha-numeric string of length start with "test" for the + // table name. + completion.table = "test" + rand.nextNum(31).toString(36); + + completion.chunkId = rand.nextNum(16); + } + completionSet.push(completion); + } + + return completionSet; +} + +var completionSets = [ + basicCompletionSet, + falseCompletionSet, + dupedCompletionSet, + multipleResponsesCompletionSet, +]; +var currentCompletionSet = -1; +var finishedCompletions = 0; + +const SERVER_PATH = "/hash-completer"; +var server; + +// Completion hashes are automatically right-padded with null chars to have a +// length of COMPLETE_LENGTH. +// Taken from nsUrlClassifierDBService.h +const COMPLETE_LENGTH = 32; + +var completer = Cc["@mozilla.org/url-classifier/hashcompleter;1"].getService( + Ci.nsIUrlClassifierHashCompleter +); + +var gethashUrl; + +// Expected highest completion set for which the server sends a response. +var expectedMaxServerCompletionSet = 0; +var maxServerCompletionSet = 0; + +function run_test() { + // This test case exercises the backoff functionality so we can't leave it disabled. + Services.prefs.setBoolPref( + "browser.safebrowsing.provider.test.disableBackoff", + false + ); + // Generate a random completion set that return successful responses. + completionSets.push(getRandomCompletionSet(false)); + // We backoff after receiving an error, so requests shouldn't reach the + // server after that. + expectedMaxServerCompletionSet = completionSets.length; + // Generate some completion sets that return 503s. + for (let j = 0; j < 10; ++j) { + completionSets.push(getRandomCompletionSet(true)); + } + + // Fix up the completions before running the test. + for (let completionSet of completionSets) { + for (let completion of completionSet) { + // Pad the right of each |hash| so that the length is COMPLETE_LENGTH. + if (completion.multipleCompletions) { + for (let responseCompletion of completion.completions) { + let numChars = COMPLETE_LENGTH - responseCompletion.hash.length; + responseCompletion.hash += new Array(numChars + 1).join("\u0000"); + } + } else { + let numChars = COMPLETE_LENGTH - completion.hash.length; + completion.hash += new Array(numChars + 1).join("\u0000"); + } + } + } + do_test_pending(); + + server = new HttpServer(); + server.registerPathHandler(SERVER_PATH, hashCompleterServer); + + server.start(-1); + const SERVER_PORT = server.identity.primaryPort; + + gethashUrl = "http://localhost:" + SERVER_PORT + SERVER_PATH; + + runNextCompletion(); +} + +function runNextCompletion() { + // The server relies on currentCompletionSet to send the correct response, so + // don't increment it until we start the new set of callbacks. + currentCompletionSet++; + if (currentCompletionSet >= completionSets.length) { + finish(); + return; + } + + dump( + "Now on completion set index " + + currentCompletionSet + + ", length " + + completionSets[currentCompletionSet].length + + "\n" + ); + // Number of finished completions for this set. + finishedCompletions = 0; + for (let completion of completionSets[currentCompletionSet]) { + completer.complete( + completion.hash.substring(0, 4), + gethashUrl, + "test-phish-shavar", // Could be arbitrary v2 table name. + new callback(completion) + ); + } +} + +function hashCompleterServer(aRequest, aResponse) { + let stream = aRequest.bodyInputStream; + let wrapperStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + wrapperStream.setInputStream(stream); + + let len = stream.available(); + let data = wrapperStream.readBytes(len); + + // Check if we got the expected completion request. + let expectedRequest = buildCompletionRequest( + completionSets[currentCompletionSet] + ); + compareCompletionRequest(data, expectedRequest); + + // To avoid a response with duplicate hash completions, we keep track of all + // completed hash prefixes so far. + let completedHashes = []; + let responseText = ""; + + function responseForCompletion(x) { + return x.table + ":" + x.chunkId + ":" + x.hash.length + "\n" + x.hash; + } + // As per the spec, a server should response with a 204 if there are no + // full-length hashes that match the prefixes. + let httpStatus = 204; + for (let completion of completionSets[currentCompletionSet]) { + if ( + completion.expectCompletion && + !completedHashes.includes(completion.hash) + ) { + completedHashes.push(completion.hash); + + if (completion.multipleCompletions) { + responseText += completion.completions + .map(responseForCompletion) + .join(""); + } else { + responseText += responseForCompletion(completion); + } + } + if (completion.forceServerError) { + httpStatus = 503; + } + } + + dump("Server sending response for " + currentCompletionSet + "\n"); + maxServerCompletionSet = currentCompletionSet; + if (responseText && httpStatus != 503) { + aResponse.write(responseText); + } else { + aResponse.setStatusLine(null, httpStatus, null); + } +} + +function callback(completion) { + this._completion = completion; +} + +callback.prototype = { + completionV2: function completionV2(hash, table, chunkId, trusted) { + Assert.ok(this._completion.expectCompletion); + if (this._completion.multipleCompletions) { + for (let completion of this._completion.completions) { + if (completion.hash == hash) { + Assert.equal(JSON.stringify(hash), JSON.stringify(completion.hash)); + Assert.equal(table, completion.table); + Assert.equal(chunkId, completion.chunkId); + + completion._completed = true; + + if (this._completion.completions.every(x => x._completed)) { + this._completed = true; + } + + break; + } + } + } else { + // Hashes are not actually strings and can contain arbitrary data. + Assert.equal(JSON.stringify(hash), JSON.stringify(this._completion.hash)); + Assert.equal(table, this._completion.table); + Assert.equal(chunkId, this._completion.chunkId); + + this._completed = true; + } + }, + + completionFinished: function completionFinished(status) { + finishedCompletions++; + Assert.equal(!!this._completion.expectCompletion, !!this._completed); + this._completion._finished = true; + + // currentCompletionSet can mutate before all of the callbacks are complete. + if ( + currentCompletionSet < completionSets.length && + finishedCompletions == completionSets[currentCompletionSet].length + ) { + runNextCompletion(); + } + }, +}; + +function finish() { + Services.prefs.clearUserPref( + "browser.safebrowsing.provider.test.disableBackoff" + ); + + Assert.equal(expectedMaxServerCompletionSet, maxServerCompletionSet); + server.stop(function () { + do_test_finished(); + }); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_hashcompleter_v4.js b/toolkit/components/url-classifier/tests/unit/test_hashcompleter_v4.js new file mode 100644 index 0000000000..5910acb0eb --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_hashcompleter_v4.js @@ -0,0 +1,292 @@ +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +// These tables have a different update URL (for v4). +const TEST_TABLE_DATA_V4 = { + tableName: "test-phish-proto", + providerName: "google4", + updateUrl: "http://localhost:5555/safebrowsing/update?", + gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4?", +}; + +const PREF_NEXTUPDATETIME_V4 = + "browser.safebrowsing.provider.google4.nextupdatetime"; +const GETHASH_PATH = "/safebrowsing/gethash-v4"; + +// The protobuf binary represention of gethash response: +// minimumWaitDuration : 12 secs 10 nanosecs +// negativeCacheDuration : 120 secs 9 nanosecs +// +// { CompleteHash, ThreatType, CacheDuration { secs, nanos } }; +// { nsCString("01234567890123456789012345678901"), SOCIAL_ENGINEERING_PUBLIC, { 8, 500 } }, +// { nsCString("12345678901234567890123456789012"), SOCIAL_ENGINEERING_PUBLIC, { 7, 100} }, +// { nsCString("23456789012345678901234567890123"), SOCIAL_ENGINEERING_PUBLIC, { 1, 20 } }, + +const GETHASH_RESPONSE_CONTENT = + "\x0A\x2D\x08\x02\x1A\x22\x0A\x20\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x2A\x05\x08\x08\x10\xF4\x03\x0A\x2C\x08\x02\x1A\x22\x0A\x20\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x2A\x04\x08\x07\x10\x64\x0A\x2C\x08\x02\x1A\x22\x0A\x20\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x2A\x04\x08\x01\x10\x14\x12\x04\x08\x0C\x10\x0A\x1A\x04\x08\x78\x10\x09"; + +// The protobuf binary represention of update response: +// +// [ +// { +// 'threat_type': 2, // SOCIAL_ENGINEERING_PUBLIC +// 'response_type': 2, // FULL_UPDATE +// 'new_client_state': 'sta\x00te', // NEW_CLIENT_STATE +// 'checksum': { "sha256": CHECKSUM }, // CHECKSUM +// 'additions': { 'compression_type': RAW, +// 'prefix_size': 4, +// 'raw_hashes': "00000001000000020000000300000004"} +// } +// ] +// +const UPDATE_RESPONSE_CONTENT = + "\x0A\x4A\x08\x02\x20\x02\x2A\x18\x08\x01\x12\x14\x08\x04\x12\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x3A\x06\x73\x74\x61\x00\x74\x65\x42\x22\x0A\x20\x30\x67\xC7\x2C\x5E\x50\x1C\x31\xE3\xFE\xCA\x73\xF0\x47\xDC\x34\x1A\x95\x63\x99\xEC\x70\x5E\x0A\xEE\x9E\xFB\x17\xA1\x55\x35\x78\x12\x08\x08\x08\x10\x80\x94\xEB\xDC\x03"; +const UPDATE_PATH = "/safebrowsing/update"; + +let gListManager = Cc["@mozilla.org/url-classifier/listmanager;1"].getService( + Ci.nsIUrlListManager +); + +let gCompleter = Cc["@mozilla.org/url-classifier/hashcompleter;1"].getService( + Ci.nsIUrlClassifierHashCompleter +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "gUrlUtil", + "@mozilla.org/url-classifier/utils;1", + "nsIUrlClassifierUtils" +); + +// Handles request for TEST_TABLE_DATA_V4. +let gHttpServV4 = null; + +const NEW_CLIENT_STATE = "sta\0te"; +const CHECKSUM = + "\x30\x67\xc7\x2c\x5e\x50\x1c\x31\xe3\xfe\xca\x73\xf0\x47\xdc\x34\x1a\x95\x63\x99\xec\x70\x5e\x0a\xee\x9e\xfb\x17\xa1\x55\x35\x78"; + +Services.prefs.setBoolPref("browser.safebrowsing.debug", true); + +// The "\xFF\xFF" is to generate a base64 string with "/". +Services.prefs.setCharPref("browser.safebrowsing.id", "Firefox\xFF\xFF"); + +// Register tables. +gListManager.registerTable( + TEST_TABLE_DATA_V4.tableName, + TEST_TABLE_DATA_V4.providerName, + TEST_TABLE_DATA_V4.updateUrl, + TEST_TABLE_DATA_V4.gethashUrl +); + +// This is unfortunately needed since v4 gethash request +// requires the threat type (table name) as well as the +// state it's associated with. We have to run the update once +// to have the state written. +add_test(function test_update_v4() { + gListManager.disableUpdate(TEST_TABLE_DATA_V4.tableName); + gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName); + + // Force table update. + Services.prefs.setCharPref(PREF_NEXTUPDATETIME_V4, "1"); + gListManager.maybeToggleUpdateChecking(); +}); + +add_test(function test_getHashRequestV4() { + let request = gUrlUtil.makeFindFullHashRequestV4( + [TEST_TABLE_DATA_V4.tableName], + [btoa(NEW_CLIENT_STATE)], + [btoa("0123"), btoa("1234567"), btoa("1111")].sort() + ); + registerHandlerGethashV4("&$req=" + request); + let completeFinishedCnt = 0; + + gCompleter.complete( + "0123", + TEST_TABLE_DATA_V4.gethashUrl, + TEST_TABLE_DATA_V4.tableName, + { + completionV4(hash, table, duration, fullhashes) { + equal(hash, "0123"); + equal(table, TEST_TABLE_DATA_V4.tableName); + equal(duration, 120); + equal(fullhashes.length, 1); + + let match = fullhashes + .QueryInterface(Ci.nsIArray) + .queryElementAt(0, Ci.nsIFullHashMatch); + + equal(match.fullHash, "01234567890123456789012345678901"); + equal(match.cacheDuration, 8); + info("completion: " + match.fullHash + ", " + table); + }, + + completionFinished(status) { + equal(status, Cr.NS_OK); + completeFinishedCnt++; + if (3 === completeFinishedCnt) { + run_next_test(); + } + }, + } + ); + + gCompleter.complete( + "1234567", + TEST_TABLE_DATA_V4.gethashUrl, + TEST_TABLE_DATA_V4.tableName, + { + completionV4(hash, table, duration, fullhashes) { + equal(hash, "1234567"); + equal(table, TEST_TABLE_DATA_V4.tableName); + equal(duration, 120); + equal(fullhashes.length, 1); + + let match = fullhashes + .QueryInterface(Ci.nsIArray) + .queryElementAt(0, Ci.nsIFullHashMatch); + + equal(match.fullHash, "12345678901234567890123456789012"); + equal(match.cacheDuration, 7); + info("completion: " + match.fullHash + ", " + table); + }, + + completionFinished(status) { + equal(status, Cr.NS_OK); + completeFinishedCnt++; + if (3 === completeFinishedCnt) { + run_next_test(); + } + }, + } + ); + + gCompleter.complete( + "1111", + TEST_TABLE_DATA_V4.gethashUrl, + TEST_TABLE_DATA_V4.tableName, + { + completionV4(hash, table, duration, fullhashes) { + equal(hash, "1111"); + equal(table, TEST_TABLE_DATA_V4.tableName); + equal(duration, 120); + equal(fullhashes.length, 0); + }, + + completionFinished(status) { + equal(status, Cr.NS_OK); + completeFinishedCnt++; + if (3 === completeFinishedCnt) { + run_next_test(); + } + }, + } + ); +}); + +add_test(function test_minWaitDuration() { + let failedComplete = function () { + gCompleter.complete( + "0123", + TEST_TABLE_DATA_V4.gethashUrl, + TEST_TABLE_DATA_V4.tableName, + { + completionFinished(status) { + equal(status, Cr.NS_ERROR_ABORT); + }, + } + ); + }; + + let successComplete = function () { + gCompleter.complete( + "1234567", + TEST_TABLE_DATA_V4.gethashUrl, + TEST_TABLE_DATA_V4.tableName, + { + completionV4(hash, table, duration, fullhashes) { + equal(hash, "1234567"); + equal(table, TEST_TABLE_DATA_V4.tableName); + equal(fullhashes.length, 1); + + let match = fullhashes + .QueryInterface(Ci.nsIArray) + .queryElementAt(0, Ci.nsIFullHashMatch); + + equal(match.fullHash, "12345678901234567890123456789012"); + equal(match.cacheDuration, 7); + info("completion: " + match.fullHash + ", " + table); + }, + + completionFinished(status) { + equal(status, Cr.NS_OK); + run_next_test(); + }, + } + ); + }; + + let request = gUrlUtil.makeFindFullHashRequestV4( + [TEST_TABLE_DATA_V4.tableName], + [btoa(NEW_CLIENT_STATE)], + [btoa("1234567")] + ); + registerHandlerGethashV4("&$req=" + request); + + // The last gethash response contained a min wait duration 12 secs 10 nano + // So subsequent requests can happen only after the min wait duration + do_timeout(1000, failedComplete); + do_timeout(2000, failedComplete); + do_timeout(4000, failedComplete); + do_timeout(13000, successComplete); +}); + +function registerHandlerGethashV4(aExpectedQuery) { + gHttpServV4.registerPathHandler(GETHASH_PATH, null); + // V4 gethash handler. + gHttpServV4.registerPathHandler(GETHASH_PATH, function (request, response) { + equal(request.queryString, aExpectedQuery); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + GETHASH_RESPONSE_CONTENT, + GETHASH_RESPONSE_CONTENT.length + ); + }); +} + +function registerHandlerUpdateV4() { + // Update handler. Will respond a valid state to be verified in the + // gethash handler. + gHttpServV4.registerPathHandler(UPDATE_PATH, function (request, response) { + response.setHeader( + "Content-Type", + "application/vnd.google.safebrowsing-update", + false + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + UPDATE_RESPONSE_CONTENT, + UPDATE_RESPONSE_CONTENT.length + ); + + waitUntilMetaDataSaved(NEW_CLIENT_STATE, CHECKSUM, () => { + run_next_test(); + }); + }); +} + +function run_test() { + throwOnUpdateErrors(); + + gHttpServV4 = new HttpServer(); + gHttpServV4.registerDirectory("/", do_get_cwd()); + + registerHandlerUpdateV4(); + gHttpServV4.start(5555); + run_next_test(); +} + +registerCleanupFunction(function () { + stopThrowingOnUpdateErrors(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_listmanager.js b/toolkit/components/url-classifier/tests/unit/test_listmanager.js new file mode 100644 index 0000000000..751320f161 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_listmanager.js @@ -0,0 +1,355 @@ +// These tables share the same updateURL. +const TEST_TABLE_DATA_LIST = [ + // 0: + { + tableName: "test-listmanager0-digest256", + providerName: "google", + updateUrl: "http://localhost:4444/safebrowsing/update", + gethashUrl: "http://localhost:4444/safebrowsing/gethash0", + }, + + // 1: + { + tableName: "test-listmanager1-digest256", + providerName: "google", + updateUrl: "http://localhost:4444/safebrowsing/update", + gethashUrl: "http://localhost:4444/safebrowsing/gethash1", + }, + + // 2. + { + tableName: "test-listmanager2-digest256", + providerName: "google", + updateUrl: "http://localhost:4444/safebrowsing/update", + gethashUrl: "http://localhost:4444/safebrowsing/gethash2", + }, +]; + +// These tables have a different update URL (for v4). +const TEST_TABLE_DATA_V4 = { + tableName: "test-phish-proto", + providerName: "google4", + updateUrl: "http://localhost:5555/safebrowsing/update?", + gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4", +}; +const TEST_TABLE_DATA_V4_DISABLED = { + tableName: "test-unwanted-proto", + providerName: "google4", + updateUrl: "http://localhost:5555/safebrowsing/update?", + gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4", +}; + +const PREF_NEXTUPDATETIME = + "browser.safebrowsing.provider.google.nextupdatetime"; +const PREF_NEXTUPDATETIME_V4 = + "browser.safebrowsing.provider.google4.nextupdatetime"; + +let gListManager = Cc["@mozilla.org/url-classifier/listmanager;1"].getService( + Ci.nsIUrlListManager +); + +let gUrlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils +); + +// Global test server for serving safebrowsing updates. +let gHttpServ = null; +let gUpdateResponse = ""; +let gExpectedUpdateRequest = ""; +let gExpectedQueryV4 = ""; + +// Handles request for TEST_TABLE_DATA_V4. +let gHttpServV4 = null; + +// These two variables are used to synchronize the last two racing updates +// (in terms of "update URL") in test_update_all_tables(). +let gUpdatedCntForTableData = 0; // For TEST_TABLE_DATA_LIST. +let gIsV4Updated = false; // For TEST_TABLE_DATA_V4. + +const NEW_CLIENT_STATE = "sta\0te"; +const CHECKSUM = + "\x30\x67\xc7\x2c\x5e\x50\x1c\x31\xe3\xfe\xca\x73\xf0\x47\xdc\x34\x1a\x95\x63\x99\xec\x70\x5e\x0a\xee\x9e\xfb\x17\xa1\x55\x35\x78"; + +Services.prefs.setBoolPref("browser.safebrowsing.debug", true); + +// The "\xFF\xFF" is to generate a base64 string with "/". +Services.prefs.setCharPref("browser.safebrowsing.id", "Firefox\xFF\xFF"); + +// Register tables. +TEST_TABLE_DATA_LIST.forEach(function (t) { + gListManager.registerTable( + t.tableName, + t.providerName, + t.updateUrl, + t.gethashUrl + ); +}); + +gListManager.registerTable( + TEST_TABLE_DATA_V4.tableName, + TEST_TABLE_DATA_V4.providerName, + TEST_TABLE_DATA_V4.updateUrl, + TEST_TABLE_DATA_V4.gethashUrl +); + +// To test Bug 1302044. +gListManager.registerTable( + TEST_TABLE_DATA_V4_DISABLED.tableName, + TEST_TABLE_DATA_V4_DISABLED.providerName, + TEST_TABLE_DATA_V4_DISABLED.updateUrl, + TEST_TABLE_DATA_V4_DISABLED.gethashUrl +); + +const SERVER_INVOLVED_TEST_CASE_LIST = [ + // - Do table0 update. + // - Server would respond "a:5:32:32\n[DATA]". + function test_update_table0() { + disableAllUpdates(); + + gListManager.enableUpdate(TEST_TABLE_DATA_LIST[0].tableName); + gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";\n"; + + gUpdateResponse = "n:1000\ni:" + TEST_TABLE_DATA_LIST[0].tableName + "\n"; + gUpdateResponse += readFileToString("data/digest2.chunk"); + + forceTableUpdate(); + }, + + // - Do table0 update again. Since chunk 5 was added to table0 in the last + // update, the expected request contains "a:5". + // - Server would respond "s;2-12\n[DATA]". + function test_update_table0_with_existing_chunks() { + disableAllUpdates(); + + gListManager.enableUpdate(TEST_TABLE_DATA_LIST[0].tableName); + gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";a:5\n"; + + gUpdateResponse = "n:1000\ni:" + TEST_TABLE_DATA_LIST[0].tableName + "\n"; + gUpdateResponse += readFileToString("data/digest1.chunk"); + + forceTableUpdate(); + }, + + // - Do all-table update. + // - Server would respond no chunk control. + // + // Note that this test MUST be the last one in the array since we rely on + // the number of sever-involved test case to synchronize the racing last + // two udpates for different URL. + function test_update_all_tables() { + disableAllUpdates(); + + // Enable all tables including TEST_TABLE_DATA_V4! + TEST_TABLE_DATA_LIST.forEach(function (t) { + gListManager.enableUpdate(t.tableName); + }); + + // We register two v4 tables but only enable one of them + // to verify that the disabled tables are not updated. + // See Bug 1302044. + gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName); + gListManager.disableUpdate(TEST_TABLE_DATA_V4_DISABLED.tableName); + + // Expected results for v2. + gExpectedUpdateRequest = + TEST_TABLE_DATA_LIST[0].tableName + + ";a:5:s:2-12\n" + + TEST_TABLE_DATA_LIST[1].tableName + + ";\n" + + TEST_TABLE_DATA_LIST[2].tableName + + ";\n"; + gUpdateResponse = "n:1000\n"; + + // We test the request against the query string since v4 request + // would be appened to the query string. The request is generated + // by protobuf API (binary) then encoded to base64 format. + let requestV4 = gUrlUtils.makeUpdateRequestV4( + [TEST_TABLE_DATA_V4.tableName], + [""] + ); + gExpectedQueryV4 = "&$req=" + requestV4; + + forceTableUpdate(); + }, +]; + +SERVER_INVOLVED_TEST_CASE_LIST.forEach(t => add_test(t)); + +add_test(function test_partialUpdateV4() { + disableAllUpdates(); + + gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName); + + // Since the new client state has been responded and saved in + // test_update_all_tables, this update request should send + // a partial update to the server. + let requestV4 = gUrlUtils.makeUpdateRequestV4( + [TEST_TABLE_DATA_V4.tableName], + [btoa(NEW_CLIENT_STATE)] + ); + gExpectedQueryV4 = "&$req=" + requestV4; + + forceTableUpdate(); +}); + +// Tests nsIUrlListManager.getGethashUrl. +add_test(function test_getGethashUrl() { + TEST_TABLE_DATA_LIST.forEach(function (t) { + equal(gListManager.getGethashUrl(t.tableName), t.gethashUrl); + }); + equal( + gListManager.getGethashUrl(TEST_TABLE_DATA_V4.tableName), + TEST_TABLE_DATA_V4.gethashUrl + ); + run_next_test(); +}); + +function run_test() { + // Setup primary testing server. + gHttpServ = new HttpServer(); + gHttpServ.registerDirectory("/", do_get_cwd()); + + gHttpServ.registerPathHandler( + "/safebrowsing/update", + function (request, response) { + let body = NetUtil.readInputStreamToString( + request.bodyInputStream, + request.bodyInputStream.available() + ); + + // Verify if the request is as expected. + equal(body, gExpectedUpdateRequest); + + // Respond the update which is controlled by the test case. + response.setHeader( + "Content-Type", + "application/vnd.google.safebrowsing-update", + false + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(gUpdateResponse, gUpdateResponse.length); + + gUpdatedCntForTableData++; + + if (gUpdatedCntForTableData !== SERVER_INVOLVED_TEST_CASE_LIST.length) { + // This is not the last test case so run the next once upon the + // the update success. + waitForUpdateSuccess(run_next_test); + return; + } + + if (gIsV4Updated) { + run_next_test(); // All tests are done. Just finish. + return; + } + + info("Waiting for TEST_TABLE_DATA_V4 to be tested ..."); + } + ); + + gHttpServ.start(4444); + + // Setup v4 testing server for the different update URL. + gHttpServV4 = new HttpServer(); + gHttpServV4.registerDirectory("/", do_get_cwd()); + + gHttpServV4.registerPathHandler( + "/safebrowsing/update", + function (request, response) { + // V4 update request body should be empty. + equal(request.bodyInputStream.available(), 0); + + // Not on the spec. Found in Chromium source code... + equal(request.getHeader("X-HTTP-Method-Override"), "POST"); + + // V4 update request uses GET. + equal(request.method, "GET"); + + // V4 append the base64 encoded request to the query string. + equal(request.queryString, gExpectedQueryV4); + equal(request.queryString.indexOf("+"), -1); + equal(request.queryString.indexOf("/"), -1); + + // Respond a V2 compatible content for now. In the future we can + // send a meaningful response to test Bug 1284178 to see if the + // update is successfully stored to database. + response.setHeader( + "Content-Type", + "application/vnd.google.safebrowsing-update", + false + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + + // The protobuf binary represention of response: + // + // [ + // { + // 'threat_type': 2, // SOCIAL_ENGINEERING_PUBLIC + // 'response_type': 2, // FULL_UPDATE + // 'new_client_state': 'sta\x00te', // NEW_CLIENT_STATE + // 'checksum': { "sha256": CHECKSUM }, // CHECKSUM + // 'additions': { 'compression_type': RAW, + // 'prefix_size': 4, + // 'raw_hashes': "00000001000000020000000300000004"} + // } + // ] + // + let content = + "\x0A\x4A\x08\x02\x20\x02\x2A\x18\x08\x01\x12\x14\x08\x04\x12\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x3A\x06\x73\x74\x61\x00\x74\x65\x42\x22\x0A\x20\x30\x67\xC7\x2C\x5E\x50\x1C\x31\xE3\xFE\xCA\x73\xF0\x47\xDC\x34\x1A\x95\x63\x99\xEC\x70\x5E\x0A\xEE\x9E\xFB\x17\xA1\x55\x35\x78\x12\x08\x08\x08\x10\x80\x94\xEB\xDC\x03"; + + response.bodyOutputStream.write(content, content.length); + + if (gIsV4Updated) { + // This falls to the case where test_partialUpdateV4 is running. + // We are supposed to have verified the update request contains + // the state we set in the previous request. + waitForUpdateSuccess(run_next_test); + return; + } + + waitUntilMetaDataSaved(NEW_CLIENT_STATE, CHECKSUM, () => { + gIsV4Updated = true; + + if (gUpdatedCntForTableData === SERVER_INVOLVED_TEST_CASE_LIST.length) { + // All tests are done! + run_next_test(); + return; + } + + info("Wait for all sever-involved tests to be done ..."); + }); + } + ); + + gHttpServV4.start(5555); + + registerCleanupFunction(function () { + return (async function () { + await Promise.all([gHttpServ.stop(), gHttpServV4.stop()]); + })(); + }); + + run_next_test(); +} + +// A trick to force updating tables. However, before calling this, we have to +// call disableAllUpdates() first to clean up the updateCheckers in listmanager. +function forceTableUpdate() { + throwOnUpdateErrors(); + Services.prefs.setCharPref(PREF_NEXTUPDATETIME, "1"); + Services.prefs.setCharPref(PREF_NEXTUPDATETIME_V4, "1"); + gListManager.maybeToggleUpdateChecking(); +} + +function disableAllUpdates() { + stopThrowingOnUpdateErrors(); + TEST_TABLE_DATA_LIST.forEach(t => gListManager.disableUpdate(t.tableName)); + gListManager.disableUpdate(TEST_TABLE_DATA_V4.tableName); +} + +function waitForUpdateSuccess(callback) { + Services.obs.addObserver(function listener() { + Services.obs.removeObserver(listener, "safebrowsing-update-finished"); + callback(); + }, "safebrowsing-update-finished"); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_malwaretable_pref.js b/toolkit/components/url-classifier/tests/unit/test_malwaretable_pref.js new file mode 100644 index 0000000000..c7118f6e49 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_malwaretable_pref.js @@ -0,0 +1,4 @@ +// Ensure that the default value of malwareTable is always in sorted order +let originalValue = Services.prefs.getCharPref("urlclassifier.malwareTable"); +let sortedValue = originalValue.split(",").sort().join(","); +Assert.equal(originalValue, sortedValue); diff --git a/toolkit/components/url-classifier/tests/unit/test_partial.js b/toolkit/components/url-classifier/tests/unit/test_partial.js new file mode 100644 index 0000000000..1220665063 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_partial.js @@ -0,0 +1,607 @@ +/** + * DummyCompleter() lets tests easily specify the results of a partial + * hash completion request. + */ +function DummyCompleter() { + this.fragments = {}; + this.queries = []; + this.tableName = "test-phish-simple"; +} + +DummyCompleter.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierHashCompleter"]), + + complete(partialHash, gethashUrl, tableName, cb) { + this.queries.push(partialHash); + var fragments = this.fragments; + var self = this; + var doCallback = function () { + if (self.alwaysFail) { + cb.completionFinished(Cr.NS_ERROR_FAILURE); + return; + } + if (fragments[partialHash]) { + for (var i = 0; i < fragments[partialHash].length; i++) { + var chunkId = fragments[partialHash][i][0]; + var hash = fragments[partialHash][i][1]; + cb.completionV2(hash, self.tableName, chunkId); + } + } + cb.completionFinished(0); + }; + executeSoon(doCallback); + }, + + getHash(fragment) { + var data = new TextEncoder().encode(fragment); + var ch = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + ch.init(ch.SHA256); + ch.update(data, data.length); + var hash = ch.finish(false); + return hash.slice(0, 32); + }, + + addFragment(chunkId, fragment) { + this.addHash(chunkId, this.getHash(fragment)); + }, + + // This method allows the caller to generate complete hashes that match the + // prefix of a real fragment, but have different complete hashes. + addConflict(chunkId, fragment) { + var realHash = this.getHash(fragment); + var invalidHash = this.getHash("blah blah blah blah blah"); + this.addHash(chunkId, realHash.slice(0, 4) + invalidHash.slice(4, 32)); + }, + + addHash(chunkId, hash) { + var partial = hash.slice(0, 4); + if (this.fragments[partial]) { + this.fragments[partial].push([chunkId, hash]); + } else { + this.fragments[partial] = [[chunkId, hash]]; + } + }, + + compareQueries(fragments) { + var expectedQueries = []; + for (let i = 0; i < fragments.length; i++) { + expectedQueries.push(this.getHash(fragments[i]).slice(0, 4)); + } + Assert.equal(this.queries.length, expectedQueries.length); + expectedQueries.sort(); + this.queries.sort(); + for (let i = 0; i < this.queries.length; i++) { + Assert.equal(this.queries[i], expectedQueries[i]); + } + }, +}; + +function setupCompleter(table, hits, conflicts) { + var completer = new DummyCompleter(); + completer.tableName = table; + for (let i = 0; i < hits.length; i++) { + let chunkId = hits[i][0]; + let fragments = hits[i][1]; + for (let j = 0; j < fragments.length; j++) { + completer.addFragment(chunkId, fragments[j]); + } + } + for (let i = 0; i < conflicts.length; i++) { + let chunkId = conflicts[i][0]; + let fragments = conflicts[i][1]; + for (let j = 0; j < fragments.length; j++) { + completer.addConflict(chunkId, fragments[j]); + } + } + + dbservice.setHashCompleter(table, completer); + + return completer; +} + +function installCompleter(table, fragments, conflictFragments) { + return setupCompleter(table, fragments, conflictFragments); +} + +function installFailingCompleter(table) { + var completer = setupCompleter(table, [], []); + completer.alwaysFail = true; + return completer; +} + +// Helper assertion for checking dummy completer queries +gAssertions.completerQueried = function (data, cb) { + var completer = data[0]; + completer.compareQueries(data[1]); + cb(); +}; + +function doTest(updates, assertions) { + doUpdateTest(updates, assertions, runNextTest, updateError); +} + +// Test an add of two partial urls to a fresh database +function testPartialAdds() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + var completer = installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: addUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +function testPartialAddsWithConflicts() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + // Each result will have both a real match and a conflict + var completer = installCompleter( + "test-phish-simple", + [[1, addUrls]], + [[1, addUrls]] + ); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: addUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +// Test whether the fragmenting code does not cause duplicated completions +function testFragments() { + var addUrls = ["foo.com/a/b/c", "foo.net/", "foo.com/c/"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + var completer = installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: addUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +// Test http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec +// section 6.2 example 1 +function testSpecFragments() { + var probeUrls = ["a.b.c/1/2.html?param=1"]; + + var addUrls = [ + "a.b.c/1/2.html", + "a.b.c/", + "a.b.c/1/", + "b.c/1/2.html?param=1", + "b.c/1/2.html", + "b.c/", + "b.c/1/", + "a.b.c/1/2.html?param=1", + ]; + + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + var completer = installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: probeUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +// Test http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec +// section 6.2 example 2 +function testMoreSpecFragments() { + var probeUrls = ["a.b.c.d.e.f.g/1.html"]; + + var addUrls = [ + "a.b.c.d.e.f.g/1.html", + "a.b.c.d.e.f.g/", + "c.d.e.f.g/1.html", + "c.d.e.f.g/", + "d.e.f.g/1.html", + "d.e.f.g/", + "e.f.g/1.html", + "e.f.g/", + "f.g/1.html", + "f.g/", + ]; + + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + var completer = installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: probeUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +function testFalsePositives() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + // Each result will have no matching complete hashes and a non-matching + // conflict + var completer = installCompleter("test-phish-simple", [], [[1, addUrls]]); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsDontExist: addUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +function testEmptyCompleter() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + // Completer will never return full hashes + var completer = installCompleter("test-phish-simple", [], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsDontExist: addUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +function testCompleterFailure() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + // Completer will never return full hashes + var completer = installFailingCompleter("test-phish-simple"); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsDontExist: addUrls, + completerQueried: [completer, addUrls], + }; + + doTest([update], assertions); +} + +function testMixedSizesSameDomain() { + var add1Urls = ["foo.com/a"]; + var add2Urls = ["foo.com/b"]; + + var update1 = buildPhishingUpdate([{ chunkNum: 1, urls: add1Urls }], 4); + var update2 = buildPhishingUpdate([{ chunkNum: 2, urls: add2Urls }], 32); + + // We should only need to complete the partial hashes + var completer = installCompleter("test-phish-simple", [[1, add1Urls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1-2", + // both urls should match... + urlsExist: add1Urls.concat(add2Urls), + // ... but the completer should only be queried for the partial entry + completerQueried: [completer, add1Urls], + }; + + doTest([update1, update2], assertions); +} + +function testMixedSizesDifferentDomains() { + var add1Urls = ["foo.com/a"]; + var add2Urls = ["bar.com/b"]; + + var update1 = buildPhishingUpdate([{ chunkNum: 1, urls: add1Urls }], 4); + var update2 = buildPhishingUpdate([{ chunkNum: 2, urls: add2Urls }], 32); + + // We should only need to complete the partial hashes + var completer = installCompleter("test-phish-simple", [[1, add1Urls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1-2", + // both urls should match... + urlsExist: add1Urls.concat(add2Urls), + // ... but the completer should only be queried for the partial entry + completerQueried: [completer, add1Urls], + }; + + doTest([update1, update2], assertions); +} + +function testInvalidHashSize() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 12); // only 4 and 32 are legal hash sizes + + var addUrls2 = ["zaz.com/a", "xyz.com/b"]; + var update2 = buildPhishingUpdate([{ chunkNum: 2, urls: addUrls2 }], 4); + + installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:2", + urlsDontExist: addUrls, + }; + + // A successful update will trigger an error + doUpdateTest([update2, update], assertions, updateError, runNextTest); +} + +function testWrongTable() { + var addUrls = ["foo.com/a"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + var completer = installCompleter( + "test-malware-simple", // wrong table + [[1, addUrls]], + [] + ); + + // The above installCompleter installs the completer for test-malware-simple, + // we want it to be used for test-phish-simple too. + dbservice.setHashCompleter("test-phish-simple", completer); + + var assertions = { + tableData: "test-phish-simple;a:1", + // The urls were added as phishing urls, but the completer is claiming + // that they are malware urls, and we trust the completer in this case. + // The result will be discarded, so we can only check for non-existence. + urlsDontExist: addUrls, + // Make sure the completer was actually queried. + completerQueried: [completer, addUrls], + }; + + doUpdateTest( + [update], + assertions, + function () { + // Give the dbservice a chance to (not) cache the result. + do_timeout(3000, function () { + // The miss earlier will have caused a miss to be cached. + // Resetting the completer does not count as an update, + // so we will not be probed again. + var newCompleter = installCompleter( + "test-malware-simple", + [[1, addUrls]], + [] + ); + dbservice.setHashCompleter("test-phish-simple", newCompleter); + + var assertions1 = { + urlsDontExist: addUrls, + }; + checkAssertions(assertions1, runNextTest); + }); + }, + updateError + ); +} + +function setupCachedResults(addUrls, part2) { + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + + var completer = installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + // Request the add url. This should cause the completion to be cached. + urlsExist: addUrls, + // Make sure the completer was actually queried. + completerQueried: [completer, addUrls], + }; + + doUpdateTest( + [update], + assertions, + function () { + // Give the dbservice a chance to cache the result. + do_timeout(3000, part2); + }, + updateError + ); +} + +function testCachedResults() { + setupCachedResults(["foo.com/a"], function (add) { + // This is called after setupCachedResults(). Verify that + // checking the url again does not cause a completer request. + + // install a new completer, this one should never be queried. + var newCompleter = installCompleter("test-phish-simple", [[1, []]], []); + + var assertions = { + urlsExist: ["foo.com/a"], + completerQueried: [newCompleter, []], + }; + checkAssertions(assertions, runNextTest); + }); +} + +function testCachedResultsWithSub() { + setupCachedResults(["foo.com/a"], function () { + // install a new completer, this one should never be queried. + var newCompleter = installCompleter("test-phish-simple", [[1, []]], []); + + var removeUpdate = buildPhishingUpdate( + [{ chunkNum: 2, chunkType: "s", urls: ["1:foo.com/a"] }], + 4 + ); + + var assertions = { + urlsDontExist: ["foo.com/a"], + completerQueried: [newCompleter, []], + }; + + doTest([removeUpdate], assertions); + }); +} + +function testCachedResultsWithExpire() { + setupCachedResults(["foo.com/a"], function () { + // install a new completer, this one should never be queried. + var newCompleter = installCompleter("test-phish-simple", [[1, []]], []); + + var expireUpdate = "n:1000\ni:test-phish-simple\nad:1\n"; + + var assertions = { + urlsDontExist: ["foo.com/a"], + completerQueried: [newCompleter, []], + }; + doTest([expireUpdate], assertions); + }); +} + +function testCachedResultsFailure() { + var existUrls = ["foo.com/a"]; + setupCachedResults(existUrls, function () { + // This is called after setupCachedResults(). Verify that + // checking the url again does not cause a completer request. + + // install a new completer, this one should never be queried. + var newCompleter = installCompleter("test-phish-simple", [[1, []]], []); + + var assertions = { + urlsExist: existUrls, + completerQueried: [newCompleter, []], + }; + + checkAssertions(assertions, function () { + // Apply the update. The cached completes should be gone. + doErrorUpdate( + "test-phish-simple,test-malware-simple", + function () { + // Now the completer gets queried again. + var newCompleter2 = installCompleter( + "test-phish-simple", + [[1, existUrls]], + [] + ); + var assertions2 = { + tableData: "test-phish-simple;a:1", + urlsExist: existUrls, + completerQueried: [newCompleter2, existUrls], + }; + checkAssertions(assertions2, runNextTest); + }, + updateError + ); + }); + }); +} + +function testErrorList() { + var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4); + // The update failure should will kill the completes, so the above + // must be a prefix to get any hit at all past the update failure. + + var completer = installCompleter("test-phish-simple", [[1, addUrls]], []); + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: addUrls, + // These are complete urls, and will only be completed if the + // list is stale. + completerQueried: [completer, addUrls], + }; + + // Apply the update. + doStreamUpdate( + update, + function () { + // Now the test-phish-simple and test-malware-simple tables are marked + // as fresh. Fake an update failure to mark them stale. + doErrorUpdate( + "test-phish-simple,test-malware-simple", + function () { + // Now the lists should be marked stale. Check assertions. + checkAssertions(assertions, runNextTest); + }, + updateError + ); + }, + updateError + ); +} + +// Verify that different lists (test-phish-simple, +// test-malware-simple) maintain their freshness separately. +function testErrorListIndependent() { + var phishUrls = ["phish.com/a"]; + var malwareUrls = ["attack.com/a"]; + var update = buildPhishingUpdate([{ chunkNum: 1, urls: phishUrls }], 4); + // These have to persist past the update failure, so they must be prefixes, + // not completes. + + update += buildMalwareUpdate([{ chunkNum: 2, urls: malwareUrls }], 32); + + var completer = installCompleter("test-phish-simple", [[1, phishUrls]], []); + + var assertions = { + tableData: "test-malware-simple;a:2\ntest-phish-simple;a:1", + urlsExist: phishUrls, + malwareUrlsExist: malwareUrls, + // Only this phishing urls should be completed, because only the phishing + // urls will be stale. + completerQueried: [completer, phishUrls], + }; + + // Apply the update. + doStreamUpdate( + update, + function () { + // Now the test-phish-simple and test-malware-simple tables are + // marked as fresh. Fake an update failure to mark *just* + // phishing data as stale. + doErrorUpdate( + "test-phish-simple", + function () { + // Now the lists should be marked stale. Check assertions. + checkAssertions(assertions, runNextTest); + }, + updateError + ); + }, + updateError + ); +} + +function run_test() { + runTests([ + testPartialAdds, + testPartialAddsWithConflicts, + testFragments, + testSpecFragments, + testMoreSpecFragments, + testFalsePositives, + testEmptyCompleter, + testCompleterFailure, + testMixedSizesSameDomain, + testMixedSizesDifferentDomains, + testInvalidHashSize, + testWrongTable, + testCachedResults, + testCachedResultsWithSub, + testCachedResultsWithExpire, + testCachedResultsFailure, + testErrorList, + testErrorListIndependent, + ]); +} + +do_test_pending(); diff --git a/toolkit/components/url-classifier/tests/unit/test_platform_specific_threats.js b/toolkit/components/url-classifier/tests/unit/test_platform_specific_threats.js new file mode 100644 index 0000000000..499c9e478c --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_platform_specific_threats.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils +); + +function testMobileOnlyThreats() { + // Mobile-only threat type(s): + // - goog-harmful-proto (POTENTIALLY_HARMFUL_APPLICATION) + + (function testUpdateRequest() { + let requestWithPHA = urlUtils.makeUpdateRequestV4( + ["goog-phish-proto", "goog-harmful-proto"], + ["AAAAAA", "AAAAAA"] + ); + + let requestNoPHA = urlUtils.makeUpdateRequestV4( + ["goog-phish-proto"], + ["AAAAAA"] + ); + + if (AppConstants.platform === "android") { + notEqual( + requestWithPHA, + requestNoPHA, + "PHA (i.e. goog-harmful-proto) shouldn't be filtered on mobile platform." + ); + } else { + equal( + requestWithPHA, + requestNoPHA, + "PHA (i.e. goog-harmful-proto) should be filtered on non-mobile platform." + ); + } + })(); + + (function testFullHashRequest() { + let requestWithPHA = urlUtils.makeFindFullHashRequestV4( + ["goog-phish-proto", "goog-harmful-proto"], + ["", ""], // state. + [btoa("0123")] + ); // prefix. + + let requestNoPHA = urlUtils.makeFindFullHashRequestV4( + ["goog-phish-proto"], + [""], // state. + [btoa("0123")] + ); // prefix. + + if (AppConstants.platform === "android") { + notEqual( + requestWithPHA, + requestNoPHA, + "PHA (i.e. goog-harmful-proto) shouldn't be filtered on mobile platform." + ); + } else { + equal( + requestWithPHA, + requestNoPHA, + "PHA (i.e. goog-harmful-proto) should be filtered on non-mobile platform." + ); + } + })(); +} + +function testDesktopOnlyThreats() { + // Desktop-only threats: + // - goog-downloadwhite-proto (CSD_WHITELIST) + // - goog-badbinurl-proto (MALICIOUS_BINARY) + + let requestWithDesktopOnlyThreats = urlUtils.makeUpdateRequestV4( + ["goog-phish-proto", "goog-downloadwhite-proto", "goog-badbinurl-proto"], + ["", "", ""] + ); + + let requestNoDesktopOnlyThreats = urlUtils.makeUpdateRequestV4( + ["goog-phish-proto"], + [""] + ); + + if (AppConstants.platform === "android") { + equal( + requestWithDesktopOnlyThreats, + requestNoDesktopOnlyThreats, + "Android shouldn't contain 'goog-downloadwhite-proto' and 'goog-badbinurl-proto'." + ); + } else { + notEqual( + requestWithDesktopOnlyThreats, + requestNoDesktopOnlyThreats, + "Desktop should contain 'goog-downloadwhite-proto' and 'goog-badbinurl-proto'." + ); + } +} + +function run_test() { + testMobileOnlyThreats(); + testDesktopOnlyThreats(); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_pref.js b/toolkit/components/url-classifier/tests/unit/test_pref.js new file mode 100644 index 0000000000..3a72eceb8e --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_pref.js @@ -0,0 +1,15 @@ +function run_test() { + let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils + ); + + // The google protocol version should be "2.2" until we enable SB v4 + // by default. + equal(urlUtils.getProtocolVersion("google"), "2.2"); + + // Mozilla protocol version will stick to "2.2". + equal(urlUtils.getProtocolVersion("mozilla"), "2.2"); + + // Unknown provider version will be "2.2". + equal(urlUtils.getProtocolVersion("unknown-provider"), "2.2"); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_prefixset.js b/toolkit/components/url-classifier/tests/unit/test_prefixset.js new file mode 100644 index 0000000000..b45d4b6771 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_prefixset.js @@ -0,0 +1,178 @@ +// newPset: returns an empty nsIUrlClassifierPrefixSet. +function newPset() { + let pset = Cc["@mozilla.org/url-classifier/prefixset;1"].createInstance( + Ci.nsIUrlClassifierPrefixSet + ); + pset.init("all"); + return pset; +} + +// arrContains: returns true if |arr| contains the element |target|. Uses binary +// search and requires |arr| to be sorted. +function arrContains(arr, target) { + let start = 0; + let end = arr.length - 1; + let i = 0; + + while (end > start) { + i = start + ((end - start) >> 1); + let value = arr[i]; + + if (value < target) { + start = i + 1; + } else if (value > target) { + end = i - 1; + } else { + break; + } + } + if (start == end) { + i = start; + } + + return !(i < 0 || i >= arr.length) && arr[i] == target; +} + +// checkContents: Check whether the PrefixSet pset contains +// the prefixes in the passed array. +function checkContents(pset, prefixes) { + var outcount = {}, + outset = {}; + outset = pset.getPrefixes(outcount); + let inset = prefixes; + Assert.equal(inset.length, outset.length); + inset.sort((x, y) => x - y); + for (let i = 0; i < inset.length; i++) { + Assert.equal(inset[i], outset[i]); + } +} + +function wrappedProbe(pset, prefix) { + return pset.contains(prefix); +} + +// doRandomLookups: we use this to test for false membership with random input +// over the range of prefixes (unsigned 32-bits integers). +// pset: a nsIUrlClassifierPrefixSet to test. +// prefixes: an array of prefixes supposed to make up the prefix set. +// N: number of random lookups to make. +function doRandomLookups(pset, prefixes, N) { + for (let i = 0; i < N; i++) { + let randInt = prefixes[0]; + while (arrContains(prefixes, randInt)) { + randInt = Math.floor(Math.random() * Math.pow(2, 32)); + } + + Assert.ok(!wrappedProbe(pset, randInt)); + } +} + +// doExpectedLookups: we use this to test expected membership. +// pset: a nsIUrlClassifierPrefixSet to test. +// prefixes: +function doExpectedLookups(pset, prefixes, N) { + for (let i = 0; i < N; i++) { + prefixes.forEach(function (x) { + dump("Checking " + x + "\n"); + Assert.ok(wrappedProbe(pset, x)); + }); + } +} + +// testBasicPset: A very basic test of the prefix set to make sure that it +// exists and to give a basic example of its use. +function testBasicPset() { + let pset = Cc["@mozilla.org/url-classifier/prefixset;1"].createInstance( + Ci.nsIUrlClassifierPrefixSet + ); + let prefixes = [2, 50, 100, 2000, 78000, 1593203]; + pset.setPrefixes(prefixes, prefixes.length); + + Assert.ok(wrappedProbe(pset, 100)); + Assert.ok(!wrappedProbe(pset, 100000)); + Assert.ok(wrappedProbe(pset, 1593203)); + Assert.ok(!wrappedProbe(pset, 999)); + Assert.ok(!wrappedProbe(pset, 0)); + + checkContents(pset, prefixes); +} + +function testDuplicates() { + let pset = Cc["@mozilla.org/url-classifier/prefixset;1"].createInstance( + Ci.nsIUrlClassifierPrefixSet + ); + let prefixes = [1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 5, 6, 6, 7, 7, 9, 9, 9]; + pset.setPrefixes(prefixes, prefixes.length); + + Assert.ok(wrappedProbe(pset, 1)); + Assert.ok(wrappedProbe(pset, 2)); + Assert.ok(wrappedProbe(pset, 5)); + Assert.ok(wrappedProbe(pset, 9)); + Assert.ok(!wrappedProbe(pset, 4)); + Assert.ok(!wrappedProbe(pset, 8)); + + checkContents(pset, prefixes); +} + +function testSimplePset() { + let pset = newPset(); + let prefixes = [1, 2, 100, 400, 123456789]; + pset.setPrefixes(prefixes, prefixes.length); + + doRandomLookups(pset, prefixes, 100); + doExpectedLookups(pset, prefixes, 1); + + checkContents(pset, prefixes); +} + +function testReSetPrefixes() { + let pset = newPset(); + let prefixes = [1, 5, 100, 1000, 150000]; + pset.setPrefixes(prefixes, prefixes.length); + + doExpectedLookups(pset, prefixes, 1); + + let secondPrefixes = [12, 50, 300, 2000, 5000, 200000]; + pset.setPrefixes(secondPrefixes, secondPrefixes.length); + + doExpectedLookups(pset, secondPrefixes, 1); + for (let i = 0; i < prefixes.length; i++) { + Assert.ok(!wrappedProbe(pset, prefixes[i])); + } + + checkContents(pset, secondPrefixes); +} + +function testTinySet() { + let pset = Cc["@mozilla.org/url-classifier/prefixset;1"].createInstance( + Ci.nsIUrlClassifierPrefixSet + ); + let prefixes = [1]; + pset.setPrefixes(prefixes, prefixes.length); + + Assert.ok(wrappedProbe(pset, 1)); + Assert.ok(!wrappedProbe(pset, 100000)); + checkContents(pset, prefixes); + + prefixes = []; + pset.setPrefixes(prefixes, prefixes.length); + Assert.ok(!wrappedProbe(pset, 1)); + checkContents(pset, prefixes); +} + +var tests = [ + testBasicPset, + testSimplePset, + testReSetPrefixes, + testDuplicates, + testTinySet, +]; + +function run_test() { + // None of the tests use |executeSoon| or any sort of callbacks, so we can + // just run them in succession. + for (let i = 0; i < tests.length; i++) { + dump("Running " + tests[i].name + "\n"); + tests[i](); + } +} diff --git a/toolkit/components/url-classifier/tests/unit/test_provider_url.js b/toolkit/components/url-classifier/tests/unit/test_provider_url.js new file mode 100644 index 0000000000..8229448a9c --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_provider_url.js @@ -0,0 +1,32 @@ +const { updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); + +function updateVersion(version) { + updateAppInfo({ version }); +} + +add_test(function test_provider_url() { + let urls = [ + "browser.safebrowsing.provider.google.updateURL", + "browser.safebrowsing.provider.google.gethashURL", + "browser.safebrowsing.provider.mozilla.updateURL", + "browser.safebrowsing.provider.mozilla.gethashURL", + ]; + + // FIXME: Most of these only worked in the past because calling + // `updateAppInfo` did not actually replace `Services.appinfo`, which + // the URL formatter uses. + // let versions = ["49.0", "49.0.1", "49.0a1", "49.0b1", "49.0esr", "49.0.1esr"]; + let versions = ["49.0", "49.0.1"]; + + for (let version of versions) { + for (let url of urls) { + updateVersion(version); + let value = Services.urlFormatter.formatURLPref(url); + Assert.notEqual(value.indexOf("&appver=49.0&"), -1); + } + } + + run_next_test(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_rsListService.js b/toolkit/components/url-classifier/tests/unit/test_rsListService.js new file mode 100644 index 0000000000..cd70f92885 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_rsListService.js @@ -0,0 +1,424 @@ +/* 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/. */ + +"use strict"; + +/* Unit tests for the nsIUrlClassifierRemoteSettingsService implementation. */ + +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); +const { SBRS_UPDATE_MINIMUM_DELAY } = ChromeUtils.importESModule( + "resource://gre/modules/UrlClassifierRemoteSettingsService.sys.mjs" +); + +const COLLECTION_NAME = "tracking-protection-lists"; + +const REMOTE_SETTINGS_DATA = [ + { + Name: "content-fingerprinting-track-digest256", + attachment: { + hash: "96a4a850a1a475001148fa8a3a5efea58951f7176d3624ad7614fbf32732ee48", + size: 948, + filename: "content-fingerprinting-track-digest256", + location: + "main-workspace/tracking-protection-lists/content-fingerprinting-track-digest256", + mimetype: "text/plain", + }, + id: "content-fingerprinting-track-digest256", + Version: 1597417364, + }, + { + Name: "mozplugin-block-digest256", + attachment: { + hash: "dd2b800c7e4bad17e1c79f3e530c0b94e0a039adf4566f30bc3c285a547fa4fc", + size: 3029, + filename: "mozplugin-block-digest256", + location: + "main-workspace/tracking-protection-lists/mozplugin-block-digest256", + mimetype: "text/plain", + }, + id: "mozplugin-block-digest256", + Version: 1575583456, + }, + { + Name: "google-trackwhite-digest256", + attachment: { + hash: "1cd6d9353e97d66ac737a9716cd3a33416d6a4884dd12dcd1d65266e4c81dfad", + size: 1470328, + filename: "google-trackwhite-digest256", + location: + "main-workspace/tracking-protection-lists/google-trackwhite-digest256", + mimetype: "text/plain", + }, + id: "google-trackwhite-digest256", + Version: 1575583456, + }, + // Entry with non-exist attachment + { + Name: "social-track-digest256", + attachment: { + location: "main-workspace/tracking-protection-lists/not-exist", + }, + id: "social-track-digest256", + Version: 1111111111, + }, + // Entry with corrupted attachment + { + Name: "analytic-track-digest256", + attachment: { + hash: "644a0662bcf7313570ee68490e3805f5cc7a0503c097f040525c28dc5bfe4c97", + size: 58, + filename: "invalid.chunk", + location: "main-workspace/tracking-protection-lists/invalid.chunk", + mimetype: "text/plain", + }, + id: "analytic-track-digest256", + Version: 1111111111, + }, +]; + +let gListService = Cc["@mozilla.org/url-classifier/list-service;1"].getService( + Ci.nsIUrlClassifierRemoteSettingsService +); +let gDbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( + Ci.nsIUrlClassifierDBService +); + +class UpdateEvent extends EventTarget {} +function waitForEvent(element, eventName) { + return new Promise(function (resolve) { + element.addEventListener(eventName, e => resolve(e.detail), { once: true }); + }); +} + +function buildPayload(tables) { + let payload = ``; + for (let table of tables) { + payload += table[0]; + if (table[1] != null) { + payload += `;a:${table[1]}`; + } + payload += `\n`; + } + return payload; +} + +let server; +add_setup(async function init() { + Services.prefs.setCharPref( + "browser.safebrowsing.provider.mozilla.updateURL", + `moz-sbrs://tracking-protection-list` + ); + // Setup HTTP server for remote setting + server = new HttpServer(); + server.start(-1); + registerCleanupFunction(() => server.stop(() => {})); + + server.registerDirectory( + "/cdn/main-workspace/tracking-protection-lists/", + do_get_file("data") + ); + + server.registerPathHandler("/v1/", (request, response) => { + response.write( + JSON.stringify({ + capabilities: { + attachments: { + base_url: `http://localhost:${server.identity.primaryPort}/cdn/`, + }, + }, + }) + ); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setStatusLine(null, 200, "OK"); + }); + + Services.prefs.setCharPref( + "services.settings.server", + `http://localhost:${server.identity.primaryPort}/v1` + ); + + // Setup remote setting initial data + let db = await RemoteSettings(COLLECTION_NAME).db; + await db.importChanges({}, 42, REMOTE_SETTINGS_DATA); + + registerCleanupFunction(() => { + Services.prefs.clearUserPref( + "browser.safebrowsing.provider.mozilla.updateURL" + ); + Services.prefs.clearUserPref("services.settings.server"); + }); +}); + +// Test updates from RemoteSettings when there is no local data +add_task(async function test_empty_update() { + let updateEvent = new UpdateEvent(); + let promise = waitForEvent(updateEvent, "update"); + + const TEST_TABLES = [ + ["mozplugin-block-digest256", null], // empty + ["content-fingerprinting-track-digest256", null], // empty + ]; + + gListService.fetchList(buildPayload(TEST_TABLES), { + // nsIStreamListener observer + onStartRequest(request) {}, + onDataAvailable(aRequest, aStream, aOffset, aCount) { + let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(aStream); + let event = new CustomEvent("update", { + detail: stream.readBytes(aCount), + }); + updateEvent.dispatchEvent(event); + }, + onStopRequest(request, status) {}, + }); + + let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n"; + for (const table of TEST_TABLES) { + expected += `i:${table[0]}\n` + readFileToString(`data/${table[0]}`); + } + + Assert.equal( + await promise, + expected, + "Receive expected data from onDataAvailable" + ); + gListService.clear(); +}); + +// Test updates from RemoteSettings when we have an empty table, +// a table with an older version, and a table which is up-to-date. +add_task(async function test_update() { + let updateEvent = new UpdateEvent(); + let promise = waitForEvent(updateEvent, "update"); + + const TEST_TABLES = [ + ["mozplugin-block-digest256", 1575583456], // up-to-date + ["content-fingerprinting-track-digest256", 1575583456 - 1], // older version + ]; + + gListService.fetchList(buildPayload(TEST_TABLES), { + // observer + // nsIStreamListener observer + onStartRequest(request) {}, + onDataAvailable(aRequest, aStream, aOffset, aCount) { + let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(aStream); + let event = new CustomEvent("update", { + detail: stream.readBytes(aCount), + }); + updateEvent.dispatchEvent(event); + }, + onStopRequest(request, status) {}, + }); + + // Build request with no version + let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n"; + for (const table of TEST_TABLES) { + if (["content-fingerprinting-track-digest256"].includes(table[0])) { + expected += `i:${table[0]}\n` + readFileToString(`data/${table[0]}`); + } + } + + Assert.equal( + await promise, + expected, + "Receive expected data from onDataAvailable" + ); + gListService.clear(); +}); + +// Test updates from RemoteSettings service when all tables are up-to-date. +add_task(async function test_no_update() { + let updateEvent = new UpdateEvent(); + let promise = waitForEvent(updateEvent, "update"); + + const TEST_TABLES = [ + ["mozplugin-block-digest256", 1575583456], // up-to-date + ["content-fingerprinting-track-digest256", 1597417364], // up-to-date + ]; + + gListService.fetchList(buildPayload(TEST_TABLES), { + // nsIStreamListener observer + onStartRequest(request) {}, + onDataAvailable(aRequest, aStream, aOffset, aCount) { + let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(aStream); + let event = new CustomEvent("update", { + detail: stream.readBytes(aCount), + }); + updateEvent.dispatchEvent(event); + }, + onStopRequest(request, status) {}, + }); + + // No data is expected + let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n"; + + Assert.equal( + await promise, + expected, + "Receive expected data from onDataAvailable" + ); + gListService.clear(); +}); + +add_test(function test_update() { + let streamUpdater = Cc[ + "@mozilla.org/url-classifier/streamupdater;1" + ].getService(Ci.nsIUrlClassifierStreamUpdater); + + // Download some updates, and don't continue until the downloads are done. + function updateSuccess(aEvent) { + Assert.equal(SBRS_UPDATE_MINIMUM_DELAY, aEvent); + info("All data processed"); + run_next_test(); + } + // Just throw if we ever get an update or download error. + function handleError(aEvent) { + do_throw("We didn't download or update correctly: " + aEvent); + } + + streamUpdater.downloadUpdates( + "content-fingerprinting-track-digest256", + "content-fingerprinting-track-digest256;\n", + true, + "moz-sbrs://remote-setting", + updateSuccess, + handleError, + handleError + ); +}); + +add_test(function test_url_not_denylisted() { + let uri = Services.io.newURI("http://example.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + gDbService.lookup( + principal, + "content-fingerprinting-track-digest256", + function handleEvent(aEvent) { + // This URI is not on any lists. + Assert.equal("", aEvent); + run_next_test(); + } + ); +}); + +add_test(function test_url_denylisted() { + let uri = Services.io.newURI("https://www.foresee.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + gDbService.lookup( + principal, + "content-fingerprinting-track-digest256", + function handleEvent(aEvent) { + Assert.equal("content-fingerprinting-track-digest256", aEvent); + run_next_test(); + } + ); +}); + +add_test(function test_update_download_error() { + let streamUpdater = Cc[ + "@mozilla.org/url-classifier/streamupdater;1" + ].getService(Ci.nsIUrlClassifierStreamUpdater); + + // Download some updates, and don't continue until the downloads are done. + function updateSuccessOrError(aEvent) { + do_throw("Should be downbload error"); + } + // Just throw if we ever get an update or download error. + function downloadError(aEvent) { + run_next_test(); + } + + streamUpdater.downloadUpdates( + "social-track-digest256", + "social-track-digest256;\n", + true, + "moz-sbrs://remote-setting", + updateSuccessOrError, + updateSuccessOrError, + downloadError + ); +}); + +add_test(function test_update_update_error() { + let streamUpdater = Cc[ + "@mozilla.org/url-classifier/streamupdater;1" + ].getService(Ci.nsIUrlClassifierStreamUpdater); + + // Download some updates, and don't continue until the downloads are done. + function updateSuccessOrDownloadError(aEvent) { + do_throw("Should be update error"); + } + // Just throw if we ever get an update or download error. + function updateError(aEvent) { + run_next_test(); + } + + streamUpdater.downloadUpdates( + "analytic-track-digest256", + "analytic-track-digest256;\n", + true, + "moz-sbrs://remote-setting", + updateSuccessOrDownloadError, + updateError, + updateSuccessOrDownloadError + ); +}); + +add_task(async function test_update_large_file() { + let updateEvent = new UpdateEvent(); + let promise = waitForEvent(updateEvent, "update"); + + const TEST_TABLES = [ + ["google-trackwhite-digest256", 1575583456 - 1], // up-to-date + ]; + + gListService.fetchList(buildPayload(TEST_TABLES), { + // observer + // nsIStreamListener observer + onStartRequest(request) {}, + onDataAvailable(aRequest, aStream, aOffset, aCount) { + let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(aStream); + let event = new CustomEvent("update", { + detail: stream.readBytes(aCount), + }); + updateEvent.dispatchEvent(event); + }, + onStopRequest(request, status) {}, + }); + + // Build request with no version + let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n"; + for (const table of TEST_TABLES) { + if (["google-trackwhite-digest256"].includes(table[0])) { + expected += `i:${table[0]}\n` + readFileToString(`data/${table[0]}`); + } + } + + Assert.equal( + await promise, + expected, + "Receive expected data from onDataAvailable" + ); + gListService.clear(); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_safebrowsing_protobuf.js b/toolkit/components/url-classifier/tests/unit/test_safebrowsing_protobuf.js new file mode 100644 index 0000000000..73426751cb --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_safebrowsing_protobuf.js @@ -0,0 +1,29 @@ +function run_test() { + let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils + ); + + // No list at all. + let requestNoList = urlUtils.makeUpdateRequestV4([], []); + + // Only one valid list name. + let requestOneValid = urlUtils.makeUpdateRequestV4( + ["goog-phish-proto"], + ["AAAAAA"] + ); + + // Only one invalid list name. + let requestOneInvalid = urlUtils.makeUpdateRequestV4( + ["bad-list-name"], + ["AAAAAA"] + ); + + // One valid and one invalid list name. + let requestOneInvalidOneValid = urlUtils.makeUpdateRequestV4( + ["goog-phish-proto", "bad-list-name"], + ["AAAAAA", "AAAAAA"] + ); + + equal(requestNoList, requestOneInvalid); + equal(requestOneValid, requestOneInvalidOneValid); +} diff --git a/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js b/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js new file mode 100644 index 0000000000..6abdbb5f85 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js @@ -0,0 +1,166 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +*/ + +"use strict"; + +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); +const { UrlClassifierTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); + +const defaultTopWindowURI = NetUtil.newURI("http://www.example.com/"); + +var httpServer; +var trackingOrigin; + +// ShouldClassify algorithm uses the following parameters: +// 1. Ci.nsIChannel.LOAD_ BYPASS_URL_CLASSIFIER loadflags +// 2. Content type +// 3. triggering principal +// 4. be Conservative +// We test are the combinations here to make sure the algorithm is correct + +// const PARAM_LOAD_BYPASS_URL_CLASSIFIER = 1 << 0; +const PARAM_CONTENT_POLICY_TYPE_DOCUMENT = 1 << 1; +const PARAM_TRIGGERING_PRINCIPAL_SYSTEM = 1 << 2; +const PARAM_CAP_BE_CONSERVATIVE = 1 << 3; +const PARAM_MAX = 1 << 4; + +function getParameters(bitFlags) { + var params = { + loadFlags: Ci.nsIRequest.LOAD_NORMAL, + contentType: Ci.nsIContentPolicy.TYPE_OTHER, + system: false, + beConservative: false, + }; + + if (bitFlags & PARAM_TRIGGERING_PRINCIPAL_SYSTEM) { + params.loadFlags = Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER; + } + + if (bitFlags & PARAM_CONTENT_POLICY_TYPE_DOCUMENT) { + params.contentType = Ci.nsIContentPolicy.TYPE_DOCUMENT; + } + + if (bitFlags & PARAM_TRIGGERING_PRINCIPAL_SYSTEM) { + params.system = true; + } + + if (bitFlags & PARAM_CAP_BE_CONSERVATIVE) { + params.beConservative = true; + } + + return params; +} + +function getExpectedResult(params) { + if (params.loadFlags & Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER) { + return false; + } + if (params.beConservative) { + return false; + } + if ( + params.system && + params.contentType != Ci.nsIContentPolicy.TYPE_DOCUMENT + ) { + return false; + } + + return true; +} + +function setupHttpServer() { + httpServer = new HttpServer(); + httpServer.start(-1); + httpServer.identity.setPrimary( + "http", + "tracking.example.org", + httpServer.identity.primaryPort + ); + httpServer.identity.add( + "http", + "example.org", + httpServer.identity.primaryPort + ); + trackingOrigin = + "http://tracking.example.org:" + httpServer.identity.primaryPort; +} + +function setupChannel(params) { + var channel; + + if (params.system) { + channel = NetUtil.newChannel({ + uri: trackingOrigin + "/evil.js", + loadUsingSystemPrincipal: true, + contentPolicyType: params.contentType, + }); + } else { + let principal = Services.scriptSecurityManager.createContentPrincipal( + NetUtil.newURI(trackingOrigin), + {} + ); + channel = NetUtil.newChannel({ + uri: trackingOrigin + "/evil.js", + loadingPrincipal: principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + contentPolicyType: params.contentType, + }); + } + + channel.QueryInterface(Ci.nsIHttpChannel); + channel.requestMethod = "GET"; + channel.loadFlags |= params.loadFlags; + channel + .QueryInterface(Ci.nsIHttpChannelInternal) + .setTopWindowURIIfUnknown(defaultTopWindowURI); + channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = + params.beConservative; + + return channel; +} + +add_task(async function testShouldClassify() { + Services.prefs.setBoolPref( + "privacy.trackingprotection.annotate_channels", + true + ); + Services.prefs.setBoolPref("network.dns.native-is-localhost", true); + + setupHttpServer(); + + await UrlClassifierTestUtils.addTestTrackers(); + + for (let i = 0; i < PARAM_MAX; i++) { + let params = getParameters(i); + let channel = setupChannel(params); + + await new Promise(resolve => { + channel.asyncOpen({ + onStartRequest: (request, context) => { + Assert.equal( + !!( + request.QueryInterface(Ci.nsIClassifiedChannel) + .classificationFlags & + Ci.nsIClassifiedChannel.CLASSIFIED_ANY_BASIC_TRACKING + ), + getExpectedResult(params) + ); + request.cancel(Cr.NS_ERROR_ABORT); + resolve(); + }, + + onDataAvailable: (request, context, stream, offset, count) => {}, + onStopRequest: (request, context, status) => {}, + }); + }); + } + + UrlClassifierTestUtils.cleanupTestTrackers(); + + httpServer.stop(do_test_finished); +}); diff --git a/toolkit/components/url-classifier/tests/unit/test_streamupdater.js b/toolkit/components/url-classifier/tests/unit/test_streamupdater.js new file mode 100644 index 0000000000..1a2ee847d1 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_streamupdater.js @@ -0,0 +1,244 @@ +function doTest(updates, assertions, expectError) { + if (expectError) { + doUpdateTest(updates, assertions, updateError, runNextTest); + } else { + doUpdateTest(updates, assertions, runNextTest, updateError); + } +} + +// Never use the same URLs for multiple tests, because we aren't guaranteed +// to reset the database between tests. +function testFillDb() { + var add1Urls = ["zaz.com/a", "yxz.com/c"]; + + var update = "n:1000\n"; + update += "i:test-phish-simple\n"; + + var update1 = buildBareUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update1) + "\n"; + + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: add1Urls, + }; + + doTest([update], assertions, false); +} + +function testSimpleForward() { + var add1Urls = ["foo-simple.com/a", "bar-simple.com/c"]; + var add2Urls = ["foo-simple.com/b"]; + var add3Urls = ["bar-simple.com/d"]; + + var update = "n:1000\n"; + update += "i:test-phish-simple\n"; + + var update1 = buildBareUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update1) + "\n"; + + var update2 = buildBareUpdate([{ chunkNum: 2, urls: add2Urls }]); + update += "u:data:," + encodeURIComponent(update2) + "\n"; + + var update3 = buildBareUpdate([{ chunkNum: 3, urls: add3Urls }]); + update += "u:data:," + encodeURIComponent(update3) + "\n"; + + var assertions = { + tableData: "test-phish-simple;a:1-3", + urlsExist: add1Urls.concat(add2Urls).concat(add3Urls), + }; + + doTest([update], assertions, false); +} + +// Make sure that a nested forward (a forward within a forward) causes +// the update to fail. +function testNestedForward() { + var add1Urls = ["foo-nested.com/a", "bar-nested.com/c"]; + var add2Urls = ["foo-nested.com/b"]; + + var update = "n:1000\n"; + update += "i:test-phish-simple\n"; + + var update1 = buildBareUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update1) + "\n"; + + var update2 = buildBareUpdate([{ chunkNum: 2 }]); + var update3 = buildBareUpdate([{ chunkNum: 3, urls: add1Urls }]); + + update2 += "u:data:," + encodeURIComponent(update3) + "\n"; + + update += "u:data:," + encodeURIComponent(update2) + "\n"; + + var assertions = { + tableData: "", + urlsDontExist: add1Urls.concat(add2Urls), + }; + + doTest([update], assertions, true); +} + +// An invalid URL forward causes the update to fail. +function testInvalidUrlForward() { + var add1Urls = ["foo-invalid.com/a", "bar-invalid.com/c"]; + + var update = buildPhishingUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:asdf://blah/blah\n"; // invalid URL scheme + + // add1Urls is present, but that is an artifact of the way we do the test. + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: add1Urls, + }; + + doTest([update], assertions, true); +} + +// A failed network request causes the update to fail. +function testErrorUrlForward() { + var add1Urls = ["foo-forward.com/a", "bar-forward.com/c"]; + + var update = buildPhishingUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:http://test.invalid/asdf/asdf\n"; // invalid URL scheme + + // add1Urls is present, but that is an artifact of the way we do the test. + var assertions = { + tableData: "test-phish-simple;a:1", + urlsExist: add1Urls, + }; + + doTest([update], assertions, true); +} + +function testMultipleTables() { + var add1Urls = ["foo-multiple.com/a", "bar-multiple.com/c"]; + var add2Urls = ["foo-multiple.com/b"]; + var add3Urls = ["bar-multiple.com/d"]; + var add4Urls = ["bar-multiple.com/e"]; + var add6Urls = ["bar-multiple.com/g"]; + + var update = "n:1000\n"; + update += "i:test-phish-simple\n"; + + var update1 = buildBareUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update1) + "\n"; + + var update2 = buildBareUpdate([{ chunkNum: 2, urls: add2Urls }]); + update += "u:data:," + encodeURIComponent(update2) + "\n"; + + update += "i:test-malware-simple\n"; + + var update3 = buildBareUpdate([{ chunkNum: 3, urls: add3Urls }]); + update += "u:data:," + encodeURIComponent(update3) + "\n"; + + update += "i:test-unwanted-simple\n"; + var update4 = buildBareUpdate([{ chunkNum: 4, urls: add4Urls }]); + update += "u:data:," + encodeURIComponent(update4) + "\n"; + + update += "i:test-block-simple\n"; + var update6 = buildBareUpdate([{ chunkNum: 6, urls: add6Urls }]); + update += "u:data:," + encodeURIComponent(update6) + "\n"; + + var assertions = { + tableData: + "test-block-simple;a:6\ntest-malware-simple;a:3\ntest-phish-simple;a:1-2\ntest-unwanted-simple;a:4", + urlsExist: add1Urls.concat(add2Urls), + malwareUrlsExist: add3Urls, + unwantedUrlsExist: add4Urls, + blockedUrlsExist: add6Urls, + }; + + doTest([update], assertions, false); +} + +function testUrlInMultipleTables() { + var add1Urls = ["foo-forward.com/a"]; + + var update = "n:1000\n"; + update += "i:test-phish-simple\n"; + + var update1 = buildBareUpdate([{ chunkNum: 1, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update1) + "\n"; + + update += "i:test-malware-simple\n"; + var update2 = buildBareUpdate([{ chunkNum: 2, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update2) + "\n"; + + update += "i:test-unwanted-simple\n"; + var update3 = buildBareUpdate([{ chunkNum: 3, urls: add1Urls }]); + update += "u:data:," + encodeURIComponent(update3) + "\n"; + + var assertions = { + tableData: + "test-malware-simple;a:2\ntest-phish-simple;a:1\ntest-unwanted-simple;a:3", + urlExistInMultipleTables: { + url: add1Urls, + tables: "test-malware-simple,test-phish-simple,test-unwanted-simple", + }, + }; + + doTest([update], assertions, false); +} + +function Observer(callback) { + this.observe = callback; +} + +Observer.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), +}; + +// Tests a database reset request. +function testReset() { + // The moz-phish-simple table is populated separately from the other update in + // a separate update request. Therefore it should not be reset when we run the + // updates later in this function. + var mozAddUrls = ["moz-reset.com/a"]; + var mozUpdate = buildMozPhishingUpdate([{ chunkNum: 1, urls: mozAddUrls }]); + + var dataUpdate = "data:," + encodeURIComponent(mozUpdate); + + streamUpdater.downloadUpdates( + mozTables, + "", + true, + dataUpdate, + () => {}, + updateError, + updateError + ); + + var addUrls1 = ["foo-reset.com/a", "foo-reset.com/b"]; + var update1 = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls1 }]); + + var update2 = "n:1000\nr:pleasereset\n"; + + var addUrls3 = ["bar-reset.com/a", "bar-reset.com/b"]; + var update3 = buildPhishingUpdate([{ chunkNum: 3, urls: addUrls3 }]); + + var assertions = { + tableData: "moz-phish-simple;a:1\ntest-phish-simple;a:3", // tables that should still be there. + mozPhishingUrlsExist: mozAddUrls, // mozAddUrls added prior to the reset + // but it should still exist after reset. + urlsExist: addUrls3, // addUrls3 added after the reset. + urlsDontExist: addUrls1, // addUrls1 added prior to the reset + }; + + // Use these update responses in order. The update request only + // contains test-*-simple tables so the reset will only apply to these. + doTest([update1, update2, update3], assertions, false); +} + +function run_test() { + runTests([ + testFillDb, + testSimpleForward, + testNestedForward, + testInvalidUrlForward, + testErrorUrlForward, + testMultipleTables, + testUrlInMultipleTables, + testReset, + ]); +} + +do_test_pending(); diff --git a/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js b/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js new file mode 100644 index 0000000000..b1bec40b4f --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js @@ -0,0 +1,50 @@ +function run_test() { + let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( + Ci.nsIUrlClassifierUtils + ); + + // Test list name to threat type conversion. + + equal(urlUtils.convertListNameToThreatType("goog-malware-proto"), 1); + equal(urlUtils.convertListNameToThreatType("googpub-phish-proto"), 2); + equal(urlUtils.convertListNameToThreatType("goog-unwanted-proto"), 3); + equal(urlUtils.convertListNameToThreatType("goog-harmful-proto"), 4); + equal(urlUtils.convertListNameToThreatType("goog-phish-proto"), 5); + equal(urlUtils.convertListNameToThreatType("goog-badbinurl-proto"), 7); + equal(urlUtils.convertListNameToThreatType("goog-downloadwhite-proto"), 9); + + try { + urlUtils.convertListNameToThreatType("bad-list-name"); + ok(false, "Bad list name should lead to exception."); + } catch (e) {} + + try { + urlUtils.convertListNameToThreatType("bad-list-name"); + ok(false, "Bad list name should lead to exception."); + } catch (e) {} + + // Test threat type to list name conversion. + equal(urlUtils.convertThreatTypeToListNames(1), "goog-malware-proto"); + equal( + urlUtils.convertThreatTypeToListNames(2), + "googpub-phish-proto,moztest-phish-proto,test-phish-proto" + ); + equal( + urlUtils.convertThreatTypeToListNames(3), + "goog-unwanted-proto,moztest-unwanted-proto,test-unwanted-proto" + ); + equal(urlUtils.convertThreatTypeToListNames(4), "goog-harmful-proto"); + equal(urlUtils.convertThreatTypeToListNames(5), "goog-phish-proto"); + equal(urlUtils.convertThreatTypeToListNames(7), "goog-badbinurl-proto"); + equal(urlUtils.convertThreatTypeToListNames(9), "goog-downloadwhite-proto"); + + try { + urlUtils.convertThreatTypeToListNames(0); + ok(false, "Bad threat type should lead to exception."); + } catch (e) {} + + try { + urlUtils.convertThreatTypeToListNames(100); + ok(false, "Bad threat type should lead to exception."); + } catch (e) {} +} diff --git a/toolkit/components/url-classifier/tests/unit/xpcshell.toml b/toolkit/components/url-classifier/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..561188ceb3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/unit/xpcshell.toml @@ -0,0 +1,56 @@ +[DEFAULT] +head = "head_urlclassifier.js" +tags = "condprof" +skip-if = ["os == 'android'"] +support-files = ["data/**"] + +["test_addsub.js"] + +["test_backoff.js"] + +["test_bug1274685_unowned_list.js"] + +["test_canonicalization.js"] + +["test_channelClassifierService.js"] + +["test_dbservice.js"] +skip-if = ["condprof"] # Bug 1769828 + +["test_digest256.js"] +run-sequentially = "very high failure rate in parallel" + +["test_exceptionListService.js"] +tags = "remote-settings" + +["test_features.js"] + +["test_hashcompleter.js"] + +["test_hashcompleter_v4.js"] + +["test_listmanager.js"] +run-sequentially = "very high failure rate in parallel" + +["test_malwaretable_pref.js"] + +["test_partial.js"] + +["test_platform_specific_threats.js"] + +["test_pref.js"] + +["test_prefixset.js"] + +["test_provider_url.js"] + +["test_rsListService.js"] +tags = "remote-settings" + +["test_safebrowsing_protobuf.js"] + +["test_shouldclassify.js"] + +["test_streamupdater.js"] + +["test_threat_type_conversion.js"] |