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