Files
wxWidgets/src/common/lzmastream.cpp

404 lines
11 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/common/lzmastream.cpp
// Purpose: Implementation of LZMA stream classes
// Author: Vadim Zeitlin
// Created: 2018-03-29
// Copyright: (c) 2018 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#if wxUSE_LIBLZMA && wxUSE_STREAMS
#include "wx/lzmastream.h"
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/translation.h"
#endif // WX_PRECOMP
#include <lzma.h>
namespace wxPrivate
{
// ----------------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------------
const size_t wxLZMA_BUF_SIZE = 4096;
// ----------------------------------------------------------------------------
// Private helpers
// ----------------------------------------------------------------------------
// Simpler wrapper around lzma_stream, taking care of initializing and
// finalizing it.
struct wxLZMAStream : lzma_stream
{
wxLZMAStream()
{
memset(this, 0, sizeof(lzma_stream));
}
~wxLZMAStream()
{
lzma_end(this);
}
};
} // namespace wxPrivate
using namespace wxPrivate;
// ============================================================================
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// Functions
// ----------------------------------------------------------------------------
wxVersionInfo wxGetLibLZMAVersionInfo()
{
const uint32_t ver = lzma_version_number();
// For now ignore the "stability" part of the version.
return wxVersionInfo
(
"liblzma",
ver / 10000000,
(ver % 10000000) / 10000,
(ver % 10000) / 10
);
}
// ----------------------------------------------------------------------------
// wxLZMAData: common helpers for compression and decompression
// ----------------------------------------------------------------------------
wxLZMAData::wxLZMAData()
{
m_stream = new wxLZMAStream;
m_streamBuf = new wxUint8[wxLZMA_BUF_SIZE];
m_pos = 0;
}
wxLZMAData::~wxLZMAData()
{
delete [] m_streamBuf;
delete m_stream;
}
// ----------------------------------------------------------------------------
// wxLZMAInputStream: decompression
// ----------------------------------------------------------------------------
void wxLZMAInputStream::Init()
{
// We don't specify any memory usage limit nor any flags, not even
// LZMA_CONCATENATED recommended by liblzma documentation, because we don't
// foresee the need to support concatenated compressed files for now.
const lzma_ret rc = lzma_stream_decoder(m_stream, UINT64_MAX, 0);
switch ( rc )
{
case LZMA_OK:
// Skip setting m_lasterror below.
return;
case LZMA_MEM_ERROR:
wxLogError(_("Failed to allocate memory for LZMA decompression."));
break;
default:
wxLogError(_("Failed to initialize LZMA decompression: "
"unexpected error %u."),
rc);
break;
}
m_lasterror = wxSTREAM_READ_ERROR;
}
size_t wxLZMAInputStream::OnSysRead(void* outbuf, size_t size)
{
m_stream->next_out = static_cast<uint8_t*>(outbuf);
m_stream->avail_out = size;
// Decompress input as long as we don't have any errors (including EOF, as
// it doesn't make sense to continue after it neither) and have space to
// decompress it to.
while ( m_lasterror == wxSTREAM_NO_ERROR && m_stream->avail_out > 0 )
{
// Get more input data if needed.
if ( !m_stream->avail_in )
{
m_parent_i_stream->Read(m_streamBuf, wxLZMA_BUF_SIZE);
m_stream->next_in = m_streamBuf;
m_stream->avail_in = m_parent_i_stream->LastRead();
if ( !m_stream->avail_in )
{
if ( m_parent_i_stream->GetLastError() == wxSTREAM_EOF )
{
// We have reached end of the underlying stream.
m_lasterror = wxSTREAM_EOF;
break;
}
m_lasterror = wxSTREAM_READ_ERROR;
return 0;
}
}
// Do decompress.
const lzma_ret rc = lzma_code(m_stream, LZMA_RUN);
wxString err;
switch ( rc )
{
case LZMA_OK:
continue;
case LZMA_STREAM_END:
m_lasterror = wxSTREAM_EOF;
continue;
case LZMA_FORMAT_ERROR:
err = wxTRANSLATE("input is not in XZ format");
break;
case LZMA_OPTIONS_ERROR:
err = wxTRANSLATE("input compressed using unknown XZ option");
break;
case LZMA_DATA_ERROR:
case LZMA_BUF_ERROR:
err = wxTRANSLATE("input is corrupted");
break;
default:
err = wxTRANSLATE("unknown decompression error");
break;
}
wxLogError(_("LZMA decompression error: %s"), wxGetTranslation(err));
m_lasterror = wxSTREAM_READ_ERROR;
return 0;
}
// Return the number of bytes actually read, this may be less than the
// requested size if we hit EOF.
size -= m_stream->avail_out;
m_pos += size;
return size;
}
// ----------------------------------------------------------------------------
// wxLZMAOutputStream: compression
// ----------------------------------------------------------------------------
void wxLZMAOutputStream::Init(int level)
{
if ( level == -1 )
level = LZMA_PRESET_DEFAULT;
// Use the check type recommended by liblzma documentation.
const lzma_ret rc = lzma_easy_encoder(m_stream, level, LZMA_CHECK_CRC64);
switch ( rc )
{
case LZMA_OK:
// Prepare for the first call to OnSysWrite().
m_stream->next_out = m_streamBuf;
m_stream->avail_out = wxLZMA_BUF_SIZE;
// Skip setting m_lasterror below.
return;
case LZMA_MEM_ERROR:
wxLogError(_("Failed to allocate memory for LZMA compression."));
break;
default:
wxLogError(_("Failed to initialize LZMA compression: "
"unexpected error %u."),
rc);
break;
}
m_lasterror = wxSTREAM_WRITE_ERROR;
}
size_t wxLZMAOutputStream::OnSysWrite(const void *inbuf, size_t size)
{
m_stream->next_in = static_cast<const uint8_t*>(inbuf);
m_stream->avail_in = size;
// Compress as long as we have any input data, but stop at first error as
// it's useless to try to continue after it (or even starting if the stream
// had already been in an error state).
while ( m_lasterror == wxSTREAM_NO_ERROR && m_stream->avail_in > 0 )
{
// Flush the output buffer if necessary.
if ( !UpdateOutputIfNecessary() )
return 0;
const lzma_ret rc = lzma_code(m_stream, LZMA_RUN);
wxString err;
switch ( rc )
{
case LZMA_OK:
continue;
case LZMA_MEM_ERROR:
err = wxTRANSLATE("out of memory");
break;
case LZMA_STREAM_END:
// This is unexpected as we don't use LZMA_FINISH here.
wxFAIL_MSG( "Unexpected LZMA stream end" );
wxFALLTHROUGH;
default:
err = wxTRANSLATE("unknown compression error");
break;
}
wxLogError(_("LZMA compression error: %s"), wxGetTranslation(err));
m_lasterror = wxSTREAM_WRITE_ERROR;
return 0;
}
m_pos += size;
return size;
}
bool wxLZMAOutputStream::UpdateOutput()
{
// Write the buffer contents to the real output, taking care only to write
// as much of it as we actually have, as the buffer can (and very likely
// will) be incomplete.
const size_t numOut = wxLZMA_BUF_SIZE - m_stream->avail_out;
m_parent_o_stream->Write(m_streamBuf, numOut);
if ( m_parent_o_stream->LastWrite() != numOut )
{
m_lasterror = wxSTREAM_WRITE_ERROR;
return false;
}
return true;
}
bool wxLZMAOutputStream::UpdateOutputIfNecessary()
{
if ( !m_stream->avail_out )
{
if ( !UpdateOutput() )
return false;
m_stream->next_out = m_streamBuf;
m_stream->avail_out = wxLZMA_BUF_SIZE;
}
return true;
}
bool wxLZMAOutputStream::DoFlush(bool finish)
{
const lzma_action action = finish ? LZMA_FINISH : LZMA_FULL_FLUSH;
while ( m_lasterror == wxSTREAM_NO_ERROR )
{
if ( !UpdateOutputIfNecessary() )
break;
const lzma_ret rc = lzma_code(m_stream, action);
wxString err;
switch ( rc )
{
case LZMA_OK:
continue;
case LZMA_STREAM_END:
// Don't forget to output the last part of the data.
return UpdateOutput();
case LZMA_MEM_ERROR:
err = wxTRANSLATE("out of memory");
break;
default:
err = wxTRANSLATE("unknown compression error");
break;
}
wxLogError(_("LZMA compression error when flushing output: %s"),
wxGetTranslation(err));
m_lasterror = wxSTREAM_WRITE_ERROR;
}
return false;
}
bool wxLZMAOutputStream::Close()
{
if ( !DoFlush(true) )
return false;
m_stream->next_out = m_streamBuf;
m_stream->avail_out = wxLZMA_BUF_SIZE;
return wxFilterOutputStream::Close() && IsOk();
}
// ----------------------------------------------------------------------------
// wxLZMAClassFactory: allow creating streams from extension/MIME type
// ----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(wxLZMAClassFactory, wxFilterClassFactory);
static wxLZMAClassFactory g_wxLZMAClassFactory;
wxLZMAClassFactory::wxLZMAClassFactory()
{
if ( this == &g_wxLZMAClassFactory )
PushFront();
}
const wxChar * const *
wxLZMAClassFactory::GetProtocols(wxStreamProtocolType type) const
{
static const wxChar *mime[] = { wxT("application/xz"), NULL };
static const wxChar *encs[] = { wxT("xz"), NULL };
static const wxChar *exts[] = { wxT(".xz"), NULL };
const wxChar* const* ret = NULL;
switch ( type )
{
case wxSTREAM_PROTOCOL: ret = encs; break;
case wxSTREAM_MIMETYPE: ret = mime; break;
case wxSTREAM_ENCODING: ret = encs; break;
case wxSTREAM_FILEEXT: ret = exts; break;
}
return ret;
}
#endif // wxUSE_LIBLZMA && wxUSE_STREAMS