Backed out changeset 4191b252db9b (bug 1886734) for causing build bustages @netwerk...
[gecko.git] / netwerk / streamconv / converters / nsHTTPCompressConv.cpp
blobaad23b52d429a7c7c6eae7ff2ee71e3536aee398
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"
25 // brotli headers
26 #undef assert
27 #include "assert.h"
28 #include "state.h"
29 #include "brotli/decode.h"
31 namespace mozilla {
32 namespace net {
34 extern LazyLogModule gHttpLog;
35 #define LOG(args) \
36 MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
38 class BrotliWrapper {
39 public:
40 BrotliWrapper() {
41 BrotliDecoderStateInit(&mState, nullptr, nullptr, nullptr);
43 ~BrotliWrapper() { BrotliDecoderStateCleanup(&mState); }
45 BrotliDecoderState mState{};
46 Atomic<size_t, Relaxed> mTotalOut{0};
47 nsresult mStatus = NS_OK;
48 Atomic<bool, Relaxed> mBrotliStateIsStreamEnd{false};
50 nsIRequest* mRequest{nullptr};
51 nsISupports* mContext{nullptr};
52 uint64_t mSourceOffset{0};
55 // nsISupports implementation
56 NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener,
57 nsIRequestObserver, nsICompressConvStats,
58 nsIThreadRetargetableStreamListener)
60 // nsFTPDirListingConv methods
61 nsHTTPCompressConv::nsHTTPCompressConv() {
62 LOG(("nsHttpCompresssConv %p ctor\n", this));
63 if (NS_IsMainThread()) {
64 mFailUncleanStops =
65 Preferences::GetBool("network.http.enforce-framing.http", false);
66 } else {
67 mFailUncleanStops = false;
71 nsHTTPCompressConv::~nsHTTPCompressConv() {
72 LOG(("nsHttpCompresssConv %p dtor\n", this));
73 if (mInpBuffer) {
74 free(mInpBuffer);
77 if (mOutBuffer) {
78 free(mOutBuffer);
81 // For some reason we are not getting Z_STREAM_END. But this was also seen
82 // for mozilla bug 198133. Need to handle this case.
83 if (mStreamInitialized && !mStreamEnded) {
84 inflateEnd(&d_stream);
88 NS_IMETHODIMP
89 nsHTTPCompressConv::GetDecodedDataLength(uint64_t* aDecodedDataLength) {
90 *aDecodedDataLength = mDecodedDataLength;
91 return NS_OK;
94 NS_IMETHODIMP
95 nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType,
96 nsIStreamListener* aListener,
97 nsISupports* aCtxt) {
98 if (!nsCRT::strncasecmp(aFromType, HTTP_COMPRESS_TYPE,
99 sizeof(HTTP_COMPRESS_TYPE) - 1) ||
100 !nsCRT::strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE,
101 sizeof(HTTP_X_COMPRESS_TYPE) - 1)) {
102 mMode = HTTP_COMPRESS_COMPRESS;
103 } else if (!nsCRT::strncasecmp(aFromType, HTTP_GZIP_TYPE,
104 sizeof(HTTP_GZIP_TYPE) - 1) ||
105 !nsCRT::strncasecmp(aFromType, HTTP_X_GZIP_TYPE,
106 sizeof(HTTP_X_GZIP_TYPE) - 1)) {
107 mMode = HTTP_COMPRESS_GZIP;
108 } else if (!nsCRT::strncasecmp(aFromType, HTTP_DEFLATE_TYPE,
109 sizeof(HTTP_DEFLATE_TYPE) - 1)) {
110 mMode = HTTP_COMPRESS_DEFLATE;
111 } else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_TYPE,
112 sizeof(HTTP_BROTLI_TYPE) - 1)) {
113 mMode = HTTP_COMPRESS_BROTLI;
115 LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this,
116 aFromType, aToType, (CompressMode)mMode));
118 MutexAutoLock lock(mMutex);
119 // hook ourself up with the receiving listener.
120 mListener = aListener;
122 return NS_OK;
125 NS_IMETHODIMP
126 nsHTTPCompressConv::GetConvertedType(const nsACString& aFromType,
127 nsIChannel* aChannel,
128 nsACString& aToType) {
129 return NS_ERROR_NOT_IMPLEMENTED;
132 NS_IMETHODIMP
133 nsHTTPCompressConv::MaybeRetarget(nsIRequest* request) {
134 MOZ_ASSERT(NS_IsMainThread());
135 nsresult rv;
136 nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(request);
137 if (!req) {
138 return NS_ERROR_NO_INTERFACE;
140 if (!StaticPrefs::network_decompression_off_mainthread()) {
141 return NS_OK;
143 nsCOMPtr<nsISerialEventTarget> target;
144 rv = req->GetDeliveryTarget(getter_AddRefs(target));
145 if (NS_FAILED(rv) || !target || target->IsOnCurrentThread()) {
146 // No retargetting was performed. Decompress off MainThread,
147 // and dispatch results back to MainThread
148 nsCOMPtr<nsISerialEventTarget> backgroundThread;
149 rv = NS_CreateBackgroundTaskQueue("nsHTTPCompressConv",
150 getter_AddRefs(backgroundThread));
151 NS_ENSURE_SUCCESS(rv, rv);
152 rv = req->RetargetDeliveryTo(backgroundThread);
153 NS_ENSURE_SUCCESS(rv, rv);
154 if (NS_SUCCEEDED(rv)) {
155 mDispatchToMainThread = true;
159 return NS_OK;
162 NS_IMETHODIMP
163 nsHTTPCompressConv::OnStartRequest(nsIRequest* request) {
164 LOG(("nsHttpCompresssConv %p onstart\n", this));
165 nsCOMPtr<nsIStreamListener> listener;
167 MutexAutoLock lock(mMutex);
168 listener = mListener;
170 nsresult rv = listener->OnStartRequest(request);
171 if (NS_SUCCEEDED(rv)) {
172 if (XRE_IsContentProcess()) {
173 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetlistener =
174 do_QueryInterface(listener);
175 // |nsHTTPCompressConv| should *always* be dispatched off of the main
176 // thread from a content process, even if its listeners don't support it.
178 // If its listener chain does not support being retargeted off of the
179 // main thread, it will be dispatched back to the main thread in
180 // |do_OnDataAvailable| and |OnStopRequest|.
181 if (!retargetlistener ||
182 NS_FAILED(retargetlistener->CheckListenerChain())) {
183 mDispatchToMainThread = true;
187 return rv;
190 NS_IMETHODIMP
191 nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
192 nsresult status = aStatus;
193 // Bug 1886237 : TRRServiceChannel calls OnStopRequest OMT
194 // MOZ_ASSERT(NS_IsMainThread());
195 LOG(("nsHttpCompresssConv %p onstop %" PRIx32 " mDispatchToMainThread %d\n",
196 this, static_cast<uint32_t>(aStatus), mDispatchToMainThread));
198 // Framing integrity is enforced for content-encoding: gzip, but not for
199 // content-encoding: deflate. Note that gzip vs deflate is NOT determined
200 // by content sniffing but only via header.
201 if (!mStreamEnded && NS_SUCCEEDED(status) &&
202 (mFailUncleanStops && (mMode == HTTP_COMPRESS_GZIP))) {
203 // This is not a clean end of gzip stream: the transfer is incomplete.
204 status = NS_ERROR_NET_PARTIAL_TRANSFER;
205 LOG(("nsHttpCompresssConv %p onstop partial gzip\n", this));
207 if (NS_SUCCEEDED(status) && mMode == HTTP_COMPRESS_BROTLI) {
208 nsCOMPtr<nsIForcePendingChannel> fpChannel = do_QueryInterface(request);
209 bool isPending = false;
210 if (request) {
211 request->IsPending(&isPending);
213 if (fpChannel && !isPending) {
214 fpChannel->ForcePending(true);
216 bool allowTruncatedEmpty =
217 StaticPrefs::network_compress_allow_truncated_empty_brotli();
218 if (mBrotli && ((allowTruncatedEmpty && NS_FAILED(mBrotli->mStatus)) ||
219 (!allowTruncatedEmpty && mBrotli->mTotalOut == 0 &&
220 !mBrotli->mBrotliStateIsStreamEnd))) {
221 status = NS_ERROR_INVALID_CONTENT_ENCODING;
223 LOG(("nsHttpCompresssConv %p onstop brotlihandler rv %" PRIx32 "\n", this,
224 static_cast<uint32_t>(status)));
225 if (fpChannel && !isPending) {
226 fpChannel->ForcePending(false);
230 nsCOMPtr<nsIStreamListener> listener;
232 MutexAutoLock lock(mMutex);
233 listener = mListener;
236 return listener->OnStopRequest(request, status);
239 /* static */
240 nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream,
241 void* closure, const char* dataIn,
242 uint32_t, uint32_t aAvail,
243 uint32_t* countRead) {
244 MOZ_ASSERT(stream);
245 nsHTTPCompressConv* self = static_cast<nsHTTPCompressConv*>(closure);
246 *countRead = 0;
248 const size_t kOutSize = 128 * 1024; // just a chunk size, we call in a loop
249 uint8_t* outPtr;
250 size_t outSize;
251 size_t avail = aAvail;
252 BrotliDecoderResult res;
254 if (!self->mBrotli) {
255 *countRead = aAvail;
256 return NS_OK;
259 auto outBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize);
260 if (outBuffer == nullptr) {
261 self->mBrotli->mStatus = NS_ERROR_OUT_OF_MEMORY;
262 return self->mBrotli->mStatus;
264 do {
265 outSize = kOutSize;
266 outPtr = outBuffer.get();
268 // brotli api is documented in brotli/dec/decode.h and brotli/dec/decode.c
269 LOG(("nsHttpCompresssConv %p brotlihandler decompress %zu\n", self, avail));
270 size_t totalOut = self->mBrotli->mTotalOut;
271 res = ::BrotliDecoderDecompressStream(
272 &self->mBrotli->mState, &avail,
273 reinterpret_cast<const unsigned char**>(&dataIn), &outSize, &outPtr,
274 &totalOut);
275 outSize = kOutSize - outSize;
276 self->mBrotli->mTotalOut = totalOut;
277 self->mBrotli->mBrotliStateIsStreamEnd =
278 BrotliDecoderIsFinished(&self->mBrotli->mState);
279 LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%" PRIx32
280 " out=%zu\n",
281 self, static_cast<uint32_t>(res), outSize));
283 if (res == BROTLI_DECODER_RESULT_ERROR) {
284 LOG(("nsHttpCompressConv %p marking invalid encoding", self));
285 self->mBrotli->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING;
286 return self->mBrotli->mStatus;
289 // in 'the current implementation' brotli must consume everything before
290 // asking for more input
291 if (res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
292 MOZ_ASSERT(!avail);
293 if (avail) {
294 LOG(("nsHttpCompressConv %p did not consume all input", self));
295 self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
296 return self->mBrotli->mStatus;
300 auto callOnDataAvailable = [&](uint64_t aSourceOffset, const char* aBuffer,
301 uint32_t aCount) {
302 nsresult rv = self->do_OnDataAvailable(self->mBrotli->mRequest,
303 aSourceOffset, aBuffer, aCount);
304 LOG(("nsHttpCompressConv %p BrotliHandler ODA rv=%" PRIx32, self,
305 static_cast<uint32_t>(rv)));
306 if (NS_FAILED(rv)) {
307 self->mBrotli->mStatus = rv;
310 return rv;
313 if (outSize > 0) {
314 if (NS_FAILED(callOnDataAvailable(
315 self->mBrotli->mSourceOffset,
316 reinterpret_cast<const char*>(outBuffer.get()), outSize))) {
317 return self->mBrotli->mStatus;
319 self->mBrotli->mSourceOffset += outSize;
322 // See bug 1759745. If the decoder has more output data, take it.
323 while (::BrotliDecoderHasMoreOutput(&self->mBrotli->mState)) {
324 outSize = kOutSize;
325 const uint8_t* buffer =
326 ::BrotliDecoderTakeOutput(&self->mBrotli->mState, &outSize);
327 if (NS_FAILED(callOnDataAvailable(self->mBrotli->mSourceOffset,
328 reinterpret_cast<const char*>(buffer),
329 outSize))) {
330 return self->mBrotli->mStatus;
332 self->mBrotli->mSourceOffset += outSize;
335 if (res == BROTLI_DECODER_RESULT_SUCCESS ||
336 res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
337 *countRead = aAvail;
338 return NS_OK;
340 MOZ_ASSERT(res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
341 } while (res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
343 self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
344 return self->mBrotli->mStatus;
347 NS_IMETHODIMP
348 nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr,
349 uint64_t aSourceOffset, uint32_t aCount) {
350 nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING;
351 uint32_t streamLen = aCount;
352 LOG(("nsHttpCompressConv %p OnDataAvailable aSourceOffset:%" PRIu64
353 " count:%u",
354 this, aSourceOffset, aCount));
356 if (streamLen == 0) {
357 NS_ERROR("count of zero passed to OnDataAvailable");
358 return NS_ERROR_UNEXPECTED;
361 if (mStreamEnded) {
362 // Hmm... this may just indicate that the data stream is done and that
363 // what's left is either metadata or padding of some sort.... throwing
364 // it out is probably the safe thing to do.
365 uint32_t n;
366 return iStr->ReadSegments(NS_DiscardSegment, nullptr, streamLen, &n);
369 switch (mMode) {
370 case HTTP_COMPRESS_GZIP:
371 streamLen = check_header(iStr, streamLen, &rv);
373 if (rv != NS_OK) {
374 return rv;
377 if (streamLen == 0) {
378 return NS_OK;
381 [[fallthrough]];
383 case HTTP_COMPRESS_DEFLATE:
385 if (mInpBuffer != nullptr && streamLen > mInpBufferLen) {
386 unsigned char* originalInpBuffer = mInpBuffer;
387 if (!(mInpBuffer = (unsigned char*)realloc(
388 originalInpBuffer, mInpBufferLen = streamLen))) {
389 free(originalInpBuffer);
392 if (mOutBufferLen < streamLen * 2) {
393 unsigned char* originalOutBuffer = mOutBuffer;
394 if (!(mOutBuffer = (unsigned char*)realloc(
395 mOutBuffer, mOutBufferLen = streamLen * 3))) {
396 free(originalOutBuffer);
400 if (mInpBuffer == nullptr || mOutBuffer == nullptr) {
401 return NS_ERROR_OUT_OF_MEMORY;
405 if (mInpBuffer == nullptr) {
406 mInpBuffer = (unsigned char*)malloc(mInpBufferLen = streamLen);
409 if (mOutBuffer == nullptr) {
410 mOutBuffer = (unsigned char*)malloc(mOutBufferLen = streamLen * 3);
413 if (mInpBuffer == nullptr || mOutBuffer == nullptr) {
414 return NS_ERROR_OUT_OF_MEMORY;
417 uint32_t unused;
418 iStr->Read((char*)mInpBuffer, streamLen, &unused);
420 if (mMode == HTTP_COMPRESS_DEFLATE) {
421 if (!mStreamInitialized) {
422 memset(&d_stream, 0, sizeof(d_stream));
424 if (inflateInit(&d_stream) != Z_OK) {
425 return NS_ERROR_FAILURE;
428 mStreamInitialized = true;
430 d_stream.next_in = mInpBuffer;
431 d_stream.avail_in = (uInt)streamLen;
433 mDummyStreamInitialised = false;
434 for (;;) {
435 d_stream.next_out = mOutBuffer;
436 d_stream.avail_out = (uInt)mOutBufferLen;
438 int code = inflate(&d_stream, Z_NO_FLUSH);
439 unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
441 if (code == Z_STREAM_END) {
442 if (bytesWritten) {
443 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
444 bytesWritten);
445 if (NS_FAILED(rv)) {
446 return rv;
450 inflateEnd(&d_stream);
451 mStreamEnded = true;
452 break;
454 if (code == Z_OK) {
455 if (bytesWritten) {
456 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
457 bytesWritten);
458 if (NS_FAILED(rv)) {
459 return rv;
462 } else if (code == Z_BUF_ERROR) {
463 if (bytesWritten) {
464 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
465 bytesWritten);
466 if (NS_FAILED(rv)) {
467 return rv;
470 break;
471 } else if (code == Z_DATA_ERROR) {
472 // some servers (notably Apache with mod_deflate) don't generate
473 // zlib headers insert a dummy header and try again
474 static char dummy_head[2] = {
475 0x8 + 0x7 * 0x10,
476 (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
478 inflateReset(&d_stream);
479 d_stream.next_in = (Bytef*)dummy_head;
480 d_stream.avail_in = sizeof(dummy_head);
482 code = inflate(&d_stream, Z_NO_FLUSH);
483 if (code != Z_OK) {
484 return NS_ERROR_FAILURE;
487 // stop an endless loop caused by non-deflate data being labelled as
488 // deflate
489 if (mDummyStreamInitialised) {
490 NS_WARNING(
491 "endless loop detected"
492 " - invalid deflate");
493 return NS_ERROR_INVALID_CONTENT_ENCODING;
495 mDummyStreamInitialised = true;
496 // reset stream pointers to our original data
497 d_stream.next_in = mInpBuffer;
498 d_stream.avail_in = (uInt)streamLen;
499 } else {
500 return NS_ERROR_INVALID_CONTENT_ENCODING;
502 } /* for */
503 } else {
504 if (!mStreamInitialized) {
505 memset(&d_stream, 0, sizeof(d_stream));
507 if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK) {
508 return NS_ERROR_FAILURE;
511 mStreamInitialized = true;
514 d_stream.next_in = mInpBuffer;
515 d_stream.avail_in = (uInt)streamLen;
517 for (;;) {
518 d_stream.next_out = mOutBuffer;
519 d_stream.avail_out = (uInt)mOutBufferLen;
521 int code = inflate(&d_stream, Z_NO_FLUSH);
522 unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
524 if (code == Z_STREAM_END) {
525 if (bytesWritten) {
526 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
527 bytesWritten);
528 if (NS_FAILED(rv)) {
529 return rv;
533 inflateEnd(&d_stream);
534 mStreamEnded = true;
535 break;
537 if (code == Z_OK) {
538 if (bytesWritten) {
539 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
540 bytesWritten);
541 if (NS_FAILED(rv)) {
542 return rv;
545 } else if (code == Z_BUF_ERROR) {
546 if (bytesWritten) {
547 rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer,
548 bytesWritten);
549 if (NS_FAILED(rv)) {
550 return rv;
553 break;
554 } else {
555 return NS_ERROR_INVALID_CONTENT_ENCODING;
557 } /* for */
558 } /* gzip */
559 break;
561 case HTTP_COMPRESS_BROTLI: {
562 if (!mBrotli) {
563 mBrotli = MakeUnique<BrotliWrapper>();
566 mBrotli->mRequest = request;
567 mBrotli->mContext = nullptr;
568 mBrotli->mSourceOffset = aSourceOffset;
570 uint32_t countRead;
571 rv = iStr->ReadSegments(BrotliHandler, this, streamLen, &countRead);
572 if (NS_SUCCEEDED(rv)) {
573 rv = mBrotli->mStatus;
575 if (NS_FAILED(rv)) {
576 return rv;
578 } break;
580 default:
581 nsCOMPtr<nsIStreamListener> listener;
583 MutexAutoLock lock(mMutex);
584 listener = mListener;
586 rv = listener->OnDataAvailable(request, iStr, aSourceOffset, aCount);
587 if (NS_FAILED(rv)) {
588 return rv;
590 } /* switch */
592 return NS_OK;
593 } /* OnDataAvailable */
595 // XXX/ruslan: need to implement this too
597 NS_IMETHODIMP
598 nsHTTPCompressConv::Convert(nsIInputStream* aFromStream, const char* aFromType,
599 const char* aToType, nsISupports* aCtxt,
600 nsIInputStream** _retval) {
601 return NS_ERROR_NOT_IMPLEMENTED;
604 nsresult nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request,
605 uint64_t offset,
606 const char* buffer,
607 uint32_t count) {
608 LOG(("nsHttpCompressConv %p do_OnDataAvailable mDispatchToMainThread %d",
609 this, mDispatchToMainThread));
610 if (mDispatchToMainThread && !NS_IsMainThread()) {
611 nsCOMPtr<nsIInputStream> stream;
612 MOZ_TRY(NS_NewByteInputStream(getter_AddRefs(stream), Span(buffer, count),
613 nsAssignmentType::NS_ASSIGNMENT_COPY));
615 nsCOMPtr<nsIStreamListener> listener;
617 MutexAutoLock lock(mMutex);
618 listener = mListener;
621 // This is safe and will always run before OnStopRequest, because
622 // ChanneleventQueue means that we can't enqueue OnStopRequest until after
623 // the OMT OnDataAvailable call has completed. So Dispatching here will
624 // ensure it's in the MainThread event queue before OnStopRequest
625 nsCOMPtr<nsIRunnable> handler = NS_NewRunnableFunction(
626 "nsHTTPCompressConv::do_OnDataAvailable",
627 [request{RefPtr<nsIRequest>(request)}, stream{std::move(stream)},
628 listener{std::move(listener)}, offset, count]() {
629 LOG(("nsHttpCompressConv Calling OnDataAvailable on Mainthread"));
630 Unused << listener->OnDataAvailable(request, stream, offset, count);
633 mDecodedDataLength += count;
634 return NS_DispatchToMainThread(handler);
637 if (!mStream) {
638 mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
639 NS_ENSURE_STATE(mStream);
642 mStream->ShareData(buffer, count);
644 nsCOMPtr<nsIStreamListener> listener;
646 MutexAutoLock lock(mMutex);
647 listener = mListener;
649 LOG(("nsHTTPCompressConv::do_OnDataAvailable req:%p offset: offset:%" PRIu64
650 "count:%u",
651 request, offset, count));
652 nsresult rv = listener->OnDataAvailable(request, mStream, offset, count);
654 // Make sure the stream no longer references |buffer| in case our listener
655 // is crazy enough to try to read from |mStream| after ODA.
656 mStream->ShareData("", 0);
657 mDecodedDataLength += count;
659 return rv;
662 #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
663 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
664 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
665 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
666 #define COMMENT 0x10 /* bit 4 set: file comment present */
667 #define RESERVED 0xE0 /* bits 5..7: reserved */
669 static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
671 uint32_t nsHTTPCompressConv::check_header(nsIInputStream* iStr,
672 uint32_t streamLen, nsresult* rs) {
673 enum {
674 GZIP_INIT = 0,
675 GZIP_OS,
676 GZIP_EXTRA0,
677 GZIP_EXTRA1,
678 GZIP_EXTRA2,
679 GZIP_ORIG,
680 GZIP_COMMENT,
681 GZIP_CRC
683 char c;
685 *rs = NS_OK;
687 if (mCheckHeaderDone) {
688 return streamLen;
691 while (streamLen) {
692 switch (hMode) {
693 case GZIP_INIT:
694 uint32_t unused;
695 iStr->Read(&c, 1, &unused);
696 streamLen--;
698 if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0]) {
699 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
700 return 0;
703 if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1]) {
704 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
705 return 0;
708 if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED) {
709 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
710 return 0;
713 mSkipCount++;
714 if (mSkipCount == 4) {
715 mFlags = (unsigned)c & 0377;
716 if (mFlags & RESERVED) {
717 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
718 return 0;
720 hMode = GZIP_OS;
721 mSkipCount = 0;
723 break;
725 case GZIP_OS:
726 iStr->Read(&c, 1, &unused);
727 streamLen--;
728 mSkipCount++;
730 if (mSkipCount == 6) {
731 hMode = GZIP_EXTRA0;
733 break;
735 case GZIP_EXTRA0:
736 if (mFlags & EXTRA_FIELD) {
737 iStr->Read(&c, 1, &unused);
738 streamLen--;
739 mLen = (uInt)c & 0377;
740 hMode = GZIP_EXTRA1;
741 } else {
742 hMode = GZIP_ORIG;
744 break;
746 case GZIP_EXTRA1:
747 iStr->Read(&c, 1, &unused);
748 streamLen--;
749 mLen |= ((uInt)c & 0377) << 8;
750 mSkipCount = 0;
751 hMode = GZIP_EXTRA2;
752 break;
754 case GZIP_EXTRA2:
755 if (mSkipCount == mLen) {
756 hMode = GZIP_ORIG;
757 } else {
758 iStr->Read(&c, 1, &unused);
759 streamLen--;
760 mSkipCount++;
762 break;
764 case GZIP_ORIG:
765 if (mFlags & ORIG_NAME) {
766 iStr->Read(&c, 1, &unused);
767 streamLen--;
768 if (c == 0) hMode = GZIP_COMMENT;
769 } else {
770 hMode = GZIP_COMMENT;
772 break;
774 case GZIP_COMMENT:
775 if (mFlags & COMMENT) {
776 iStr->Read(&c, 1, &unused);
777 streamLen--;
778 if (c == 0) {
779 hMode = GZIP_CRC;
780 mSkipCount = 0;
782 } else {
783 hMode = GZIP_CRC;
784 mSkipCount = 0;
786 break;
788 case GZIP_CRC:
789 if (mFlags & HEAD_CRC) {
790 iStr->Read(&c, 1, &unused);
791 streamLen--;
792 mSkipCount++;
793 if (mSkipCount == 2) {
794 mCheckHeaderDone = true;
795 return streamLen;
797 } else {
798 mCheckHeaderDone = true;
799 return streamLen;
801 break;
804 return streamLen;
807 NS_IMETHODIMP
808 nsHTTPCompressConv::CheckListenerChain() {
809 if (XRE_IsContentProcess()) {
810 // handle decompression OMT always. If the chain needs to be MT,
811 // we'll determine that in OnStartRequest and dispatch to MT
812 return NS_OK;
814 nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
816 MutexAutoLock lock(mMutex);
817 listener = do_QueryInterface(mListener);
819 if (!listener) {
820 return NS_ERROR_NO_INTERFACE;
823 return listener->CheckListenerChain();
826 NS_IMETHODIMP
827 nsHTTPCompressConv::OnDataFinished(nsresult aStatus) {
828 nsCOMPtr<nsIThreadRetargetableStreamListener> listener;
831 MutexAutoLock lock(mMutex);
832 listener = do_QueryInterface(mListener);
835 if (listener) {
836 if (mDispatchToMainThread && !NS_IsMainThread()) {
837 nsCOMPtr<nsIRunnable> handler = NS_NewRunnableFunction(
838 "dispatch", [listener{std::move(listener)}, aStatus]() {
839 Unused << listener->OnDataFinished(aStatus);
842 return NS_DispatchToMainThread(handler);
845 return listener->OnDataFinished(aStatus);
848 return NS_OK;
851 } // namespace net
852 } // namespace mozilla
854 nsresult NS_NewHTTPCompressConv(
855 mozilla::net::nsHTTPCompressConv** aHTTPCompressConv) {
856 MOZ_ASSERT(aHTTPCompressConv != nullptr, "null ptr");
857 if (!aHTTPCompressConv) {
858 return NS_ERROR_NULL_POINTER;
861 RefPtr<mozilla::net::nsHTTPCompressConv> outVal =
862 new mozilla::net::nsHTTPCompressConv();
863 if (!outVal) {
864 return NS_ERROR_OUT_OF_MEMORY;
866 outVal.forget(aHTTPCompressConv);
867 return NS_OK;