MythTV  0.26-pre
unzip.cpp
Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Filename: unzip.cpp
00003 ** Last updated [dd/mm/yyyy]: 07/09/2008
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-2008 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
00080 
00081 #define UNZIP_CD_ENTRY_SIZE_NS 42
00082 
00083 #define UNZIP_DD_SIZE 12
00084 
00085 #define UNZIP_EOCD_SIZE 22
00086 
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
00132 
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                         // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here.
00765                         offset -= 1 /*UNZIP_EOCD_SIZE*/;
00766                         if (offset <= 0)
00767                                 return UnZip::InvalidArchive;
00768 
00769                         if (!device->seek( offset ))
00770                                 return UnZip::SeekFailed;
00771                 }
00772         }
00773 
00774         if (!eocdFound)
00775                 return UnZip::InvalidArchive;
00776 
00777         // Parse EOCD to locate CD offset
00778         offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4);
00779 
00780         cdOffset = offset;
00781 
00782         cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4);
00783 
00784         quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4);
00785         if (commentLength != 0)
00786         {
00787                 QByteArray c = device->read(commentLength);
00788                 if (c.count() != commentLength)
00789                         return UnZip::ReadFailed;
00790 
00791                 comment = c;
00792         }
00793 
00794         // Seek to the start of the CD record
00795         if (!device->seek( cdOffset ))
00796                 return UnZip::SeekFailed;
00797 
00798         return UnZip::Ok;
00799 }
00800 
00837 UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord()
00838 {
00839         // Read CD record
00840         if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS)
00841                 return UnZip::ReadFailed;
00842 
00843         bool skipEntry = false;
00844 
00845         // Get compression type so we can skip non compatible algorithms
00846         quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD);
00847 
00848         // Get variable size fields length so we can skip the whole record
00849         // if necessary
00850         quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN);
00851         quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN);
00852         quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN);
00853 
00854         quint32 skipLength = szName + szExtra + szComment;
00855 
00856         UnZip::ErrorCode ec = UnZip::Ok;
00857 
00858         if ((compMethod != 0) && (compMethod != 8))
00859         {
00860                 qDebug() << "Unsupported compression method. Skipping file.";
00861                 skipEntry = true;
00862         }
00863 
00864         // Header parsing may be a problem if version is bigger than UNZIP_VERSION
00865         if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION)
00866         {
00867                 qDebug() << "Unsupported PKZip version. Skipping file.";
00868                 skipEntry = true;
00869         }
00870 
00871         if (!skipEntry && szName == 0)
00872         {
00873                 qDebug() << "Skipping file with no name.";
00874                 skipEntry = true;
00875         }
00876 
00877         if (!skipEntry && device->read(buffer2, szName) != szName)
00878         {
00879                 ec = UnZip::ReadFailed;
00880                 skipEntry = true;
00881         }
00882 
00883         if (skipEntry)
00884         {
00885                 if (ec == UnZip::Ok)
00886                 {
00887                         if (!device->seek( device->pos() + skipLength ))
00888                                 ec = UnZip::SeekFailed;
00889 
00890                         unsupportedEntryCount++;
00891                 }
00892 
00893                 return ec;
00894         }
00895 
00896         QString filename = QString::fromAscii(buffer2, szName);
00897 
00898         ZipEntryP* h = new ZipEntryP;
00899         h->compMethod = compMethod;
00900 
00901         h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG];
00902         h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1];
00903 
00904         h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT];
00905         h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1];
00906 
00907         h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD];
00908         h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1];
00909 
00910         h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32);
00911         h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE);
00912         h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE);
00913 
00914         // Skip extra field (if any)
00915         if (szExtra != 0)
00916         {
00917                 if (!device->seek( device->pos() + szExtra ))
00918                 {
00919                         delete h;
00920                         return UnZip::SeekFailed;
00921                 }
00922         }
00923 
00924         // Read comment field (if any)
00925         if (szComment != 0)
00926         {
00927                 if (device->read(buffer2, szComment) != szComment)
00928                 {
00929                         delete h;
00930                         return UnZip::ReadFailed;
00931                 }
00932 
00933                 h->comment = QString::fromAscii(buffer2, szComment);
00934         }
00935 
00936         h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET);
00937 
00938         if (headers == 0)
00939                 headers = new QMap<QString, ZipEntryP*>();
00940         headers->insert(filename, h);
00941 
00942         return UnZip::Ok;
00943 }
00944 
00946 void UnzipPrivate::closeArchive()
00947 {
00948         if (device == 0)
00949                 return;
00950 
00951         skipAllEncrypted = false;
00952 
00953         if (headers != 0)
00954         {
00955                 qDeleteAll(*headers);
00956                 delete headers;
00957                 headers = 0;
00958         }
00959 
00960         delete device; device = 0;
00961 
00962         cdOffset = eocdOffset = 0;
00963         cdEntryCount = 0;
00964         unsupportedEntryCount = 0;
00965 
00966         comment.clear();
00967 }
00968 
00970 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options)
00971 {
00972         QString name(path);
00973         QString dirname;
00974         QString directory;
00975 
00976         int pos = name.lastIndexOf('/');
00977 
00978         // This entry is for a directory
00979         if (pos == name.length() - 1)
00980         {
00981                 if (options.testFlag(UnZip::SkipPaths))
00982                         return UnZip::Ok;
00983 
00984                 directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name));
00985                 if (!createDirectory(directory))
00986                 {
00987                         qDebug() << QString("Unable to create directory: %1").arg(directory);
00988                         return UnZip::CreateDirFailed;
00989                 }
00990 
00991                 return UnZip::Ok;
00992         }
00993 
00994         // Extract path from entry
00995         if (pos > 0)
00996         {
00997                 // get directory part
00998                 dirname = name.left(pos);
00999                 if (options.testFlag(UnZip::SkipPaths))
01000                 {
01001                         directory = dir.absolutePath();
01002                 }
01003                 else
01004                 {
01005                         directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname));
01006                         if (!createDirectory(directory))
01007                         {
01008                                 qDebug() << QString("Unable to create directory: %1").arg(directory);
01009                                 return UnZip::CreateDirFailed;
01010                         }
01011                 }
01012                 name = name.right(name.length() - pos - 1);
01013         } else directory = dir.absolutePath();
01014 
01015         name = QString("%1/%2").arg(directory).arg(name);
01016 
01017         QFile outFile(name);
01018 
01019         if (!outFile.open(QIODevice::WriteOnly))
01020         {
01021                 qDebug() << QString("Unable to open %1 for writing").arg(name);
01022                 return UnZip::OpenFailed;
01023         }
01024 
01026 
01027         UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options);
01028 
01029         outFile.close();
01030 
01031         if (ec != UnZip::Ok)
01032         {
01033                 if (!outFile.remove())
01034                         qDebug() << QString("Unable to remove corrupted file: %1").arg(name);
01035         }
01036 
01037         return ec;
01038 }
01039 
01041 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options)
01042 {
01043         Q_UNUSED(options);
01044         Q_ASSERT(dev != 0);
01045 
01046         if (!entry.lhEntryChecked)
01047         {
01048                 UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry);
01049                 entry.lhEntryChecked = true;
01050 
01051                 if (ec != UnZip::Ok)
01052                         return ec;
01053         }
01054 
01055         if (!device->seek(entry.dataOffset))
01056                 return UnZip::SeekFailed;
01057 
01058         // Encryption keys
01059         quint32 keys[3];
01060 
01061         if (entry.isEncrypted())
01062         {
01063                 UnZip::ErrorCode e = testPassword(keys, path, entry);
01064                 if (e != UnZip::Ok)
01065                 {
01066                         qDebug() << QString("Unable to decrypt %1").arg(path);
01067                         return e;
01068                 }
01069                 entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size
01070         }
01071 
01072         if (entry.szComp == 0)
01073         {
01074                 if (entry.crc != 0)
01075                         return UnZip::Corrupted;
01076 
01077                 return UnZip::Ok;
01078         }
01079 
01080         uInt rep = entry.szComp / UNZIP_READ_BUFFER;
01081         uInt rem = entry.szComp % UNZIP_READ_BUFFER;
01082         uInt cur = 0;
01083 
01084         // extract data
01085         qint64 read;
01086         quint64 tot = 0;
01087 
01088         quint32 myCRC = crc32(0L, Z_NULL, 0);
01089 
01090         if (entry.compMethod == 0)
01091         {
01092                 while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 )
01093                 {
01094                         if (entry.isEncrypted())
01095                                 decryptBytes(keys, buffer1, read);
01096 
01097                         myCRC = crc32(myCRC, uBuffer, read);
01098 
01099                         if (dev->write(buffer1, read) != read)
01100                                 return UnZip::WriteFailed;
01101 
01102                         cur++;
01103                         tot += read;
01104 
01105                         if (tot == entry.szComp)
01106                                 break;
01107                 }
01108 
01109                 if (read < 0)
01110                         return UnZip::ReadFailed;
01111         }
01112         else if (entry.compMethod == 8)
01113         {
01114                 /* Allocate inflate state */
01115                 z_stream zstr;
01116                 zstr.zalloc = Z_NULL;
01117                 zstr.zfree = Z_NULL;
01118                 zstr.opaque = Z_NULL;
01119                 zstr.next_in = Z_NULL;
01120                 zstr.avail_in = 0;
01121 
01122                 int zret;
01123 
01124                 // Use inflateInit2 with negative windowBits to get raw decompression
01125                 if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK )
01126                         return UnZip::ZlibError;
01127 
01128                 int szDecomp;
01129 
01130                 // Decompress until deflate stream ends or end of file
01131                 do
01132                 {
01133                         read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem);
01134                         if (read == 0)
01135                                 break;
01136                         if (read < 0)
01137                         {
01138                                 (void)inflateEnd(&zstr);
01139                                 return UnZip::ReadFailed;
01140                         }
01141 
01142                         if (entry.isEncrypted())
01143                                 decryptBytes(keys, buffer1, read);
01144 
01145                         cur++;
01146                         tot += read;
01147 
01148                         zstr.avail_in = (uInt) read;
01149                         zstr.next_in = (Bytef*) buffer1;
01150 
01151 
01152                         // Run inflate() on input until output buffer not full
01153                         do {
01154                                 zstr.avail_out = UNZIP_READ_BUFFER;
01155                                 zstr.next_out = (Bytef*) buffer2;;
01156 
01157                                 zret = inflate(&zstr, Z_NO_FLUSH);
01158 
01159                                 switch (zret) {
01160                                         case Z_NEED_DICT:
01161                                         case Z_DATA_ERROR:
01162                                         case Z_MEM_ERROR:
01163                                                 inflateEnd(&zstr);
01164                                                 return UnZip::WriteFailed;
01165                                         default:
01166                                                 ;
01167                                 }
01168 
01169                                 szDecomp = UNZIP_READ_BUFFER - zstr.avail_out;
01170                                 if (dev->write(buffer2, szDecomp) != szDecomp)
01171                                 {
01172                                         inflateEnd(&zstr);
01173                                         return UnZip::ZlibError;
01174                                 }
01175 
01176                                 myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp);
01177 
01178                         } while (zstr.avail_out == 0);
01179 
01180                 }
01181                 while (zret != Z_STREAM_END);
01182 
01183                 inflateEnd(&zstr);
01184         }
01185 
01186         if (myCRC != entry.crc)
01187                 return UnZip::Corrupted;
01188 
01189         return UnZip::Ok;
01190 }
01191 
01193 bool UnzipPrivate::createDirectory(const QString& path)
01194 {
01195         QDir d(path);
01196         if (!d.exists())
01197         {
01198                 int sep = path.lastIndexOf("/");
01199                 if (sep <= 0) return true;
01200 
01201                 if (!createDirectory(path.left(sep)))
01202                         return false;
01203 
01204                 if (!d.mkdir(path))
01205                 {
01206                         qDebug() << QString("Unable to create directory: %1").arg(path);
01207                         return false;
01208                 }
01209         }
01210 
01211         return true;
01212 }
01213 
01217 quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const
01218 {
01219         quint32 res = (quint32) data[offset];
01220         res |= (((quint32)data[offset+1]) << 8);
01221         res |= (((quint32)data[offset+2]) << 16);
01222         res |= (((quint32)data[offset+3]) << 24);
01223 
01224         return res;
01225 }
01226 
01230 quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const
01231 {
01232         quint64 res = (quint64) data[offset];
01233         res |= (((quint64)data[offset+1]) << 8);
01234         res |= (((quint64)data[offset+2]) << 16);
01235         res |= (((quint64)data[offset+3]) << 24);
01236         res |= (((quint64)data[offset+1]) << 32);
01237         res |= (((quint64)data[offset+2]) << 40);
01238         res |= (((quint64)data[offset+3]) << 48);
01239         res |= (((quint64)data[offset+3]) << 56);
01240 
01241         return res;
01242 }
01243 
01247 quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const
01248 {
01249         return (quint16) data[offset] | (((quint16)data[offset+1]) << 8);
01250 }
01251 
01255 int UnzipPrivate::decryptByte(quint32 key2) const
01256 {
01257         quint16 temp = ((quint16)(key2) & 0xffff) | 2;
01258         return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
01259 }
01260 
01264 void UnzipPrivate::updateKeys(quint32* keys, int c) const
01265 {
01266         keys[0] = CRC32(keys[0], c);
01267         keys[1] += keys[0] & 0xff;
01268         keys[1] = keys[1] * 134775813L + 1;
01269         keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
01270 }
01271 
01276 void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const
01277 {
01278         keys[0] = 305419896L;
01279         keys[1] = 591751049L;
01280         keys[2] = 878082192L;
01281 
01282         QByteArray pwdBytes = pwd.toAscii();
01283         int sz = pwdBytes.size();
01284         const char* ascii = pwdBytes.data();
01285 
01286         for (int i=0; i<sz; ++i)
01287                 updateKeys(keys, (int)ascii[i]);
01288 }
01289 
01295 UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header)
01296 {
01297         Q_UNUSED(file);
01298 
01299         // read encryption keys
01300         if (device->read(buffer1, 12) != 12)
01301                 return UnZip::Corrupted;
01302 
01303         // Replace this code if you want to i.e. call some dialog and ask the user for a password
01304         initKeys(password, keys);
01305         if (testKeys(header, keys))
01306                 return UnZip::Ok;
01307 
01308         return UnZip::Skip;
01309 }
01310 
01314 bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys)
01315 {
01316         char lastByte;
01317 
01318         // decrypt encryption header
01319         for (int i=0; i<11; ++i)
01320                 updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2]));
01321         updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2]));
01322 
01323         // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time
01324         // with no extended header we have to check the crc high-order byte
01325         char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24;
01326 
01327         return (lastByte == c);
01328 }
01329 
01333 void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read)
01334 {
01335         for (int i=0; i<(int)read; ++i)
01336                 updateKeys(keys, buffer[i] ^= decryptByte(keys[2]));
01337 }
01338 
01342 QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const
01343 {
01344         QDateTime dt;
01345 
01346         // Usual PKZip low-byte to high-byte order
01347 
01348         // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day
01349         quint16 year = (date[1] >> 1) & 127;
01350         quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7);
01351         quint16 day = date[0] & 31;
01352 
01353         // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision
01354         quint16 hour = (time[1] >> 3) & 31;
01355         quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7);
01356         quint16 seconds = (time[0] & 31) * 2;
01357 
01358         dt.setDate(QDate(1980 + year, month, day));
01359         dt.setTime(QTime(hour, minutes, seconds));
01360         return dt;
01361 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends