commit ccced9f2874d009a0140ca76f2480b8459d3c0dd
parent aabfdecb3a623752e5a973d751d690ddd961a8bc
Author: Randell Jesup <rjesup@mozilla.com>
Date: Wed, 1 Oct 2025 18:45:59 +0000
Bug 1978495: Compression Dictionaries clear-site and clear cookies support r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D258167
Diffstat:
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);