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 the zipios::ZipOutputStreambuf class.
24 : *
25 : * This file includes the functions necessary to write data to a Zip
26 : * archive.
27 : */
28 :
29 : #include "zipoutputstreambuf.hpp"
30 :
31 : #include "zipios/zipiosexceptions.hpp"
32 :
33 : #include "ziplocalentry.hpp"
34 : #include "zipendofcentraldirectory.hpp"
35 :
36 :
37 : namespace zipios
38 : {
39 :
40 :
41 : namespace
42 : {
43 :
44 :
45 : /** \brief Help function used to write the central directory.
46 : *
47 : * When you create a Zip archive, it includes a central directory where
48 : * all the meta data about each file is saved. This function saves an
49 : * array of entries in an output stream to generate the Zip file
50 : * central directory.
51 : *
52 : * \param[in] os The output stream.
53 : * \param[in] entries The array of entries to save in this central directory.
54 : * \param[in] comment The zip archive global comment.
55 : */
56 243 : void writeZipCentralDirectory(std::ostream &os, FileEntry::vector_t& entries, std::string const& comment)
57 : {
58 486 : ZipEndOfCentralDirectory eocd(comment);
59 243 : eocd.setOffset(os.tellp()); // start position
60 243 : eocd.setCount(entries.size());
61 :
62 243 : size_t central_directory_size(0);
63 171708 : for(auto it = entries.begin(); it != entries.end(); ++it)
64 : {
65 171467 : (*it)->write(os);
66 171465 : central_directory_size += (*it)->getHeaderSize();
67 : }
68 :
69 241 : eocd.setCentralDirectorySize(central_directory_size);
70 241 : eocd.write(os);
71 239 : }
72 :
73 :
74 : } // no name namespace
75 :
76 :
77 : /** \class ZipOutputStreambuf
78 : * \brief Handle the output buffer of a zip archive.
79 : *
80 : * The ZipOutputStreambuf class is a zip archive output
81 : * streambuf filter.
82 : */
83 :
84 :
85 : /** \brief Initialize a ZipOutputStreambuf object.
86 : *
87 : * Note that a new initialized ZipOutputStreambuf is not ready to
88 : * accept data, putNextEntry() must be invoked at least once first.
89 : *
90 : * \param[in] outbuf The streambuf to use for output.
91 : */
92 243 : ZipOutputStreambuf::ZipOutputStreambuf(std::streambuf * outbuf)
93 243 : : DeflateOutputStreambuf(outbuf)
94 : //, m_zip_comment("") -- auto-init
95 : //, m_entries() -- auto-init
96 : //, m_compression_level(FileEntry::COMPRESSION_LEVEL_DEFAULT) -- auto-init
97 : //, m_open_entry(false) -- auto-init
98 : //, m_open(true) -- auto-init
99 : {
100 243 : }
101 :
102 :
103 : /** \brief Clean up the buffer.
104 : *
105 : * This function cleans up this output buffer. In general this ensures
106 : * that the data still cached gets flushed.
107 : *
108 : * \warning
109 : * This function may gobble up some important exceptions. If you want
110 : * to make sure that the file is properly written, you must call the
111 : * finish() function (or the close() function) to fully terminate the
112 : * file. If these functions do not fail, then the output file is
113 : * considered valid and you can keep it. The finish() function can fail
114 : * because of a comment or a file which are too large, for example.
115 : */
116 729 : ZipOutputStreambuf::~ZipOutputStreambuf()
117 : {
118 : // avoid possible exceptions when writing the central directory
119 : try
120 : {
121 243 : finish();
122 : }
123 1 : catch(...)
124 : {
125 : }
126 486 : }
127 :
128 :
129 : /** \brief Close this buffer entry.
130 : *
131 : * Closes the current output buffer entry and positions the stream
132 : * write pointer at the beginning of the next entry.
133 : */
134 171952 : void ZipOutputStreambuf::closeEntry()
135 : {
136 171952 : if(!m_open_entry)
137 : {
138 486 : return;
139 : }
140 :
141 171466 : switch(m_compression_level)
142 : {
143 : case FileEntry::COMPRESSION_LEVEL_NONE:
144 77735 : overflow(); // flush
145 77735 : break;
146 :
147 : default:
148 93731 : closeStream();
149 93731 : break;
150 :
151 : }
152 :
153 171466 : updateEntryHeaderInfo();
154 171466 : setEntryClosedState();
155 : }
156 :
157 :
158 : /** \brief Close the output stream buffer.
159 : *
160 : * This function calls finish to make sure that any cached
161 : * data is saved and then close the stream buffer.
162 : */
163 239 : void ZipOutputStreambuf::close()
164 : {
165 239 : finish();
166 239 : }
167 :
168 :
169 : /** \brief Finish up an output stream buffer.
170 : *
171 : * Closes the current entry (if one is open), then writes the Zip
172 : * Central Directory Structure closing the ZipOutputStream. The
173 : * output stream (std::ostream) that the zip archive is being
174 : * written to is not closed.
175 : */
176 724 : void ZipOutputStreambuf::finish()
177 : {
178 724 : if(!m_open)
179 : {
180 481 : return;
181 : }
182 243 : m_open = false;
183 :
184 482 : std::ostream os(m_outbuf);
185 243 : closeEntry();
186 243 : writeZipCentralDirectory(os, m_entries, m_zip_comment);
187 : }
188 :
189 :
190 : /** \brief Start saving an entry in the output buffer.
191 : *
192 : * Opens the next entry in the zip archive and returns a const pointer to a
193 : * FileEntry object for the entry.
194 : *
195 : * If a previous entry was still open, the function calls closeEntry()
196 : * first.
197 : *
198 : * \param[in] entry The entry to be saved and made current.
199 : */
200 171467 : void ZipOutputStreambuf::putNextEntry(FileEntry::pointer_t entry)
201 : {
202 171467 : closeEntry();
203 :
204 : // if the method is STORED force uncompressed data
205 171467 : if(entry->getMethod() == StorageMethod::STORED)
206 : {
207 : // force to "no compression" when the method is STORED
208 76700 : m_compression_level = FileEntry::COMPRESSION_LEVEL_NONE;
209 : }
210 : else
211 : {
212 : // get the user defined compression level
213 94767 : m_compression_level = entry->getLevel();
214 : }
215 171467 : m_overflown_bytes = 0;
216 171467 : switch(m_compression_level)
217 : {
218 : case FileEntry::COMPRESSION_LEVEL_NONE:
219 77736 : setp(&m_invec[0], &m_invec[0] + getBufferSize());
220 77736 : break;
221 :
222 : default:
223 93731 : init(m_compression_level);
224 93731 : break;
225 :
226 : }
227 :
228 171467 : m_entries.push_back(entry);
229 :
230 342934 : std::ostream os(m_outbuf);
231 :
232 : // Update entry header info
233 171467 : entry->setEntryOffset(os.tellp());
234 : /** \TODO
235 : * Rethink the design as we have to force a call to the correct
236 : * write() function?
237 : */
238 171467 : static_cast<ZipLocalEntry *>(entry.get())->ZipLocalEntry::write(os);
239 :
240 171466 : m_open_entry = true;
241 171466 : }
242 :
243 :
244 : /** \brief Set the archive comment.
245 : *
246 : * This function saves a global comment for the Zip archive.
247 : *
248 : * You may set it to an empty string which means that no comment
249 : * will be saved.
250 : *
251 : * The comment is saved when the first entry is saved so it
252 : * has to be put in there early on.
253 : *
254 : * \param[in] comment The comment to save in the Zip archive.
255 : */
256 243 : void ZipOutputStreambuf::setComment(std::string const& comment)
257 : {
258 243 : m_zip_comment = comment;
259 243 : }
260 :
261 :
262 : //
263 : // Protected and private methods
264 : //
265 :
266 : /** \brief Implementation of the overflow() function.
267 : *
268 : * When writing to a buffer, the overflow() function gets called when
269 : * there is no more room in the output buffer. The buffer is expected
270 : * to flush the data to disk and reset the buffer availability.
271 : *
272 : * \param[in] c The character that made it all happen. Maybe EOF.
273 : *
274 : * \return EOF if the function fails, 0 otherwise.
275 : */
276 745232 : int ZipOutputStreambuf::overflow(int c)
277 : {
278 745232 : size_t const size(pptr() - pbase());
279 745232 : m_overflown_bytes += size;
280 745232 : switch(m_compression_level)
281 : {
282 : case FileEntry::COMPRESSION_LEVEL_NONE:
283 : {
284 : // Ok, we are STORED, so we handle it ourselves to avoid "side
285 : // effects" from zlib, which adds markers every now and then.
286 84955 : size_t const bc(m_outbuf->sputn(&m_invec[0], size));
287 84955 : if(size != bc)
288 : {
289 : // Without implementing our own stream in our test, this
290 : // cannot really be reached because it is all happening
291 : // inside the same loop in ZipFile::saveCollectionToArchive()
292 : throw IOException("ZipOutputStreambuf::overflow(): write to buffer failed."); // LCOV_EXCL_LINE
293 : }
294 84955 : setp(&m_invec[0], &m_invec[0] + getBufferSize());
295 :
296 84955 : if(c != EOF)
297 : {
298 7220 : *pptr() = c;
299 7220 : pbump(1);
300 : }
301 :
302 84955 : return 0;
303 : }
304 :
305 : default:
306 660277 : return DeflateOutputStreambuf::overflow(c);
307 :
308 : }
309 : }
310 :
311 :
312 :
313 : /** \brief Implement the sync() functionality.
314 : *
315 : * This virtual function is reimplemented to make sure that the system
316 : * does not run a default sync() function.
317 : *
318 : * This function calls the DeflateOutputStreambuf::sync() function which
319 : * returns -1 because it will not "synchronize" the input buffer.
320 : */
321 : int ZipOutputStreambuf::sync() // LCOV_EXCL_LINE
322 : {
323 : return DeflateOutputStreambuf::sync(); // LCOV_EXCL_LINE
324 : }
325 :
326 :
327 :
328 : /** \brief Mark the current entry as closed.
329 : *
330 : * After the putNextEntry() call and saving of the file content, the
331 : * closeEntry() function can be called to close the entry. The entry
332 : * is really closed when this setEntryClosedState() is called.
333 : */
334 171466 : void ZipOutputStreambuf::setEntryClosedState()
335 : {
336 171466 : m_open_entry = false;
337 :
338 : /** \FIXME
339 : * Update put pointers to trigger overflow on write. Overflow
340 : * should then return EOF while m_open_entry is false.
341 : */
342 171466 : }
343 :
344 :
345 : /** \brief Save the header information.
346 : *
347 : * This function saves parameters that are now available in the header
348 : * of the local entry.
349 : *
350 : * These parameters include:
351 : *
352 : * \li The uncompressed size of the entry
353 : * \li The compressed size of the entry
354 : * \li The CRC32 of the input file (before the compression)
355 : */
356 171466 : void ZipOutputStreambuf::updateEntryHeaderInfo()
357 : {
358 171466 : if(!m_open_entry)
359 : {
360 0 : return;
361 : }
362 :
363 342932 : std::ostream os(m_outbuf);
364 171466 : int const curr_pos(os.tellp());
365 :
366 : // update fields in m_entries.back()
367 342932 : FileEntry::pointer_t entry(m_entries.back());
368 171466 : entry->setSize(getSize());
369 171466 : entry->setCrc(getCrc32());
370 : /** \TODO
371 : * Rethink the design as we have to force a call to the correct
372 : * getHeaderSize() function?
373 : */
374 171466 : entry->setCompressedSize(curr_pos - entry->getEntryOffset() - static_cast<ZipLocalEntry *>(entry.get())->ZipLocalEntry::getHeaderSize());
375 :
376 : // write ZipLocalEntry header to header position
377 171466 : os.seekp(entry->getEntryOffset());
378 : /** \TODO
379 : * Rethink the design as we have to force a call to the correct write()
380 : * function?
381 : */
382 171466 : static_cast<ZipLocalEntry *>(entry.get())->ZipLocalEntry::write(os);
383 171466 : os.seekp(curr_pos);
384 : }
385 :
386 :
387 3 : } // zipios namespace
388 :
389 : // Local Variables:
390 : // mode: cpp
391 : // indent-tabs-mode: nil
392 : // c-basic-offset: 4
393 : // tab-width: 4
394 : // End:
395 :
396 : // vim: ts=4 sw=4 et
|