blob: 17bd1c4708ec9224270291bdfa114ba8eeb7338d [file] [log] [blame]
// (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com)
// (C) Copyright 2003-2007 Jonathan Turkanis
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)
// See http://www.boost.org/libs/iostreams for documentation.
// Contains the definitions of the class templates gzip_compressor and
// gzip_decompressor for reading and writing files in the gzip file format
// (RFC 1952). Based in part on work of Jonathan de Halleux; see [...]
#ifndef BOOST_IOSTREAMS_GZIP_HPP_INCLUDED
#define BOOST_IOSTREAMS_GZIP_HPP_INCLUDED
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif
#include <boost/config.hpp> // STATIC_CONSTANT, STDC_NAMESPACE,
// DINKUMWARE_STDLIB, __STL_CONFIG_H.
#include <algorithm> // min.
#include <cstdio> // EOF.
#include <cstddef> // size_t.
#include <ctime> // std::time_t.
#include <memory> // allocator.
#include <boost/config.hpp> // Put size_t in std.
#include <boost/detail/workaround.hpp>
#include <boost/cstdint.hpp> // uint8_t, uint32_t.
#include <boost/iostreams/constants.hpp> // buffer size.
#include <boost/iostreams/detail/adapter/non_blocking_adapter.hpp>
#include <boost/iostreams/detail/adapter/range_adapter.hpp>
#include <boost/iostreams/detail/char_traits.hpp>
#include <boost/iostreams/detail/ios.hpp> // failure.
#include <boost/iostreams/operations.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/pipeline.hpp>
// Must come last.
#if defined(BOOST_MSVC)
# pragma warning(push)
# pragma warning(disable: 4309) // Truncation of constant value.
#endif
#ifdef BOOST_NO_STDC_NAMESPACE
namespace std { using ::time_t; }
#endif
namespace boost { namespace iostreams {
namespace gzip {
using namespace boost::iostreams::zlib;
// Error codes used by gzip_error.
const int zlib_error = 1;
const int bad_crc = 2; // Recorded crc doesn't match data.
const int bad_length = 3; // Recorded length doesn't match data.
const int bad_header = 4; // Malformed header.
const int bad_footer = 5; // Malformed footer.
namespace magic {
// Magic numbers used by gzip header.
const int id1 = 0x1f;
const int id2 = 0x8b;
} // End namespace magic.
namespace method {
// Codes used for the 'CM' byte of the gzip header.
const int deflate = 8;
} // End namespace method.
namespace flags {
// Codes used for the 'FLG' byte of the gzip header.
const int text = 1;
const int header_crc = 2;
const int extra = 4;
const int name = 8;
const int comment = 16;
} // End namespace flags.
namespace extra_flags {
// Codes used for the 'XFL' byte of the gzip header.
const int best_compression = 2;
const int best_speed = 4;
} // End namespace extra_flags.
// Codes used for the 'OS' byte of the gzip header.
const int os_fat = 0;
const int os_amiga = 1;
const int os_vms = 2;
const int os_unix = 3;
const int os_vm_cms = 4;
const int os_atari = 5;
const int os_hpfs = 6;
const int os_macintosh = 7;
const int os_z_system = 8;
const int os_cp_m = 9;
const int os_tops_20 = 10;
const int os_ntfs = 11;
const int os_qdos = 12;
const int os_acorn = 13;
const int os_unknown = 255;
} // End namespace gzip.
//
// Class name: gzip_params.
// Description: Subclass of zlib_params with an additional field
// representing a file name.
//
struct gzip_params : zlib_params {
// Non-explicit constructor.
gzip_params( int level = gzip::default_compression,
int method = gzip::deflated,
int window_bits = gzip::default_window_bits,
int mem_level = gzip::default_mem_level,
int strategy = gzip::default_strategy,
std::string file_name = "",
std::string comment = "",
std::time_t mtime = 0 )
: zlib_params(level, method, window_bits, mem_level, strategy),
file_name(file_name), comment(comment), mtime(mtime)
{ }
std::string file_name;
std::string comment;
std::time_t mtime;
};
//
// Class name: gzip_error.
// Description: Subclass of std::ios_base::failure thrown to indicate
// zlib errors other than out-of-memory conditions.
//
class gzip_error : public BOOST_IOSTREAMS_FAILURE {
public:
explicit gzip_error(int error)
: BOOST_IOSTREAMS_FAILURE("gzip error"),
error_(error), zlib_error_code_(zlib::okay) { }
explicit gzip_error(const zlib_error& e)
: BOOST_IOSTREAMS_FAILURE("gzip error"),
error_(gzip::zlib_error), zlib_error_code_(e.error())
{ }
int error() const { return error_; }
int zlib_error_code() const { return zlib_error_code_; }
private:
int error_;
int zlib_error_code_;
};
//
// Template name: gzip_compressor
// Description: Model of OutputFilter implementing compression in the
// gzip format.
//
template<typename Alloc = std::allocator<char> >
class basic_gzip_compressor : basic_zlib_compressor<Alloc> {
private:
typedef basic_zlib_compressor<Alloc> base_type;
public:
typedef char char_type;
struct category
: dual_use,
filter_tag,
multichar_tag,
closable_tag
{ };
basic_gzip_compressor( const gzip_params& = gzip::default_compression,
int buffer_size = default_device_buffer_size );
template<typename Source>
std::streamsize read(Source& src, char_type* s, std::streamsize n)
{
std::streamsize result = 0;
// Read header.
if (!(flags_ & f_header_done))
result += read_string(s, n, header_);
// Read body.
if (!(flags_ & f_body_done)) {
// Read from basic_zlib_filter.
std::streamsize amt = base_type::read(src, s + result, n - result);
if (amt != -1) {
result += amt;
if (amt < n - result) { // Double-check for EOF.
amt = base_type::read(src, s + result, n - result);
if (amt != -1)
result += amt;
}
}
if (amt == -1)
prepare_footer();
}
// Read footer.
if ((flags_ & f_body_done) != 0 && result < n)
result += read_string(s + result, n - result, footer_);
return result != 0 ? result : -1;
}
template<typename Sink>
std::streamsize write(Sink& snk, const char_type* s, std::streamsize n)
{
if (!(flags_ & f_header_done)) {
std::streamsize amt =
static_cast<std::streamsize>(header_.size() - offset_);
offset_ += boost::iostreams::write(snk, header_.data() + offset_, amt);
if (offset_ == header_.size())
flags_ |= f_header_done;
else
return 0;
}
return base_type::write(snk, s, n);
}
template<typename Sink>
void close(Sink& snk, BOOST_IOS::openmode m)
{
if (m == BOOST_IOS::out) {
try {
// Close zlib compressor.
base_type::close(snk, BOOST_IOS::out);
if (flags_ & f_header_done) {
// Write final fields of gzip file format.
write_long(this->crc(), snk);
write_long(this->total_in(), snk);
}
} catch (...) {
close_impl();
throw;
}
close_impl();
} else {
close_impl();
}
}
private:
static gzip_params normalize_params(gzip_params p);
void prepare_footer();
std::streamsize read_string(char* s, std::streamsize n, std::string& str);
template<typename Sink>
static void write_long(long n, Sink& next)
{
boost::iostreams::put(next, static_cast<char>(0xFF & n));
boost::iostreams::put(next, static_cast<char>(0xFF & (n >> 8)));
boost::iostreams::put(next, static_cast<char>(0xFF & (n >> 16)));
boost::iostreams::put(next, static_cast<char>(0xFF & (n >> 24)));
}
void close_impl()
{
#if BOOST_WORKAROUND(__GNUC__, == 2) && defined(__STL_CONFIG_H) || \
BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1) \
/**/
footer_.erase(0, std::string::npos);
#else
footer_.clear();
#endif
offset_ = 0;
flags_ = 0;
}
enum flag_type {
f_header_done = 1,
f_body_done = f_header_done << 1,
f_footer_done = f_body_done << 1
};
std::string header_;
std::string footer_;
std::size_t offset_;
int flags_;
};
BOOST_IOSTREAMS_PIPABLE(basic_gzip_compressor, 1)
typedef basic_gzip_compressor<> gzip_compressor;
//
// Template name: basic_gzip_decompressor
// Description: Model of InputFilter implementing compression in the
// gzip format.
//
template<typename Alloc = std::allocator<char> >
class basic_gzip_decompressor : basic_zlib_decompressor<Alloc> {
public:
typedef char char_type;
struct category
: multichar_input_filter_tag,
closable_tag
{ };
basic_gzip_decompressor( int window_bits = gzip::default_window_bits,
int buffer_size = default_device_buffer_size );
template<typename Source>
std::streamsize read(Source& src, char_type* s, std::streamsize n)
{
if ((flags_ & f_header_read) == 0) {
non_blocking_adapter<Source> nb(src);
read_header(nb);
flags_ |= f_header_read;
}
if ((flags_ & f_footer_read) != 0)
return -1;
try {
std::streamsize result = 0;
std::streamsize amt;
if ((amt = base_type::read(src, s, n)) != -1) {
result += amt;
if (amt < n) { // Double check for EOF.
amt = base_type::read(src, s + result, n - result);
if (amt != -1)
result += amt;
}
}
if (amt == -1) {
non_blocking_adapter<Source> nb(src);
read_footer(nb);
flags_ |= f_footer_read;
}
return result;
} catch (const zlib_error& e) {
throw gzip_error(e);
}
}
template<typename Source>
void close(Source& src)
{
try {
base_type::close(src, BOOST_IOS::in);
} catch (const zlib_error& e) {
flags_ = 0;
throw gzip_error(e);
}
flags_ = 0;
}
std::string file_name() const { return file_name_; }
std::string comment() const { return comment_; }
bool text() const { return (flags_ & gzip::flags::text) != 0; }
int os() const { return os_; }
std::time_t mtime() const { return mtime_; }
private:
typedef basic_zlib_decompressor<Alloc> base_type;
typedef BOOST_IOSTREAMS_CHAR_TRAITS(char) traits_type;
static bool is_eof(int c) { return traits_type::eq_int_type(c, EOF); }
static gzip_params make_params(int window_bits);
template<typename Source>
static uint8_t read_uint8(Source& src, int error)
{
int c;
if ((c = boost::iostreams::get(src)) == EOF || c == WOULD_BLOCK)
throw gzip_error(error);
return static_cast<uint8_t>(traits_type::to_char_type(c));
}
template<typename Source>
static uint32_t read_uint32(Source& src, int error)
{
uint8_t b1 = read_uint8(src, error);
uint8_t b2 = read_uint8(src, error);
uint8_t b3 = read_uint8(src, error);
uint8_t b4 = read_uint8(src, error);
return b1 + (b2 << 8) + (b3 << 16) + (b4 << 24);
}
template<typename Source>
std::string read_string(Source& src)
{
std::string result;
while (true) {
int c;
if (is_eof(c = boost::iostreams::get(src)))
throw gzip_error(gzip::bad_header);
else if (c == 0)
return result;
else
result += static_cast<char>(c);
}
}
template<typename Source>
void read_header(Source& src) // Source is non-blocking.
{
// Reset saved values.
#if BOOST_WORKAROUND(__GNUC__, == 2) && defined(__STL_CONFIG_H) || \
BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1) \
/**/
file_name_.erase(0, std::string::npos);
comment_.erase(0, std::string::npos);
#else
file_name_.clear();
comment_.clear();
#endif
os_ = gzip::os_unknown;
mtime_ = 0;
int flags;
// Read header, without checking header crc.
if ( boost::iostreams::get(src) != gzip::magic::id1 || // ID1.
boost::iostreams::get(src) != gzip::magic::id2 || // ID2.
is_eof(boost::iostreams::get(src)) || // CM.
is_eof(flags = boost::iostreams::get(src)) ) // FLG.
{
throw gzip_error(gzip::bad_header);
}
mtime_ = read_uint32(src, gzip::bad_header); // MTIME.
read_uint8(src, gzip::bad_header); // XFL.
os_ = read_uint8(src, gzip::bad_header); // OS.
if (flags & boost::iostreams::gzip::flags::text)
flags_ |= f_text;
// Skip extra field. (From J. Halleaux; see note at top.)
if (flags & gzip::flags::extra) {
int length =
static_cast<int>(
read_uint8(src, gzip::bad_header) +
(read_uint8(src, gzip::bad_header) << 8)
);
// length is garbage if EOF but the loop below will quit anyway.
do { }
while (length-- != 0 && !is_eof(boost::iostreams::get(src)));
}
if (flags & gzip::flags::name) // Read file name.
file_name_ = read_string(src);
if (flags & gzip::flags::comment) // Read comment.
comment_ = read_string(src);
if (flags & gzip::flags::header_crc) { // Skip header crc.
read_uint8(src, gzip::bad_header);
read_uint8(src, gzip::bad_header);
}
}
template<typename Source>
void read_footer(Source& src)
{
typename base_type::string_type footer =
this->unconsumed_input();
int c;
while (!is_eof(c = boost::iostreams::get(src)))
footer += c;
detail::range_adapter<input, std::string>
rng(footer.begin(), footer.end());
if (read_uint32(rng, gzip::bad_footer) != this->crc())
throw gzip_error(gzip::bad_crc);
if (static_cast<int>(read_uint32(rng, gzip::bad_footer)) != this->total_out())
throw gzip_error(gzip::bad_length);
}
enum flag_type {
f_header_read = 1,
f_footer_read = f_header_read << 1,
f_text = f_footer_read << 1
};
std::string file_name_;
std::string comment_;
int os_;
std::time_t mtime_;
int flags_;
};
BOOST_IOSTREAMS_PIPABLE(basic_gzip_decompressor, 1)
typedef basic_gzip_decompressor<> gzip_decompressor;
//------------------Implementation of gzip_compressor-------------------------//
template<typename Alloc>
basic_gzip_compressor<Alloc>::basic_gzip_compressor
(const gzip_params& p, int buffer_size)
: base_type(normalize_params(p), buffer_size),
offset_(0), flags_(0)
{
// Calculate gzip header.
bool has_name = !p.file_name.empty();
bool has_comment = !p.comment.empty();
std::string::size_type length =
10 +
(has_name ? p.file_name.size() + 1 : 0) +
(has_comment ? p.comment.size() + 1 : 0);
// + 2; // Header crc confuses gunzip.
int flags =
//gzip::flags::header_crc +
(has_name ? gzip::flags::name : 0) +
(has_comment ? gzip::flags::comment : 0);
int extra_flags =
( p.level == zlib::best_compression ?
gzip::extra_flags::best_compression :
0 ) +
( p.level == zlib::best_speed ?
gzip::extra_flags::best_speed :
0 );
header_.reserve(length);
header_ += gzip::magic::id1; // ID1.
header_ += gzip::magic::id2; // ID2.
header_ += gzip::method::deflate; // CM.
header_ += static_cast<char>(flags); // FLG.
header_ += static_cast<char>(0xFF & p.mtime); // MTIME.
header_ += static_cast<char>(0xFF & (p.mtime >> 8));
header_ += static_cast<char>(0xFF & (p.mtime >> 16));
header_ += static_cast<char>(0xFF & (p.mtime >> 24));
header_ += static_cast<char>(extra_flags); // XFL.
header_ += static_cast<char>(gzip::os_unknown); // OS.
if (has_name) {
header_ += p.file_name;
header_ += '\0';
}
if (has_comment) {
header_ += p.comment;
header_ += '\0';
}
}
template<typename Alloc>
gzip_params basic_gzip_compressor<Alloc>::normalize_params(gzip_params p)
{
p.noheader = true;
p.calculate_crc = true;
return p;
}
template<typename Alloc>
void basic_gzip_compressor<Alloc>::prepare_footer()
{
boost::iostreams::back_insert_device<std::string> out(footer_);
write_long(this->crc(), out);
write_long(this->total_in(), out);
flags_ |= f_body_done;
offset_ = 0;
}
template<typename Alloc>
std::streamsize basic_gzip_compressor<Alloc>::read_string
(char* s, std::streamsize n, std::string& str)
{
std::streamsize avail =
static_cast<std::streamsize>(str.size() - offset_);
std::streamsize amt = (std::min)(avail, n);
std::copy( str.data() + offset_,
str.data() + offset_ + amt,
s );
offset_ += amt;
if ( !(flags_ & f_header_done) &&
offset_ == static_cast<std::size_t>(str.size()) )
{
flags_ |= f_header_done;
}
return amt;
}
//------------------Implementation of gzip_decompressor-----------------------//
template<typename Alloc>
basic_gzip_decompressor<Alloc>::basic_gzip_decompressor
(int window_bits, int buffer_size)
: base_type(make_params(window_bits), buffer_size),
os_(gzip::os_unknown), mtime_(0), flags_(0)
{ }
template<typename Alloc>
gzip_params basic_gzip_decompressor<Alloc>::make_params(int window_bits)
{
gzip_params p;
p.window_bits = window_bits;
p.noheader = true;
p.calculate_crc = true;
return p;
}
//----------------------------------------------------------------------------//
} } // End namespaces iostreams, boost.
#if defined(BOOST_MSVC)
# pragma warning(pop)
#endif
#endif // #ifndef BOOST_IOSTREAMS_GZIP_HPP_INCLUDED