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 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 :
34 : #include "dostime.h"
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 endianess 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_dostime;
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 69412 : ZipCentralDirectoryEntry::ZipCentralDirectoryEntry()
145 : //: ZipLocalEntry() -- auto-init
146 : {
147 69412 : }
148 :
149 :
150 : /** \brief Initialize a ZipCentralDirectoryEntry.
151 : *
152 : * This function initializes a ZipCentralDirectoryEntry from a FileEntry.
153 : *
154 : * The funtion 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 133066 : ZipCentralDirectoryEntry::ZipCentralDirectoryEntry(FileEntry const& entry)
160 133066 : : ZipLocalEntry(entry)
161 : {
162 133066 : }
163 :
164 :
165 : /** \brief Clean up the entry.
166 : *
167 : * The destructor makes sure the entry is fully cleaned up.
168 : */
169 406464 : ZipCentralDirectoryEntry::~ZipCentralDirectoryEntry()
170 : {
171 406464 : }
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 133064 : 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 133064 : + m_filename.length() + (m_is_directory ? 1 : 0)
193 133064 : + m_extra_field.size()
194 266128 : + 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 754 : FileEntry::pointer_t ZipCentralDirectoryEntry::clone() const
206 : {
207 754 : 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 69412 : void ZipCentralDirectoryEntry::read(std::istream& is)
236 : {
237 69412 : m_valid = false; // set back to true upon successful completion below.
238 :
239 : // verify the signature
240 : uint32_t signature;
241 69412 : zipRead(is, signature);
242 69412 : 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 69402 : uint16_t writer_version(0);
249 69402 : uint16_t compress_method(0);
250 69402 : uint32_t dostime(0);
251 69402 : uint32_t compressed_size(0);
252 69402 : uint32_t uncompressed_size(0);
253 69402 : uint32_t rel_offset_loc_head(0);
254 69402 : uint16_t filename_len(0);
255 69402 : uint16_t extra_field_len(0);
256 69402 : uint16_t file_comment_len(0);
257 69402 : uint16_t intern_file_attr(0);
258 69402 : uint32_t extern_file_attr(0);
259 69402 : uint16_t disk_num_start(0);
260 69402 : std::string filename;
261 :
262 : // read the header
263 69402 : zipRead(is, writer_version); // 16
264 69402 : zipRead(is, m_extract_version); // 16
265 69402 : zipRead(is, m_general_purpose_bitfield); // 16
266 69402 : zipRead(is, compress_method); // 16
267 69402 : zipRead(is, dostime); // 32
268 69402 : zipRead(is, m_crc_32); // 32
269 69402 : zipRead(is, compressed_size); // 32
270 69402 : zipRead(is, uncompressed_size); // 32
271 69402 : zipRead(is, filename_len); // 16
272 69402 : zipRead(is, extra_field_len); // 16
273 69402 : zipRead(is, file_comment_len); // 16
274 69402 : zipRead(is, disk_num_start); // 16
275 69402 : zipRead(is, intern_file_attr); // 16
276 69402 : zipRead(is, extern_file_attr); // 32
277 69402 : zipRead(is, rel_offset_loc_head); // 32
278 69402 : zipRead(is, filename, filename_len); // string
279 69402 : zipRead(is, m_extra_field, extra_field_len); // buffer
280 69402 : 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 69402 : m_is_directory = !filename.empty() && filename.back() == g_separator;
288 :
289 69402 : m_compress_method = static_cast<StorageMethod>(compress_method);
290 69402 : m_unix_time = dos2unixtime(dostime);
291 69402 : m_compressed_size = compressed_size;
292 69402 : m_uncompressed_size = uncompressed_size;
293 69402 : m_entry_offset = rel_offset_loc_head;
294 69402 : m_filename = FilePath(filename);
295 :
296 : // the zipRead() should throw if it is false...
297 69402 : m_valid = true;
298 69402 : }
299 :
300 :
301 : /** \brief Write a Central Directory Entry to the output stream.
302 : *
303 : * This function verifies that the data of the Central Directory entry
304 : * can be written to disk. If so, then it writes a block. The size of
305 : * the blocks varies depending on the filename, file comment, and extra
306 : * data. The current size can be determined using the getHeaderSize()
307 : * function.
308 : *
309 : * \warning
310 : * The function saves the filename with an ending separator in case
311 : * the entry is marked as a directory entry. Note that Zip only really
312 : * knows about the trailing slash as a way to detect a file as a
313 : * directory.
314 : *
315 : * \exception InvalidStateException
316 : * The function verifies whether the filename, extra field,
317 : * file comment, file data, or data offset are not too large.
318 : * If any one of these parameters is too large, then this
319 : * exception is raised.
320 : *
321 : * \param[in] os The output stream where the data is written.
322 : *
323 : * \sa getHeaderSize()
324 : * \sa read()
325 : */
326 133066 : void ZipCentralDirectoryEntry::write(std::ostream& os)
327 : {
328 : /** \TODO add support for 64 bit entries
329 : * (zip64 is available, just need to add a 64 bit header...)
330 : */
331 266132 : if(m_filename.length() > 0x10000
332 133066 : || m_extra_field.size() > 0x10000
333 266131 : || m_comment.length() > 0x10000)
334 : {
335 2 : throw InvalidStateException("ZipCentralDirectoryEntry::write(): file name, comment, or extra field too large to save in a Zip file.");
336 : }
337 :
338 : // Solaris defines _ILP32 for 32 bit platforms
339 : #if !defined(_ILP32)
340 266128 : if(m_compressed_size >= 0x100000000ULL
341 133064 : || m_uncompressed_size >= 0x100000000ULL
342 266128 : || m_entry_offset >= 0x100000000LL)
343 : {
344 : // This represents really large files which we do not test at this point
345 : throw InvalidStateException("ZipCentralDirectoryEntry::write(): The size of this file is too large to fit in a zip archive."); // LCOV_EXCL_LINE
346 : }
347 : #endif
348 :
349 : // define version
350 133064 : uint16_t writer_version = g_zip_format_version;
351 : // including the "compatibility" code
352 : #if defined(WIN32) || defined(_WIN32) || defined(__WIN32)
353 : // MS-Windows
354 : /** \TODO should we use g_msdos instead?
355 : */
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 133064 : writer_version |= g_unix;
363 : #endif
364 :
365 133064 : std::string filename(m_filename);
366 133064 : 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 6033 : filename += g_separator;
372 : }
373 :
374 133064 : uint16_t compress_method(static_cast<uint8_t>(m_compress_method));
375 133064 : if(m_compression_level == COMPRESSION_LEVEL_NONE)
376 : {
377 7077 : compress_method = static_cast<uint8_t>(StorageMethod::STORED);
378 : }
379 :
380 133064 : uint32_t dostime(unix2dostime(m_unix_time));
381 133064 : uint32_t compressed_size(m_compressed_size);
382 133064 : uint32_t uncompressed_size(m_uncompressed_size);
383 133064 : uint16_t filename_len(filename.length());
384 133064 : uint16_t extra_field_len(m_extra_field.size());
385 133064 : uint16_t file_comment_len(m_comment.length());
386 133064 : uint16_t disk_num_start(0);
387 133064 : uint16_t intern_file_attr(0);
388 : /** \FIXME
389 : * The external_file_attr supports the standard Unix
390 : * permissions in the higher 16 bits defined as:
391 : *
392 : * \<type> \<rwx> \<rwx> \<rwx>
393 : *
394 : * The \<type> is the top 4 bits and is set to either 8 or 4:
395 : *
396 : * \li 8 for regular files
397 : * \li 4 for directories
398 : *
399 : * The \<rwx> are the standard permission flags representing the
400 : * owner, group, and other read/write/execute permissions.
401 : *
402 : * The value also includes the special flags SUID, SGID and VTX.
403 : *
404 : * So to have a fix here we need to have a way to read those flags
405 : * from the file entry.
406 : */
407 133064 : uint32_t extern_file_attr(m_is_directory ? 0x41FD0010 : 0x81B40000);
408 133064 : uint32_t rel_offset_loc_head(m_entry_offset);
409 :
410 133064 : zipWrite(os, g_signature); // 32
411 133064 : zipWrite(os, writer_version); // 16
412 133064 : zipWrite(os, m_extract_version); // 16
413 133064 : zipWrite(os, m_general_purpose_bitfield); // 16
414 133064 : zipWrite(os, compress_method); // 16
415 133064 : zipWrite(os, dostime); // 32
416 133064 : zipWrite(os, m_crc_32); // 32
417 133064 : zipWrite(os, compressed_size); // 32
418 133064 : zipWrite(os, uncompressed_size); // 32
419 133064 : zipWrite(os, filename_len); // 16
420 133064 : zipWrite(os, extra_field_len); // 16
421 133064 : zipWrite(os, file_comment_len); // 16
422 133064 : zipWrite(os, disk_num_start); // 16
423 133064 : zipWrite(os, intern_file_attr); // 16
424 133064 : zipWrite(os, extern_file_attr); // 32
425 133064 : zipWrite(os, rel_offset_loc_head); // 32
426 133064 : zipWrite(os, filename); // string
427 133064 : zipWrite(os, m_extra_field); // buffer
428 133064 : zipWrite(os, m_comment); // string
429 133064 : }
430 :
431 :
432 3 : } // zipios namespace
433 :
434 : // Local Variables:
435 : // mode: cpp
436 : // indent-tabs-mode: nil
437 : // c-basic-offset: 4
438 : // tab-width: 4
439 : // End:
440 :
441 : // vim: ts=4 sw=4 et
|