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 the zipios::ZipLocalEntry class.
24 : *
25 : * This file is the implementation of the zipios::ZipLocalEntry class
26 : * which handles zipios::FileEntry's found in Zip archives.
27 : */
28 :
29 : #include "ziplocalentry.hpp"
30 :
31 : #include "zipios/zipiosexceptions.hpp"
32 : #include "zipios/dosdatetime.hpp"
33 :
34 : #include "zipios_common.hpp"
35 :
36 :
37 : namespace zipios
38 : {
39 :
40 :
41 : /** \brief Various definitions for local blocks.
42 : *
43 : * The ZipLocalEntry needs a signature, a flag, and makes use
44 : * of a structure declaration (although we cannot really use
45 : * that structure.)
46 : */
47 : namespace
48 : {
49 :
50 : /** \brief The signature of a local entry.
51 : *
52 : * This value represents the signature of a Zip archive block defining
53 : * a ZipLocalEntry.
54 : *
55 : * \code
56 : * "PK 3.4"
57 : * \endcode
58 : */
59 : uint32_t const g_signature = 0x04034b50;
60 :
61 :
62 : /** \brief A bit in the general purpose flags.
63 : *
64 : * This mask is used to know whether the size and CRC are saved in
65 : * the header or after the header. At this time Zipios does not
66 : * support such trailing data as it makes use of the compressed
67 : * and uncompressed sizes to properly stream the output data.
68 : *
69 : * This is bit 3. (see point 4.4.4 in doc/zip-format.txt)
70 : */
71 : uint16_t const g_trailing_data_descriptor = 1 << 3;
72 :
73 :
74 : /** \brief ZipLocalEntry Header
75 : *
76 : * This structure shows how the header of the ZipLocalEntry is defined.
77 : * Note that the file name and extra field have a variable size which
78 : * is defined in two 16 bit values just before they appear.
79 : *
80 : * The filename cannot be empty, however, the extra field can (and
81 : * usually is).
82 : *
83 : * \note
84 : * This structure is NOT used directly only for its sizeof() and
85 : * documentation because that way zipios can work on little and big
86 : * endians without the need to know the endianness of your computer.
87 : */
88 : struct ZipLocalEntryHeader
89 : {
90 : uint32_t m_signature;
91 : uint16_t m_extract_version;
92 : uint16_t m_general_purpose_bitfield;
93 : uint16_t m_compress_method;
94 : uint32_t m_dosdatetime;
95 : uint32_t m_crc_32;
96 : uint32_t m_compressed_size;
97 : uint32_t m_uncompressed_size;
98 : uint16_t m_filename_len;
99 : uint16_t m_extra_field_len;
100 : //uint8_t m_filename[m_filename_len];
101 : //uint8_t m_extra_field[m_extra_field_len];
102 : };
103 :
104 :
105 : } // no name namespace
106 :
107 :
108 :
109 : /** \class ZipLocalEntry
110 : * \brief An implementation of the FileEntry for Zip archives.
111 : *
112 : * A concrete implementation of the abstract FileEntry base class for
113 : * ZipFile entries, specifically for representing the information
114 : * present in the local headers of file entries in a zip file.
115 : */
116 :
117 :
118 :
119 : /** \brief Create a default ZipLocalEntry objects.
120 : *
121 : * This constructor is used to create a default ZipLocalEntry object.
122 : */
123 171443 : ZipLocalEntry::ZipLocalEntry()
124 171443 : : FileEntry(FilePath(""))
125 : {
126 171443 : }
127 :
128 :
129 : /** \brief Copy of the ZipLocalEntry from any kind of FileEntry object.
130 : *
131 : * This function is used when copying a DirectoryEntry to a
132 : * ZipCentralDirectoryEntry object when creating a copy while
133 : * saving a Zip archive.
134 : *
135 : * \param[in] src The source to copy in this new ZipLocalEntry.
136 : */
137 121751 : ZipLocalEntry::ZipLocalEntry(FileEntry const & src)
138 : : FileEntry(src)
139 121751 : , m_is_directory(src.isDirectory())
140 : {
141 121751 : }
142 :
143 :
144 : /** \brief Create a clone of a ZipLocalEntry object.
145 : *
146 : * This function allocates a new ZipLocalEntry on the heap and returns
147 : * a copy of this ZipLocalEntry object in it.
148 : *
149 : * \return A new ZipLocalEntry which is a clone of this ZipLocalEntry object.
150 : */
151 : FileEntry::pointer_t ZipLocalEntry::clone() const // LCOV_EXCL_LINE
152 : {
153 : // It makes sense to keep the clone() function for this class
154 : // but since it is internal and never allocated as is (we use
155 : // the ZipCentralDirectoryEntry instead) it is marked as
156 : // non-reachable by the coverage tests
157 : return std::make_shared<ZipLocalEntry>(*this); // LCOV_EXCL_LINE
158 : }
159 :
160 :
161 : /** \brief Clean up a ZipLocalEntry object.
162 : *
163 : * This function ensures proper clean up of a ZipLocationEntry object.
164 : */
165 294270 : ZipLocalEntry::~ZipLocalEntry()
166 : {
167 294270 : }
168 :
169 :
170 : /** \brief Check whether the filename represents a directory.
171 : *
172 : * This function checks the last character of the filename, if it
173 : * is a separator ('/') then the function returns true meaning
174 : * that the file represents a directory.
175 : *
176 : * \return true if the entry represents a directory.
177 : */
178 749041 : bool ZipLocalEntry::isDirectory() const
179 : {
180 749041 : return m_is_directory;
181 : }
182 :
183 :
184 : /** \brief Compare two file entries for equality.
185 : *
186 : * This function compares most of the fields between two file
187 : * entries to see whether they are equal or not.
188 : *
189 : * \note
190 : * This function calls the base class isEqual() and also verifies
191 : * that the ZipLocalEntry fields are equal.
192 : *
193 : * \note
194 : * This function is also used to compare ZipCDirEntry since none
195 : * of the additional field participate in the comparison.
196 : *
197 : * \param[in] file_entry The file entry to compare this against.
198 : *
199 : * \return true if both FileEntry objects are considered equal.
200 : */
201 59909 : bool ZipLocalEntry::isEqual(FileEntry const & file_entry) const
202 : {
203 59909 : ZipLocalEntry const * const ze(dynamic_cast<ZipLocalEntry const * const>(&file_entry));
204 59909 : if(ze == nullptr)
205 : {
206 738 : return false;
207 : }
208 59171 : return FileEntry::isEqual(file_entry)
209 59161 : && m_extract_version == ze->m_extract_version
210 59161 : && m_general_purpose_bitfield == ze->m_general_purpose_bitfield
211 118332 : && m_is_directory == ze->m_is_directory;
212 : //&& m_compressed_size == ze->m_compressed_size -- ignore in comparison
213 : }
214 :
215 :
216 : /** \brief Retrieve the size of the file when compressed.
217 : *
218 : * This function returns the compressed size of the entry. If the
219 : * entry is not stored in a compressed format, the uncompressed
220 : * size is returned.
221 : *
222 : * \return The compressed size of the entry.
223 : */
224 7349 : size_t ZipLocalEntry::getCompressedSize() const
225 : {
226 7349 : return m_compressed_size;
227 : }
228 :
229 :
230 : /** \brief Retrieve the size of the header.
231 : *
232 : * This function dynamically determines the size of the Zip archive
233 : * header necessary for this FileEntry.
234 : *
235 : * This function returns the size of the Zip archive header.
236 : *
237 : * \return The size of the header in bytes.
238 : */
239 121750 : size_t ZipLocalEntry::getHeaderSize() const
240 : {
241 : // Note that the structure is 32 bytes because of an alignment
242 : // and attempting to use options to avoid the alignment would
243 : // not be portable so we use a hard coded value (yuck!)
244 : return 30 /* sizeof(ZipLocalEntryHeader) */
245 121750 : + m_filename.length() + (m_is_directory ? 1 : 0)
246 121750 : + m_extra_field.size();
247 : }
248 :
249 :
250 : /** \brief Set the size when the file is compressed.
251 : *
252 : * This function saves the compressed size of the entry in this object.
253 : *
254 : * By default the compressed size is viewed as the same as the
255 : * uncompressed size (i.e. as if STORED was used for the compression
256 : * method.)
257 : *
258 : * \param[in] size Value to set the compressed size field of the entry to.
259 : */
260 121750 : void ZipLocalEntry::setCompressedSize(size_t size)
261 : {
262 121750 : m_compressed_size = size;
263 121750 : }
264 :
265 :
266 : /** \brief Save the CRC of the entry.
267 : *
268 : * This function saves the CRC field in this FileEntry field.
269 : *
270 : * This function has the side of setting the m_has_crc_32 flag
271 : * to true meaning that the CRC was defined as expected.
272 : *
273 : * \param[in] crc Value to set the CRC field to.
274 : */
275 121750 : void ZipLocalEntry::setCrc(uint32_t crc)
276 : {
277 121750 : m_crc_32 = crc;
278 121750 : m_has_crc_32 = true;
279 121750 : }
280 :
281 :
282 : /** \brief Is there a trailing data descriptor?
283 : *
284 : * This function checks the bit in the General Purpose Flags to know
285 : * whether the local entry header includes the compressed and uncompressed
286 : * sizes or whether these are defined after the compressed data.
287 : *
288 : * The trailing data buffer looks like this:
289 : *
290 : * \code
291 : * signature (PK 8.7) -- OPTIONAL -- 32 bit
292 : * CRC 32 -- 32 bit
293 : * compressed size -- 32 or 64 bit
294 : * uncompressed size -- 32 or 64 bit
295 : * \endcode
296 : *
297 : * When a trailing data buffer is defined, the header has the compressed
298 : * and uncompressed sizes set to zero.
299 : *
300 : * \note
301 : * Zipios does not support such a scheme.
302 : *
303 : * \return true if this file makes use of a trailing data buffer.
304 : */
305 53061 : bool ZipLocalEntry::hasTrailingDataDescriptor() const
306 : {
307 53061 : return (m_general_purpose_bitfield & g_trailing_data_descriptor) != 0;
308 : }
309 :
310 :
311 : /** \brief Read one local entry from \p is.
312 : *
313 : * This function verifies that the input stream starts with a local entry
314 : * signature. If so, it reads the input stream for a complete local entry.
315 : *
316 : * Calling this function first marks the FileEntry object as invalid. If
317 : * the read succeeds in full, then the entry is again marked as valid.
318 : *
319 : * If a read fails, the function throws an exception as defined in
320 : * the various zipRead() functions.
321 : *
322 : * \note
323 : * Some of the data found in the local entry on disk are not kept in
324 : * this class because there is nothing we can do with it.
325 : *
326 : * \param[in] is The input stream to read from.
327 : */
328 112242 : void ZipLocalEntry::read(std::istream & is)
329 : {
330 112242 : m_valid = false; // set to true upon successful completion.
331 :
332 : // // Before reading anything we record the position in the stream
333 : // // This is a field in the central directory entry, but not
334 : // // in the local entry. After all, we know where we are, anyway.
335 : // zlh.rel_offset_loc_head = is.tellg() ;
336 :
337 112242 : uint32_t signature(0);
338 112242 : zipRead(is, signature); // 32
339 112242 : if(g_signature != signature)
340 : {
341 : // put stream in error state and return
342 10 : is.setstate(std::ios::failbit);
343 10 : throw IOException("ZipLocalEntry::read() expected a signature but got some other data");
344 : }
345 :
346 112232 : uint16_t compress_method(0);
347 112232 : uint32_t dosdatetime(0);
348 112232 : uint32_t compressed_size(0);
349 112232 : uint32_t uncompressed_size(0);
350 112232 : uint16_t filename_len(0);
351 112232 : uint16_t extra_field_len(0);
352 112232 : std::string filename;
353 :
354 : // See the ZipLocalEntryHeader for more details
355 112232 : zipRead(is, m_extract_version); // 16
356 112232 : zipRead(is, m_general_purpose_bitfield); // 16
357 112232 : zipRead(is, compress_method); // 16
358 112232 : zipRead(is, dosdatetime); // 32
359 112232 : zipRead(is, m_crc_32); // 32
360 112232 : zipRead(is, compressed_size); // 32
361 112232 : zipRead(is, uncompressed_size); // 32
362 112232 : zipRead(is, filename_len); // 16
363 112232 : zipRead(is, extra_field_len); // 16
364 112232 : zipRead(is, filename, filename_len); // string
365 112232 : zipRead(is, m_extra_field, extra_field_len); // buffer
366 : /** \todo add support for zip64, some of those parameters
367 : * may be 0xFFFFF...FFFF in which case the 64 bit
368 : * header should be read
369 : */
370 :
371 : // the FilePath() will remove the trailing slash so make sure
372 : // to defined the m_is_directory ahead of time!
373 112232 : m_is_directory = !filename.empty() && filename.back() == g_separator;
374 :
375 112232 : m_compress_method = static_cast<StorageMethod>(compress_method);
376 112232 : DOSDateTime t;
377 112232 : t.setDOSDateTime(dosdatetime);
378 112232 : m_unix_time = t.getUnixTimestamp();
379 112232 : m_compressed_size = compressed_size;
380 112232 : m_uncompressed_size = uncompressed_size;
381 112232 : m_filename = FilePath(filename);
382 :
383 112232 : m_valid = true;
384 112232 : }
385 :
386 :
387 : /** \brief Write a ZipLocalEntry to \p os.
388 : *
389 : * This function writes this ZipLocalEntry header to the specified
390 : * output stream.
391 : *
392 : * \exception IOException
393 : * If an error occurs while writing to the output stream, the function
394 : * throws an IOException.
395 : *
396 : * \param[in] os The output stream where the ZipLocalEntry is written.
397 : */
398 243501 : void ZipLocalEntry::write(std::ostream & os)
399 : {
400 243501 : if(m_filename.length() > 0x10000
401 243501 : || m_extra_field.size() > 0x10000)
402 : {
403 1 : throw InvalidStateException("ZipLocalEntry::write(): file name or extra field too large to save in a Zip file.");
404 : }
405 :
406 : /** todo: add support for 64 bit zip archive
407 : */
408 : #if INTPTR_MAX != INT32_MAX
409 243500 : if(m_compressed_size >= 0x100000000UL
410 243500 : || m_uncompressed_size >= 0x100000000UL)
411 : {
412 : // these are really big files, we do not currently test such so ignore in coverage
413 : //
414 : // Note: The compressed size is known at the end, we seek back to
415 : // this header and re-save it with the info; thus the error
416 : // is caught then if it was not out of bounds earlier.
417 : throw InvalidStateException("The size of this file is too large to fit in a 32 bit zip archive."); // LCOV_EXCL_LINE
418 : }
419 : #endif
420 :
421 243500 : std::string filename(m_filename);
422 243500 : if(m_is_directory)
423 : {
424 9630 : filename += g_separator;
425 : }
426 :
427 243500 : std::uint16_t compress_method(static_cast<uint8_t>(m_compress_method));
428 243500 : if(m_compression_level == COMPRESSION_LEVEL_NONE)
429 : {
430 13166 : compress_method = static_cast<uint8_t>(StorageMethod::STORED);
431 : }
432 :
433 243500 : DOSDateTime t;
434 243500 : t.setUnixTimestamp(m_unix_time);
435 243500 : std::uint32_t dosdatetime(t.getDOSDateTime()); // type could use DOSDateTime::dosdatetime_t
436 243500 : std::uint32_t compressed_size(m_compressed_size);
437 243500 : std::uint32_t uncompressed_size(m_uncompressed_size);
438 243500 : std::uint16_t filename_len(filename.length());
439 243500 : std::uint16_t extra_field_len(m_extra_field.size());
440 :
441 : // See the ZipLocalEntryHeader for more details
442 243500 : zipWrite(os, g_signature); // 32
443 243500 : zipWrite(os, m_extract_version); // 16
444 243500 : zipWrite(os, m_general_purpose_bitfield); // 16
445 243500 : zipWrite(os, compress_method); // 16
446 243500 : zipWrite(os, dosdatetime); // 32
447 243500 : zipWrite(os, m_crc_32); // 32
448 243500 : zipWrite(os, compressed_size); // 32
449 243500 : zipWrite(os, uncompressed_size); // 32
450 243500 : zipWrite(os, filename_len); // 16
451 243500 : zipWrite(os, extra_field_len); // 16
452 243500 : zipWrite(os, filename); // string
453 243500 : zipWrite(os, m_extra_field); // buffer
454 243500 : }
455 :
456 :
457 : } // zipios namespace
458 :
459 : // Local Variables:
460 : // mode: cpp
461 : // indent-tabs-mode: nil
462 : // c-basic-offset: 4
463 : // tab-width: 4
464 : // End:
465 :
466 : // vim: ts=4 sw=4 et
|