Line data Source code
1 : /* 2 : Zipios -- a small C++ library that provides easy access to .zip files. 3 : 4 : Copyright (C) 2000-2007 Thomas Sondergaard 5 : Copyright (c) 2015-2022 Made to Order Software Corp. All Rights Reserved 6 : 7 : This library is free software; you can redistribute it and/or 8 : modify it under the terms of the GNU Lesser General Public 9 : License as published by the Free Software Foundation; either 10 : version 2.1 of the License, or (at your option) any later version. 11 : 12 : This library is distributed in the hope that it will be useful, 13 : but WITHOUT ANY WARRANTY; without even the implied warranty of 14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 : Lesser General Public License for more details. 16 : 17 : You should have received a copy of the GNU Lesser General Public 18 : License along with this library; if not, write to the Free Software 19 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 : */ 21 : 22 : /** \file 23 : * \brief Implementation of zipios::InflateInputStreambuf. 24 : * 25 : * This file defines the various functions found in the 26 : * zipios::InflateInputStreambuf class. It filters a stream buffer 27 : * to decompress data that was compressed using the zlib library. 28 : */ 29 : 30 : #include "inflateinputstreambuf.hpp" 31 : 32 : #include "zipios/zipiosexceptions.hpp" 33 : 34 : #include "zipios_common.hpp" 35 : 36 : 37 : namespace zipios 38 : { 39 : 40 : /** \class InflateInputStreambuf 41 : * \brief A stream buffer to inflate data previous compressed with zlib. 42 : * 43 : * The InflateInputStreambuf class is an input stream filter, that 44 : * inflates the input from the attached input stream. 45 : * 46 : * Deflation/Inflation is a compression/decompression method used 47 : * in gzip and zip. The zlib library is used to perform the actual 48 : * inflation, this class only wraps the functionality in an input 49 : * stream filter. 50 : * 51 : * \todo 52 : * Add support for bzip2, lzma compressions. 53 : */ 54 : 55 : 56 : 57 : /** \brief Initialize a InflateInputStreambuf. 58 : * 59 : * The constructor initializes the various stream buffers and 60 : * setup the stream start position using the \p start_pos 61 : * parameter. 62 : * 63 : * Data will be inflated (decompressed using zlib) before being 64 : * returned. 65 : * 66 : * \param[in,out] inbuf The streambuf to use for input. 67 : * \param[in] start_pos A position to reset the inbuf to before reading. Specify 68 : * -1 to not change the position. 69 : */ 70 53061 : InflateInputStreambuf::InflateInputStreambuf(std::streambuf * inbuf, offset_t start_pos) 71 : : FilterInputStreambuf(inbuf) 72 53061 : , m_outvec(getBufferSize()) 73 106122 : , m_invec(getBufferSize()) 74 : { 75 : // NOTICE: It is important that this constructor and the methods it 76 : // calls doesn't do anything with the input streambuf inbuf, other 77 : // than repositioning it to the specified \p start_pos. The reason is 78 : // that this class can be sub-classed, and the sub-class should get a 79 : // chance to read from the buffer first 80 : 81 53061 : reset(start_pos); 82 : // We are not checking the return value of reset() and throwing 83 : // an exception in case of an error, because we cannot catch the 84 : // exception in the constructors of sub-classes with all compilers. 85 53061 : } 86 : 87 : 88 : /** \brief Clean up the InflateInputStreambuf object. 89 : * 90 : * The destructor makes sure all allocated resources get cleaned up. 91 : */ 92 53061 : InflateInputStreambuf::~InflateInputStreambuf() 93 : { 94 : // Dealloc z_stream stuff 95 53061 : int const err(inflateEnd(&m_zs)); 96 53061 : if(err != Z_OK) 97 : { 98 : // in a destructor we cannot throw... 99 : OutputStringStream msgs; // LCOV_EXCL_LINE 100 : msgs << "InflateInputStreambuf::~InflateInputStreambuf(): inflateEnd() failed" // LCOV_EXCL_LINE 101 : << ": " << zError(err); // LCOV_EXCL_LINE 102 : /** \TODO 103 : * Write an error callback interface and call that instead of 104 : * using std::cerr... 105 : */ 106 : std::cerr << msgs.str() << std::endl; // LCOV_EXCL_LINE 107 0 : } 108 53061 : } 109 : 110 : 111 : /** \brief Called when more data is required. 112 : * 113 : * The function ensures that at least one byte is available 114 : * in the input area by updating the pointers to the input area 115 : * and reading more data in from the input sequence if required. 116 : * 117 : * This function actually passes the data through the zlib library 118 : * to decompress it. 119 : * 120 : * \return The value of that character on success or 121 : * std::streambuf::traits_type::eof() on failure. 122 : */ 123 399682 : std::streambuf::int_type InflateInputStreambuf::underflow() 124 : { 125 : // If not really underflow do not fill buffer 126 : // (is that really possible?!) 127 399682 : if(gptr() < egptr()) 128 : { 129 : return traits_type::to_int_type(*gptr()); // LCOV_EXCL_LINE 130 : } 131 : 132 : // Prepare _outvec and get array pointers 133 399682 : m_zs.avail_out = getBufferSize(); 134 399682 : m_zs.next_out = reinterpret_cast<unsigned char *>(&m_outvec[0]); 135 : 136 : // Inflate until _outvec is full 137 : // eof (or I/O prob) on _inbuf will break out of loop too. 138 399682 : int err(Z_OK); 139 1097747 : while(m_zs.avail_out > 0 && err == Z_OK) 140 : { 141 698065 : if(m_zs.avail_in == 0) 142 : { 143 : // fill m_invec 144 349141 : std::streamsize const bc(m_inbuf->sgetn(&m_invec[0], getBufferSize())); 145 : /** \FIXME 146 : * Add I/O error handling while inflating data from a file. 147 : */ 148 349141 : m_zs.next_in = reinterpret_cast<unsigned char *>(&m_invec[0]); 149 349141 : m_zs.avail_in = bc; 150 : // If we could not read any new data (bc == 0) and inflate is not 151 : // done it will return Z_BUF_ERROR and thus breaks out of the 152 : // loop. This means we do not have to respond to the situation 153 : // where we cannot read more bytes here. 154 : } 155 : 156 698065 : err = inflate(&m_zs, Z_NO_FLUSH); 157 : } 158 : 159 : // Normally the number of inflated bytes will be the 160 : // full length of the output buffer, but if we can't read 161 : // more input from the _inbuf streambuf, we end up with 162 : // less. 163 399682 : offset_t const inflated_bytes = getBufferSize() - m_zs.avail_out; 164 399682 : setg(&m_outvec[0], &m_outvec[0], &m_outvec[0] + inflated_bytes); 165 : 166 : /** \FIXME 167 : * Look at the error returned from inflate here, if there is 168 : * some way to report it to the InflateInputStreambuf user. 169 : * Until I find out I'll just print a warning to stdout. 170 : * This at least throws, we probably want to create a log 171 : * mechanism that the end user can connect to with a callback. 172 : */ 173 399682 : if(err != Z_OK && err != Z_STREAM_END) 174 : { 175 10 : OutputStringStream msgs; 176 : msgs << "InflateInputStreambuf::underflow(): inflate failed" 177 10 : << ": " << zError(err); 178 : // Throw an exception to immediately exit to the read() or similar 179 : // function and make istream set badbit 180 10 : throw IOException(msgs.str()); 181 10 : } 182 : 183 399672 : if(inflated_bytes > 0) 184 : { 185 348924 : return traits_type::to_int_type(*gptr()); 186 : } 187 : 188 50748 : return traits_type::eof(); 189 : } 190 : 191 : 192 : 193 : /** \brief Initializes the stream buffer. 194 : * 195 : * This function resets the zlib stream and purges input and output buffers. 196 : * It also repositions the input streambuf at stream_position. 197 : * 198 : * \warning 199 : * This method is called in the constructor, so it must not read anything 200 : * from the input streambuf m_inbuf (see notice in constructor.) 201 : * 202 : * \param[in] stream_position A position to reset the inbuf to before 203 : * reading. Specify -1 to read from the 204 : * current position. 205 : * 206 : * \sa InflateInputStreambuf() 207 : */ 208 103819 : bool InflateInputStreambuf::reset(offset_t stream_position) 209 : { 210 103819 : if(stream_position >= 0) 211 : { 212 : // reposition m_inbuf 213 53061 : m_inbuf->pubseekpos(stream_position); 214 : } 215 : 216 : // m_zs.next_in and avail_in must be set according to 217 : // zlib.h (inline doc). 218 103819 : m_zs.next_in = reinterpret_cast<Bytef *>(&m_invec[0]); 219 103819 : m_zs.avail_in = 0; 220 : 221 103819 : int err(Z_OK); 222 103819 : if(m_zs_initialized) 223 : { 224 : // just reset it 225 50758 : err = inflateReset(&m_zs); 226 : } 227 : else 228 : { 229 : // initialize it 230 53061 : err = inflateInit2(&m_zs, -MAX_WBITS); 231 : /* windowBits is passed < 0 to tell that there is no zlib header. 232 : Note that in this case inflate *requires* an extra "dummy" byte 233 : after the compressed stream in order to complete decompression 234 : and return Z_STREAM_END. We always have an extra "dummy" byte, 235 : because there is always some trailing data after the compressed 236 : data (either the next entry or the central directory. */ 237 53061 : m_zs_initialized = true; 238 : } 239 : 240 : // streambuf init: 241 : // The important thing here, is that 242 : // - the pointers are not NULL (which would mean unbuffered) 243 : // - and that gptr() is not less than egptr() (so we trigger underflow 244 : // the first time data is read). 245 103819 : setg(&m_outvec[0], &m_outvec[0] + getBufferSize(), &m_outvec[0] + getBufferSize()); 246 : 247 103819 : return err == Z_OK; 248 : } 249 : 250 : 251 : } // zipios namespace 252 : 253 : // Local Variables: 254 : // mode: cpp 255 : // indent-tabs-mode: nil 256 : // c-basic-offset: 4 257 : // tab-width: 4 258 : // End: 259 : 260 : // vim: ts=4 sw=4 et