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::ZipCentralDirectoryEntry.
24 : *
25 : * This file includes the implementation of the zipios::ZipCentralDirectoryEntry
26 : * which is a zipios::FileEntry used when reading the central
27 : * directory of a Zip archive.
28 : */
29 :
30 : #include "zipcentraldirectoryentry.hpp"
31 :
32 : #include "zipios/zipiosexceptions.hpp"
33 : #include "zipios/dosdatetime.hpp"
34 :
35 : #include "zipios_common.hpp"
36 :
37 :
38 : namespace zipios
39 : {
40 :
41 :
42 :
43 : namespace
44 : {
45 :
46 :
47 : /** \brief The signature of a ZipCentralDirectoryEntry.
48 : *
49 : * This value represents the signature of a Zip Central Directory Entry.
50 : *
51 : * The signature represents:
52 : *
53 : * \code
54 : * "PK 1.2"
55 : * \endcode
56 : */
57 : uint32_t const g_signature = 0x02014b50;
58 :
59 :
60 : // The zip codes (values are pre-shifted)
61 : uint16_t const g_msdos = 0x0000;
62 : uint16_t const g_amiga = 0x0100;
63 : uint16_t const g_open_vms = 0x0200;
64 : uint16_t const g_unix = 0x0300;
65 : uint16_t const g_vm_cms = 0x0400;
66 : uint16_t const g_atari_st = 0x0500;
67 : uint16_t const g_os2_hpfs = 0x0600;
68 : uint16_t const g_macintosh = 0x0700;
69 : uint16_t const g_z_system = 0x0800;
70 : uint16_t const g_cpm = 0x0900;
71 : uint16_t const g_windows = 0x0A00;
72 : uint16_t const g_mvs = 0x0B00;
73 : uint16_t const g_vse = 0x0C00;
74 : uint16_t const g_acorn_risc = 0x0D00;
75 : uint16_t const g_vfat = 0x0E00;
76 : uint16_t const g_alternate_vms = 0x0F00;
77 : uint16_t const g_beos = 0x1000;
78 : uint16_t const g_tandem = 0x1100;
79 : uint16_t const g_os400 = 0x1200;
80 : uint16_t const g_osx = 0x1300;
81 :
82 :
83 : /** \brief The header of a ZipCentralDirectoryEntry in a Zip archive.
84 : *
85 : * This structure shows how the header of the ZipCentralDirectoryEntry is defined.
86 : * Note that the file name, file comment, and extra field have a
87 : * variable size which is defined in three 16 bit values before
88 : * they appear.
89 : *
90 : * The filename cannot be empty, however, the file comment and the
91 : * extra field can (and usually are.)
92 : *
93 : * \note
94 : * This structure is NOT used directly only for its sizeof() and
95 : * documentation because that way zipios can work on little and big
96 : * endians without the need to know the endianness of your computer.
97 : *
98 : * \bug
99 : * This structure is aligned on 4 bytes since it includes some uint32_t
100 : * values and thus has a size of 48 bytes instead of 46.
101 : */
102 : struct ZipCentralDirectoryEntryHeader
103 : {
104 : uint32_t m_signature;
105 : uint16_t m_writer_version;
106 : uint16_t m_extract_version;
107 : uint16_t m_general_purpose_bitfield;
108 : uint16_t m_compress_method;
109 : uint32_t m_dosdatetime;
110 : uint32_t m_crc_32;
111 : uint32_t m_compressed_size;
112 : uint32_t m_uncompressed_size;
113 : uint16_t m_filename_len;
114 : uint16_t m_extra_field_len;
115 : uint16_t m_file_comment_len;
116 : uint16_t m_disk_num_start;
117 : uint16_t m_intern_file_attr;
118 : uint32_t m_extern_file_attr;
119 : uint32_t m_relative_offset_location_header;
120 : //uint8_t m_filename[m_filename_len];
121 : //uint8_t m_extra_field[m_extra_field_len];
122 : //uint8_t m_file_comment[m_file_comment_len];
123 : };
124 :
125 :
126 : } // no name namespace
127 :
128 :
129 : /** \class ZipCentralDirectoryEntry
130 : * \brief A specialization of ZipLocalEntry for
131 : *
132 : * Specialization of ZipLocalEntry, that add fields for storing the
133 : * extra information, that is only present in the entries in the zip
134 : * central directory and not in the local entry headers.
135 : */
136 :
137 :
138 : /** \brief Initializes a default ZipCentralDirectoryEntry object.
139 : *
140 : * This function initializes a ZipCentralDirectoryEntry object that is
141 : * expected to be used to read data from an input stream representing
142 : * a Zip archive.
143 : */
144 59201 : ZipCentralDirectoryEntry::ZipCentralDirectoryEntry()
145 : {
146 59201 : }
147 :
148 :
149 : /** \brief Initialize a ZipCentralDirectoryEntry.
150 : *
151 : * This function initializes a ZipCentralDirectoryEntry from a FileEntry.
152 : *
153 : * The function copies all the data it may be interested in and then
154 : * ignores the FileEntry.
155 : *
156 : * \param[in] entry The entry to transform in a ZipCentralDirectoryEntry.
157 : */
158 121751 : ZipCentralDirectoryEntry::ZipCentralDirectoryEntry(FileEntry const & entry)
159 121751 : : ZipLocalEntry(entry)
160 : {
161 121751 : }
162 :
163 :
164 : /** \brief Clean up the entry.
165 : *
166 : * The destructor makes sure the entry is fully cleaned up.
167 : */
168 182028 : ZipCentralDirectoryEntry::~ZipCentralDirectoryEntry()
169 : {
170 182028 : }
171 :
172 :
173 : /** \brief Compute and return the current header size.
174 : *
175 : * This function computes the size that this entry will take in the
176 : * Central Directory of the Zip archive.
177 : *
178 : * \return The total size of the Central Directory entry on disk.
179 : */
180 121749 : size_t ZipCentralDirectoryEntry::getHeaderSize() const
181 : {
182 : /** \todo
183 : * Add support for 64 bit Zip. At this time this function returns
184 : * an invalid size if the filename, extra field, or file comment
185 : * sizes are more than allowed in an older version of the Zip format.
186 : */
187 : // Note that the structure is 48 bytes because of an alignment
188 : // and attempting to use options to avoid the alignment would
189 : // not be portable so we use a hard coded value (yuck!)
190 : return 46 /* sizeof(ZipCentralDirectoryEntryHeader) */
191 121749 : + m_filename.length() + (m_is_directory ? 1 : 0)
192 121749 : + m_extra_field.size()
193 121749 : + m_comment.length();
194 : }
195 :
196 :
197 : /** \brief Create a clone of this Central Directory entry.
198 : *
199 : * This function allocates a new copy of this ZipCentralDirectoryEntry
200 : * object and returns a smart pointer to it.
201 : *
202 : * \return A smart pointer to the copy.
203 : */
204 1076 : FileEntry::pointer_t ZipCentralDirectoryEntry::clone() const
205 : {
206 1076 : return std::make_shared<ZipCentralDirectoryEntry>(*this);
207 : }
208 :
209 :
210 : /** \brief Read a Central Directory entry.
211 : *
212 : * This function reads one Central Directory entry from the specified
213 : * input stream. If anything goes wrong with the input stream, the read
214 : * function will throw an error.
215 : *
216 : * \note
217 : * While reading the entry is marked as invalid. If the read fails, the
218 : * entry will remain invalid. On success, the function restores the status
219 : * back to valid.
220 : *
221 : * \note
222 : * If the signature or some other parameter is found to be invalid, then
223 : * the input stream is marked as failed and an exception is thrown.
224 : *
225 : * \exception IOException
226 : * This exception is thrown if the signature read does not match the
227 : * signature of a Central Directory entry. This can only mean a bug
228 : * in a Zip writer or an invalid/corrupt file altogether.
229 : *
230 : * \param[in] is The input stream to read from.
231 : *
232 : * \sa write()
233 : */
234 59201 : void ZipCentralDirectoryEntry::read(std::istream & is)
235 : {
236 59201 : m_valid = false; // set back to true upon successful completion below.
237 :
238 : // verify the signature
239 : uint32_t signature;
240 59201 : zipRead(is, signature);
241 59201 : if(g_signature != signature)
242 : {
243 10 : is.setstate(std::ios::failbit);
244 10 : throw IOException("ZipCentralDirectoryEntry::read(): Expected Central Directory entry signature not found");
245 : }
246 :
247 59191 : uint16_t writer_version(0);
248 59191 : uint16_t compress_method(0);
249 59191 : uint32_t dosdatetime(0);
250 59191 : uint32_t compressed_size(0);
251 59191 : uint32_t uncompressed_size(0);
252 59191 : uint32_t rel_offset_loc_head(0);
253 59191 : uint16_t filename_len(0);
254 59191 : uint16_t extra_field_len(0);
255 59191 : uint16_t file_comment_len(0);
256 59191 : uint16_t intern_file_attr(0);
257 59191 : uint32_t extern_file_attr(0);
258 59191 : uint16_t disk_num_start(0);
259 59191 : std::string filename;
260 :
261 : // read the header
262 59191 : zipRead(is, writer_version); // 16
263 59191 : zipRead(is, m_extract_version); // 16
264 59191 : zipRead(is, m_general_purpose_bitfield); // 16
265 59191 : zipRead(is, compress_method); // 16
266 59191 : zipRead(is, dosdatetime); // 32
267 59191 : zipRead(is, m_crc_32); // 32
268 59191 : zipRead(is, compressed_size); // 32
269 59191 : zipRead(is, uncompressed_size); // 32
270 59191 : zipRead(is, filename_len); // 16
271 59191 : zipRead(is, extra_field_len); // 16
272 59191 : zipRead(is, file_comment_len); // 16
273 59191 : zipRead(is, disk_num_start); // 16
274 59191 : zipRead(is, intern_file_attr); // 16
275 59191 : zipRead(is, extern_file_attr); // 32
276 59191 : zipRead(is, rel_offset_loc_head); // 32
277 59191 : zipRead(is, filename, filename_len); // string
278 59191 : zipRead(is, m_extra_field, extra_field_len); // buffer
279 59191 : zipRead(is, m_comment, file_comment_len); // string
280 : /** \todo check whether this was a 64 bit header and make sure
281 : * to read the 64 bit header too if so
282 : */
283 :
284 : // the FilePath() will remove the trailing slash so make sure
285 : // to defined the m_is_directory ahead of time!
286 59191 : m_is_directory = !filename.empty() && filename.back() == g_separator;
287 :
288 59191 : m_compress_method = static_cast<StorageMethod>(compress_method);
289 59191 : DOSDateTime t;
290 59191 : t.setDOSDateTime(dosdatetime);
291 59191 : m_unix_time = t.getUnixTimestamp();
292 59191 : m_compressed_size = compressed_size;
293 59191 : m_uncompressed_size = uncompressed_size;
294 59191 : m_entry_offset = rel_offset_loc_head;
295 59191 : m_filename = FilePath(filename);
296 :
297 : // the zipRead() should throw if it is false...
298 59191 : m_valid = true;
299 59191 : }
300 :
301 :
302 : /** \brief Write a Central Directory Entry to the output stream.
303 : *
304 : * This function verifies that the data of the Central Directory entry
305 : * can be written to disk. If so, then it writes a block. The size of
306 : * the blocks varies depending on the filename, file comment, and extra
307 : * data. The current size can be determined using the getHeaderSize()
308 : * function.
309 : *
310 : * \warning
311 : * The function saves the filename with an ending separator in case
312 : * the entry is marked as a directory entry. Note that Zip only really
313 : * knows about the trailing slash as a way to detect a file as a
314 : * directory.
315 : *
316 : * \exception InvalidStateException
317 : * The function verifies whether the filename, extra field,
318 : * file comment, file data, or data offset are not too large.
319 : * If any one of these parameters is too large, then this
320 : * exception is raised.
321 : *
322 : * \param[in] os The output stream where the data is written.
323 : *
324 : * \sa getHeaderSize()
325 : * \sa read()
326 : */
327 121751 : void ZipCentralDirectoryEntry::write(std::ostream & os)
328 : {
329 : /** \todo add support for 64 bit entries
330 : * (zip64 is available, just need to add a 64 bit header...)
331 : */
332 121751 : if(m_filename.length() > 0x10000
333 121751 : || m_extra_field.size() > 0x10000
334 243502 : || m_comment.length() > 0x10000)
335 : {
336 2 : throw InvalidStateException("ZipCentralDirectoryEntry::write(): file name, comment, or extra field too large to save in a Zip file.");
337 : }
338 :
339 : // Solaris defines _ILP32 for 32 bit platforms
340 : #if INTPTR_MAX != INT32_MAX
341 243498 : if(m_compressed_size >= 0x100000000ULL
342 121749 : || m_uncompressed_size >= 0x100000000ULL
343 243498 : || m_entry_offset >= 0x100000000LL)
344 : {
345 : // This represents really large files which we do not test at this point
346 : throw InvalidStateException("ZipCentralDirectoryEntry::write(): The size of this file is too large to fit in a zip archive."); // LCOV_EXCL_LINE
347 : }
348 : #endif
349 :
350 : // define version
351 121749 : uint16_t writer_version = g_zip_format_version;
352 : // including the "compatibility" code
353 : #if defined(WIN32) || defined(_WIN32) || defined(__WIN32)
354 : // MS-Windows
355 : // TBD: should we use g_msdos instead?
356 : writer_version |= g_windows;
357 : #elif defined(__APPLE__) && defined(__MACH__)
358 : // OS/X
359 : writer_version |= g_osx;
360 : #else
361 : // Other Unices
362 121749 : writer_version |= g_unix;
363 : #endif
364 :
365 121749 : std::string filename(m_filename);
366 121749 : if(m_is_directory)
367 : {
368 : // add a trailing separator for directories
369 : // (this is VERY important for zip files which do not otherwise
370 : // indicate that a file is a directory)
371 4815 : filename += g_separator;
372 : }
373 :
374 121749 : uint16_t compress_method(static_cast<uint8_t>(m_compress_method));
375 121749 : if(m_compression_level == COMPRESSION_LEVEL_NONE)
376 : {
377 6583 : compress_method = static_cast<uint8_t>(StorageMethod::STORED);
378 : }
379 :
380 121749 : DOSDateTime t;
381 121749 : t.setUnixTimestamp(m_unix_time);
382 121749 : uint32_t dosdatetime(t.getDOSDateTime()); // type could be set to DOSDateTime::dosdatetime_t
383 121749 : uint32_t compressed_size(m_compressed_size);
384 121749 : uint32_t uncompressed_size(m_uncompressed_size);
385 121749 : uint16_t filename_len(filename.length());
386 121749 : uint16_t extra_field_len(m_extra_field.size());
387 121749 : uint16_t file_comment_len(m_comment.length());
388 121749 : uint16_t disk_num_start(0);
389 121749 : uint16_t intern_file_attr(0);
390 : /** \FIXME
391 : * The external_file_attr supports the standard Unix
392 : * permissions in the higher 16 bits defined as:
393 : *
394 : * \<type> \<rwx> \<rwx> \<rwx>
395 : *
396 : * The \<type> is the top 4 bits and is set to either 8 or 4:
397 : *
398 : * \li 8 for regular files
399 : * \li 4 for directories
400 : *
401 : * The \<rwx> are the standard permission flags representing the
402 : * owner, group, and other read/write/execute permissions.
403 : *
404 : * The value also includes the special flags SUID, SGID and VTX.
405 : *
406 : * So to have a fix here we need to have a way to read those flags
407 : * from the file entry.
408 : */
409 121749 : uint32_t extern_file_attr(m_is_directory ? 0x41FD0010 : 0x81B40000);
410 121749 : uint32_t rel_offset_loc_head(m_entry_offset);
411 :
412 121749 : zipWrite(os, g_signature); // 32
413 121749 : zipWrite(os, writer_version); // 16
414 121749 : zipWrite(os, m_extract_version); // 16
415 121749 : zipWrite(os, m_general_purpose_bitfield); // 16
416 121749 : zipWrite(os, compress_method); // 16
417 121749 : zipWrite(os, dosdatetime); // 32
418 121749 : zipWrite(os, m_crc_32); // 32
419 121749 : zipWrite(os, compressed_size); // 32
420 121749 : zipWrite(os, uncompressed_size); // 32
421 121749 : zipWrite(os, filename_len); // 16
422 121749 : zipWrite(os, extra_field_len); // 16
423 121749 : zipWrite(os, file_comment_len); // 16
424 121749 : zipWrite(os, disk_num_start); // 16
425 121749 : zipWrite(os, intern_file_attr); // 16
426 121749 : zipWrite(os, extern_file_attr); // 32
427 121749 : zipWrite(os, rel_offset_loc_head); // 32
428 121749 : zipWrite(os, filename); // string
429 121749 : zipWrite(os, m_extra_field); // buffer
430 121749 : zipWrite(os, m_comment); // string
431 121749 : }
432 :
433 :
434 : } // zipios namespace
435 :
436 : // Local Variables:
437 : // mode: cpp
438 : // indent-tabs-mode: nil
439 : // c-basic-offset: 4
440 : // tab-width: 4
441 : // End:
442 :
443 : // vim: ts=4 sw=4 et
|