Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / netwerk / streamconv / converters / nsHTTPCompressConv.cpp
blob7c7404f1107e16809541fcf1cdd470b52a62ce6c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsHTTPCompressConv.h"
8 #include "ErrorList.h"
9 #include "nsCOMPtr.h"
10 #include "nsCRT.h"
11 #include "nsError.h"
12 #include "nsIThreadRetargetableStreamListener.h"
13 #include "nsStreamUtils.h"
14 #include "nsStringStream.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsThreadUtils.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/StaticPrefs_network.h"
19 #include "mozilla/Logging.h"
20 #include "nsIForcePendingChannel.h"
21 #include "nsIRequest.h"
22 #include "mozilla/UniquePtrExtensions.h"
23 #include "nsIThreadRetargetableRequest.h"
24 #include "nsIChannel.h"
26 // brotli headers
27 #undef assert
28 #include "assert.h"
29 #include "state.h"
30 #include "brotli/decode.h"
32 #include "zstd/zstd.h"
34 namespace mozilla {
35 namespace net {
37 extern LazyLogModule gHttpLog;
38 #define LOG(args) \
39 MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
41 class BrotliWrapper {
42 public:
43 BrotliWrapper() {
44 BrotliDecoderStateInit(&mState, nullptr, nullptr, nullptr);
46 ~BrotliWrapper() { BrotliDecoderStateCleanup(&mState); }
48 BrotliDecoderState mState{};
49 Atomic<size_t, Relaxed> mTotalOut{0};
50 nsresult mStatus = NS_OK;
51 Atomic<bool, Relaxed> mBrotliStateIsStreamEnd{false};
53 nsIRequest* mRequest{nullptr};
54 nsISupports* mContext{nullptr};
55 uint64_t mSourceOffset{0};
58 class ZstdWrapper {
59 public:
60 ZstdWrapper() {
61 mDStream = ZSTD_createDStream();
62 ZSTD_DCtx_setParameter(mDStream, ZSTD_d_windowLogMax, 23 /*8*1024*1024*/);
64 ~ZstdWrapper() {
65 if (mDStream) {
66 ZSTD_freeDStream(mDStream);
70 UniquePtr<uint8_t[]> mOutBuffer;
71 nsresult mStatus = NS_OK;
72 nsIRequest* mRequest{nullptr};
73 nsISupports* mContext{nullptr};
74 uint64_t mSourceOffset{0};
75 ZSTD_DStream* mDStream{nullptr};
78 // nsISupports implementation
79 NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener,
80 nsIRequestObserver, nsICompressConvStats,
81 nsIThreadRetargetableStreamListener)
83 // nsFTPDirListingConv methods
84 nsHTTPCompressConv::nsHTTPCompressConv() {
85 LOG(("nsHttpCompresssConv %p ctor\n", this));
86 if (NS_IsMainThread()) {
87 mFailUncleanStops =
88 Preferences::GetBool("network.http.enforce-framing.http", false);
89 } else {
90 mFailUncleanStops = false;
94 nsHTTPCompressConv::~nsHTTPCompressConv() {
95 LOG(("nsHttpCompresssConv %p dtor\n", this));
96 if (mInpBuffer) {
97 free(mInpBuffer);
100 if (mOutBuffer) {
101 free(mOutBuffer);
104 // For some reason we are not getting Z_STREAM_END. But this was also seen
105 // for mozilla bug 198133. Need to handle this case.
106 if (mStreamInitialized && !mStreamEnded) {
107 inflateEnd(&d_stream);
111 NS_IMETHODIMP
112 nsHTTPCompressConv::GetDecodedDataLength(uint64_t* aDecodedDataLength) {
113 *aDecodedDataLength = mDecodedDataLength;
114 return NS_OK;
117 NS_IMETHODIMP
118 nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType,
119 nsIStreamListener* aListener,
120 nsISupports* aCtxt) {
121 if (!nsCRT::strncasecmp(aFromType, HTTP_COMPRESS_TYPE,
122 sizeof(HTTP_COMPRESS_TYPE) - 1) ||
123 !nsCRT::strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE,
124 sizeof(HTTP_X_COMPRESS_TYPE) - 1)) {
125 mMode = HTTP_COMPRESS_COMPRESS;
126 } else if (!nsCRT::strncasecmp(aFromType, HTTP_GZIP_TYPE,
127 sizeof(HTTP_GZIP_TYPE) - 1) ||
128 !nsCRT::strncasecmp(aFromType, HTTP_X_GZIP_TYPE,
129 sizeof(HTTP_X_GZIP_TYPE) - 1)) {
130 mMode = HTTP_COMPRESS_GZIP;
131 } else if (!nsCRT::strncasecmp(aFromType, HTTP_DEFLATE_TYPE,
132 sizeof(HTTP_DEFLATE_TYPE) - 1)) {
133 mMode = HTTP_COMPRESS_DEFLATE;
134 } else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_TYPE,
135 sizeof(HTTP_BROTLI_TYPE) - 1)) {
136 mMode = HTTP_COMPRESS_BROTLI;
137 } else if (!nsCRT::strncasecmp(aFromType, HTTP_ZSTD_TYPE,
138 sizeof(HTTP_ZSTD_TYPE) - 1)) {
139 mMode = HTTP_COMPRESS_ZSTD;
140 } else if (!nsCRT::strncasecmp(aFromType, HTTP_ZST_TYPE,
141 sizeof(HTTP_ZST_TYPE) - 1)) {
142 mMode = HTTP_COMPRESS_ZSTD;
144 LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this,
145 aFromType, aToType, (CompressMode)mMode));
147 MutexAutoLock lock(mMutex);
148 // hook ourself up with the receiving listener.
149 mListener = aListener;
151 return NS_OK;
154 NS_IMETHODIMP
155 nsHTTPCompressConv::GetConvertedType(const nsACString& aFromType,
156 nsIChannel* aChannel,
157 nsACString& aToType) {
158 return NS_ERROR_NOT_IMPLEMENTED;
161 NS_IMETHODIMP
162 nsHTTPCompressConv::MaybeRetarget(nsIRequest* request) {
163 MOZ_ASSERT(NS_IsMainThread());
164 nsresult rv;
165 nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(request);
166 if (!req) {
167 return NS_ERROR_NO_INTERFACE;
169 if (!StaticPrefs::network_decompression_off_mainthread()) {
170 return NS_OK;
172 nsCOMPtr<nsISerialEventTarget> target;
173 rv = req->GetDeliveryTarget(getter_AddRefs(target));
174 if (NS_FAILED(rv) || !target || target->IsOnCurrentThread()) {
175 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
176 int64_t length = -1;
177 if (channel) {
178 channel->GetContentLength(&length);
179 // If this fails we'll retarget
181 if (length <= 0 ||
182 length >=
183 StaticPrefs::network_decompression_off_mainthread_min_size()) {
184 LOG(("MaybeRetarget: Retargeting to background thread: Length %" PRId64,
185 length));
186 // No retargetting was performed. Decompress off MainThread,
187 // and dispatch results back to MainThread.
188 // Don't do this if the input is small, if we know the length.
189 // If the length is 0 (unknown), always use OMT.
190 nsCOMPtr<nsISerialEventTarget> backgroundThread;
191 rv = NS_CreateBackgroundTaskQueue("nsHTTPCompressConv",
192 getter_AddRefs(backgroundThread));
193 NS_ENSURE_SUCCESS(rv, rv);
194 rv = req->RetargetDeliveryTo(backgroundThread);
195 NS_ENSURE_SUCCESS(rv, rv);
196 if (NS_SUCCEEDED(rv)) {
197 mDispatchToMainThread = true;
199 } else {
200 LOG(("MaybeRetarget: Not retargeting: Length %" PRId64, length));
202 } else {
203 LOG(("MaybeRetarget: Don't need to retarget"));
206 return NS_OK;
209 NS_IMETHODIMP
210 nsHTTPCompressConv::OnStartRequest(nsIRequest* request) {
211 LOG(("nsHttpCompresssConv %p onstart\n", this));
212 nsCOMPtr<nsIStreamListener> listener;
214 MutexAutoLock lock(mMutex);
215 listener = mListener;
217 nsresult rv = listener->OnStartRequest(request);
218 if (NS_SUCCEEDED(rv)) {
219 if (XRE_IsContentProcess()) {
220 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetlistener =
221 do_QueryInterface(listener);
222 // |nsHTTPCompressConv| should *always* be dispatched off of the main
223 // thread from a content process, even if its listeners don't support it.
225 // If its listener chain does not support being retargeted off of the
226 // main thread, it will be dispatched back to the main thread in
227 // |do_OnDataAvailable| and |OnStopRequest|.
228 if (!retargetlistener ||
229 NS_FAILED(retargetlistener->CheckListenerChain())) {
230 mDispatchToMainThread = true;
234 return rv;
237 NS_IMETHODIMP
238 nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
239 nsresult status = aStatus;
240 // Bug 1886237 : TRRServiceChannel calls OnStopRequest OMT
241 // MOZ_ASSERT(NS_IsMainThread());
242 LOG(("nsHttpCompresssConv %p onstop %" PRIx32 " mDispatchToMainThread %d\n",
243 this, static_cast<uint32_t>(aStatus), mDispatchToMainThread));
245 // Framing integrity is enforced for content-encoding: gzip, but not for
246 // content-encoding: deflate. Note that gzip vs deflate is NOT determined
247 // by content sniffing but only via header.
248 if (!mStreamEnded && NS_SUCCEEDED(status) &&
249 (mFailUncleanStops && (mMode == HTTP_COMPRESS_GZIP))) {
250 // This is not a clean end of gzip stream: the transfer is incomplete.
251 status = NS_ERROR_NET_PARTIAL_TRANSFER;
252 LOG(("nsHttpCompresssConv %p onstop partial gzip\n", this));
254 if (NS_SUCCEEDED(status) && mMode == HTTP_COMPRESS_BROTLI) {
255 nsCOMPtr<nsIForcePendingChannel> fpChannel = do_QueryInterface(request);
256 bool isPending = false;
257 if (request) {
258 request->IsPending(&isPending);
260 if (fpChannel && !isPending) {
261 fpChannel->ForcePending(true);
263 bool allowTruncatedEmpty =
264 StaticPrefs::network_compress_allow_truncated_empty_brotli();
265 if (mBrotli && ((allowTruncatedEmpty && NS_FAILED(mBrotli->mStatus)) ||
266 (!allowTruncatedEmpty && mBrotli->mTotalOut == 0 &&
267 !mBrotli->mBrotliStateIsStreamEnd))) {
268 status = NS_ERROR_INVALID_CONTENT_ENCODING;
270 LOG(("nsHttpCompresssConv %p onstop brotlihandler rv %" PRIx32 "\n", this,
271 static_cast<uint32_t>(status)));
272 if (fpChannel && !isPending) {
273 fpChannel->ForcePending(false);
277 nsCOMPtr<nsIStreamListener> listener;
279 MutexAutoLock lock(mMutex);
280 listener = mListener;
283 return listener->OnStopRequest(request, status);
286 /* static */
287 nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream,
288 void* closure, const char* dataIn,
289 uint32_t, uint32_t aAvail,
290 uint32_t* countRead) {
291 MOZ_ASSERT(stream);
292 nsHTTPCompressConv* self = static_cast<nsHTTPCompressConv*>(closure);
293 *countRead = 0;
295 const size_t kOutSize = 128 * 1024; // just a chunk size, we call in a loop
296 uint8_t* outPtr;
297 size_t outSize;
298 size_t avail = aAvail;
299 BrotliDecoderResult res;
301 if (!self->mBrotli) {
302 *countRead = aAvail;
303 return NS_OK;
306 auto outBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize);
307 if (outBuffer == nullptr) {
308 self->mBrotli->mStatus = NS_ERROR_OUT_OF_MEMORY;
309 return self->mBrotli->mStatus;
311 do {
312 outSize = kOutSize;
313 outPtr = outBuffer.get();
315 // brotli api is documented in brotli/dec/decode.h and brotli/dec/decode.c
316 LOG(("nsHttpCompresssConv %p brotlihandler decompress %zu\n", self, avail));
317 size_t totalOut = self->mBrotli->mTotalOut;
318 res = ::BrotliDecoderDecompressStream(
319 &self->mBrotli->mState, &avail,
320 reinterpret_cast<const unsigned char**>(&dataIn), &outSize, &outPtr,
321 &totalOut);
322 outSize = kOutSize - outSize;
323 self->mBrotli->mTotalOut = totalOut;
324 self->mBrotli->mBrotliStateIsStreamEnd =
325 BrotliDecoderIsFinished(&self->mBrotli->mState);
326 LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%" PRIx32
327 " out=%zu\n",
328 self, static_cast<uint32_t>(res), outSize));
330 if (res == BROTLI_DECODER_RESULT_ERROR) {
331 LOG(("nsHttpCompressConv %p marking invalid encoding", self));
332 self->mBrotli->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING;
333 return self->mBrotli->mStatus;
336 // in 'the current implementation' brotli must consume everything before
337 // asking for more input
338 if (res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
339 MOZ_ASSERT(!avail);
340 if (avail) {
341 LOG(("nsHttpCompressConv %p did not consume all input", self));
342 self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
343 return self->mBrotli->mStatus;
347 auto callOnDataAvailable = [&](uint64_t aSourceOffset, const char* aBuffer,
348 uint32_t aCount) {
349 nsresult rv = self->do_OnDataAvailable(self->mBrotli->mRequest,
350 aSourceOffset, aBuffer, aCount);
351 LOG(("nsHttpCompressConv %p BrotliHandler ODA rv=%" PRIx32, self,
352 static_cast<uint32_t>(rv)));
353 if (NS_FAILED(rv)) {
354 self->mBrotli->mStatus = rv;
357 return rv;
360 if (outSize > 0) {
361 if (NS_FAILED(callOnDataAvailable(
362 self->mBrotli->mSourceOffset,
363 reinterpret_cast<const char*>(outBuffer.get()), outSize))) {
364 return self->mBrotli->mStatus;
366 self->mBrotli->mSourceOffset += outSize;
369 // See bug 1759745. If the decoder has more output data, take it.
370 while (::BrotliDecoderHasMoreOutput(&self->mBrotli->mState)) {
371 outSize = kOutSize;
372 const uint8_t* buffer =
373 ::BrotliDecoderTakeOutput(&self->mBrotli->mState, &outSize);
374 if (NS_FAILED(callOnDataAvailable(self->mBrotli->mSourceOffset,
375 reinterpret_cast<const char*>(buffer),
376 outSize))) {
377 return self->mBrotli->mStatus;
379 self->mBrotli->mSourceOffset += outSize;
382 if (res == BROTLI_DECODER_RESULT_SUCCESS ||
383 res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
384 *countRead = aAvail;
385 return NS_OK;
387 MOZ_ASSERT(res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
388 } while (res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
390 self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
391 return self->mBrotli->mStatus;
394 /* static */
395 nsresult nsHTTPCompressConv::ZstdHandler(nsIInputStream* stream, void* closure,
396 const char* dataIn, uint32_t,
397 uint32_t aAvail, uint32_t* countRead) {
398 MOZ_ASSERT(stream);
399 nsHTTPCompressConv* self = static_cast<nsHTTPCompressConv*>(closure);
400 *countRead = 0;
402 const size_t kOutSize = ZSTD_DStreamOutSize(); // normally 128K
403 uint8_t* outPtr;
404 size_t avail = aAvail;
406 // Stop decompressing after an error
407 if (self->mZstd->mStatus != NS_OK) {
408 *countRead = aAvail;
409 return NS_OK;
412 if (!self->mZstd->mOutBuffer) {
413 self->mZstd->mOutBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize);
414 if (!self->mZstd->mOutBuffer) {
415 self->mZstd->mStatus = NS_ERROR_OUT_OF_MEMORY;
416 return self->mZstd->mStatus;
419 ZSTD_inBuffer inBuffer = {.src = dataIn, .size = aAvail, .pos = 0};
420 uint32_t last_pos = 0;
421 while (inBuffer.pos < inBuffer.size) {
422 outPtr = self->mZstd->mOutBuffer.get();
424 LOG(("nsHttpCompresssConv %p zstdhandler decompress %zu\n", self, avail));
425 // Use ZSTD_(de)compressStream to (de)compress the input buffer into the
426 // output buffer, and fill aReadCount with the number of bytes consumed.
427 ZSTD_outBuffer outBuffer{.dst = outPtr, .size = kOutSize};
428 size_t result;
429 bool output_full;
430 do {
431 outBuffer.pos = 0;
432 result =
433 ZSTD_decompressStream(self->mZstd->mDStream, &outBuffer, &inBuffer);
435 // If we errored when writing, flag this and abort writing.
436 if (ZSTD_isError(result)) {
437 self->mZstd->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING;
438 return self->mZstd->mStatus;
441 nsresult rv = self->do_OnDataAvailable(
442 self->mZstd->mRequest, self->mZstd->mSourceOffset,
443 reinterpret_cast<const char*>(outPtr), outBuffer.pos);
444 if (NS_FAILED(rv)) {
445 self->mZstd->mStatus = rv;
446 return rv;
448 self->mZstd->mSourceOffset += inBuffer.pos - last_pos;
449 last_pos = inBuffer.pos;
450 output_full = outBuffer.pos == outBuffer.size;
451 // in the unlikely case that the output buffer was full, loop to
452 // drain it before processing more input
453 } while (output_full);
455 *countRead = inBuffer.pos;
456 return NS_OK;
459 NS_IMETHODIMP
460 nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr,
461 uint64_t aSourceOffset, uint32_t aCount) {
462 nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING;
463 uint32_t streamLen = aCount;
464 LOG(("nsHttpCompressConv %p OnDataAvailable aSourceOffset:%" PRIu64
465 " count:%u",
466 this, aSourceOffset, aCount));
468 if (streamLen == 0) {
469 NS_ERROR("count of zero passed to OnDataAvailable");
470 return NS_ERROR_UNEXPECTED;
473 if (mStreamEnded) {
474 // Hmm... this may just indicate that the data stream is done and that
475 // what's left is either metadata or padding of some sort.... throwing
476 // it out is probably the safe thing to do.
477 uint32_t n;
478 return iStr->ReadSegments(NS_DiscardSegment, nullptr, streamLen, &n);
481 switch (mMode) {
482 case HTTP_COMPRESS_GZIP:
483 streamLen = check_header(iStr, streamLen, &rv);
485 if (rv != NS_OK) {
486 return rv;
489 if (streamLen == 0) {
490 return NS_OK;
493 [[fallthrough]];
495 case HTTP_COMPRESS_DEFLATE:
497 if (mInpBuffer != nullptr && streamLen > mInpBufferLen) {
498 unsigned char* originalInpBuffer = mInpBuffer;
499 if (!(mInpBuffer = (unsigned char*)realloc(
500 originalInpBuffer, mInpBufferLen = streamLen))) {
501 free(originalInpBuffer);
504 if (mOutBufferLen < streamLen * 2) {
505 unsigned char* originalOutBuffer = mOutBuffer;
506 if (!(mOutBuffer = (unsigned char*)realloc(
507 mOutBuffer, mOutBufferLen = streamLen * 3))) {
508 free(originalOutBuffer);
512 if (mInpBuffer == nullptr || mOutBuffer == nullptr) {
513 return NS_ERROR_OUT_OF_MEMORY;
517 if (mInpBuffer == nullptr) {
518 mInpBuffer = (unsigned char*)malloc(mInpBufferLen = streamLen);
521 if (mOutBuffer == nullptr) {
522 mOutBuffer = (unsigned char*)malloc(mOutBufferLen = streamLen * 3);
525 if (mInpBuffer == nullptr || mOutBuffer == nullptr) {
526 return NS_ERROR_OUT_OF_MEMORY;
529 uint32_t unused;
530 iStr->Read((char*)mInpBuffer, streamLen, &unused);
532 if (mMode == HTTP_COMPRESS_DEFLATE) {
533 if (!mStreamInitialized) {
534 memset(&d_stream, 0, sizeof(d_stream));
536 if (inflateInit(&d_stream) != Z_OK) {
537 return NS_ERROR_FAILURE;
540 mStreamInitialized = true;
542 d_stream.next_in = mInpBuffer;
543 d_stream.avail_in = (uInt)streamLen;
545 mDummyStreamInitialised = false;
546 for (;;) {
547 d_stream.next_out = mOutBuffer;
548 d_stream.avail_out = (uInt)mOutBufferLen;
550 int code = inflate(&d_stream, Z_NO_FLUSH);
551 unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
553 if (code == Z_STREAM_END) {
554 if (bytesWritten) {
555 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
556 bytesWritten);
557 if (NS_FAILED(rv)) {
558 return rv;
562 inflateEnd(&d_stream);
563 mStreamEnded = true;
564 break;
566 if (code == Z_OK) {
567 if (bytesWritten) {
568 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
569 bytesWritten);
570 if (NS_FAILED(rv)) {
571 return rv;
574 } else if (code == Z_BUF_ERROR) {
575 if (bytesWritten) {
576 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
577 bytesWritten);
578 if (NS_FAILED(rv)) {
579 return rv;
582 break;
583 } else if (code == Z_DATA_ERROR) {
584 // some servers (notably Apache with mod_deflate) don't generate
585 // zlib headers insert a dummy header and try again
586 static char dummy_head[2] = {
587 0x8 + 0x7 * 0x10,
588 (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
590 inflateReset(&d_stream);
591 d_stream.next_in = (Bytef*)dummy_head;
592 d_stream.avail_in = sizeof(dummy_head);
594 code = inflate(&d_stream, Z_NO_FLUSH);
595 if (code != Z_OK) {
596 return NS_ERROR_FAILURE;
599 // stop an endless loop caused by non-deflate data being labelled as
600 // deflate
601 if (mDummyStreamInitialised) {
602 NS_WARNING(
603 "endless loop detected"
604 " - invalid deflate");
605 return NS_ERROR_INVALID_CONTENT_ENCODING;
607 mDummyStreamInitialised = true;
608 // reset stream pointers to our original data
609 d_stream.next_in = mInpBuffer;
610 d_stream.avail_in = (uInt)streamLen;
611 } else {
612 return NS_ERROR_INVALID_CONTENT_ENCODING;
614 } /* for */
615 } else {
616 if (!mStreamInitialized) {
617 memset(&d_stream, 0, sizeof(d_stream));
619 if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK) {
620 return NS_ERROR_FAILURE;
623 mStreamInitialized = true;
626 d_stream.next_in = mInpBuffer;
627 d_stream.avail_in = (uInt)streamLen;
629 for (;;) {
630 d_stream.next_out = mOutBuffer;
631 d_stream.avail_out = (uInt)mOutBufferLen;
633 int code = inflate(&d_stream, Z_NO_FLUSH);
634 unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
636 if (code == Z_STREAM_END) {
637 if (bytesWritten) {
638 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
639 bytesWritten);
640 if (NS_FAILED(rv)) {
641 return rv;
645 inflateEnd(&d_stream);
646 mStreamEnded = true;
647 break;
649 if (code == Z_OK) {
650 if (bytesWritten) {
651 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
652 bytesWritten);
653 if (NS_FAILED(rv)) {
654 return rv;
657 } else if (code == Z_BUF_ERROR) {
658 if (bytesWritten) {
659 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
660 bytesWritten);
661 if (NS_FAILED(rv)) {
662 return rv;
665 break;
666 } else {
667 return NS_ERROR_INVALID_CONTENT_ENCODING;
669 } /* for */
670 } /* gzip */
671 break;
673 case HTTP_COMPRESS_BROTLI: {
674 if (!mBrotli) {
675 mBrotli = MakeUnique<BrotliWrapper>();
678 mBrotli->mRequest = request;
679 mBrotli->mContext = nullptr;
680 mBrotli->mSourceOffset = aSourceOffset;
682 uint32_t countRead;
683 rv = iStr->ReadSegments(BrotliHandler, this, streamLen, &countRead);
684 if (NS_SUCCEEDED(rv)) {
685 rv = mBrotli->mStatus;
687 if (NS_FAILED(rv)) {
688 return rv;
690 } break;
692 case HTTP_COMPRESS_ZSTD: {
693 if (!mZstd) {
694 mZstd = MakeUnique<ZstdWrapper>();
697 mZstd->mRequest = request;
698 mZstd->mContext = nullptr;
699 mZstd->mSourceOffset = aSourceOffset;
701 uint32_t countRead;
702 rv = iStr->ReadSegments(ZstdHandler, this, streamLen, &countRead);
703 if (NS_SUCCEEDED(rv)) {
704 rv = mZstd->mStatus;
706 if (NS_FAILED(rv)) {
707 return rv;
709 } break;
711 default:
712 nsCOMPtr<nsIStreamListener> listener;
714 MutexAutoLock lock(mMutex);
715 listener = mListener;
717 rv = listener->OnDataAvailable(request, iStr, aSourceOffset, aCount);
718 if (NS_FAILED(rv)) {
719 return rv;
721 } /* switch */
723 return NS_OK;
724 } /* OnDataAvailable */
726 // XXX/ruslan: need to implement this too
728 NS_IMETHODIMP
729 nsHTTPCompressConv::Convert(nsIInputStream* aFromStream, const char* aFromType,
730 const char* aToType, nsISupports* aCtxt,
731 nsIInputStream** _retval) {
732 return NS_ERROR_NOT_IMPLEMENTED;
735 nsresult nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request,
736 uint64_t offset,
737 const char* buffer,
738 uint32_t count) {
739 LOG(("nsHttpCompressConv %p do_OnDataAvailable mDispatchToMainThread %d",
740 this, mDispatchToMainThread));
741 if (mDispatchToMainThread && !NS_IsMainThread()) {
742 nsCOMPtr<nsIInputStream> stream;
743 MOZ_TRY(NS_NewByteInputStream(getter_AddRefs(stream), Span(buffer, count),
744 nsAssignmentType::NS_ASSIGNMENT_COPY));
746 nsCOMPtr<nsIStreamListener> listener;
748 MutexAutoLock lock(mMutex);
749 listener = mListener;
752 // This is safe and will always run before OnStopRequest, because
753 // ChanneleventQueue means that we can't enqueue OnStopRequest until after
754 // the OMT OnDataAvailable call has completed. So Dispatching here will
755 // ensure it's in the MainThread event queue before OnStopRequest
756 nsCOMPtr<nsIRunnable> handler = NS_NewRunnableFunction(
757 "nsHTTPCompressConv::do_OnDataAvailable",
758 [request{RefPtr<nsIRequest>(request)}, stream{std::move(stream)},
759 listener{std::move(listener)}, offset, count]() {
760 LOG(("nsHttpCompressConv Calling OnDataAvailable on Mainthread"));
761 Unused << listener->OnDataAvailable(request, stream, offset, count);
764 mDecodedDataLength += count;
765 return NS_DispatchToMainThread(handler);
768 if (!mStream) {
769 mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
770 NS_ENSURE_STATE(mStream);
773 mStream->ShareData(buffer, count);
775 nsCOMPtr<nsIStreamListener> listener;
777 MutexAutoLock lock(mMutex);
778 listener = mListener;
780 LOG(("nsHTTPCompressConv::do_OnDataAvailable req:%p offset: offset:%" PRIu64
781 "count:%u",
782 request, offset, count));
783 nsresult rv = listener->OnDataAvailable(request, mStream, offset, count);
785 // Make sure the stream no longer references |buffer| in case our listener
786 // is crazy enough to try to read from |mStream| after ODA.
787 mStream->ShareData("", 0);
788 mDecodedDataLength += count;
790 return rv;
793 #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
794 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
795 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
796 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
797 #define COMMENT 0x10 /* bit 4 set: file comment present */
798 #define RESERVED 0xE0 /* bits 5..7: reserved */
800 static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
802 uint32_t nsHTTPCompressConv::check_header(nsIInputStream* iStr,
803 uint32_t streamLen, nsresult* rs) {
804 enum {
805 GZIP_INIT = 0,
806 GZIP_OS,
807 GZIP_EXTRA0,
808 GZIP_EXTRA1,
809 GZIP_EXTRA2,
810 GZIP_ORIG,
811 GZIP_COMMENT,
812 GZIP_CRC
814 char c;
816 *rs = NS_OK;
818 if (mCheckHeaderDone) {
819 return streamLen;
822 while (streamLen) {
823 switch (hMode) {
824 case GZIP_INIT:
825 uint32_t unused;
826 iStr->Read(&c, 1, &unused);
827 streamLen--;
829 if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0]) {
830 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
831 return 0;
834 if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1]) {
835 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
836 return 0;
839 if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED) {
840 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
841 return 0;
844 mSkipCount++;
845 if (mSkipCount == 4) {
846 mFlags = (unsigned)c & 0377;
847 if (mFlags & RESERVED) {
848 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
849 return 0;
851 hMode = GZIP_OS;
852 mSkipCount = 0;
854 break;
856 case GZIP_OS:
857 iStr->Read(&c, 1, &unused);
858 streamLen--;
859 mSkipCount++;
861 if (mSkipCount == 6) {
862 hMode = GZIP_EXTRA0;
864 break;
866 case GZIP_EXTRA0:
867 if (mFlags & EXTRA_FIELD) {
868 iStr->Read(&c, 1, &unused);
869 streamLen--;
870 mLen = (uInt)c & 0377;
871 hMode = GZIP_EXTRA1;
872 } else {
873 hMode = GZIP_ORIG;
875 break;
877 case GZIP_EXTRA1:
878 iStr->Read(&c, 1, &unused);
879 streamLen--;
880 mLen |= ((uInt)c & 0377) << 8;
881 mSkipCount = 0;
882 hMode = GZIP_EXTRA2;
883 break;
885 case GZIP_EXTRA2:
886 if (mSkipCount == mLen) {
887 hMode = GZIP_ORIG;
888 } else {
889 iStr->Read(&c, 1, &unused);
890 streamLen--;
891 mSkipCount++;
893 break;
895 case GZIP_ORIG:
896 if (mFlags & ORIG_NAME) {
897 iStr->Read(&c, 1, &unused);
898 streamLen--;
899 if (c == 0) hMode = GZIP_COMMENT;
900 } else {
901 hMode = GZIP_COMMENT;
903 break;
905 case GZIP_COMMENT:
906 if (mFlags & COMMENT) {
907 iStr->Read(&c, 1, &unused);
908 streamLen--;
909 if (c == 0) {
910 hMode = GZIP_CRC;
911 mSkipCount = 0;
913 } else {
914 hMode = GZIP_CRC;
915 mSkipCount = 0;
917 break;
919 case GZIP_CRC:
920 if (mFlags & HEAD_CRC) {
921 iStr->Read(&c, 1, &unused);
922 streamLen--;
923 mSkipCount++;
924 if (mSkipCount == 2) {
925 mCheckHeaderDone = true;
926 return streamLen;
928 } else {
929 mCheckHeaderDone = true;
930 return streamLen;
932 break;
935 return streamLen;
938 NS_IMETHODIMP
939 nsHTTPCompressConv::CheckListenerChain() {
940 if (XRE_IsContentProcess()) {
941 // handle decompression OMT always. If the chain needs to be MT,
942 // we'll determine that in OnStartRequest and dispatch to MT
943 return NS_OK;
945 nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
947 MutexAutoLock lock(mMutex);
948 listener = do_QueryInterface(mListener);
950 if (!listener) {
951 return NS_ERROR_NO_INTERFACE;
954 return listener->CheckListenerChain();
957 NS_IMETHODIMP
958 nsHTTPCompressConv::OnDataFinished(nsresult aStatus) {
959 nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
962 MutexAutoLock lock(mMutex);
963 listener = do_QueryInterface(mListener);
966 if (listener) {
967 if (mDispatchToMainThread && !NS_IsMainThread()) {
968 nsCOMPtr<nsIRunnable> handler = NS_NewRunnableFunction(
969 "dispatch", [listener{std::move(listener)}, aStatus]() {
970 Unused << listener->OnDataFinished(aStatus);
973 return NS_DispatchToMainThread(handler);
976 return listener->OnDataFinished(aStatus);
979 return NS_OK;
982 } // namespace net
983 } // namespace mozilla
985 nsresult NS_NewHTTPCompressConv(
986 mozilla::net::nsHTTPCompressConv** aHTTPCompressConv) {
987 MOZ_ASSERT(aHTTPCompressConv != nullptr, "null ptr");
988 if (!aHTTPCompressConv) {
989 return NS_ERROR_NULL_POINTER;
992 RefPtr<mozilla::net::nsHTTPCompressConv> outVal =
993 new mozilla::net::nsHTTPCompressConv();
994 if (!outVal) {
995 return NS_ERROR_OUT_OF_MEMORY;
997 outVal.forget(aHTTPCompressConv);
998 return NS_OK;