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::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 108615 : ZipCentralDirectoryEntry::ZipCentralDirectoryEntry()
145 : //: ZipLocalEntry() -- auto-init
146 : {
147 108615 : }
148 :
149 :
150 : /** \brief Initialize a ZipCentralDirectoryEntry.
151 : *
152 : * This function initializes a ZipCentralDirectoryEntry from a FileEntry.
153 : *
154 : * The function copies all the data it may be interested in and then
155 : * ignores the FileEntry.
156 : *
157 : * \param[in] entry The entry to transform in a ZipCentralDirectoryEntry.
158 : */
159 171467 : ZipCentralDirectoryEntry::ZipCentralDirectoryEntry(FileEntry const& entry)
160 171467 : : ZipLocalEntry(entry)
161 : {
162 171467 : }
163 :
164 :
165 : /** \brief Clean up the entry.
166 : *
167 : * The destructor makes sure the entry is fully cleaned up.
168 : */
169 562310 : ZipCentralDirectoryEntry::~ZipCentralDirectoryEntry()
170 : {
171 562310 : }
172 :
173 :
174 : /** \brief Compute and return the current header size.
175 : *
176 : * This function computes the size that this entry will take in the
177 : * Central Directory of the Zip archive.
178 : *
179 : * \return The total size of the Central Directory entry on disk.
180 : */
181 171465 : size_t ZipCentralDirectoryEntry::getHeaderSize() const
182 : {
183 : /** \todo
184 : * Add support for 64 bit Zip. At this time this function returns
185 : * an invalid size if the filename, extra field, or file comment
186 : * sizes are more than allowed in an older version of the Zip format.
187 : */
188 : // Note that the structure is 48 bytes because of an alignment
189 : // and attempting to use options to avoid the alignment would
190 : // not be portable so we use a hard coded value (yuck!)
191 : return 46 /* sizeof(ZipCentralDirectoryEntryHeader) */
192 171465 : + m_filename.length() + (m_is_directory ? 1 : 0)
193 171465 : + m_extra_field.size()
194 342930 : + m_comment.length();
195 : }
196 :
197 :
198 : /** \brief Create a clone of this Central Directory entry.
199 : *
200 : * This function allocates a new copy of this ZipCentralDirectoryEntry
201 : * object and returns a smart pointer to it.
202 : *
203 : * \return A smart pointer to the copy.
204 : */
205 1073 : FileEntry::pointer_t ZipCentralDirectoryEntry::clone() const
206 : {
207 1073 : return FileEntry::pointer_t(new ZipCentralDirectoryEntry(*this));
208 : }
209 :
210 :
211 : /** \brief Read a Central Directory entry.
212 : *
213 : * This function reads one Central Directory entry from the specified
214 : * input stream. If anything goes wrong with the input stream, the read
215 : * function will throw an error.
216 : *
217 : * \note
218 : * While reading the entry is marked as invalid. If the read fails, the
219 : * entry will remain invalid. On success, the function restores the status
220 : * back to valid.
221 : *
222 : * \note
223 : * If the signature or some other parameter is found to be invalid, then
224 : * the input stream is marked as failed and an exception is thrown.
225 : *
226 : * \exception IOException
227 : * This exception is thrown if the signature read does not match the
228 : * signature of a Central Directory entry. This can only mean a bug
229 : * in a Zip writer or an invalid/corrupt file altogether.
230 : *
231 : * \param[in] is The input stream to read from.
232 : *
233 : * \sa write()
234 : */
235 108615 : void ZipCentralDirectoryEntry::read(std::istream& is)
236 : {
237 108615 : m_valid = false; // set back to true upon successful completion below.
238 :
239 : // verify the signature
240 : uint32_t signature;
241 108615 : zipRead(is, signature);
242 108615 : if(g_signature != signature)
243 : {
244 10 : is.setstate(std::ios::failbit);
245 10 : throw IOException("ZipCentralDirectoryEntry::read(): Expected Central Directory entry signature not found");
246 : }
247 :
248 108605 : uint16_t writer_version(0);
249 108605 : uint16_t compress_method(0);
250 108605 : uint32_t dosdatetime(0);
251 108605 : uint32_t compressed_size(0);
252 108605 : uint32_t uncompressed_size(0);
253 108605 : uint32_t rel_offset_loc_head(0);
254 108605 : uint16_t filename_len(0);
255 108605 : uint16_t extra_field_len(0);
256 108605 : uint16_t file_comment_len(0);
257 108605 : uint16_t intern_file_attr(0);
258 108605 : uint32_t extern_file_attr(0);
259 108605 : uint16_t disk_num_start(0);
260 217210 : std::string filename;
261 :
262 : // read the header
263 108605 : zipRead(is, writer_version); // 16
264 108605 : zipRead(is, m_extract_version); // 16
265 108605 : zipRead(is, m_general_purpose_bitfield); // 16
266 108605 : zipRead(is, compress_method); // 16
267 108605 : zipRead(is, dosdatetime); // 32
268 108605 : zipRead(is, m_crc_32); // 32
269 108605 : zipRead(is, compressed_size); // 32
270 108605 : zipRead(is, uncompressed_size); // 32
271 108605 : zipRead(is, filename_len); // 16
272 108605 : zipRead(is, extra_field_len); // 16
273 108605 : zipRead(is, file_comment_len); // 16
274 108605 : zipRead(is, disk_num_start); // 16
275 108605 : zipRead(is, intern_file_attr); // 16
276 108605 : zipRead(is, extern_file_attr); // 32
277 108605 : zipRead(is, rel_offset_loc_head); // 32
278 108605 : zipRead(is, filename, filename_len); // string
279 108605 : zipRead(is, m_extra_field, extra_field_len); // buffer
280 108605 : zipRead(is, m_comment, file_comment_len); // string
281 : /** \todo check whether this was a 64 bit header and make sure
282 : * to read the 64 bit header too if so
283 : */
284 :
285 : // the FilePath() will remove the trailing slash so make sure
286 : // to defined the m_is_directory ahead of time!
287 108605 : m_is_directory = !filename.empty() && filename.back() == g_separator;
288 :
289 108605 : m_compress_method = static_cast<StorageMethod>(compress_method);
290 108605 : DOSDateTime t;
291 108605 : t.setDOSDateTime(dosdatetime);
292 108605 : m_unix_time = t.getUnixTimestamp();
293 108605 : m_compressed_size = compressed_size;
294 108605 : m_uncompressed_size = uncompressed_size;
295 108605 : m_entry_offset = rel_offset_loc_head;
296 108605 : m_filename = FilePath(filename);
297 :
298 : // the zipRead() should throw if it is false...
299 108605 : m_valid = true;
300 108605 : }
301 :
302 :
303 : /** \brief Write a Central Directory Entry to the output stream.
304 : *
305 : * This function verifies that the data of the Central Directory entry
306 : * can be written to disk. If so, then it writes a block. The size of
307 : * the blocks varies depending on the filename, file comment, and extra
308 : * data. The current size can be determined using the getHeaderSize()
309 : * function.
310 : *
311 : * \warning
312 : * The function saves the filename with an ending separator in case
313 : * the entry is marked as a directory entry. Note that Zip only really
314 : * knows about the trailing slash as a way to detect a file as a
315 : * directory.
316 : *
317 : * \exception InvalidStateException
318 : * The function verifies whether the filename, extra field,
319 : * file comment, file data, or data offset are not too large.
320 : * If any one of these parameters is too large, then this
321 : * exception is raised.
322 : *
323 : * \param[in] os The output stream where the data is written.
324 : *
325 : * \sa getHeaderSize()
326 : * \sa read()
327 : */
328 171467 : void ZipCentralDirectoryEntry::write(std::ostream& os)
329 : {
330 : /** \todo add support for 64 bit entries
331 : * (zip64 is available, just need to add a 64 bit header...)
332 : */
333 342934 : if(m_filename.length() > 0x10000
334 171467 : || m_extra_field.size() > 0x10000
335 342933 : || m_comment.length() > 0x10000)
336 : {
337 2 : throw InvalidStateException("ZipCentralDirectoryEntry::write(): file name, comment, or extra field too large to save in a Zip file.");
338 : }
339 :
340 : // Solaris defines _ILP32 for 32 bit platforms
341 : #if !defined(_ILP32)
342 342930 : if(m_compressed_size >= 0x100000000ULL
343 171465 : || m_uncompressed_size >= 0x100000000ULL
344 342930 : || m_entry_offset >= 0x100000000LL)
345 : {
346 : // This represents really large files which we do not test at this point
347 : throw InvalidStateException("ZipCentralDirectoryEntry::write(): The size of this file is too large to fit in a zip archive."); // LCOV_EXCL_LINE
348 : }
349 : #endif
350 :
351 : // define version
352 171465 : uint16_t writer_version = g_zip_format_version;
353 : // including the "compatibility" code
354 : #if defined(WIN32) || defined(_WIN32) || defined(__WIN32)
355 : // MS-Windows
356 : // TBD: should we use g_msdos instead?
357 : writer_version |= g_windows;
358 : #elif defined(__APPLE__) && defined(__MACH__)
359 : // OS/X
360 : writer_version |= g_osx;
361 : #else
362 : // Other Unices
363 171465 : writer_version |= g_unix;
364 : #endif
365 :
366 342930 : std::string filename(m_filename);
367 171465 : if(m_is_directory)
368 : {
369 : // add a trailing separator for directories
370 : // (this is VERY important for zip files which do not otherwise
371 : // indicate that a file is a directory)
372 10278 : filename += g_separator;
373 : }
374 :
375 171465 : uint16_t compress_method(static_cast<uint8_t>(m_compress_method));
376 171465 : if(m_compression_level == COMPRESSION_LEVEL_NONE)
377 : {
378 12054 : compress_method = static_cast<uint8_t>(StorageMethod::STORED);
379 : }
380 :
381 171465 : DOSDateTime t;
382 171465 : t.setUnixTimestamp(m_unix_time);
383 171465 : uint32_t dosdatetime(t.getDOSDateTime()); // type could be set to DOSDateTime::dosdatetime_t
384 171465 : uint32_t compressed_size(m_compressed_size);
385 171465 : uint32_t uncompressed_size(m_uncompressed_size);
386 171465 : uint16_t filename_len(filename.length());
387 171465 : uint16_t extra_field_len(m_extra_field.size());
388 171465 : uint16_t file_comment_len(m_comment.length());
389 171465 : uint16_t disk_num_start(0);
390 171465 : uint16_t intern_file_attr(0);
391 : /** \FIXME
392 : * The external_file_attr supports the standard Unix
393 : * permissions in the higher 16 bits defined as:
394 : *
395 : * \<type> \<rwx> \<rwx> \<rwx>
396 : *
397 : * The \<type> is the top 4 bits and is set to either 8 or 4:
398 : *
399 : * \li 8 for regular files
400 : * \li 4 for directories
401 : *
402 : * The \<rwx> are the standard permission flags representing the
403 : * owner, group, and other read/write/execute permissions.
404 : *
405 : * The value also includes the special flags SUID, SGID and VTX.
406 : *
407 : * So to have a fix here we need to have a way to read those flags
408 : * from the file entry.
409 : */
410 171465 : uint32_t extern_file_attr(m_is_directory ? 0x41FD0010 : 0x81B40000);
411 171465 : uint32_t rel_offset_loc_head(m_entry_offset);
412 :
413 171465 : zipWrite(os, g_signature); // 32
414 171465 : zipWrite(os, writer_version); // 16
415 171465 : zipWrite(os, m_extract_version); // 16
416 171465 : zipWrite(os, m_general_purpose_bitfield); // 16
417 171465 : zipWrite(os, compress_method); // 16
418 171465 : zipWrite(os, dosdatetime); // 32
419 171465 : zipWrite(os, m_crc_32); // 32
420 171465 : zipWrite(os, compressed_size); // 32
421 171465 : zipWrite(os, uncompressed_size); // 32
422 171465 : zipWrite(os, filename_len); // 16
423 171465 : zipWrite(os, extra_field_len); // 16
424 171465 : zipWrite(os, file_comment_len); // 16
425 171465 : zipWrite(os, disk_num_start); // 16
426 171465 : zipWrite(os, intern_file_attr); // 16
427 171465 : zipWrite(os, extern_file_attr); // 32
428 171465 : zipWrite(os, rel_offset_loc_head); // 32
429 171465 : zipWrite(os, filename); // string
430 171465 : zipWrite(os, m_extra_field); // buffer
431 171465 : zipWrite(os, m_comment); // string
432 171465 : }
433 :
434 :
435 3 : } // zipios namespace
436 :
437 : // Local Variables:
438 : // mode: cpp
439 : // indent-tabs-mode: nil
440 : // c-basic-offset: 4
441 : // tab-width: 4
442 : // End:
443 :
444 : // vim: ts=4 sw=4 et
|