tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit ef4c80cb062951db8bc4b7608eeb6ac323288771
parent f014fa8be1bc8b6762b33bd9b18bb4c779666c59
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Tue,  7 Oct 2025 17:55:58 +0000

Bug 1978495: Compression Dictionaries clear-site and clear cookies support r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D258167

Diffstat:
Mnetwerk/cache2/CacheFile.cpp | 1+
Mnetwerk/cache2/CacheFileContextEvictor.cpp | 14++++++++------
Mnetwerk/cache2/CacheFileIOManager.cpp | 28+++++++++++++++++-----------
Mnetwerk/cache2/CacheIndex.cpp | 10+++++++---
Mnetwerk/cache2/CacheIndex.h | 3++-
Mnetwerk/cache2/CacheStorageService.cpp | 23++++++++++++++++++++++-
Mnetwerk/cache2/Dictionary.cpp | 159++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mnetwerk/cache2/Dictionary.h | 14+++++++++++---
Mnetwerk/cache2/nsICacheStorageService.idl | 14++++++++++++++
Mtoolkit/components/cleardata/ClearDataService.sys.mjs | 15+++++++++++++++
Mtoolkit/components/clearsitedata/ClearSiteData.cpp | 9+++++++++
11 files changed, 247 insertions(+), 43 deletions(-)

