tor-browser

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

commit 3e35fa2501f0d2706fcc94c20d2628af51ab2c81
parent 06974fe1c159fa16f26a317af39023c39b04e618
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Tue,  7 Oct 2025 17:55:53 +0000

Bug 1917965: Add support for dcb and dcz content-encoding r=necko-reviewers,valentin

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

Diffstat:
Mmodules/libpref/init/all.js | 3++-
Mnetwerk/protocol/http/HttpBaseChannel.cpp | 37++++++++++++++++++++++++++++++++++---
Mnetwerk/protocol/http/HttpChannelChild.h | 7+++++++
Mnetwerk/protocol/http/InterceptedHttpChannel.h | 8++++++++
Mnetwerk/protocol/http/NullHttpChannel.cpp | 13+++++++++++++
Mnetwerk/protocol/http/ObliviousHttpChannel.cpp | 11+++++++++++
Mnetwerk/protocol/http/TRRServiceChannel.h | 7+++++++
Mnetwerk/protocol/http/nsHttpChannel.cpp | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mnetwerk/protocol/http/nsHttpChannel.h | 3+++
Mnetwerk/protocol/http/nsHttpHandler.cpp | 142+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mnetwerk/protocol/http/nsHttpHandler.h | 8+++-----
Mnetwerk/protocol/http/nsIHttpChannel.idl | 11+++++++++++
Mnetwerk/protocol/viewsource/nsViewSourceChannel.cpp | 13+++++++++++++
Mnetwerk/test/unit/xpcshell.toml | 2++
14 files changed, 286 insertions(+), 110 deletions(-)

diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js @@ -1168,7 +1168,8 @@ pref("network.http.redirection-limit", 20); pref("network.http.accept-encoding", "gzip, deflate"); pref("network.http.accept-encoding.secure", "gzip, deflate, br, zstd"); // dictionary compression is always only for secure connections -pref("network.http.accept-encoding.dictionary", "gzip, deflate, br, zstd, dcb, dcz"); +// Added to network.http.accept-encoding.secure +pref("network.http.accept-encoding.dictionary", "dcb, dcz"); // Prompt for redirects resulting in unsafe HTTP requests pref("network.http.prompt-temp-redirect", false); diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -376,6 +376,13 @@ nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps, rv = mRequestHead.SetHeader(nsHttp::Host, hostLine); if (NS_FAILED(rv)) return rv; + rv = gHttpHandler->AddAcceptAndDictionaryHeaders(aURI, &mRequestHead, isHTTPS, + mDict); + if (NS_FAILED(rv)) return rv; + if (mDict) { + mDict->InUse(); + } + // Override the Accept header if a specific MediaDocument kind is forced. ExtContentPolicy contentPolicyType = mLoadInfo->GetExternalContentPolicyType(); @@ -395,7 +402,7 @@ nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps, } rv = gHttpHandler->AddStandardRequestHeaders( - &mRequestHead, isHTTPS, aURI, contentPolicyType, + &mRequestHead, aURI, contentPolicyType, nsContentUtils::ShouldResistFingerprinting(this, RFPTarget::HttpUserAgent)); if (NS_FAILED(rv)) return rv; @@ -1472,6 +1479,23 @@ HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener, LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this)); +#ifdef DEBUG + { + nsAutoCString contentEncoding; + nsresult rv = + mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding); + if (NS_SUCCEEDED(rv) && !contentEncoding.IsEmpty()) { + nsAutoCString newEncoding; + char* cePtr = contentEncoding.BeginWriting(); + while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) { + if (strcmp(val, "dcb") == 0 || strcmp(val, "dcz") == 0) { + MOZ_ASSERT(LoadApplyConversion() && !LoadHasAppliedConversion()); + } + } + } + } +#endif + if (!LoadApplyConversion()) { LOG(("not applying conversion per ApplyConversion\n")); return NS_OK; @@ -1502,12 +1526,12 @@ 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; while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) { if (++count > 16) { - // That's ridiculous. We only understand 2 different ones :) - // but for compatibility with old code, we will just carry on without + // For compatibility with old code, we will just carry on without // removing the encodings LOG(("Too many Content-Encodings. Ignoring remainder.\n")); break; @@ -1543,8 +1567,15 @@ HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener, 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; } } + + rv = mResponseHead->SetHeader(nsHttp::Content_Encoding, newEncoding); + *aNewNextListener = do_AddRef(nextListener).take(); return NS_OK; } diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h @@ -84,6 +84,13 @@ class HttpChannelChild final : public PHttpChannelChild, // nsIChannel NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override; NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + NS_IMETHOD GetDictionary(DictionaryCacheEntry** aDictionary) override { + *aDictionary = nullptr; + return NS_OK; + } + NS_IMETHOD SetDictionary(DictionaryCacheEntry* aDictionary) override { + return NS_OK; + } // HttpBaseChannel::nsIHttpChannel NS_IMETHOD SetRequestHeader(const nsACString& aHeader, diff --git a/netwerk/protocol/http/InterceptedHttpChannel.h b/netwerk/protocol/http/InterceptedHttpChannel.h @@ -319,6 +319,14 @@ class InterceptedHttpChannel final void DoNotifyListenerCleanup() override; void DoAsyncAbort(nsresult aStatus) override; + + NS_IMETHOD GetDictionary(DictionaryCacheEntry** aDictionary) override { + *aDictionary = nullptr; + return NS_OK; + } + NS_IMETHOD SetDictionary(DictionaryCacheEntry* aDictionary) override { + return NS_OK; + } }; } // namespace mozilla::net diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp @@ -846,6 +846,19 @@ NullHttpChannel::GetRenderBlocking(bool* aRenderBlocking) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +NullHttpChannel::GetDictionary( + mozilla::net::DictionaryCacheEntry** aDictionary) { + *aDictionary = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetDictionary( + mozilla::net::DictionaryCacheEntry* aDictionary) { + return NS_OK; +} + #define IMPL_TIMING_ATTR(name) \ NS_IMETHODIMP \ NullHttpChannel::Get##name##Time(PRTime* _retval) { \ diff --git a/netwerk/protocol/http/ObliviousHttpChannel.cpp b/netwerk/protocol/http/ObliviousHttpChannel.cpp @@ -880,4 +880,15 @@ NS_IMETHODIMP ObliviousHttpChannel::GetDocumentCharacterSet( return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP ObliviousHttpChannel::GetDictionary( + DictionaryCacheEntry** aDictionary) { + *aDictionary = nullptr; + return NS_OK; +} + +NS_IMETHODIMP ObliviousHttpChannel::SetDictionary( + DictionaryCacheEntry* aDictionary) { + return NS_OK; +} + } // namespace mozilla::net diff --git a/netwerk/protocol/http/TRRServiceChannel.h b/netwerk/protocol/http/TRRServiceChannel.h @@ -76,6 +76,13 @@ class TRRServiceChannel : public HttpBaseChannel, NS_IMETHOD SetNotificationCallbacks( nsIInterfaceRequestor* aCallbacks) override; + NS_IMETHOD GetDictionary(DictionaryCacheEntry** aDictionary) override { + *aDictionary = nullptr; + return NS_OK; + } + NS_IMETHOD SetDictionary(DictionaryCacheEntry* aDictionary) override { + return NS_OK; + } // nsISupportsPriority NS_IMETHOD SetPriority(int32_t value) override; // nsIClassOfService diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -150,6 +150,9 @@ # include "mozilla/StaticPrefs_fuzzing.h" #endif +// TODO replace in later patch +#define LOG_DICTIONARIES(x) LOG(x) + namespace mozilla { using namespace dom; @@ -504,6 +507,10 @@ nsHttpChannel::~nsHttpChannel() { if (gHttpHandler) { gHttpHandler->RemoveHttpChannel(mChannelId); } + + if (mDict) { + mDict->UseCompleted(); + } } void nsHttpChannel::ReleaseMainThreadOnlyReferences() { @@ -1960,8 +1967,8 @@ nsresult nsHttpChannel::SetupChannelForTransaction() { // override that, so we don't try. Section 4.5 step 8.19 and 8.20 of // HTTP-network-or-cache fetch // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch - rv = mHttpHandler->AddEncodingHeaders(&mRequestHead, - mURI->SchemeIs("https"), mURI); + rv = mHttpHandler->AddAcceptAndDictionaryHeaders( + mURI, &mRequestHead, mURI->SchemeIs("https"), mDict); if (NS_FAILED(rv)) return rv; } @@ -3502,8 +3509,34 @@ nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) { if (mCacheEntry) { rv = InitCacheEntry(); if (NS_FAILED(rv)) CloseCacheEntry(true); + } else { + // We may still need to decompress in the parent if it's dcb or dcz + nsAutoCString contentEncoding; + Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, + contentEncoding); + if (contentEncoding.Equals("dcb") || contentEncoding.Equals("dcz")) { + LOG_DICTIONARIES( + ("Removing Content-Encoding %s for %p", contentEncoding.get(), this)); + nsCOMPtr<nsIStreamListener> listener; + // otherwise we won't convert in the parent process + // XXX may be redundant, but safe + SetApplyConversion(true); + rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), + nullptr); + if (NS_FAILED(rv)) { + return rv; + } + if (listener) { + LOG_DICTIONARIES(("Installed nsHTTPCompressConv %p without cache tee", + listener.get())); + mListener = listener; + mCompressListener = listener; + StoreHasAppliedConversion(true); + } else { + LOG_DICTIONARIES(("Didn't install decompressor without cache tee")); + } + } } - // Check that the server sent us what we were asking for if (LoadResuming()) { // Create an entity id from the response @@ -3530,15 +3563,19 @@ nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) { } } - rv = CallOnStartRequest(); - if (NS_FAILED(rv)) return rv; + // We need to do this before CallonStartRequest, since this can modify + // the Content-Encoding to remove dcb/dcz (and perhaps others), and + // CallOnStartRequest() sends this to the content process. - // install cache listener if we still have a cache entry open + // Install cache listener if we still have a cache entry open if (mCacheEntry && !LoadCacheEntryIsReadOnly()) { rv = InstallCacheListener(); if (NS_FAILED(rv)) return rv; } + rv = CallOnStartRequest(); + if (NS_FAILED(rv)) return rv; + return NS_OK; } @@ -6008,28 +6045,14 @@ nsresult nsHttpChannel::InstallCacheListener(int64_t offset) { mRaceCacheWithNetwork); MOZ_ASSERT(mListener); - nsAutoCString contentEncoding, contentType; - Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding); - mResponseHead->ContentType(contentType); - // If the content is compressible and the server has not compressed it, - // mark the cache entry for compression. - if (contentEncoding.IsEmpty() && - (contentType.EqualsLiteral(TEXT_HTML) || - contentType.EqualsLiteral(TEXT_PLAIN) || - contentType.EqualsLiteral(TEXT_CSS) || - contentType.EqualsLiteral(TEXT_JAVASCRIPT) || - contentType.EqualsLiteral(TEXT_ECMASCRIPT) || - contentType.EqualsLiteral(TEXT_XML) || - contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) || - contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) || - contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) || - contentType.EqualsLiteral(APPLICATION_XHTML_XML))) { - rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0"); - if (NS_FAILED(rv)) { - LOG(("unable to mark cache entry for compression")); - } + // XXX We may want to consider recompressing any dcb/dcz files to save space + // and improve hitrate. Downside is CPU use, complexity and perhaps delay, + // maybe. + nsAutoCString dictionary; + Unused << mResponseHead->GetHeader(nsHttp::Use_As_Dictionary, dictionary); + if (!dictionary.IsEmpty()) { + mCacheEntry->SetDictionary(mDict); } - LOG(("Trading cache input stream for output stream [channel=%p]", this)); // We must close the input stream first because cache entries do not @@ -6081,12 +6104,44 @@ nsresult nsHttpChannel::InstallCacheListener(int64_t offset) { do_CreateInstance(kStreamListenerTeeCID, &rv); if (NS_FAILED(rv)) return rv; + rv = tee->Init(mListener, out, nullptr); LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(), static_cast<uint32_t>(rv))); - rv = tee->Init(mListener, out, nullptr); if (NS_FAILED(rv)) return rv; - mListener = tee; + + // If this is Use-As-Dictionary we need to be able to read it quickly for + // dictionary use, OR if it's encoded in dcb or dcz (using a dictionary), + // we must decompress it before storing since we won't have the dictionary + // when we go to read it out later. + // In this case, we hook an nsHTTPCompressConv instance in before the tee + // since we don't want to have to decompress it here and again in the content + // process (if it's not dcb/dcz); if it is dcb/dcz we must decompress it + // before the content process gets to see it + nsAutoCString contentEncoding; + Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding); + LOG(("Content-Encoding for %p: %s", this, + PromiseFlatCString(contentEncoding).get())); + if (!dictionary.IsEmpty() || contentEncoding.Equals("dcb") || + contentEncoding.Equals("dcz")) { + nsCOMPtr<nsIStreamListener> listener; + // otherwise we won't convert in the parent process + SetApplyConversion(true); + rv = + DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr); + if (NS_FAILED(rv)) { + return rv; + } + if (listener) { + LOG(("Installed nsHTTPCompressConv %p before tee", listener.get())); + mListener = listener; + mCompressListener = listener; + StoreHasAppliedConversion(true); + } else + LOG(("Didn't install decompressor before tee")); + } + + // XXX telemetry as to how often we get dcb/dcz return NS_OK; } @@ -7700,6 +7755,24 @@ nsHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) { return NS_OK; } +NS_IMETHODIMP +nsHttpChannel::GetDictionary(DictionaryCacheEntry** aDictionary) { + *aDictionary = do_AddRef(mDict).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetDictionary(DictionaryCacheEntry* aDictionary) { + if (mDict) { + mDict->UseCompleted(); + } + mDict = aDictionary; + if (aDictionary) { + aDictionary->InUse(); + } + return NS_OK; +} + //----------------------------------------------------------------------------- // nsHttpChannel::nsIHttpChannelInternal //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h @@ -205,6 +205,9 @@ class nsHttpChannel final : public HttpBaseChannel, NS_IMETHOD SetResponseStatus(uint32_t aStatus, const nsACString& aStatusText) override; + NS_IMETHOD GetDictionary(DictionaryCacheEntry** aDictionary) override; + NS_IMETHOD SetDictionary(DictionaryCacheEntry* aDictionary) override; + void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter); HttpChannelSecurityWarningReporter* GetWarningReporter(); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp @@ -27,6 +27,7 @@ #include "nsCOMPtr.h" #include "nsNetCID.h" #include "mozilla/AppShutdown.h" +#include "mozilla/Base64.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Components.h" #include "mozilla/Printf.h" @@ -653,58 +654,72 @@ nsresult nsHttpHandler::InitConnectionMgr() { // We're using RequestOverride because this can get called when these are // set by Fetch from the old request -// only call if we're https! nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( - nsIURI* aURI, nsHttpRequestHead* aRequest) { + nsIURI* aURI, nsHttpRequestHead* aRequest, bool aSecure, + RefPtr<DictionaryCacheEntry>& aDict) { LOG(("Adding Dictionary headers")); nsresult rv; - // The dictionary info may require us to check the cache. - // XXX This would require that AddAcceptAndDictionaryHeaders be effectively - // async, perhaps by passing a lambda in. - RefPtr<DictionaryCacheEntry> dict = - mDictionaryCache ? mDictionaryCache->GetDictionaryFor(aURI) : nullptr; - if (dict) { - rv = - aRequest->SetHeader(nsHttp::Accept_Encoding, mDictionaryAcceptEncodings, - false, nsHttpHeaderArray::eVarietyRequestOverride); - if (NS_FAILED(rv)) { - return rv; - } - LOG(("Setting Accept-Encoding: %s", - PromiseFlatCString(mDictionaryAcceptEncodings).get())); + // Add the "Accept-Encoding" header and possibly Dictionary headers + if (aSecure) { + // The dictionary info may require us to check the cache. + // XXX This would require that AddAcceptAndDictionaryHeaders be effectively + // async, perhaps by passing a lambda to call AddAcceptAndDictionaryHeaders + // and then unblock the request + aDict = + mDictionaryCache ? mDictionaryCache->GetDictionaryFor(aURI) : nullptr; + if (aDict) { + rv = aRequest->SetHeader(nsHttp::Accept_Encoding, + mDictionaryAcceptEncodings, false, + nsHttpHeaderArray::eVarietyRequestOverride); + if (NS_FAILED(rv)) { + return rv; + } + LOG(("Setting Accept-Encoding: %s", mDictionaryAcceptEncodings.get())); - LOG(("Setting Available-Dictionary: %s", - PromiseFlatCString(dict->GetHash()).get())); - rv = aRequest->SetHeader(nsHttp::Available_Dictionary, dict->GetHash(), - false, nsHttpHeaderArray::eVarietyRequestOverride); - if (NS_FAILED(rv)) { - return rv; - } - if (!dict->GetId().IsEmpty()) { - rv = aRequest->SetHeader(nsHttp::Dictionary_Id, dict->GetId(), false, + nsAutoCStringN<64> encodedHash = ":"_ns + aDict->GetHash() + ":"_ns; + + LOG(("Setting Available-Dictionary: %s", encodedHash.get())); + rv = aRequest->SetHeader(nsHttp::Available_Dictionary, encodedHash, false, nsHttpHeaderArray::eVarietyRequestOverride); if (NS_FAILED(rv)) { return rv; } - LOG(("Setting Dictionary-Id: %s", - PromiseFlatCString(dict->GetId()).get())); - } - // Need to retain access to the dictionary until the request completes. - // Note that this includes if the dictionary we offered gets replaced - // by another request while we're waiting for a response; in that case we - // need to read in a copy of the dictionary into memory before overwriting - // it and store in dict temporarily. - dict->InUse(); - aRequest->SetDictionary(dict); + if (!aDict->GetId().IsEmpty()) { + rv = aRequest->SetHeader(nsHttp::Dictionary_Id, aDict->GetId(), false, + nsHttpHeaderArray::eVarietyRequestOverride); + if (NS_FAILED(rv)) { + return rv; + } + LOG(("Setting Dictionary-Id: %s", aDict->GetId().get())); + } + // Need to retain access to the dictionary until the request completes. + // Note that this includes if the dictionary we offered gets replaced + // by another request while we're waiting for a response; in that case we + // need to read in a copy of the dictionary into memory before overwriting + // it and store in dict temporarily. + aRequest->SetDictionary(aDict); + } else { + rv = aRequest->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings, + false, + nsHttpHeaderArray::eVarietyRequestOverride); + } } else { - rv = aRequest->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings, - false, nsHttpHeaderArray::eVarietyRequestOverride); + // 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); + } } + return rv; } nsresult nsHttpHandler::AddStandardRequestHeaders( - nsHttpRequestHead* request, bool isSecure, nsIURI* aURI, + nsHttpRequestHead* request, nsIURI* aURI, ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting) { nsresult rv; @@ -751,15 +766,6 @@ nsresult nsHttpHandler::AddStandardRequestHeaders( if (NS_FAILED(rv)) return rv; } - // Add the "Accept-Encoding" header - if (isSecure) { - rv = AddAcceptAndDictionaryHeaders(aURI, request); - } else { - rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings, - false, nsHttpHeaderArray::eVarietyRequestDefault); - } - if (NS_FAILED(rv)) return rv; - // add the "Send Hint" header if (mSafeHintEnabled || sParentalControlsEnabled) { rv = request->SetHeader(nsHttp::Prefer, "safe"_ns, false, @@ -769,19 +775,6 @@ nsresult nsHttpHandler::AddStandardRequestHeaders( return NS_OK; } -nsresult nsHttpHandler::AddEncodingHeaders(nsHttpRequestHead* request, - bool isSecure, nsIURI* aURI) { - // Add the "Accept-Encoding" header and any dictionary headers - nsresult rv; - if (isSecure) { - rv = AddAcceptAndDictionaryHeaders(aURI, request); - } else { - rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings, - false, nsHttpHeaderArray::eVarietyRequestOverride); - } - return rv; -} - nsresult nsHttpHandler::AddConnectionHeader(nsHttpRequestHead* request, uint32_t caps) { // RFC2616 section 19.6.2 states that the "Connection: keep-alive" @@ -807,8 +800,10 @@ bool nsHttpHandler::IsAcceptableEncoding(const char* enc, bool isSecure) { // continuing bad behavior.. so limit it to known x-* patterns bool rv; if (isSecure) { - rv = nsHttp::FindToken(mHttpsAcceptEncodings.get(), enc, HTTP_LWS ",") != - nullptr; + // Should be a superset of mAcceptEncodings (unless someone messes with + // prefs) + rv = nsHttp::FindToken(mDictionaryAcceptEncodings.get(), enc, + HTTP_LWS ",") != nullptr; } else { rv = nsHttp::FindToken(mHttpAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr; @@ -1487,22 +1482,25 @@ void nsHttpHandler::PrefsChanged(const char* pref) { } } - if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure"))) { + if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure")) || + PREF_CHANGED(HTTP_PREF("accept-encoding.dictionary"))) { nsAutoCString acceptEncodings; rv = Preferences::GetCString(HTTP_PREF("accept-encoding.secure"), acceptEncodings); if (NS_SUCCEEDED(rv)) { rv = SetAcceptEncodings(acceptEncodings.get(), true, false); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - } - } - if (PREF_CHANGED(HTTP_PREF("accept-encoding.dictionary"))) { - nsAutoCString acceptDictionaryEncodings; - rv = Preferences::GetCString(HTTP_PREF("accept-encoding.dictionary"), - acceptDictionaryEncodings); - if (NS_SUCCEEDED(rv)) { - rv = SetAcceptEncodings(acceptDictionaryEncodings.get(), true, true); + // Since dictionary encodings are dependent on both accept-encoding.secure + // and accept-encoding.dictionary, update both if either changes (which is + // quite rare, so there's no real perf hit) + nsAutoCString acceptDictionaryEncodings; + rv = Preferences::GetCString(HTTP_PREF("accept-encoding.dictionary"), + acceptDictionaryEncodings); + if (NS_SUCCEEDED(rv) && !acceptDictionaryEncodings.IsEmpty()) { + acceptEncodings.Append(", "_ns); + acceptEncodings.Append(acceptDictionaryEncodings); + rv = SetAcceptEncodings(acceptEncodings.get(), true, true); + } MOZ_ASSERT(NS_SUCCEEDED(rv)); } } diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h @@ -118,13 +118,11 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, static already_AddRefed<nsHttpHandler> GetInstance(); [[nodiscard]] nsresult AddAcceptAndDictionaryHeaders( - nsIURI* aURI, nsHttpRequestHead* aRequest); + nsIURI* aURI, nsHttpRequestHead* aRequest, bool aSecure, + RefPtr<DictionaryCacheEntry>& aDict); [[nodiscard]] nsresult AddStandardRequestHeaders( - nsHttpRequestHead*, bool isSecure, nsIURI* aURI, - ExtContentPolicyType aContentPolicyType, + nsHttpRequestHead*, nsIURI* aURI, ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting); - [[nodiscard]] nsresult AddEncodingHeaders(nsHttpRequestHead* request, - bool isSecure, nsIURI* aURI); [[nodiscard]] nsresult AddConnectionHeader(nsHttpRequestHead*, uint32_t caps); bool IsAcceptableEncoding(const char* encoding, bool isSecure); diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl @@ -10,8 +10,13 @@ interface nsIReferrerInfo; %{C++ #include "GeckoProfiler.h" +namespace mozilla::net { +class DictionaryCacheEntry; +} %} +[ptr] native DictionaryCacheEntry(mozilla::net::DictionaryCacheEntry); + native UniqueProfileChunkedBuffer(mozilla::UniquePtr<mozilla::ProfileChunkedBuffer>); /** @@ -508,4 +513,10 @@ interface nsIHttpChannel : nsIIdentChannel * and user agent on the channel is outdated. */ [noscript, must_use] attribute boolean isUserAgentHeaderOutdated; + /** + * Dictionary for decompression, if any + */ + [noscript] attribute DictionaryCacheEntry dictionary; + + }; diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp @@ -1224,6 +1224,19 @@ nsViewSourceChannel::AsyncOnChannelRedirect( return NS_OK; } +NS_IMETHODIMP +nsViewSourceChannel::GetDictionary( + mozilla::net::DictionaryCacheEntry** aDictionary) { + *aDictionary = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsViewSourceChannel::SetDictionary( + mozilla::net::DictionaryCacheEntry* aDictionary) { + return NS_OK; +} + // nsIInterfaceRequestor NS_IMETHODIMP diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml @@ -471,6 +471,8 @@ run-sequentially = ["true"] # httpd server ["test_cache2_nostore.js"] +["test_cache2_compression_dictionary.js"] + ["test_cache_204_response.js"] ["test_cache_jar.js"]