unzip.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Filename: unzip.cpp
00003 ** Last updated [dd/mm/yyyy]: 28/01/2007
00004 **
00005 ** pkzip 2.0 decompression.
00006 **
00007 ** Some of the code has been inspired by other open source projects,
00008 ** (mainly Info-Zip and Gilles Vollant's minizip).
00009 ** Compression and decompression actually uses the zlib library.
00010 **
00011 ** Copyright (C) 2007 Angius Fabrizio. All rights reserved.
00012 **
00013 ** This file is part of the OSDaB project (http://osdab.sourceforge.net/).
00014 **
00015 ** This file may be distributed and/or modified under the terms of the
00016 ** GNU General Public License version 2 as published by the Free Software
00017 ** Foundation and appearing in the file LICENSE.GPL included in the
00018 ** packaging of this file.
00019 **
00020 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00021 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00022 **
00023 ** See the file LICENSE.GPL that came with this software distribution or
00024 ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
00025 **
00026 **********************************************************************/
00027 
00028 #include "unzip.h"
00029 #include "unzip_p.h"
00030 #include "zipentry_p.h"
00031 
00032 #include <QString>
00033 #include <QStringList>
00034 #include <QDir>
00035 #include <QFile>
00036 #include <QCoreApplication>
00037 
00038 // You can remove this #include if you replace the qDebug() statements.
00039 #include <QtDebug>
00040 
00078 
00079 #define UNZIP_LOCAL_HEADER_SIZE 26
00081 #define UNZIP_CD_ENTRY_SIZE_NS 42
00083 #define UNZIP_DD_SIZE 12
00085 #define UNZIP_EOCD_SIZE 22
00087 #define UNZIP_LOCAL_ENC_HEADER_SIZE 12
00088 
00089 // Some offsets inside a CD record (excluding signature)
00090 #define UNZIP_CD_OFF_VERSION 0
00091 #define UNZIP_CD_OFF_GPFLAG 4
00092 #define UNZIP_CD_OFF_CMETHOD 6
00093 #define UNZIP_CD_OFF_MODT 8
00094 #define UNZIP_CD_OFF_MODD 10
00095 #define UNZIP_CD_OFF_CRC32 12
00096 #define UNZIP_CD_OFF_CSIZE 16
00097 #define UNZIP_CD_OFF_USIZE 20
00098 #define UNZIP_CD_OFF_NAMELEN 24
00099 #define UNZIP_CD_OFF_XLEN 26
00100 #define UNZIP_CD_OFF_COMMLEN 28
00101 #define UNZIP_CD_OFF_LHOFFSET 38
00102 
00103 // Some offsets inside a local header record (excluding signature)
00104 #define UNZIP_LH_OFF_VERSION 0
00105 #define UNZIP_LH_OFF_GPFLAG 2
00106 #define UNZIP_LH_OFF_CMETHOD 4
00107 #define UNZIP_LH_OFF_MODT 6
00108 #define UNZIP_LH_OFF_MODD 8
00109 #define UNZIP_LH_OFF_CRC32 10
00110 #define UNZIP_LH_OFF_CSIZE 14
00111 #define UNZIP_LH_OFF_USIZE 18
00112 #define UNZIP_LH_OFF_NAMELEN 22
00113 #define UNZIP_LH_OFF_XLEN 24
00114 
00115 // Some offsets inside a data descriptor record (excluding signature)
00116 #define UNZIP_DD_OFF_CRC32 0
00117 #define UNZIP_DD_OFF_CSIZE 4
00118 #define UNZIP_DD_OFF_USIZE 8
00119 
00120 // Some offsets inside a EOCD record
00121 #define UNZIP_EOCD_OFF_ENTRIES 6
00122 #define UNZIP_EOCD_OFF_CDOFF 12
00123 #define UNZIP_EOCD_OFF_COMMLEN 16
00124 
00131 #define UNZIP_VERSION 0x1B
00133 #define UNZIP_VERSION_STRICT 0x14
00134 
00136 #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8)
00137 
00139 #define UNZIP_CHECK_FOR_VALID_DATA \
00140     {\
00141         if (headers != 0)\
00142         {\
00143             qDebug() << "Corrupted zip archive. Some files might be extracted.";\
00144             ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\
00145             break;\
00146         }\
00147         else\
00148         {\
00149             delete device;\
00150             device = 0;\
00151             qDebug() << "Corrupted or invalid zip archive";\
00152             ec = UnZip::Corrupted;\
00153             break;\
00154         }\
00155     }
00156 
00157 
00158 /************************************************************************
00159  Public interface
00160 *************************************************************************/
00161 
00165 UnZip::UnZip()
00166 {
00167     d = new UnzipPrivate;
00168 }
00169 
00173 UnZip::~UnZip()
00174 {
00175     closeArchive();
00176     delete d;
00177 }
00178 
00182 bool UnZip::isOpen() const
00183 {
00184     return d->device != 0;
00185 }
00186 
00190 UnZip::ErrorCode UnZip::openArchive(const QString& filename)
00191 {
00192     QFile* file = new QFile(filename);
00193 
00194     if (!file->exists()) {
00195         delete file;
00196         return UnZip::FileNotFound;
00197     }
00198 
00199     if (!file->open(QIODevice::ReadOnly)) {
00200         delete file;
00201         return UnZip::OpenFailed;
00202     }
00203 
00204     return openArchive(file);
00205 }
00206 
00212 UnZip::ErrorCode UnZip::openArchive(QIODevice* device)
00213 {
00214     if (device == 0)
00215     {
00216         qDebug() << "Invalid device.";
00217         return UnZip::InvalidDevice;
00218     }
00219 
00220     return d->openArchive(device);
00221 }
00222 
00226 void UnZip::closeArchive()
00227 {
00228     d->closeArchive();
00229 }
00230 
00231 QString UnZip::archiveComment() const
00232 {
00233     if (d->device == 0)
00234         return QString();
00235     return d->comment;
00236 }
00237 
00241 QString UnZip::formatError(UnZip::ErrorCode c) const
00242 {
00243     switch (c)
00244     {
00245     case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break;
00246     case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break;
00247     case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break;
00248     case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break;
00249     case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break;
00250     case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break;
00251     case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break;
00252     case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break;
00253     case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break;
00254     case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break;
00255     case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break;
00256     case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break;
00257     case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break;
00258     case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break;
00259     case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break;
00260     case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break;
00261     default: ;
00262     }
00263 
00264     return QCoreApplication::translate("UnZip", "Unknown error.");
00265 }
00266 
00270 bool UnZip::contains(const QString& file) const
00271 {
00272     if (d->headers == 0)
00273         return false;
00274 
00275     return d->headers->contains(file);
00276 }
00277 
00281 QStringList UnZip::fileList() const
00282 {
00283     return d->headers == 0 ? QStringList() : d->headers->keys();
00284 }
00285 
00289 QList<UnZip::ZipEntry> UnZip::entryList() const
00290 {
00291     QList<UnZip::ZipEntry> list;
00292 
00293     if (d->headers != 0)
00294     {
00295         for (QMap<QString,ZipEntryP*>::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it)
00296         {
00297             const ZipEntryP* entry = it.value();
00298             Q_ASSERT(entry != 0);
00299 
00300             ZipEntry z;
00301 
00302             z.filename = it.key();
00303             if (!entry->comment.isEmpty())
00304                 z.comment = entry->comment;
00305             z.compressedSize = entry->szComp;
00306             z.uncompressedSize = entry->szUncomp;
00307             z.crc32 = entry->crc;
00308             z.lastModified = d->convertDateTime(entry->modDate, entry->modTime);
00309 
00310             z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression;
00311             z.type = z.filename.endsWith("/") ? Directory : File;
00312 
00313             z.encrypted = entry->isEncrypted();
00314 
00315             list.append(z);
00316         }
00317     }
00318 
00319     return list;
00320 }
00321 
00325 UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options)
00326 {
00327     return extractAll(QDir(dirname), options);
00328 }
00329 
00333 UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options)
00334 {
00335     // this should only happen if we didn't call openArchive() yet
00336     if (d->device == 0)
00337         return NoOpenArchive;
00338 
00339     if (d->headers == 0)
00340         return Ok;
00341 
00342     bool end = false;
00343     for (QMap<QString,ZipEntryP*>::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr)
00344     {
00345         ZipEntryP* entry = itr.value();
00346         Q_ASSERT(entry != 0);
00347 
00348         if ((entry->isEncrypted()) && d->skipAllEncrypted)
00349             continue;
00350 
00351         switch (d->extractFile(itr.key(), *entry, dir, options))
00352         {
00353         case Corrupted:
00354             qDebug() << "Removing corrupted entry" << itr.key();
00355             d->headers->erase(itr++);
00356             if (itr == d->headers->end())
00357                 end = true;
00358             break;
00359         case CreateDirFailed:
00360             break;
00361         case Skip:
00362             break;
00363         case SkipAll:
00364             d->skipAllEncrypted = true;
00365             break;
00366         default:
00367             ;
00368         }
00369 
00370         if (end)
00371             break;
00372     }
00373 
00374     return Ok;
00375 }
00376 
00380 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options)
00381 {
00382     return extractFile(filename, QDir(dirname), options);
00383 }
00384 
00388 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options)
00389 {
00390     QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
00391     if (itr != d->headers->end())
00392     {
00393         ZipEntryP* entry = itr.value();
00394         Q_ASSERT(entry != 0);
00395         return d->extractFile(itr.key(), *entry, dir, options);
00396     }
00397 
00398     return FileNotFound;
00399 }
00400 
00404 UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options)
00405 {
00406     if (dev == 0)
00407         return InvalidDevice;
00408 
00409     QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
00410     if (itr != d->headers->end()) {
00411         ZipEntryP* entry = itr.value();
00412         Q_ASSERT(entry != 0);
00413         return d->extractFile(itr.key(), *entry, dev, options);
00414     }
00415 
00416     return FileNotFound;
00417 }
00418 
00423 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options)
00424 {
00425     QDir dir(dirname);
00426     ErrorCode ec;
00427 
00428     for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
00429     {
00430         ec = extractFile(*itr, dir, options);
00431         if (ec == FileNotFound)
00432             continue;
00433         if (ec != Ok)
00434             return ec;
00435     }
00436 
00437     return Ok;
00438 }
00439 
00444 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options)
00445 {
00446     ErrorCode ec;
00447 
00448     for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
00449     {
00450         ec = extractFile(*itr, dir, options);
00451         if (ec == FileNotFound)
00452             continue;
00453         if (ec != Ok)
00454             return ec;
00455     }
00456 
00457     return Ok;
00458 }
00459 
00463 void UnZip::setPassword(const QString& pwd)
00464 {
00465     d->password = pwd;
00466 }
00467 
00471 UnZip::ZipEntry::ZipEntry()
00472 {
00473     compressedSize = uncompressedSize = crc32 = 0;
00474     compression = NoCompression;
00475     type = File;
00476     encrypted = false;
00477 }
00478 
00479 
00480 /************************************************************************
00481  Private interface
00482 *************************************************************************/
00483 
00485 UnzipPrivate::UnzipPrivate()
00486 {
00487     skipAllEncrypted = false;
00488     headers = 0;
00489     device = 0;
00490 
00491     uBuffer = (unsigned char*) buffer1;
00492     crcTable = (quint32*) get_crc_table();
00493 
00494     cdOffset = eocdOffset = 0;
00495     cdEntryCount = 0;
00496     unsupportedEntryCount = 0;
00497 }
00498 
00500 UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev)
00501 {
00502     Q_ASSERT(dev != 0);
00503 
00504     if (device != 0)
00505         closeArchive();
00506 
00507     device = dev;
00508 
00509     if (!(device->isOpen() || device->open(QIODevice::ReadOnly)))
00510     {
00511         delete device;
00512         device = 0;
00513 
00514         qDebug() << "Unable to open device for reading";
00515         return UnZip::OpenFailed;
00516     }
00517 
00518     UnZip::ErrorCode ec;
00519 
00520     ec = seekToCentralDirectory();
00521     if (ec != UnZip::Ok)
00522     {
00523         closeArchive();
00524         return ec;
00525     }
00526 
00528     if (cdEntryCount == 0)
00529     {
00530         return UnZip::Ok;
00531     }
00532 
00533     bool continueParsing = true;
00534 
00535     while (continueParsing)
00536     {
00537         if (device->read(buffer1, 4) != 4)
00538             UNZIP_CHECK_FOR_VALID_DATA
00539 
00540         if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01  && buffer1[3] == 0x02) )
00541             break;
00542 
00543         if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok )
00544             break;
00545     }
00546 
00547     if (ec != UnZip::Ok)
00548         closeArchive();
00549 
00550     return ec;
00551 }
00552 
00553 /*
00554     \internal Parses a local header record and makes some consistency check
00555     with the information stored in the Central Directory record for this entry
00556     that has been previously parsed.
00557     \todo Optional consistency check (as a ExtractionOptions flag)
00558 
00559     local file header signature     4 bytes  (0x04034b50)
00560     version needed to extract       2 bytes
00561     general purpose bit flag        2 bytes
00562     compression method              2 bytes
00563     last mod file time              2 bytes
00564     last mod file date              2 bytes
00565     crc-32                          4 bytes
00566     compressed size                 4 bytes
00567     uncompressed size               4 bytes
00568     file name length                2 bytes
00569     extra field length              2 bytes
00570 
00571     file name (variable size)
00572     extra field (variable size)
00573 */
00574 UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry)
00575 {
00576     if (!device->seek(entry.lhOffset))
00577         return UnZip::SeekFailed;
00578 
00579     // Test signature
00580     if (device->read(buffer1, 4) != 4)
00581         return UnZip::ReadFailed;
00582 
00583     if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04))
00584         return UnZip::InvalidArchive;
00585 
00586     if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE)
00587         return UnZip::ReadFailed;
00588 
00589     /*
00590         Check 3rd general purpose bit flag.
00591 
00592         "bit 3: If this bit is set, the fields crc-32, compressed size
00593         and uncompressed size are set to zero in the local
00594         header.  The correct values are put in the data descriptor
00595         immediately following the compressed data."
00596     */
00597     bool hasDataDescriptor = entry.hasDataDescriptor();
00598 
00599     bool checkFailed = false;
00600 
00601     if (!checkFailed)
00602         checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD);
00603     if (!checkFailed)
00604         checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG];
00605     if (!checkFailed)
00606         checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1];
00607     if (!checkFailed)
00608         checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT];
00609     if (!checkFailed)
00610         checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1];
00611     if (!checkFailed)
00612         checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD];
00613     if (!checkFailed)
00614         checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1];
00615     if (!hasDataDescriptor)
00616     {
00617         if (!checkFailed)
00618             checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32);
00619         if (!checkFailed)
00620             checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE);
00621         if (!checkFailed)
00622             checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE);
00623     }
00624 
00625     if (checkFailed)
00626         return UnZip::HeaderConsistencyError;
00627 
00628     // Check filename
00629     quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN);
00630     if (szName == 0)
00631         return UnZip::HeaderConsistencyError;
00632 
00633     if (device->read(buffer2, szName) != szName)
00634         return UnZip::ReadFailed;
00635 
00636     QString filename = QString::fromAscii(buffer2, szName);
00637     if (filename != path)
00638     {
00639         qDebug() << "Filename in local header mismatches.";
00640         return UnZip::HeaderConsistencyError;
00641     }
00642 
00643     // Skip extra field
00644     quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN);
00645     if (szExtra != 0)
00646     {
00647         if (!device->seek(device->pos() + szExtra))
00648             return UnZip::SeekFailed;
00649     }
00650 
00651     entry.dataOffset = device->pos();
00652 
00653     if (hasDataDescriptor)
00654     {
00655         /*
00656             The data descriptor has this OPTIONAL signature: PK\7\8
00657             We try to skip the compressed data relying on the size set in the
00658             Central Directory record.
00659         */
00660         if (!device->seek(device->pos() + entry.szComp))
00661             return UnZip::SeekFailed;
00662 
00663         // Read 4 bytes and check if there is a data descriptor signature
00664         if (device->read(buffer2, 4) != 4)
00665             return UnZip::ReadFailed;
00666 
00667         bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08;
00668         if (hasSignature)
00669         {
00670             if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE)
00671                 return UnZip::ReadFailed;
00672         }
00673         else
00674         {
00675             if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4)
00676                 return UnZip::ReadFailed;
00677         }
00678 
00679         // DD: crc, compressed size, uncompressed size
00680         if (
00681             entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) ||
00682             entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) ||
00683             entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE)
00684             )
00685             return UnZip::HeaderConsistencyError;
00686     }
00687 
00688     return UnZip::Ok;
00689 }
00690 
00712 UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory()
00713 {
00714     qint64 length = device->size();
00715     qint64 offset = length - UNZIP_EOCD_SIZE;
00716 
00717     if (length < UNZIP_EOCD_SIZE)
00718         return UnZip::InvalidArchive;
00719 
00720     if (!device->seek( offset ))
00721         return UnZip::SeekFailed;
00722 
00723     if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
00724         return UnZip::ReadFailed;
00725 
00726     bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06);
00727 
00728     if (eocdFound)
00729     {
00730         // Zip file has no comment (the only variable length field in the EOCD record)
00731         eocdOffset = offset;
00732     }
00733     else
00734     {
00735         qint64 read;
00736         char* p = 0;
00737 
00738         offset -= UNZIP_EOCD_SIZE;
00739 
00740         if (offset <= 0)
00741             return UnZip::InvalidArchive;
00742 
00743         if (!device->seek( offset ))
00744             return UnZip::SeekFailed;
00745 
00746         while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0)
00747         {
00748             if ( (p = strstr(buffer1, "PK\5\6")) != 0)
00749             {
00750                 // Seek to the start of the EOCD record so we can read it fully
00751                 // Yes... we could simply read the missing bytes and append them to the buffer
00752                 // but this is far easier so heck it!
00753                 device->seek( offset + (p - buffer1) );
00754                 eocdFound = true;
00755                 eocdOffset = offset + (p - buffer1);
00756 
00757                 // Read EOCD record
00758                 if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
00759                     return UnZip::ReadFailed;
00760 
00761                 break;
00762             }
00763 
00764             offset -= UNZIP_EOCD_SIZE;
00765             if (offset <= 0)
00766                 return UnZip::InvalidArchive;
00767 
00768             if (!device->seek( offset ))
00769                 return UnZip::SeekFailed;
00770         }
00771     }
00772 
00773     if (!eocdFound)
00774         return UnZip::InvalidArchive;
00775 
00776     // Parse EOCD to locate CD offset
00777     offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4);
00778 
00779     cdOffset = offset;
00780 
00781     cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4);
00782 
00783     quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4);
00784     if (commentLength != 0)
00785     {
00786         QByteArray c = device->read(commentLength);
00787         if (c.count() != commentLength)
00788             return UnZip::ReadFailed;
00789 
00790         comment = c;
00791     }
00792 
00793     // Seek to the start of the CD record
00794     if (!device->seek( cdOffset ))
00795         return UnZip::SeekFailed;
00796 
00797     return UnZip::Ok;
00798 }
00799 
00836 UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord()
00837 {
00838     // Read CD record
00839     if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS)
00840         return UnZip::ReadFailed;
00841 
00842     bool skipEntry = false;
00843 
00844     // Get compression type so we can skip non compatible algorithms
00845     quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD);
00846 
00847     // Get variable size fields length so we can skip the whole record
00848     // if necessary
00849     quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN);
00850     quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN);
00851     quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN);
00852 
00853     quint32 skipLength = szName + szExtra + szComment;
00854 
00855     UnZip::ErrorCode ec = UnZip::Ok;
00856 
00857     if ((compMethod != 0) && (compMethod != 8))
00858     {
00859         qDebug() << "Unsupported compression method. Skipping file.";
00860         skipEntry = true;
00861     }
00862 
00863     // Header parsing may be a problem if version is bigger than UNZIP_VERSION
00864     if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION)
00865     {
00866         qDebug() << "Unsupported PKZip version. Skipping file.";
00867         skipEntry = true;
00868     }
00869 
00870     if (!skipEntry && szName == 0)
00871     {
00872         qDebug() << "Skipping file with no name.";
00873         skipEntry = true;
00874     }
00875 
00876     if (!skipEntry && device->read(buffer2, szName) != szName)
00877     {
00878         ec = UnZip::ReadFailed;
00879         skipEntry = true;
00880     }
00881 
00882     if (skipEntry)
00883     {
00884         if (ec == UnZip::Ok)
00885         {
00886             if (!device->seek( device->pos() + skipLength ))
00887                 ec = UnZip::SeekFailed;
00888 
00889             unsupportedEntryCount++;
00890         }
00891 
00892         return ec;
00893     }
00894 
00895     QString filename = QString::fromAscii(buffer2, szName);
00896 
00897     ZipEntryP* h = new ZipEntryP;
00898     h->compMethod = compMethod;
00899 
00900     h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG];
00901     h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1];
00902 
00903     h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT];
00904     h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1];
00905 
00906     h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD];
00907     h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1];
00908 
00909     h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32);
00910     h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE);
00911     h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE);
00912 
00913     // Skip extra field (if any)
00914     if (szExtra != 0)
00915     {
00916         if (!device->seek( device->pos() + szExtra ))
00917         {
00918             delete h;
00919             return UnZip::SeekFailed;
00920         }
00921     }
00922 
00923     // Read comment field (if any)
00924     if (szComment != 0)
00925     {
00926         if (device->read(buffer2, szComment) != szComment)
00927         {
00928             delete h;
00929             return UnZip::ReadFailed;
00930         }
00931 
00932         h->comment = QString::fromAscii(buffer2, szComment);
00933     }
00934 
00935     h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET);
00936 
00937     if (headers == 0)
00938         headers = new QMap<QString, ZipEntryP*>();
00939     headers->insert(filename, h);
00940 
00941     return UnZip::Ok;
00942 }
00943 
00945 void UnzipPrivate::closeArchive()
00946 {
00947     if (device == 0)
00948         return;
00949 
00950     skipAllEncrypted = false;
00951 
00952     if (headers != 0)
00953     {
00954         qDeleteAll(*headers);
00955         delete headers;
00956         headers = 0;
00957     }
00958 
00959     delete device; device = 0;
00960 
00961     cdOffset = eocdOffset = 0;
00962     cdEntryCount = 0;
00963     unsupportedEntryCount = 0;
00964 
00965     comment.clear();
00966 }
00967 
00969 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options)
00970 {
00971     QString name(path);
00972     QString dirname;
00973     QString directory;
00974 
00975     int pos = name.lastIndexOf('/');
00976 
00977     // This entry is for a directory
00978     if (pos == name.length() - 1)
00979     {
00980         if (options.testFlag(UnZip::SkipPaths))
00981             return UnZip::Ok;
00982 
00983         directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name));
00984         if (!createDirectory(directory))
00985         {
00986             qDebug() << QString("Unable to create directory: %1").arg(directory);
00987             return UnZip::CreateDirFailed;
00988         }
00989 
00990         return UnZip::Ok;
00991     }
00992 
00993     // Extract path from entry
00994     if (pos > 0)
00995     {
00996         // get directory part
00997         dirname = name.left(pos);
00998         if (options.testFlag(UnZip::SkipPaths))
00999         {
01000             directory = dir.absolutePath();
01001         }
01002         else
01003         {
01004             directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname));
01005             if (!createDirectory(directory))
01006             {
01007                 qDebug() << QString("Unable to create directory: %1").arg(directory);
01008                 return UnZip::CreateDirFailed;
01009             }
01010         }
01011         name = name.right(name.length() - pos - 1);
01012     } else directory = dir.absolutePath();
01013 
01014     name = QString("%1/%2").arg(directory).arg(name);
01015 
01016     QFile outFile(name);
01017 
01018     if (!outFile.open(QIODevice::WriteOnly))
01019     {
01020         qDebug() << QString("Unable to open %1 for writing").arg(name);
01021         return UnZip::OpenFailed;
01022     }
01023 
01025 
01026     UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options);
01027 
01028     outFile.close();
01029 
01030     if (ec != UnZip::Ok)
01031     {
01032         if (!outFile.remove())
01033             qDebug() << QString("Unable to remove corrupted file: %1").arg(name);
01034     }
01035 
01036     return ec;
01037 }
01038 
01040 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options)
01041 {
01042     Q_UNUSED(options);
01043     Q_ASSERT(dev != 0);
01044 
01045     if (!entry.lhEntryChecked)
01046     {
01047         UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry);
01048         entry.lhEntryChecked = true;
01049 
01050         if (ec != UnZip::Ok)
01051             return ec;
01052     }
01053 
01054     if (!device->seek(entry.dataOffset))
01055         return UnZip::SeekFailed;
01056 
01057     // Encryption keys
01058     quint32 keys[3];
01059 
01060     if (entry.isEncrypted())
01061     {
01062         UnZip::ErrorCode e = testPassword(keys, path, entry);
01063         if (e != UnZip::Ok)
01064         {
01065             qDebug() << QString("Unable to decrypt %1").arg(path);
01066             return e;
01067         }
01068         entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size
01069     }
01070 
01071     if (entry.szComp == 0)
01072     {
01073         if (entry.crc != 0)
01074             return UnZip::Corrupted;
01075 
01076         return UnZip::Ok;
01077     }
01078 
01079     uInt rep = entry.szComp / UNZIP_READ_BUFFER;
01080     uInt rem = entry.szComp % UNZIP_READ_BUFFER;
01081     uInt cur = 0;
01082 
01083     // extract data
01084     qint64 read;
01085     quint64 tot = 0;
01086 
01087     quint32 myCRC = crc32(0L, Z_NULL, 0);
01088 
01089     if (entry.compMethod == 0)
01090     {
01091         while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 )
01092         {
01093             if (entry.isEncrypted())
01094                 decryptBytes(keys, buffer1, read);
01095 
01096             myCRC = crc32(myCRC, uBuffer, read);
01097 
01098             if (dev->write(buffer1, read) != read)
01099                 return UnZip::WriteFailed;
01100 
01101             cur++;
01102             tot += read;
01103 
01104             if (tot == entry.szComp)
01105                 break;
01106         }
01107 
01108         if (read < 0)
01109             return UnZip::ReadFailed;
01110     }
01111     else if (entry.compMethod == 8)
01112     {
01113         /* Allocate inflate state */
01114         z_stream zstr;
01115         zstr.zalloc = Z_NULL;
01116         zstr.zfree = Z_NULL;
01117         zstr.opaque = Z_NULL;
01118         zstr.next_in = Z_NULL;
01119         zstr.avail_in = 0;
01120 
01121         int zret;
01122 
01123         // Use inflateInit2 with negative windowBits to get raw decompression
01124         if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK )
01125             return UnZip::ZlibError;
01126 
01127         int szDecomp;
01128 
01129         // Decompress until deflate stream ends or end of file
01130         do
01131         {
01132             read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem);
01133             if (read == 0)
01134                 break;
01135             if (read < 0)
01136             {
01137                 (void)inflateEnd(&zstr);
01138                 return UnZip::ReadFailed;
01139             }
01140 
01141             if (entry.isEncrypted())
01142                 decryptBytes(keys, buffer1, read);
01143 
01144             cur++;
01145             tot += read;
01146 
01147             zstr.avail_in = (uInt) read;
01148             zstr.next_in = (Bytef*) buffer1;
01149 
01150 
01151             // Run inflate() on input until output buffer not full
01152             do {
01153                 zstr.avail_out = UNZIP_READ_BUFFER;
01154                 zstr.next_out = (Bytef*) buffer2;;
01155 
01156                 zret = inflate(&zstr, Z_NO_FLUSH);
01157 
01158                 switch (zret) {
01159                     case Z_NEED_DICT:
01160                     case Z_DATA_ERROR:
01161                     case Z_MEM_ERROR:
01162                         inflateEnd(&zstr);
01163                         return UnZip::WriteFailed;
01164                     default:
01165                         ;
01166                 }
01167 
01168                 szDecomp = UNZIP_READ_BUFFER - zstr.avail_out;
01169                 if (dev->write(buffer2, szDecomp) != szDecomp)
01170                 {
01171                     inflateEnd(&zstr);
01172                     return UnZip::ZlibError;
01173                 }
01174 
01175                 myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp);
01176 
01177             } while (zstr.avail_out == 0);
01178 
01179         }
01180         while (zret != Z_STREAM_END);
01181 
01182         inflateEnd(&zstr);
01183     }
01184 
01185     if (myCRC != entry.crc)
01186         return UnZip::Corrupted;
01187 
01188     return UnZip::Ok;
01189 }
01190 
01192 bool UnzipPrivate::createDirectory(const QString& path)
01193 {
01194     QDir d(path);
01195     if (!d.exists())
01196     {
01197         int sep = path.lastIndexOf("/");
01198         if (sep <= 0) return true;
01199 
01200         if (!createDirectory(path.left(sep)))
01201             return false;
01202 
01203         if (!d.mkdir(path))
01204         {
01205             qDebug() << QString("Unable to create directory: %1").arg(path);
01206             return false;
01207         }
01208     }
01209 
01210     return true;
01211 }
01212 
01216 quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const
01217 {
01218     quint32 res = (quint32) data[offset];
01219     res |= (((quint32)data[offset+1]) << 8);
01220     res |= (((quint32)data[offset+2]) << 16);
01221     res |= (((quint32)data[offset+3]) << 24);
01222 
01223     return res;
01224 }
01225 
01229 quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const
01230 {
01231     quint64 res = (quint64) data[offset];
01232     res |= (((quint64)data[offset+1]) << 8);
01233     res |= (((quint64)data[offset+2]) << 16);
01234     res |= (((quint64)data[offset+3]) << 24);
01235     res |= (((quint64)data[offset+1]) << 32);
01236     res |= (((quint64)data[offset+2]) << 40);
01237     res |= (((quint64)data[offset+3]) << 48);
01238     res |= (((quint64)data[offset+3]) << 56);
01239 
01240     return res;
01241 }
01242 
01246 quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const
01247 {
01248     return (quint16) data[offset] | (((quint16)data[offset+1]) << 8);
01249 }
01250 
01254 int UnzipPrivate::decryptByte(quint32 key2) const
01255 {
01256     quint16 temp = ((quint16)(key2) & 0xffff) | 2;
01257     return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
01258 }
01259 
01263 void UnzipPrivate::updateKeys(quint32* keys, int c) const
01264 {
01265     keys[0] = CRC32(keys[0], c);
01266     keys[1] += keys[0] & 0xff;
01267     keys[1] = keys[1] * 134775813L + 1;
01268     keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
01269 }
01270 
01275 void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const
01276 {
01277     keys[0] = 305419896L;
01278     keys[1] = 591751049L;
01279     keys[2] = 878082192L;
01280 
01281     QByteArray pwdBytes = pwd.toAscii();
01282     int sz = pwdBytes.size();
01283     const char* ascii = pwdBytes.data();
01284 
01285     for (int i=0; i<sz; ++i)
01286         updateKeys(keys, (int)ascii[i]);
01287 }
01288 
01294 UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header)
01295 {
01296     Q_UNUSED(file);
01297 
01298     // read encryption keys
01299     if (device->read(buffer1, 12) != 12)
01300         return UnZip::Corrupted;
01301 
01302     // Replace this code if you want to i.e. call some dialog and ask the user for a password
01303     initKeys(password, keys);
01304     if (testKeys(header, keys))
01305         return UnZip::Ok;
01306 
01307     return UnZip::Skip;
01308 }
01309 
01313 bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys)
01314 {
01315     char lastByte;
01316 
01317     // decrypt encryption header
01318     for (int i=0; i<11; ++i)
01319         updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2]));
01320     updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2]));
01321 
01322     // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time
01323     // with no extended header we have to check the crc high-order byte
01324     char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24;
01325 
01326     return (lastByte == c);
01327 }
01328 
01332 void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read)
01333 {
01334     for (int i=0; i<(int)read; ++i)
01335         updateKeys(keys, buffer[i] ^= decryptByte(keys[2]));
01336 }
01337 
01341 QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const
01342 {
01343     QDateTime dt;
01344 
01345     // Usual PKZip low-byte to high-byte order
01346 
01347     // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day
01348     quint16 year = (date[1] >> 1) & 127;
01349     quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7);
01350     quint16 day = date[0] & 31;
01351 
01352     // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision
01353     quint16 hour = (time[1] >> 3) & 31;
01354     quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7);
01355     quint16 seconds = (time[0] & 31) * 2;
01356 
01357     dt.setDate(QDate(1980 + year, month, day));
01358     dt.setTime(QTime(hour, minutes, seconds));
01359     return dt;
01360 }