diff --git a/netwerk/cache2/CacheFile.cpp b/netwerk/cache2/CacheFile.cpp @@ -493,6 +493,7 @@ nsresult CacheFile::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) { if (mDict && OutputStreamExists(false)) { mOutput->SetDictionary(mDict); + // leave mDict set for hash accumulation } autoDoom.mListener.swap(mDoomAfterOpenListener); diff --git a/netwerk/cache2/CacheFileContextEvictor.cpp b/netwerk/cache2/CacheFileContextEvictor.cpp @@ -684,20 +684,22 @@ void CacheFileContextEvictor::EvictEntries() { continue; } - // Check whether we must filter by either base domain or by origin. - if (!mEntries[0]->mBaseDomain.IsEmpty() || - !mEntries[0]->mOrigin.IsEmpty()) { + // Read metadata from the file synchronously + RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata(); + { // Get and read metadata for the entry nsCOMPtr<nsIFile> file; CacheFileIOManager::gInstance->GetFile(&hash, getter_AddRefs(file)); - // Read metadata from the file synchronously - RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata(); rv = metadata->SyncReadMetadata(file); if (NS_WARN_IF(NS_FAILED(rv))) { continue; } + } + // Check whether we must filter by either base domain or by origin. + if (!mEntries[0]->mBaseDomain.IsEmpty() || + !mEntries[0]->mOrigin.IsEmpty()) { // Now get the context + enhance id + URL from the key. nsAutoCString uriSpec; RefPtr<nsILoadContextInfo> info = @@ -820,7 +822,7 @@ void CacheFileContextEvictor::EvictEntries() { LOG(("CacheFileContextEvictor::EvictEntries - Removing entry.")); file->Remove(false); - CacheIndex::RemoveEntry(&hash); + CacheIndex::RemoveEntry(&hash, metadata->GetKey()); } MOZ_ASSERT_UNREACHABLE("We should never get here"); diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp @@ -1360,7 +1360,7 @@ void CacheFileIOManager::ShutdownInternal() { // (hashes won't match). if (!h->IsSpecialFile() && !h->mIsDoomed && !h->mFileExists) { - CacheIndex::RemoveEntry(h->Hash()); + CacheIndex::RemoveEntry(h->Hash(), h->Key()); } // Remove the handle from mHandles/mSpecialHandles @@ -1820,7 +1820,7 @@ nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash, NS_ENSURE_SUCCESS(rv, rv); if (exists) { - CacheIndex::RemoveEntry(aHash); + CacheIndex::RemoveEntry(aHash, handle->Key()); LOG( ("CacheFileIOManager::OpenFileInternal() - Removing old file from " @@ -2033,7 +2033,7 @@ void CacheFileIOManager::CloseHandleInternal(CacheFileHandle* aHandle) { if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed && (aHandle->mInvalid || !aHandle->mFileExists)) { - CacheIndex::RemoveEntry(aHandle->Hash()); + CacheIndex::RemoveEntry(aHandle->Hash(), aHandle->Key()); } // Don't remove handles after shutdown @@ -2512,12 +2512,9 @@ nsresult CacheFileIOManager::DoomFileInternal( } if (!aHandle->IsSpecialFile()) { - // Remove from Dictionary cache if this is a dictionary - if (!mDictionaryCache) { - // mDictionaryCache = DictionaryCache::GetInstance(); - } - // mDictionaryCache->RemoveDictionaryFor(aHandle->mKey); - CacheIndex::RemoveEntry(aHandle->Hash()); + // Ensure the string doesn't disappear with the handle + RefPtr<CacheFileHandle> handle(aHandle); + CacheIndex::RemoveEntry(aHandle->Hash(), aHandle->Key()); } aHandle->mIsDoomed = true; @@ -2612,7 +2609,15 @@ nsresult CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash* aHash) { static_cast<uint32_t>(rv))); } - CacheIndex::RemoveEntry(aHash); + // Find the key for the hash + // Read metadata from the file synchronously + RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata(); + rv = metadata->SyncReadMetadata(file); + if (NS_WARN_IF(NS_FAILED(rv))) { + CacheIndex::RemoveEntry(aHash, ""_ns); + } else { + CacheIndex::RemoveEntry(aHash, metadata->GetKey()); + } return NS_OK; } @@ -3239,7 +3244,8 @@ nsresult CacheFileIOManager::OverLimitEvictionInternal() { // TODO index is outdated, start update // Make sure index won't return the same entry again - CacheIndex::RemoveEntry(&hash); + // XXX find the key for the hash + CacheIndex::RemoveEntry(&hash, ""_ns); consecutiveFailures = 0; } else { // This shouldn't normally happen, but the eviction must not fail diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp @@ -840,12 +840,16 @@ nsresult CacheIndex::InitEntry(const SHA1Sum::Hash* aHash, } // static -nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash) { - LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x]", - LOGSHA1(aHash))); +nsresult CacheIndex::RemoveEntry(const SHA1Sum::Hash* aHash, + const nsACString& aKey) { + LOG(("CacheIndex::RemoveEntry() [hash=%08x%08x%08x%08x%08x] key=%s", + LOGSHA1(aHash), PromiseFlatCString(aKey).get())); MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); + // Remove the dictionary even if we later error out - async + DictionaryCache::RemoveDictionaryFor(aKey); + StaticMutexAutoLock lock(sLock); RefPtr<CacheIndex> index = gInstance; diff --git a/netwerk/cache2/CacheIndex.h b/netwerk/cache2/CacheIndex.h @@ -747,7 +747,8 @@ class CacheIndex final : public CacheFileIOListener, public nsIRunnable { bool aPinned); // Remove entry from index. The entry should be present in index. - static nsresult RemoveEntry(const SHA1Sum::Hash* aHash); + static nsresult RemoveEntry(const SHA1Sum::Hash* aHash, + const nsACString& aKey); // Update some information in entry. The entry MUST be present in index and // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp @@ -697,6 +697,8 @@ NS_IMETHODIMP CacheStorageService::ClearOriginsByPrincipal( nsAutoString origin; rv = nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, origin); NS_ENSURE_SUCCESS(rv, rv); + LOG(("CacheStorageService::ClearOriginsByPrincipal %s", + NS_ConvertUTF16toUTF8(origin).get())); rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true); NS_ENSURE_SUCCESS(rv, rv); @@ -710,6 +712,8 @@ NS_IMETHODIMP CacheStorageService::ClearOriginsByPrincipal( NS_IMETHODIMP CacheStorageService::ClearOriginsByOriginAttributes( const nsAString& aOriginAttributes) { nsresult rv; + LOG(("CacheStorageService::ClearOriginsByOriginAttributes %s", + NS_ConvertUTF16toUTF8(aOriginAttributes).get())); if (NS_WARN_IF(aOriginAttributes.IsEmpty())) { return NS_ERROR_FAILURE; @@ -747,6 +751,8 @@ static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey, NS_IMETHODIMP CacheStorageService::ClearBaseDomain( const nsAString& aBaseDomain) { + LOG(("CacheStorageService::ClearBaseDomain %s", + NS_ConvertUTF16toUTF8(aBaseDomain).get())); mozilla::MutexAutoLock lock(sLock); if (sGlobalEntryTables) { if (mShutdown) return NS_ERROR_NOT_AVAILABLE; @@ -816,6 +822,7 @@ NS_IMETHODIMP CacheStorageService::ClearBaseDomain( // Clear matched keys. for (uint32_t i = 0; i < keys.Length(); ++i) { + LOG(("CacheStorageService::ClearBaseDomain Dooming %s", keys[i].get())); DoomStorageEntries(keys[i], nullptr, true, false, nullptr); } } @@ -889,6 +896,19 @@ nsresult CacheStorageService::ClearOriginInternal( return NS_OK; } +NS_IMETHODIMP CacheStorageService::ClearOriginDictionary(nsIURI* aURI) { + LOG(("CacheStorageService::ClearOriginDictionary")); + // Note: due to cookie samesite rules, we need to clean for all ports + DictionaryCache::RemoveDictionaries(aURI); + return NS_OK; +} + +NS_IMETHODIMP CacheStorageService::ClearAllOriginDictionaries() { + LOG(("CacheStorageService::ClearAllOriginDictionaries")); + DictionaryCache::RemoveAllDictionaries(); + return NS_OK; +} + NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) { uint32_t what; @@ -1834,7 +1854,8 @@ NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener, nsresult CacheStorageService::DoomStorageEntry( CacheStorage const* aStorage, const nsACString& aURI, const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) { - LOG(("CacheStorageService::DoomStorageEntry")); + LOG(("CacheStorageService::DoomStorageEntry %s", + PromiseFlatCString(aURI).get())); NS_ENSURE_ARG(aStorage); diff --git a/netwerk/cache2/Dictionary.cpp b/netwerk/cache2/Dictionary.cpp @@ -7,6 +7,8 @@ #include "Dictionary.h" +#include "CacheFileUtils.h" +#include "nsString.h" #include "nsAppDirectoryServiceDefs.h" #include "nsIAsyncInputStream.h" #include "nsICacheStorageService.h" @@ -40,7 +42,6 @@ #include "mozilla/StaticPrefs_network.h" #include "mozilla/glean/NetwerkMetrics.h" -#include "mozilla/net/MozURL.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/net/NeckoChild.h" @@ -96,6 +97,7 @@ bool DictionaryCacheEntry::Match(const nsACString& aFilePath, } if (mNotCached) { // Not actually in the cache + // May not actually be necessary, but good safety valve. return false; } // Not worth checking if we wouldn't use it @@ -159,6 +161,10 @@ nsresult DictionaryCacheEntry::Prefetch(nsILoadContextInfo* aLoadContextInfo, // Start reading the cache entry into memory and call completion // function when done if (mWaitingPrefetch.IsEmpty()) { + // Note that if the cache entry has been cleared, and we still have active + // users of it, we'll hold onto that data since we have outstanding requests + // for it. Probably we shouldn't allow new requests to use this data (and + // the WPTs assume we shouldn't). if (!mDictionaryDataComplete) { // We haven't requested it yet from the Cache and don't have it in memory // already. @@ -189,7 +195,11 @@ nsresult DictionaryCacheEntry::Prefetch(nsILoadContextInfo* aLoadContextInfo, // For some reason the cache no longer has this entry; fail Prefetch // and also remove this from our origin aShouldSuspend = false; - // XXX Remove from origin + // Remove from origin + if (mOrigin) { + mOrigin->RemoveEntry(this); + mOrigin = nullptr; + } return NS_ERROR_FAILURE; } mWaitingPrefetch.AppendElement(aFunc); @@ -256,7 +266,6 @@ void DictionaryCacheEntry::FinishHash() { if (!mBlocked) { mOrigin->FinishAddEntry(this); } - mOrigin = nullptr; } } } @@ -325,6 +334,7 @@ nsresult DictionaryCacheEntry::Write(nsICacheEntry* aCacheEntry) { } nsresult DictionaryCacheEntry::RemoveEntry(nsICacheEntry* aCacheEntry) { + DICTIONARY_LOG(("RemoveEntry from metadata for %s", mURI.get())); return aCacheEntry->SetMetaDataElement(mURI.BeginReading(), nullptr); } @@ -450,7 +460,7 @@ void DictionaryCacheEntry::UnblockAddEntry(DictionaryOrigin* aOrigin) { mBlocked = false; } -void DictionaryCacheEntry::WriteOnHash(DictionaryOrigin* aOrigin) { +void DictionaryCacheEntry::WriteOnHash() { bool hasHash = false; { MOZ_ASSERT(NS_IsMainThread()); @@ -458,12 +468,9 @@ void DictionaryCacheEntry::WriteOnHash(DictionaryOrigin* aOrigin) { hasHash = true; } } - if (hasHash) { + if (hasHash && mOrigin) { DICTIONARY_LOG(("Write already hashed")); - aOrigin->Write(this); - } else if (!mStopReceived) { - // This creates a cycle, but we'll clear it when we get FinishFile - mOrigin = aOrigin; + mOrigin->Write(this); } } @@ -656,6 +663,7 @@ DictionaryOriginReader::OnStopRequest(nsIRequest* request, nsresult result) { // static already_AddRefed<DictionaryCache> DictionaryCache::GetInstance() { + // XXX lock? In practice probably not needed, in theory yes if (!gDictionaryCache) { gDictionaryCache = new DictionaryCache(); MOZ_ASSERT(NS_SUCCEEDED(gDictionaryCache->Init())); @@ -720,16 +728,19 @@ already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry( // that the entry we're adding will need to be saved later once // we have the entry + // This creates a cycle until the dictionary is removed from the cache + aDictEntry->SetOrigin(origin); + // Open (and parse metadata) or create RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader(); reader->Start( origin, prepath, aURI, this, - [origin, aDictEntry]( + [entry = RefPtr(aDictEntry)]( DictionaryCacheEntry* aDict) { // XXX avoid so many lambdas which cause allocations // Write the dirty entry we couldn't write before once // we get the hash - aDictEntry->WriteOnHash(origin); + aDictEntry->WriteOnHash(); return NS_OK; }); return origin; @@ -746,6 +757,8 @@ nsresult DictionaryCache::RemoveEntry(nsIURI* aURI, const nsACString& aKey) { if (NS_FAILED(aURI->GetPrePath(prepath))) { return NS_ERROR_FAILURE; } + DICTIONARY_LOG(("Dictionary RemoveEntry for %s : %s", prepath.get(), + PromiseFlatCString(aKey).get())); if (auto origin = mDictionaryCache.Lookup(prepath)) { return origin.Data()->RemoveEntry(aKey); } @@ -760,16 +773,91 @@ void DictionaryCache::Clear() { } // Remove a dictionary if it exists for the key given +// static void DictionaryCache::RemoveDictionaryFor(const nsACString& aKey) { - RefPtr<MozURL> url; - nsresult rv = MozURL::Init(getter_AddRefs(url), aKey); + RefPtr<DictionaryCache> cache = GetInstance(); + NS_DispatchToMainThread(NewRunnableMethod<const nsCString>( + "DictionaryCache::RemoveDictionaryFor", cache, + &DictionaryCache::RemoveDictionary, aKey)); +} + +// Remove a dictionary if it exists for the key given +void DictionaryCache::RemoveDictionary(const nsACString& aKey) { + DICTIONARY_LOG( + ("Removing dictionary for %s", PromiseFlatCString(aKey).get())); + nsCString enhance; + nsCString urlstring; + nsCOMPtr<nsILoadContextInfo> info = + CacheFileUtils::ParseKey(aKey, &enhance, &urlstring); + MOZ_ASSERT(info); + if (!info) { + DICTIONARY_LOG(("DictionaryCache::RemoveDictionary() - Cannot parse key!")); + return; + } + nsCOMPtr<nsIURI> url; + nsresult rv = NS_NewURI(getter_AddRefs(url), urlstring); if (NS_SUCCEEDED(rv)) { - nsDependentCSubstring prepath = url->PrePath(); + nsCString prepath; + if (NS_SUCCEEDED(url->GetPrePath(prepath))) { + if (auto origin = mDictionaryCache.Lookup(prepath)) { + origin.Data()->RemoveEntry(urlstring); + } + } + } +} - if (auto origin = mDictionaryCache.Lookup(prepath)) { - origin.Data()->RemoveEntry(aKey); +// Remove a dictionary if it exists for the key given. Mainthread only. +// Note: due to cookie samesite rules, we need to clean for all ports +// static +void DictionaryCache::RemoveDictionaries(nsIURI* aURI) { + RefPtr<DictionaryCache> cache = GetInstance(); + nsDependentCSubstring spec; // aka https://site/ with no port + if (NS_FAILED(aURI->GetSpec(spec))) { + return; + } + + DICTIONARY_LOG(("Removing all dictionaries for %s (%zu)", + PromiseFlatCString(spec).get(), spec.Length())); + RefPtr<DictionaryOrigin> origin; + // We can't just use Remove here; the ClearSiteData service strips the port. + // We need to clear all that match the host with any port or none + cache->mDictionaryCache.RemoveIf([&spec](auto& entry) { + // We need to drop any port. Assuming they're the same up to the / or : in + // mOrigin, we want to limit the host there. Verify that: + // a) they're equal to that point + // b) that the next character of mOrigin is '/' or ':', which avoids + // issues like matching https://foo.bar/ to (mOrigin) + // https://foo.barsoom.com:666/ + if (entry.Data()->mOrigin.Length() > spec.Length() && + (entry.Data()->mOrigin[spec.Length() - 1] == '/' || // no port + entry.Data()->mOrigin[spec.Length() - 1] == ':')) { // port + // no strncmp() for nsCStrings... + nsDependentCSubstring host = + Substring(entry.Data()->mOrigin, 0, + spec.Length() - 1); // not including '/' or ':' + nsDependentCSubstring temp = + Substring(spec, 0, spec.Length() - 1); // Drop '/' + if (temp.Equals(host)) { + DICTIONARY_LOG( + ("Removing dictionary for %s", entry.Data()->mOrigin.get())); + entry.Data()->Clear(); + return true; + } } + return false; + }); +} + +// Remove a dictionary if it exists for the key given. Mainthread only +// static +void DictionaryCache::RemoveAllDictionaries() { + RefPtr<DictionaryCache> cache = GetInstance(); + + DICTIONARY_LOG(("Removing all dictionaries")); + for (auto& origin : cache->mDictionaryCache) { + origin.GetData()->Clear(); } + cache->mDictionaryCache.Clear(); } // Return an entry via a callback (async). @@ -929,10 +1017,33 @@ already_AddRefed<DictionaryCacheEntry> DictionaryOrigin::AddEntry( } nsresult DictionaryOrigin::RemoveEntry(const nsACString& aKey) { + DICTIONARY_LOG( + ("DictionaryOrigin::RemoveEntry for %s", PromiseFlatCString(aKey).get())); for (const auto& dict : mEntries) { if (dict->GetURI().Equals(aKey)) { + // Ensure it doesn't disappear on us + RefPtr<DictionaryCacheEntry> hold(dict); mEntries.RemoveElement(dict); - dict->RemoveEntry(mEntry); + hold->RemoveEntry(mEntry); + if (MOZ_UNLIKELY( + MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) { + DumpEntries(); + } + return NS_OK; + } + } + DICTIONARY_LOG(("DictionaryOrigin::RemoveEntry (pending) for %s", + PromiseFlatCString(aKey).get())); + for (const auto& dict : mPendingEntries) { + if (dict->GetURI().Equals(aKey)) { + // Ensure it doesn't disappear on us + RefPtr<DictionaryCacheEntry> hold(dict); + mPendingEntries.RemoveElement(dict); + hold->RemoveEntry(mEntry); + if (MOZ_UNLIKELY( + MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) { + DumpEntries(); + } return NS_OK; } } @@ -949,7 +1060,19 @@ void DictionaryOrigin::FinishAddEntry(DictionaryCacheEntry* aEntry) { } void DictionaryOrigin::RemoveEntry(DictionaryCacheEntry* aEntry) { - mEntries.RemoveElement(aEntry); + DICTIONARY_LOG(("RemoveEntry(%s)", aEntry->mURI.get())); + if (!mEntries.RemoveElement(aEntry)) { + mPendingEntries.RemoveElement(aEntry); + } + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) { + DumpEntries(); + } +} + +void DictionaryOrigin::Clear() { + mEntries.Clear(); + mPendingEntries.Clear(); + mEntry->AsyncDoom(nullptr); } // caller will throw this into a RefPtr diff --git a/netwerk/cache2/Dictionary.h b/netwerk/cache2/Dictionary.h @@ -83,7 +83,9 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback, mHash = aHash; } - void WriteOnHash(DictionaryOrigin* aOrigin); + void WriteOnHash(); + + void SetOrigin(DictionaryOrigin* aOrigin) { mOrigin = aOrigin; } const nsCString& GetId() const { return mId; } @@ -241,6 +243,7 @@ class DictionaryOrigin : public nsICacheEntryMetaDataVisitor { void RemoveEntry(DictionaryCacheEntry* aEntry); DictionaryCacheEntry* Match(const nsACString& path); void FinishAddEntry(DictionaryCacheEntry* aEntry); + void Clear(); private: virtual ~DictionaryOrigin() {} @@ -269,7 +272,7 @@ class DictionaryCache final { friend class DictionaryCacheEntry; public: - NS_INLINE_DECL_REFCOUNTING(DictionaryCache) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DictionaryCache) static already_AddRefed<DictionaryCache> GetInstance(); @@ -283,11 +286,16 @@ class DictionaryCache final { already_AddRefed<DictionaryCacheEntry> AddEntry( nsIURI* aURI, bool aNewEntry, DictionaryCacheEntry* aDictEntry); + static void RemoveDictionaryFor(const nsACString& aKey); + // Remove a dictionary if it exists for the key given - void RemoveDictionaryFor(const nsACString& aKey); + void RemoveDictionary(const nsACString& aKey); nsresult RemoveEntry(nsIURI* aURI, const nsACString& aKey); + static void RemoveDictionaries(nsIURI* aURI); + static void RemoveAllDictionaries(); + // Clears all ports at host void Clear(); diff --git a/netwerk/cache2/nsICacheStorageService.idl b/netwerk/cache2/nsICacheStorageService.idl @@ -10,6 +10,7 @@ interface nsIEventTarget; interface nsICacheStorageConsumptionObserver; interface nsICacheStorageVisitor; interface nsIPrincipal; +interface nsIURI; /** * Provides access to particual cache storages of the network URI cache. @@ -76,6 +77,19 @@ interface nsICacheStorageService : nsISupports void clear(); /** + * Evict any Dictionary cache entry by site + * + * @param aURI + * The URI to compare the dictionary entries with. + */ + void clearOriginDictionary(in nsIURI aURI); + + /** + * Evict all Dictionary cache entries + */ + void clearAllOriginDictionaries(); + + /** * Purge only data of disk backed entries. Metadata are left for * performance purposes. */ diff --git a/toolkit/components/cleardata/ClearDataService.sys.mjs b/toolkit/components/cleardata/ClearDataService.sys.mjs @@ -192,6 +192,9 @@ function hasSite( // Sanitizer.sanitizeOnShutdown() and // Sanitizer.onStartup() +// IETF spec for compression dictionaries requires clearing them when cookies +// are cleared for the site - Section 10 of +// https://datatracker.ietf.org/doc/draft-ietf-httpbis-compression-dictionary/ const CookieCleaner = { deleteByLocalFiles(aOriginAttributes) { return new Promise(aResolve => { @@ -205,6 +208,9 @@ const CookieCleaner = { deleteByHost(aHost, aOriginAttributes) { return new Promise(aResolve => { + // Compression dictionaries are https only + let httpsURI = Services.io.newURI("https://" + aHost); + Services.cache2.clearOriginDictionary(httpsURI); Services.cookies.removeCookiesFromExactHost( aHost, JSON.stringify(aOriginAttributes) @@ -217,6 +223,9 @@ const CookieCleaner = { // Fall back to clearing by host and OA pattern. This will over-clear, since // any properties that are not explicitly set in aPrincipal.originAttributes // will be wildcard matched. + // Note that we clear cookies for all ports, because apparently + // cookies historically have ignored ports based on sameSite rules: + // https://html.spec.whatwg.org/#same-site return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes); }, @@ -235,6 +244,9 @@ const CookieCleaner = { JSON.stringify(cookie.originAttributes) ); }); + // Compression dictionaries are https only + let httpsURI = Services.io.newURI("https://" + aSchemelessSite); + Services.cache2.clearOriginDictionary(httpsURI); }, deleteByRange(aFrom) { @@ -248,6 +260,8 @@ const CookieCleaner = { aOriginAttributesString ); } catch (ex) {} + // XXX Bug 1984198 we need to clear dictionaries here (probably has + // to be in CookieService::RemoveCookiesWithOriginAttributes() aResolve(); }); }, @@ -255,6 +269,7 @@ const CookieCleaner = { deleteAll() { return new Promise(aResolve => { Services.cookies.removeAll(); + Services.cache2.clearAllOriginDictionaries(); aResolve(); }); }, diff --git a/toolkit/components/clearsitedata/ClearSiteData.cpp b/toolkit/components/clearsitedata/ClearSiteData.cpp @@ -23,9 +23,14 @@ #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsNetUtil.h" +#include "mozilla/Logging.h" using namespace mozilla; +LazyLogModule gClearSiteDataLog("ClearSiteData"); + +#define LOG(args) MOZ_LOG(gClearSiteDataLog, mozilla::LogLevel::Debug, args) + namespace { StaticRefPtr<ClearSiteData> gClearSiteData; @@ -204,6 +209,8 @@ void ClearSiteData::ClearDataFromChannel(nsIHttpChannel* aChannel) { // in a different principal. int32_t cleanNetworkFlags = 0; + LOG(("ClearSiteData: %s, %x", uri->GetSpecOrDefault().get(), flags)); + if (StaticPrefs::privacy_clearSiteDataHeader_cache_enabled() && (flags & eCache)) { LogOpToConsole(aChannel, uri, eCache); @@ -224,6 +231,8 @@ void ClearSiteData::ClearDataFromChannel(nsIHttpChannel* aChannel) { nsIClearDataService::CLEAR_FINGERPRINTING_PROTECTION_STATE; } + LOG(("ClearSiteData: cleanFlags=%x, cleanNetworkFlags=%x", cleanFlags, + cleanNetworkFlags)); // for each `DeleteDataFromPrincipal` we need to wait for one callback. // cleanFlags elicits once callback. uint32_t numClearCalls = (cleanFlags != 0) + (cleanNetworkFlags != 0);