tor-browser

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

commit a2c00deb4d2d113ecf9d1e6c25de7dca65c8da2a
parent bf874cae0dc56c292e7e3a5658eeb33bdce5be5e
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Tue,  7 Oct 2025 14:07:07 +0000

Bug 1978494: Don't use stale dictionaries, more logs, improve handling of dictionary origin reading r=necko-reviewers,valentin

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

Diffstat:
Mnetwerk/cache2/Dictionary.cpp | 302++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mnetwerk/cache2/Dictionary.h | 56+++++++++++++++++++++++++++++++++++++++++++++-----------
Mnetwerk/protocol/http/nsHttpChannel.cpp | 32+++++++++++++++++++++-----------
Mnetwerk/protocol/http/nsHttpHandler.cpp | 20+++++++++++---------
Mnetwerk/protocol/http/nsHttpHandler.h | 4++--
Mnetwerk/streamconv/converters/nsHTTPCompressConv.cpp | 5+++--
6 files changed, 266 insertions(+), 153 deletions(-)

diff --git a/netwerk/cache2/Dictionary.cpp b/netwerk/cache2/Dictionary.cpp @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <algorithm> +#include <stdlib.h> #include "Dictionary.h" @@ -89,15 +90,24 @@ static nsresult GetDictPath(nsIURI* aURI, nsACString& aPrePath) { return NS_OK; } -DictionaryCacheEntry::DictionaryCacheEntry(const char* aKey) { mURI = aKey; } +DictionaryCacheEntry::DictionaryCacheEntry(const char* aKey) { + mURI = aKey; + DICTIONARY_LOG(("Created DictionaryCacheEntry %p, uri=%s", this, aKey)); +} DictionaryCacheEntry::DictionaryCacheEntry(const nsACString& aURI, const nsACString& aPattern, nsTArray<nsCString>& aMatchDest, const nsACString& aId, + uint32_t aExpiration, const Maybe<nsCString>& aHash) - : mURI(aURI), mPattern(aPattern), mId(aId) { + : mURI(aURI), mExpiration(aExpiration), mPattern(aPattern), mId(aId) { ConvertMatchDestToEnumArray(aMatchDest, mMatchDest); + DICTIONARY_LOG( + ("Created DictionaryCacheEntry %p, uri=%s, pattern=%s, id=%s, " + "expiration=%u", + this, PromiseFlatCString(aURI).get(), PromiseFlatCString(aPattern).get(), + PromiseFlatCString(aId).get(), aExpiration)); if (aHash) { mHash = aHash.value(); } @@ -123,9 +133,11 @@ void DictionaryCacheEntry::ConvertMatchDestToEnumArray( aMatchEnums.SwapElements(temp); } -// returns true if the pattern for the dictionary matches the path given +// Returns true if the pattern for the dictionary matches the path given. +// Note: we need to verify that this entry has not expired due to 2.2.1 of +// https://datatracker.ietf.org/doc/draft-ietf-httpbis-compression-dictionary/ bool DictionaryCacheEntry::Match(const nsACString& aFilePath, - ExtContentPolicyType aType, + ExtContentPolicyType aType, uint32_t aNow, uint32_t& aLongest) { if (mHash.IsEmpty()) { // We don't have the file yet @@ -137,9 +149,11 @@ bool DictionaryCacheEntry::Match(const nsACString& aFilePath, return false; } // Not worth checking if we wouldn't use it - DICTIONARY_LOG(("Match: %s to %s, %s", PromiseFlatCString(aFilePath).get(), - mPattern.get(), NS_CP_ContentTypeName(aType))); - if (mPattern.Length() > aLongest) { + DICTIONARY_LOG(("Match: %p %s to %s, %s (now=%u, expiration=%u)", this, + PromiseFlatCString(aFilePath).get(), mPattern.get(), + NS_CP_ContentTypeName(aType), aNow, mExpiration)); + if ((mExpiration == 0 || aNow < mExpiration) && + mPattern.Length() > aLongest) { // Need to match using match-dest, if it exists if (mMatchDest.IsEmpty() || mMatchDest.IndexOf( @@ -170,7 +184,12 @@ bool DictionaryCacheEntry::Match(const nsACString& aFilePath, DICTIONARY_LOG(("Match: %s (longest %u)", mURI.get(), aLongest)); return true; } + } else { + DICTIONARY_LOG((" Failed on matchDest")); } + } else { + DICTIONARY_LOG( + (" Failed due to expiration: %u vs %u", aNow, mExpiration)); } return false; } @@ -304,7 +323,10 @@ void DictionaryCacheEntry::FinishHash() { if (mOrigin) { DICTIONARY_LOG(("Write on hash")); // This will also move us from mPendingEntries to mEntries - mOrigin->Write(this); + if (NS_FAILED(mOrigin->Write(this))) { + mOrigin->RemoveEntry(this); + return; + } if (!mBlocked) { mOrigin->FinishAddEntry(this); } @@ -326,7 +348,8 @@ static const uint32_t METADATA_VERSION = 1; // ** match-dest CString list, terminated by empty string // *** CString: Match-dest // ** CString: Id -// ** CString: type +// ** uint32 as a CString: expiration. If missing, 0 (none) +// ** CString: type -- defaults to 'raw' if missing // We store strings with a delimiter, and use escapes for delimiters or escape // characters in the source strings. // @@ -367,6 +390,10 @@ void DictionaryCacheEntry::MakeMetadataEntry(nsCString& aNewValue) { } // List of match-dest values is terminated by an empty string EscapeMetadataString(""_ns, aNewValue); + // Expiration time, as a CString + nsAutoCStringN<12> expiration; + expiration = nsPrintfCString("%u", mExpiration); + EscapeMetadataString(expiration, aNewValue); // We don't store type, since we only support type 'raw' We can support // type in the future by considering missing type as raw without changing the // format @@ -432,7 +459,12 @@ bool DictionaryCacheEntry::ParseMetadata(const char* aSrc) { } } } while (!temp.IsEmpty()); - // XXX type + if (*aSrc == '|') { + char* newSrc; + mExpiration = strtoul(++aSrc, &newSrc, 10); + aSrc = newSrc; + } // else leave default of 0 + // XXX type - we assume and only support 'raw', may be missing aSrc = GetEncodedString(aSrc, temp); DICTIONARY_LOG( @@ -590,31 +622,26 @@ DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew, //---------------------------------------------------------------------------------- -class DictionaryOriginReader final : public nsICacheEntryOpenCallback, - public nsIStreamListener { - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSICACHEENTRYOPENCALLBACK - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSISTREAMLISTENER - - DictionaryOriginReader() {} - - // If aOrigin is nullptr, we're just reading; if it's set then we're adding - // a Dictionary to a maybe-existing Origin and we need to create it if it - // doesn't exist yet. - void Start(DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI, - ExtContentPolicyType aType, DictionaryCache* aCache, - const std::function<nsresult(DictionaryCacheEntry*)>& aCallback) { - mOrigin = aOrigin; - mURI = aURI; - mType = aType; - mCallback = aCallback; - mCache = aCache; - mSelf = this; // keep this alive until we call aCallback - - // The cache entry is for originattribute extension of - // META_DICTIONARY_PREFIX, plus key of prepath - +// Read the metadata for an Origin and parse it, creating DictionaryCacheEntrys +// as needed. If aType is TYPE_OTHER, there is no Match() to do +void DictionaryOriginReader::Start( + DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI, + ExtContentPolicyType aType, DictionaryCache* aCache, + const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) { + mOrigin = aOrigin; + mURI = aURI; + mType = aType; + mCallback = aCallback; + mCache = aCache; + + // The cache entry is for originattribute extension of + // META_DICTIONARY_PREFIX, plus key of prepath + + // This also keeps this alive until we get the callback. We must do this + // BEFORE we call AsyncOpenURIString, or we may get a callback to + // OnCacheEntryAvailable before we can do this + mOrigin->mWaitingCacheRead.AppendElement(this); + if (mOrigin->mWaitingCacheRead.Length() == 1) { // was empty DICTIONARY_LOG(("DictionaryOriginReader::Start(%s): %p", PromiseFlatCString(aKey).get(), this)); DictionaryCache::sCacheStorage->AsyncOpenURIString( @@ -625,18 +652,23 @@ class DictionaryOriginReader final : public nsICacheEntryOpenCallback, : nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | nsICacheStorage::CHECK_MULTITHREADED, this); + // This one will get the direct callback to do Match() } + // Else we already have a read for this cache entry pending, just wait + // for that +} - private: - ~DictionaryOriginReader() {} - - RefPtr<DictionaryOrigin> mOrigin; - nsCOMPtr<nsIURI> mURI; - ExtContentPolicyType mType; - std::function<nsresult(DictionaryCacheEntry*)> mCallback; - RefPtr<DictionaryCache> mCache; - RefPtr<DictionaryOriginReader> mSelf; -}; +void DictionaryOriginReader::FinishMatch() { + RefPtr<DictionaryCacheEntry> result; + // Don't Match if this was a call from AddEntry() + if (mType != ExtContentPolicy::TYPE_OTHER) { + nsCString path; + mURI->GetPathQueryRef(path); + result = mOrigin->Match(path, mType); + } + DICTIONARY_LOG(("Done with reading origin for %p", mOrigin.get())); + (mCallback)(true, result); +} NS_IMPL_ISUPPORTS(DictionaryOriginReader, nsICacheEntryOpenCallback, nsIStreamListener) @@ -665,47 +697,24 @@ NS_IMETHODIMP DictionaryOriginReader::OnCacheEntryAvailable( if (!aCacheEntry) { // Didn't have any dictionaries for this origin, and must have been readonly - (mCallback)(nullptr); + for (auto& reader : mOrigin->mWaitingCacheRead) { + (reader->mCallback)(true, nullptr); + } + mOrigin->mWaitingCacheRead.Clear(); return NS_OK; } - // If we have an mOrigin, then it's already been created and inserted - if (mOrigin) { - mOrigin->SetCacheEntry(aCacheEntry); - // There's no data in the cache entry, just metadata - nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin); - aCacheEntry->VisitMetaData(metadata); - - // We only have mOrigin if we're creating it in AddEntry(), so no - // need to Match() - (mCallback)(nullptr); - mSelf = nullptr; // this will delete 'this' - return NS_OK; - } + mOrigin->SetCacheEntry(aCacheEntry); + // There's no data in the cache entry, just metadata + nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin); + aCacheEntry->VisitMetaData(metadata); - nsCString prepath; - if (NS_FAILED(GetDictPath(mURI, prepath))) { - (mCallback)(nullptr); - return NS_ERROR_FAILURE; + // This list is the only thing keeping us alive + RefPtr<DictionaryOriginReader> safety(this); + for (auto& reader : mOrigin->mWaitingCacheRead) { + reader->FinishMatch(); } - // Add the Origin to the hash in DictionaryCache - Unused << mCache->mDictionaryCache.WithEntryHandle( - prepath, [&](auto&& entry) { - auto& origin = entry.OrInsertWith( - [&] { return new DictionaryOrigin(prepath, aCacheEntry); }); - - // There's no data in the cache entry, just metadata - nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(origin); - aCacheEntry->VisitMetaData(metadata); - - nsCString path; - mURI->GetPathQueryRef(path); - RefPtr<DictionaryCacheEntry> result = origin->Match(path, mType); - - (mCallback)(result); - mSelf = nullptr; // this will delete 'this' - return NS_OK; - }); + mOrigin->mWaitingCacheRead.Clear(); return NS_OK; } @@ -766,13 +775,18 @@ nsresult DictionaryCache::AddEntry(nsIURI* aURI, const nsACString& aKey, nsTArray<nsCString>& aMatchDest, const nsACString& aId, const Maybe<nsCString>& aHash, - bool aNewEntry, + bool aNewEntry, uint32_t aExpiration, DictionaryCacheEntry** aDictEntry) { // Note that normally we're getting an entry in and until all the data // has been received, we can't use it. The Hash being null is a flag // that it's not yet valid. - RefPtr<DictionaryCacheEntry> dict = - new DictionaryCacheEntry(aKey, aPattern, aMatchDest, aId, aHash); + DICTIONARY_LOG(("AddEntry for %s, pattern %s, id %s, expiration %u", + PromiseFlatCString(aKey).get(), + PromiseFlatCString(aPattern).get(), + PromiseFlatCString(aId).get(), aExpiration)); + // Note that we don't know if there's an entry for this key in the origin + RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry( + aKey, aPattern, aMatchDest, aId, aExpiration, aHash); dict = AddEntry(aURI, aNewEntry, dict); if (dict) { *aDictEntry = do_AddRef(dict).take(); @@ -793,6 +807,8 @@ already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry( if (NS_FAILED(GetDictPath(aURI, prepath))) { return nullptr; } + DICTIONARY_LOG( + ("AddEntry: %s, %d, %p", prepath.get(), aNewEntry, aDictEntry)); // create for the origin if it doesn't exist RefPtr<DictionaryCacheEntry> newEntry; Unused << mDictionaryCache.WithEntryHandle(prepath, [&](auto&& entry) { @@ -811,17 +827,21 @@ already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry( reader->Start( origin, prepath, aURI, ExtContentPolicy::TYPE_OTHER, this, [entry = RefPtr(aDictEntry)]( - DictionaryCacheEntry* - aDict) { // XXX avoid so many lambdas which cause allocations + bool, 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(); return NS_OK; }); + // Since this is read asynchronously, we need to either add the entry + // async once the read is done and it's populated, or we have to handle + // collisions on the read return origin; }); newEntry = origin->AddEntry(aDictEntry, aNewEntry); + DICTIONARY_LOG(("AddEntry: added %s", prepath.get())); return NS_OK; }); return newEntry.forget(); @@ -942,35 +962,48 @@ void DictionaryCache::RemoveAllDictionaries() { // Once we have a DictionaryOrigin (in-memory or parsed), scan it for matches. // If it's not in the cache, return nullptr via callback. void DictionaryCache::GetDictionaryFor( - nsIURI* aURI, ExtContentPolicyType aType, - const std::function<nsresult(DictionaryCacheEntry*)>& aCallback) { + nsIURI* aURI, ExtContentPolicyType aType, bool& aAsync, + const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) { + aAsync = false; // Note: IETF 2.2.3 Multiple Matching Directories // We need to return match-dest matches first // If no match-dest, then the longest match nsCString prepath; if (NS_FAILED(GetDictPath(aURI, prepath))) { - (aCallback)(nullptr); + (aCallback)(false, nullptr); return; } - if (auto origin = mDictionaryCache.Lookup(prepath)) { - // Find the longest match - nsCString path; - RefPtr<DictionaryCacheEntry> result; - - aURI->GetPathQueryRef(path); - DICTIONARY_LOG(("GetDictionaryFor(%s %s)", prepath.get(), path.get())); - - result = origin.Data()->Match(path, aType); - (aCallback)(result); + // Match immediately if we've already created the origin and read any + // metadata + if (auto existing = mDictionaryCache.Lookup(prepath)) { + if (existing.Data()->mWaitingCacheRead.IsEmpty()) { + // Find the longest match + nsCString path; + RefPtr<DictionaryCacheEntry> result; + + aURI->GetPathQueryRef(path); + DICTIONARY_LOG(("GetDictionaryFor(%s %s)", prepath.get(), path.get())); + + result = existing.Data()->Match(path, aType); + (aCallback)(false, result); + } else { + DICTIONARY_LOG( + ("GetDictionaryFor(%s): Waiting for metadata read to match", + prepath.get())); + // Wait for the metadata read to complete + RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader(); + reader->Start(existing.Data(), prepath, aURI, aType, this, aCallback); + aAsync = true; + } return; } - // We don't have an entry at all. We need to check if there's an - // entry on disk for <origin>, unless we know we have all entries - // in the memory cache. + // We don't have an entry at all. We need to check if there's an entry + // on disk for <origin>, unless we know we have all entries in the memory + // cache. // Handle unknown origins by checking the disk cache if (!sCacheStorage) { - (aCallback)(nullptr); // in case we have no disk storage + (aCallback)(false, nullptr); // in case we have no disk storage return; } @@ -985,41 +1018,56 @@ void DictionaryCache::GetDictionaryFor( sCacheStorage->Exists(prepathURI, META_DICTIONARY_PREFIX, &exists)) && exists) { // To keep track of the callback, we need a new object to get the - // OnCacheEntryAvailable can resolve the callback. It will keep a reference - // to itself until it call aCallback after the cache read has resolved + // OnCacheEntryAvailable can resolve the callback. DICTIONARY_LOG(("Reading %s for dictionary entries", prepath.get())); + RefPtr<DictionaryOrigin> origin = new DictionaryOrigin(prepath, nullptr); + // Add the origin to the list; we'll immediately start a reader which + // will set mWaitingCacheRead, so future GetDictionaryFor() calls + // will wait for the metadata to be read before doing Match() + mDictionaryCache.InsertOrUpdate(prepath, origin); + RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader(); // After Start(), if we drop this ref reader will kill itself on // completion; it holds a self-ref - reader->Start(nullptr, prepath, aURI, aType, this, aCallback); - } else { - // No dictionaries for origin - (aCallback)(nullptr); + reader->Start(origin, prepath, aURI, aType, this, aCallback); + aAsync = true; + return; } + // No dictionaries for origin + (aCallback)(false, nullptr); } + //----------------------------------------------------------------------------- // DictionaryOrigin //----------------------------------------------------------------------------- NS_IMPL_ISUPPORTS(DictionaryOrigin, nsICacheEntryMetaDataVisitor) -void DictionaryOrigin::Write(DictionaryCacheEntry* aDictEntry) { +nsresult DictionaryOrigin::Write(DictionaryCacheEntry* aDictEntry) { DICTIONARY_LOG(("DictionaryOrigin::Write %s %p", mOrigin.get(), aDictEntry)); if (mEntry) { - aDictEntry->Write(mEntry); - } else { - // Write it once DictionaryOriginReader creates the entry - mDeferredWrites = true; + return aDictEntry->Write(mEntry); } + // Write it once DictionaryOriginReader creates the entry + mDeferredWrites = true; + return NS_OK; } void DictionaryOrigin::SetCacheEntry(nsICacheEntry* aEntry) { mEntry = aEntry; if (mDeferredWrites) { for (auto& entry : mEntries) { - Write(entry); + if (NS_FAILED(Write(entry))) { + RemoveEntry(entry); + } } } mDeferredWrites = false; + // Handle removes that were pending + for (auto& remove : mPendingRemove) { + DICTIONARY_LOG(("Pending RemoveEntry for %s", remove->mURI.get())); + remove->RemoveEntry(mEntry); + } + mPendingRemove.Clear(); } already_AddRefed<DictionaryCacheEntry> DictionaryOrigin::AddEntry( @@ -1128,7 +1176,13 @@ nsresult DictionaryOrigin::RemoveEntry(const nsACString& aKey) { // Ensure it doesn't disappear on us RefPtr<DictionaryCacheEntry> hold(dict); mEntries.RemoveElement(dict); - hold->RemoveEntry(mEntry); + if (mEntry) { + hold->RemoveEntry(mEntry); + } else { + // We don't have the cache entry yet. Defer the removal from + // the entry until we do + mPendingRemove.AppendElement(hold); + } if (MOZ_UNLIKELY( MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) { DumpEntries(); @@ -1184,9 +1238,10 @@ DictionaryCacheEntry* DictionaryOrigin::Match(const nsACString& aPath, ExtContentPolicyType aType) { uint32_t longest = 0; DictionaryCacheEntry* result = nullptr; + uint32_t now = mozilla::net::NowInSeconds(); for (const auto& dict : mEntries) { - if (dict->Match(aPath, aType, longest)) { + if (dict->Match(aPath, aType, now, longest)) { result = dict; } } @@ -1209,9 +1264,20 @@ nsresult DictionaryOrigin::OnMetaDataElement(const char* asciiKey, DICTIONARY_LOG(("DictionaryOrigin::OnMetaDataElement %s %s", asciiKey ? asciiKey : "", asciiValue)); + // If we already have an entry for this key (pending or in the list), + // don't override it + for (auto& entry : mEntries) { + if (entry->GetURI().Equals(asciiKey)) { + return NS_OK; + } + } + for (auto& entry : mPendingEntries) { + if (entry->GetURI().Equals(asciiKey)) { + return NS_OK; + } + } RefPtr<DictionaryCacheEntry> entry = new DictionaryCacheEntry(asciiKey); if (entry->ParseMetadata(asciiValue)) { - // XXX Check for duplicates? mEntries.AppendElement(entry); } return NS_OK; diff --git a/netwerk/cache2/Dictionary.h b/netwerk/cache2/Dictionary.h @@ -60,6 +60,7 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback, DictionaryCacheEntry(const char* aKey); DictionaryCacheEntry(const nsACString& aKey, const nsACString& aPattern, nsTArray<nsCString>& aMatchDest, const nsACString& aId, + uint32_t aExpiration = 0, const Maybe<nsCString>& aHash = Nothing()); static void ConvertMatchDestToEnumArray( @@ -68,7 +69,7 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback, // returns true if the pattern for the dictionary matches the path given bool Match(const nsACString& aFilePath, ExtContentPolicyType aType, - uint32_t& aLongest); + uint32_t aNow, uint32_t& aLongest); // This will fail if the cache entry is no longer available. // Start reading the cache entry into memory and call completion @@ -171,12 +172,14 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback, void UnblockAddEntry(DictionaryOrigin* aOrigin); private: - nsCString mURI; // URI (without ref) for the dictionary + // URI (without ref) for the dictionary + nsCString mURI; + // Expiration time, or 0 for none (default) + uint32_t mExpiration{0}; + nsCString mPattern; nsCString mId; // max length 1024 nsTArray<dom::RequestDestination> mMatchDest; - // XXX add type - // dcb and dcz use type 'raw'. We're allowed to ignore types we don't // understand, so we can fail to record a dictionary with type != 'raw' // nsCString mType; @@ -224,7 +227,32 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback, // memory for rarely used origins. We could have a limit for dictionaries, and // above that switch to partial caching and empty entries for origins without. -// XXX Clear all dictionaries when cookies are cleared for a site +class DictionaryCache; + +class DictionaryOriginReader final : public nsICacheEntryOpenCallback, + public nsIStreamListener { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + DictionaryOriginReader() {} + + void Start( + DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI, + ExtContentPolicyType aType, DictionaryCache* aCache, + const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback); + void FinishMatch(); + + private: + ~DictionaryOriginReader() {} + + RefPtr<DictionaryOrigin> mOrigin; + nsCOMPtr<nsIURI> mURI; + ExtContentPolicyType mType; + std::function<nsresult(bool, DictionaryCacheEntry*)> mCallback; + RefPtr<DictionaryCache> mCache; +}; // using DictCacheList = AutoCleanLinkedList<RefPtr<DictionaryCacheEntry>>; using DictCacheList = nsTArray<RefPtr<DictionaryCacheEntry>>; @@ -233,6 +261,7 @@ using DictCacheList = nsTArray<RefPtr<DictionaryCacheEntry>>; // add this: public LinkedListElement<RefPtr<DictionaryOrigin>>, class DictionaryOrigin : public nsICacheEntryMetaDataVisitor { friend class DictionaryCache; + friend class DictionaryOriginReader; public: NS_DECL_THREADSAFE_ISUPPORTS @@ -242,7 +271,7 @@ class DictionaryOrigin : public nsICacheEntryMetaDataVisitor { : mOrigin(aOrigin), mEntry(aEntry) {} void SetCacheEntry(nsICacheEntry* aEntry); - void Write(DictionaryCacheEntry* aDictEntry); + nsresult Write(DictionaryCacheEntry* aDictEntry); already_AddRefed<DictionaryCacheEntry> AddEntry( DictionaryCacheEntry* aDictEntry, bool aNewEntry); nsresult RemoveEntry(const nsACString& aKey); @@ -261,11 +290,15 @@ class DictionaryOrigin : public nsICacheEntryMetaDataVisitor { // Dictionaries currently being received. Once these get a Hash, move to // mEntries DictCacheList mPendingEntries; + // Dictionaries removed from mEntries but waiting to be removed from the + // Cache metadata + DictCacheList mPendingRemove; // Write out all entries once we have a cacheentry bool mDeferredWrites{false}; -}; -class DictionaryOriginReader; + // readers that are waiting for this origin's metadata to be read + nsTArray<RefPtr<DictionaryOriginReader>> mWaitingCacheRead; +}; // singleton class class DictionaryCache final { @@ -290,7 +323,8 @@ class DictionaryCache final { nsresult AddEntry(nsIURI* aURI, const nsACString& aKey, const nsACString& aPattern, nsTArray<nsCString>& aMatchDest, const nsACString& aId, const Maybe<nsCString>& aHash, - bool aNewEntry, DictionaryCacheEntry** aDictEntry); + bool aNewEntry, uint32_t aExpiration, + DictionaryCacheEntry** aDictEntry); already_AddRefed<DictionaryCacheEntry> AddEntry( nsIURI* aURI, bool aNewEntry, DictionaryCacheEntry* aDictEntry); @@ -310,8 +344,8 @@ class DictionaryCache final { // return an entry void GetDictionaryFor( - nsIURI* aURI, ExtContentPolicyType aType, - const std::function<nsresult(DictionaryCacheEntry*)>& aCallback); + nsIURI* aURI, ExtContentPolicyType aType, bool& aAsync, + const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { // XXX diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -1565,7 +1565,10 @@ nsresult nsHttpChannel::DoConnectActual( return rv; } - return DispatchTransaction(aTransWithStickyConn); + return CallOrWaitForResume( + [trans = RefPtr(aTransWithStickyConn)](auto* self) { + return self->DispatchTransaction(trans); + }); } nsresult nsHttpChannel::DispatchTransaction( @@ -1960,10 +1963,16 @@ nsresult nsHttpChannel::SetupChannelForTransaction() { // This may be async; the dictionary headers may need to fetch an origin // dictionary cache entry from disk before adding the headers. We can // continue with channel creation, and just block on this being done later + bool async; rv = gHttpHandler->AddAcceptAndDictionaryHeaders( mURI, mLoadInfo->GetExternalContentPolicyType(), &mRequestHead, - IsHTTPS(), [self = RefPtr(this)](DictionaryCacheEntry* aDict) { + IsHTTPS(), async, + [self = RefPtr(this)](bool aNeedsResume, DictionaryCacheEntry* aDict) { self->mDictDecompress = aDict; + if (aNeedsResume) { + LOG_DICTIONARIES(("Resuming after getting Dictionary headers")); + self->Resume(); + } if (self->mDictDecompress) { LOG_DICTIONARIES( ("Added dictionary header for %p, DirectoryCacheEntry %p", @@ -1991,6 +2000,11 @@ nsresult nsHttpChannel::SetupChannelForTransaction() { return true; }); if (NS_FAILED(rv)) return rv; + if (async) { + // we'll continue later if GetDictionaryFor is still reading + LOG_DICTIONARIES(("Suspending to get Dictionary headers")); + Suspend(); + } } // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer. @@ -5976,14 +5990,6 @@ nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry, rv = entry->SetMetaDataElement("original-response-headers", head.get()); if (NS_FAILED(rv)) return rv; - // If this is being marked as a dictionary, add it to the list - if (StaticPrefs::network_http_dictionaries_enable() && self->IsHTTPS()) { - if (!self->ParseDictionary(entry, responseHead, aModified)) { - LOG_DICTIONARIES( - ("Failed to parse use-as-dictionary from %s", head.get())); - } - } - // Indicate we have successfully finished setting metadata on the cache entry. rv = entry->MetaDataReady(); @@ -6021,8 +6027,12 @@ bool nsHttpChannel::ParseDictionary(nsICacheEntry* aEntry, matchIdVal.get(), matchDestItems.Length() > 0 ? matchDestItems[0].get() : "<none>", typeVal.get())); + + uint32_t expTime = 0; + Unused << GetCacheTokenExpirationTime(&expTime); + dicts->AddEntry(mURI, key, matchVal, matchDestItems, matchIdVal, Some(hash), - aModified, getter_AddRefs(mDictSaving)); + aModified, expTime, getter_AddRefs(mDictSaving)); // If this was 304 Not Modified, then we don't need the dictionary data // (though we may update the dictionary entry if the match/id/etc changed). // If this is 304, mDictSaving will be cleared by AddEntry. diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp @@ -656,7 +656,8 @@ nsresult nsHttpHandler::InitConnectionMgr() { // set by Fetch from the old request nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( nsIURI* aURI, ExtContentPolicyType aType, nsHttpRequestHead* aRequest, - bool aSecure, const std::function<bool(DictionaryCacheEntry*)>& aCallback) { + bool aSecure, bool& aAsync, + const std::function<bool(bool, DictionaryCacheEntry*)>& aCallback) { LOG(("Adding Dictionary headers")); nsresult rv = NS_OK; // Add the "Accept-Encoding" header and possibly Dictionary headers @@ -664,9 +665,9 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( // The dictionary info may require us to check the cache. if (StaticPrefs::network_http_dictionaries_enable()) { mDictionaryCache->GetDictionaryFor( - aURI, aType, - [self = RefPtr(this), aRequest, - aCallback](DictionaryCacheEntry* aDict) { + aURI, aType, aAsync, + [self = RefPtr(this), aRequest, aCallback]( + bool aNeedsResume, DictionaryCacheEntry* aDict) { nsresult rv; if (aDict) { nsAutoCStringN<64> encodedHash = @@ -688,7 +689,7 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( // DictionaryCache hasn't been updated to remove the entry yet (or // any other thing that were to desynchronize the DictionaryCache // with the actual cache. - if ((aCallback)(aDict)) { + if ((aCallback)(aNeedsResume, aDict)) { LOG_DICTIONARIES( ("Setting Available-Dictionary: %s", encodedHash.get())); rv = aRequest->SetHeader( @@ -715,14 +716,15 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( rv = aRequest->SetHeader( nsHttp::Accept_Encoding, self->mHttpsAcceptEncodings, false, nsHttpHeaderArray::eVarietyRequestOverride); - (aCallback)(nullptr); + (aCallback)(false, nullptr); return rv; }); } else { rv = aRequest->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings, false, nsHttpHeaderArray::eVarietyRequestOverride); - (aCallback)(nullptr); + aAsync = false; + (aCallback)(false, nullptr); } } else { // We need to not override a previous setting of 'identity' (for range @@ -734,9 +736,9 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( false, nsHttpHeaderArray::eVarietyRequestOverride); } - (aCallback)(nullptr); + aAsync = false; + (aCallback)(false, nullptr); } - return rv; } diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h @@ -119,8 +119,8 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, [[nodiscard]] nsresult AddAcceptAndDictionaryHeaders( nsIURI* aURI, ExtContentPolicyType aType, nsHttpRequestHead* aRequest, - bool aSecure, - const std::function<bool(DictionaryCacheEntry*)>& aCallback); + bool aSecure, bool& aAsync, + const std::function<bool(bool, DictionaryCacheEntry*)>& aCallback); [[nodiscard]] nsresult AddStandardRequestHeaders( nsHttpRequestHead*, nsIURI* aURI, ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting); diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp @@ -404,7 +404,8 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream, return NS_OK; } - // Dictionary-encoded brotli has a 36-byte header (4bit fixed + 32bit SHA-256) + // Dictionary-encoded brotli has a 36-byte header (4 byte fixed + 32 byte + // SHA-256) if (self->mBrotli->mDictionary && self->mBrotli->mEaten < 36) { uint8_t header_needed = 36 - self->mBrotli->mEaten; if (avail >= header_needed) { @@ -453,7 +454,7 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream, self->mBrotli->mTotalOut = totalOut; self->mBrotli->mBrotliStateIsStreamEnd = BrotliDecoderIsFinished(&self->mBrotli->mState); - LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%" PRIx32 + LOG(("nsHttpCompressConv %p brotlihandler decompress rv=%" PRIx32 " out=%zu\n", self, static_cast<uint32_t>(res), outSize));