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