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-2019 Made to Order Software Corporation
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 96763 : InflateInputStreambuf::InflateInputStreambuf(std::streambuf *inbuf, offset_t start_pos)
71 : : FilterInputStreambuf(inbuf)
72 : , m_outvec(getBufferSize())
73 96763 : , m_invec(getBufferSize())
74 : //, m_zs() -- auto-init
75 : //, m_zs_initialized(false) -- auto-init
76 : {
77 : // NOTICE: It is important that this constructor and the methods it
78 : // calls doesn't do anything with the input streambuf inbuf, other
79 : // than repositioning it to the specified position. The reason is
80 : // that this class can be subclassed, and the subclass should get a
81 : // chance to read from the buffer first)
82 :
83 : // zlib init:
84 96763 : m_zs.zalloc = Z_NULL;
85 96763 : m_zs.zfree = Z_NULL;
86 96763 : m_zs.opaque = Z_NULL;
87 :
88 96763 : reset(start_pos);
89 : // We are not checking the return value of reset() and throwing
90 : // an exception in case of an error, because we cannot catch the exception
91 : // in the constructors of subclasses with all compilers.
92 96763 : }
93 :
94 :
95 : /** \brief Clean up the InflateInputStreambuf object.
96 : *
97 : * The destructor makes sure all allocated resources get cleaned up.
98 : */
99 193526 : InflateInputStreambuf::~InflateInputStreambuf()
100 : {
101 : // Dealloc z_stream stuff
102 96763 : int const err(inflateEnd(&m_zs));
103 96763 : if(err != Z_OK)
104 : {
105 : // in a destructor we cannot throw...
106 : OutputStringStream msgs; // LCOV_EXCL_LINE
107 : msgs << "InflateInputStreambuf::~InflateInputStreambuf(): inflateEnd() failed" // LCOV_EXCL_LINE
108 : << ": " << zError(err); // LCOV_EXCL_LINE
109 : /** \TODO
110 : * Write an error callback interface and call that instead of
111 : * using std::cerr...
112 : */
113 : std::cerr << msgs.str() << std::endl; // LCOV_EXCL_LINE
114 : }
115 96763 : }
116 :
117 :
118 : /** \brief Called when more data is required.
119 : *
120 : * The function ensures that at least one byte is available
121 : * in the input area by updating the pointers to the input area
122 : * and reading more data in from the input sequence if required.
123 : *
124 : * This function actually passes the data through the zlib library
125 : * to decompress it.
126 : *
127 : * \return The value of that character on success or
128 : * std::streambuf::traits_type::eof() on failure.
129 : */
130 763145 : std::streambuf::int_type InflateInputStreambuf::underflow()
131 : {
132 : // If not really underflow do not fill buffer
133 : // (is that really possible?!)
134 763145 : if(gptr() < egptr())
135 : {
136 : return traits_type::to_int_type(*gptr()); // LCOV_EXCL_LINE
137 : }
138 :
139 : // Prepare _outvec and get array pointers
140 763145 : m_zs.avail_out = getBufferSize();
141 763145 : m_zs.next_out = reinterpret_cast<unsigned char *>(&m_outvec[0]);
142 :
143 : // Inflate until _outvec is full
144 : // eof (or I/O prob) on _inbuf will break out of loop too.
145 763145 : int err(Z_OK);
146 3437901 : while(m_zs.avail_out > 0 && err == Z_OK)
147 : {
148 1337378 : if(m_zs.avail_in == 0)
149 : {
150 : // fill m_invec
151 668901 : std::streamsize const bc(m_inbuf->sgetn(&m_invec[0], getBufferSize()));
152 : /** \FIXME
153 : * Add I/O error handling while inflating data from a file.
154 : */
155 668901 : m_zs.next_in = reinterpret_cast<unsigned char *>(&m_invec[0]);
156 668901 : m_zs.avail_in = bc;
157 : // If we could not read any new data (bc == 0) and inflate is not
158 : // done it will return Z_BUF_ERROR and thus breaks out of the
159 : // loop. This means we do not have to respond to the situation
160 : // where we cannot read more bytes here.
161 : }
162 :
163 1337378 : err = inflate(&m_zs, Z_NO_FLUSH);
164 : }
165 :
166 : // Normally the number of inflated bytes will be the
167 : // full length of the output buffer, but if we can't read
168 : // more input from the _inbuf streambuf, we end up with
169 : // less.
170 763145 : offset_t const inflated_bytes = getBufferSize() - m_zs.avail_out;
171 763145 : setg(&m_outvec[0], &m_outvec[0], &m_outvec[0] + inflated_bytes);
172 :
173 : /** \FIXME
174 : * Look at the error returned from inflate here, if there is
175 : * some way to report it to the InflateInputStreambuf user.
176 : * Until I find out I'll just print a warning to stdout.
177 : * This at least throws, we probably want to create a log
178 : * mechanism that the end user can connect to with a callback.
179 : */
180 763145 : if(err != Z_OK && err != Z_STREAM_END)
181 : {
182 20 : OutputStringStream msgs;
183 10 : msgs << "InflateInputStreambuf::underflow(): inflate failed"
184 20 : << ": " << zError(err);
185 : // Throw an exception to immediately exit to the read() or similar
186 : // function and make istream set badbit
187 10 : throw IOException(msgs.str());
188 : }
189 :
190 763135 : if(inflated_bytes > 0)
191 : {
192 668477 : return traits_type::to_int_type(*gptr());
193 : }
194 :
195 94658 : return traits_type::eof();
196 : }
197 :
198 :
199 :
200 : /** \brief Initializes the stream buffer.
201 : *
202 : * This function resets the zlib stream and purges input and output buffers.
203 : * It also repositions the input streambuf at stream_position.
204 : *
205 : * \warning
206 : * This method is called in the constructor, so it must not read anything
207 : * from the input streambuf m_inbuf (see notice in constructor.)
208 : *
209 : * \param[in] stream_position A position to reset the inbuf to before
210 : * reading. Specify -1 to read from the
211 : * current position.
212 : *
213 : * \sa InflateInputStreambuf()
214 : */
215 191431 : bool InflateInputStreambuf::reset(offset_t stream_position)
216 : {
217 191431 : if(stream_position >= 0)
218 : {
219 : // reposition m_inbuf
220 96763 : m_inbuf->pubseekpos(stream_position);
221 : }
222 :
223 : // m_zs.next_in and avail_in must be set according to
224 : // zlib.h (inline doc).
225 191431 : m_zs.next_in = reinterpret_cast<Bytef *>(&m_invec[0]);
226 191431 : m_zs.avail_in = 0;
227 :
228 191431 : int err(Z_OK);
229 191431 : if(m_zs_initialized)
230 : {
231 : // just reset it
232 94668 : err = inflateReset(&m_zs);
233 : }
234 : else
235 : {
236 : // initialize it
237 96763 : err = inflateInit2(&m_zs, -MAX_WBITS);
238 : /* windowBits is passed < 0 to tell that there is no zlib header.
239 : Note that in this case inflate *requires* an extra "dummy" byte
240 : after the compressed stream in order to complete decompression
241 : and return Z_STREAM_END. We always have an extra "dummy" byte,
242 : because there is always some trailing data after the compressed
243 : data (either the next entry or the central directory. */
244 96763 : m_zs_initialized = true;
245 : }
246 :
247 : // streambuf init:
248 : // The important thing here, is that
249 : // - the pointers are not NULL (which would mean unbuffered)
250 : // - and that gptr() is not less than egptr() (so we trigger underflow
251 : // the first time data is read).
252 191431 : setg(&m_outvec[0], &m_outvec[0] + getBufferSize(), &m_outvec[0] + getBufferSize());
253 :
254 191431 : return err == Z_OK;
255 : }
256 :
257 :
258 3 : } // zipios namespace
259 :
260 : // Local Variables:
261 : // mode: cpp
262 : // indent-tabs-mode: nil
263 : // c-basic-offset: 4
264 : // tab-width: 4
265 : // End:
266 :
267 : // vim: ts=4 sw=4 et
|