--- /dev/null
+/****************************************************************************\r
+** Filename: unzip.cpp\r
+** Last updated [dd/mm/yyyy]: 07/09/2008\r
+**\r
+** pkzip 2.0 decompression.\r
+**\r
+** Some of the code has been inspired by other open source projects,\r
+** (mainly Info-Zip and Gilles Vollant's minizip).\r
+** Compression and decompression actually uses the zlib library.\r
+**\r
+** Copyright (C) 2007-2008 Angius Fabrizio. All rights reserved.\r
+**\r
+** This file is part of the OSDaB project (http://osdab.sourceforge.net/).\r
+**\r
+** This file may be distributed and/or modified under the terms of the\r
+** GNU General Public License version 2 as published by the Free Software\r
+** Foundation and appearing in the file LICENSE.GPL included in the\r
+** packaging of this file.\r
+**\r
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE\r
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.\r
+**\r
+** See the file LICENSE.GPL that came with this software distribution or\r
+** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.\r
+**\r
+**********************************************************************/\r
+\r
+#include "unzip.h"\r
+#include "unzip_p.h"\r
+#include "zipentry_p.h"\r
+\r
+#include <QString>\r
+#include <QStringList>\r
+#include <QDir>\r
+#include <QFile>\r
+#include <QCoreApplication>\r
+\r
+// You can remove this #include if you replace the qDebug() statements.\r
+#include <QtDebug>\r
+\r
+/*!\r
+ \class UnZip unzip.h\r
+\r
+ \brief PKZip 2.0 file decompression.\r
+ Compatibility with later versions is not ensured as they may use\r
+ unsupported compression algorithms.\r
+ Versions after 2.7 may have an incompatible header format and thus be\r
+ completely incompatible.\r
+*/\r
+\r
+/*! \enum UnZip::ErrorCode The result of a decompression operation.\r
+ \value UnZip::Ok No error occurred.\r
+ \value UnZip::ZlibInit Failed to init or load the zlib library.\r
+ \value UnZip::ZlibError The zlib library returned some error.\r
+ \value UnZip::OpenFailed Unable to create or open a device.\r
+ \value UnZip::PartiallyCorrupted Corrupted zip archive - some files could be extracted.\r
+ \value UnZip::Corrupted Corrupted or invalid zip archive.\r
+ \value UnZip::WrongPassword Unable to decrypt a password protected file.\r
+ \value UnZip::NoOpenArchive No archive has been opened yet.\r
+ \value UnZip::FileNotFound Unable to find the requested file in the archive.\r
+ \value UnZip::ReadFailed Reading of a file failed.\r
+ \value UnZip::WriteFailed Writing of a file failed.\r
+ \value UnZip::SeekFailed Seek failed.\r
+ \value UnZip::CreateDirFailed Could not create a directory.\r
+ \value UnZip::InvalidDevice A null device has been passed as parameter.\r
+ \value UnZip::InvalidArchive This is not a valid (or supported) ZIP archive.\r
+ \value UnZip::HeaderConsistencyError Local header record info does not match with the central directory record info. The archive may be corrupted.\r
+\r
+ \value UnZip::Skip Internal use only.\r
+ \value UnZip::SkipAll Internal use only.\r
+*/\r
+\r
+/*! \enum UnZip::ExtractionOptions Some options for the file extraction methods.\r
+ \value UnZip::ExtractPaths Default. Does not ignore the path of the zipped files.\r
+ \value UnZip::SkipPaths Default. Ignores the path of the zipped files and extracts them all to the same root directory.\r
+*/\r
+\r
+//! Local header size (excluding signature, excluding variable length fields)\r
+#define UNZIP_LOCAL_HEADER_SIZE 26\r
+//! Central Directory file entry size (excluding signature, excluding variable length fields)\r
+#define UNZIP_CD_ENTRY_SIZE_NS 42\r
+//! Data descriptor size (excluding signature)\r
+#define UNZIP_DD_SIZE 12\r
+//! End Of Central Directory size (including signature, excluding variable length fields)\r
+#define UNZIP_EOCD_SIZE 22\r
+//! Local header entry encryption header size\r
+#define UNZIP_LOCAL_ENC_HEADER_SIZE 12\r
+\r
+// Some offsets inside a CD record (excluding signature)\r
+#define UNZIP_CD_OFF_VERSION 0\r
+#define UNZIP_CD_OFF_GPFLAG 4\r
+#define UNZIP_CD_OFF_CMETHOD 6\r
+#define UNZIP_CD_OFF_MODT 8\r
+#define UNZIP_CD_OFF_MODD 10\r
+#define UNZIP_CD_OFF_CRC32 12\r
+#define UNZIP_CD_OFF_CSIZE 16\r
+#define UNZIP_CD_OFF_USIZE 20\r
+#define UNZIP_CD_OFF_NAMELEN 24\r
+#define UNZIP_CD_OFF_XLEN 26\r
+#define UNZIP_CD_OFF_COMMLEN 28\r
+#define UNZIP_CD_OFF_LHOFFSET 38\r
+\r
+// Some offsets inside a local header record (excluding signature)\r
+#define UNZIP_LH_OFF_VERSION 0\r
+#define UNZIP_LH_OFF_GPFLAG 2\r
+#define UNZIP_LH_OFF_CMETHOD 4\r
+#define UNZIP_LH_OFF_MODT 6\r
+#define UNZIP_LH_OFF_MODD 8\r
+#define UNZIP_LH_OFF_CRC32 10\r
+#define UNZIP_LH_OFF_CSIZE 14\r
+#define UNZIP_LH_OFF_USIZE 18\r
+#define UNZIP_LH_OFF_NAMELEN 22\r
+#define UNZIP_LH_OFF_XLEN 24\r
+\r
+// Some offsets inside a data descriptor record (excluding signature)\r
+#define UNZIP_DD_OFF_CRC32 0\r
+#define UNZIP_DD_OFF_CSIZE 4\r
+#define UNZIP_DD_OFF_USIZE 8\r
+\r
+// Some offsets inside a EOCD record\r
+#define UNZIP_EOCD_OFF_ENTRIES 6\r
+#define UNZIP_EOCD_OFF_CDOFF 12\r
+#define UNZIP_EOCD_OFF_COMMLEN 16\r
+\r
+/*!\r
+ Max version handled by this API.\r
+ 0x1B = 2.7 --> full compatibility only up to version 2.0 (0x14)\r
+ versions from 2.1 to 2.7 may use unsupported compression methods\r
+ versions after 2.7 may have an incompatible header format\r
+*/\r
+#define UNZIP_VERSION 0x1B\r
+//! Full compatibility granted until this version\r
+#define UNZIP_VERSION_STRICT 0x14\r
+\r
+//! CRC32 routine\r
+#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8)\r
+\r
+//! Checks if some file has been already extracted.\r
+#define UNZIP_CHECK_FOR_VALID_DATA \\r
+ {\\r
+ if (headers != 0)\\r
+ {\\r
+ qDebug() << "Corrupted zip archive. Some files might be extracted.";\\r
+ ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\\r
+ break;\\r
+ }\\r
+ else\\r
+ {\\r
+ delete device;\\r
+ device = 0;\\r
+ qDebug() << "Corrupted or invalid zip archive";\\r
+ ec = UnZip::Corrupted;\\r
+ break;\\r
+ }\\r
+ }\r
+\r
+\r
+/************************************************************************\r
+ Public interface\r
+*************************************************************************/\r
+\r
+/*!\r
+ Creates a new Zip file decompressor.\r
+*/\r
+UnZip::UnZip()\r
+{\r
+ d = new UnzipPrivate;\r
+}\r
+\r
+/*!\r
+ Closes any open archive and releases used resources.\r
+*/\r
+UnZip::~UnZip()\r
+{\r
+ closeArchive();\r
+ delete d;\r
+}\r
+\r
+/*!\r
+ Returns true if there is an open archive.\r
+*/\r
+bool UnZip::isOpen() const\r
+{\r
+ return d->device != 0;\r
+}\r
+\r
+/*!\r
+ Opens a zip archive and reads the files list. Closes any previously opened archive.\r
+*/\r
+UnZip::ErrorCode UnZip::openArchive(const QString& filename)\r
+{\r
+ QFile* file = new QFile(filename);\r
+\r
+ if (!file->exists()) {\r
+ delete file;\r
+ return UnZip::FileNotFound;\r
+ }\r
+\r
+ if (!file->open(QIODevice::ReadOnly)) {\r
+ delete file;\r
+ return UnZip::OpenFailed;\r
+ }\r
+\r
+ return openArchive(file);\r
+}\r
+\r
+/*!\r
+ Opens a zip archive and reads the entries list.\r
+ Closes any previously opened archive.\r
+ \warning The class takes ownership of the device so don't delete it!\r
+*/\r
+UnZip::ErrorCode UnZip::openArchive(QIODevice* device)\r
+{\r
+ if (device == 0)\r
+ {\r
+ qDebug() << "Invalid device.";\r
+ return UnZip::InvalidDevice;\r
+ }\r
+\r
+ return d->openArchive(device);\r
+}\r
+\r
+/*!\r
+ Closes the archive and releases all the used resources (like cached passwords).\r
+*/\r
+void UnZip::closeArchive()\r
+{\r
+ d->closeArchive();\r
+}\r
+\r
+QString UnZip::archiveComment() const\r
+{\r
+ if (d->device == 0)\r
+ return QString();\r
+ return d->comment;\r
+}\r
+\r
+/*!\r
+ Returns a locale translated error string for a given error code.\r
+*/\r
+QString UnZip::formatError(UnZip::ErrorCode c) const\r
+{\r
+ switch (c)\r
+ {\r
+ case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break;\r
+ case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break;\r
+ case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break;\r
+ case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break;\r
+ case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break;\r
+ case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break;\r
+ case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break;\r
+ case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break;\r
+ case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break;\r
+ case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break;\r
+ case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break;\r
+ case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break;\r
+ case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break;\r
+ case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break;\r
+ case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break;\r
+ case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break;\r
+ default: ;\r
+ }\r
+\r
+ return QCoreApplication::translate("UnZip", "Unknown error.");\r
+}\r
+\r
+/*!\r
+ Returns true if the archive contains a file with the given path and name.\r
+*/\r
+bool UnZip::contains(const QString& file) const\r
+{\r
+ if (d->headers == 0)\r
+ return false;\r
+\r
+ return d->headers->contains(file);\r
+}\r
+\r
+/*!\r
+ Returns complete paths of files and directories in this archive.\r
+*/\r
+QStringList UnZip::fileList() const\r
+{\r
+ return d->headers == 0 ? QStringList() : d->headers->keys();\r
+}\r
+\r
+/*!\r
+ Returns information for each (correctly parsed) entry of this archive.\r
+*/\r
+QList<UnZip::ZipEntry> UnZip::entryList() const\r
+{\r
+ QList<UnZip::ZipEntry> list;\r
+\r
+ if (d->headers != 0)\r
+ {\r
+ for (QMap<QString,ZipEntryP*>::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it)\r
+ {\r
+ const ZipEntryP* entry = it.value();\r
+ Q_ASSERT(entry != 0);\r
+\r
+ ZipEntry z;\r
+\r
+ z.filename = it.key();\r
+ if (!entry->comment.isEmpty())\r
+ z.comment = entry->comment;\r
+ z.compressedSize = entry->szComp;\r
+ z.uncompressedSize = entry->szUncomp;\r
+ z.crc32 = entry->crc;\r
+ z.lastModified = d->convertDateTime(entry->modDate, entry->modTime);\r
+\r
+ z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression;\r
+ z.type = z.filename.endsWith("/") ? Directory : File;\r
+\r
+ z.encrypted = entry->isEncrypted();\r
+\r
+ list.append(z);\r
+ }\r
+ }\r
+\r
+ return list;\r
+}\r
+\r
+/*!\r
+ Extracts the whole archive to a directory.\r
+*/\r
+UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options)\r
+{\r
+ return extractAll(QDir(dirname), options);\r
+}\r
+\r
+/*!\r
+ Extracts the whole archive to a directory.\r
+*/\r
+UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options)\r
+{\r
+ // this should only happen if we didn't call openArchive() yet\r
+ if (d->device == 0)\r
+ return NoOpenArchive;\r
+\r
+ if (d->headers == 0)\r
+ return Ok;\r
+\r
+ bool end = false;\r
+ for (QMap<QString,ZipEntryP*>::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr)\r
+ {\r
+ ZipEntryP* entry = itr.value();\r
+ Q_ASSERT(entry != 0);\r
+\r
+ if ((entry->isEncrypted()) && d->skipAllEncrypted)\r
+ continue;\r
+\r
+ switch (d->extractFile(itr.key(), *entry, dir, options))\r
+ {\r
+ case Corrupted:\r
+ qDebug() << "Removing corrupted entry" << itr.key();\r
+ d->headers->erase(itr++);\r
+ if (itr == d->headers->end())\r
+ end = true;\r
+ break;\r
+ case CreateDirFailed:\r
+ break;\r
+ case Skip:\r
+ break;\r
+ case SkipAll:\r
+ d->skipAllEncrypted = true;\r
+ break;\r
+ default:\r
+ ;\r
+ }\r
+\r
+ if (end)\r
+ break;\r
+ }\r
+\r
+ return Ok;\r
+}\r
+\r
+/*!\r
+ Extracts a single file to a directory.\r
+*/\r
+UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options)\r
+{\r
+ return extractFile(filename, QDir(dirname), options);\r
+}\r
+\r
+/*!\r
+ Extracts a single file to a directory.\r
+*/\r
+UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options)\r
+{\r
+ QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);\r
+ if (itr != d->headers->end())\r
+ {\r
+ ZipEntryP* entry = itr.value();\r
+ Q_ASSERT(entry != 0);\r
+ return d->extractFile(itr.key(), *entry, dir, options);\r
+ }\r
+\r
+ return FileNotFound;\r
+}\r
+\r
+/*!\r
+ Extracts a single file to a directory.\r
+*/\r
+UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options)\r
+{\r
+ if (dev == 0)\r
+ return InvalidDevice;\r
+\r
+ QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);\r
+ if (itr != d->headers->end()) {\r
+ ZipEntryP* entry = itr.value();\r
+ Q_ASSERT(entry != 0);\r
+ return d->extractFile(itr.key(), *entry, dev, options);\r
+ }\r
+\r
+ return FileNotFound;\r
+}\r
+\r
+/*!\r
+ Extracts a list of files.\r
+ Stops extraction at the first error (but continues if a file does not exist in the archive).\r
+ */\r
+UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options)\r
+{\r
+ QDir dir(dirname);\r
+ ErrorCode ec;\r
+\r
+ for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)\r
+ {\r
+ ec = extractFile(*itr, dir, options);\r
+ if (ec == FileNotFound)\r
+ continue;\r
+ if (ec != Ok)\r
+ return ec;\r
+ }\r
+\r
+ return Ok;\r
+}\r
+\r
+/*!\r
+ Extracts a list of files.\r
+ Stops extraction at the first error (but continues if a file does not exist in the archive).\r
+ */\r
+UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options)\r
+{\r
+ ErrorCode ec;\r
+\r
+ for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)\r
+ {\r
+ ec = extractFile(*itr, dir, options);\r
+ if (ec == FileNotFound)\r
+ continue;\r
+ if (ec != Ok)\r
+ return ec;\r
+ }\r
+\r
+ return Ok;\r
+}\r
+\r
+/*!\r
+ Remove/replace this method to add your own password retrieval routine.\r
+*/\r
+void UnZip::setPassword(const QString& pwd)\r
+{\r
+ d->password = pwd;\r
+}\r
+\r
+/*!\r
+ ZipEntry constructor - initialize data. Type is set to File.\r
+*/\r
+UnZip::ZipEntry::ZipEntry()\r
+{\r
+ compressedSize = uncompressedSize = crc32 = 0;\r
+ compression = NoCompression;\r
+ type = File;\r
+ encrypted = false;\r
+}\r
+\r
+\r
+/************************************************************************\r
+ Private interface\r
+*************************************************************************/\r
+\r
+//! \internal\r
+UnzipPrivate::UnzipPrivate()\r
+{\r
+ skipAllEncrypted = false;\r
+ headers = 0;\r
+ device = 0;\r
+\r
+ uBuffer = (unsigned char*) buffer1;\r
+ crcTable = (quint32*) get_crc_table();\r
+\r
+ cdOffset = eocdOffset = 0;\r
+ cdEntryCount = 0;\r
+ unsupportedEntryCount = 0;\r
+}\r
+\r
+//! \internal Parses a Zip archive.\r
+UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev)\r
+{\r
+ Q_ASSERT(dev != 0);\r
+\r
+ if (device != 0)\r
+ closeArchive();\r
+\r
+ device = dev;\r
+\r
+ if (!(device->isOpen() || device->open(QIODevice::ReadOnly)))\r
+ {\r
+ delete device;\r
+ device = 0;\r
+\r
+ qDebug() << "Unable to open device for reading";\r
+ return UnZip::OpenFailed;\r
+ }\r
+\r
+ UnZip::ErrorCode ec;\r
+\r
+ ec = seekToCentralDirectory();\r
+ if (ec != UnZip::Ok)\r
+ {\r
+ closeArchive();\r
+ return ec;\r
+ }\r
+\r
+ //! \todo Ignore CD entry count? CD may be corrupted.\r
+ if (cdEntryCount == 0)\r
+ {\r
+ return UnZip::Ok;\r
+ }\r
+\r
+ bool continueParsing = true;\r
+\r
+ while (continueParsing)\r
+ {\r
+ if (device->read(buffer1, 4) != 4)\r
+ UNZIP_CHECK_FOR_VALID_DATA\r
+\r
+ if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) )\r
+ break;\r
+\r
+ if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok )\r
+ break;\r
+ }\r
+\r
+ if (ec != UnZip::Ok)\r
+ closeArchive();\r
+\r
+ return ec;\r
+}\r
+\r
+/*\r
+ \internal Parses a local header record and makes some consistency check\r
+ with the information stored in the Central Directory record for this entry\r
+ that has been previously parsed.\r
+ \todo Optional consistency check (as a ExtractionOptions flag)\r
+\r
+ local file header signature 4 bytes (0x04034b50)\r
+ version needed to extract 2 bytes\r
+ general purpose bit flag 2 bytes\r
+ compression method 2 bytes\r
+ last mod file time 2 bytes\r
+ last mod file date 2 bytes\r
+ crc-32 4 bytes\r
+ compressed size 4 bytes\r
+ uncompressed size 4 bytes\r
+ file name length 2 bytes\r
+ extra field length 2 bytes\r
+\r
+ file name (variable size)\r
+ extra field (variable size)\r
+*/\r
+UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry)\r
+{\r
+ if (!device->seek(entry.lhOffset))\r
+ return UnZip::SeekFailed;\r
+\r
+ // Test signature\r
+ if (device->read(buffer1, 4) != 4)\r
+ return UnZip::ReadFailed;\r
+\r
+ if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04))\r
+ return UnZip::InvalidArchive;\r
+\r
+ if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE)\r
+ return UnZip::ReadFailed;\r
+\r
+ /*\r
+ Check 3rd general purpose bit flag.\r
+\r
+ "bit 3: If this bit is set, the fields crc-32, compressed size\r
+ and uncompressed size are set to zero in the local\r
+ header. The correct values are put in the data descriptor\r
+ immediately following the compressed data."\r
+ */\r
+ bool hasDataDescriptor = entry.hasDataDescriptor();\r
+\r
+ bool checkFailed = false;\r
+\r
+ if (!checkFailed)\r
+ checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD);\r
+ if (!checkFailed)\r
+ checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG];\r
+ if (!checkFailed)\r
+ checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1];\r
+ if (!checkFailed)\r
+ checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT];\r
+ if (!checkFailed)\r
+ checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1];\r
+ if (!checkFailed)\r
+ checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD];\r
+ if (!checkFailed)\r
+ checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1];\r
+ if (!hasDataDescriptor)\r
+ {\r
+ if (!checkFailed)\r
+ checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32);\r
+ if (!checkFailed)\r
+ checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE);\r
+ if (!checkFailed)\r
+ checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE);\r
+ }\r
+\r
+ if (checkFailed)\r
+ return UnZip::HeaderConsistencyError;\r
+\r
+ // Check filename\r
+ quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN);\r
+ if (szName == 0)\r
+ return UnZip::HeaderConsistencyError;\r
+\r
+ if (device->read(buffer2, szName) != szName)\r
+ return UnZip::ReadFailed;\r
+\r
+ QString filename = QString::fromAscii(buffer2, szName);\r
+ if (filename != path)\r
+ {\r
+ qDebug() << "Filename in local header mismatches.";\r
+ return UnZip::HeaderConsistencyError;\r
+ }\r
+\r
+ // Skip extra field\r
+ quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN);\r
+ if (szExtra != 0)\r
+ {\r
+ if (!device->seek(device->pos() + szExtra))\r
+ return UnZip::SeekFailed;\r
+ }\r
+\r
+ entry.dataOffset = device->pos();\r
+\r
+ if (hasDataDescriptor)\r
+ {\r
+ /*\r
+ The data descriptor has this OPTIONAL signature: PK\7\8\r
+ We try to skip the compressed data relying on the size set in the\r
+ Central Directory record.\r
+ */\r
+ if (!device->seek(device->pos() + entry.szComp))\r
+ return UnZip::SeekFailed;\r
+\r
+ // Read 4 bytes and check if there is a data descriptor signature\r
+ if (device->read(buffer2, 4) != 4)\r
+ return UnZip::ReadFailed;\r
+\r
+ bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08;\r
+ if (hasSignature)\r
+ {\r
+ if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE)\r
+ return UnZip::ReadFailed;\r
+ }\r
+ else\r
+ {\r
+ if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4)\r
+ return UnZip::ReadFailed;\r
+ }\r
+\r
+ // DD: crc, compressed size, uncompressed size\r
+ if (\r
+ entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) ||\r
+ entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) ||\r
+ entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE)\r
+ )\r
+ return UnZip::HeaderConsistencyError;\r
+ }\r
+\r
+ return UnZip::Ok;\r
+}\r
+\r
+/*! \internal Attempts to find the start of the central directory record.\r
+\r
+ We seek the file back until we reach the "End Of Central Directory"\r
+ signature PK\5\6.\r
+\r
+ end of central dir signature 4 bytes (0x06054b50)\r
+ number of this disk 2 bytes\r
+ number of the disk with the\r
+ start of the central directory 2 bytes\r
+ total number of entries in the\r
+ central directory on this disk 2 bytes\r
+ total number of entries in\r
+ the central directory 2 bytes\r
+ size of the central directory 4 bytes\r
+ offset of start of central\r
+ directory with respect to\r
+ the starting disk number 4 bytes\r
+ .ZIP file comment length 2 bytes\r
+ --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE ---\r
+ .ZIP file comment (variable size)\r
+*/\r
+UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory()\r
+{\r
+ qint64 length = device->size();\r
+ qint64 offset = length - UNZIP_EOCD_SIZE;\r
+\r
+ if (length < UNZIP_EOCD_SIZE)\r
+ return UnZip::InvalidArchive;\r
+\r
+ if (!device->seek( offset ))\r
+ return UnZip::SeekFailed;\r
+\r
+ if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)\r
+ return UnZip::ReadFailed;\r
+\r
+ bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06);\r
+\r
+ if (eocdFound)\r
+ {\r
+ // Zip file has no comment (the only variable length field in the EOCD record)\r
+ eocdOffset = offset;\r
+ }\r
+ else\r
+ {\r
+ qint64 read;\r
+ char* p = 0;\r
+\r
+ offset -= UNZIP_EOCD_SIZE;\r
+\r
+ if (offset <= 0)\r
+ return UnZip::InvalidArchive;\r
+\r
+ if (!device->seek( offset ))\r
+ return UnZip::SeekFailed;\r
+\r
+ while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0)\r
+ {\r
+ if ( (p = strstr(buffer1, "PK\5\6")) != 0)\r
+ {\r
+ // Seek to the start of the EOCD record so we can read it fully\r
+ // Yes... we could simply read the missing bytes and append them to the buffer\r
+ // but this is far easier so heck it!\r
+ device->seek( offset + (p - buffer1) );\r
+ eocdFound = true;\r
+ eocdOffset = offset + (p - buffer1);\r
+\r
+ // Read EOCD record\r
+ if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)\r
+ return UnZip::ReadFailed;\r
+\r
+ break;\r
+ }\r
+\r
+ // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here.\r
+ offset -= 1 /*UNZIP_EOCD_SIZE*/;\r
+ if (offset <= 0)\r
+ return UnZip::InvalidArchive;\r
+\r
+ if (!device->seek( offset ))\r
+ return UnZip::SeekFailed;\r
+ }\r
+ }\r
+\r
+ if (!eocdFound)\r
+ return UnZip::InvalidArchive;\r
+\r
+ // Parse EOCD to locate CD offset\r
+ offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4);\r
+\r
+ cdOffset = offset;\r
+\r
+ cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4);\r
+\r
+ quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4);\r
+ if (commentLength != 0)\r
+ {\r
+ QByteArray c = device->read(commentLength);\r
+ if (c.count() != commentLength)\r
+ return UnZip::ReadFailed;\r
+\r
+ comment = c;\r
+ }\r
+\r
+ // Seek to the start of the CD record\r
+ if (!device->seek( cdOffset ))\r
+ return UnZip::SeekFailed;\r
+\r
+ return UnZip::Ok;\r
+}\r
+\r
+/*!\r
+ \internal Parses a central directory record.\r
+\r
+ Central Directory record structure:\r
+\r
+ [file header 1]\r
+ .\r
+ .\r
+ .\r
+ [file header n]\r
+ [digital signature] // PKZip 6.2 or later only\r
+\r
+ File header:\r
+\r
+ central file header signature 4 bytes (0x02014b50)\r
+ version made by 2 bytes\r
+ version needed to extract 2 bytes\r
+ general purpose bit flag 2 bytes\r
+ compression method 2 bytes\r
+ last mod file time 2 bytes\r
+ last mod file date 2 bytes\r
+ crc-32 4 bytes\r
+ compressed size 4 bytes\r
+ uncompressed size 4 bytes\r
+ file name length 2 bytes\r
+ extra field length 2 bytes\r
+ file comment length 2 bytes\r
+ disk number start 2 bytes\r
+ internal file attributes 2 bytes\r
+ external file attributes 4 bytes\r
+ relative offset of local header 4 bytes\r
+\r
+ file name (variable size)\r
+ extra field (variable size)\r
+ file comment (variable size)\r
+*/\r
+UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord()\r
+{\r
+ // Read CD record\r
+ if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS)\r
+ return UnZip::ReadFailed;\r
+\r
+ bool skipEntry = false;\r
+\r
+ // Get compression type so we can skip non compatible algorithms\r
+ quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD);\r
+\r
+ // Get variable size fields length so we can skip the whole record\r
+ // if necessary\r
+ quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN);\r
+ quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN);\r
+ quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN);\r
+\r
+ quint32 skipLength = szName + szExtra + szComment;\r
+\r
+ UnZip::ErrorCode ec = UnZip::Ok;\r
+\r
+ if ((compMethod != 0) && (compMethod != 8))\r
+ {\r
+ qDebug() << "Unsupported compression method. Skipping file.";\r
+ skipEntry = true;\r
+ }\r
+\r
+ // Header parsing may be a problem if version is bigger than UNZIP_VERSION\r
+ if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION)\r
+ {\r
+ qDebug() << "Unsupported PKZip version. Skipping file.";\r
+ skipEntry = true;\r
+ }\r
+\r
+ if (!skipEntry && szName == 0)\r
+ {\r
+ qDebug() << "Skipping file with no name.";\r
+ skipEntry = true;\r
+ }\r
+\r
+ if (!skipEntry && device->read(buffer2, szName) != szName)\r
+ {\r
+ ec = UnZip::ReadFailed;\r
+ skipEntry = true;\r
+ }\r
+\r
+ if (skipEntry)\r
+ {\r
+ if (ec == UnZip::Ok)\r
+ {\r
+ if (!device->seek( device->pos() + skipLength ))\r
+ ec = UnZip::SeekFailed;\r
+\r
+ unsupportedEntryCount++;\r
+ }\r
+\r
+ return ec;\r
+ }\r
+\r
+ QString filename = QString::fromAscii(buffer2, szName);\r
+\r
+ ZipEntryP* h = new ZipEntryP;\r
+ h->compMethod = compMethod;\r
+\r
+ h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG];\r
+ h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1];\r
+\r
+ h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT];\r
+ h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1];\r
+\r
+ h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD];\r
+ h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1];\r
+\r
+ h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32);\r
+ h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE);\r
+ h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE);\r
+\r
+ // Skip extra field (if any)\r
+ if (szExtra != 0)\r
+ {\r
+ if (!device->seek( device->pos() + szExtra ))\r
+ {\r
+ delete h;\r
+ return UnZip::SeekFailed;\r
+ }\r
+ }\r
+\r
+ // Read comment field (if any)\r
+ if (szComment != 0)\r
+ {\r
+ if (device->read(buffer2, szComment) != szComment)\r
+ {\r
+ delete h;\r
+ return UnZip::ReadFailed;\r
+ }\r
+\r
+ h->comment = QString::fromAscii(buffer2, szComment);\r
+ }\r
+\r
+ h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET);\r
+\r
+ if (headers == 0)\r
+ headers = new QMap<QString, ZipEntryP*>();\r
+ headers->insert(filename, h);\r
+\r
+ return UnZip::Ok;\r
+}\r
+\r
+//! \internal Closes the archive and resets the internal status.\r
+void UnzipPrivate::closeArchive()\r
+{\r
+ if (device == 0)\r
+ return;\r
+\r
+ skipAllEncrypted = false;\r
+\r
+ if (headers != 0)\r
+ {\r
+ qDeleteAll(*headers);\r
+ delete headers;\r
+ headers = 0;\r
+ }\r
+\r
+ delete device; device = 0;\r
+\r
+ cdOffset = eocdOffset = 0;\r
+ cdEntryCount = 0;\r
+ unsupportedEntryCount = 0;\r
+\r
+ comment.clear();\r
+}\r
+\r
+//! \internal\r
+UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options)\r
+{\r
+ QString name(path);\r
+ QString dirname;\r
+ QString directory;\r
+\r
+ int pos = name.lastIndexOf('/');\r
+\r
+ // This entry is for a directory\r
+ if (pos == name.length() - 1)\r
+ {\r
+ if (options.testFlag(UnZip::SkipPaths))\r
+ return UnZip::Ok;\r
+\r
+ directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name));\r
+ if (!createDirectory(directory))\r
+ {\r
+ qDebug() << QString("Unable to create directory: %1").arg(directory);\r
+ return UnZip::CreateDirFailed;\r
+ }\r
+\r
+ return UnZip::Ok;\r
+ }\r
+\r
+ // Extract path from entry\r
+ if (pos > 0)\r
+ {\r
+ // get directory part\r
+ dirname = name.left(pos);\r
+ if (options.testFlag(UnZip::SkipPaths))\r
+ {\r
+ directory = dir.absolutePath();\r
+ }\r
+ else\r
+ {\r
+ directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname));\r
+ if (!createDirectory(directory))\r
+ {\r
+ qDebug() << QString("Unable to create directory: %1").arg(directory);\r
+ return UnZip::CreateDirFailed;\r
+ }\r
+ }\r
+ name = name.right(name.length() - pos - 1);\r
+ } else directory = dir.absolutePath();\r
+\r
+ name = QString("%1/%2").arg(directory).arg(name);\r
+\r
+ QFile outFile(name);\r
+\r
+ if (!outFile.open(QIODevice::WriteOnly))\r
+ {\r
+ qDebug() << QString("Unable to open %1 for writing").arg(name);\r
+ return UnZip::OpenFailed;\r
+ }\r
+\r
+ //! \todo Set creation/last_modified date/time\r
+\r
+ UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options);\r
+\r
+ outFile.close();\r
+\r
+ if (ec != UnZip::Ok)\r
+ {\r
+ if (!outFile.remove())\r
+ qDebug() << QString("Unable to remove corrupted file: %1").arg(name);\r
+ }\r
+\r
+ return ec;\r
+}\r
+\r
+//! \internal\r
+UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options)\r
+{\r
+ Q_UNUSED(options);\r
+ Q_ASSERT(dev != 0);\r
+\r
+ if (!entry.lhEntryChecked)\r
+ {\r
+ UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry);\r
+ entry.lhEntryChecked = true;\r
+\r
+ if (ec != UnZip::Ok)\r
+ return ec;\r
+ }\r
+\r
+ if (!device->seek(entry.dataOffset))\r
+ return UnZip::SeekFailed;\r
+\r
+ // Encryption keys\r
+ quint32 keys[3];\r
+\r
+ if (entry.isEncrypted())\r
+ {\r
+ UnZip::ErrorCode e = testPassword(keys, path, entry);\r
+ if (e != UnZip::Ok)\r
+ {\r
+ qDebug() << QString("Unable to decrypt %1").arg(path);\r
+ return e;\r
+ }//! Encryption header size\r
+ entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size\r
+ }\r
+\r
+ if (entry.szComp == 0)\r
+ {\r
+ if (entry.crc != 0)\r
+ return UnZip::Corrupted;\r
+\r
+ return UnZip::Ok;\r
+ }\r
+\r
+ uInt rep = entry.szComp / UNZIP_READ_BUFFER;\r
+ uInt rem = entry.szComp % UNZIP_READ_BUFFER;\r
+ uInt cur = 0;\r
+\r
+ // extract data\r
+ qint64 read;\r
+ quint64 tot = 0;\r
+\r
+ quint32 myCRC = crc32(0L, Z_NULL, 0);\r
+\r
+ if (entry.compMethod == 0)\r
+ {\r
+ while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 )\r
+ {\r
+ if (entry.isEncrypted())\r
+ decryptBytes(keys, buffer1, read);\r
+\r
+ myCRC = crc32(myCRC, uBuffer, read);\r
+\r
+ if (dev->write(buffer1, read) != read)\r
+ return UnZip::WriteFailed;\r
+\r
+ cur++;\r
+ tot += read;\r
+\r
+ if (tot == entry.szComp)\r
+ break;\r
+ }\r
+\r
+ if (read < 0)\r
+ return UnZip::ReadFailed;\r
+ }\r
+ else if (entry.compMethod == 8)\r
+ {\r
+ /* Allocate inflate state */\r
+ z_stream zstr;\r
+ zstr.zalloc = Z_NULL;\r
+ zstr.zfree = Z_NULL;\r
+ zstr.opaque = Z_NULL;\r
+ zstr.next_in = Z_NULL;\r
+ zstr.avail_in = 0;\r
+\r
+ int zret;\r
+\r
+ // Use inflateInit2 with negative windowBits to get raw decompression\r
+ if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK )\r
+ return UnZip::ZlibError;\r
+\r
+ int szDecomp;\r
+\r
+ // Decompress until deflate stream ends or end of file\r
+ do\r
+ {\r
+ read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem);\r
+ if (read == 0)\r
+ break;\r
+ if (read < 0)\r
+ {\r
+ (void)inflateEnd(&zstr);\r
+ return UnZip::ReadFailed;\r
+ }\r
+\r
+ if (entry.isEncrypted())\r
+ decryptBytes(keys, buffer1, read);\r
+\r
+ cur++;\r
+ tot += read;\r
+\r
+ zstr.avail_in = (uInt) read;\r
+ zstr.next_in = (Bytef*) buffer1;\r
+\r
+\r
+ // Run inflate() on input until output buffer not full\r
+ do {\r
+ zstr.avail_out = UNZIP_READ_BUFFER;\r
+ zstr.next_out = (Bytef*) buffer2;;\r
+\r
+ zret = inflate(&zstr, Z_NO_FLUSH);\r
+\r
+ switch (zret) {\r
+ case Z_NEED_DICT:\r
+ case Z_DATA_ERROR:\r
+ case Z_MEM_ERROR:\r
+ inflateEnd(&zstr);\r
+ return UnZip::WriteFailed;\r
+ default:\r
+ ;\r
+ }\r
+\r
+ szDecomp = UNZIP_READ_BUFFER - zstr.avail_out;\r
+ if (dev->write(buffer2, szDecomp) != szDecomp)\r
+ {\r
+ inflateEnd(&zstr);\r
+ return UnZip::ZlibError;\r
+ }\r
+\r
+ myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp);\r
+\r
+ } while (zstr.avail_out == 0);\r
+\r
+ }\r
+ while (zret != Z_STREAM_END);\r
+\r
+ inflateEnd(&zstr);\r
+ }\r
+\r
+ if (myCRC != entry.crc)\r
+ return UnZip::Corrupted;\r
+\r
+ return UnZip::Ok;\r
+}\r
+\r
+//! \internal Creates a new directory and all the needed parent directories.\r
+bool UnzipPrivate::createDirectory(const QString& path)\r
+{\r
+ QDir d(path);\r
+ if (!d.exists())\r
+ {\r
+ int sep = path.lastIndexOf("/");\r
+ if (sep <= 0) return true;\r
+\r
+ if (!createDirectory(path.left(sep)))\r
+ return false;\r
+\r
+ if (!d.mkdir(path))\r
+ {\r
+ qDebug() << QString("Unable to create directory: %1").arg(path);\r
+ return false;\r
+ }\r
+ }\r
+\r
+ return true;\r
+}\r
+\r
+/*!\r
+ \internal Reads an quint32 (4 bytes) from a byte array starting at given offset.\r
+*/\r
+quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const\r
+{\r
+ quint32 res = (quint32) data[offset];\r
+ res |= (((quint32)data[offset+1]) << 8);\r
+ res |= (((quint32)data[offset+2]) << 16);\r
+ res |= (((quint32)data[offset+3]) << 24);\r
+\r
+ return res;\r
+}\r
+\r
+/*!\r
+ \internal Reads an quint64 (8 bytes) from a byte array starting at given offset.\r
+*/\r
+quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const\r
+{\r
+ quint64 res = (quint64) data[offset];\r
+ res |= (((quint64)data[offset+1]) << 8);\r
+ res |= (((quint64)data[offset+2]) << 16);\r
+ res |= (((quint64)data[offset+3]) << 24);\r
+ res |= (((quint64)data[offset+1]) << 32);\r
+ res |= (((quint64)data[offset+2]) << 40);\r
+ res |= (((quint64)data[offset+3]) << 48);\r
+ res |= (((quint64)data[offset+3]) << 56);\r
+\r
+ return res;\r
+}\r
+\r
+/*!\r
+ \internal Reads an quint16 (2 bytes) from a byte array starting at given offset.\r
+*/\r
+quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const\r
+{\r
+ return (quint16) data[offset] | (((quint16)data[offset+1]) << 8);\r
+}\r
+\r
+/*!\r
+ \internal Return the next byte in the pseudo-random sequence\r
+ */\r
+int UnzipPrivate::decryptByte(quint32 key2) const\r
+{\r
+ quint16 temp = ((quint16)(key2) & 0xffff) | 2;\r
+ return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);\r
+}\r
+\r
+/*!\r
+ \internal Update the encryption keys with the next byte of plain text\r
+ */\r
+void UnzipPrivate::updateKeys(quint32* keys, int c) const\r
+{\r
+ keys[0] = CRC32(keys[0], c);\r
+ keys[1] += keys[0] & 0xff;\r
+ keys[1] = keys[1] * 134775813L + 1;\r
+ keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);\r
+}\r
+\r
+/*!\r
+ \internal Initialize the encryption keys and the random header according to\r
+ the given password.\r
+ */\r
+void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const\r
+{\r
+ keys[0] = 305419896L;\r
+ keys[1] = 591751049L;\r
+ keys[2] = 878082192L;\r
+\r
+ QByteArray pwdBytes = pwd.toAscii();\r
+ int sz = pwdBytes.size();\r
+ const char* ascii = pwdBytes.data();\r
+\r
+ for (int i=0; i<sz; ++i)\r
+ updateKeys(keys, (int)ascii[i]);\r
+}\r
+\r
+/*!\r
+ \internal Attempts to test a password without actually extracting a file.\r
+ The \p file parameter can be used in the user interface or for debugging purposes\r
+ as it is the name of the encrypted file for wich the password is being tested.\r
+*/\r
+UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header)\r
+{\r
+ Q_UNUSED(file);\r
+\r
+ // read encryption keys\r
+ if (device->read(buffer1, 12) != 12)\r
+ return UnZip::Corrupted;\r
+\r
+ // Replace this code if you want to i.e. call some dialog and ask the user for a password\r
+ initKeys(password, keys);\r
+ if (testKeys(header, keys))\r
+ return UnZip::Ok;\r
+\r
+ return UnZip::Skip;\r
+}\r
+\r
+/*!\r
+ \internal Tests a set of keys on the encryption header.\r
+*/\r
+bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys)\r
+{\r
+ char lastByte;\r
+\r
+ // decrypt encryption header\r
+ for (int i=0; i<11; ++i)\r
+ updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2]));\r
+ updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2]));\r
+\r
+ // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time\r
+ // with no extended header we have to check the crc high-order byte\r
+ char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24;\r
+\r
+ return (lastByte == c);\r
+}\r
+\r
+/*!\r
+ \internal Decrypts an array of bytes long \p read.\r
+*/\r
+void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read)\r
+{\r
+ for (int i=0; i<(int)read; ++i)\r
+ updateKeys(keys, buffer[i] ^= decryptByte(keys[2]));\r
+}\r
+\r
+/*!\r
+ \internal Converts date and time values from ZIP format to a QDateTime object.\r
+*/\r
+QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const\r
+{\r
+ QDateTime dt;\r
+\r
+ // Usual PKZip low-byte to high-byte order\r
+\r
+ // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day\r
+ quint16 year = (date[1] >> 1) & 127;\r
+ quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7);\r
+ quint16 day = date[0] & 31;\r
+\r
+ // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision\r
+ quint16 hour = (time[1] >> 3) & 31;\r
+ quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7);\r
+ quint16 seconds = (time[0] & 31) * 2;\r
+\r
+ dt.setDate(QDate(1980 + year, month, day));\r
+ dt.setTime(QTime(hour, minutes, seconds));\r
+ return dt;\r
+}\r