Dactyloidae/mailnews/base/util/nsMsgCompressIStream.cpp
2019-11-17 00:10:21 +08:00

227 lines
6.2 KiB
C++

/* 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 "nsMsgCompressIStream.h"
#include "prio.h"
#include "prmem.h"
#include <algorithm>
#define BUFFER_SIZE 16384
nsMsgCompressIStream::nsMsgCompressIStream() :
m_dataptr(nullptr),
m_dataleft(0),
m_inflateAgain(false)
{
}
nsMsgCompressIStream::~nsMsgCompressIStream()
{
Close();
}
NS_IMPL_ISUPPORTS(nsMsgCompressIStream, nsIInputStream,
nsIAsyncInputStream)
nsresult nsMsgCompressIStream::InitInputStream(nsIInputStream *rawStream)
{
// protect against repeat calls
if (m_iStream)
return NS_ERROR_UNEXPECTED;
// allocate some memory for buffering
m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
if (!m_zbuf)
return NS_ERROR_OUT_OF_MEMORY;
// allocate some memory for buffering
m_databuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
if (!m_databuf)
return NS_ERROR_OUT_OF_MEMORY;
// set up zlib object
m_zstream.zalloc = Z_NULL;
m_zstream.zfree = Z_NULL;
m_zstream.opaque = Z_NULL;
// http://zlib.net/manual.html is rather silent on the topic, but
// perl's Compress::Raw::Zlib manual says:
// -WindowBits
// To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS.
if (inflateInit2(&m_zstream, -MAX_WBITS) != Z_OK)
return NS_ERROR_FAILURE;
m_iStream = rawStream;
return NS_OK;
}
nsresult nsMsgCompressIStream::DoInflation()
{
// if there's something in the input buffer of the zstream, process it.
m_zstream.next_out = (Bytef *) m_databuf.get();
m_zstream.avail_out = BUFFER_SIZE;
int zr = inflate(&m_zstream, Z_SYNC_FLUSH);
// inflate() should normally be called until it returns
// Z_STREAM_END or an error, and Z_BUF_ERROR just means
// unable to progress any further (possible if we filled
// an output buffer exactly)
if (zr == Z_BUF_ERROR || zr == Z_STREAM_END)
zr = Z_OK;
// otherwise it's an error
if (zr != Z_OK)
return NS_ERROR_FAILURE;
// http://www.zlib.net/manual.html says:
// If inflate returns Z_OK and with zero avail_out, it must be called
// again after making room in the output buffer because there might be
// more output pending.
m_inflateAgain = m_zstream.avail_out ? false : true;
// set the pointer to the start of the buffer, and the count to how
// based on how many bytes are left unconsumed.
m_dataptr = m_databuf.get();
m_dataleft = BUFFER_SIZE - m_zstream.avail_out;
return NS_OK;
}
/* void close (); */
NS_IMETHODIMP nsMsgCompressIStream::Close()
{
return CloseWithStatus(NS_OK);
}
NS_IMETHODIMP nsMsgCompressIStream::CloseWithStatus(nsresult reason)
{
nsresult rv = NS_OK;
if (m_iStream)
{
// pass the status through to our wrapped stream
nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream);
if (asyncInputStream)
rv = asyncInputStream->CloseWithStatus(reason);
// tidy up
m_iStream = nullptr;
inflateEnd(&m_zstream);
}
// clean up all the buffers
m_zbuf = nullptr;
m_databuf = nullptr;
m_dataptr = nullptr;
m_dataleft = 0;
return rv;
}
/* unsigned long long available (); */
NS_IMETHODIMP nsMsgCompressIStream::Available(uint64_t *aResult)
{
if (!m_iStream)
return NS_BASE_STREAM_CLOSED;
// check if there's anything still in flight
if (!m_dataleft && m_inflateAgain)
{
nsresult rv = DoInflation();
NS_ENSURE_SUCCESS(rv, rv);
}
// we'll be returning this many to the next read, guaranteed
if (m_dataleft)
{
*aResult = m_dataleft;
return NS_OK;
}
// this value isn't accurate, but will give a good true/false
// indication for idle purposes, and next read will fill
// m_dataleft, so we'll have an accurate count for the next call.
return m_iStream->Available(aResult);
}
/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
NS_IMETHODIMP nsMsgCompressIStream::Read(char * aBuf, uint32_t aCount, uint32_t *aResult)
{
if (!m_iStream)
{
*aResult = 0;
return NS_OK;
}
// There are two stages of buffering:
// * m_zbuf contains the compressed data from the remote server
// * m_databuf contains the uncompressed raw bytes for consumption
// by the local client.
//
// Each buffer will only be filled when the following buffers
// have been entirely consumed.
//
// m_dataptr and m_dataleft are respectively a pointer to the
// unconsumed portion of m_databuf and the number of bytes
// of uncompressed data remaining in m_databuf.
//
// both buffers have a maximum size of BUFFER_SIZE, so it is
// possible that multiple inflate passes will be required to
// consume all of m_zbuf.
while (!m_dataleft)
{
// get some more data if we don't already have any
if (!m_inflateAgain)
{
uint32_t bytesRead;
nsresult rv = m_iStream->Read(m_zbuf.get(), (uint32_t)BUFFER_SIZE, &bytesRead);
NS_ENSURE_SUCCESS(rv, rv);
if (!bytesRead)
return NS_BASE_STREAM_CLOSED;
m_zstream.next_in = (Bytef *) m_zbuf.get();
m_zstream.avail_in = bytesRead;
}
nsresult rv = DoInflation();
NS_ENSURE_SUCCESS(rv, rv);
}
*aResult = std::min(m_dataleft, aCount);
if (*aResult)
{
memcpy(aBuf, m_dataptr, *aResult);
m_dataptr += *aResult;
m_dataleft -= *aResult;
}
return NS_OK;
}
/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
NS_IMETHODIMP nsMsgCompressIStream::ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsMsgCompressIStream::AsyncWait(nsIInputStreamCallback *callback, uint32_t flags, uint32_t amount, nsIEventTarget *target)
{
if (!m_iStream)
return NS_BASE_STREAM_CLOSED;
nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream);
if (asyncInputStream)
return asyncInputStream->AsyncWait(callback, flags, amount, target);
return NS_OK;
}
/* boolean isNonBlocking (); */
NS_IMETHODIMP nsMsgCompressIStream::IsNonBlocking(bool *aNonBlocking)
{
*aNonBlocking = false;
return NS_OK;
}