michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 sts=4 cindent et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsHTTPCompressConv.h" michael@0: #include "nsMemory.h" michael@0: #include "plstr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsError.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: // nsISupports implementation michael@0: NS_IMPL_ISUPPORTS(nsHTTPCompressConv, michael@0: nsIStreamConverter, michael@0: nsIStreamListener, michael@0: nsIRequestObserver) michael@0: michael@0: // nsFTPDirListingConv methods michael@0: nsHTTPCompressConv::nsHTTPCompressConv() michael@0: : mListener(nullptr) michael@0: , mMode(HTTP_COMPRESS_IDENTITY) michael@0: , mOutBuffer(nullptr) michael@0: , mInpBuffer(nullptr) michael@0: , mOutBufferLen(0) michael@0: , mInpBufferLen(0) michael@0: , mCheckHeaderDone(false) michael@0: , mStreamEnded(false) michael@0: , mStreamInitialized(false) michael@0: , mLen(0) michael@0: , hMode(0) michael@0: , mSkipCount(0) michael@0: , mFlags(0) michael@0: { michael@0: } michael@0: michael@0: nsHTTPCompressConv::~nsHTTPCompressConv() michael@0: { michael@0: NS_IF_RELEASE(mListener); michael@0: michael@0: if (mInpBuffer) michael@0: nsMemory::Free(mInpBuffer); michael@0: michael@0: if (mOutBuffer) michael@0: nsMemory::Free(mOutBuffer); michael@0: michael@0: // For some reason we are not getting Z_STREAM_END. But this was also seen michael@0: // for mozilla bug 198133. Need to handle this case. michael@0: if (mStreamInitialized && !mStreamEnded) michael@0: inflateEnd (&d_stream); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPCompressConv::AsyncConvertData(const char *aFromType, michael@0: const char *aToType, michael@0: nsIStreamListener *aListener, michael@0: nsISupports *aCtxt) michael@0: { michael@0: if (!PL_strncasecmp(aFromType, HTTP_COMPRESS_TYPE, sizeof(HTTP_COMPRESS_TYPE)-1) || michael@0: !PL_strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE, sizeof(HTTP_X_COMPRESS_TYPE)-1)) michael@0: mMode = HTTP_COMPRESS_COMPRESS; michael@0: michael@0: else if (!PL_strncasecmp(aFromType, HTTP_GZIP_TYPE, sizeof(HTTP_GZIP_TYPE)-1) || michael@0: !PL_strncasecmp(aFromType, HTTP_X_GZIP_TYPE, sizeof(HTTP_X_GZIP_TYPE)-1)) michael@0: mMode = HTTP_COMPRESS_GZIP; michael@0: michael@0: else if (!PL_strncasecmp(aFromType, HTTP_DEFLATE_TYPE, sizeof(HTTP_DEFLATE_TYPE)-1)) michael@0: mMode = HTTP_COMPRESS_DEFLATE; michael@0: michael@0: // hook ourself up with the receiving listener. michael@0: mListener = aListener; michael@0: NS_ADDREF(mListener); michael@0: michael@0: mAsyncConvContext = aCtxt; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPCompressConv::OnStartRequest(nsIRequest* request, nsISupports *aContext) michael@0: { michael@0: return mListener->OnStartRequest(request, aContext); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext, michael@0: nsresult aStatus) michael@0: { michael@0: return mListener->OnStopRequest(request, aContext, aStatus); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, michael@0: nsISupports *aContext, michael@0: nsIInputStream *iStr, michael@0: uint64_t aSourceOffset, michael@0: uint32_t aCount) michael@0: { michael@0: nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: uint32_t streamLen = aCount; michael@0: michael@0: if (streamLen == 0) michael@0: { michael@0: NS_ERROR("count of zero passed to OnDataAvailable"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (mStreamEnded) michael@0: { michael@0: // Hmm... this may just indicate that the data stream is done and that michael@0: // what's left is either metadata or padding of some sort.... throwing michael@0: // it out is probably the safe thing to do. michael@0: uint32_t n; michael@0: return iStr->ReadSegments(NS_DiscardSegment, nullptr, streamLen, &n); michael@0: } michael@0: michael@0: switch (mMode) michael@0: { michael@0: case HTTP_COMPRESS_GZIP: michael@0: streamLen = check_header(iStr, streamLen, &rv); michael@0: michael@0: if (rv != NS_OK) michael@0: return rv; michael@0: michael@0: if (streamLen == 0) michael@0: return NS_OK; michael@0: michael@0: // FALLTHROUGH michael@0: michael@0: case HTTP_COMPRESS_DEFLATE: michael@0: michael@0: if (mInpBuffer != nullptr && streamLen > mInpBufferLen) michael@0: { michael@0: mInpBuffer = (unsigned char *) moz_realloc(mInpBuffer, mInpBufferLen = streamLen); michael@0: michael@0: if (mOutBufferLen < streamLen * 2) michael@0: mOutBuffer = (unsigned char *) moz_realloc(mOutBuffer, mOutBufferLen = streamLen * 3); michael@0: michael@0: if (mInpBuffer == nullptr || mOutBuffer == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (mInpBuffer == nullptr) michael@0: mInpBuffer = (unsigned char *) moz_malloc(mInpBufferLen = streamLen); michael@0: michael@0: if (mOutBuffer == nullptr) michael@0: mOutBuffer = (unsigned char *) moz_malloc(mOutBufferLen = streamLen * 3); michael@0: michael@0: if (mInpBuffer == nullptr || mOutBuffer == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: uint32_t unused; michael@0: iStr->Read((char *)mInpBuffer, streamLen, &unused); michael@0: michael@0: if (mMode == HTTP_COMPRESS_DEFLATE) michael@0: { michael@0: if (!mStreamInitialized) michael@0: { michael@0: memset(&d_stream, 0, sizeof (d_stream)); michael@0: michael@0: if (inflateInit(&d_stream) != Z_OK) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mStreamInitialized = true; michael@0: } michael@0: d_stream.next_in = mInpBuffer; michael@0: d_stream.avail_in = (uInt)streamLen; michael@0: michael@0: mDummyStreamInitialised = false; michael@0: for (;;) michael@0: { michael@0: d_stream.next_out = mOutBuffer; michael@0: d_stream.avail_out = (uInt)mOutBufferLen; michael@0: michael@0: int code = inflate(&d_stream, Z_NO_FLUSH); michael@0: unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out; michael@0: michael@0: if (code == Z_STREAM_END) michael@0: { michael@0: if (bytesWritten) michael@0: { michael@0: rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } michael@0: michael@0: inflateEnd(&d_stream); michael@0: mStreamEnded = true; michael@0: break; michael@0: } michael@0: else if (code == Z_OK) michael@0: { michael@0: if (bytesWritten) michael@0: { michael@0: rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: else if (code == Z_BUF_ERROR) michael@0: { michael@0: if (bytesWritten) michael@0: { michael@0: rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } michael@0: break; michael@0: } michael@0: else if (code == Z_DATA_ERROR) michael@0: { michael@0: // some servers (notably Apache with mod_deflate) don't generate zlib headers michael@0: // insert a dummy header and try again michael@0: static char dummy_head[2] = michael@0: { michael@0: 0x8 + 0x7 * 0x10, michael@0: (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF, michael@0: }; michael@0: inflateReset(&d_stream); michael@0: d_stream.next_in = (Bytef*) dummy_head; michael@0: d_stream.avail_in = sizeof(dummy_head); michael@0: michael@0: code = inflate(&d_stream, Z_NO_FLUSH); michael@0: if (code != Z_OK) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // stop an endless loop caused by non-deflate data being labelled as deflate michael@0: if (mDummyStreamInitialised) { michael@0: NS_WARNING("endless loop detected" michael@0: " - invalid deflate"); michael@0: return NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: } michael@0: mDummyStreamInitialised = true; michael@0: // reset stream pointers to our original data michael@0: d_stream.next_in = mInpBuffer; michael@0: d_stream.avail_in = (uInt)streamLen; michael@0: } michael@0: else michael@0: return NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: } /* for */ michael@0: } michael@0: else michael@0: { michael@0: if (!mStreamInitialized) michael@0: { michael@0: memset(&d_stream, 0, sizeof (d_stream)); michael@0: michael@0: if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mStreamInitialized = true; michael@0: } michael@0: michael@0: d_stream.next_in = mInpBuffer; michael@0: d_stream.avail_in = (uInt)streamLen; michael@0: michael@0: for (;;) michael@0: { michael@0: d_stream.next_out = mOutBuffer; michael@0: d_stream.avail_out = (uInt)mOutBufferLen; michael@0: michael@0: int code = inflate (&d_stream, Z_NO_FLUSH); michael@0: unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out; michael@0: michael@0: if (code == Z_STREAM_END) michael@0: { michael@0: if (bytesWritten) michael@0: { michael@0: rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } michael@0: michael@0: inflateEnd(&d_stream); michael@0: mStreamEnded = true; michael@0: break; michael@0: } michael@0: else if (code == Z_OK) michael@0: { michael@0: if (bytesWritten) michael@0: { michael@0: rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: else if (code == Z_BUF_ERROR) michael@0: { michael@0: if (bytesWritten) michael@0: { michael@0: rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } michael@0: break; michael@0: } michael@0: else michael@0: return NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: } /* for */ michael@0: } /* gzip */ michael@0: break; michael@0: michael@0: default: michael@0: rv = mListener->OnDataAvailable(request, aContext, iStr, aSourceOffset, aCount); michael@0: if (NS_FAILED (rv)) michael@0: return rv; michael@0: } /* switch */ michael@0: michael@0: return NS_OK; michael@0: } /* OnDataAvailable */ michael@0: michael@0: michael@0: // XXX/ruslan: need to implement this too michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPCompressConv::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, michael@0: const char *aToType, michael@0: nsISupports *aCtxt, michael@0: nsIInputStream **_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request, michael@0: nsISupports *context, uint64_t offset, michael@0: const char *buffer, uint32_t count) michael@0: { michael@0: if (!mStream) { michael@0: mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID); michael@0: NS_ENSURE_STATE(mStream); michael@0: } michael@0: michael@0: mStream->ShareData(buffer, count); michael@0: michael@0: nsresult rv = mListener->OnDataAvailable(request, context, mStream, michael@0: offset, count); michael@0: michael@0: // Make sure the stream no longer references |buffer| in case our listener michael@0: // is crazy enough to try to read from |mStream| after ODA. michael@0: mStream->ShareData("", 0); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ michael@0: #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ michael@0: #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ michael@0: #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ michael@0: #define COMMENT 0x10 /* bit 4 set: file comment present */ michael@0: #define RESERVED 0xE0 /* bits 5..7: reserved */ michael@0: michael@0: static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ michael@0: michael@0: uint32_t michael@0: nsHTTPCompressConv::check_header(nsIInputStream *iStr, uint32_t streamLen, nsresult *rs) michael@0: { michael@0: enum { GZIP_INIT = 0, GZIP_OS, GZIP_EXTRA0, GZIP_EXTRA1, GZIP_EXTRA2, GZIP_ORIG, GZIP_COMMENT, GZIP_CRC }; michael@0: char c; michael@0: michael@0: *rs = NS_OK; michael@0: michael@0: if (mCheckHeaderDone) michael@0: return streamLen; michael@0: michael@0: while (streamLen) michael@0: { michael@0: switch (hMode) michael@0: { michael@0: case GZIP_INIT: michael@0: uint32_t unused; michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: michael@0: if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0]) michael@0: { michael@0: *rs = NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: return 0; michael@0: } michael@0: michael@0: if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1]) michael@0: { michael@0: *rs = NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: return 0; michael@0: } michael@0: michael@0: if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED) michael@0: { michael@0: *rs = NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: return 0; michael@0: } michael@0: michael@0: mSkipCount++; michael@0: if (mSkipCount == 4) michael@0: { michael@0: mFlags = (unsigned) c & 0377; michael@0: if (mFlags & RESERVED) michael@0: { michael@0: *rs = NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: return 0; michael@0: } michael@0: hMode = GZIP_OS; michael@0: mSkipCount = 0; michael@0: } michael@0: break; michael@0: michael@0: case GZIP_OS: michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: mSkipCount++; michael@0: michael@0: if (mSkipCount == 6) michael@0: hMode = GZIP_EXTRA0; michael@0: break; michael@0: michael@0: case GZIP_EXTRA0: michael@0: if (mFlags & EXTRA_FIELD) michael@0: { michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: mLen = (uInt) c & 0377; michael@0: hMode = GZIP_EXTRA1; michael@0: } michael@0: else michael@0: hMode = GZIP_ORIG; michael@0: break; michael@0: michael@0: case GZIP_EXTRA1: michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: mLen = ((uInt) c & 0377) << 8; michael@0: mSkipCount = 0; michael@0: hMode = GZIP_EXTRA2; michael@0: break; michael@0: michael@0: case GZIP_EXTRA2: michael@0: if (mSkipCount == mLen) michael@0: hMode = GZIP_ORIG; michael@0: else michael@0: { michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: mSkipCount++; michael@0: } michael@0: break; michael@0: michael@0: case GZIP_ORIG: michael@0: if (mFlags & ORIG_NAME) michael@0: { michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: if (c == 0) michael@0: hMode = GZIP_COMMENT; michael@0: } michael@0: else michael@0: hMode = GZIP_COMMENT; michael@0: break; michael@0: michael@0: case GZIP_COMMENT: michael@0: if (mFlags & COMMENT) michael@0: { michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: if (c == 0) michael@0: { michael@0: hMode = GZIP_CRC; michael@0: mSkipCount = 0; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: hMode = GZIP_CRC; michael@0: mSkipCount = 0; michael@0: } michael@0: break; michael@0: michael@0: case GZIP_CRC: michael@0: if (mFlags & HEAD_CRC) michael@0: { michael@0: iStr->Read(&c, 1, &unused); michael@0: streamLen--; michael@0: mSkipCount++; michael@0: if (mSkipCount == 2) michael@0: { michael@0: mCheckHeaderDone = true; michael@0: return streamLen; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: mCheckHeaderDone = true; michael@0: return streamLen; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: return streamLen; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewHTTPCompressConv(nsHTTPCompressConv **aHTTPCompressConv) michael@0: { michael@0: NS_PRECONDITION(aHTTPCompressConv != nullptr, "null ptr"); michael@0: michael@0: if (!aHTTPCompressConv) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aHTTPCompressConv = new nsHTTPCompressConv(); michael@0: michael@0: if (!*aHTTPCompressConv) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*aHTTPCompressConv); michael@0: return NS_OK; michael@0: }