diff options
Diffstat (limited to 'rbutil/rbutilqt/zip/unzip.cpp')
-rw-r--r-- | rbutil/rbutilqt/zip/unzip.cpp | 1360 |
1 files changed, 1360 insertions, 0 deletions
diff --git a/rbutil/rbutilqt/zip/unzip.cpp b/rbutil/rbutilqt/zip/unzip.cpp new file mode 100644 index 0000000000..3cc385ab36 --- /dev/null +++ b/rbutil/rbutilqt/zip/unzip.cpp | |||
@@ -0,0 +1,1360 @@ | |||
1 | /**************************************************************************** | ||
2 | ** Filename: unzip.cpp | ||
3 | ** Last updated [dd/mm/yyyy]: 28/01/2007 | ||
4 | ** | ||
5 | ** pkzip 2.0 decompression. | ||
6 | ** | ||
7 | ** Some of the code has been inspired by other open source projects, | ||
8 | ** (mainly Info-Zip and Gilles Vollant's minizip). | ||
9 | ** Compression and decompression actually uses the zlib library. | ||
10 | ** | ||
11 | ** Copyright (C) 2007 Angius Fabrizio. All rights reserved. | ||
12 | ** | ||
13 | ** This file is part of the OSDaB project (http://osdab.sourceforge.net/). | ||
14 | ** | ||
15 | ** This file may be distributed and/or modified under the terms of the | ||
16 | ** GNU General Public License version 2 as published by the Free Software | ||
17 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
18 | ** packaging of this file. | ||
19 | ** | ||
20 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
21 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
22 | ** | ||
23 | ** See the file LICENSE.GPL that came with this software distribution or | ||
24 | ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. | ||
25 | ** | ||
26 | **********************************************************************/ | ||
27 | |||
28 | #include "unzip.h" | ||
29 | #include "unzip_p.h" | ||
30 | #include "zipentry_p.h" | ||
31 | |||
32 | #include <QString> | ||
33 | #include <QStringList> | ||
34 | #include <QDir> | ||
35 | #include <QFile> | ||
36 | #include <QCoreApplication> | ||
37 | |||
38 | // You can remove this #include if you replace the qDebug() statements. | ||
39 | #include <QtDebug> | ||
40 | |||
41 | /*! | ||
42 | \class UnZip unzip.h | ||
43 | |||
44 | \brief PKZip 2.0 file decompression. | ||
45 | Compatibility with later versions is not ensured as they may use | ||
46 | unsupported compression algorithms. | ||
47 | Versions after 2.7 may have an incompatible header format and thus be | ||
48 | completely incompatible. | ||
49 | */ | ||
50 | |||
51 | /*! \enum UnZip::ErrorCode The result of a decompression operation. | ||
52 | \value UnZip::Ok No error occurred. | ||
53 | \value UnZip::ZlibInit Failed to init or load the zlib library. | ||
54 | \value UnZip::ZlibError The zlib library returned some error. | ||
55 | \value UnZip::OpenFailed Unable to create or open a device. | ||
56 | \value UnZip::PartiallyCorrupted Corrupted zip archive - some files could be extracted. | ||
57 | \value UnZip::Corrupted Corrupted or invalid zip archive. | ||
58 | \value UnZip::WrongPassword Unable to decrypt a password protected file. | ||
59 | \value UnZip::NoOpenArchive No archive has been opened yet. | ||
60 | \value UnZip::FileNotFound Unable to find the requested file in the archive. | ||
61 | \value UnZip::ReadFailed Reading of a file failed. | ||
62 | \value UnZip::WriteFailed Writing of a file failed. | ||
63 | \value UnZip::SeekFailed Seek failed. | ||
64 | \value UnZip::CreateDirFailed Could not create a directory. | ||
65 | \value UnZip::InvalidDevice A null device has been passed as parameter. | ||
66 | \value UnZip::InvalidArchive This is not a valid (or supported) ZIP archive. | ||
67 | \value UnZip::HeaderConsistencyError Local header record info does not match with the central directory record info. The archive may be corrupted. | ||
68 | |||
69 | \value UnZip::Skip Internal use only. | ||
70 | \value UnZip::SkipAll Internal use only. | ||
71 | */ | ||
72 | |||
73 | /*! \enum UnZip::ExtractionOptions Some options for the file extraction methods. | ||
74 | \value UnZip::ExtractPaths Default. Does not ignore the path of the zipped files. | ||
75 | \value UnZip::SkipPaths Default. Ignores the path of the zipped files and extracts them all to the same root directory. | ||
76 | */ | ||
77 | |||
78 | //! Local header size (excluding signature, excluding variable length fields) | ||
79 | #define UNZIP_LOCAL_HEADER_SIZE 26 | ||
80 | //! Central Directory file entry size (excluding signature, excluding variable length fields) | ||
81 | #define UNZIP_CD_ENTRY_SIZE_NS 42 | ||
82 | //! Data descriptor size (excluding signature) | ||
83 | #define UNZIP_DD_SIZE 12 | ||
84 | //! End Of Central Directory size (including signature, excluding variable length fields) | ||
85 | #define UNZIP_EOCD_SIZE 22 | ||
86 | //! Local header entry encryption header size | ||
87 | #define UNZIP_LOCAL_ENC_HEADER_SIZE 12 | ||
88 | |||
89 | // Some offsets inside a CD record (excluding signature) | ||
90 | #define UNZIP_CD_OFF_VERSION 0 | ||
91 | #define UNZIP_CD_OFF_GPFLAG 4 | ||
92 | #define UNZIP_CD_OFF_CMETHOD 6 | ||
93 | #define UNZIP_CD_OFF_MODT 8 | ||
94 | #define UNZIP_CD_OFF_MODD 10 | ||
95 | #define UNZIP_CD_OFF_CRC32 12 | ||
96 | #define UNZIP_CD_OFF_CSIZE 16 | ||
97 | #define UNZIP_CD_OFF_USIZE 20 | ||
98 | #define UNZIP_CD_OFF_NAMELEN 24 | ||
99 | #define UNZIP_CD_OFF_XLEN 26 | ||
100 | #define UNZIP_CD_OFF_COMMLEN 28 | ||
101 | #define UNZIP_CD_OFF_LHOFFSET 38 | ||
102 | |||
103 | // Some offsets inside a local header record (excluding signature) | ||
104 | #define UNZIP_LH_OFF_VERSION 0 | ||
105 | #define UNZIP_LH_OFF_GPFLAG 2 | ||
106 | #define UNZIP_LH_OFF_CMETHOD 4 | ||
107 | #define UNZIP_LH_OFF_MODT 6 | ||
108 | #define UNZIP_LH_OFF_MODD 8 | ||
109 | #define UNZIP_LH_OFF_CRC32 10 | ||
110 | #define UNZIP_LH_OFF_CSIZE 14 | ||
111 | #define UNZIP_LH_OFF_USIZE 18 | ||
112 | #define UNZIP_LH_OFF_NAMELEN 22 | ||
113 | #define UNZIP_LH_OFF_XLEN 24 | ||
114 | |||
115 | // Some offsets inside a data descriptor record (excluding signature) | ||
116 | #define UNZIP_DD_OFF_CRC32 0 | ||
117 | #define UNZIP_DD_OFF_CSIZE 4 | ||
118 | #define UNZIP_DD_OFF_USIZE 8 | ||
119 | |||
120 | // Some offsets inside a EOCD record | ||
121 | #define UNZIP_EOCD_OFF_ENTRIES 6 | ||
122 | #define UNZIP_EOCD_OFF_CDOFF 12 | ||
123 | #define UNZIP_EOCD_OFF_COMMLEN 16 | ||
124 | |||
125 | /*! | ||
126 | Max version handled by this API. | ||
127 | 0x1B = 2.7 --> full compatibility only up to version 2.0 (0x14) | ||
128 | versions from 2.1 to 2.7 may use unsupported compression methods | ||
129 | versions after 2.7 may have an incompatible header format | ||
130 | */ | ||
131 | #define UNZIP_VERSION 0x1B | ||
132 | //! Full compatibility granted until this version | ||
133 | #define UNZIP_VERSION_STRICT 0x14 | ||
134 | |||
135 | //! CRC32 routine | ||
136 | #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) | ||
137 | |||
138 | //! Checks if some file has been already extracted. | ||
139 | #define UNZIP_CHECK_FOR_VALID_DATA \ | ||
140 | {\ | ||
141 | if (headers != 0)\ | ||
142 | {\ | ||
143 | qDebug() << "Corrupted zip archive. Some files might be extracted.";\ | ||
144 | ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\ | ||
145 | break;\ | ||
146 | }\ | ||
147 | else\ | ||
148 | {\ | ||
149 | delete device;\ | ||
150 | device = 0;\ | ||
151 | qDebug() << "Corrupted or invalid zip archive";\ | ||
152 | ec = UnZip::Corrupted;\ | ||
153 | break;\ | ||
154 | }\ | ||
155 | } | ||
156 | |||
157 | |||
158 | /************************************************************************ | ||
159 | Public interface | ||
160 | *************************************************************************/ | ||
161 | |||
162 | /*! | ||
163 | Creates a new Zip file decompressor. | ||
164 | */ | ||
165 | UnZip::UnZip() | ||
166 | { | ||
167 | d = new UnzipPrivate; | ||
168 | } | ||
169 | |||
170 | /*! | ||
171 | Closes any open archive and releases used resources. | ||
172 | */ | ||
173 | UnZip::~UnZip() | ||
174 | { | ||
175 | closeArchive(); | ||
176 | delete d; | ||
177 | } | ||
178 | |||
179 | /*! | ||
180 | Returns true if there is an open archive. | ||
181 | */ | ||
182 | bool UnZip::isOpen() const | ||
183 | { | ||
184 | return d->device != 0; | ||
185 | } | ||
186 | |||
187 | /*! | ||
188 | Opens a zip archive and reads the files list. Closes any previously opened archive. | ||
189 | */ | ||
190 | UnZip::ErrorCode UnZip::openArchive(const QString& filename) | ||
191 | { | ||
192 | QFile* file = new QFile(filename); | ||
193 | |||
194 | if (!file->exists()) { | ||
195 | delete file; | ||
196 | return UnZip::FileNotFound; | ||
197 | } | ||
198 | |||
199 | if (!file->open(QIODevice::ReadOnly)) { | ||
200 | delete file; | ||
201 | return UnZip::OpenFailed; | ||
202 | } | ||
203 | |||
204 | return openArchive(file); | ||
205 | } | ||
206 | |||
207 | /*! | ||
208 | Opens a zip archive and reads the entries list. | ||
209 | Closes any previously opened archive. | ||
210 | \warning The class takes ownership of the device so don't delete it! | ||
211 | */ | ||
212 | UnZip::ErrorCode UnZip::openArchive(QIODevice* device) | ||
213 | { | ||
214 | if (device == 0) | ||
215 | { | ||
216 | qDebug() << "Invalid device."; | ||
217 | return UnZip::InvalidDevice; | ||
218 | } | ||
219 | |||
220 | return d->openArchive(device); | ||
221 | } | ||
222 | |||
223 | /*! | ||
224 | Closes the archive and releases all the used resources (like cached passwords). | ||
225 | */ | ||
226 | void UnZip::closeArchive() | ||
227 | { | ||
228 | d->closeArchive(); | ||
229 | } | ||
230 | |||
231 | QString UnZip::archiveComment() const | ||
232 | { | ||
233 | if (d->device == 0) | ||
234 | return QString(); | ||
235 | return d->comment; | ||
236 | } | ||
237 | |||
238 | /*! | ||
239 | Returns a locale translated error string for a given error code. | ||
240 | */ | ||
241 | QString UnZip::formatError(UnZip::ErrorCode c) const | ||
242 | { | ||
243 | switch (c) | ||
244 | { | ||
245 | case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break; | ||
246 | case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break; | ||
247 | case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break; | ||
248 | case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break; | ||
249 | case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break; | ||
250 | case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break; | ||
251 | case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break; | ||
252 | case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break; | ||
253 | case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break; | ||
254 | case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break; | ||
255 | case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break; | ||
256 | case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break; | ||
257 | case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break; | ||
258 | case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break; | ||
259 | case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break; | ||
260 | case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break; | ||
261 | default: ; | ||
262 | } | ||
263 | |||
264 | return QCoreApplication::translate("UnZip", "Unknown error."); | ||
265 | } | ||
266 | |||
267 | /*! | ||
268 | Returns true if the archive contains a file with the given path and name. | ||
269 | */ | ||
270 | bool UnZip::contains(const QString& file) const | ||
271 | { | ||
272 | if (d->headers == 0) | ||
273 | return false; | ||
274 | |||
275 | return d->headers->contains(file); | ||
276 | } | ||
277 | |||
278 | /*! | ||
279 | Returns complete paths of files and directories in this archive. | ||
280 | */ | ||
281 | QStringList UnZip::fileList() const | ||
282 | { | ||
283 | return d->headers == 0 ? QStringList() : d->headers->keys(); | ||
284 | } | ||
285 | |||
286 | /*! | ||
287 | Returns information for each (correctly parsed) entry of this archive. | ||
288 | */ | ||
289 | QList<UnZip::ZipEntry> UnZip::entryList() const | ||
290 | { | ||
291 | QList<UnZip::ZipEntry> list; | ||
292 | |||
293 | if (d->headers != 0) | ||
294 | { | ||
295 | for (QMap<QString,ZipEntryP*>::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it) | ||
296 | { | ||
297 | const ZipEntryP* entry = it.value(); | ||
298 | Q_ASSERT(entry != 0); | ||
299 | |||
300 | ZipEntry z; | ||
301 | |||
302 | z.filename = it.key(); | ||
303 | if (!entry->comment.isEmpty()) | ||
304 | z.comment = entry->comment; | ||
305 | z.compressedSize = entry->szComp; | ||
306 | z.uncompressedSize = entry->szUncomp; | ||
307 | z.crc32 = entry->crc; | ||
308 | z.lastModified = d->convertDateTime(entry->modDate, entry->modTime); | ||
309 | |||
310 | z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression; | ||
311 | z.type = z.filename.endsWith("/") ? Directory : File; | ||
312 | |||
313 | z.encrypted = entry->isEncrypted(); | ||
314 | |||
315 | list.append(z); | ||
316 | } | ||
317 | } | ||
318 | |||
319 | return list; | ||
320 | } | ||
321 | |||
322 | /*! | ||
323 | Extracts the whole archive to a directory. | ||
324 | */ | ||
325 | UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options) | ||
326 | { | ||
327 | return extractAll(QDir(dirname), options); | ||
328 | } | ||
329 | |||
330 | /*! | ||
331 | Extracts the whole archive to a directory. | ||
332 | */ | ||
333 | UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) | ||
334 | { | ||
335 | // this should only happen if we didn't call openArchive() yet | ||
336 | if (d->device == 0) | ||
337 | return NoOpenArchive; | ||
338 | |||
339 | if (d->headers == 0) | ||
340 | return Ok; | ||
341 | |||
342 | bool end = false; | ||
343 | for (QMap<QString,ZipEntryP*>::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr) | ||
344 | { | ||
345 | ZipEntryP* entry = itr.value(); | ||
346 | Q_ASSERT(entry != 0); | ||
347 | |||
348 | if ((entry->isEncrypted()) && d->skipAllEncrypted) | ||
349 | continue; | ||
350 | |||
351 | switch (d->extractFile(itr.key(), *entry, dir, options)) | ||
352 | { | ||
353 | case Corrupted: | ||
354 | qDebug() << "Removing corrupted entry" << itr.key(); | ||
355 | d->headers->erase(itr++); | ||
356 | if (itr == d->headers->end()) | ||
357 | end = true; | ||
358 | break; | ||
359 | case CreateDirFailed: | ||
360 | break; | ||
361 | case Skip: | ||
362 | break; | ||
363 | case SkipAll: | ||
364 | d->skipAllEncrypted = true; | ||
365 | break; | ||
366 | default: | ||
367 | ; | ||
368 | } | ||
369 | |||
370 | if (end) | ||
371 | break; | ||
372 | } | ||
373 | |||
374 | return Ok; | ||
375 | } | ||
376 | |||
377 | /*! | ||
378 | Extracts a single file to a directory. | ||
379 | */ | ||
380 | UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options) | ||
381 | { | ||
382 | return extractFile(filename, QDir(dirname), options); | ||
383 | } | ||
384 | |||
385 | /*! | ||
386 | Extracts a single file to a directory. | ||
387 | */ | ||
388 | UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options) | ||
389 | { | ||
390 | QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename); | ||
391 | if (itr != d->headers->end()) | ||
392 | { | ||
393 | ZipEntryP* entry = itr.value(); | ||
394 | Q_ASSERT(entry != 0); | ||
395 | return d->extractFile(itr.key(), *entry, dir, options); | ||
396 | } | ||
397 | |||
398 | return FileNotFound; | ||
399 | } | ||
400 | |||
401 | /*! | ||
402 | Extracts a single file to a directory. | ||
403 | */ | ||
404 | UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options) | ||
405 | { | ||
406 | if (dev == 0) | ||
407 | return InvalidDevice; | ||
408 | |||
409 | QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename); | ||
410 | if (itr != d->headers->end()) { | ||
411 | ZipEntryP* entry = itr.value(); | ||
412 | Q_ASSERT(entry != 0); | ||
413 | return d->extractFile(itr.key(), *entry, dev, options); | ||
414 | } | ||
415 | |||
416 | return FileNotFound; | ||
417 | } | ||
418 | |||
419 | /*! | ||
420 | Extracts a list of files. | ||
421 | Stops extraction at the first error (but continues if a file does not exist in the archive). | ||
422 | */ | ||
423 | UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options) | ||
424 | { | ||
425 | QDir dir(dirname); | ||
426 | ErrorCode ec; | ||
427 | |||
428 | for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) | ||
429 | { | ||
430 | ec = extractFile(*itr, dir, options); | ||
431 | if (ec == FileNotFound) | ||
432 | continue; | ||
433 | if (ec != Ok) | ||
434 | return ec; | ||
435 | } | ||
436 | |||
437 | return Ok; | ||
438 | } | ||
439 | |||
440 | /*! | ||
441 | Extracts a list of files. | ||
442 | Stops extraction at the first error (but continues if a file does not exist in the archive). | ||
443 | */ | ||
444 | UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options) | ||
445 | { | ||
446 | ErrorCode ec; | ||
447 | |||
448 | for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) | ||
449 | { | ||
450 | ec = extractFile(*itr, dir, options); | ||
451 | if (ec == FileNotFound) | ||
452 | continue; | ||
453 | if (ec != Ok) | ||
454 | return ec; | ||
455 | } | ||
456 | |||
457 | return Ok; | ||
458 | } | ||
459 | |||
460 | /*! | ||
461 | Remove/replace this method to add your own password retrieval routine. | ||
462 | */ | ||
463 | void UnZip::setPassword(const QString& pwd) | ||
464 | { | ||
465 | d->password = pwd; | ||
466 | } | ||
467 | |||
468 | /*! | ||
469 | ZipEntry constructor - initialize data. Type is set to File. | ||
470 | */ | ||
471 | UnZip::ZipEntry::ZipEntry() | ||
472 | { | ||
473 | compressedSize = uncompressedSize = crc32 = 0; | ||
474 | compression = NoCompression; | ||
475 | type = File; | ||
476 | encrypted = false; | ||
477 | } | ||
478 | |||
479 | |||
480 | /************************************************************************ | ||
481 | Private interface | ||
482 | *************************************************************************/ | ||
483 | |||
484 | //! \internal | ||
485 | UnzipPrivate::UnzipPrivate() | ||
486 | { | ||
487 | skipAllEncrypted = false; | ||
488 | headers = 0; | ||
489 | device = 0; | ||
490 | |||
491 | uBuffer = (unsigned char*) buffer1; | ||
492 | crcTable = (quint32*) get_crc_table(); | ||
493 | |||
494 | cdOffset = eocdOffset = 0; | ||
495 | cdEntryCount = 0; | ||
496 | unsupportedEntryCount = 0; | ||
497 | } | ||
498 | |||
499 | //! \internal Parses a Zip archive. | ||
500 | UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) | ||
501 | { | ||
502 | Q_ASSERT(dev != 0); | ||
503 | |||
504 | if (device != 0) | ||
505 | closeArchive(); | ||
506 | |||
507 | device = dev; | ||
508 | |||
509 | if (!(device->isOpen() || device->open(QIODevice::ReadOnly))) | ||
510 | { | ||
511 | delete device; | ||
512 | device = 0; | ||
513 | |||
514 | qDebug() << "Unable to open device for reading"; | ||
515 | return UnZip::OpenFailed; | ||
516 | } | ||
517 | |||
518 | UnZip::ErrorCode ec; | ||
519 | |||
520 | ec = seekToCentralDirectory(); | ||
521 | if (ec != UnZip::Ok) | ||
522 | { | ||
523 | closeArchive(); | ||
524 | return ec; | ||
525 | } | ||
526 | |||
527 | //! \todo Ignore CD entry count? CD may be corrupted. | ||
528 | if (cdEntryCount == 0) | ||
529 | { | ||
530 | return UnZip::Ok; | ||
531 | } | ||
532 | |||
533 | bool continueParsing = true; | ||
534 | |||
535 | while (continueParsing) | ||
536 | { | ||
537 | if (device->read(buffer1, 4) != 4) | ||
538 | UNZIP_CHECK_FOR_VALID_DATA | ||
539 | |||
540 | if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) ) | ||
541 | break; | ||
542 | |||
543 | if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok ) | ||
544 | break; | ||
545 | } | ||
546 | |||
547 | if (ec != UnZip::Ok) | ||
548 | closeArchive(); | ||
549 | |||
550 | return ec; | ||
551 | } | ||
552 | |||
553 | /* | ||
554 | \internal Parses a local header record and makes some consistency check | ||
555 | with the information stored in the Central Directory record for this entry | ||
556 | that has been previously parsed. | ||
557 | \todo Optional consistency check (as a ExtractionOptions flag) | ||
558 | |||
559 | local file header signature 4 bytes (0x04034b50) | ||
560 | version needed to extract 2 bytes | ||
561 | general purpose bit flag 2 bytes | ||
562 | compression method 2 bytes | ||
563 | last mod file time 2 bytes | ||
564 | last mod file date 2 bytes | ||
565 | crc-32 4 bytes | ||
566 | compressed size 4 bytes | ||
567 | uncompressed size 4 bytes | ||
568 | file name length 2 bytes | ||
569 | extra field length 2 bytes | ||
570 | |||
571 | file name (variable size) | ||
572 | extra field (variable size) | ||
573 | */ | ||
574 | UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry) | ||
575 | { | ||
576 | if (!device->seek(entry.lhOffset)) | ||
577 | return UnZip::SeekFailed; | ||
578 | |||
579 | // Test signature | ||
580 | if (device->read(buffer1, 4) != 4) | ||
581 | return UnZip::ReadFailed; | ||
582 | |||
583 | if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04)) | ||
584 | return UnZip::InvalidArchive; | ||
585 | |||
586 | if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE) | ||
587 | return UnZip::ReadFailed; | ||
588 | |||
589 | /* | ||
590 | Check 3rd general purpose bit flag. | ||
591 | |||
592 | "bit 3: If this bit is set, the fields crc-32, compressed size | ||
593 | and uncompressed size are set to zero in the local | ||
594 | header. The correct values are put in the data descriptor | ||
595 | immediately following the compressed data." | ||
596 | */ | ||
597 | bool hasDataDescriptor = entry.hasDataDescriptor(); | ||
598 | |||
599 | bool checkFailed = false; | ||
600 | |||
601 | if (!checkFailed) | ||
602 | checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD); | ||
603 | if (!checkFailed) | ||
604 | checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG]; | ||
605 | if (!checkFailed) | ||
606 | checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1]; | ||
607 | if (!checkFailed) | ||
608 | checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT]; | ||
609 | if (!checkFailed) | ||
610 | checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1]; | ||
611 | if (!checkFailed) | ||
612 | checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD]; | ||
613 | if (!checkFailed) | ||
614 | checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1]; | ||
615 | if (!hasDataDescriptor) | ||
616 | { | ||
617 | if (!checkFailed) | ||
618 | checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32); | ||
619 | if (!checkFailed) | ||
620 | checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE); | ||
621 | if (!checkFailed) | ||
622 | checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE); | ||
623 | } | ||
624 | |||
625 | if (checkFailed) | ||
626 | return UnZip::HeaderConsistencyError; | ||
627 | |||
628 | // Check filename | ||
629 | quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN); | ||
630 | if (szName == 0) | ||
631 | return UnZip::HeaderConsistencyError; | ||
632 | |||
633 | if (device->read(buffer2, szName) != szName) | ||
634 | return UnZip::ReadFailed; | ||
635 | |||
636 | QString filename = QString::fromAscii(buffer2, szName); | ||
637 | if (filename != path) | ||
638 | { | ||
639 | qDebug() << "Filename in local header mismatches."; | ||
640 | return UnZip::HeaderConsistencyError; | ||
641 | } | ||
642 | |||
643 | // Skip extra field | ||
644 | quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN); | ||
645 | if (szExtra != 0) | ||
646 | { | ||
647 | if (!device->seek(device->pos() + szExtra)) | ||
648 | return UnZip::SeekFailed; | ||
649 | } | ||
650 | |||
651 | entry.dataOffset = device->pos(); | ||
652 | |||
653 | if (hasDataDescriptor) | ||
654 | { | ||
655 | /* | ||
656 | The data descriptor has this OPTIONAL signature: PK\7\8 | ||
657 | We try to skip the compressed data relying on the size set in the | ||
658 | Central Directory record. | ||
659 | */ | ||
660 | if (!device->seek(device->pos() + entry.szComp)) | ||
661 | return UnZip::SeekFailed; | ||
662 | |||
663 | // Read 4 bytes and check if there is a data descriptor signature | ||
664 | if (device->read(buffer2, 4) != 4) | ||
665 | return UnZip::ReadFailed; | ||
666 | |||
667 | bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08; | ||
668 | if (hasSignature) | ||
669 | { | ||
670 | if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE) | ||
671 | return UnZip::ReadFailed; | ||
672 | } | ||
673 | else | ||
674 | { | ||
675 | if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4) | ||
676 | return UnZip::ReadFailed; | ||
677 | } | ||
678 | |||
679 | // DD: crc, compressed size, uncompressed size | ||
680 | if ( | ||
681 | entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) || | ||
682 | entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) || | ||
683 | entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE) | ||
684 | ) | ||
685 | return UnZip::HeaderConsistencyError; | ||
686 | } | ||
687 | |||
688 | return UnZip::Ok; | ||
689 | } | ||
690 | |||
691 | /*! \internal Attempts to find the start of the central directory record. | ||
692 | |||
693 | We seek the file back until we reach the "End Of Central Directory" | ||
694 | signature PK\5\6. | ||
695 | |||
696 | end of central dir signature 4 bytes (0x06054b50) | ||
697 | number of this disk 2 bytes | ||
698 | number of the disk with the | ||
699 | start of the central directory 2 bytes | ||
700 | total number of entries in the | ||
701 | central directory on this disk 2 bytes | ||
702 | total number of entries in | ||
703 | the central directory 2 bytes | ||
704 | size of the central directory 4 bytes | ||
705 | offset of start of central | ||
706 | directory with respect to | ||
707 | the starting disk number 4 bytes | ||
708 | .ZIP file comment length 2 bytes | ||
709 | --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE --- | ||
710 | .ZIP file comment (variable size) | ||
711 | */ | ||
712 | UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() | ||
713 | { | ||
714 | qint64 length = device->size(); | ||
715 | qint64 offset = length - UNZIP_EOCD_SIZE; | ||
716 | |||
717 | if (length < UNZIP_EOCD_SIZE) | ||
718 | return UnZip::InvalidArchive; | ||
719 | |||
720 | if (!device->seek( offset )) | ||
721 | return UnZip::SeekFailed; | ||
722 | |||
723 | if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) | ||
724 | return UnZip::ReadFailed; | ||
725 | |||
726 | bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06); | ||
727 | |||
728 | if (eocdFound) | ||
729 | { | ||
730 | // Zip file has no comment (the only variable length field in the EOCD record) | ||
731 | eocdOffset = offset; | ||
732 | } | ||
733 | else | ||
734 | { | ||
735 | qint64 read; | ||
736 | char* p = 0; | ||
737 | |||
738 | offset -= UNZIP_EOCD_SIZE; | ||
739 | |||
740 | if (offset <= 0) | ||
741 | return UnZip::InvalidArchive; | ||
742 | |||
743 | if (!device->seek( offset )) | ||
744 | return UnZip::SeekFailed; | ||
745 | |||
746 | while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0) | ||
747 | { | ||
748 | if ( (p = strstr(buffer1, "PK\5\6")) != 0) | ||
749 | { | ||
750 | // Seek to the start of the EOCD record so we can read it fully | ||
751 | // Yes... we could simply read the missing bytes and append them to the buffer | ||
752 | // but this is far easier so heck it! | ||
753 | device->seek( offset + (p - buffer1) ); | ||
754 | eocdFound = true; | ||
755 | eocdOffset = offset + (p - buffer1); | ||
756 | |||
757 | // Read EOCD record | ||
758 | if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) | ||
759 | return UnZip::ReadFailed; | ||
760 | |||
761 | break; | ||
762 | } | ||
763 | |||
764 | offset -= UNZIP_EOCD_SIZE; | ||
765 | if (offset <= 0) | ||
766 | return UnZip::InvalidArchive; | ||
767 | |||
768 | if (!device->seek( offset )) | ||
769 | return UnZip::SeekFailed; | ||
770 | } | ||
771 | } | ||
772 | |||
773 | if (!eocdFound) | ||
774 | return UnZip::InvalidArchive; | ||
775 | |||
776 | // Parse EOCD to locate CD offset | ||
777 | offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4); | ||
778 | |||
779 | cdOffset = offset; | ||
780 | |||
781 | cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4); | ||
782 | |||
783 | quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4); | ||
784 | if (commentLength != 0) | ||
785 | { | ||
786 | QByteArray c = device->read(commentLength); | ||
787 | if (c.count() != commentLength) | ||
788 | return UnZip::ReadFailed; | ||
789 | |||
790 | comment = c; | ||
791 | } | ||
792 | |||
793 | // Seek to the start of the CD record | ||
794 | if (!device->seek( cdOffset )) | ||
795 | return UnZip::SeekFailed; | ||
796 | |||
797 | return UnZip::Ok; | ||
798 | } | ||
799 | |||
800 | /*! | ||
801 | \internal Parses a central directory record. | ||
802 | |||
803 | Central Directory record structure: | ||
804 | |||
805 | [file header 1] | ||
806 | . | ||
807 | . | ||
808 | . | ||
809 | [file header n] | ||
810 | [digital signature] // PKZip 6.2 or later only | ||
811 | |||
812 | File header: | ||
813 | |||
814 | central file header signature 4 bytes (0x02014b50) | ||
815 | version made by 2 bytes | ||
816 | version needed to extract 2 bytes | ||
817 | general purpose bit flag 2 bytes | ||
818 | compression method 2 bytes | ||
819 | last mod file time 2 bytes | ||
820 | last mod file date 2 bytes | ||
821 | crc-32 4 bytes | ||
822 | compressed size 4 bytes | ||
823 | uncompressed size 4 bytes | ||
824 | file name length 2 bytes | ||
825 | extra field length 2 bytes | ||
826 | file comment length 2 bytes | ||
827 | disk number start 2 bytes | ||
828 | internal file attributes 2 bytes | ||
829 | external file attributes 4 bytes | ||
830 | relative offset of local header 4 bytes | ||
831 | |||
832 | file name (variable size) | ||
833 | extra field (variable size) | ||
834 | file comment (variable size) | ||
835 | */ | ||
836 | UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() | ||
837 | { | ||
838 | // Read CD record | ||
839 | if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS) | ||
840 | return UnZip::ReadFailed; | ||
841 | |||
842 | bool skipEntry = false; | ||
843 | |||
844 | // Get compression type so we can skip non compatible algorithms | ||
845 | quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD); | ||
846 | |||
847 | // Get variable size fields length so we can skip the whole record | ||
848 | // if necessary | ||
849 | quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN); | ||
850 | quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN); | ||
851 | quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN); | ||
852 | |||
853 | quint32 skipLength = szName + szExtra + szComment; | ||
854 | |||
855 | UnZip::ErrorCode ec = UnZip::Ok; | ||
856 | |||
857 | if ((compMethod != 0) && (compMethod != 8)) | ||
858 | { | ||
859 | qDebug() << "Unsupported compression method. Skipping file."; | ||
860 | skipEntry = true; | ||
861 | } | ||
862 | |||
863 | // Header parsing may be a problem if version is bigger than UNZIP_VERSION | ||
864 | if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION) | ||
865 | { | ||
866 | qDebug() << "Unsupported PKZip version. Skipping file."; | ||
867 | skipEntry = true; | ||
868 | } | ||
869 | |||
870 | if (!skipEntry && szName == 0) | ||
871 | { | ||
872 | qDebug() << "Skipping file with no name."; | ||
873 | skipEntry = true; | ||
874 | } | ||
875 | |||
876 | if (!skipEntry && device->read(buffer2, szName) != szName) | ||
877 | { | ||
878 | ec = UnZip::ReadFailed; | ||
879 | skipEntry = true; | ||
880 | } | ||
881 | |||
882 | if (skipEntry) | ||
883 | { | ||
884 | if (ec == UnZip::Ok) | ||
885 | { | ||
886 | if (!device->seek( device->pos() + skipLength )) | ||
887 | ec = UnZip::SeekFailed; | ||
888 | |||
889 | unsupportedEntryCount++; | ||
890 | } | ||
891 | |||
892 | return ec; | ||
893 | } | ||
894 | |||
895 | QString filename = QString::fromAscii(buffer2, szName); | ||
896 | |||
897 | ZipEntryP* h = new ZipEntryP; | ||
898 | h->compMethod = compMethod; | ||
899 | |||
900 | h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG]; | ||
901 | h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1]; | ||
902 | |||
903 | h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT]; | ||
904 | h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1]; | ||
905 | |||
906 | h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD]; | ||
907 | h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1]; | ||
908 | |||
909 | h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32); | ||
910 | h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE); | ||
911 | h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE); | ||
912 | |||
913 | // Skip extra field (if any) | ||
914 | if (szExtra != 0) | ||
915 | { | ||
916 | if (!device->seek( device->pos() + szExtra )) | ||
917 | { | ||
918 | delete h; | ||
919 | return UnZip::SeekFailed; | ||
920 | } | ||
921 | } | ||
922 | |||
923 | // Read comment field (if any) | ||
924 | if (szComment != 0) | ||
925 | { | ||
926 | if (device->read(buffer2, szComment) != szComment) | ||
927 | { | ||
928 | delete h; | ||
929 | return UnZip::ReadFailed; | ||
930 | } | ||
931 | |||
932 | h->comment = QString::fromAscii(buffer2, szComment); | ||
933 | } | ||
934 | |||
935 | h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET); | ||
936 | |||
937 | if (headers == 0) | ||
938 | headers = new QMap<QString, ZipEntryP*>(); | ||
939 | headers->insert(filename, h); | ||
940 | |||
941 | return UnZip::Ok; | ||
942 | } | ||
943 | |||
944 | //! \internal Closes the archive and resets the internal status. | ||
945 | void UnzipPrivate::closeArchive() | ||
946 | { | ||
947 | if (device == 0) | ||
948 | return; | ||
949 | |||
950 | skipAllEncrypted = false; | ||
951 | |||
952 | if (headers != 0) | ||
953 | { | ||
954 | qDeleteAll(*headers); | ||
955 | delete headers; | ||
956 | headers = 0; | ||
957 | } | ||
958 | |||
959 | delete device; device = 0; | ||
960 | |||
961 | cdOffset = eocdOffset = 0; | ||
962 | cdEntryCount = 0; | ||
963 | unsupportedEntryCount = 0; | ||
964 | |||
965 | comment.clear(); | ||
966 | } | ||
967 | |||
968 | //! \internal | ||
969 | UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options) | ||
970 | { | ||
971 | QString name(path); | ||
972 | QString dirname; | ||
973 | QString directory; | ||
974 | |||
975 | int pos = name.lastIndexOf('/'); | ||
976 | |||
977 | // This entry is for a directory | ||
978 | if (pos == name.length() - 1) | ||
979 | { | ||
980 | if (options.testFlag(UnZip::SkipPaths)) | ||
981 | return UnZip::Ok; | ||
982 | |||
983 | directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name)); | ||
984 | if (!createDirectory(directory)) | ||
985 | { | ||
986 | qDebug() << QString("Unable to create directory: %1").arg(directory); | ||
987 | return UnZip::CreateDirFailed; | ||
988 | } | ||
989 | |||
990 | return UnZip::Ok; | ||
991 | } | ||
992 | |||
993 | // Extract path from entry | ||
994 | if (pos > 0) | ||
995 | { | ||
996 | // get directory part | ||
997 | dirname = name.left(pos); | ||
998 | if (options.testFlag(UnZip::SkipPaths)) | ||
999 | { | ||
1000 | directory = dir.absolutePath(); | ||
1001 | } | ||
1002 | else | ||
1003 | { | ||
1004 | directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname)); | ||
1005 | if (!createDirectory(directory)) | ||
1006 | { | ||
1007 | qDebug() << QString("Unable to create directory: %1").arg(directory); | ||
1008 | return UnZip::CreateDirFailed; | ||
1009 | } | ||
1010 | } | ||
1011 | name = name.right(name.length() - pos - 1); | ||
1012 | } else directory = dir.absolutePath(); | ||
1013 | |||
1014 | name = QString("%1/%2").arg(directory).arg(name); | ||
1015 | |||
1016 | QFile outFile(name); | ||
1017 | |||
1018 | if (!outFile.open(QIODevice::WriteOnly)) | ||
1019 | { | ||
1020 | qDebug() << QString("Unable to open %1 for writing").arg(name); | ||
1021 | return UnZip::OpenFailed; | ||
1022 | } | ||
1023 | |||
1024 | //! \todo Set creation/last_modified date/time | ||
1025 | |||
1026 | UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options); | ||
1027 | |||
1028 | outFile.close(); | ||
1029 | |||
1030 | if (ec != UnZip::Ok) | ||
1031 | { | ||
1032 | if (!outFile.remove()) | ||
1033 | qDebug() << QString("Unable to remove corrupted file: %1").arg(name); | ||
1034 | } | ||
1035 | |||
1036 | return ec; | ||
1037 | } | ||
1038 | |||
1039 | //! \internal | ||
1040 | UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options) | ||
1041 | { | ||
1042 | Q_UNUSED(options); | ||
1043 | Q_ASSERT(dev != 0); | ||
1044 | |||
1045 | if (!entry.lhEntryChecked) | ||
1046 | { | ||
1047 | UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry); | ||
1048 | entry.lhEntryChecked = true; | ||
1049 | |||
1050 | if (ec != UnZip::Ok) | ||
1051 | return ec; | ||
1052 | } | ||
1053 | |||
1054 | if (!device->seek(entry.dataOffset)) | ||
1055 | return UnZip::SeekFailed; | ||
1056 | |||
1057 | // Encryption keys | ||
1058 | quint32 keys[3]; | ||
1059 | |||
1060 | if (entry.isEncrypted()) | ||
1061 | { | ||
1062 | UnZip::ErrorCode e = testPassword(keys, path, entry); | ||
1063 | if (e != UnZip::Ok) | ||
1064 | { | ||
1065 | qDebug() << QString("Unable to decrypt %1").arg(path); | ||
1066 | return e; | ||
1067 | }//! Encryption header size | ||
1068 | entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size | ||
1069 | } | ||
1070 | |||
1071 | if (entry.szComp == 0) | ||
1072 | { | ||
1073 | if (entry.crc != 0) | ||
1074 | return UnZip::Corrupted; | ||
1075 | |||
1076 | return UnZip::Ok; | ||
1077 | } | ||
1078 | |||
1079 | uInt rep = entry.szComp / UNZIP_READ_BUFFER; | ||
1080 | uInt rem = entry.szComp % UNZIP_READ_BUFFER; | ||
1081 | uInt cur = 0; | ||
1082 | |||
1083 | // extract data | ||
1084 | qint64 read; | ||
1085 | quint64 tot = 0; | ||
1086 | |||
1087 | quint32 myCRC = crc32(0L, Z_NULL, 0); | ||
1088 | |||
1089 | if (entry.compMethod == 0) | ||
1090 | { | ||
1091 | while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 ) | ||
1092 | { | ||
1093 | if (entry.isEncrypted()) | ||
1094 | decryptBytes(keys, buffer1, read); | ||
1095 | |||
1096 | myCRC = crc32(myCRC, uBuffer, read); | ||
1097 | |||
1098 | if (dev->write(buffer1, read) != read) | ||
1099 | return UnZip::WriteFailed; | ||
1100 | |||
1101 | cur++; | ||
1102 | tot += read; | ||
1103 | |||
1104 | if (tot == entry.szComp) | ||
1105 | break; | ||
1106 | } | ||
1107 | |||
1108 | if (read < 0) | ||
1109 | return UnZip::ReadFailed; | ||
1110 | } | ||
1111 | else if (entry.compMethod == 8) | ||
1112 | { | ||
1113 | /* Allocate inflate state */ | ||
1114 | z_stream zstr; | ||
1115 | zstr.zalloc = Z_NULL; | ||
1116 | zstr.zfree = Z_NULL; | ||
1117 | zstr.opaque = Z_NULL; | ||
1118 | zstr.next_in = Z_NULL; | ||
1119 | zstr.avail_in = 0; | ||
1120 | |||
1121 | int zret; | ||
1122 | |||
1123 | // Use inflateInit2 with negative windowBits to get raw decompression | ||
1124 | if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK ) | ||
1125 | return UnZip::ZlibError; | ||
1126 | |||
1127 | int szDecomp; | ||
1128 | |||
1129 | // Decompress until deflate stream ends or end of file | ||
1130 | do | ||
1131 | { | ||
1132 | read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem); | ||
1133 | if (read == 0) | ||
1134 | break; | ||
1135 | if (read < 0) | ||
1136 | { | ||
1137 | (void)inflateEnd(&zstr); | ||
1138 | return UnZip::ReadFailed; | ||
1139 | } | ||
1140 | |||
1141 | if (entry.isEncrypted()) | ||
1142 | decryptBytes(keys, buffer1, read); | ||
1143 | |||
1144 | cur++; | ||
1145 | tot += read; | ||
1146 | |||
1147 | zstr.avail_in = (uInt) read; | ||
1148 | zstr.next_in = (Bytef*) buffer1; | ||
1149 | |||
1150 | |||
1151 | // Run inflate() on input until output buffer not full | ||
1152 | do { | ||
1153 | zstr.avail_out = UNZIP_READ_BUFFER; | ||
1154 | zstr.next_out = (Bytef*) buffer2;; | ||
1155 | |||
1156 | zret = inflate(&zstr, Z_NO_FLUSH); | ||
1157 | |||
1158 | switch (zret) { | ||
1159 | case Z_NEED_DICT: | ||
1160 | case Z_DATA_ERROR: | ||
1161 | case Z_MEM_ERROR: | ||
1162 | inflateEnd(&zstr); | ||
1163 | return UnZip::WriteFailed; | ||
1164 | default: | ||
1165 | ; | ||
1166 | } | ||
1167 | |||
1168 | szDecomp = UNZIP_READ_BUFFER - zstr.avail_out; | ||
1169 | if (dev->write(buffer2, szDecomp) != szDecomp) | ||
1170 | { | ||
1171 | inflateEnd(&zstr); | ||
1172 | return UnZip::ZlibError; | ||
1173 | } | ||
1174 | |||
1175 | myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp); | ||
1176 | |||
1177 | } while (zstr.avail_out == 0); | ||
1178 | |||
1179 | } | ||
1180 | while (zret != Z_STREAM_END); | ||
1181 | |||
1182 | inflateEnd(&zstr); | ||
1183 | } | ||
1184 | |||
1185 | if (myCRC != entry.crc) | ||
1186 | return UnZip::Corrupted; | ||
1187 | |||
1188 | return UnZip::Ok; | ||
1189 | } | ||
1190 | |||
1191 | //! \internal Creates a new directory and all the needed parent directories. | ||
1192 | bool UnzipPrivate::createDirectory(const QString& path) | ||
1193 | { | ||
1194 | QDir d(path); | ||
1195 | if (!d.exists()) | ||
1196 | { | ||
1197 | int sep = path.lastIndexOf("/"); | ||
1198 | if (sep <= 0) return true; | ||
1199 | |||
1200 | if (!createDirectory(path.left(sep))) | ||
1201 | return false; | ||
1202 | |||
1203 | if (!d.mkdir(path)) | ||
1204 | { | ||
1205 | qDebug() << QString("Unable to create directory: %1").arg(path); | ||
1206 | return false; | ||
1207 | } | ||
1208 | } | ||
1209 | |||
1210 | return true; | ||
1211 | } | ||
1212 | |||
1213 | /*! | ||
1214 | \internal Reads an quint32 (4 bytes) from a byte array starting at given offset. | ||
1215 | */ | ||
1216 | quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const | ||
1217 | { | ||
1218 | quint32 res = (quint32) data[offset]; | ||
1219 | res |= (((quint32)data[offset+1]) << 8); | ||
1220 | res |= (((quint32)data[offset+2]) << 16); | ||
1221 | res |= (((quint32)data[offset+3]) << 24); | ||
1222 | |||
1223 | return res; | ||
1224 | } | ||
1225 | |||
1226 | /*! | ||
1227 | \internal Reads an quint64 (8 bytes) from a byte array starting at given offset. | ||
1228 | */ | ||
1229 | quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const | ||
1230 | { | ||
1231 | quint64 res = (quint64) data[offset]; | ||
1232 | res |= (((quint64)data[offset+1]) << 8); | ||
1233 | res |= (((quint64)data[offset+2]) << 16); | ||
1234 | res |= (((quint64)data[offset+3]) << 24); | ||
1235 | res |= (((quint64)data[offset+1]) << 32); | ||
1236 | res |= (((quint64)data[offset+2]) << 40); | ||
1237 | res |= (((quint64)data[offset+3]) << 48); | ||
1238 | res |= (((quint64)data[offset+3]) << 56); | ||
1239 | |||
1240 | return res; | ||
1241 | } | ||
1242 | |||
1243 | /*! | ||
1244 | \internal Reads an quint16 (2 bytes) from a byte array starting at given offset. | ||
1245 | */ | ||
1246 | quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const | ||
1247 | { | ||
1248 | return (quint16) data[offset] | (((quint16)data[offset+1]) << 8); | ||
1249 | } | ||
1250 | |||
1251 | /*! | ||
1252 | \internal Return the next byte in the pseudo-random sequence | ||
1253 | */ | ||
1254 | int UnzipPrivate::decryptByte(quint32 key2) const | ||
1255 | { | ||
1256 | quint16 temp = ((quint16)(key2) & 0xffff) | 2; | ||
1257 | return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); | ||
1258 | } | ||
1259 | |||
1260 | /*! | ||
1261 | \internal Update the encryption keys with the next byte of plain text | ||
1262 | */ | ||
1263 | void UnzipPrivate::updateKeys(quint32* keys, int c) const | ||
1264 | { | ||
1265 | keys[0] = CRC32(keys[0], c); | ||
1266 | keys[1] += keys[0] & 0xff; | ||
1267 | keys[1] = keys[1] * 134775813L + 1; | ||
1268 | keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24); | ||
1269 | } | ||
1270 | |||
1271 | /*! | ||
1272 | \internal Initialize the encryption keys and the random header according to | ||
1273 | the given password. | ||
1274 | */ | ||
1275 | void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const | ||
1276 | { | ||
1277 | keys[0] = 305419896L; | ||
1278 | keys[1] = 591751049L; | ||
1279 | keys[2] = 878082192L; | ||
1280 | |||
1281 | QByteArray pwdBytes = pwd.toAscii(); | ||
1282 | int sz = pwdBytes.size(); | ||
1283 | const char* ascii = pwdBytes.data(); | ||
1284 | |||
1285 | for (int i=0; i<sz; ++i) | ||
1286 | updateKeys(keys, (int)ascii[i]); | ||
1287 | } | ||
1288 | |||
1289 | /*! | ||
1290 | \internal Attempts to test a password without actually extracting a file. | ||
1291 | The \p file parameter can be used in the user interface or for debugging purposes | ||
1292 | as it is the name of the encrypted file for wich the password is being tested. | ||
1293 | */ | ||
1294 | UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header) | ||
1295 | { | ||
1296 | Q_UNUSED(file); | ||
1297 | |||
1298 | // read encryption keys | ||
1299 | if (device->read(buffer1, 12) != 12) | ||
1300 | return UnZip::Corrupted; | ||
1301 | |||
1302 | // Replace this code if you want to i.e. call some dialog and ask the user for a password | ||
1303 | initKeys(password, keys); | ||
1304 | if (testKeys(header, keys)) | ||
1305 | return UnZip::Ok; | ||
1306 | |||
1307 | return UnZip::Skip; | ||
1308 | } | ||
1309 | |||
1310 | /*! | ||
1311 | \internal Tests a set of keys on the encryption header. | ||
1312 | */ | ||
1313 | bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys) | ||
1314 | { | ||
1315 | char lastByte; | ||
1316 | |||
1317 | // decrypt encryption header | ||
1318 | for (int i=0; i<11; ++i) | ||
1319 | updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2])); | ||
1320 | updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2])); | ||
1321 | |||
1322 | // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time | ||
1323 | // with no extended header we have to check the crc high-order byte | ||
1324 | char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24; | ||
1325 | |||
1326 | return (lastByte == c); | ||
1327 | } | ||
1328 | |||
1329 | /*! | ||
1330 | \internal Decrypts an array of bytes long \p read. | ||
1331 | */ | ||
1332 | void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read) | ||
1333 | { | ||
1334 | for (int i=0; i<(int)read; ++i) | ||
1335 | updateKeys(keys, buffer[i] ^= decryptByte(keys[2])); | ||
1336 | } | ||
1337 | |||
1338 | /*! | ||
1339 | \internal Converts date and time values from ZIP format to a QDateTime object. | ||
1340 | */ | ||
1341 | QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const | ||
1342 | { | ||
1343 | QDateTime dt; | ||
1344 | |||
1345 | // Usual PKZip low-byte to high-byte order | ||
1346 | |||
1347 | // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day | ||
1348 | quint16 year = (date[1] >> 1) & 127; | ||
1349 | quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7); | ||
1350 | quint16 day = date[0] & 31; | ||
1351 | |||
1352 | // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision | ||
1353 | quint16 hour = (time[1] >> 3) & 31; | ||
1354 | quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7); | ||
1355 | quint16 seconds = (time[0] & 31) * 2; | ||
1356 | |||
1357 | dt.setDate(QDate(1980 + year, month, day)); | ||
1358 | dt.setTime(QTime(hour, minutes, seconds)); | ||
1359 | return dt; | ||
1360 | } | ||