diff options
Diffstat (limited to 'toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtectionStorage.cpp')
-rw-r--r-- | toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtectionStorage.cpp | 841 |
1 files changed, 841 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtectionStorage.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtectionStorage.cpp new file mode 100644 index 0000000000..bdd2c7dc18 --- /dev/null +++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtectionStorage.cpp @@ -0,0 +1,841 @@ +/* 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 "BounceTrackingProtectionStorage.h" +#include <cstdint> + +#include "BounceTrackingState.h" +#include "BounceTrackingStateGlobal.h" +#include "ErrorList.h" +#include "MainThreadUtils.h" +#include "mozIStorageConnection.h" +#include "mozIStorageService.h" +#include "mozIStorageStatement.h" +#include "mozStorageCID.h" +#include "mozilla/Components.h" +#include "mozilla/Monitor.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Services.h" +#include "mozilla/ShutdownPhase.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIObserverService.h" +#include "nsIPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsStringFwd.h" +#include "nsVariant.h" +#include "nscore.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCRT.h" + +#define BOUNCE_TRACKING_PROTECTION_DB_FILENAME \ + "bounce-tracking-protection.sqlite"_ns +#define SCHEMA_VERSION 1 + +namespace mozilla { + +NS_IMPL_ISUPPORTS(BounceTrackingProtectionStorage, nsIAsyncShutdownBlocker, + nsIObserver); + +BounceTrackingStateGlobal* +BounceTrackingProtectionStorage::GetOrCreateStateGlobal( + nsIPrincipal* aPrincipal) { + MOZ_ASSERT(aPrincipal); + return GetOrCreateStateGlobal(aPrincipal->OriginAttributesRef()); +} + +BounceTrackingStateGlobal* +BounceTrackingProtectionStorage::GetOrCreateStateGlobal( + BounceTrackingState* aBounceTrackingState) { + MOZ_ASSERT(aBounceTrackingState); + return GetOrCreateStateGlobal(aBounceTrackingState->OriginAttributesRef()); +} + +BounceTrackingStateGlobal* +BounceTrackingProtectionStorage::GetOrCreateStateGlobal( + const OriginAttributes& aOriginAttributes) { + return mStateGlobal.GetOrInsertNew(aOriginAttributes, this, + aOriginAttributes); +} + +nsresult BounceTrackingProtectionStorage::ClearBySiteHost( + const nsACString& aSiteHost, OriginAttributes* aOriginAttributes) { + NS_ENSURE_TRUE(!aSiteHost.IsEmpty(), NS_ERROR_INVALID_ARG); + + // If OriginAttributes are passed only clear the matching state global. + if (aOriginAttributes) { + RefPtr<BounceTrackingStateGlobal> stateGlobal = + mStateGlobal.Get(*aOriginAttributes); + if (stateGlobal) { + nsresult rv = stateGlobal->ClearSiteHost(aSiteHost, true); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + // Otherwise we need to clear the host across all state globals. + for (auto iter = mStateGlobal.Iter(); !iter.Done(); iter.Next()) { + BounceTrackingStateGlobal* stateGlobal = iter.Data(); + MOZ_ASSERT(stateGlobal); + // Update in memory state. Skip storage so we can batch the writes later. + nsresult rv = stateGlobal->ClearSiteHost(aSiteHost, true); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // Update the database. + // Private browsing data is not written to disk. + if (aOriginAttributes && + aOriginAttributes->mPrivateBrowsingId != + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) { + return NS_OK; + } + return DeleteDBEntries(aOriginAttributes, aSiteHost); +} + +nsresult BounceTrackingProtectionStorage::ClearByTimeRange(PRTime aFrom, + PRTime aTo) { + for (auto iter = mStateGlobal.Iter(); !iter.Done(); iter.Next()) { + BounceTrackingStateGlobal* stateGlobal = iter.Data(); + MOZ_ASSERT(stateGlobal); + // Update in memory state. Skip storage so we can batch the writes later. + nsresult rv = + stateGlobal->ClearByTimeRange(aFrom, Some(aTo), Nothing(), true); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Update the database. + return DeleteDBEntriesInTimeRange(nullptr, aFrom, Some(aTo)); +} + +nsresult BounceTrackingProtectionStorage::ClearByOriginAttributesPattern( + const OriginAttributesPattern& aOriginAttributesPattern) { + // Clear in memory state. + for (auto iter = mStateGlobal.Iter(); !iter.Done(); iter.Next()) { + if (aOriginAttributesPattern.Matches(iter.Key())) { + iter.Remove(); + } + } + + // Update the database. + // Private browsing data is not written to disk. + if (aOriginAttributesPattern.mPrivateBrowsingId.WasPassed() && + aOriginAttributesPattern.mPrivateBrowsingId.Value() != + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) { + return NS_OK; + } + return DeleteDBEntriesByOriginAttributesPattern(aOriginAttributesPattern); +} + +nsresult BounceTrackingProtectionStorage::UpdateDBEntry( + const OriginAttributes& aOriginAttributes, const nsACString& aSiteHost, + EntryType aEntryType, PRTime aTimeStamp) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSiteHost.IsEmpty()); + MOZ_ASSERT(aTimeStamp); + + nsresult rv = WaitForInitialization(); + NS_ENSURE_SUCCESS(rv, rv); + + if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { + nsAutoCString originAttributeSuffix; + aOriginAttributes.CreateSuffix(originAttributeSuffix); + MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, + ("%s: originAttributes: %s, siteHost=%s, entryType=%d, " + "timeStamp=%" PRId64, + __FUNCTION__, originAttributeSuffix.get(), + PromiseFlatCString(aSiteHost).get(), + static_cast<uint8_t>(aEntryType), aTimeStamp)); + } + + IncrementPendingWrites(); + + RefPtr<BounceTrackingProtectionStorage> self = this; + nsCString siteHost(aSiteHost); + + mBackgroundThread->Dispatch( + NS_NewRunnableFunction( + "BounceTrackingProtectionStorage::UpdateEntry", + [self, aOriginAttributes, siteHost, aEntryType, aTimeStamp]() { + nsresult rv = + UpsertData(self->mDatabaseConnection, aOriginAttributes, + siteHost, aEntryType, aTimeStamp); + self->DecrementPendingWrites(); + NS_ENSURE_SUCCESS_VOID(rv); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + + return NS_OK; +} + +nsresult BounceTrackingProtectionStorage::DeleteDBEntries( + OriginAttributes* aOriginAttributes, const nsACString& aSiteHost) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSiteHost.IsEmpty()); + MOZ_ASSERT(!aOriginAttributes || + aOriginAttributes->mPrivateBrowsingId == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID, + "Must not write private browsing data to the table."); + + nsresult rv = WaitForInitialization(); + NS_ENSURE_SUCCESS(rv, rv); + + if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { + nsAutoCString originAttributeSuffix("*"); + if (aOriginAttributes) { + aOriginAttributes->CreateSuffix(originAttributeSuffix); + } + MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, + ("%s: originAttributes: %s, siteHost=%s", __FUNCTION__, + originAttributeSuffix.get(), PromiseFlatCString(aSiteHost).get())); + } + + RefPtr<BounceTrackingProtectionStorage> self = this; + nsCString siteHost(aSiteHost); + Maybe<OriginAttributes> originAttributes; + if (aOriginAttributes) { + originAttributes.emplace(*aOriginAttributes); + } + + IncrementPendingWrites(); + mBackgroundThread->Dispatch( + NS_NewRunnableFunction("BounceTrackingProtectionStorage::DeleteEntry", + [self, originAttributes, siteHost]() { + nsresult rv = + DeleteData(self->mDatabaseConnection, + originAttributes, siteHost); + self->DecrementPendingWrites(); + NS_ENSURE_SUCCESS_VOID(rv); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + + return NS_OK; +} + +nsresult BounceTrackingProtectionStorage::Clear() { + MOZ_ASSERT(NS_IsMainThread()); + // Clear in memory data. + mStateGlobal.Clear(); + + // Clear on disk data. + nsresult rv = WaitForInitialization(); + NS_ENSURE_SUCCESS(rv, rv); + + IncrementPendingWrites(); + RefPtr<BounceTrackingProtectionStorage> self = this; + mBackgroundThread->Dispatch( + NS_NewRunnableFunction("BounceTrackingProtectionStorage::Clear", + [self]() { + nsresult rv = + ClearData(self->mDatabaseConnection); + self->DecrementPendingWrites(); + NS_ENSURE_SUCCESS_VOID(rv); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + return NS_OK; +} + +nsresult BounceTrackingProtectionStorage::DeleteDBEntriesInTimeRange( + OriginAttributes* aOriginAttributes, PRTime aFrom, Maybe<PRTime> aTo, + Maybe<BounceTrackingProtectionStorage::EntryType> aEntryType) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_MIN(aFrom, 0); + NS_ENSURE_TRUE(aTo.isNothing() || aTo.value() > aFrom, NS_ERROR_INVALID_ARG); + + nsresult rv = WaitForInitialization(); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<BounceTrackingProtectionStorage> self = this; + Maybe<OriginAttributes> originAttributes; + if (aOriginAttributes) { + originAttributes.emplace(*aOriginAttributes); + } + + IncrementPendingWrites(); + mBackgroundThread->Dispatch( + NS_NewRunnableFunction( + "BounceTrackingProtectionStorage::DeleteDBEntriesInTimeRange", + [self, originAttributes, aFrom, aTo, aEntryType]() { + nsresult rv = + DeleteDataInTimeRange(self->mDatabaseConnection, + originAttributes, aFrom, aTo, aEntryType); + self->DecrementPendingWrites(); + NS_ENSURE_SUCCESS_VOID(rv); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + return NS_OK; +} + +nsresult +BounceTrackingProtectionStorage::DeleteDBEntriesByOriginAttributesPattern( + const OriginAttributesPattern& aOriginAttributesPattern) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aOriginAttributesPattern.mPrivateBrowsingId.WasPassed() || + aOriginAttributesPattern.mPrivateBrowsingId.Value() == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID, + "Must not clear private browsing data from the table."); + + nsresult rv = WaitForInitialization(); + NS_ENSURE_SUCCESS(rv, rv); + + IncrementPendingWrites(); + RefPtr<BounceTrackingProtectionStorage> self = this; + mBackgroundThread->Dispatch( + NS_NewRunnableFunction( + "BounceTrackingProtectionStorage::" + "DeleteEntriesByOriginAttributesPattern", + [self, aOriginAttributesPattern]() { + nsresult rv = DeleteDataByOriginAttributesPattern( + self->mDatabaseConnection, aOriginAttributesPattern); + self->DecrementPendingWrites(); + NS_ENSURE_SUCCESS_VOID(rv); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + return NS_OK; +} + +// nsIAsyncShutdownBlocker + +NS_IMETHODIMP BounceTrackingProtectionStorage::BlockShutdown( + nsIAsyncShutdownClient* aClient) { + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = WaitForInitialization(); + NS_ENSURE_SUCCESS(rv, rv); + + MonitorAutoLock lock(mMonitor); + mShuttingDown.Flip(); + + RefPtr<BounceTrackingProtectionStorage> self = this; + mBackgroundThread->Dispatch( + NS_NewRunnableFunction( + "BounceTrackingProtectionStorage::BlockShutdown", + [self]() { + MonitorAutoLock lock(self->mMonitor); + + MOZ_ASSERT(self->mPendingWrites == 0); + + if (self->mDatabaseConnection) { + Unused << self->mDatabaseConnection->Close(); + self->mDatabaseConnection = nullptr; + } + + self->mFinalized.Flip(); + self->mMonitor.NotifyAll(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "BounceTrackingProtectionStorage::BlockShutdown " + "- mainthread callback", + [self]() { self->Finalize(); })); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + + return NS_OK; +} + +nsresult BounceTrackingProtectionStorage::WaitForInitialization() { + MOZ_ASSERT(NS_IsMainThread(), + "Must only wait for initialization in the main thread."); + MonitorAutoLock lock(mMonitor); + while (!mInitialized && !mErrored && !mShuttingDown) { + mMonitor.Wait(); + } + if (mErrored) { + return NS_ERROR_FAILURE; + } + if (mShuttingDown) { + return NS_ERROR_NOT_AVAILABLE; + } + return NS_OK; +} + +void BounceTrackingProtectionStorage::Finalize() { + nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier(); + MOZ_ASSERT(asc); + DebugOnly<nsresult> rv = asc->RemoveBlocker(this); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +// nsIObserver + +NS_IMETHODIMP +BounceTrackingProtectionStorage::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + AssertIsOnMainThread(); + if (nsCRT::strcmp(aTopic, "last-pb-context-exited") != 0) { + return nsresult::NS_ERROR_FAILURE; + } + + uint32_t removedCount = 0; + // Clear in-memory private browsing entries. + for (auto iter = mStateGlobal.Iter(); !iter.Done(); iter.Next()) { + BounceTrackingStateGlobal* stateGlobal = iter.Data(); + MOZ_ASSERT(stateGlobal); + if (stateGlobal->IsPrivateBrowsing()) { + iter.Remove(); + removedCount++; + } + } + MOZ_LOG( + gBounceTrackingProtectionLog, LogLevel::Debug, + ("%s: last-pb-context-exited: Removed %d private browsing state globals", + __FUNCTION__, removedCount)); + + return NS_OK; +} + +// nsIAsyncShutdownBlocker + +already_AddRefed<nsIAsyncShutdownClient> +BounceTrackingProtectionStorage::GetAsyncShutdownBarrier() const { + nsCOMPtr<nsIAsyncShutdownService> svc = components::AsyncShutdown::Service(); + MOZ_RELEASE_ASSERT(svc); + + nsCOMPtr<nsIAsyncShutdownClient> client; + nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(client)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + MOZ_RELEASE_ASSERT(client); + + return client.forget(); +} + +NS_IMETHODIMP BounceTrackingProtectionStorage::GetState(nsIPropertyBag**) { + return NS_OK; +} + +NS_IMETHODIMP BounceTrackingProtectionStorage::GetName(nsAString& aName) { + aName.AssignLiteral("BounceTrackingProtectionStorage: Flushing to disk"); + return NS_OK; +} + +nsresult BounceTrackingProtectionStorage::Init() { + MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__)); + + // Init shouldn't be called if the feature is disabled. + NS_ENSURE_TRUE( + StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup(), + NS_ERROR_FAILURE); + + // Register a shutdown blocker so we can flush pending changes to disk before + // shutdown. + // Init may also be called during shutdown, e.g. because of clearing data + // during shutdown. + nsCOMPtr<nsIAsyncShutdownClient> shutdownBarrier = GetAsyncShutdownBarrier(); + NS_ENSURE_TRUE(shutdownBarrier, NS_ERROR_FAILURE); + + bool closed; + nsresult rv = shutdownBarrier->GetIsClosed(&closed); + if (closed || NS_WARN_IF(NS_FAILED(rv))) { + MonitorAutoLock lock(mMonitor); + mShuttingDown.Flip(); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + rv = shutdownBarrier->AddBlocker( + this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Listen for last private browsing context exited message so we can clean up + // in memory state when the PBM session ends. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_FAILURE); + rv = observerService->AddObserver(this, "last-pb-context-exited", false); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the database file. + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(mDatabaseFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDatabaseFile->AppendNative(BOUNCE_TRACKING_PROTECTION_DB_FILENAME); + NS_ENSURE_SUCCESS(rv, rv); + + // Init the database and import data. + NS_ENSURE_SUCCESS( + NS_CreateBackgroundTaskQueue("BounceTrackingProtectionStorage", + getter_AddRefs(mBackgroundThread)), + NS_ERROR_FAILURE); + + RefPtr<BounceTrackingProtectionStorage> self = this; + + mBackgroundThread->Dispatch( + NS_NewRunnableFunction("BounceTrackingProtectionStorage::Init", + [self]() { + MonitorAutoLock lock(self->mMonitor); + nsresult rv = self->CreateDatabaseConnection(); + if (NS_WARN_IF(NS_FAILED(rv))) { + self->mErrored.Flip(); + self->mMonitor.Notify(); + return; + } + + rv = self->LoadMemoryStateFromDisk(); + if (NS_WARN_IF(NS_FAILED(rv))) { + self->mErrored.Flip(); + self->mMonitor.Notify(); + return; + } + + self->mInitialized.Flip(); + self->mMonitor.Notify(); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + + return NS_OK; +} + +nsresult BounceTrackingProtectionStorage::CreateDatabaseConnection() { + MOZ_ASSERT(!NS_IsMainThread()); + NS_ENSURE_TRUE(mDatabaseFile, NS_ERROR_NULL_POINTER); + + nsCOMPtr<mozIStorageService> storage = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(storage, NS_ERROR_UNEXPECTED); + + nsresult rv = storage->OpenDatabase(mDatabaseFile, + mozIStorageService::CONNECTION_DEFAULT, + getter_AddRefs(mDatabaseConnection)); + if (rv == NS_ERROR_FILE_CORRUPTED) { + rv = mDatabaseFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + rv = storage->OpenDatabase(mDatabaseFile, + mozIStorageService::CONNECTION_DEFAULT, + getter_AddRefs(mDatabaseConnection)); + } + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(mDatabaseConnection, NS_ERROR_UNEXPECTED); + bool ready = false; + mDatabaseConnection->GetConnectionReady(&ready); + NS_ENSURE_TRUE(ready, NS_ERROR_UNEXPECTED); + + return EnsureTable(); +} + +nsresult BounceTrackingProtectionStorage::EnsureTable() { + MOZ_ASSERT(!NS_IsMainThread()); + NS_ENSURE_TRUE(mDatabaseConnection, NS_ERROR_UNEXPECTED); + + nsresult rv = mDatabaseConnection->SetSchemaVersion(SCHEMA_VERSION); + NS_ENSURE_SUCCESS(rv, rv); + + const constexpr auto createTableQuery = + "CREATE TABLE IF NOT EXISTS sites (" + "originAttributeSuffix TEXT NOT NULL," + "siteHost TEXT NOT NULL, " + "entryType INTEGER NOT NULL, " + "timeStamp INTEGER NOT NULL, " + "PRIMARY KEY (originAttributeSuffix, siteHost)" + ");"_ns; + + return mDatabaseConnection->ExecuteSimpleSQL(createTableQuery); +} + +nsresult BounceTrackingProtectionStorage::LoadMemoryStateFromDisk() { + MOZ_ASSERT(!NS_IsMainThread(), + "Must not load the table from disk in the main thread."); + + const constexpr auto selectAllQuery( + "SELECT originAttributeSuffix, siteHost, entryType, timeStamp FROM sites;"_ns); + + nsCOMPtr<mozIStorageStatement> readStmt; + nsresult rv = mDatabaseConnection->CreateStatement(selectAllQuery, + getter_AddRefs(readStmt)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult; + // Collect DB entries into an array to hand to the main thread later. + nsTArray<ImportEntry> importEntries; + while (NS_SUCCEEDED(readStmt->ExecuteStep(&hasResult)) && hasResult) { + nsAutoCString originAttributeSuffix, siteHost; + int64_t timeStamp; + int32_t typeInt; + + rv = readStmt->GetUTF8String(0, originAttributeSuffix); + NS_ENSURE_SUCCESS(rv, rv); + rv = readStmt->GetUTF8String(1, siteHost); + NS_ENSURE_SUCCESS(rv, rv); + rv = readStmt->GetInt32(2, &typeInt); + NS_ENSURE_SUCCESS(rv, rv); + rv = readStmt->GetInt64(3, &timeStamp); + NS_ENSURE_SUCCESS(rv, rv); + + // Convert entryType field to enum. + BounceTrackingProtectionStorage::EntryType entryType = + static_cast<BounceTrackingProtectionStorage::EntryType>(typeInt); + // Check that the enum value is valid. + if (NS_WARN_IF( + entryType != + BounceTrackingProtectionStorage::EntryType::BounceTracker && + entryType != + BounceTrackingProtectionStorage::EntryType::UserActivation)) { + continue; + } + + OriginAttributes oa; + bool success = oa.PopulateFromSuffix(originAttributeSuffix); + if (NS_WARN_IF(!success)) { + continue; + } + + // Collect entries to dispatch to main thread later. + importEntries.AppendElement( + ImportEntry{oa, siteHost, entryType, timeStamp}); + } + + // We can only access the state map on the main thread. + RefPtr<BounceTrackingProtectionStorage> self = this; + return NS_DispatchToMainThread(NS_NewRunnableFunction( + "BounceTrackingProtectionStorage::LoadMemoryStateFromDisk", + [self, importEntries = std::move(importEntries)]() { + // For each entry get or create BounceTrackingStateGlobal and insert it + // into global state map. + for (const ImportEntry& entry : importEntries) { + RefPtr<BounceTrackingStateGlobal> stateGlobal = + self->GetOrCreateStateGlobal(entry.mOriginAttributes); + MOZ_ASSERT(stateGlobal); + + nsresult rv; + if (entry.mEntryType == + BounceTrackingProtectionStorage::EntryType::BounceTracker) { + rv = stateGlobal->RecordBounceTracker(entry.mSiteHost, + entry.mTimeStamp, true); + } else { + rv = stateGlobal->RecordUserActivation(entry.mSiteHost, + entry.mTimeStamp, true); + } + if (NS_WARN_IF(NS_FAILED(rv)) && + MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { + nsAutoCString originAttributeSuffix; + entry.mOriginAttributes.CreateSuffix(originAttributeSuffix); + + MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, + ("%s: Failed to load entry from disk: " + "originAttributeSuffix=%s, siteHost=%s, entryType=%d, " + "timeStamp=%" PRId64, + __FUNCTION__, originAttributeSuffix.get(), + PromiseFlatCString(entry.mSiteHost).get(), + static_cast<uint8_t>(entry.mEntryType), entry.mTimeStamp)); + } + } + })); +} + +void BounceTrackingProtectionStorage::IncrementPendingWrites() { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mPendingWrites < std::numeric_limits<uint32_t>::max()); + mPendingWrites++; +} + +void BounceTrackingProtectionStorage::DecrementPendingWrites() { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mPendingWrites > 0); + mPendingWrites--; +} + +// static +nsresult BounceTrackingProtectionStorage::UpsertData( + mozIStorageConnection* aDatabaseConnection, + const OriginAttributes& aOriginAttributes, const nsACString& aSiteHost, + BounceTrackingProtectionStorage::EntryType aEntryType, PRTime aTimeStamp) { + MOZ_ASSERT(!NS_IsMainThread(), + "Must not write to the table from the main thread."); + MOZ_ASSERT(aDatabaseConnection); + MOZ_ASSERT(!aSiteHost.IsEmpty()); + MOZ_ASSERT(aTimeStamp > 0); + MOZ_ASSERT(aOriginAttributes.mPrivateBrowsingId == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID, + "Must not write private browsing data to the table."); + + auto constexpr upsertQuery = + "INSERT INTO sites (originAttributeSuffix, siteHost, entryType, " + "timeStamp)" + "VALUES (:originAttributeSuffix, :siteHost, :entryType, :timeStamp)" + "ON CONFLICT (originAttributeSuffix, siteHost)" + "DO UPDATE SET entryType = :entryType, timeStamp = :timeStamp;"_ns; + + nsCOMPtr<mozIStorageStatement> upsertStmt; + nsresult rv = aDatabaseConnection->CreateStatement( + upsertQuery, getter_AddRefs(upsertStmt)); + NS_ENSURE_SUCCESS(rv, rv); + + // Serialize OriginAttributes. + nsAutoCString originAttributeSuffix; + aOriginAttributes.CreateSuffix(originAttributeSuffix); + + rv = upsertStmt->BindUTF8StringByName("originAttributeSuffix"_ns, + originAttributeSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + rv = upsertStmt->BindUTF8StringByName("siteHost"_ns, aSiteHost); + NS_ENSURE_SUCCESS(rv, rv); + + rv = upsertStmt->BindInt32ByName("entryType"_ns, + static_cast<int32_t>(aEntryType)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = upsertStmt->BindInt64ByName("timeStamp"_ns, aTimeStamp); + NS_ENSURE_SUCCESS(rv, rv); + + return upsertStmt->Execute(); +} + +// static +nsresult BounceTrackingProtectionStorage::DeleteData( + mozIStorageConnection* aDatabaseConnection, + Maybe<OriginAttributes> aOriginAttributes, const nsACString& aSiteHost) { + MOZ_ASSERT(!NS_IsMainThread(), + "Must not write to the table from the main thread."); + MOZ_ASSERT(aDatabaseConnection); + MOZ_ASSERT(!aSiteHost.IsEmpty()); + MOZ_ASSERT(aOriginAttributes.isNothing() || + aOriginAttributes->mPrivateBrowsingId == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID); + + nsAutoCString deleteQuery("DELETE FROM sites WHERE siteHost = :siteHost"); + + if (aOriginAttributes) { + deleteQuery.AppendLiteral( + " AND originAttributeSuffix = :originAttributeSuffix"); + } + + nsCOMPtr<mozIStorageStatement> upsertStmt; + nsresult rv = aDatabaseConnection->CreateStatement( + deleteQuery, getter_AddRefs(upsertStmt)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = upsertStmt->BindUTF8StringByName("siteHost"_ns, aSiteHost); + NS_ENSURE_SUCCESS(rv, rv); + + if (aOriginAttributes) { + nsAutoCString originAttributeSuffix; + aOriginAttributes->CreateSuffix(originAttributeSuffix); + rv = upsertStmt->BindUTF8StringByName("originAttributeSuffix"_ns, + originAttributeSuffix); + NS_ENSURE_SUCCESS(rv, rv); + } + + return upsertStmt->Execute(); +} + +// static +nsresult BounceTrackingProtectionStorage::DeleteDataInTimeRange( + mozIStorageConnection* aDatabaseConnection, + Maybe<OriginAttributes> aOriginAttributes, PRTime aFrom, Maybe<PRTime> aTo, + Maybe<BounceTrackingProtectionStorage::EntryType> aEntryType) { + MOZ_ASSERT(!NS_IsMainThread(), + "Must not write to the table from the main thread."); + MOZ_ASSERT(aDatabaseConnection); + MOZ_ASSERT(aOriginAttributes.isNothing() || + aOriginAttributes->mPrivateBrowsingId == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID); + MOZ_ASSERT(aFrom >= 0); + MOZ_ASSERT(aTo.isNothing() || aTo.value() > aFrom); + + nsAutoCString deleteQuery( + "DELETE FROM sites " + "WHERE timeStamp >= :aFrom"_ns); + + if (aTo.isSome()) { + deleteQuery.AppendLiteral(" AND timeStamp <= :aTo"); + } + + if (aOriginAttributes) { + deleteQuery.AppendLiteral( + " AND originAttributeSuffix = :originAttributeSuffix"); + } + + if (aEntryType.isSome()) { + deleteQuery.AppendLiteral(" AND entryType = :entryType"); + } + deleteQuery.AppendLiteral(";"); + + nsCOMPtr<mozIStorageStatement> deleteStmt; + nsresult rv = aDatabaseConnection->CreateStatement( + deleteQuery, getter_AddRefs(deleteStmt)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = deleteStmt->BindInt64ByName("aFrom"_ns, aFrom); + NS_ENSURE_SUCCESS(rv, rv); + + if (aTo.isSome()) { + rv = deleteStmt->BindInt64ByName("aTo"_ns, aTo.value()); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aOriginAttributes) { + nsAutoCString originAttributeSuffix; + aOriginAttributes->CreateSuffix(originAttributeSuffix); + rv = deleteStmt->BindUTF8StringByName("originAttributeSuffix"_ns, + originAttributeSuffix); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aEntryType.isSome()) { + rv = deleteStmt->BindInt32ByName("entryType"_ns, + static_cast<int32_t>(*aEntryType)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return deleteStmt->Execute(); +} + +nsresult BounceTrackingProtectionStorage::DeleteDataByOriginAttributesPattern( + mozIStorageConnection* aDatabaseConnection, + const OriginAttributesPattern& aOriginAttributesPattern) { + MOZ_ASSERT(!NS_IsMainThread(), + "Must not write to the table from the main thread."); + MOZ_ASSERT(aDatabaseConnection); + + nsCOMPtr<mozIStorageFunction> patternMatchFunction( + new OriginAttrsPatternMatchOASuffixSQLFunction(aOriginAttributesPattern)); + + nsresult rv = aDatabaseConnection->CreateFunction( + "ORIGIN_ATTRS_PATTERN_MATCH_OA_SUFFIX"_ns, 1, patternMatchFunction); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDatabaseConnection->ExecuteSimpleSQL( + "DELETE FROM sites WHERE " + "ORIGIN_ATTRS_PATTERN_MATCH_OA_SUFFIX(originAttributeSuffix);"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + return aDatabaseConnection->RemoveFunction( + "ORIGIN_ATTRS_PATTERN_MATCH_OA_SUFFIX"_ns); +} + +// static +nsresult BounceTrackingProtectionStorage::ClearData( + mozIStorageConnection* aDatabaseConnection) { + MOZ_ASSERT(!NS_IsMainThread(), + "Must not write to the table from the main thread."); + NS_ENSURE_ARG_POINTER(aDatabaseConnection); + return aDatabaseConnection->ExecuteSimpleSQL("DELETE FROM sites;"_ns); +} + +NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchOASuffixSQLFunction, + mozIStorageFunction) + +NS_IMETHODIMP +OriginAttrsPatternMatchOASuffixSQLFunction::OnFunctionCall( + mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { + nsresult rv; + + nsAutoCString originAttributeSuffix; + rv = aFunctionArguments->GetUTF8String(0, originAttributeSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + OriginAttributes originAttributes; + bool parsedSuccessfully = + originAttributes.PopulateFromSuffix(originAttributeSuffix); + NS_ENSURE_TRUE(parsedSuccessfully, NS_ERROR_FAILURE); + + bool result = mPattern.Matches(originAttributes); + + RefPtr<nsVariant> outVar(new nsVariant()); + rv = outVar->SetAsBool(result); + NS_ENSURE_SUCCESS(rv, rv); + + outVar.forget(aResult); + return NS_OK; +} + +} // namespace mozilla |