Fix manual download links.
[kugel-rb.git] / rbutil / rbutilqt / zip / unzip.cpp
blob8c4028b40c9bde16f73220a8cd637eef19b84da8
1 /****************************************************************************
2 ** Filename: unzip.cpp
3 ** Last updated [dd/mm/yyyy]: 07/09/2008
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.
11 ** Copyright (C) 2007-2008 Angius Fabrizio. All rights reserved.
13 ** This file is part of the OSDaB project (http://osdab.sourceforge.net/).
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.
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.
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.
26 **********************************************************************/
28 #include "unzip.h"
29 #include "unzip_p.h"
30 #include "zipentry_p.h"
32 #include <QString>
33 #include <QStringList>
34 #include <QDir>
35 #include <QFile>
36 #include <QCoreApplication>
38 // You can remove this #include if you replace the qDebug() statements.
39 #include <QtDebug>
41 /*!
42 \class UnZip unzip.h
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.
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.
69 \value UnZip::Skip Internal use only.
70 \value UnZip::SkipAll Internal use only.
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.
78 //! Local header size (excluding signature, excluding variable length fields)
79 #define UNZIP_LOCAL_HEADER_SIZE 26
80 //! Central Directory file entry size (excluding signature, excluding variable length fields)
81 #define UNZIP_CD_ENTRY_SIZE_NS 42
82 //! Data descriptor size (excluding signature)
83 #define UNZIP_DD_SIZE 12
84 //! End Of Central Directory size (including signature, excluding variable length fields)
85 #define UNZIP_EOCD_SIZE 22
86 //! Local header entry encryption header size
87 #define UNZIP_LOCAL_ENC_HEADER_SIZE 12
89 // Some offsets inside a CD record (excluding signature)
90 #define UNZIP_CD_OFF_VERSION 0
91 #define UNZIP_CD_OFF_GPFLAG 4
92 #define UNZIP_CD_OFF_CMETHOD 6
93 #define UNZIP_CD_OFF_MODT 8
94 #define UNZIP_CD_OFF_MODD 10
95 #define UNZIP_CD_OFF_CRC32 12
96 #define UNZIP_CD_OFF_CSIZE 16
97 #define UNZIP_CD_OFF_USIZE 20
98 #define UNZIP_CD_OFF_NAMELEN 24
99 #define UNZIP_CD_OFF_XLEN 26
100 #define UNZIP_CD_OFF_COMMLEN 28
101 #define UNZIP_CD_OFF_LHOFFSET 38
103 // Some offsets inside a local header record (excluding signature)
104 #define UNZIP_LH_OFF_VERSION 0
105 #define UNZIP_LH_OFF_GPFLAG 2
106 #define UNZIP_LH_OFF_CMETHOD 4
107 #define UNZIP_LH_OFF_MODT 6
108 #define UNZIP_LH_OFF_MODD 8
109 #define UNZIP_LH_OFF_CRC32 10
110 #define UNZIP_LH_OFF_CSIZE 14
111 #define UNZIP_LH_OFF_USIZE 18
112 #define UNZIP_LH_OFF_NAMELEN 22
113 #define UNZIP_LH_OFF_XLEN 24
115 // Some offsets inside a data descriptor record (excluding signature)
116 #define UNZIP_DD_OFF_CRC32 0
117 #define UNZIP_DD_OFF_CSIZE 4
118 #define UNZIP_DD_OFF_USIZE 8
120 // Some offsets inside a EOCD record
121 #define UNZIP_EOCD_OFF_ENTRIES 6
122 #define UNZIP_EOCD_OFF_CDOFF 12
123 #define UNZIP_EOCD_OFF_COMMLEN 16
126 Max version handled by this API.
127 0x1B = 2.7 --> full compatibility only up to version 2.0 (0x14)
128 versions from 2.1 to 2.7 may use unsupported compression methods
129 versions after 2.7 may have an incompatible header format
131 /* NOTE: changed to 0x1e as info-zip 3.0 uses that header type which breaks
132 * usage. Extraction seems to work fine with the value increased.
133 * No guarantees though.
135 //#define UNZIP_VERSION 0x1B
136 #define UNZIP_VERSION 0x1e
137 //! Full compatibility granted until this version
138 #define UNZIP_VERSION_STRICT 0x14
140 //! CRC32 routine
141 #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8)
143 //! Checks if some file has been already extracted.
144 #define UNZIP_CHECK_FOR_VALID_DATA \
146 if (headers != 0)\
148 qDebug() << "Corrupted zip archive. Some files might be extracted.";\
149 ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\
150 break;\
152 else\
154 delete device;\
155 device = 0;\
156 qDebug() << "Corrupted or invalid zip archive";\
157 ec = UnZip::Corrupted;\
158 break;\
163 /************************************************************************
164 Public interface
165 *************************************************************************/
168 Creates a new Zip file decompressor.
170 UnZip::UnZip()
172 d = new UnzipPrivate;
176 Closes any open archive and releases used resources.
178 UnZip::~UnZip()
180 closeArchive();
181 delete d;
185 Returns true if there is an open archive.
187 bool UnZip::isOpen() const
189 return d->device != 0;
193 Opens a zip archive and reads the files list. Closes any previously opened archive.
195 UnZip::ErrorCode UnZip::openArchive(const QString& filename)
197 QFile* file = new QFile(filename);
199 if (!file->exists()) {
200 delete file;
201 return UnZip::FileNotFound;
204 if (!file->open(QIODevice::ReadOnly)) {
205 delete file;
206 return UnZip::OpenFailed;
209 return openArchive(file);
213 Opens a zip archive and reads the entries list.
214 Closes any previously opened archive.
215 \warning The class takes ownership of the device so don't delete it!
217 UnZip::ErrorCode UnZip::openArchive(QIODevice* device)
219 if (device == 0)
221 qDebug() << "Invalid device.";
222 return UnZip::InvalidDevice;
225 return d->openArchive(device);
229 Closes the archive and releases all the used resources (like cached passwords).
231 void UnZip::closeArchive()
233 d->closeArchive();
236 QString UnZip::archiveComment() const
238 if (d->device == 0)
239 return QString();
240 return d->comment;
244 Returns a locale translated error string for a given error code.
246 QString UnZip::formatError(UnZip::ErrorCode c) const
248 switch (c)
250 case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break;
251 case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break;
252 case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break;
253 case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break;
254 case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break;
255 case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break;
256 case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break;
257 case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break;
258 case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break;
259 case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break;
260 case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break;
261 case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break;
262 case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break;
263 case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break;
264 case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break;
265 case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break;
266 default: ;
269 return QCoreApplication::translate("UnZip", "Unknown error.");
273 Returns true if the archive contains a file with the given path and name.
275 bool UnZip::contains(const QString& file) const
277 if (d->headers == 0)
278 return false;
280 return d->headers->contains(file);
284 Returns complete paths of files and directories in this archive.
286 QStringList UnZip::fileList() const
288 return d->headers == 0 ? QStringList() : d->headers->keys();
292 Returns information for each (correctly parsed) entry of this archive.
294 QList<UnZip::ZipEntry> UnZip::entryList() const
296 QList<UnZip::ZipEntry> list;
298 if (d->headers != 0)
300 for (QMap<QString,ZipEntryP*>::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it)
302 const ZipEntryP* entry = it.value();
303 Q_ASSERT(entry != 0);
305 ZipEntry z;
307 z.filename = it.key();
308 if (!entry->comment.isEmpty())
309 z.comment = entry->comment;
310 z.compressedSize = entry->szComp;
311 z.uncompressedSize = entry->szUncomp;
312 z.crc32 = entry->crc;
313 z.lastModified = d->convertDateTime(entry->modDate, entry->modTime);
315 z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression;
316 z.type = z.filename.endsWith("/") ? Directory : File;
318 z.encrypted = entry->isEncrypted();
320 list.append(z);
324 return list;
328 Extracts the whole archive to a directory.
330 UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options)
332 return extractAll(QDir(dirname), options);
336 Extracts the whole archive to a directory.
338 UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options)
340 // this should only happen if we didn't call openArchive() yet
341 if (d->device == 0)
342 return NoOpenArchive;
344 if (d->headers == 0)
345 return Ok;
347 bool end = false;
348 for (QMap<QString,ZipEntryP*>::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr)
350 ZipEntryP* entry = itr.value();
351 Q_ASSERT(entry != 0);
353 if ((entry->isEncrypted()) && d->skipAllEncrypted)
354 continue;
356 switch (d->extractFile(itr.key(), *entry, dir, options))
358 case Corrupted:
359 qDebug() << "Removing corrupted entry" << itr.key();
360 d->headers->erase(itr++);
361 if (itr == d->headers->end())
362 end = true;
363 break;
364 case CreateDirFailed:
365 break;
366 case Skip:
367 break;
368 case SkipAll:
369 d->skipAllEncrypted = true;
370 break;
371 default:
375 if (end)
376 break;
379 return Ok;
383 Extracts a single file to a directory.
385 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options)
387 return extractFile(filename, QDir(dirname), options);
391 Extracts a single file to a directory.
393 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options)
395 QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
396 if (itr != d->headers->end())
398 ZipEntryP* entry = itr.value();
399 Q_ASSERT(entry != 0);
400 return d->extractFile(itr.key(), *entry, dir, options);
403 return FileNotFound;
407 Extracts a single file to a directory.
409 UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options)
411 if (dev == 0)
412 return InvalidDevice;
414 QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
415 if (itr != d->headers->end()) {
416 ZipEntryP* entry = itr.value();
417 Q_ASSERT(entry != 0);
418 return d->extractFile(itr.key(), *entry, dev, options);
421 return FileNotFound;
425 Extracts a list of files.
426 Stops extraction at the first error (but continues if a file does not exist in the archive).
428 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options)
430 QDir dir(dirname);
431 ErrorCode ec;
433 for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
435 ec = extractFile(*itr, dir, options);
436 if (ec == FileNotFound)
437 continue;
438 if (ec != Ok)
439 return ec;
442 return Ok;
446 Extracts a list of files.
447 Stops extraction at the first error (but continues if a file does not exist in the archive).
449 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options)
451 ErrorCode ec;
453 for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
455 ec = extractFile(*itr, dir, options);
456 if (ec == FileNotFound)
457 continue;
458 if (ec != Ok)
459 return ec;
462 return Ok;
466 Remove/replace this method to add your own password retrieval routine.
468 void UnZip::setPassword(const QString& pwd)
470 d->password = pwd;
474 ZipEntry constructor - initialize data. Type is set to File.
476 UnZip::ZipEntry::ZipEntry()
478 compressedSize = uncompressedSize = crc32 = 0;
479 compression = NoCompression;
480 type = File;
481 encrypted = false;
485 /************************************************************************
486 Private interface
487 *************************************************************************/
489 //! \internal
490 UnzipPrivate::UnzipPrivate()
492 skipAllEncrypted = false;
493 headers = 0;
494 device = 0;
496 uBuffer = (unsigned char*) buffer1;
497 crcTable = (quint32*) get_crc_table();
499 cdOffset = eocdOffset = 0;
500 cdEntryCount = 0;
501 unsupportedEntryCount = 0;
504 //! \internal Parses a Zip archive.
505 UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev)
507 Q_ASSERT(dev != 0);
509 if (device != 0)
510 closeArchive();
512 device = dev;
514 if (!(device->isOpen() || device->open(QIODevice::ReadOnly)))
516 delete device;
517 device = 0;
519 qDebug() << "Unable to open device for reading";
520 return UnZip::OpenFailed;
523 UnZip::ErrorCode ec;
525 ec = seekToCentralDirectory();
526 if (ec != UnZip::Ok)
528 closeArchive();
529 return ec;
532 //! \todo Ignore CD entry count? CD may be corrupted.
533 if (cdEntryCount == 0)
535 return UnZip::Ok;
538 bool continueParsing = true;
540 while (continueParsing)
542 if (device->read(buffer1, 4) != 4)
543 UNZIP_CHECK_FOR_VALID_DATA
545 if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) )
546 break;
548 if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok )
549 break;
552 if (ec != UnZip::Ok)
553 closeArchive();
555 return ec;
559 \internal Parses a local header record and makes some consistency check
560 with the information stored in the Central Directory record for this entry
561 that has been previously parsed.
562 \todo Optional consistency check (as a ExtractionOptions flag)
564 local file header signature 4 bytes (0x04034b50)
565 version needed to extract 2 bytes
566 general purpose bit flag 2 bytes
567 compression method 2 bytes
568 last mod file time 2 bytes
569 last mod file date 2 bytes
570 crc-32 4 bytes
571 compressed size 4 bytes
572 uncompressed size 4 bytes
573 file name length 2 bytes
574 extra field length 2 bytes
576 file name (variable size)
577 extra field (variable size)
579 UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry)
581 if (!device->seek(entry.lhOffset))
582 return UnZip::SeekFailed;
584 // Test signature
585 if (device->read(buffer1, 4) != 4)
586 return UnZip::ReadFailed;
588 if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04))
589 return UnZip::InvalidArchive;
591 if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE)
592 return UnZip::ReadFailed;
595 Check 3rd general purpose bit flag.
597 "bit 3: If this bit is set, the fields crc-32, compressed size
598 and uncompressed size are set to zero in the local
599 header. The correct values are put in the data descriptor
600 immediately following the compressed data."
602 bool hasDataDescriptor = entry.hasDataDescriptor();
604 bool checkFailed = false;
606 if (!checkFailed)
607 checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD);
608 if (!checkFailed)
609 checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG];
610 if (!checkFailed)
611 checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1];
612 if (!checkFailed)
613 checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT];
614 if (!checkFailed)
615 checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1];
616 if (!checkFailed)
617 checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD];
618 if (!checkFailed)
619 checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1];
620 if (!hasDataDescriptor)
622 if (!checkFailed)
623 checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32);
624 if (!checkFailed)
625 checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE);
626 if (!checkFailed)
627 checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE);
630 if (checkFailed)
631 return UnZip::HeaderConsistencyError;
633 // Check filename
634 quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN);
635 if (szName == 0)
636 return UnZip::HeaderConsistencyError;
638 if (device->read(buffer2, szName) != szName)
639 return UnZip::ReadFailed;
641 QString filename = QString::fromAscii(buffer2, szName);
642 if (filename != path)
644 qDebug() << "Filename in local header mismatches.";
645 return UnZip::HeaderConsistencyError;
648 // Skip extra field
649 quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN);
650 if (szExtra != 0)
652 if (!device->seek(device->pos() + szExtra))
653 return UnZip::SeekFailed;
656 entry.dataOffset = device->pos();
658 if (hasDataDescriptor)
661 The data descriptor has this OPTIONAL signature: PK\7\8
662 We try to skip the compressed data relying on the size set in the
663 Central Directory record.
665 if (!device->seek(device->pos() + entry.szComp))
666 return UnZip::SeekFailed;
668 // Read 4 bytes and check if there is a data descriptor signature
669 if (device->read(buffer2, 4) != 4)
670 return UnZip::ReadFailed;
672 bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08;
673 if (hasSignature)
675 if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE)
676 return UnZip::ReadFailed;
678 else
680 if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4)
681 return UnZip::ReadFailed;
684 // DD: crc, compressed size, uncompressed size
685 if (
686 entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) ||
687 entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) ||
688 entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE)
690 return UnZip::HeaderConsistencyError;
693 return UnZip::Ok;
696 /*! \internal Attempts to find the start of the central directory record.
698 We seek the file back until we reach the "End Of Central Directory"
699 signature PK\5\6.
701 end of central dir signature 4 bytes (0x06054b50)
702 number of this disk 2 bytes
703 number of the disk with the
704 start of the central directory 2 bytes
705 total number of entries in the
706 central directory on this disk 2 bytes
707 total number of entries in
708 the central directory 2 bytes
709 size of the central directory 4 bytes
710 offset of start of central
711 directory with respect to
712 the starting disk number 4 bytes
713 .ZIP file comment length 2 bytes
714 --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE ---
715 .ZIP file comment (variable size)
717 UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory()
719 qint64 length = device->size();
720 qint64 offset = length - UNZIP_EOCD_SIZE;
722 if (length < UNZIP_EOCD_SIZE)
723 return UnZip::InvalidArchive;
725 if (!device->seek( offset ))
726 return UnZip::SeekFailed;
728 if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
729 return UnZip::ReadFailed;
731 bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06);
733 if (eocdFound)
735 // Zip file has no comment (the only variable length field in the EOCD record)
736 eocdOffset = offset;
738 else
740 qint64 read;
741 char* p = 0;
743 offset -= UNZIP_EOCD_SIZE;
745 if (offset <= 0)
746 return UnZip::InvalidArchive;
748 if (!device->seek( offset ))
749 return UnZip::SeekFailed;
751 while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0)
753 if ( (p = strstr(buffer1, "PK\5\6")) != 0)
755 // Seek to the start of the EOCD record so we can read it fully
756 // Yes... we could simply read the missing bytes and append them to the buffer
757 // but this is far easier so heck it!
758 device->seek( offset + (p - buffer1) );
759 eocdFound = true;
760 eocdOffset = offset + (p - buffer1);
762 // Read EOCD record
763 if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
764 return UnZip::ReadFailed;
766 break;
769 // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here.
770 offset -= 1 /*UNZIP_EOCD_SIZE*/;
771 if (offset <= 0)
772 return UnZip::InvalidArchive;
774 if (!device->seek( offset ))
775 return UnZip::SeekFailed;
779 if (!eocdFound)
780 return UnZip::InvalidArchive;
782 // Parse EOCD to locate CD offset
783 offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4);
785 cdOffset = offset;
787 cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4);
789 quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4);
790 if (commentLength != 0)
792 QByteArray c = device->read(commentLength);
793 if (c.count() != commentLength)
794 return UnZip::ReadFailed;
796 comment = c;
799 // Seek to the start of the CD record
800 if (!device->seek( cdOffset ))
801 return UnZip::SeekFailed;
803 return UnZip::Ok;
807 \internal Parses a central directory record.
809 Central Directory record structure:
811 [file header 1]
815 [file header n]
816 [digital signature] // PKZip 6.2 or later only
818 File header:
820 central file header signature 4 bytes (0x02014b50)
821 version made by 2 bytes
822 version needed to extract 2 bytes
823 general purpose bit flag 2 bytes
824 compression method 2 bytes
825 last mod file time 2 bytes
826 last mod file date 2 bytes
827 crc-32 4 bytes
828 compressed size 4 bytes
829 uncompressed size 4 bytes
830 file name length 2 bytes
831 extra field length 2 bytes
832 file comment length 2 bytes
833 disk number start 2 bytes
834 internal file attributes 2 bytes
835 external file attributes 4 bytes
836 relative offset of local header 4 bytes
838 file name (variable size)
839 extra field (variable size)
840 file comment (variable size)
842 UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord()
844 // Read CD record
845 if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS)
846 return UnZip::ReadFailed;
848 bool skipEntry = false;
850 // Get compression type so we can skip non compatible algorithms
851 quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD);
853 // Get variable size fields length so we can skip the whole record
854 // if necessary
855 quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN);
856 quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN);
857 quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN);
859 quint32 skipLength = szName + szExtra + szComment;
861 UnZip::ErrorCode ec = UnZip::Ok;
863 if ((compMethod != 0) && (compMethod != 8))
865 qDebug() << "Unsupported compression method. Skipping file.";
866 skipEntry = true;
869 // Header parsing may be a problem if version is bigger than UNZIP_VERSION
870 if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION)
872 qDebug() << "Unsupported PKZip version. Skipping file.";
873 skipEntry = true;
876 if (!skipEntry && szName == 0)
878 qDebug() << "Skipping file with no name.";
879 skipEntry = true;
882 if (!skipEntry && device->read(buffer2, szName) != szName)
884 ec = UnZip::ReadFailed;
885 skipEntry = true;
888 if (skipEntry)
890 if (ec == UnZip::Ok)
892 if (!device->seek( device->pos() + skipLength ))
893 ec = UnZip::SeekFailed;
895 unsupportedEntryCount++;
898 return ec;
901 QString filename = QString::fromAscii(buffer2, szName);
903 ZipEntryP* h = new ZipEntryP;
904 h->compMethod = compMethod;
906 h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG];
907 h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1];
909 h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT];
910 h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1];
912 h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD];
913 h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1];
915 h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32);
916 h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE);
917 h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE);
919 // Skip extra field (if any)
920 if (szExtra != 0)
922 if (!device->seek( device->pos() + szExtra ))
924 delete h;
925 return UnZip::SeekFailed;
929 // Read comment field (if any)
930 if (szComment != 0)
932 if (device->read(buffer2, szComment) != szComment)
934 delete h;
935 return UnZip::ReadFailed;
938 h->comment = QString::fromAscii(buffer2, szComment);
941 h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET);
943 if (headers == 0)
944 headers = new QMap<QString, ZipEntryP*>();
945 headers->insert(filename, h);
947 return UnZip::Ok;
950 //! \internal Closes the archive and resets the internal status.
951 void UnzipPrivate::closeArchive()
953 if (device == 0)
954 return;
956 skipAllEncrypted = false;
958 if (headers != 0)
960 qDeleteAll(*headers);
961 delete headers;
962 headers = 0;
965 delete device; device = 0;
967 cdOffset = eocdOffset = 0;
968 cdEntryCount = 0;
969 unsupportedEntryCount = 0;
971 comment.clear();
974 //! \internal
975 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options)
977 QString name(path);
978 QString dirname;
979 QString directory;
981 int pos = name.lastIndexOf('/');
983 // This entry is for a directory
984 if (pos == name.length() - 1)
986 if (options.testFlag(UnZip::SkipPaths))
987 return UnZip::Ok;
989 directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name));
990 if (!createDirectory(directory))
992 qDebug() << QString("Unable to create directory: %1").arg(directory);
993 return UnZip::CreateDirFailed;
996 return UnZip::Ok;
999 // Extract path from entry
1000 if (pos > 0)
1002 // get directory part
1003 dirname = name.left(pos);
1004 if (options.testFlag(UnZip::SkipPaths))
1006 directory = dir.absolutePath();
1008 else
1010 directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname));
1011 if (!createDirectory(directory))
1013 qDebug() << QString("Unable to create directory: %1").arg(directory);
1014 return UnZip::CreateDirFailed;
1017 name = name.right(name.length() - pos - 1);
1018 } else directory = dir.absolutePath();
1020 name = QString("%1/%2").arg(directory).arg(name);
1022 QFile outFile(name);
1024 if (!outFile.open(QIODevice::WriteOnly))
1026 qDebug() << QString("Unable to open %1 for writing").arg(name);
1027 return UnZip::OpenFailed;
1030 //! \todo Set creation/last_modified date/time
1032 UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options);
1034 outFile.close();
1036 if (ec != UnZip::Ok)
1038 if (!outFile.remove())
1039 qDebug() << QString("Unable to remove corrupted file: %1").arg(name);
1042 return ec;
1045 //! \internal
1046 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options)
1048 Q_UNUSED(options);
1049 Q_ASSERT(dev != 0);
1051 if (!entry.lhEntryChecked)
1053 UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry);
1054 entry.lhEntryChecked = true;
1056 if (ec != UnZip::Ok)
1057 return ec;
1060 if (!device->seek(entry.dataOffset))
1061 return UnZip::SeekFailed;
1063 // Encryption keys
1064 quint32 keys[3];
1066 if (entry.isEncrypted())
1068 UnZip::ErrorCode e = testPassword(keys, path, entry);
1069 if (e != UnZip::Ok)
1071 qDebug() << QString("Unable to decrypt %1").arg(path);
1072 return e;
1073 }//! Encryption header size
1074 entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size
1077 if (entry.szComp == 0)
1079 if (entry.crc != 0)
1080 return UnZip::Corrupted;
1082 return UnZip::Ok;
1085 uInt rep = entry.szComp / UNZIP_READ_BUFFER;
1086 uInt rem = entry.szComp % UNZIP_READ_BUFFER;
1087 uInt cur = 0;
1089 // extract data
1090 qint64 read;
1091 quint64 tot = 0;
1093 quint32 myCRC = crc32(0L, Z_NULL, 0);
1095 if (entry.compMethod == 0)
1097 while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 )
1099 if (entry.isEncrypted())
1100 decryptBytes(keys, buffer1, read);
1102 myCRC = crc32(myCRC, uBuffer, read);
1104 if (dev->write(buffer1, read) != read)
1105 return UnZip::WriteFailed;
1107 cur++;
1108 tot += read;
1110 if (tot == entry.szComp)
1111 break;
1114 if (read < 0)
1115 return UnZip::ReadFailed;
1117 else if (entry.compMethod == 8)
1119 /* Allocate inflate state */
1120 z_stream zstr;
1121 zstr.zalloc = Z_NULL;
1122 zstr.zfree = Z_NULL;
1123 zstr.opaque = Z_NULL;
1124 zstr.next_in = Z_NULL;
1125 zstr.avail_in = 0;
1127 int zret;
1129 // Use inflateInit2 with negative windowBits to get raw decompression
1130 if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK )
1131 return UnZip::ZlibError;
1133 int szDecomp;
1135 // Decompress until deflate stream ends or end of file
1138 read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem);
1139 if (read == 0)
1140 break;
1141 if (read < 0)
1143 (void)inflateEnd(&zstr);
1144 return UnZip::ReadFailed;
1147 if (entry.isEncrypted())
1148 decryptBytes(keys, buffer1, read);
1150 cur++;
1151 tot += read;
1153 zstr.avail_in = (uInt) read;
1154 zstr.next_in = (Bytef*) buffer1;
1157 // Run inflate() on input until output buffer not full
1158 do {
1159 zstr.avail_out = UNZIP_READ_BUFFER;
1160 zstr.next_out = (Bytef*) buffer2;;
1162 zret = inflate(&zstr, Z_NO_FLUSH);
1164 switch (zret) {
1165 case Z_NEED_DICT:
1166 case Z_DATA_ERROR:
1167 case Z_MEM_ERROR:
1168 inflateEnd(&zstr);
1169 return UnZip::WriteFailed;
1170 default:
1174 szDecomp = UNZIP_READ_BUFFER - zstr.avail_out;
1175 if (dev->write(buffer2, szDecomp) != szDecomp)
1177 inflateEnd(&zstr);
1178 return UnZip::ZlibError;
1181 myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp);
1183 } while (zstr.avail_out == 0);
1186 while (zret != Z_STREAM_END);
1188 inflateEnd(&zstr);
1191 if (myCRC != entry.crc)
1192 return UnZip::Corrupted;
1194 return UnZip::Ok;
1197 //! \internal Creates a new directory and all the needed parent directories.
1198 bool UnzipPrivate::createDirectory(const QString& path)
1200 QDir d(path);
1201 if (!d.exists())
1203 int sep = path.lastIndexOf("/");
1204 if (sep <= 0) return true;
1206 if (!createDirectory(path.left(sep)))
1207 return false;
1209 if (!d.mkdir(path))
1211 qDebug() << QString("Unable to create directory: %1").arg(path);
1212 return false;
1216 return true;
1220 \internal Reads an quint32 (4 bytes) from a byte array starting at given offset.
1222 quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const
1224 quint32 res = (quint32) data[offset];
1225 res |= (((quint32)data[offset+1]) << 8);
1226 res |= (((quint32)data[offset+2]) << 16);
1227 res |= (((quint32)data[offset+3]) << 24);
1229 return res;
1233 \internal Reads an quint64 (8 bytes) from a byte array starting at given offset.
1235 quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const
1237 quint64 res = (quint64) data[offset];
1238 res |= (((quint64)data[offset+1]) << 8);
1239 res |= (((quint64)data[offset+2]) << 16);
1240 res |= (((quint64)data[offset+3]) << 24);
1241 res |= (((quint64)data[offset+1]) << 32);
1242 res |= (((quint64)data[offset+2]) << 40);
1243 res |= (((quint64)data[offset+3]) << 48);
1244 res |= (((quint64)data[offset+3]) << 56);
1246 return res;
1250 \internal Reads an quint16 (2 bytes) from a byte array starting at given offset.
1252 quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const
1254 return (quint16) data[offset] | (((quint16)data[offset+1]) << 8);
1258 \internal Return the next byte in the pseudo-random sequence
1260 int UnzipPrivate::decryptByte(quint32 key2) const
1262 quint16 temp = ((quint16)(key2) & 0xffff) | 2;
1263 return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
1267 \internal Update the encryption keys with the next byte of plain text
1269 void UnzipPrivate::updateKeys(quint32* keys, int c) const
1271 keys[0] = CRC32(keys[0], c);
1272 keys[1] += keys[0] & 0xff;
1273 keys[1] = keys[1] * 134775813L + 1;
1274 keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
1278 \internal Initialize the encryption keys and the random header according to
1279 the given password.
1281 void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const
1283 keys[0] = 305419896L;
1284 keys[1] = 591751049L;
1285 keys[2] = 878082192L;
1287 QByteArray pwdBytes = pwd.toAscii();
1288 int sz = pwdBytes.size();
1289 const char* ascii = pwdBytes.data();
1291 for (int i=0; i<sz; ++i)
1292 updateKeys(keys, (int)ascii[i]);
1296 \internal Attempts to test a password without actually extracting a file.
1297 The \p file parameter can be used in the user interface or for debugging purposes
1298 as it is the name of the encrypted file for wich the password is being tested.
1300 UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header)
1302 Q_UNUSED(file);
1304 // read encryption keys
1305 if (device->read(buffer1, 12) != 12)
1306 return UnZip::Corrupted;
1308 // Replace this code if you want to i.e. call some dialog and ask the user for a password
1309 initKeys(password, keys);
1310 if (testKeys(header, keys))
1311 return UnZip::Ok;
1313 return UnZip::Skip;
1317 \internal Tests a set of keys on the encryption header.
1319 bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys)
1321 char lastByte;
1323 // decrypt encryption header
1324 for (int i=0; i<11; ++i)
1325 updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2]));
1326 updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2]));
1328 // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time
1329 // with no extended header we have to check the crc high-order byte
1330 char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24;
1332 return (lastByte == c);
1336 \internal Decrypts an array of bytes long \p read.
1338 void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read)
1340 for (int i=0; i<(int)read; ++i)
1341 updateKeys(keys, buffer[i] ^= decryptByte(keys[2]));
1345 \internal Converts date and time values from ZIP format to a QDateTime object.
1347 QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const
1349 QDateTime dt;
1351 // Usual PKZip low-byte to high-byte order
1353 // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day
1354 quint16 year = (date[1] >> 1) & 127;
1355 quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7);
1356 quint16 day = date[0] & 31;
1358 // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision
1359 quint16 hour = (time[1] >> 3) & 31;
1360 quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7);
1361 quint16 seconds = (time[0] & 31) * 2;
1363 dt.setDate(QDate(1980 + year, month, day));
1364 dt.setTime(QTime(hour, minutes, seconds));
1365 return dt;