From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../streamconv/converters/nsHTTPCompressConv.cpp | 233 ++++++++++++++++++++- 1 file changed, 229 insertions(+), 4 deletions(-) (limited to 'netwerk/streamconv/converters/nsHTTPCompressConv.cpp') diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp index e06c72222b..7c7404f110 100644 --- a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp +++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsHTTPCompressConv.h" +#include "ErrorList.h" #include "nsCOMPtr.h" #include "nsCRT.h" #include "nsError.h" @@ -19,6 +20,8 @@ #include "nsIForcePendingChannel.h" #include "nsIRequest.h" #include "mozilla/UniquePtrExtensions.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIChannel.h" // brotli headers #undef assert @@ -26,6 +29,8 @@ #include "state.h" #include "brotli/decode.h" +#include "zstd/zstd.h" + namespace mozilla { namespace net { @@ -50,6 +55,26 @@ class BrotliWrapper { uint64_t mSourceOffset{0}; }; +class ZstdWrapper { + public: + ZstdWrapper() { + mDStream = ZSTD_createDStream(); + ZSTD_DCtx_setParameter(mDStream, ZSTD_d_windowLogMax, 23 /*8*1024*1024*/); + } + ~ZstdWrapper() { + if (mDStream) { + ZSTD_freeDStream(mDStream); + } + } + + UniquePtr mOutBuffer; + nsresult mStatus = NS_OK; + nsIRequest* mRequest{nullptr}; + nsISupports* mContext{nullptr}; + uint64_t mSourceOffset{0}; + ZSTD_DStream* mDStream{nullptr}; +}; + // nsISupports implementation NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener, nsIRequestObserver, nsICompressConvStats, @@ -109,6 +134,12 @@ nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType, } else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_TYPE, sizeof(HTTP_BROTLI_TYPE) - 1)) { mMode = HTTP_COMPRESS_BROTLI; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_ZSTD_TYPE, + sizeof(HTTP_ZSTD_TYPE) - 1)) { + mMode = HTTP_COMPRESS_ZSTD; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_ZST_TYPE, + sizeof(HTTP_ZST_TYPE) - 1)) { + mMode = HTTP_COMPRESS_ZSTD; } LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this, aFromType, aToType, (CompressMode)mMode)); @@ -127,6 +158,54 @@ nsHTTPCompressConv::GetConvertedType(const nsACString& aFromType, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsHTTPCompressConv::MaybeRetarget(nsIRequest* request) { + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv; + nsCOMPtr req = do_QueryInterface(request); + if (!req) { + return NS_ERROR_NO_INTERFACE; + } + if (!StaticPrefs::network_decompression_off_mainthread()) { + return NS_OK; + } + nsCOMPtr target; + rv = req->GetDeliveryTarget(getter_AddRefs(target)); + if (NS_FAILED(rv) || !target || target->IsOnCurrentThread()) { + nsCOMPtr channel(do_QueryInterface(request)); + int64_t length = -1; + if (channel) { + channel->GetContentLength(&length); + // If this fails we'll retarget + } + if (length <= 0 || + length >= + StaticPrefs::network_decompression_off_mainthread_min_size()) { + LOG(("MaybeRetarget: Retargeting to background thread: Length %" PRId64, + length)); + // No retargetting was performed. Decompress off MainThread, + // and dispatch results back to MainThread. + // Don't do this if the input is small, if we know the length. + // If the length is 0 (unknown), always use OMT. + nsCOMPtr backgroundThread; + rv = NS_CreateBackgroundTaskQueue("nsHTTPCompressConv", + getter_AddRefs(backgroundThread)); + NS_ENSURE_SUCCESS(rv, rv); + rv = req->RetargetDeliveryTo(backgroundThread); + NS_ENSURE_SUCCESS(rv, rv); + if (NS_SUCCEEDED(rv)) { + mDispatchToMainThread = true; + } + } else { + LOG(("MaybeRetarget: Not retargeting: Length %" PRId64, length)); + } + } else { + LOG(("MaybeRetarget: Don't need to retarget")); + } + + return NS_OK; +} + NS_IMETHODIMP nsHTTPCompressConv::OnStartRequest(nsIRequest* request) { LOG(("nsHttpCompresssConv %p onstart\n", this)); @@ -135,14 +214,33 @@ nsHTTPCompressConv::OnStartRequest(nsIRequest* request) { MutexAutoLock lock(mMutex); listener = mListener; } - return listener->OnStartRequest(request); + nsresult rv = listener->OnStartRequest(request); + if (NS_SUCCEEDED(rv)) { + if (XRE_IsContentProcess()) { + nsCOMPtr retargetlistener = + do_QueryInterface(listener); + // |nsHTTPCompressConv| should *always* be dispatched off of the main + // thread from a content process, even if its listeners don't support it. + // + // If its listener chain does not support being retargeted off of the + // main thread, it will be dispatched back to the main thread in + // |do_OnDataAvailable| and |OnStopRequest|. + if (!retargetlistener || + NS_FAILED(retargetlistener->CheckListenerChain())) { + mDispatchToMainThread = true; + } + } + } + return rv; } NS_IMETHODIMP nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) { nsresult status = aStatus; - LOG(("nsHttpCompresssConv %p onstop %" PRIx32 "\n", this, - static_cast(aStatus))); + // Bug 1886237 : TRRServiceChannel calls OnStopRequest OMT + // MOZ_ASSERT(NS_IsMainThread()); + LOG(("nsHttpCompresssConv %p onstop %" PRIx32 " mDispatchToMainThread %d\n", + this, static_cast(aStatus), mDispatchToMainThread)); // Framing integrity is enforced for content-encoding: gzip, but not for // content-encoding: deflate. Note that gzip vs deflate is NOT determined @@ -181,6 +279,7 @@ nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) { MutexAutoLock lock(mMutex); listener = mListener; } + return listener->OnStopRequest(request, status); } @@ -292,6 +391,71 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream, return self->mBrotli->mStatus; } +/* static */ +nsresult nsHTTPCompressConv::ZstdHandler(nsIInputStream* stream, void* closure, + const char* dataIn, uint32_t, + uint32_t aAvail, uint32_t* countRead) { + MOZ_ASSERT(stream); + nsHTTPCompressConv* self = static_cast(closure); + *countRead = 0; + + const size_t kOutSize = ZSTD_DStreamOutSize(); // normally 128K + uint8_t* outPtr; + size_t avail = aAvail; + + // Stop decompressing after an error + if (self->mZstd->mStatus != NS_OK) { + *countRead = aAvail; + return NS_OK; + } + + if (!self->mZstd->mOutBuffer) { + self->mZstd->mOutBuffer = MakeUniqueFallible(kOutSize); + if (!self->mZstd->mOutBuffer) { + self->mZstd->mStatus = NS_ERROR_OUT_OF_MEMORY; + return self->mZstd->mStatus; + } + } + ZSTD_inBuffer inBuffer = {.src = dataIn, .size = aAvail, .pos = 0}; + uint32_t last_pos = 0; + while (inBuffer.pos < inBuffer.size) { + outPtr = self->mZstd->mOutBuffer.get(); + + LOG(("nsHttpCompresssConv %p zstdhandler decompress %zu\n", self, avail)); + // Use ZSTD_(de)compressStream to (de)compress the input buffer into the + // output buffer, and fill aReadCount with the number of bytes consumed. + ZSTD_outBuffer outBuffer{.dst = outPtr, .size = kOutSize}; + size_t result; + bool output_full; + do { + outBuffer.pos = 0; + result = + ZSTD_decompressStream(self->mZstd->mDStream, &outBuffer, &inBuffer); + + // If we errored when writing, flag this and abort writing. + if (ZSTD_isError(result)) { + self->mZstd->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING; + return self->mZstd->mStatus; + } + + nsresult rv = self->do_OnDataAvailable( + self->mZstd->mRequest, self->mZstd->mSourceOffset, + reinterpret_cast(outPtr), outBuffer.pos); + if (NS_FAILED(rv)) { + self->mZstd->mStatus = rv; + return rv; + } + self->mZstd->mSourceOffset += inBuffer.pos - last_pos; + last_pos = inBuffer.pos; + output_full = outBuffer.pos == outBuffer.size; + // in the unlikely case that the output buffer was full, loop to + // drain it before processing more input + } while (output_full); + } + *countRead = inBuffer.pos; + return NS_OK; +} + NS_IMETHODIMP nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr, uint64_t aSourceOffset, uint32_t aCount) { @@ -525,6 +689,25 @@ nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr, } } break; + case HTTP_COMPRESS_ZSTD: { + if (!mZstd) { + mZstd = MakeUnique(); + } + + mZstd->mRequest = request; + mZstd->mContext = nullptr; + mZstd->mSourceOffset = aSourceOffset; + + uint32_t countRead; + rv = iStr->ReadSegments(ZstdHandler, this, streamLen, &countRead); + if (NS_SUCCEEDED(rv)) { + rv = mZstd->mStatus; + } + if (NS_FAILED(rv)) { + return rv; + } + } break; + default: nsCOMPtr listener; { @@ -553,6 +736,35 @@ nsresult nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request, uint64_t offset, const char* buffer, uint32_t count) { + LOG(("nsHttpCompressConv %p do_OnDataAvailable mDispatchToMainThread %d", + this, mDispatchToMainThread)); + if (mDispatchToMainThread && !NS_IsMainThread()) { + nsCOMPtr stream; + MOZ_TRY(NS_NewByteInputStream(getter_AddRefs(stream), Span(buffer, count), + nsAssignmentType::NS_ASSIGNMENT_COPY)); + + nsCOMPtr listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + + // This is safe and will always run before OnStopRequest, because + // ChanneleventQueue means that we can't enqueue OnStopRequest until after + // the OMT OnDataAvailable call has completed. So Dispatching here will + // ensure it's in the MainThread event queue before OnStopRequest + nsCOMPtr handler = NS_NewRunnableFunction( + "nsHTTPCompressConv::do_OnDataAvailable", + [request{RefPtr(request)}, stream{std::move(stream)}, + listener{std::move(listener)}, offset, count]() { + LOG(("nsHttpCompressConv Calling OnDataAvailable on Mainthread")); + Unused << listener->OnDataAvailable(request, stream, offset, count); + }); + + mDecodedDataLength += count; + return NS_DispatchToMainThread(handler); + } + if (!mStream) { mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID); NS_ENSURE_STATE(mStream); @@ -725,12 +937,16 @@ uint32_t nsHTTPCompressConv::check_header(nsIInputStream* iStr, NS_IMETHODIMP nsHTTPCompressConv::CheckListenerChain() { + if (XRE_IsContentProcess()) { + // handle decompression OMT always. If the chain needs to be MT, + // we'll determine that in OnStartRequest and dispatch to MT + return NS_OK; + } nsCOMPtr listener; { MutexAutoLock lock(mMutex); listener = do_QueryInterface(mListener); } - if (!listener) { return NS_ERROR_NO_INTERFACE; } @@ -748,6 +964,15 @@ nsHTTPCompressConv::OnDataFinished(nsresult aStatus) { } if (listener) { + if (mDispatchToMainThread && !NS_IsMainThread()) { + nsCOMPtr handler = NS_NewRunnableFunction( + "dispatch", [listener{std::move(listener)}, aStatus]() { + Unused << listener->OnDataFinished(aStatus); + }); + + return NS_DispatchToMainThread(handler); + } + return listener->OnDataFinished(aStatus); } -- cgit v1.2.3