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"
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"
30 #include "brotli/decode.h"
32 #include "zstd/zstd.h"
37 extern LazyLogModule gHttpLog
;
39 MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
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};
61 mDStream
= ZSTD_createDStream();
62 ZSTD_DCtx_setParameter(mDStream
, ZSTD_d_windowLogMax
, 23 /*8*1024*1024*/);
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()) {
88 Preferences::GetBool("network.http.enforce-framing.http", false);
90 mFailUncleanStops
= false;
94 nsHTTPCompressConv::~nsHTTPCompressConv() {
95 LOG(("nsHttpCompresssConv %p dtor\n", this));
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
);
112 nsHTTPCompressConv::GetDecodedDataLength(uint64_t* aDecodedDataLength
) {
113 *aDecodedDataLength
= mDecodedDataLength
;
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
;
155 nsHTTPCompressConv::GetConvertedType(const nsACString
& aFromType
,
156 nsIChannel
* aChannel
,
157 nsACString
& aToType
) {
158 return NS_ERROR_NOT_IMPLEMENTED
;
162 nsHTTPCompressConv::MaybeRetarget(nsIRequest
* request
) {
163 MOZ_ASSERT(NS_IsMainThread());
165 nsCOMPtr
<nsIThreadRetargetableRequest
> req
= do_QueryInterface(request
);
167 return NS_ERROR_NO_INTERFACE
;
169 if (!StaticPrefs::network_decompression_off_mainthread()) {
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
));
178 channel
->GetContentLength(&length
);
179 // If this fails we'll retarget
183 StaticPrefs::network_decompression_off_mainthread_min_size()) {
184 LOG(("MaybeRetarget: Retargeting to background thread: Length %" PRId64
,
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;
200 LOG(("MaybeRetarget: Not retargeting: Length %" PRId64
, length
));
203 LOG(("MaybeRetarget: Don't need to retarget"));
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;
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;
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
);
287 nsresult
nsHTTPCompressConv::BrotliHandler(nsIInputStream
* stream
,
288 void* closure
, const char* dataIn
,
289 uint32_t, uint32_t aAvail
,
290 uint32_t* countRead
) {
292 nsHTTPCompressConv
* self
= static_cast<nsHTTPCompressConv
*>(closure
);
295 const size_t kOutSize
= 128 * 1024; // just a chunk size, we call in a loop
298 size_t avail
= aAvail
;
299 BrotliDecoderResult res
;
301 if (!self
->mBrotli
) {
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
;
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
,
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
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
) {
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
,
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
)));
354 self
->mBrotli
->mStatus
= rv
;
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
)) {
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
),
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
) {
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
;
395 nsresult
nsHTTPCompressConv::ZstdHandler(nsIInputStream
* stream
, void* closure
,
396 const char* dataIn
, uint32_t,
397 uint32_t aAvail
, uint32_t* countRead
) {
399 nsHTTPCompressConv
* self
= static_cast<nsHTTPCompressConv
*>(closure
);
402 const size_t kOutSize
= ZSTD_DStreamOutSize(); // normally 128K
404 size_t avail
= aAvail
;
406 // Stop decompressing after an error
407 if (self
->mZstd
->mStatus
!= 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
};
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
);
445 self
->mZstd
->mStatus
= 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
;
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
466 this, aSourceOffset
, aCount
));
468 if (streamLen
== 0) {
469 NS_ERROR("count of zero passed to OnDataAvailable");
470 return NS_ERROR_UNEXPECTED
;
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.
478 return iStr
->ReadSegments(NS_DiscardSegment
, nullptr, streamLen
, &n
);
482 case HTTP_COMPRESS_GZIP
:
483 streamLen
= check_header(iStr
, streamLen
, &rv
);
489 if (streamLen
== 0) {
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
;
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;
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
) {
555 rv
= do_OnDataAvailable(request
, aSourceOffset
, (char*)mOutBuffer
,
562 inflateEnd(&d_stream
);
568 rv
= do_OnDataAvailable(request
, aSourceOffset
, (char*)mOutBuffer
,
574 } else if (code
== Z_BUF_ERROR
) {
576 rv
= do_OnDataAvailable(request
, aSourceOffset
, (char*)mOutBuffer
,
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] = {
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
);
596 return NS_ERROR_FAILURE
;
599 // stop an endless loop caused by non-deflate data being labelled as
601 if (mDummyStreamInitialised
) {
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
;
612 return NS_ERROR_INVALID_CONTENT_ENCODING
;
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
;
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
) {
638 rv
= do_OnDataAvailable(request
, aSourceOffset
, (char*)mOutBuffer
,
645 inflateEnd(&d_stream
);
651 rv
= do_OnDataAvailable(request
, aSourceOffset
, (char*)mOutBuffer
,
657 } else if (code
== Z_BUF_ERROR
) {
659 rv
= do_OnDataAvailable(request
, aSourceOffset
, (char*)mOutBuffer
,
667 return NS_ERROR_INVALID_CONTENT_ENCODING
;
673 case HTTP_COMPRESS_BROTLI
: {
675 mBrotli
= MakeUnique
<BrotliWrapper
>();
678 mBrotli
->mRequest
= request
;
679 mBrotli
->mContext
= nullptr;
680 mBrotli
->mSourceOffset
= aSourceOffset
;
683 rv
= iStr
->ReadSegments(BrotliHandler
, this, streamLen
, &countRead
);
684 if (NS_SUCCEEDED(rv
)) {
685 rv
= mBrotli
->mStatus
;
692 case HTTP_COMPRESS_ZSTD
: {
694 mZstd
= MakeUnique
<ZstdWrapper
>();
697 mZstd
->mRequest
= request
;
698 mZstd
->mContext
= nullptr;
699 mZstd
->mSourceOffset
= aSourceOffset
;
702 rv
= iStr
->ReadSegments(ZstdHandler
, this, streamLen
, &countRead
);
703 if (NS_SUCCEEDED(rv
)) {
712 nsCOMPtr
<nsIStreamListener
> listener
;
714 MutexAutoLock
lock(mMutex
);
715 listener
= mListener
;
717 rv
= listener
->OnDataAvailable(request
, iStr
, aSourceOffset
, aCount
);
724 } /* OnDataAvailable */
726 // XXX/ruslan: need to implement this too
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
,
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
);
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
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
;
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
) {
818 if (mCheckHeaderDone
) {
826 iStr
->Read(&c
, 1, &unused
);
829 if (mSkipCount
== 0 && ((unsigned)c
& 0377) != gz_magic
[0]) {
830 *rs
= NS_ERROR_INVALID_CONTENT_ENCODING
;
834 if (mSkipCount
== 1 && ((unsigned)c
& 0377) != gz_magic
[1]) {
835 *rs
= NS_ERROR_INVALID_CONTENT_ENCODING
;
839 if (mSkipCount
== 2 && ((unsigned)c
& 0377) != Z_DEFLATED
) {
840 *rs
= NS_ERROR_INVALID_CONTENT_ENCODING
;
845 if (mSkipCount
== 4) {
846 mFlags
= (unsigned)c
& 0377;
847 if (mFlags
& RESERVED
) {
848 *rs
= NS_ERROR_INVALID_CONTENT_ENCODING
;
857 iStr
->Read(&c
, 1, &unused
);
861 if (mSkipCount
== 6) {
867 if (mFlags
& EXTRA_FIELD
) {
868 iStr
->Read(&c
, 1, &unused
);
870 mLen
= (uInt
)c
& 0377;
878 iStr
->Read(&c
, 1, &unused
);
880 mLen
|= ((uInt
)c
& 0377) << 8;
886 if (mSkipCount
== mLen
) {
889 iStr
->Read(&c
, 1, &unused
);
896 if (mFlags
& ORIG_NAME
) {
897 iStr
->Read(&c
, 1, &unused
);
899 if (c
== 0) hMode
= GZIP_COMMENT
;
901 hMode
= GZIP_COMMENT
;
906 if (mFlags
& COMMENT
) {
907 iStr
->Read(&c
, 1, &unused
);
920 if (mFlags
& HEAD_CRC
) {
921 iStr
->Read(&c
, 1, &unused
);
924 if (mSkipCount
== 2) {
925 mCheckHeaderDone
= true;
929 mCheckHeaderDone
= true;
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
945 nsCOMPtr
<nsIThreadRetargetableStreamListener
> listener
;
947 MutexAutoLock
lock(mMutex
);
948 listener
= do_QueryInterface(mListener
);
951 return NS_ERROR_NO_INTERFACE
;
954 return listener
->CheckListenerChain();
958 nsHTTPCompressConv::OnDataFinished(nsresult aStatus
) {
959 nsCOMPtr
<nsIThreadRetargetableStreamListener
> listener
;
962 MutexAutoLock
lock(mMutex
);
963 listener
= do_QueryInterface(mListener
);
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
);
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();
995 return NS_ERROR_OUT_OF_MEMORY
;
997 outVal
.forget(aHTTPCompressConv
);