diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/streamconv/converters/nsHTTPCompressConv.cpp | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp new file mode 100644 index 0000000000..b5c58b9754 --- /dev/null +++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp @@ -0,0 +1,746 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsHTTPCompressConv.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsError.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Logging.h" +#include "nsIForcePendingChannel.h" +#include "nsIRequest.h" +#include "mozilla/UniquePtrExtensions.h" + +// brotli headers +#undef assert +#include "assert.h" +#include "state.h" +#include "brotli/decode.h" + +namespace mozilla { +namespace net { + +extern LazyLogModule gHttpLog; +#define LOG(args) \ + MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args) + +class BrotliWrapper { + public: + BrotliWrapper() { + BrotliDecoderStateInit(&mState, nullptr, nullptr, nullptr); + } + ~BrotliWrapper() { BrotliDecoderStateCleanup(&mState); } + + BrotliDecoderState mState{}; + Atomic<size_t, Relaxed> mTotalOut{0}; + nsresult mStatus = NS_OK; + Atomic<bool, Relaxed> mBrotliStateIsStreamEnd{false}; + + nsIRequest* mRequest{nullptr}; + nsISupports* mContext{nullptr}; + uint64_t mSourceOffset{0}; +}; + +// nsISupports implementation +NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener, + nsIRequestObserver, nsICompressConvStats, + nsIThreadRetargetableStreamListener) + +// nsFTPDirListingConv methods +nsHTTPCompressConv::nsHTTPCompressConv() { + LOG(("nsHttpCompresssConv %p ctor\n", this)); + if (NS_IsMainThread()) { + mFailUncleanStops = + Preferences::GetBool("network.http.enforce-framing.http", false); + } else { + mFailUncleanStops = false; + } +} + +nsHTTPCompressConv::~nsHTTPCompressConv() { + LOG(("nsHttpCompresssConv %p dtor\n", this)); + if (mInpBuffer) { + free(mInpBuffer); + } + + if (mOutBuffer) { + free(mOutBuffer); + } + + // For some reason we are not getting Z_STREAM_END. But this was also seen + // for mozilla bug 198133. Need to handle this case. + if (mStreamInitialized && !mStreamEnded) { + inflateEnd(&d_stream); + } +} + +NS_IMETHODIMP +nsHTTPCompressConv::GetDecodedDataLength(uint64_t* aDecodedDataLength) { + *aDecodedDataLength = mDecodedDataLength; + return NS_OK; +} + +NS_IMETHODIMP +nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType, + nsIStreamListener* aListener, + nsISupports* aCtxt) { + if (!nsCRT::strncasecmp(aFromType, HTTP_COMPRESS_TYPE, + sizeof(HTTP_COMPRESS_TYPE) - 1) || + !nsCRT::strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE, + sizeof(HTTP_X_COMPRESS_TYPE) - 1)) { + mMode = HTTP_COMPRESS_COMPRESS; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_GZIP_TYPE, + sizeof(HTTP_GZIP_TYPE) - 1) || + !nsCRT::strncasecmp(aFromType, HTTP_X_GZIP_TYPE, + sizeof(HTTP_X_GZIP_TYPE) - 1)) { + mMode = HTTP_COMPRESS_GZIP; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_DEFLATE_TYPE, + sizeof(HTTP_DEFLATE_TYPE) - 1)) { + mMode = HTTP_COMPRESS_DEFLATE; + } else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_TYPE, + sizeof(HTTP_BROTLI_TYPE) - 1)) { + mMode = HTTP_COMPRESS_BROTLI; + } + LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this, + aFromType, aToType, (CompressMode)mMode)); + + MutexAutoLock lock(mMutex); + // hook ourself up with the receiving listener. + mListener = aListener; + + return NS_OK; +} + +NS_IMETHODIMP +nsHTTPCompressConv::GetConvertedType(const nsACString& aFromType, + nsIChannel* aChannel, + nsACString& aToType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsHTTPCompressConv::OnStartRequest(nsIRequest* request) { + LOG(("nsHttpCompresssConv %p onstart\n", this)); + nsCOMPtr<nsIStreamListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + return listener->OnStartRequest(request); +} + +NS_IMETHODIMP +nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsresult aStatus) { + nsresult status = aStatus; + LOG(("nsHttpCompresssConv %p onstop %" PRIx32 "\n", this, + static_cast<uint32_t>(aStatus))); + + // Framing integrity is enforced for content-encoding: gzip, but not for + // content-encoding: deflate. Note that gzip vs deflate is NOT determined + // by content sniffing but only via header. + if (!mStreamEnded && NS_SUCCEEDED(status) && + (mFailUncleanStops && (mMode == HTTP_COMPRESS_GZIP))) { + // This is not a clean end of gzip stream: the transfer is incomplete. + status = NS_ERROR_NET_PARTIAL_TRANSFER; + LOG(("nsHttpCompresssConv %p onstop partial gzip\n", this)); + } + if (NS_SUCCEEDED(status) && mMode == HTTP_COMPRESS_BROTLI) { + nsCOMPtr<nsIForcePendingChannel> fpChannel = do_QueryInterface(request); + bool isPending = false; + if (request) { + request->IsPending(&isPending); + } + if (fpChannel && !isPending) { + fpChannel->ForcePending(true); + } + if (mBrotli && (mBrotli->mTotalOut == 0) && + !mBrotli->mBrotliStateIsStreamEnd) { + status = NS_ERROR_INVALID_CONTENT_ENCODING; + } + LOG(("nsHttpCompresssConv %p onstop brotlihandler rv %" PRIx32 "\n", this, + static_cast<uint32_t>(status))); + if (fpChannel && !isPending) { + fpChannel->ForcePending(false); + } + } + + nsCOMPtr<nsIStreamListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + return listener->OnStopRequest(request, status); +} + +/* static */ +nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream, + void* closure, const char* dataIn, + uint32_t, uint32_t aAvail, + uint32_t* countRead) { + MOZ_ASSERT(stream); + nsHTTPCompressConv* self = static_cast<nsHTTPCompressConv*>(closure); + *countRead = 0; + + const size_t kOutSize = 128 * 1024; // just a chunk size, we call in a loop + uint8_t* outPtr; + size_t outSize; + size_t avail = aAvail; + BrotliDecoderResult res; + + if (!self->mBrotli) { + *countRead = aAvail; + return NS_OK; + } + + auto outBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize); + if (outBuffer == nullptr) { + self->mBrotli->mStatus = NS_ERROR_OUT_OF_MEMORY; + return self->mBrotli->mStatus; + } + do { + outSize = kOutSize; + outPtr = outBuffer.get(); + + // brotli api is documented in brotli/dec/decode.h and brotli/dec/decode.c + LOG(("nsHttpCompresssConv %p brotlihandler decompress %zu\n", self, avail)); + size_t totalOut = self->mBrotli->mTotalOut; + res = ::BrotliDecoderDecompressStream( + &self->mBrotli->mState, &avail, + reinterpret_cast<const unsigned char**>(&dataIn), &outSize, &outPtr, + &totalOut); + outSize = kOutSize - outSize; + self->mBrotli->mTotalOut = totalOut; + self->mBrotli->mBrotliStateIsStreamEnd = + BrotliDecoderIsFinished(&self->mBrotli->mState); + LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%" PRIx32 + " out=%zu\n", + self, static_cast<uint32_t>(res), outSize)); + + if (res == BROTLI_DECODER_RESULT_ERROR) { + LOG(("nsHttpCompressConv %p marking invalid encoding", self)); + self->mBrotli->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING; + return self->mBrotli->mStatus; + } + + // in 'the current implementation' brotli must consume everything before + // asking for more input + if (res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { + MOZ_ASSERT(!avail); + if (avail) { + LOG(("nsHttpCompressConv %p did not consume all input", self)); + self->mBrotli->mStatus = NS_ERROR_UNEXPECTED; + return self->mBrotli->mStatus; + } + } + + auto callOnDataAvailable = [&](uint64_t aSourceOffset, const char* aBuffer, + uint32_t aCount) { + nsresult rv = self->do_OnDataAvailable(self->mBrotli->mRequest, + aSourceOffset, aBuffer, aCount); + LOG(("nsHttpCompressConv %p BrotliHandler ODA rv=%" PRIx32, self, + static_cast<uint32_t>(rv))); + if (NS_FAILED(rv)) { + self->mBrotli->mStatus = rv; + } + + return rv; + }; + + if (outSize > 0) { + if (NS_FAILED(callOnDataAvailable( + self->mBrotli->mSourceOffset, + reinterpret_cast<const char*>(outBuffer.get()), outSize))) { + return self->mBrotli->mStatus; + } + } + + // See bug 1759745. If the decoder has more output data, take it. + while (::BrotliDecoderHasMoreOutput(&self->mBrotli->mState)) { + outSize = kOutSize; + const uint8_t* buffer = + ::BrotliDecoderTakeOutput(&self->mBrotli->mState, &outSize); + if (NS_FAILED(callOnDataAvailable(self->mBrotli->mSourceOffset, + reinterpret_cast<const char*>(buffer), + outSize))) { + return self->mBrotli->mStatus; + } + } + + if (res == BROTLI_DECODER_RESULT_SUCCESS || + res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { + *countRead = aAvail; + return NS_OK; + } + MOZ_ASSERT(res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT); + } while (res == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT); + + self->mBrotli->mStatus = NS_ERROR_UNEXPECTED; + return self->mBrotli->mStatus; +} + +NS_IMETHODIMP +nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr, + uint64_t aSourceOffset, uint32_t aCount) { + nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING; + uint32_t streamLen = aCount; + LOG(("nsHttpCompressConv %p OnDataAvailable %d", this, aCount)); + + if (streamLen == 0) { + NS_ERROR("count of zero passed to OnDataAvailable"); + return NS_ERROR_UNEXPECTED; + } + + if (mStreamEnded) { + // Hmm... this may just indicate that the data stream is done and that + // what's left is either metadata or padding of some sort.... throwing + // it out is probably the safe thing to do. + uint32_t n; + return iStr->ReadSegments(NS_DiscardSegment, nullptr, streamLen, &n); + } + + switch (mMode) { + case HTTP_COMPRESS_GZIP: + streamLen = check_header(iStr, streamLen, &rv); + + if (rv != NS_OK) { + return rv; + } + + if (streamLen == 0) { + return NS_OK; + } + + [[fallthrough]]; + + case HTTP_COMPRESS_DEFLATE: + + if (mInpBuffer != nullptr && streamLen > mInpBufferLen) { + unsigned char* originalInpBuffer = mInpBuffer; + if (!(mInpBuffer = (unsigned char*)realloc( + originalInpBuffer, mInpBufferLen = streamLen))) { + free(originalInpBuffer); + } + + if (mOutBufferLen < streamLen * 2) { + unsigned char* originalOutBuffer = mOutBuffer; + if (!(mOutBuffer = (unsigned char*)realloc( + mOutBuffer, mOutBufferLen = streamLen * 3))) { + free(originalOutBuffer); + } + } + + if (mInpBuffer == nullptr || mOutBuffer == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (mInpBuffer == nullptr) { + mInpBuffer = (unsigned char*)malloc(mInpBufferLen = streamLen); + } + + if (mOutBuffer == nullptr) { + mOutBuffer = (unsigned char*)malloc(mOutBufferLen = streamLen * 3); + } + + if (mInpBuffer == nullptr || mOutBuffer == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t unused; + iStr->Read((char*)mInpBuffer, streamLen, &unused); + + if (mMode == HTTP_COMPRESS_DEFLATE) { + if (!mStreamInitialized) { + memset(&d_stream, 0, sizeof(d_stream)); + + if (inflateInit(&d_stream) != Z_OK) { + return NS_ERROR_FAILURE; + } + + mStreamInitialized = true; + } + d_stream.next_in = mInpBuffer; + d_stream.avail_in = (uInt)streamLen; + + mDummyStreamInitialised = false; + for (;;) { + d_stream.next_out = mOutBuffer; + d_stream.avail_out = (uInt)mOutBufferLen; + + int code = inflate(&d_stream, Z_NO_FLUSH); + unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out; + + if (code == Z_STREAM_END) { + if (bytesWritten) { + rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer, + bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + } + + inflateEnd(&d_stream); + mStreamEnded = true; + break; + } + if (code == Z_OK) { + if (bytesWritten) { + rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer, + bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + } + } else if (code == Z_BUF_ERROR) { + if (bytesWritten) { + rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer, + bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + } + break; + } else if (code == Z_DATA_ERROR) { + // some servers (notably Apache with mod_deflate) don't generate + // zlib headers insert a dummy header and try again + static char dummy_head[2] = { + 0x8 + 0x7 * 0x10, + (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF, + }; + inflateReset(&d_stream); + d_stream.next_in = (Bytef*)dummy_head; + d_stream.avail_in = sizeof(dummy_head); + + code = inflate(&d_stream, Z_NO_FLUSH); + if (code != Z_OK) { + return NS_ERROR_FAILURE; + } + + // stop an endless loop caused by non-deflate data being labelled as + // deflate + if (mDummyStreamInitialised) { + NS_WARNING( + "endless loop detected" + " - invalid deflate"); + return NS_ERROR_INVALID_CONTENT_ENCODING; + } + mDummyStreamInitialised = true; + // reset stream pointers to our original data + d_stream.next_in = mInpBuffer; + d_stream.avail_in = (uInt)streamLen; + } else { + return NS_ERROR_INVALID_CONTENT_ENCODING; + } + } /* for */ + } else { + if (!mStreamInitialized) { + memset(&d_stream, 0, sizeof(d_stream)); + + if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK) { + return NS_ERROR_FAILURE; + } + + mStreamInitialized = true; + } + + d_stream.next_in = mInpBuffer; + d_stream.avail_in = (uInt)streamLen; + + for (;;) { + d_stream.next_out = mOutBuffer; + d_stream.avail_out = (uInt)mOutBufferLen; + + int code = inflate(&d_stream, Z_NO_FLUSH); + unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out; + + if (code == Z_STREAM_END) { + if (bytesWritten) { + rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer, + bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + } + + inflateEnd(&d_stream); + mStreamEnded = true; + break; + } + if (code == Z_OK) { + if (bytesWritten) { + rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer, + bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + } + } else if (code == Z_BUF_ERROR) { + if (bytesWritten) { + rv = do_OnDataAvailable(request, aSourceOffset, (char*)mOutBuffer, + bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + } + break; + } else { + return NS_ERROR_INVALID_CONTENT_ENCODING; + } + } /* for */ + } /* gzip */ + break; + + case HTTP_COMPRESS_BROTLI: { + if (!mBrotli) { + mBrotli = MakeUnique<BrotliWrapper>(); + } + + mBrotli->mRequest = request; + mBrotli->mContext = nullptr; + mBrotli->mSourceOffset = aSourceOffset; + + uint32_t countRead; + rv = iStr->ReadSegments(BrotliHandler, this, streamLen, &countRead); + if (NS_SUCCEEDED(rv)) { + rv = mBrotli->mStatus; + } + if (NS_FAILED(rv)) { + return rv; + } + } break; + + default: + nsCOMPtr<nsIStreamListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + rv = listener->OnDataAvailable(request, iStr, aSourceOffset, aCount); + if (NS_FAILED(rv)) { + return rv; + } + } /* switch */ + + return NS_OK; +} /* OnDataAvailable */ + +// XXX/ruslan: need to implement this too + +NS_IMETHODIMP +nsHTTPCompressConv::Convert(nsIInputStream* aFromStream, const char* aFromType, + const char* aToType, nsISupports* aCtxt, + nsIInputStream** _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request, + uint64_t offset, + const char* buffer, + uint32_t count) { + if (!mStream) { + mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID); + NS_ENSURE_STATE(mStream); + } + + mStream->ShareData(buffer, count); + + nsCOMPtr<nsIStreamListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + nsresult rv = listener->OnDataAvailable(request, mStream, offset, count); + + // Make sure the stream no longer references |buffer| in case our listener + // is crazy enough to try to read from |mStream| after ODA. + mStream->ShareData("", 0); + mDecodedDataLength += count; + + return rv; +} + +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + +static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ + +uint32_t nsHTTPCompressConv::check_header(nsIInputStream* iStr, + uint32_t streamLen, nsresult* rs) { + enum { + GZIP_INIT = 0, + GZIP_OS, + GZIP_EXTRA0, + GZIP_EXTRA1, + GZIP_EXTRA2, + GZIP_ORIG, + GZIP_COMMENT, + GZIP_CRC + }; + char c; + + *rs = NS_OK; + + if (mCheckHeaderDone) { + return streamLen; + } + + while (streamLen) { + switch (hMode) { + case GZIP_INIT: + uint32_t unused; + iStr->Read(&c, 1, &unused); + streamLen--; + + if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0]) { + *rs = NS_ERROR_INVALID_CONTENT_ENCODING; + return 0; + } + + if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1]) { + *rs = NS_ERROR_INVALID_CONTENT_ENCODING; + return 0; + } + + if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED) { + *rs = NS_ERROR_INVALID_CONTENT_ENCODING; + return 0; + } + + mSkipCount++; + if (mSkipCount == 4) { + mFlags = (unsigned)c & 0377; + if (mFlags & RESERVED) { + *rs = NS_ERROR_INVALID_CONTENT_ENCODING; + return 0; + } + hMode = GZIP_OS; + mSkipCount = 0; + } + break; + + case GZIP_OS: + iStr->Read(&c, 1, &unused); + streamLen--; + mSkipCount++; + + if (mSkipCount == 6) { + hMode = GZIP_EXTRA0; + } + break; + + case GZIP_EXTRA0: + if (mFlags & EXTRA_FIELD) { + iStr->Read(&c, 1, &unused); + streamLen--; + mLen = (uInt)c & 0377; + hMode = GZIP_EXTRA1; + } else { + hMode = GZIP_ORIG; + } + break; + + case GZIP_EXTRA1: + iStr->Read(&c, 1, &unused); + streamLen--; + mLen |= ((uInt)c & 0377) << 8; + mSkipCount = 0; + hMode = GZIP_EXTRA2; + break; + + case GZIP_EXTRA2: + if (mSkipCount == mLen) { + hMode = GZIP_ORIG; + } else { + iStr->Read(&c, 1, &unused); + streamLen--; + mSkipCount++; + } + break; + + case GZIP_ORIG: + if (mFlags & ORIG_NAME) { + iStr->Read(&c, 1, &unused); + streamLen--; + if (c == 0) hMode = GZIP_COMMENT; + } else { + hMode = GZIP_COMMENT; + } + break; + + case GZIP_COMMENT: + if (mFlags & COMMENT) { + iStr->Read(&c, 1, &unused); + streamLen--; + if (c == 0) { + hMode = GZIP_CRC; + mSkipCount = 0; + } + } else { + hMode = GZIP_CRC; + mSkipCount = 0; + } + break; + + case GZIP_CRC: + if (mFlags & HEAD_CRC) { + iStr->Read(&c, 1, &unused); + streamLen--; + mSkipCount++; + if (mSkipCount == 2) { + mCheckHeaderDone = true; + return streamLen; + } + } else { + mCheckHeaderDone = true; + return streamLen; + } + break; + } + } + return streamLen; +} + +NS_IMETHODIMP +nsHTTPCompressConv::CheckListenerChain() { + nsCOMPtr<nsIThreadRetargetableStreamListener> listener; + { + MutexAutoLock lock(mMutex); + listener = do_QueryInterface(mListener); + } + + if (!listener) { + return NS_ERROR_NO_INTERFACE; + } + + return listener->CheckListenerChain(); +} + +} // namespace net +} // namespace mozilla + +nsresult NS_NewHTTPCompressConv( + mozilla::net::nsHTTPCompressConv** aHTTPCompressConv) { + MOZ_ASSERT(aHTTPCompressConv != nullptr, "null ptr"); + if (!aHTTPCompressConv) { + return NS_ERROR_NULL_POINTER; + } + + RefPtr<mozilla::net::nsHTTPCompressConv> outVal = + new mozilla::net::nsHTTPCompressConv(); + if (!outVal) { + return NS_ERROR_OUT_OF_MEMORY; + } + outVal.forget(aHTTPCompressConv); + return NS_OK; +} |