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