tor-browser

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

commit d4c735d662766f99e8233d8978470b2c7f207bfc
parent 24da1c66cf9cdba3f2754e71da2739d689044ae3
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Wed,  1 Oct 2025 18:45:55 +0000

Bug 1917970: Support decoding brotli & zstd dictionary-encoded streams r=necko-reviewers,valentin

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

Diffstat:
Mnetwerk/streamconv/converters/nsHTTPCompressConv.cpp | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mnetwerk/streamconv/converters/nsHTTPCompressConv.h | 4++++
2 files changed, 139 insertions(+), 14 deletions(-)

diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp @@ -9,19 +9,21 @@ #include "nsCOMPtr.h" #include "nsCRT.h" #include "nsError.h" +#include "nsIChannel.h" +#include "nsIForcePendingChannel.h" +#include "nsIHttpChannel.h" +#include "nsIRequest.h" +#include "nsIThreadRetargetableRequest.h" #include "nsIThreadRetargetableStreamListener.h" +#include "nsThreadUtils.h" #include "nsStreamUtils.h" #include "nsStringStream.h" #include "nsComponentManagerUtils.h" -#include "nsThreadUtils.h" +#include "mozilla/net/Dictionary.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/Logging.h" -#include "nsIForcePendingChannel.h" -#include "nsIRequest.h" #include "mozilla/UniquePtrExtensions.h" -#include "nsIThreadRetargetableRequest.h" -#include "nsIChannel.h" // brotli headers #undef assert @@ -35,17 +37,48 @@ namespace mozilla { namespace net { +class DictionaryCacheEntry; + extern LazyLogModule gHttpLog; #define LOG(args) \ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args) +extern LazyLogModule gDictionaryLog; +#define DICTIONARY_LOG(args) \ + MOZ_LOG(mozilla::net::gDictionaryLog, mozilla::LogLevel::Debug, args) + class BrotliWrapper { public: - BrotliWrapper() { - BrotliDecoderStateInit(&mState, nullptr, nullptr, nullptr); - } + BrotliWrapper() {} ~BrotliWrapper() { BrotliDecoderStateCleanup(&mState); } + bool Init(nsIRequest* aRequest) { + if (!BrotliDecoderStateInit(&mState, nullptr, nullptr, nullptr)) { + return false; + } + + nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(aRequest)); + if (!httpchannel) { + return false; + } + // XXX Wait for dictionary to be read into RAM!! + if (NS_SUCCEEDED(httpchannel->GetDictionary(getter_AddRefs(mDictionary))) && + mDictionary) { + size_t length = mDictionary->GetDictionary().length(); + DICTIONARY_LOG(("Brotli: dictionary %zu bytes", length)); + if (length > 0) { + BROTLI_BOOL result = BrotliDecoderAttachDictionary( + &mState, BROTLI_SHARED_DICTIONARY_RAW, length, + mDictionary->GetDictionary().begin()); + if (!result) { + DICTIONARY_LOG(("Brotli: AttachDictionary failed")); + return false; + } + } + } + return true; + } + BrotliDecoderState mState{}; Atomic<size_t, Relaxed> mTotalOut{0}; nsresult mStatus = NS_OK; @@ -54,6 +87,11 @@ class BrotliWrapper { nsIRequest* mRequest{nullptr}; nsISupports* mContext{nullptr}; uint64_t mSourceOffset{0}; + + RefPtr<DictionaryCacheEntry> mDictionary; + + uint8_t mEaten{0}; + uint8_t mHeader[36]; // \FF\44\43\42 + 32-byte SHA-256 }; #ifdef ZSTD_INFALLIBLE @@ -67,7 +105,24 @@ ZSTD_customMem const zstd_allocators = {zstd_malloc, zstd_free, nullptr}; class ZstdWrapper { public: - ZstdWrapper() { + ZstdWrapper(nsIRequest* aRequest, nsHTTPCompressConv::CompressMode aMode) { + size_t length = 0; + if (aMode == nsHTTPCompressConv::HTTP_COMPRESS_ZSTD_DICTIONARY) { + nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(aRequest)); + if (httpchannel) { + // XXX Wait for dictionary to be read into RAM!! + if (NS_FAILED( + httpchannel->GetDictionary(getter_AddRefs(mDictionary))) || + !mDictionary) { + return; + } + length = mDictionary->GetDictionary().length(); + } else { + // Can't decode without a dictionary + return; + } + } + #ifdef ZSTD_INFALLIBLE mDStream = ZSTD_createDStream_advanced(zstd_allocators); // infallible #else @@ -79,6 +134,15 @@ class ZstdWrapper { return; } #endif + if (mDictionary) { + DICTIONARY_LOG(("zstd: dictionary %zu bytes", length)); + ZSTD_DCtx_reset(mDStream, ZSTD_reset_session_only); + if (ZSTD_isError(ZSTD_DCtx_loadDictionary( + mDStream, mDictionary->GetDictionary().begin(), length))) { + return; + } + } + ZSTD_DCtx_setParameter(mDStream, ZSTD_d_windowLogMax, 23 /*8*1024*1024*/); } ~ZstdWrapper() { @@ -93,6 +157,8 @@ class ZstdWrapper { nsISupports* mContext{nullptr}; uint64_t mSourceOffset{0}; ZSTD_DStream* mDStream{nullptr}; + + RefPtr<DictionaryCacheEntry> mDictionary; }; // nsISupports implementation @@ -160,6 +226,12 @@ nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType, } else if (!nsCRT::strncasecmp(aFromType, HTTP_ZST_TYPE, sizeof(HTTP_ZST_TYPE) - 1)) { mMode = HTTP_COMPRESS_ZSTD; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_DICTIONARY_TYPE, + sizeof(HTTP_BROTLI_DICTIONARY_TYPE) - 1)) { + mMode = HTTP_COMPRESS_BROTLI_DICTIONARY; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_ZSTD_DICTIONARY_TYPE, + sizeof(HTTP_ZSTD_DICTIONARY_TYPE) - 1)) { + mMode = HTTP_COMPRESS_ZSTD_DICTIONARY; } LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this, aFromType, aToType, (CompressMode)mMode)); @@ -271,7 +343,8 @@ nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) { status = NS_ERROR_NET_PARTIAL_TRANSFER; LOG(("nsHttpCompresssConv %p onstop partial gzip\n", this)); } - if (NS_SUCCEEDED(status) && mMode == HTTP_COMPRESS_BROTLI) { + if (NS_SUCCEEDED(status) && (mMode == HTTP_COMPRESS_BROTLI || + mMode == HTTP_COMPRESS_BROTLI_DICTIONARY)) { nsCOMPtr<nsIForcePendingChannel> fpChannel = do_QueryInterface(request); bool isPending = false; if (request) { @@ -289,6 +362,17 @@ nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) { fpChannel->ForcePending(false); } } + // We don't need the dictionary data anymore + if (mBrotli || mZstd) { + RefPtr<DictionaryCacheEntry> dict; + nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(request)); + if (httpchannel) { + httpchannel->SetDictionary(nullptr); + } + // paranoia + mBrotli = nullptr; + mZstd = nullptr; + } nsCOMPtr<nsIStreamListener> listener; { @@ -319,6 +403,35 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream, return NS_OK; } + // Dictionary-encoded brotli has a 36-byte header (4bit fixed + 32bit SHA-256) + if (self->mBrotli->mDictionary && self->mBrotli->mEaten < 36) { + uint8_t header_needed = 36 - self->mBrotli->mEaten; + if (avail >= header_needed) { + memcpy(&self->mBrotli->mHeader[self->mBrotli->mEaten], dataIn, + header_needed); + avail -= header_needed; + dataIn += header_needed; + self->mBrotli->mEaten = 36; + + // Validate header + // XXX we could verify the SHA-256 matches what we offered + static uint8_t brotli_header[4] = {0xff, 0x44, 0x43, 0x42}; + if (memcmp(self->mBrotli->mHeader, brotli_header, 4) != 0) { + DICTIONARY_LOG( + ("!! %p Brotli failed: bad magic header 0x%02x%02x%02x%02x", self, + self->mBrotli->mHeader[0], self->mBrotli->mHeader[1], + self->mBrotli->mHeader[2], self->mBrotli->mHeader[3])); + self->mBrotli->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING; + return self->mBrotli->mStatus; + } + } else { + memcpy(&self->mBrotli->mHeader[self->mBrotli->mEaten], dataIn, aAvail); + self->mBrotli->mEaten += aAvail; + *countRead = aAvail; + return NS_OK; + } + } + auto outBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize); if (outBuffer == nullptr) { self->mBrotli->mStatus = NS_ERROR_OUT_OF_MEMORY; @@ -344,7 +457,10 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream, self, static_cast<uint32_t>(res), outSize)); if (res == BROTLI_DECODER_RESULT_ERROR) { - LOG(("nsHttpCompressConv %p marking invalid encoding", self)); + DICTIONARY_LOG( + ("nsHttpCompressConv %p decoding error: marking invalid encoding " + "(%zu)", + self, avail)); self->mBrotli->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING; return self->mBrotli->mStatus; } @@ -699,9 +815,13 @@ nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr, } /* gzip */ break; - case HTTP_COMPRESS_BROTLI: { + case HTTP_COMPRESS_BROTLI: + case HTTP_COMPRESS_BROTLI_DICTIONARY: { if (!mBrotli) { mBrotli = MakeUnique<BrotliWrapper>(); + if (!mBrotli->Init(request)) { + return NS_ERROR_FAILURE; + } } mBrotli->mRequest = request; @@ -718,9 +838,10 @@ nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr, } } break; - case HTTP_COMPRESS_ZSTD: { + case HTTP_COMPRESS_ZSTD: + case HTTP_COMPRESS_ZSTD_DICTIONARY: { if (!mZstd) { - mZstd = MakeUnique<ZstdWrapper>(); + mZstd = MakeUnique<ZstdWrapper>(request, mMode); if (!mZstd->mDStream) { return NS_ERROR_OUT_OF_MEMORY; } diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.h b/netwerk/streamconv/converters/nsHTTPCompressConv.h @@ -35,6 +35,8 @@ class nsIStringInputStream; # define HTTP_UNCOMPRESSED_TYPE "uncompressed" # define HTTP_ZSTD_TYPE "zstd" # define HTTP_ZST_TYPE "zst" +# define HTTP_BROTLI_DICTIONARY_TYPE "dcb" +# define HTTP_ZSTD_DICTIONARY_TYPE "dcz" namespace mozilla { namespace net { @@ -64,6 +66,8 @@ class nsHTTPCompressConv : public nsIStreamConverter, HTTP_COMPRESS_BROTLI, HTTP_COMPRESS_IDENTITY, HTTP_COMPRESS_ZSTD, + HTTP_COMPRESS_BROTLI_DICTIONARY, + HTTP_COMPRESS_ZSTD_DICTIONARY, }; private: