tor-browser

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

commit 4c7312620865aee9739abccef2c90f8dab6d8cd6
parent d7cb9a5e0b91481697d6f1ebff33aba1b8e0a916
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Tue,  7 Oct 2025 14:07:10 +0000

Bug 1917970: Update handling of removing encodings for CompressionDictionaries r=necko-reviewers,valentin,kershaw

We need to ensure that we remove all entries from the Content-Encoding if
it's a dictionary or if it's dictionary-encoded

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

Diffstat:
Mnetwerk/protocol/http/HttpBaseChannel.cpp | 35+++++++++++++++++++----------------
Mnetwerk/protocol/http/nsHttpChannel.cpp | 90+++++++++++++++++++++++++++++++++++--------------------------------------------
Mnetwerk/protocol/http/nsHttpChannel.h | 6++----
Mnetwerk/protocol/http/nsHttpHandler.cpp | 23++++-------------------
4 files changed, 65 insertions(+), 89 deletions(-)

diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -1518,10 +1518,9 @@ HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener, // being a stack with the last converter created being the first one // to accept the raw network data. - nsAutoCString newEncoding; char* cePtr = contentEncoding.BeginWriting(); uint32_t count = 0; - bool removed_encoding = false; + bool removeEncodings = false; while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) { if (++count > 16) { // For compatibility with old code, we will just carry on without @@ -1562,30 +1561,34 @@ HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener, glean::http::content_encoding.AccumulateSingleSample(mode); } if (from.EqualsLiteral("dcb") || from.EqualsLiteral("dcz")) { - removed_encoding = true; + MOZ_ASSERT(XRE_IsParentProcess()); + removeEncodings = true; } nextListener = converter; } else { - if (val) LOG(("Unknown content encoding '%s', ignoring\n", val)); - // leave it in content-encoding if we didn't decompress it, though if - // there are following decoders, this will just be wrong, and we - // should error out. Or maybe error out on any unknown encoding - newEncoding += val; + if (val) { + LOG(("Unknown content encoding '%s'\n", val)); + } + // we *should* return NS_ERROR_UNEXPECTED here, but that will break sites + // that use things like content-encoding: x-gzip, x-gzip (or any other + // weird encoding) } } // dcb and dcz encodings are removed when it's decompressed (always in // the parent process). However, in theory you could have // Content-Encoding: dcb,gzip - // in which case we should pass it down to the content process as - // Content-Encoding: gzip - // This of course would be silly, but supported by the spec - if (removed_encoding) { - LOG(("Changing Content-Encoding from %s to %s", contentEncoding.get(), - newEncoding.get())); + // in which case we could pass it down to the content process as + // Content-Encoding: gzip. We won't do that; we'll remove all compressors + // if we need to remove any. + // This double compression of course is silly, but supported by the spec. + if (removeEncodings) { + // if we have dcb or dcz, all content-encodings in the header should + // be removed as we're decompressing before the tee in the parent + // process + LOG(("Changing Content-Encoding from '%s' to ''", contentEncoding.get())); // Can't use SetHeader; we need to overwrite the current value - rv = - mResponseHead->SetHeaderOverride(nsHttp::Content_Encoding, newEncoding); + rv = mResponseHead->SetHeaderOverride(nsHttp::Content_Encoding, ""_ns); } *aNewNextListener = do_AddRef(nextListener).take(); return NS_OK; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -6002,23 +6002,7 @@ nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry, rv = StoreAuthorizationMetaData(entry, requestHead); if (NS_FAILED(rv)) return rv; - rv = self->ProcessVaryCacheEntryHeaders(entry, nullptr); - if (NS_FAILED(rv)) return rv; - - // Store the received HTTP head with the cache entry as an element of - // the meta data. - nsAutoCString head; - responseHead->Flatten(head, true); - rv = entry->SetMetaDataElement("response-head", head.get()); - if (NS_FAILED(rv)) return rv; - head.Truncate(); - responseHead->FlattenNetworkOriginalHeaders(head); - rv = entry->SetMetaDataElement("original-response-headers", head.get()); - if (NS_FAILED(rv)) return rv; - - // Indicate we have successfully finished setting metadata on the cache entry. - rv = entry->MetaDataReady(); - + rv = self->UpdateCacheEntryHeaders(entry, nullptr); return rv; } @@ -6028,8 +6012,8 @@ nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry* entry, mSecurityInfo, aModified); } -nsresult nsHttpChannel::ProcessVaryCacheEntryHeaders(nsICacheEntry* entry, - const nsHttpAtom* aAtom) { +nsresult nsHttpChannel::UpdateCacheEntryHeaders(nsICacheEntry* entry, + const nsHttpAtom* aAtom) { nsresult rv = NS_OK; // Iterate over the headers listed in the Vary response header, and @@ -6094,18 +6078,23 @@ nsresult nsHttpChannel::ProcessVaryCacheEntryHeaders(nsICacheEntry* entry, } } } - return rv; -} - -nsresult nsHttpChannel::ModifiedCacheEntryHeaders(nsICacheEntry* entry, - const nsHttpAtom& aAtom) { - nsresult rv = ProcessVaryCacheEntryHeaders(entry, &aAtom); - if (NS_SUCCEEDED(rv)) { - // Indicate we have successfully finished setting metadata on the cache - // entry. - rv = entry->MetaDataReady(); + if (NS_FAILED(rv)) { + return rv; } - return rv; + // Store the received HTTP head with the cache entry as an element of + // the meta data. + nsAutoCString head; + mResponseHead->Flatten(head, true); + rv = entry->SetMetaDataElement("response-head", head.get()); + if (NS_FAILED(rv)) return rv; + head.Truncate(); + mResponseHead->FlattenNetworkOriginalHeaders(head); + rv = entry->SetMetaDataElement("original-response-headers", head.get()); + if (NS_FAILED(rv)) return rv; + + // Indicate we have successfully finished setting metadata on the cache + // entry. + return entry->MetaDataReady(); } bool nsHttpChannel::ParseDictionary(nsICacheEntry* aEntry, @@ -6219,7 +6208,10 @@ nsresult nsHttpChannel::InstallCacheListener(int64_t offset) { // normal listener chain, which often will eventually include a // decompressor. If the Content-Encoding is dcb or dcz, we'll include a // decompressor *before* the tee, so the cache will see decompressed data -// (we can't decompress dcb/dcz when reading from the cache). +// (we can't decompress dcb/dcz when reading from the cache). Also, if an +// entry is being used as a dictionary (Use-As-Dictionary), we want the data +// to in the cache to be decompressed, so we should install a decompressor +// before the tee as well. nsresult nsHttpChannel::DoInstallCacheListener(bool aIsDictionaryCompressed, nsACString* aDictionary, int64_t offset) { @@ -6299,7 +6291,7 @@ nsresult nsHttpChannel::DoInstallCacheListener(bool aIsDictionaryCompressed, // before the content process gets to see it // XXX We could recompress this with e.g. gzip to save space and improve // hitrate, at the cost of some CPU. - bool removedEncoding = false; + // Note: this doesn't handle cases like "dcb, gzip" or (worse?) "gzip, dcb". // We could in theory handle them. if (aDictionary || aIsDictionaryCompressed) { @@ -6311,38 +6303,36 @@ nsresult nsHttpChannel::DoInstallCacheListener(bool aIsDictionaryCompressed, if (NS_FAILED(rv)) { return rv; } + // Remove Available-Dictionary from Vary header if present. This + // avoids us refusing to match on a future load, for example if this + // dictionary was decoded from an earlier version using a dictionary + // (i.e. the update jquery to new version using the old version as a + // dictionary; no future load will use that old version). + + // XXX It would be slightly more efficient to remove all at once + // instead of sequentially by passing an array of strings + RemoveFromVary(mResponseHead.get(), "available-dictionary"_ns); + RemoveFromVary(mResponseHead.get(), "accept-encoding"_ns); + if (listener) { LOG_DICTIONARIES( ("Installed nsHTTPCompressConv %p before tee", listener.get())); mListener = listener; mCompressListener = listener; StoreHasAppliedConversion(true); - removedEncoding = true; + } else { LOG_DICTIONARIES(("Didn't install decompressor before tee")); } - if (removedEncoding) { - // Remove Available-Dictionary from Vary header if present if we - // decompressed and changed the content type. This avoids us refusing to - // match on a future load, for example if this dictionary was decoded from - // an earlier version using a dictionary (i.e. the update jquery to new - // version using the old version as a dictionary; no future load will use - // that old version). - // XXX It would be slightly more efficient to remove all at once - // instead of sequentially by passing an array of strings - RemoveFromVary(mResponseHead.get(), "available-dictionary"_ns); - RemoveFromVary(mResponseHead.get(), "accept-encoding"_ns); - } - } - - if (removedEncoding) { - // After decompressors, we modified Content-Encoding - rv = ModifiedCacheEntryHeaders(mCacheEntry, nsHttp::Content_Encoding); + // We may have modified Content-Encoding; make sure cache metadata + // reflects that. Pass nullptr so we pick up the Vary updates above + rv = UpdateCacheEntryHeaders(mCacheEntry, nullptr); if (NS_FAILED(rv)) { mCacheEntry->AsyncDoom(nullptr); return rv; } } + return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h @@ -421,10 +421,8 @@ class nsHttpChannel final : public HttpBaseChannel, bool aModified); [[nodiscard]] nsresult AddCacheEntryHeaders(nsICacheEntry* entry, bool aModified); - [[nodiscard]] nsresult ProcessVaryCacheEntryHeaders(nsICacheEntry* entry, - const nsHttpAtom* aAtom); - [[nodiscard]] nsresult ModifiedCacheEntryHeaders(nsICacheEntry* entry, - const nsHttpAtom& aAtom); + [[nodiscard]] nsresult UpdateCacheEntryHeaders(nsICacheEntry* entry, + const nsHttpAtom* aAtom); [[nodiscard]] nsresult FinalizeCacheEntry(); [[nodiscard]] nsresult InstallCacheListener(int64_t offset = 0); [[nodiscard]] nsresult DoInstallCacheListener(bool aIsDictionaryCompressed, diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp @@ -674,13 +674,10 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( aURI, aType, aAsync, [self = RefPtr(this), aRequest, aCallback]( bool aNeedsResume, DictionaryCacheEntry* aDict) { - nsresult rv; if (!aDict) { - rv = aRequest->SetHeader( - nsHttp::Accept_Encoding, self->mHttpsAcceptEncodings, false, - nsHttpHeaderArray::eVarietyRequestOverride); - (aCallback)(false, nullptr); - return rv; + // Accept-Encoding was already set in AddStandardHeaders + (aCallback)(aNeedsResume, nullptr); + return NS_OK; } nsAutoCStringN<64> encodedHash = ":"_ns + aDict->GetHash() + ":"_ns; @@ -704,7 +701,7 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( if ((aCallback)(aNeedsResume, aDict)) { LOG_DICTIONARIES( ("Setting Available-Dictionary: %s", encodedHash.get())); - rv = aRequest->SetHeader( + nsresult rv = aRequest->SetHeader( nsHttp::Available_Dictionary, encodedHash, false, nsHttpHeaderArray::eVarietyRequestOverride); if (NS_FAILED(rv)) { @@ -727,21 +724,9 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( return NS_OK; }); } else { - rv = aRequest->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings, - false, - nsHttpHeaderArray::eVarietyRequestOverride); aAsync = false; } } else { - // We need to not override a previous setting of 'identity' (for range - // requests) - nsAutoCString encoding; - Unused << aRequest->GetHeader(nsHttp::Accept_Encoding, encoding); - if (!encoding.EqualsLiteral("identity")) { - rv = aRequest->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings, - false, - nsHttpHeaderArray::eVarietyRequestOverride); - } aAsync = false; } // guard may call aCallback here