From 9c54187678281077b8700c1c107f54a0b40d7050 Mon Sep 17 00:00:00 2001 From: Nicolas Pennequin Date: Sun, 11 May 2008 17:21:14 +0000 Subject: Set svn:eol-style on files from the rbutil directory and its subdirectories. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17462 a1c6a512-1295-4272-9138-f99709370657 --- rbutil/rbutilqt/zip/unzip.cpp | 2720 +++++++++++++++++++------------------- rbutil/rbutilqt/zip/unzip.h | 288 ++-- rbutil/rbutilqt/zip/unzip_p.h | 224 ++-- rbutil/rbutilqt/zip/zip.cpp | 2442 +++++++++++++++++----------------- rbutil/rbutilqt/zip/zip.h | 230 ++-- rbutil/rbutilqt/zip/zip_p.h | 186 +-- rbutil/rbutilqt/zip/zipentry_p.h | 156 +-- 7 files changed, 3123 insertions(+), 3123 deletions(-) (limited to 'rbutil/rbutilqt/zip') diff --git a/rbutil/rbutilqt/zip/unzip.cpp b/rbutil/rbutilqt/zip/unzip.cpp index 3cc385ab36..d49529bad9 100644 --- a/rbutil/rbutilqt/zip/unzip.cpp +++ b/rbutil/rbutilqt/zip/unzip.cpp @@ -1,1360 +1,1360 @@ -/**************************************************************************** -** Filename: unzip.cpp -** Last updated [dd/mm/yyyy]: 28/01/2007 -** -** pkzip 2.0 decompression. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -#include "unzip.h" -#include "unzip_p.h" -#include "zipentry_p.h" - -#include -#include -#include -#include -#include - -// You can remove this #include if you replace the qDebug() statements. -#include - -/*! - \class UnZip unzip.h - - \brief PKZip 2.0 file decompression. - Compatibility with later versions is not ensured as they may use - unsupported compression algorithms. - Versions after 2.7 may have an incompatible header format and thus be - completely incompatible. -*/ - -/*! \enum UnZip::ErrorCode The result of a decompression operation. - \value UnZip::Ok No error occurred. - \value UnZip::ZlibInit Failed to init or load the zlib library. - \value UnZip::ZlibError The zlib library returned some error. - \value UnZip::OpenFailed Unable to create or open a device. - \value UnZip::PartiallyCorrupted Corrupted zip archive - some files could be extracted. - \value UnZip::Corrupted Corrupted or invalid zip archive. - \value UnZip::WrongPassword Unable to decrypt a password protected file. - \value UnZip::NoOpenArchive No archive has been opened yet. - \value UnZip::FileNotFound Unable to find the requested file in the archive. - \value UnZip::ReadFailed Reading of a file failed. - \value UnZip::WriteFailed Writing of a file failed. - \value UnZip::SeekFailed Seek failed. - \value UnZip::CreateDirFailed Could not create a directory. - \value UnZip::InvalidDevice A null device has been passed as parameter. - \value UnZip::InvalidArchive This is not a valid (or supported) ZIP archive. - \value UnZip::HeaderConsistencyError Local header record info does not match with the central directory record info. The archive may be corrupted. - - \value UnZip::Skip Internal use only. - \value UnZip::SkipAll Internal use only. -*/ - -/*! \enum UnZip::ExtractionOptions Some options for the file extraction methods. - \value UnZip::ExtractPaths Default. Does not ignore the path of the zipped files. - \value UnZip::SkipPaths Default. Ignores the path of the zipped files and extracts them all to the same root directory. -*/ - -//! Local header size (excluding signature, excluding variable length fields) -#define UNZIP_LOCAL_HEADER_SIZE 26 -//! Central Directory file entry size (excluding signature, excluding variable length fields) -#define UNZIP_CD_ENTRY_SIZE_NS 42 -//! Data descriptor size (excluding signature) -#define UNZIP_DD_SIZE 12 -//! End Of Central Directory size (including signature, excluding variable length fields) -#define UNZIP_EOCD_SIZE 22 -//! Local header entry encryption header size -#define UNZIP_LOCAL_ENC_HEADER_SIZE 12 - -// Some offsets inside a CD record (excluding signature) -#define UNZIP_CD_OFF_VERSION 0 -#define UNZIP_CD_OFF_GPFLAG 4 -#define UNZIP_CD_OFF_CMETHOD 6 -#define UNZIP_CD_OFF_MODT 8 -#define UNZIP_CD_OFF_MODD 10 -#define UNZIP_CD_OFF_CRC32 12 -#define UNZIP_CD_OFF_CSIZE 16 -#define UNZIP_CD_OFF_USIZE 20 -#define UNZIP_CD_OFF_NAMELEN 24 -#define UNZIP_CD_OFF_XLEN 26 -#define UNZIP_CD_OFF_COMMLEN 28 -#define UNZIP_CD_OFF_LHOFFSET 38 - -// Some offsets inside a local header record (excluding signature) -#define UNZIP_LH_OFF_VERSION 0 -#define UNZIP_LH_OFF_GPFLAG 2 -#define UNZIP_LH_OFF_CMETHOD 4 -#define UNZIP_LH_OFF_MODT 6 -#define UNZIP_LH_OFF_MODD 8 -#define UNZIP_LH_OFF_CRC32 10 -#define UNZIP_LH_OFF_CSIZE 14 -#define UNZIP_LH_OFF_USIZE 18 -#define UNZIP_LH_OFF_NAMELEN 22 -#define UNZIP_LH_OFF_XLEN 24 - -// Some offsets inside a data descriptor record (excluding signature) -#define UNZIP_DD_OFF_CRC32 0 -#define UNZIP_DD_OFF_CSIZE 4 -#define UNZIP_DD_OFF_USIZE 8 - -// Some offsets inside a EOCD record -#define UNZIP_EOCD_OFF_ENTRIES 6 -#define UNZIP_EOCD_OFF_CDOFF 12 -#define UNZIP_EOCD_OFF_COMMLEN 16 - -/*! - Max version handled by this API. - 0x1B = 2.7 --> full compatibility only up to version 2.0 (0x14) - versions from 2.1 to 2.7 may use unsupported compression methods - versions after 2.7 may have an incompatible header format -*/ -#define UNZIP_VERSION 0x1B -//! Full compatibility granted until this version -#define UNZIP_VERSION_STRICT 0x14 - -//! CRC32 routine -#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) - -//! Checks if some file has been already extracted. -#define UNZIP_CHECK_FOR_VALID_DATA \ - {\ - if (headers != 0)\ - {\ - qDebug() << "Corrupted zip archive. Some files might be extracted.";\ - ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\ - break;\ - }\ - else\ - {\ - delete device;\ - device = 0;\ - qDebug() << "Corrupted or invalid zip archive";\ - ec = UnZip::Corrupted;\ - break;\ - }\ - } - - -/************************************************************************ - Public interface -*************************************************************************/ - -/*! - Creates a new Zip file decompressor. -*/ -UnZip::UnZip() -{ - d = new UnzipPrivate; -} - -/*! - Closes any open archive and releases used resources. -*/ -UnZip::~UnZip() -{ - closeArchive(); - delete d; -} - -/*! - Returns true if there is an open archive. -*/ -bool UnZip::isOpen() const -{ - return d->device != 0; -} - -/*! - Opens a zip archive and reads the files list. Closes any previously opened archive. -*/ -UnZip::ErrorCode UnZip::openArchive(const QString& filename) -{ - QFile* file = new QFile(filename); - - if (!file->exists()) { - delete file; - return UnZip::FileNotFound; - } - - if (!file->open(QIODevice::ReadOnly)) { - delete file; - return UnZip::OpenFailed; - } - - return openArchive(file); -} - -/*! - Opens a zip archive and reads the entries list. - Closes any previously opened archive. - \warning The class takes ownership of the device so don't delete it! -*/ -UnZip::ErrorCode UnZip::openArchive(QIODevice* device) -{ - if (device == 0) - { - qDebug() << "Invalid device."; - return UnZip::InvalidDevice; - } - - return d->openArchive(device); -} - -/*! - Closes the archive and releases all the used resources (like cached passwords). -*/ -void UnZip::closeArchive() -{ - d->closeArchive(); -} - -QString UnZip::archiveComment() const -{ - if (d->device == 0) - return QString(); - return d->comment; -} - -/*! - Returns a locale translated error string for a given error code. -*/ -QString UnZip::formatError(UnZip::ErrorCode c) const -{ - switch (c) - { - case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break; - case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break; - case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break; - case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break; - case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break; - case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break; - case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break; - case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break; - case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break; - case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break; - case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break; - case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break; - case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break; - case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break; - case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break; - case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break; - default: ; - } - - return QCoreApplication::translate("UnZip", "Unknown error."); -} - -/*! - Returns true if the archive contains a file with the given path and name. -*/ -bool UnZip::contains(const QString& file) const -{ - if (d->headers == 0) - return false; - - return d->headers->contains(file); -} - -/*! - Returns complete paths of files and directories in this archive. -*/ -QStringList UnZip::fileList() const -{ - return d->headers == 0 ? QStringList() : d->headers->keys(); -} - -/*! - Returns information for each (correctly parsed) entry of this archive. -*/ -QList UnZip::entryList() const -{ - QList list; - - if (d->headers != 0) - { - for (QMap::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it) - { - const ZipEntryP* entry = it.value(); - Q_ASSERT(entry != 0); - - ZipEntry z; - - z.filename = it.key(); - if (!entry->comment.isEmpty()) - z.comment = entry->comment; - z.compressedSize = entry->szComp; - z.uncompressedSize = entry->szUncomp; - z.crc32 = entry->crc; - z.lastModified = d->convertDateTime(entry->modDate, entry->modTime); - - z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression; - z.type = z.filename.endsWith("/") ? Directory : File; - - z.encrypted = entry->isEncrypted(); - - list.append(z); - } - } - - return list; -} - -/*! - Extracts the whole archive to a directory. -*/ -UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options) -{ - return extractAll(QDir(dirname), options); -} - -/*! - Extracts the whole archive to a directory. -*/ -UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) -{ - // this should only happen if we didn't call openArchive() yet - if (d->device == 0) - return NoOpenArchive; - - if (d->headers == 0) - return Ok; - - bool end = false; - for (QMap::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr) - { - ZipEntryP* entry = itr.value(); - Q_ASSERT(entry != 0); - - if ((entry->isEncrypted()) && d->skipAllEncrypted) - continue; - - switch (d->extractFile(itr.key(), *entry, dir, options)) - { - case Corrupted: - qDebug() << "Removing corrupted entry" << itr.key(); - d->headers->erase(itr++); - if (itr == d->headers->end()) - end = true; - break; - case CreateDirFailed: - break; - case Skip: - break; - case SkipAll: - d->skipAllEncrypted = true; - break; - default: - ; - } - - if (end) - break; - } - - return Ok; -} - -/*! - Extracts a single file to a directory. -*/ -UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options) -{ - return extractFile(filename, QDir(dirname), options); -} - -/*! - Extracts a single file to a directory. -*/ -UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options) -{ - QMap::Iterator itr = d->headers->find(filename); - if (itr != d->headers->end()) - { - ZipEntryP* entry = itr.value(); - Q_ASSERT(entry != 0); - return d->extractFile(itr.key(), *entry, dir, options); - } - - return FileNotFound; -} - -/*! - Extracts a single file to a directory. -*/ -UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options) -{ - if (dev == 0) - return InvalidDevice; - - QMap::Iterator itr = d->headers->find(filename); - if (itr != d->headers->end()) { - ZipEntryP* entry = itr.value(); - Q_ASSERT(entry != 0); - return d->extractFile(itr.key(), *entry, dev, options); - } - - return FileNotFound; -} - -/*! - Extracts a list of files. - Stops extraction at the first error (but continues if a file does not exist in the archive). - */ -UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options) -{ - QDir dir(dirname); - ErrorCode ec; - - for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) - { - ec = extractFile(*itr, dir, options); - if (ec == FileNotFound) - continue; - if (ec != Ok) - return ec; - } - - return Ok; -} - -/*! - Extracts a list of files. - Stops extraction at the first error (but continues if a file does not exist in the archive). - */ -UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options) -{ - ErrorCode ec; - - for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) - { - ec = extractFile(*itr, dir, options); - if (ec == FileNotFound) - continue; - if (ec != Ok) - return ec; - } - - return Ok; -} - -/*! - Remove/replace this method to add your own password retrieval routine. -*/ -void UnZip::setPassword(const QString& pwd) -{ - d->password = pwd; -} - -/*! - ZipEntry constructor - initialize data. Type is set to File. -*/ -UnZip::ZipEntry::ZipEntry() -{ - compressedSize = uncompressedSize = crc32 = 0; - compression = NoCompression; - type = File; - encrypted = false; -} - - -/************************************************************************ - Private interface -*************************************************************************/ - -//! \internal -UnzipPrivate::UnzipPrivate() -{ - skipAllEncrypted = false; - headers = 0; - device = 0; - - uBuffer = (unsigned char*) buffer1; - crcTable = (quint32*) get_crc_table(); - - cdOffset = eocdOffset = 0; - cdEntryCount = 0; - unsupportedEntryCount = 0; -} - -//! \internal Parses a Zip archive. -UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) -{ - Q_ASSERT(dev != 0); - - if (device != 0) - closeArchive(); - - device = dev; - - if (!(device->isOpen() || device->open(QIODevice::ReadOnly))) - { - delete device; - device = 0; - - qDebug() << "Unable to open device for reading"; - return UnZip::OpenFailed; - } - - UnZip::ErrorCode ec; - - ec = seekToCentralDirectory(); - if (ec != UnZip::Ok) - { - closeArchive(); - return ec; - } - - //! \todo Ignore CD entry count? CD may be corrupted. - if (cdEntryCount == 0) - { - return UnZip::Ok; - } - - bool continueParsing = true; - - while (continueParsing) - { - if (device->read(buffer1, 4) != 4) - UNZIP_CHECK_FOR_VALID_DATA - - if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) ) - break; - - if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok ) - break; - } - - if (ec != UnZip::Ok) - closeArchive(); - - return ec; -} - -/* - \internal Parses a local header record and makes some consistency check - with the information stored in the Central Directory record for this entry - that has been previously parsed. - \todo Optional consistency check (as a ExtractionOptions flag) - - local file header signature 4 bytes (0x04034b50) - version needed to extract 2 bytes - general purpose bit flag 2 bytes - compression method 2 bytes - last mod file time 2 bytes - last mod file date 2 bytes - crc-32 4 bytes - compressed size 4 bytes - uncompressed size 4 bytes - file name length 2 bytes - extra field length 2 bytes - - file name (variable size) - extra field (variable size) -*/ -UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry) -{ - if (!device->seek(entry.lhOffset)) - return UnZip::SeekFailed; - - // Test signature - if (device->read(buffer1, 4) != 4) - return UnZip::ReadFailed; - - if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04)) - return UnZip::InvalidArchive; - - if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE) - return UnZip::ReadFailed; - - /* - Check 3rd general purpose bit flag. - - "bit 3: If this bit is set, the fields crc-32, compressed size - and uncompressed size are set to zero in the local - header. The correct values are put in the data descriptor - immediately following the compressed data." - */ - bool hasDataDescriptor = entry.hasDataDescriptor(); - - bool checkFailed = false; - - if (!checkFailed) - checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD); - if (!checkFailed) - checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG]; - if (!checkFailed) - checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1]; - if (!checkFailed) - checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT]; - if (!checkFailed) - checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1]; - if (!checkFailed) - checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD]; - if (!checkFailed) - checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1]; - if (!hasDataDescriptor) - { - if (!checkFailed) - checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32); - if (!checkFailed) - checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE); - if (!checkFailed) - checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE); - } - - if (checkFailed) - return UnZip::HeaderConsistencyError; - - // Check filename - quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN); - if (szName == 0) - return UnZip::HeaderConsistencyError; - - if (device->read(buffer2, szName) != szName) - return UnZip::ReadFailed; - - QString filename = QString::fromAscii(buffer2, szName); - if (filename != path) - { - qDebug() << "Filename in local header mismatches."; - return UnZip::HeaderConsistencyError; - } - - // Skip extra field - quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN); - if (szExtra != 0) - { - if (!device->seek(device->pos() + szExtra)) - return UnZip::SeekFailed; - } - - entry.dataOffset = device->pos(); - - if (hasDataDescriptor) - { - /* - The data descriptor has this OPTIONAL signature: PK\7\8 - We try to skip the compressed data relying on the size set in the - Central Directory record. - */ - if (!device->seek(device->pos() + entry.szComp)) - return UnZip::SeekFailed; - - // Read 4 bytes and check if there is a data descriptor signature - if (device->read(buffer2, 4) != 4) - return UnZip::ReadFailed; - - bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08; - if (hasSignature) - { - if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE) - return UnZip::ReadFailed; - } - else - { - if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4) - return UnZip::ReadFailed; - } - - // DD: crc, compressed size, uncompressed size - if ( - entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) || - entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) || - entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE) - ) - return UnZip::HeaderConsistencyError; - } - - return UnZip::Ok; -} - -/*! \internal Attempts to find the start of the central directory record. - - We seek the file back until we reach the "End Of Central Directory" - signature PK\5\6. - - end of central dir signature 4 bytes (0x06054b50) - number of this disk 2 bytes - number of the disk with the - start of the central directory 2 bytes - total number of entries in the - central directory on this disk 2 bytes - total number of entries in - the central directory 2 bytes - size of the central directory 4 bytes - offset of start of central - directory with respect to - the starting disk number 4 bytes - .ZIP file comment length 2 bytes - --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE --- - .ZIP file comment (variable size) -*/ -UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() -{ - qint64 length = device->size(); - qint64 offset = length - UNZIP_EOCD_SIZE; - - if (length < UNZIP_EOCD_SIZE) - return UnZip::InvalidArchive; - - if (!device->seek( offset )) - return UnZip::SeekFailed; - - if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) - return UnZip::ReadFailed; - - bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06); - - if (eocdFound) - { - // Zip file has no comment (the only variable length field in the EOCD record) - eocdOffset = offset; - } - else - { - qint64 read; - char* p = 0; - - offset -= UNZIP_EOCD_SIZE; - - if (offset <= 0) - return UnZip::InvalidArchive; - - if (!device->seek( offset )) - return UnZip::SeekFailed; - - while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0) - { - if ( (p = strstr(buffer1, "PK\5\6")) != 0) - { - // Seek to the start of the EOCD record so we can read it fully - // Yes... we could simply read the missing bytes and append them to the buffer - // but this is far easier so heck it! - device->seek( offset + (p - buffer1) ); - eocdFound = true; - eocdOffset = offset + (p - buffer1); - - // Read EOCD record - if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) - return UnZip::ReadFailed; - - break; - } - - offset -= UNZIP_EOCD_SIZE; - if (offset <= 0) - return UnZip::InvalidArchive; - - if (!device->seek( offset )) - return UnZip::SeekFailed; - } - } - - if (!eocdFound) - return UnZip::InvalidArchive; - - // Parse EOCD to locate CD offset - offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4); - - cdOffset = offset; - - cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4); - - quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4); - if (commentLength != 0) - { - QByteArray c = device->read(commentLength); - if (c.count() != commentLength) - return UnZip::ReadFailed; - - comment = c; - } - - // Seek to the start of the CD record - if (!device->seek( cdOffset )) - return UnZip::SeekFailed; - - return UnZip::Ok; -} - -/*! - \internal Parses a central directory record. - - Central Directory record structure: - - [file header 1] - . - . - . - [file header n] - [digital signature] // PKZip 6.2 or later only - - File header: - - central file header signature 4 bytes (0x02014b50) - version made by 2 bytes - version needed to extract 2 bytes - general purpose bit flag 2 bytes - compression method 2 bytes - last mod file time 2 bytes - last mod file date 2 bytes - crc-32 4 bytes - compressed size 4 bytes - uncompressed size 4 bytes - file name length 2 bytes - extra field length 2 bytes - file comment length 2 bytes - disk number start 2 bytes - internal file attributes 2 bytes - external file attributes 4 bytes - relative offset of local header 4 bytes - - file name (variable size) - extra field (variable size) - file comment (variable size) -*/ -UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() -{ - // Read CD record - if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS) - return UnZip::ReadFailed; - - bool skipEntry = false; - - // Get compression type so we can skip non compatible algorithms - quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD); - - // Get variable size fields length so we can skip the whole record - // if necessary - quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN); - quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN); - quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN); - - quint32 skipLength = szName + szExtra + szComment; - - UnZip::ErrorCode ec = UnZip::Ok; - - if ((compMethod != 0) && (compMethod != 8)) - { - qDebug() << "Unsupported compression method. Skipping file."; - skipEntry = true; - } - - // Header parsing may be a problem if version is bigger than UNZIP_VERSION - if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION) - { - qDebug() << "Unsupported PKZip version. Skipping file."; - skipEntry = true; - } - - if (!skipEntry && szName == 0) - { - qDebug() << "Skipping file with no name."; - skipEntry = true; - } - - if (!skipEntry && device->read(buffer2, szName) != szName) - { - ec = UnZip::ReadFailed; - skipEntry = true; - } - - if (skipEntry) - { - if (ec == UnZip::Ok) - { - if (!device->seek( device->pos() + skipLength )) - ec = UnZip::SeekFailed; - - unsupportedEntryCount++; - } - - return ec; - } - - QString filename = QString::fromAscii(buffer2, szName); - - ZipEntryP* h = new ZipEntryP; - h->compMethod = compMethod; - - h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG]; - h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1]; - - h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT]; - h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1]; - - h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD]; - h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1]; - - h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32); - h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE); - h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE); - - // Skip extra field (if any) - if (szExtra != 0) - { - if (!device->seek( device->pos() + szExtra )) - { - delete h; - return UnZip::SeekFailed; - } - } - - // Read comment field (if any) - if (szComment != 0) - { - if (device->read(buffer2, szComment) != szComment) - { - delete h; - return UnZip::ReadFailed; - } - - h->comment = QString::fromAscii(buffer2, szComment); - } - - h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET); - - if (headers == 0) - headers = new QMap(); - headers->insert(filename, h); - - return UnZip::Ok; -} - -//! \internal Closes the archive and resets the internal status. -void UnzipPrivate::closeArchive() -{ - if (device == 0) - return; - - skipAllEncrypted = false; - - if (headers != 0) - { - qDeleteAll(*headers); - delete headers; - headers = 0; - } - - delete device; device = 0; - - cdOffset = eocdOffset = 0; - cdEntryCount = 0; - unsupportedEntryCount = 0; - - comment.clear(); -} - -//! \internal -UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options) -{ - QString name(path); - QString dirname; - QString directory; - - int pos = name.lastIndexOf('/'); - - // This entry is for a directory - if (pos == name.length() - 1) - { - if (options.testFlag(UnZip::SkipPaths)) - return UnZip::Ok; - - directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name)); - if (!createDirectory(directory)) - { - qDebug() << QString("Unable to create directory: %1").arg(directory); - return UnZip::CreateDirFailed; - } - - return UnZip::Ok; - } - - // Extract path from entry - if (pos > 0) - { - // get directory part - dirname = name.left(pos); - if (options.testFlag(UnZip::SkipPaths)) - { - directory = dir.absolutePath(); - } - else - { - directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname)); - if (!createDirectory(directory)) - { - qDebug() << QString("Unable to create directory: %1").arg(directory); - return UnZip::CreateDirFailed; - } - } - name = name.right(name.length() - pos - 1); - } else directory = dir.absolutePath(); - - name = QString("%1/%2").arg(directory).arg(name); - - QFile outFile(name); - - if (!outFile.open(QIODevice::WriteOnly)) - { - qDebug() << QString("Unable to open %1 for writing").arg(name); - return UnZip::OpenFailed; - } - - //! \todo Set creation/last_modified date/time - - UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options); - - outFile.close(); - - if (ec != UnZip::Ok) - { - if (!outFile.remove()) - qDebug() << QString("Unable to remove corrupted file: %1").arg(name); - } - - return ec; -} - -//! \internal -UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options) -{ - Q_UNUSED(options); - Q_ASSERT(dev != 0); - - if (!entry.lhEntryChecked) - { - UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry); - entry.lhEntryChecked = true; - - if (ec != UnZip::Ok) - return ec; - } - - if (!device->seek(entry.dataOffset)) - return UnZip::SeekFailed; - - // Encryption keys - quint32 keys[3]; - - if (entry.isEncrypted()) - { - UnZip::ErrorCode e = testPassword(keys, path, entry); - if (e != UnZip::Ok) - { - qDebug() << QString("Unable to decrypt %1").arg(path); - return e; - }//! Encryption header size - entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size - } - - if (entry.szComp == 0) - { - if (entry.crc != 0) - return UnZip::Corrupted; - - return UnZip::Ok; - } - - uInt rep = entry.szComp / UNZIP_READ_BUFFER; - uInt rem = entry.szComp % UNZIP_READ_BUFFER; - uInt cur = 0; - - // extract data - qint64 read; - quint64 tot = 0; - - quint32 myCRC = crc32(0L, Z_NULL, 0); - - if (entry.compMethod == 0) - { - while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 ) - { - if (entry.isEncrypted()) - decryptBytes(keys, buffer1, read); - - myCRC = crc32(myCRC, uBuffer, read); - - if (dev->write(buffer1, read) != read) - return UnZip::WriteFailed; - - cur++; - tot += read; - - if (tot == entry.szComp) - break; - } - - if (read < 0) - return UnZip::ReadFailed; - } - else if (entry.compMethod == 8) - { - /* Allocate inflate state */ - z_stream zstr; - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; - zstr.next_in = Z_NULL; - zstr.avail_in = 0; - - int zret; - - // Use inflateInit2 with negative windowBits to get raw decompression - if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK ) - return UnZip::ZlibError; - - int szDecomp; - - // Decompress until deflate stream ends or end of file - do - { - read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem); - if (read == 0) - break; - if (read < 0) - { - (void)inflateEnd(&zstr); - return UnZip::ReadFailed; - } - - if (entry.isEncrypted()) - decryptBytes(keys, buffer1, read); - - cur++; - tot += read; - - zstr.avail_in = (uInt) read; - zstr.next_in = (Bytef*) buffer1; - - - // Run inflate() on input until output buffer not full - do { - zstr.avail_out = UNZIP_READ_BUFFER; - zstr.next_out = (Bytef*) buffer2;; - - zret = inflate(&zstr, Z_NO_FLUSH); - - switch (zret) { - case Z_NEED_DICT: - case Z_DATA_ERROR: - case Z_MEM_ERROR: - inflateEnd(&zstr); - return UnZip::WriteFailed; - default: - ; - } - - szDecomp = UNZIP_READ_BUFFER - zstr.avail_out; - if (dev->write(buffer2, szDecomp) != szDecomp) - { - inflateEnd(&zstr); - return UnZip::ZlibError; - } - - myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp); - - } while (zstr.avail_out == 0); - - } - while (zret != Z_STREAM_END); - - inflateEnd(&zstr); - } - - if (myCRC != entry.crc) - return UnZip::Corrupted; - - return UnZip::Ok; -} - -//! \internal Creates a new directory and all the needed parent directories. -bool UnzipPrivate::createDirectory(const QString& path) -{ - QDir d(path); - if (!d.exists()) - { - int sep = path.lastIndexOf("/"); - if (sep <= 0) return true; - - if (!createDirectory(path.left(sep))) - return false; - - if (!d.mkdir(path)) - { - qDebug() << QString("Unable to create directory: %1").arg(path); - return false; - } - } - - return true; -} - -/*! - \internal Reads an quint32 (4 bytes) from a byte array starting at given offset. -*/ -quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const -{ - quint32 res = (quint32) data[offset]; - res |= (((quint32)data[offset+1]) << 8); - res |= (((quint32)data[offset+2]) << 16); - res |= (((quint32)data[offset+3]) << 24); - - return res; -} - -/*! - \internal Reads an quint64 (8 bytes) from a byte array starting at given offset. -*/ -quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const -{ - quint64 res = (quint64) data[offset]; - res |= (((quint64)data[offset+1]) << 8); - res |= (((quint64)data[offset+2]) << 16); - res |= (((quint64)data[offset+3]) << 24); - res |= (((quint64)data[offset+1]) << 32); - res |= (((quint64)data[offset+2]) << 40); - res |= (((quint64)data[offset+3]) << 48); - res |= (((quint64)data[offset+3]) << 56); - - return res; -} - -/*! - \internal Reads an quint16 (2 bytes) from a byte array starting at given offset. -*/ -quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const -{ - return (quint16) data[offset] | (((quint16)data[offset+1]) << 8); -} - -/*! - \internal Return the next byte in the pseudo-random sequence - */ -int UnzipPrivate::decryptByte(quint32 key2) const -{ - quint16 temp = ((quint16)(key2) & 0xffff) | 2; - return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); -} - -/*! - \internal Update the encryption keys with the next byte of plain text - */ -void UnzipPrivate::updateKeys(quint32* keys, int c) const -{ - keys[0] = CRC32(keys[0], c); - keys[1] += keys[0] & 0xff; - keys[1] = keys[1] * 134775813L + 1; - keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24); -} - -/*! - \internal Initialize the encryption keys and the random header according to - the given password. - */ -void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const -{ - keys[0] = 305419896L; - keys[1] = 591751049L; - keys[2] = 878082192L; - - QByteArray pwdBytes = pwd.toAscii(); - int sz = pwdBytes.size(); - const char* ascii = pwdBytes.data(); - - for (int i=0; iread(buffer1, 12) != 12) - return UnZip::Corrupted; - - // Replace this code if you want to i.e. call some dialog and ask the user for a password - initKeys(password, keys); - if (testKeys(header, keys)) - return UnZip::Ok; - - return UnZip::Skip; -} - -/*! - \internal Tests a set of keys on the encryption header. -*/ -bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys) -{ - char lastByte; - - // decrypt encryption header - for (int i=0; i<11; ++i) - updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2])); - updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2])); - - // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time - // with no extended header we have to check the crc high-order byte - char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24; - - return (lastByte == c); -} - -/*! - \internal Decrypts an array of bytes long \p read. -*/ -void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read) -{ - for (int i=0; i<(int)read; ++i) - updateKeys(keys, buffer[i] ^= decryptByte(keys[2])); -} - -/*! - \internal Converts date and time values from ZIP format to a QDateTime object. -*/ -QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const -{ - QDateTime dt; - - // Usual PKZip low-byte to high-byte order - - // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day - quint16 year = (date[1] >> 1) & 127; - quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7); - quint16 day = date[0] & 31; - - // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision - quint16 hour = (time[1] >> 3) & 31; - quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7); - quint16 seconds = (time[0] & 31) * 2; - - dt.setDate(QDate(1980 + year, month, day)); - dt.setTime(QTime(hour, minutes, seconds)); - return dt; -} +/**************************************************************************** +** Filename: unzip.cpp +** Last updated [dd/mm/yyyy]: 28/01/2007 +** +** pkzip 2.0 decompression. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +#include "unzip.h" +#include "unzip_p.h" +#include "zipentry_p.h" + +#include +#include +#include +#include +#include + +// You can remove this #include if you replace the qDebug() statements. +#include + +/*! + \class UnZip unzip.h + + \brief PKZip 2.0 file decompression. + Compatibility with later versions is not ensured as they may use + unsupported compression algorithms. + Versions after 2.7 may have an incompatible header format and thus be + completely incompatible. +*/ + +/*! \enum UnZip::ErrorCode The result of a decompression operation. + \value UnZip::Ok No error occurred. + \value UnZip::ZlibInit Failed to init or load the zlib library. + \value UnZip::ZlibError The zlib library returned some error. + \value UnZip::OpenFailed Unable to create or open a device. + \value UnZip::PartiallyCorrupted Corrupted zip archive - some files could be extracted. + \value UnZip::Corrupted Corrupted or invalid zip archive. + \value UnZip::WrongPassword Unable to decrypt a password protected file. + \value UnZip::NoOpenArchive No archive has been opened yet. + \value UnZip::FileNotFound Unable to find the requested file in the archive. + \value UnZip::ReadFailed Reading of a file failed. + \value UnZip::WriteFailed Writing of a file failed. + \value UnZip::SeekFailed Seek failed. + \value UnZip::CreateDirFailed Could not create a directory. + \value UnZip::InvalidDevice A null device has been passed as parameter. + \value UnZip::InvalidArchive This is not a valid (or supported) ZIP archive. + \value UnZip::HeaderConsistencyError Local header record info does not match with the central directory record info. The archive may be corrupted. + + \value UnZip::Skip Internal use only. + \value UnZip::SkipAll Internal use only. +*/ + +/*! \enum UnZip::ExtractionOptions Some options for the file extraction methods. + \value UnZip::ExtractPaths Default. Does not ignore the path of the zipped files. + \value UnZip::SkipPaths Default. Ignores the path of the zipped files and extracts them all to the same root directory. +*/ + +//! Local header size (excluding signature, excluding variable length fields) +#define UNZIP_LOCAL_HEADER_SIZE 26 +//! Central Directory file entry size (excluding signature, excluding variable length fields) +#define UNZIP_CD_ENTRY_SIZE_NS 42 +//! Data descriptor size (excluding signature) +#define UNZIP_DD_SIZE 12 +//! End Of Central Directory size (including signature, excluding variable length fields) +#define UNZIP_EOCD_SIZE 22 +//! Local header entry encryption header size +#define UNZIP_LOCAL_ENC_HEADER_SIZE 12 + +// Some offsets inside a CD record (excluding signature) +#define UNZIP_CD_OFF_VERSION 0 +#define UNZIP_CD_OFF_GPFLAG 4 +#define UNZIP_CD_OFF_CMETHOD 6 +#define UNZIP_CD_OFF_MODT 8 +#define UNZIP_CD_OFF_MODD 10 +#define UNZIP_CD_OFF_CRC32 12 +#define UNZIP_CD_OFF_CSIZE 16 +#define UNZIP_CD_OFF_USIZE 20 +#define UNZIP_CD_OFF_NAMELEN 24 +#define UNZIP_CD_OFF_XLEN 26 +#define UNZIP_CD_OFF_COMMLEN 28 +#define UNZIP_CD_OFF_LHOFFSET 38 + +// Some offsets inside a local header record (excluding signature) +#define UNZIP_LH_OFF_VERSION 0 +#define UNZIP_LH_OFF_GPFLAG 2 +#define UNZIP_LH_OFF_CMETHOD 4 +#define UNZIP_LH_OFF_MODT 6 +#define UNZIP_LH_OFF_MODD 8 +#define UNZIP_LH_OFF_CRC32 10 +#define UNZIP_LH_OFF_CSIZE 14 +#define UNZIP_LH_OFF_USIZE 18 +#define UNZIP_LH_OFF_NAMELEN 22 +#define UNZIP_LH_OFF_XLEN 24 + +// Some offsets inside a data descriptor record (excluding signature) +#define UNZIP_DD_OFF_CRC32 0 +#define UNZIP_DD_OFF_CSIZE 4 +#define UNZIP_DD_OFF_USIZE 8 + +// Some offsets inside a EOCD record +#define UNZIP_EOCD_OFF_ENTRIES 6 +#define UNZIP_EOCD_OFF_CDOFF 12 +#define UNZIP_EOCD_OFF_COMMLEN 16 + +/*! + Max version handled by this API. + 0x1B = 2.7 --> full compatibility only up to version 2.0 (0x14) + versions from 2.1 to 2.7 may use unsupported compression methods + versions after 2.7 may have an incompatible header format +*/ +#define UNZIP_VERSION 0x1B +//! Full compatibility granted until this version +#define UNZIP_VERSION_STRICT 0x14 + +//! CRC32 routine +#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) + +//! Checks if some file has been already extracted. +#define UNZIP_CHECK_FOR_VALID_DATA \ + {\ + if (headers != 0)\ + {\ + qDebug() << "Corrupted zip archive. Some files might be extracted.";\ + ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\ + break;\ + }\ + else\ + {\ + delete device;\ + device = 0;\ + qDebug() << "Corrupted or invalid zip archive";\ + ec = UnZip::Corrupted;\ + break;\ + }\ + } + + +/************************************************************************ + Public interface +*************************************************************************/ + +/*! + Creates a new Zip file decompressor. +*/ +UnZip::UnZip() +{ + d = new UnzipPrivate; +} + +/*! + Closes any open archive and releases used resources. +*/ +UnZip::~UnZip() +{ + closeArchive(); + delete d; +} + +/*! + Returns true if there is an open archive. +*/ +bool UnZip::isOpen() const +{ + return d->device != 0; +} + +/*! + Opens a zip archive and reads the files list. Closes any previously opened archive. +*/ +UnZip::ErrorCode UnZip::openArchive(const QString& filename) +{ + QFile* file = new QFile(filename); + + if (!file->exists()) { + delete file; + return UnZip::FileNotFound; + } + + if (!file->open(QIODevice::ReadOnly)) { + delete file; + return UnZip::OpenFailed; + } + + return openArchive(file); +} + +/*! + Opens a zip archive and reads the entries list. + Closes any previously opened archive. + \warning The class takes ownership of the device so don't delete it! +*/ +UnZip::ErrorCode UnZip::openArchive(QIODevice* device) +{ + if (device == 0) + { + qDebug() << "Invalid device."; + return UnZip::InvalidDevice; + } + + return d->openArchive(device); +} + +/*! + Closes the archive and releases all the used resources (like cached passwords). +*/ +void UnZip::closeArchive() +{ + d->closeArchive(); +} + +QString UnZip::archiveComment() const +{ + if (d->device == 0) + return QString(); + return d->comment; +} + +/*! + Returns a locale translated error string for a given error code. +*/ +QString UnZip::formatError(UnZip::ErrorCode c) const +{ + switch (c) + { + case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break; + case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break; + case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break; + case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break; + case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break; + case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break; + case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break; + case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break; + case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break; + case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break; + case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break; + case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break; + case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break; + case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break; + case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break; + case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break; + default: ; + } + + return QCoreApplication::translate("UnZip", "Unknown error."); +} + +/*! + Returns true if the archive contains a file with the given path and name. +*/ +bool UnZip::contains(const QString& file) const +{ + if (d->headers == 0) + return false; + + return d->headers->contains(file); +} + +/*! + Returns complete paths of files and directories in this archive. +*/ +QStringList UnZip::fileList() const +{ + return d->headers == 0 ? QStringList() : d->headers->keys(); +} + +/*! + Returns information for each (correctly parsed) entry of this archive. +*/ +QList UnZip::entryList() const +{ + QList list; + + if (d->headers != 0) + { + for (QMap::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it) + { + const ZipEntryP* entry = it.value(); + Q_ASSERT(entry != 0); + + ZipEntry z; + + z.filename = it.key(); + if (!entry->comment.isEmpty()) + z.comment = entry->comment; + z.compressedSize = entry->szComp; + z.uncompressedSize = entry->szUncomp; + z.crc32 = entry->crc; + z.lastModified = d->convertDateTime(entry->modDate, entry->modTime); + + z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression; + z.type = z.filename.endsWith("/") ? Directory : File; + + z.encrypted = entry->isEncrypted(); + + list.append(z); + } + } + + return list; +} + +/*! + Extracts the whole archive to a directory. +*/ +UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options) +{ + return extractAll(QDir(dirname), options); +} + +/*! + Extracts the whole archive to a directory. +*/ +UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) +{ + // this should only happen if we didn't call openArchive() yet + if (d->device == 0) + return NoOpenArchive; + + if (d->headers == 0) + return Ok; + + bool end = false; + for (QMap::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr) + { + ZipEntryP* entry = itr.value(); + Q_ASSERT(entry != 0); + + if ((entry->isEncrypted()) && d->skipAllEncrypted) + continue; + + switch (d->extractFile(itr.key(), *entry, dir, options)) + { + case Corrupted: + qDebug() << "Removing corrupted entry" << itr.key(); + d->headers->erase(itr++); + if (itr == d->headers->end()) + end = true; + break; + case CreateDirFailed: + break; + case Skip: + break; + case SkipAll: + d->skipAllEncrypted = true; + break; + default: + ; + } + + if (end) + break; + } + + return Ok; +} + +/*! + Extracts a single file to a directory. +*/ +UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options) +{ + return extractFile(filename, QDir(dirname), options); +} + +/*! + Extracts a single file to a directory. +*/ +UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options) +{ + QMap::Iterator itr = d->headers->find(filename); + if (itr != d->headers->end()) + { + ZipEntryP* entry = itr.value(); + Q_ASSERT(entry != 0); + return d->extractFile(itr.key(), *entry, dir, options); + } + + return FileNotFound; +} + +/*! + Extracts a single file to a directory. +*/ +UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options) +{ + if (dev == 0) + return InvalidDevice; + + QMap::Iterator itr = d->headers->find(filename); + if (itr != d->headers->end()) { + ZipEntryP* entry = itr.value(); + Q_ASSERT(entry != 0); + return d->extractFile(itr.key(), *entry, dev, options); + } + + return FileNotFound; +} + +/*! + Extracts a list of files. + Stops extraction at the first error (but continues if a file does not exist in the archive). + */ +UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options) +{ + QDir dir(dirname); + ErrorCode ec; + + for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) + { + ec = extractFile(*itr, dir, options); + if (ec == FileNotFound) + continue; + if (ec != Ok) + return ec; + } + + return Ok; +} + +/*! + Extracts a list of files. + Stops extraction at the first error (but continues if a file does not exist in the archive). + */ +UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options) +{ + ErrorCode ec; + + for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) + { + ec = extractFile(*itr, dir, options); + if (ec == FileNotFound) + continue; + if (ec != Ok) + return ec; + } + + return Ok; +} + +/*! + Remove/replace this method to add your own password retrieval routine. +*/ +void UnZip::setPassword(const QString& pwd) +{ + d->password = pwd; +} + +/*! + ZipEntry constructor - initialize data. Type is set to File. +*/ +UnZip::ZipEntry::ZipEntry() +{ + compressedSize = uncompressedSize = crc32 = 0; + compression = NoCompression; + type = File; + encrypted = false; +} + + +/************************************************************************ + Private interface +*************************************************************************/ + +//! \internal +UnzipPrivate::UnzipPrivate() +{ + skipAllEncrypted = false; + headers = 0; + device = 0; + + uBuffer = (unsigned char*) buffer1; + crcTable = (quint32*) get_crc_table(); + + cdOffset = eocdOffset = 0; + cdEntryCount = 0; + unsupportedEntryCount = 0; +} + +//! \internal Parses a Zip archive. +UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) +{ + Q_ASSERT(dev != 0); + + if (device != 0) + closeArchive(); + + device = dev; + + if (!(device->isOpen() || device->open(QIODevice::ReadOnly))) + { + delete device; + device = 0; + + qDebug() << "Unable to open device for reading"; + return UnZip::OpenFailed; + } + + UnZip::ErrorCode ec; + + ec = seekToCentralDirectory(); + if (ec != UnZip::Ok) + { + closeArchive(); + return ec; + } + + //! \todo Ignore CD entry count? CD may be corrupted. + if (cdEntryCount == 0) + { + return UnZip::Ok; + } + + bool continueParsing = true; + + while (continueParsing) + { + if (device->read(buffer1, 4) != 4) + UNZIP_CHECK_FOR_VALID_DATA + + if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) ) + break; + + if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok ) + break; + } + + if (ec != UnZip::Ok) + closeArchive(); + + return ec; +} + +/* + \internal Parses a local header record and makes some consistency check + with the information stored in the Central Directory record for this entry + that has been previously parsed. + \todo Optional consistency check (as a ExtractionOptions flag) + + local file header signature 4 bytes (0x04034b50) + version needed to extract 2 bytes + general purpose bit flag 2 bytes + compression method 2 bytes + last mod file time 2 bytes + last mod file date 2 bytes + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + file name length 2 bytes + extra field length 2 bytes + + file name (variable size) + extra field (variable size) +*/ +UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry) +{ + if (!device->seek(entry.lhOffset)) + return UnZip::SeekFailed; + + // Test signature + if (device->read(buffer1, 4) != 4) + return UnZip::ReadFailed; + + if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04)) + return UnZip::InvalidArchive; + + if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE) + return UnZip::ReadFailed; + + /* + Check 3rd general purpose bit flag. + + "bit 3: If this bit is set, the fields crc-32, compressed size + and uncompressed size are set to zero in the local + header. The correct values are put in the data descriptor + immediately following the compressed data." + */ + bool hasDataDescriptor = entry.hasDataDescriptor(); + + bool checkFailed = false; + + if (!checkFailed) + checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD); + if (!checkFailed) + checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG]; + if (!checkFailed) + checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1]; + if (!checkFailed) + checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT]; + if (!checkFailed) + checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1]; + if (!checkFailed) + checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD]; + if (!checkFailed) + checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1]; + if (!hasDataDescriptor) + { + if (!checkFailed) + checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32); + if (!checkFailed) + checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE); + if (!checkFailed) + checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE); + } + + if (checkFailed) + return UnZip::HeaderConsistencyError; + + // Check filename + quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN); + if (szName == 0) + return UnZip::HeaderConsistencyError; + + if (device->read(buffer2, szName) != szName) + return UnZip::ReadFailed; + + QString filename = QString::fromAscii(buffer2, szName); + if (filename != path) + { + qDebug() << "Filename in local header mismatches."; + return UnZip::HeaderConsistencyError; + } + + // Skip extra field + quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN); + if (szExtra != 0) + { + if (!device->seek(device->pos() + szExtra)) + return UnZip::SeekFailed; + } + + entry.dataOffset = device->pos(); + + if (hasDataDescriptor) + { + /* + The data descriptor has this OPTIONAL signature: PK\7\8 + We try to skip the compressed data relying on the size set in the + Central Directory record. + */ + if (!device->seek(device->pos() + entry.szComp)) + return UnZip::SeekFailed; + + // Read 4 bytes and check if there is a data descriptor signature + if (device->read(buffer2, 4) != 4) + return UnZip::ReadFailed; + + bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08; + if (hasSignature) + { + if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE) + return UnZip::ReadFailed; + } + else + { + if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4) + return UnZip::ReadFailed; + } + + // DD: crc, compressed size, uncompressed size + if ( + entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) || + entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) || + entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE) + ) + return UnZip::HeaderConsistencyError; + } + + return UnZip::Ok; +} + +/*! \internal Attempts to find the start of the central directory record. + + We seek the file back until we reach the "End Of Central Directory" + signature PK\5\6. + + end of central dir signature 4 bytes (0x06054b50) + number of this disk 2 bytes + number of the disk with the + start of the central directory 2 bytes + total number of entries in the + central directory on this disk 2 bytes + total number of entries in + the central directory 2 bytes + size of the central directory 4 bytes + offset of start of central + directory with respect to + the starting disk number 4 bytes + .ZIP file comment length 2 bytes + --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE --- + .ZIP file comment (variable size) +*/ +UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() +{ + qint64 length = device->size(); + qint64 offset = length - UNZIP_EOCD_SIZE; + + if (length < UNZIP_EOCD_SIZE) + return UnZip::InvalidArchive; + + if (!device->seek( offset )) + return UnZip::SeekFailed; + + if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) + return UnZip::ReadFailed; + + bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06); + + if (eocdFound) + { + // Zip file has no comment (the only variable length field in the EOCD record) + eocdOffset = offset; + } + else + { + qint64 read; + char* p = 0; + + offset -= UNZIP_EOCD_SIZE; + + if (offset <= 0) + return UnZip::InvalidArchive; + + if (!device->seek( offset )) + return UnZip::SeekFailed; + + while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0) + { + if ( (p = strstr(buffer1, "PK\5\6")) != 0) + { + // Seek to the start of the EOCD record so we can read it fully + // Yes... we could simply read the missing bytes and append them to the buffer + // but this is far easier so heck it! + device->seek( offset + (p - buffer1) ); + eocdFound = true; + eocdOffset = offset + (p - buffer1); + + // Read EOCD record + if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) + return UnZip::ReadFailed; + + break; + } + + offset -= UNZIP_EOCD_SIZE; + if (offset <= 0) + return UnZip::InvalidArchive; + + if (!device->seek( offset )) + return UnZip::SeekFailed; + } + } + + if (!eocdFound) + return UnZip::InvalidArchive; + + // Parse EOCD to locate CD offset + offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4); + + cdOffset = offset; + + cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4); + + quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4); + if (commentLength != 0) + { + QByteArray c = device->read(commentLength); + if (c.count() != commentLength) + return UnZip::ReadFailed; + + comment = c; + } + + // Seek to the start of the CD record + if (!device->seek( cdOffset )) + return UnZip::SeekFailed; + + return UnZip::Ok; +} + +/*! + \internal Parses a central directory record. + + Central Directory record structure: + + [file header 1] + . + . + . + [file header n] + [digital signature] // PKZip 6.2 or later only + + File header: + + central file header signature 4 bytes (0x02014b50) + version made by 2 bytes + version needed to extract 2 bytes + general purpose bit flag 2 bytes + compression method 2 bytes + last mod file time 2 bytes + last mod file date 2 bytes + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + file name length 2 bytes + extra field length 2 bytes + file comment length 2 bytes + disk number start 2 bytes + internal file attributes 2 bytes + external file attributes 4 bytes + relative offset of local header 4 bytes + + file name (variable size) + extra field (variable size) + file comment (variable size) +*/ +UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() +{ + // Read CD record + if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS) + return UnZip::ReadFailed; + + bool skipEntry = false; + + // Get compression type so we can skip non compatible algorithms + quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD); + + // Get variable size fields length so we can skip the whole record + // if necessary + quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN); + quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN); + quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN); + + quint32 skipLength = szName + szExtra + szComment; + + UnZip::ErrorCode ec = UnZip::Ok; + + if ((compMethod != 0) && (compMethod != 8)) + { + qDebug() << "Unsupported compression method. Skipping file."; + skipEntry = true; + } + + // Header parsing may be a problem if version is bigger than UNZIP_VERSION + if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION) + { + qDebug() << "Unsupported PKZip version. Skipping file."; + skipEntry = true; + } + + if (!skipEntry && szName == 0) + { + qDebug() << "Skipping file with no name."; + skipEntry = true; + } + + if (!skipEntry && device->read(buffer2, szName) != szName) + { + ec = UnZip::ReadFailed; + skipEntry = true; + } + + if (skipEntry) + { + if (ec == UnZip::Ok) + { + if (!device->seek( device->pos() + skipLength )) + ec = UnZip::SeekFailed; + + unsupportedEntryCount++; + } + + return ec; + } + + QString filename = QString::fromAscii(buffer2, szName); + + ZipEntryP* h = new ZipEntryP; + h->compMethod = compMethod; + + h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG]; + h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1]; + + h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT]; + h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1]; + + h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD]; + h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1]; + + h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32); + h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE); + h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE); + + // Skip extra field (if any) + if (szExtra != 0) + { + if (!device->seek( device->pos() + szExtra )) + { + delete h; + return UnZip::SeekFailed; + } + } + + // Read comment field (if any) + if (szComment != 0) + { + if (device->read(buffer2, szComment) != szComment) + { + delete h; + return UnZip::ReadFailed; + } + + h->comment = QString::fromAscii(buffer2, szComment); + } + + h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET); + + if (headers == 0) + headers = new QMap(); + headers->insert(filename, h); + + return UnZip::Ok; +} + +//! \internal Closes the archive and resets the internal status. +void UnzipPrivate::closeArchive() +{ + if (device == 0) + return; + + skipAllEncrypted = false; + + if (headers != 0) + { + qDeleteAll(*headers); + delete headers; + headers = 0; + } + + delete device; device = 0; + + cdOffset = eocdOffset = 0; + cdEntryCount = 0; + unsupportedEntryCount = 0; + + comment.clear(); +} + +//! \internal +UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options) +{ + QString name(path); + QString dirname; + QString directory; + + int pos = name.lastIndexOf('/'); + + // This entry is for a directory + if (pos == name.length() - 1) + { + if (options.testFlag(UnZip::SkipPaths)) + return UnZip::Ok; + + directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name)); + if (!createDirectory(directory)) + { + qDebug() << QString("Unable to create directory: %1").arg(directory); + return UnZip::CreateDirFailed; + } + + return UnZip::Ok; + } + + // Extract path from entry + if (pos > 0) + { + // get directory part + dirname = name.left(pos); + if (options.testFlag(UnZip::SkipPaths)) + { + directory = dir.absolutePath(); + } + else + { + directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname)); + if (!createDirectory(directory)) + { + qDebug() << QString("Unable to create directory: %1").arg(directory); + return UnZip::CreateDirFailed; + } + } + name = name.right(name.length() - pos - 1); + } else directory = dir.absolutePath(); + + name = QString("%1/%2").arg(directory).arg(name); + + QFile outFile(name); + + if (!outFile.open(QIODevice::WriteOnly)) + { + qDebug() << QString("Unable to open %1 for writing").arg(name); + return UnZip::OpenFailed; + } + + //! \todo Set creation/last_modified date/time + + UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options); + + outFile.close(); + + if (ec != UnZip::Ok) + { + if (!outFile.remove()) + qDebug() << QString("Unable to remove corrupted file: %1").arg(name); + } + + return ec; +} + +//! \internal +UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options) +{ + Q_UNUSED(options); + Q_ASSERT(dev != 0); + + if (!entry.lhEntryChecked) + { + UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry); + entry.lhEntryChecked = true; + + if (ec != UnZip::Ok) + return ec; + } + + if (!device->seek(entry.dataOffset)) + return UnZip::SeekFailed; + + // Encryption keys + quint32 keys[3]; + + if (entry.isEncrypted()) + { + UnZip::ErrorCode e = testPassword(keys, path, entry); + if (e != UnZip::Ok) + { + qDebug() << QString("Unable to decrypt %1").arg(path); + return e; + }//! Encryption header size + entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size + } + + if (entry.szComp == 0) + { + if (entry.crc != 0) + return UnZip::Corrupted; + + return UnZip::Ok; + } + + uInt rep = entry.szComp / UNZIP_READ_BUFFER; + uInt rem = entry.szComp % UNZIP_READ_BUFFER; + uInt cur = 0; + + // extract data + qint64 read; + quint64 tot = 0; + + quint32 myCRC = crc32(0L, Z_NULL, 0); + + if (entry.compMethod == 0) + { + while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 ) + { + if (entry.isEncrypted()) + decryptBytes(keys, buffer1, read); + + myCRC = crc32(myCRC, uBuffer, read); + + if (dev->write(buffer1, read) != read) + return UnZip::WriteFailed; + + cur++; + tot += read; + + if (tot == entry.szComp) + break; + } + + if (read < 0) + return UnZip::ReadFailed; + } + else if (entry.compMethod == 8) + { + /* Allocate inflate state */ + z_stream zstr; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + zstr.next_in = Z_NULL; + zstr.avail_in = 0; + + int zret; + + // Use inflateInit2 with negative windowBits to get raw decompression + if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK ) + return UnZip::ZlibError; + + int szDecomp; + + // Decompress until deflate stream ends or end of file + do + { + read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem); + if (read == 0) + break; + if (read < 0) + { + (void)inflateEnd(&zstr); + return UnZip::ReadFailed; + } + + if (entry.isEncrypted()) + decryptBytes(keys, buffer1, read); + + cur++; + tot += read; + + zstr.avail_in = (uInt) read; + zstr.next_in = (Bytef*) buffer1; + + + // Run inflate() on input until output buffer not full + do { + zstr.avail_out = UNZIP_READ_BUFFER; + zstr.next_out = (Bytef*) buffer2;; + + zret = inflate(&zstr, Z_NO_FLUSH); + + switch (zret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&zstr); + return UnZip::WriteFailed; + default: + ; + } + + szDecomp = UNZIP_READ_BUFFER - zstr.avail_out; + if (dev->write(buffer2, szDecomp) != szDecomp) + { + inflateEnd(&zstr); + return UnZip::ZlibError; + } + + myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp); + + } while (zstr.avail_out == 0); + + } + while (zret != Z_STREAM_END); + + inflateEnd(&zstr); + } + + if (myCRC != entry.crc) + return UnZip::Corrupted; + + return UnZip::Ok; +} + +//! \internal Creates a new directory and all the needed parent directories. +bool UnzipPrivate::createDirectory(const QString& path) +{ + QDir d(path); + if (!d.exists()) + { + int sep = path.lastIndexOf("/"); + if (sep <= 0) return true; + + if (!createDirectory(path.left(sep))) + return false; + + if (!d.mkdir(path)) + { + qDebug() << QString("Unable to create directory: %1").arg(path); + return false; + } + } + + return true; +} + +/*! + \internal Reads an quint32 (4 bytes) from a byte array starting at given offset. +*/ +quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const +{ + quint32 res = (quint32) data[offset]; + res |= (((quint32)data[offset+1]) << 8); + res |= (((quint32)data[offset+2]) << 16); + res |= (((quint32)data[offset+3]) << 24); + + return res; +} + +/*! + \internal Reads an quint64 (8 bytes) from a byte array starting at given offset. +*/ +quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const +{ + quint64 res = (quint64) data[offset]; + res |= (((quint64)data[offset+1]) << 8); + res |= (((quint64)data[offset+2]) << 16); + res |= (((quint64)data[offset+3]) << 24); + res |= (((quint64)data[offset+1]) << 32); + res |= (((quint64)data[offset+2]) << 40); + res |= (((quint64)data[offset+3]) << 48); + res |= (((quint64)data[offset+3]) << 56); + + return res; +} + +/*! + \internal Reads an quint16 (2 bytes) from a byte array starting at given offset. +*/ +quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const +{ + return (quint16) data[offset] | (((quint16)data[offset+1]) << 8); +} + +/*! + \internal Return the next byte in the pseudo-random sequence + */ +int UnzipPrivate::decryptByte(quint32 key2) const +{ + quint16 temp = ((quint16)(key2) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*! + \internal Update the encryption keys with the next byte of plain text + */ +void UnzipPrivate::updateKeys(quint32* keys, int c) const +{ + keys[0] = CRC32(keys[0], c); + keys[1] += keys[0] & 0xff; + keys[1] = keys[1] * 134775813L + 1; + keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24); +} + +/*! + \internal Initialize the encryption keys and the random header according to + the given password. + */ +void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const +{ + keys[0] = 305419896L; + keys[1] = 591751049L; + keys[2] = 878082192L; + + QByteArray pwdBytes = pwd.toAscii(); + int sz = pwdBytes.size(); + const char* ascii = pwdBytes.data(); + + for (int i=0; iread(buffer1, 12) != 12) + return UnZip::Corrupted; + + // Replace this code if you want to i.e. call some dialog and ask the user for a password + initKeys(password, keys); + if (testKeys(header, keys)) + return UnZip::Ok; + + return UnZip::Skip; +} + +/*! + \internal Tests a set of keys on the encryption header. +*/ +bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys) +{ + char lastByte; + + // decrypt encryption header + for (int i=0; i<11; ++i) + updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2])); + updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2])); + + // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time + // with no extended header we have to check the crc high-order byte + char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24; + + return (lastByte == c); +} + +/*! + \internal Decrypts an array of bytes long \p read. +*/ +void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read) +{ + for (int i=0; i<(int)read; ++i) + updateKeys(keys, buffer[i] ^= decryptByte(keys[2])); +} + +/*! + \internal Converts date and time values from ZIP format to a QDateTime object. +*/ +QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const +{ + QDateTime dt; + + // Usual PKZip low-byte to high-byte order + + // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day + quint16 year = (date[1] >> 1) & 127; + quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7); + quint16 day = date[0] & 31; + + // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision + quint16 hour = (time[1] >> 3) & 31; + quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7); + quint16 seconds = (time[0] & 31) * 2; + + dt.setDate(QDate(1980 + year, month, day)); + dt.setTime(QTime(hour, minutes, seconds)); + return dt; +} diff --git a/rbutil/rbutilqt/zip/unzip.h b/rbutil/rbutilqt/zip/unzip.h index a48fbe8724..e480541716 100644 --- a/rbutil/rbutilqt/zip/unzip.h +++ b/rbutil/rbutilqt/zip/unzip.h @@ -1,144 +1,144 @@ -/**************************************************************************** -** Filename: unzip.h -** Last updated [dd/mm/yyyy]: 28/01/2007 -** -** pkzip 2.0 decompression. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -#ifndef OSDAB_UNZIP__H -#define OSDAB_UNZIP__H - -#include -#include -#include - -#include - -class UnzipPrivate; -class QIODevice; -class QFile; -class QDir; -class QStringList; -class QString; - - -class UnZip -{ -public: - enum ErrorCode - { - Ok, - ZlibInit, - ZlibError, - OpenFailed, - PartiallyCorrupted, - Corrupted, - WrongPassword, - NoOpenArchive, - FileNotFound, - ReadFailed, - WriteFailed, - SeekFailed, - CreateDirFailed, - InvalidDevice, - InvalidArchive, - HeaderConsistencyError, - - Skip, SkipAll // internal use only - }; - - enum ExtractionOption - { - //! Extracts paths (default) - ExtractPaths = 0x0001, - //! Ignores paths and extracts all the files to the same directory - SkipPaths = 0x0002 - }; - Q_DECLARE_FLAGS(ExtractionOptions, ExtractionOption) - - enum CompressionMethod - { - NoCompression, Deflated, UnknownCompression - }; - - enum FileType - { - File, Directory - }; - - typedef struct ZipEntry - { - ZipEntry(); - - QString filename; - QString comment; - - quint32 compressedSize; - quint32 uncompressedSize; - quint32 crc32; - - QDateTime lastModified; - - CompressionMethod compression; - FileType type; - - bool encrypted; - }; - - UnZip(); - virtual ~UnZip(); - - bool isOpen() const; - - ErrorCode openArchive(const QString& filename); - ErrorCode openArchive(QIODevice* device); - void closeArchive(); - - QString archiveComment() const; - - QString formatError(UnZip::ErrorCode c) const; - - bool contains(const QString& file) const; - - QStringList fileList() const; - QList entryList() const; - - ErrorCode extractAll(const QString& dirname, ExtractionOptions options = ExtractPaths); - ErrorCode extractAll(const QDir& dir, ExtractionOptions options = ExtractPaths); - - ErrorCode extractFile(const QString& filename, const QString& dirname, ExtractionOptions options = ExtractPaths); - ErrorCode extractFile(const QString& filename, const QDir& dir, ExtractionOptions options = ExtractPaths); - ErrorCode extractFile(const QString& filename, QIODevice* device, ExtractionOptions options = ExtractPaths); - - ErrorCode extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options = ExtractPaths); - ErrorCode extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options = ExtractPaths); - - void setPassword(const QString& pwd); - -private: - UnzipPrivate* d; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(UnZip::ExtractionOptions) - -#endif // OSDAB_UNZIP__H +/**************************************************************************** +** Filename: unzip.h +** Last updated [dd/mm/yyyy]: 28/01/2007 +** +** pkzip 2.0 decompression. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +#ifndef OSDAB_UNZIP__H +#define OSDAB_UNZIP__H + +#include +#include +#include + +#include + +class UnzipPrivate; +class QIODevice; +class QFile; +class QDir; +class QStringList; +class QString; + + +class UnZip +{ +public: + enum ErrorCode + { + Ok, + ZlibInit, + ZlibError, + OpenFailed, + PartiallyCorrupted, + Corrupted, + WrongPassword, + NoOpenArchive, + FileNotFound, + ReadFailed, + WriteFailed, + SeekFailed, + CreateDirFailed, + InvalidDevice, + InvalidArchive, + HeaderConsistencyError, + + Skip, SkipAll // internal use only + }; + + enum ExtractionOption + { + //! Extracts paths (default) + ExtractPaths = 0x0001, + //! Ignores paths and extracts all the files to the same directory + SkipPaths = 0x0002 + }; + Q_DECLARE_FLAGS(ExtractionOptions, ExtractionOption) + + enum CompressionMethod + { + NoCompression, Deflated, UnknownCompression + }; + + enum FileType + { + File, Directory + }; + + typedef struct ZipEntry + { + ZipEntry(); + + QString filename; + QString comment; + + quint32 compressedSize; + quint32 uncompressedSize; + quint32 crc32; + + QDateTime lastModified; + + CompressionMethod compression; + FileType type; + + bool encrypted; + }; + + UnZip(); + virtual ~UnZip(); + + bool isOpen() const; + + ErrorCode openArchive(const QString& filename); + ErrorCode openArchive(QIODevice* device); + void closeArchive(); + + QString archiveComment() const; + + QString formatError(UnZip::ErrorCode c) const; + + bool contains(const QString& file) const; + + QStringList fileList() const; + QList entryList() const; + + ErrorCode extractAll(const QString& dirname, ExtractionOptions options = ExtractPaths); + ErrorCode extractAll(const QDir& dir, ExtractionOptions options = ExtractPaths); + + ErrorCode extractFile(const QString& filename, const QString& dirname, ExtractionOptions options = ExtractPaths); + ErrorCode extractFile(const QString& filename, const QDir& dir, ExtractionOptions options = ExtractPaths); + ErrorCode extractFile(const QString& filename, QIODevice* device, ExtractionOptions options = ExtractPaths); + + ErrorCode extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options = ExtractPaths); + ErrorCode extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options = ExtractPaths); + + void setPassword(const QString& pwd); + +private: + UnzipPrivate* d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(UnZip::ExtractionOptions) + +#endif // OSDAB_UNZIP__H diff --git a/rbutil/rbutilqt/zip/unzip_p.h b/rbutil/rbutilqt/zip/unzip_p.h index c05ac67b47..c0ea11b7bf 100644 --- a/rbutil/rbutilqt/zip/unzip_p.h +++ b/rbutil/rbutilqt/zip/unzip_p.h @@ -1,112 +1,112 @@ -/**************************************************************************** -** Filename: unzip_p.h -** Last updated [dd/mm/yyyy]: 28/01/2007 -** -** pkzip 2.0 decompression. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Zip/UnZip API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef OSDAB_UNZIP_P__H -#define OSDAB_UNZIP_P__H - -#include "unzip.h" -#include "zipentry_p.h" - -#include - -// zLib authors suggest using larger buffers (128K or 256K) for (de)compression (especially for inflate()) -// we use a 256K buffer here - if you want to use this code on a pre-iceage mainframe please change it ;) -#define UNZIP_READ_BUFFER (256*1024) - -class UnzipPrivate -{ -public: - UnzipPrivate(); - - // Replace this with whatever else you use to store/retrieve the password. - QString password; - - bool skipAllEncrypted; - - QMap* headers; - - QIODevice* device; - - char buffer1[UNZIP_READ_BUFFER]; - char buffer2[UNZIP_READ_BUFFER]; - - unsigned char* uBuffer; - const quint32* crcTable; - - // Central Directory (CD) offset - quint32 cdOffset; - // End of Central Directory (EOCD) offset - quint32 eocdOffset; - - // Number of entries in the Central Directory (as to the EOCD record) - quint16 cdEntryCount; - - // The number of detected entries that have been skipped because of a non compatible format - quint16 unsupportedEntryCount; - - QString comment; - - UnZip::ErrorCode openArchive(QIODevice* device); - - UnZip::ErrorCode seekToCentralDirectory(); - UnZip::ErrorCode parseCentralDirectoryRecord(); - UnZip::ErrorCode parseLocalHeaderRecord(const QString& path, ZipEntryP& entry); - - void closeArchive(); - - UnZip::ErrorCode extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options); - UnZip::ErrorCode extractFile(const QString& path, ZipEntryP& entry, QIODevice* device, UnZip::ExtractionOptions options); - - UnZip::ErrorCode testPassword(quint32* keys, const QString& file, const ZipEntryP& header); - bool testKeys(const ZipEntryP& header, quint32* keys); - - bool createDirectory(const QString& path); - - inline void decryptBytes(quint32* keys, char* buffer, qint64 read); - - inline quint32 getULong(const unsigned char* data, quint32 offset) const; - inline quint64 getULLong(const unsigned char* data, quint32 offset) const; - inline quint16 getUShort(const unsigned char* data, quint32 offset) const; - inline int decryptByte(quint32 key2) const; - inline void updateKeys(quint32* keys, int c) const; - inline void initKeys(const QString& pwd, quint32* keys) const; - - inline QDateTime convertDateTime(const unsigned char date[2], const unsigned char time[2]) const; -}; - -#endif // OSDAB_UNZIP_P__H +/**************************************************************************** +** Filename: unzip_p.h +** Last updated [dd/mm/yyyy]: 28/01/2007 +** +** pkzip 2.0 decompression. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Zip/UnZip API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef OSDAB_UNZIP_P__H +#define OSDAB_UNZIP_P__H + +#include "unzip.h" +#include "zipentry_p.h" + +#include + +// zLib authors suggest using larger buffers (128K or 256K) for (de)compression (especially for inflate()) +// we use a 256K buffer here - if you want to use this code on a pre-iceage mainframe please change it ;) +#define UNZIP_READ_BUFFER (256*1024) + +class UnzipPrivate +{ +public: + UnzipPrivate(); + + // Replace this with whatever else you use to store/retrieve the password. + QString password; + + bool skipAllEncrypted; + + QMap* headers; + + QIODevice* device; + + char buffer1[UNZIP_READ_BUFFER]; + char buffer2[UNZIP_READ_BUFFER]; + + unsigned char* uBuffer; + const quint32* crcTable; + + // Central Directory (CD) offset + quint32 cdOffset; + // End of Central Directory (EOCD) offset + quint32 eocdOffset; + + // Number of entries in the Central Directory (as to the EOCD record) + quint16 cdEntryCount; + + // The number of detected entries that have been skipped because of a non compatible format + quint16 unsupportedEntryCount; + + QString comment; + + UnZip::ErrorCode openArchive(QIODevice* device); + + UnZip::ErrorCode seekToCentralDirectory(); + UnZip::ErrorCode parseCentralDirectoryRecord(); + UnZip::ErrorCode parseLocalHeaderRecord(const QString& path, ZipEntryP& entry); + + void closeArchive(); + + UnZip::ErrorCode extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options); + UnZip::ErrorCode extractFile(const QString& path, ZipEntryP& entry, QIODevice* device, UnZip::ExtractionOptions options); + + UnZip::ErrorCode testPassword(quint32* keys, const QString& file, const ZipEntryP& header); + bool testKeys(const ZipEntryP& header, quint32* keys); + + bool createDirectory(const QString& path); + + inline void decryptBytes(quint32* keys, char* buffer, qint64 read); + + inline quint32 getULong(const unsigned char* data, quint32 offset) const; + inline quint64 getULLong(const unsigned char* data, quint32 offset) const; + inline quint16 getUShort(const unsigned char* data, quint32 offset) const; + inline int decryptByte(quint32 key2) const; + inline void updateKeys(quint32* keys, int c) const; + inline void initKeys(const QString& pwd, quint32* keys) const; + + inline QDateTime convertDateTime(const unsigned char date[2], const unsigned char time[2]) const; +}; + +#endif // OSDAB_UNZIP_P__H diff --git a/rbutil/rbutilqt/zip/zip.cpp b/rbutil/rbutilqt/zip/zip.cpp index ac1eaaea0b..6732278fe6 100644 --- a/rbutil/rbutilqt/zip/zip.cpp +++ b/rbutil/rbutilqt/zip/zip.cpp @@ -1,1221 +1,1221 @@ -/**************************************************************************** -** Filename: zip.cpp -** Last updated [dd/mm/yyyy]: 01/02/2007 -** -** pkzip 2.0 file compression. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -#include "zip.h" -#include "zip_p.h" -#include "zipentry_p.h" - -// we only use this to seed the random number generator -#include - -#include -#include -#include -#include -#include -#include -#include - -// You can remove this #include if you replace the qDebug() statements. -#include - -//! Local header size (including signature, excluding variable length fields) -#define ZIP_LOCAL_HEADER_SIZE 30 -//! Encryption header size -#define ZIP_LOCAL_ENC_HEADER_SIZE 12 -//! Data descriptor size (signature included) -#define ZIP_DD_SIZE_WS 16 -//! Central Directory record size (signature included) -#define ZIP_CD_SIZE 46 -//! End of Central Directory record size (signature included) -#define ZIP_EOCD_SIZE 22 - -// Some offsets inside a local header record (signature included) -#define ZIP_LH_OFF_VERS 4 -#define ZIP_LH_OFF_GPFLAG 6 -#define ZIP_LH_OFF_CMET 8 -#define ZIP_LH_OFF_MODT 10 -#define ZIP_LH_OFF_MODD 12 -#define ZIP_LH_OFF_CRC 14 -#define ZIP_LH_OFF_CSIZE 18 -#define ZIP_LH_OFF_USIZE 22 -#define ZIP_LH_OFF_NAMELEN 26 -#define ZIP_LH_OFF_XLEN 28 - -// Some offsets inside a data descriptor record (including signature) -#define ZIP_DD_OFF_CRC32 4 -#define ZIP_DD_OFF_CSIZE 8 -#define ZIP_DD_OFF_USIZE 12 - -// Some offsets inside a Central Directory record (including signature) -#define ZIP_CD_OFF_MADEBY 4 -#define ZIP_CD_OFF_VERSION 6 -#define ZIP_CD_OFF_GPFLAG 8 -#define ZIP_CD_OFF_CMET 10 -#define ZIP_CD_OFF_MODT 12 -#define ZIP_CD_OFF_MODD 14 -#define ZIP_CD_OFF_CRC 16 -#define ZIP_CD_OFF_CSIZE 20 -#define ZIP_CD_OFF_USIZE 24 -#define ZIP_CD_OFF_NAMELEN 28 -#define ZIP_CD_OFF_XLEN 30 -#define ZIP_CD_OFF_COMMLEN 32 -#define ZIP_CD_OFF_DISKSTART 34 -#define ZIP_CD_OFF_IATTR 36 -#define ZIP_CD_OFF_EATTR 38 -#define ZIP_CD_OFF_LHOFF 42 - -// Some offsets inside a EOCD record (including signature) -#define ZIP_EOCD_OFF_DISKNUM 4 -#define ZIP_EOCD_OFF_CDDISKNUM 6 -#define ZIP_EOCD_OFF_ENTRIES 8 -#define ZIP_EOCD_OFF_CDENTRIES 10 -#define ZIP_EOCD_OFF_CDSIZE 12 -#define ZIP_EOCD_OFF_CDOFF 16 -#define ZIP_EOCD_OFF_COMMLEN 20 - -//! PKZip version for archives created by this API -#define ZIP_VERSION 0x14 - -//! Do not store very small files as the compression headers overhead would be to big -#define ZIP_COMPRESSION_THRESHOLD 60 - -//! This macro updates a one-char-only CRC; it's the Info-Zip macro re-adapted -#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) - -/*! - \class Zip zip.h - - \brief Zip file compression. - - Some quick usage examples. - - \verbatim - Suppose you have this directory structure: - - /root/dir1/ - /root/dir1/file1.1 - /root/dir1/file1.2 - /root/dir1/dir1.1/ - /root/dir1/dir1.2/file1.2.1 - - EXAMPLE 1: - myZipInstance.addDirectory("/root/dir1"); - - RESULT: - Beheaves like any common zip software and creates a zip file with this structure: - - dir1/ - dir1/file1.1 - dir1/file1.2 - dir1/dir1.1/ - dir1/dir1.2/file1.2.1 - - EXAMPLE 2: - myZipInstance.addDirectory("/root/dir1", "myRoot/myFolder"); - - RESULT: - Adds a custom root to the paths and creates a zip file with this structure: - - myRoot/myFolder/dir1/ - myRoot/myFolder/dir1/file1.1 - myRoot/myFolder/dir1/file1.2 - myRoot/myFolder/dir1/dir1.1/ - myRoot/myFolder/dir1/dir1.2/file1.2.1 - - EXAMPLE 3: - myZipInstance.addDirectory("/root/dir1", Zip::AbsolutePaths); - - NOTE: - Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH). - - RESULT: - Preserves absolute paths and creates a zip file with this structure: - - /root/dir1/ - /root/dir1/file1.1 - /root/dir1/file1.2 - /root/dir1/dir1.1/ - /root/dir1/dir1.2/file1.2.1 - - EXAMPLE 4: - myZipInstance.setPassword("hellopass"); - myZipInstance.addDirectory("/root/dir1", "/"); - - RESULT: - Adds and encrypts the files in /root/dir1, creating the following zip structure: - - /dir1/ - /dir1/file1.1 - /dir1/file1.2 - /dir1/dir1.1/ - /dir1/dir1.2/file1.2.1 - - \endverbatim -*/ - -/*! \enum Zip::ErrorCode The result of a compression operation. - \value Zip::Ok No error occurred. - \value Zip::ZlibInit Failed to init or load the zlib library. - \value Zip::ZlibError The zlib library returned some error. - \value Zip::FileExists The file already exists and will not be overwritten. - \value Zip::OpenFailed Unable to create or open a device. - \value Zip::NoOpenArchive CreateArchive() has not been called yet. - \value Zip::FileNotFound File or directory does not exist. - \value Zip::ReadFailed Reading of a file failed. - \value Zip::WriteFailed Writing of a file failed. - \value Zip::SeekFailed Seek failed. -*/ - -/*! \enum Zip::CompressionLevel Returns the result of a decompression operation. - \value Zip::Store No compression. - \value Zip::Deflate1 Deflate compression level 1(lowest compression). - \value Zip::Deflate1 Deflate compression level 2. - \value Zip::Deflate1 Deflate compression level 3. - \value Zip::Deflate1 Deflate compression level 4. - \value Zip::Deflate1 Deflate compression level 5. - \value Zip::Deflate1 Deflate compression level 6. - \value Zip::Deflate1 Deflate compression level 7. - \value Zip::Deflate1 Deflate compression level 8. - \value Zip::Deflate1 Deflate compression level 9 (maximum compression). - \value Zip::AutoCPU Adapt compression level to CPU speed (faster CPU => better compression). - \value Zip::AutoMIME Adapt compression level to MIME type of the file being compressed. - \value Zip::AutoFull Use both CPU and MIME type detection. -*/ - - -/************************************************************************ - Public interface -*************************************************************************/ - -/*! - Creates a new Zip file compressor. -*/ -Zip::Zip() -{ - d = new ZipPrivate; -} - -/*! - Closes any open archive and releases used resources. -*/ -Zip::~Zip() -{ - closeArchive(); - delete d; -} - -/*! - Returns true if there is an open archive. -*/ -bool Zip::isOpen() const -{ - return d->device != 0; -} - -/*! - Sets the password to be used for the next files being added! - Files added before calling this method will use the previously - set password (if any). - Closing the archive won't clear the password! -*/ -void Zip::setPassword(const QString& pwd) -{ - d->password = pwd; -} - -//! Convenience method, clears the current password. -void Zip::clearPassword() -{ - d->password.clear(); -} - -//! Returns the currently used password. -QString Zip::password() const -{ - return d->password; -} - -/*! - Attempts to create a new Zip archive. If \p overwrite is true and the file - already exist it will be overwritten. - Any open archive will be closed. - */ -Zip::ErrorCode Zip::createArchive(const QString& filename, bool overwrite) -{ - QFile* file = new QFile(filename); - - if (file->exists() && !overwrite) { - delete file; - return Zip::FileExists; - } - - if (!file->open(QIODevice::WriteOnly)) { - delete file; - return Zip::OpenFailed; - } - - Zip::ErrorCode ec = createArchive(file); - if (ec != Zip::Ok) { - file->remove(); - } - - return ec; -} - -/*! - Attempts to create a new Zip archive. If there is another open archive this will be closed. - \warning The class takes ownership of the device! - */ -Zip::ErrorCode Zip::createArchive(QIODevice* device) -{ - if (device == 0) - { - qDebug() << "Invalid device."; - return Zip::OpenFailed; - } - - return d->createArchive(device); -} - -/*! - Returns the current archive comment. -*/ -QString Zip::archiveComment() const -{ - return d->comment; -} - -/*! - Sets the comment for this archive. Note: createArchive() should have been - called before. -*/ -void Zip::setArchiveComment(const QString& comment) -{ - if (d->device != 0) - d->comment = comment; -} - -/*! - Convenience method, same as calling - Zip::addDirectory(const QString&,const QString&,CompressionLevel) - with an empty \p root parameter (or with the parent directory of \p path if the - AbsolutePaths options is set). - - The ExtractionOptions are checked in the order they are defined in the zip.h heaser file. - This means that the last one overwrites the previous one (if some conflict occurs), i.e. - Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths. - */ -Zip::ErrorCode Zip::addDirectory(const QString& path, CompressionOptions options, CompressionLevel level) -{ - return addDirectory(path, QString(), options, level); -} - -/*! - Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) - with the Zip::RelativePaths flag as compression option. - */ -Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionLevel level) -{ - return addDirectory(path, root, Zip::RelativePaths, level); -} - -/*! - Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) - with the Zip::IgnorePaths flag as compression option and an empty \p root parameter. -*/ -Zip::ErrorCode Zip::addDirectoryContents(const QString& path, CompressionLevel level) -{ - return addDirectory(path, QString(), IgnorePaths, level); -} - -/*! - Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) - with the Zip::IgnorePaths flag as compression option. -*/ -Zip::ErrorCode Zip::addDirectoryContents(const QString& path, const QString& root, CompressionLevel level) -{ - return addDirectory(path, root, IgnorePaths, level); -} - -/*! - Recursively adds files contained in \p dir to the archive, using \p root as name for the root folder. - Stops adding files if some error occurs. - - The ExtractionOptions are checked in the order they are defined in the zip.h heaser file. - This means that the last one overwrites the previous one (if some conflict occurs), i.e. - Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths. - - The \p root parameter is ignored with the Zip::IgnorePaths parameter and used as path prefix (a trailing / - is always added as directory separator!) otherwise (even with Zip::AbsolutePaths set!). -*/ -Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionOptions options, CompressionLevel level) -{ - // qDebug() << QString("addDir(path=%1, root=%2)").arg(path, root); - - // Bad boy didn't call createArchive() yet :) - if (d->device == 0) - return Zip::NoOpenArchive; - - QDir dir(path); - if (!dir.exists()) - return Zip::FileNotFound; - - // Remove any trailing separator - QString actualRoot = root.trimmed(); - - // Preserve Unix root - if (actualRoot != "/") - { - while (actualRoot.endsWith("/") || actualRoot.endsWith("\\")) - actualRoot.truncate(actualRoot.length() - 1); - } - - // QDir::cleanPath() fixes some issues with QDir::dirName() - QFileInfo current(QDir::cleanPath(path)); - - if (!actualRoot.isEmpty() && actualRoot != "/") - actualRoot.append("/"); - - /* This part is quite confusing and needs some test or check */ - /* An attempt to compress the / root directory evtl. using a root prefix should be a good test */ - if (options.testFlag(AbsolutePaths) && !options.testFlag(IgnorePaths)) - { - QString absolutePath = d->extractRoot(path); - if (!absolutePath.isEmpty() && absolutePath != "/") - absolutePath.append("/"); - actualRoot.append(absolutePath); - } - - if (!options.testFlag(IgnorePaths)) - { - actualRoot = actualRoot.append(QDir(current.absoluteFilePath()).dirName()); - actualRoot.append("/"); - } - - // actualRoot now contains the path of the file relative to the zip archive - // with a trailing / - - QFileInfoList list = dir.entryInfoList( - QDir::Files | - QDir::Dirs | - QDir::NoDotAndDotDot | - QDir::NoSymLinks); - - ErrorCode ec = Zip::Ok; - bool filesAdded = false; - - CompressionOptions recursionOptions; - if (options.testFlag(IgnorePaths)) - recursionOptions |= IgnorePaths; - else recursionOptions |= RelativePaths; - - for (int i = 0; i < list.size() && ec == Zip::Ok; ++i) - { - QFileInfo info = list.at(i); - - if (info.isDir()) - { - // Recursion :) - progress(); - ec = addDirectory(info.absoluteFilePath(), actualRoot, recursionOptions, level); - } - else - { - progress(); - ec = d->createEntry(info, actualRoot, level); - filesAdded = true; - } - } - - - // We need an explicit record for this dir - // Non-empty directories don't need it because they have a path component in the filename - if (!filesAdded && !options.testFlag(IgnorePaths)) - ec = d->createEntry(current, actualRoot, level); - - return ec; -} - -/*! - Closes the archive and writes any pending data. -*/ -Zip::ErrorCode Zip::closeArchive() -{ - Zip::ErrorCode ec = d->closeArchive(); - d->reset(); - return ec; -} - -/*! - Returns a locale translated error string for a given error code. -*/ -QString Zip::formatError(Zip::ErrorCode c) const -{ - switch (c) - { - case Ok: return QCoreApplication::translate("Zip", "ZIP operation completed successfully."); break; - case ZlibInit: return QCoreApplication::translate("Zip", "Failed to initialize or load zlib library."); break; - case ZlibError: return QCoreApplication::translate("Zip", "zlib library error."); break; - case OpenFailed: return QCoreApplication::translate("Zip", "Unable to create or open file."); break; - case NoOpenArchive: return QCoreApplication::translate("Zip", "No archive has been created yet."); break; - case FileNotFound: return QCoreApplication::translate("Zip", "File or directory does not exist."); break; - case ReadFailed: return QCoreApplication::translate("Zip", "File read error."); break; - case WriteFailed: return QCoreApplication::translate("Zip", "File write error."); break; - case SeekFailed: return QCoreApplication::translate("Zip", "File seek error."); break; - default: ; - } - - return QCoreApplication::translate("Zip", "Unknown error."); -} - - -/************************************************************************ - Private interface -*************************************************************************/ - -//! \internal -ZipPrivate::ZipPrivate() -{ - headers = 0; - device = 0; - - // keep an unsigned pointer so we avoid to over bloat the code with casts - uBuffer = (unsigned char*) buffer1; - crcTable = (quint32*) get_crc_table(); -} - -//! \internal -ZipPrivate::~ZipPrivate() -{ - closeArchive(); -} - -//! \internal -Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev) -{ - Q_ASSERT(dev != 0); - - if (device != 0) - closeArchive(); - - device = dev; - - if (!device->isOpen()) - { - if (!device->open(QIODevice::ReadOnly)) { - delete device; - device = 0; - qDebug() << "Unable to open device for writing."; - return Zip::OpenFailed; - } - } - - headers = new QMap; - return Zip::Ok; -} - -//! \internal Writes a new entry in the zip file. -Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level) -{ - //! \todo Automatic level detection (cpu, extension & file size) - - // Directories and very small files are always stored - // (small files would get bigger due to the compression headers overhead) - - // Need this for zlib - bool isPNGFile = false; - bool dirOnly = file.isDir(); - - QString entryName = root; - - // Directory entry - if (dirOnly) - level = Zip::Store; - else - { - entryName.append(file.fileName()); - - QString ext = file.completeSuffix().toLower(); - isPNGFile = ext == "png"; - - if (file.size() < ZIP_COMPRESSION_THRESHOLD) - level = Zip::Store; - else - switch (level) - { - case Zip::AutoCPU: - level = Zip::Deflate5; - break; - case Zip::AutoMIME: - level = detectCompressionByMime(ext); - break; - case Zip::AutoFull: - level = detectCompressionByMime(ext); - break; - default: - ; - } - } - - // entryName contains the path as it should be written - // in the zip file records - // qDebug() << QString("addDir(file=%1, root=%2, entry=%3)").arg(file.absoluteFilePath(), root, entryName); - - // create header and store it to write a central directory later - ZipEntryP* h = new ZipEntryP; - - h->compMethod = (level == Zip::Store) ? 0 : 0x0008; - - // Set encryption bit and set the data descriptor bit - // so we can use mod time instead of crc for password check - bool encrypt = !dirOnly && !password.isEmpty(); - if (encrypt) - h->gpFlag[0] |= 9; - - QDateTime dt = file.lastModified(); - QDate d = dt.date(); - h->modDate[1] = ((d.year() - 1980) << 1) & 254; - h->modDate[1] |= ((d.month() >> 3) & 1); - h->modDate[0] = ((d.month() & 7) << 5) & 224; - h->modDate[0] |= d.day(); - - QTime t = dt.time(); - h->modTime[1] = (t.hour() << 3) & 248; - h->modTime[1] |= ((t.minute() >> 3) & 7); - h->modTime[0] = ((t.minute() & 7) << 5) & 224; - h->modTime[0] |= t.second() / 2; - - h->szUncomp = dirOnly ? 0 : file.size(); - - // **** Write local file header **** - - // signature - buffer1[0] = 'P'; buffer1[1] = 'K'; - buffer1[2] = 0x3; buffer1[3] = 0x4; - - // version needed to extract - buffer1[ZIP_LH_OFF_VERS] = ZIP_VERSION; - buffer1[ZIP_LH_OFF_VERS + 1] = 0; - - // general purpose flag - buffer1[ZIP_LH_OFF_GPFLAG] = h->gpFlag[0]; - buffer1[ZIP_LH_OFF_GPFLAG + 1] = h->gpFlag[1]; - - // compression method - buffer1[ZIP_LH_OFF_CMET] = h->compMethod & 0xFF; - buffer1[ZIP_LH_OFF_CMET + 1] = (h->compMethod>>8) & 0xFF; - - // last mod file time - buffer1[ZIP_LH_OFF_MODT] = h->modTime[0]; - buffer1[ZIP_LH_OFF_MODT + 1] = h->modTime[1]; - - // last mod file date - buffer1[ZIP_LH_OFF_MODD] = h->modDate[0]; - buffer1[ZIP_LH_OFF_MODD + 1] = h->modDate[1]; - - // skip crc (4bytes) [14,15,16,17] - - // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21]) - buffer1[ZIP_LH_OFF_CSIZE] = - buffer1[ZIP_LH_OFF_CSIZE + 1] = - buffer1[ZIP_LH_OFF_CSIZE + 2] = - buffer1[ZIP_LH_OFF_CSIZE + 3] = 0; - - h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0; - - // uncompressed size [22,23,24,25] - setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE); - - // filename length - QByteArray entryNameBytes = entryName.toAscii(); - int sz = entryNameBytes.size(); - - buffer1[ZIP_LH_OFF_NAMELEN] = sz & 0xFF; - buffer1[ZIP_LH_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF; - - // extra field length - buffer1[ZIP_LH_OFF_XLEN] = buffer1[ZIP_LH_OFF_XLEN + 1] = 0; - - // Store offset to write crc and compressed size - h->lhOffset = device->pos(); - quint32 crcOffset = h->lhOffset + ZIP_LH_OFF_CRC; - - if (device->write(buffer1, ZIP_LOCAL_HEADER_SIZE) != ZIP_LOCAL_HEADER_SIZE) - { - delete h; - return Zip::WriteFailed; - } - - // Write out filename - if (device->write(entryNameBytes) != sz) - { - delete h; - return Zip::WriteFailed; - } - - // Encryption keys - quint32 keys[3] = { 0, 0, 0 }; - - if (encrypt) - { - // **** encryption header **** - - // XOR with PI to ensure better random numbers - // with poorly implemented rand() as suggested by Info-Zip - srand(time(NULL) ^ 3141592654UL); - int randByte; - - initKeys(keys); - for (int i=0; i<10; ++i) - { - randByte = (rand() >> 7) & 0xff; - buffer1[i] = decryptByte(keys[2]) ^ randByte; - updateKeys(keys, randByte); - } - - // Encrypt encryption header - initKeys(keys); - for (int i=0; i<10; ++i) - { - randByte = decryptByte(keys[2]); - updateKeys(keys, buffer1[i]); - buffer1[i] ^= randByte; - } - - // We don't know the CRC at this time, so we use the modification time - // as the last two bytes - randByte = decryptByte(keys[2]); - updateKeys(keys, h->modTime[0]); - buffer1[10] ^= randByte; - - randByte = decryptByte(keys[2]); - updateKeys(keys, h->modTime[1]); - buffer1[11] ^= randByte; - - // Write out encryption header - if (device->write(buffer1, ZIP_LOCAL_ENC_HEADER_SIZE) != ZIP_LOCAL_ENC_HEADER_SIZE) - { - delete h; - return Zip::WriteFailed; - } - } - - qint64 written = 0; - quint32 crc = crc32(0L, Z_NULL, 0); - - if (!dirOnly) - { - QFile actualFile(file.absoluteFilePath()); - if (!actualFile.open(QIODevice::ReadOnly)) - { - qDebug() << QString("An error occurred while opening %1").arg(file.absoluteFilePath()); - return Zip::OpenFailed; - } - - // Write file data - qint64 read = 0; - qint64 totRead = 0; - qint64 toRead = actualFile.size(); - - if (level == Zip::Store) - { - while ( (read = actualFile.read(buffer1, ZIP_READ_BUFFER)) > 0 ) - { - crc = crc32(crc, uBuffer, read); - - if (password != 0) - encryptBytes(keys, buffer1, read); - - if ( (written = device->write(buffer1, read)) != read ) - { - actualFile.close(); - delete h; - return Zip::WriteFailed; - } - } - } - else - { - z_stream zstr; - - // Initialize zalloc, zfree and opaque before calling the init function - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; - - int zret; - - // Use deflateInit2 with negative windowBits to get raw compression - if ((zret = deflateInit2_( - &zstr, - (int)level, - Z_DEFLATED, - -MAX_WBITS, - 8, - isPNGFile ? Z_RLE : Z_DEFAULT_STRATEGY, - ZLIB_VERSION, - sizeof(z_stream) - )) != Z_OK ) - { - actualFile.close(); - qDebug() << "Could not initialize zlib for compression"; - delete h; - return Zip::ZlibError; - } - - qint64 compressed; - - int flush = Z_NO_FLUSH; - - do - { - read = actualFile.read(buffer1, ZIP_READ_BUFFER); - totRead += read; - - if (read == 0) - break; - if (read < 0) - { - actualFile.close(); - deflateEnd(&zstr); - qDebug() << QString("Error while reading %1").arg(file.absoluteFilePath()); - delete h; - return Zip::ReadFailed; - } - - crc = crc32(crc, uBuffer, read); - - zstr.next_in = (Bytef*) buffer1; - zstr.avail_in = (uInt)read; - - // Tell zlib if this is the last chunk we want to encode - // by setting the flush parameter to Z_FINISH - flush = (totRead == toRead) ? Z_FINISH : Z_NO_FLUSH; - - // Run deflate() on input until output buffer not full - // finish compression if all of source has been read in - do - { - zstr.next_out = (Bytef*) buffer2; - zstr.avail_out = ZIP_READ_BUFFER; - - zret = deflate(&zstr, flush); - // State not clobbered - Q_ASSERT(zret != Z_STREAM_ERROR); - - // Write compressed data to file and empty buffer - compressed = ZIP_READ_BUFFER - zstr.avail_out; - - if (password != 0) - encryptBytes(keys, buffer2, compressed); - - if (device->write(buffer2, compressed) != compressed) - { - deflateEnd(&zstr); - actualFile.close(); - qDebug() << QString("Error while writing %1").arg(file.absoluteFilePath()); - delete h; - return Zip::WriteFailed; - } - - written += compressed; - - } while (zstr.avail_out == 0); - - // All input will be used - Q_ASSERT(zstr.avail_in == 0); - - } while (flush != Z_FINISH); - - // Stream will be complete - Q_ASSERT(zret == Z_STREAM_END); - - deflateEnd(&zstr); - - } // if (level != STORE) - - actualFile.close(); - } - - // Store end of entry offset - quint32 current = device->pos(); - - // Update crc and compressed size in local header - if (!device->seek(crcOffset)) - { - delete h; - return Zip::SeekFailed; - } - - h->crc = dirOnly ? 0 : crc; - h->szComp += written; - - setULong(h->crc, buffer1, 0); - setULong(h->szComp, buffer1, 4); - if ( device->write(buffer1, 8) != 8) - { - delete h; - return Zip::WriteFailed; - } - - // Seek to end of entry - if (!device->seek(current)) - { - delete h; - return Zip::SeekFailed; - } - - if ((h->gpFlag[0] & 8) == 8) - { - // Write data descriptor - - // Signature: PK\7\8 - buffer1[0] = 'P'; - buffer1[1] = 'K'; - buffer1[2] = 0x07; - buffer1[3] = 0x08; - - // CRC - setULong(h->crc, buffer1, ZIP_DD_OFF_CRC32); - - // Compressed size - setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE); - - // Uncompressed size - setULong(h->szUncomp, buffer1, ZIP_DD_OFF_USIZE); - - if (device->write(buffer1, ZIP_DD_SIZE_WS) != ZIP_DD_SIZE_WS) - { - delete h; - return Zip::WriteFailed; - } - } - - headers->insert(entryName, h); - return Zip::Ok; -} - -//! \internal -int ZipPrivate::decryptByte(quint32 key2) const -{ - quint16 temp = ((quint16)(key2) & 0xffff) | 2; - return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); -} - -//! \internal Writes an quint32 (4 bytes) to a byte array at given offset. -void ZipPrivate::setULong(quint32 v, char* buffer, unsigned int offset) -{ - buffer[offset+3] = ((v >> 24) & 0xFF); - buffer[offset+2] = ((v >> 16) & 0xFF); - buffer[offset+1] = ((v >> 8) & 0xFF); - buffer[offset] = (v & 0xFF); -} - -//! \internal Initializes decryption keys using a password. -void ZipPrivate::initKeys(quint32* keys) const -{ - // Encryption keys initialization constants are taken from the - // PKZip file format specification docs - keys[0] = 305419896L; - keys[1] = 591751049L; - keys[2] = 878082192L; - - QByteArray pwdBytes = password.toAscii(); - int sz = pwdBytes.size(); - const char* ascii = pwdBytes.data(); - - for (int i=0; i> 24); -} - -//! \internal Encrypts a byte array. -void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read) -{ - char t; - - for (int i=0; i<(int)read; ++i) - { - t = buffer[i]; - buffer[i] ^= decryptByte(keys[2]); - updateKeys(keys, t); - } -} - -//! \internal Detects the best compression level for a given file extension. -Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext) -{ - // files really hard to compress - if ((ext == "png") || - (ext == "jpg") || - (ext == "jpeg") || - (ext == "mp3") || - (ext == "ogg") || - (ext == "ogm") || - (ext == "avi") || - (ext == "mov") || - (ext == "rm") || - (ext == "ra") || - (ext == "zip") || - (ext == "rar") || - (ext == "bz2") || - (ext == "gz") || - (ext == "7z") || - (ext == "z") || - (ext == "jar") - ) return Zip::Store; - - // files slow and hard to compress - if ((ext == "exe") || - (ext == "bin") || - (ext == "rpm") || - (ext == "deb") - ) return Zip::Deflate2; - - return Zip::Deflate9; -} - -/*! - Closes the current archive and writes out pending data. -*/ -Zip::ErrorCode ZipPrivate::closeArchive() -{ - // Close current archive by writing out central directory - // and free up resources - - if (device == 0) - return Zip::Ok; - - if (headers == 0) - return Zip::Ok; - - const ZipEntryP* h; - - unsigned int sz; - quint32 szCentralDir = 0; - quint32 offCentralDir = device->pos(); - - for (QMap::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr) - { - h = itr.value(); - - // signature - buffer1[0] = 'P'; - buffer1[1] = 'K'; - buffer1[2] = 0x01; - buffer1[3] = 0x02; - - // version made by (currently only MS-DOS/FAT - no symlinks or other stuff supported) - buffer1[ZIP_CD_OFF_MADEBY] = buffer1[ZIP_CD_OFF_MADEBY + 1] = 0; - - // version needed to extract - buffer1[ZIP_CD_OFF_VERSION] = ZIP_VERSION; - buffer1[ZIP_CD_OFF_VERSION + 1] = 0; - - // general purpose flag - buffer1[ZIP_CD_OFF_GPFLAG] = h->gpFlag[0]; - buffer1[ZIP_CD_OFF_GPFLAG + 1] = h->gpFlag[1]; - - // compression method - buffer1[ZIP_CD_OFF_CMET] = h->compMethod & 0xFF; - buffer1[ZIP_CD_OFF_CMET + 1] = (h->compMethod >> 8) & 0xFF; - - // last mod file time - buffer1[ZIP_CD_OFF_MODT] = h->modTime[0]; - buffer1[ZIP_CD_OFF_MODT + 1] = h->modTime[1]; - - // last mod file date - buffer1[ZIP_CD_OFF_MODD] = h->modDate[0]; - buffer1[ZIP_CD_OFF_MODD + 1] = h->modDate[1]; - - // crc (4bytes) [16,17,18,19] - setULong(h->crc, buffer1, ZIP_CD_OFF_CRC); - - // compressed size (4bytes: [20,21,22,23]) - setULong(h->szComp, buffer1, ZIP_CD_OFF_CSIZE); - - // uncompressed size [24,25,26,27] - setULong(h->szUncomp, buffer1, ZIP_CD_OFF_USIZE); - - // filename - QByteArray fileNameBytes = itr.key().toAscii(); - sz = fileNameBytes.size(); - buffer1[ZIP_CD_OFF_NAMELEN] = sz & 0xFF; - buffer1[ZIP_CD_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF; - - // extra field length - buffer1[ZIP_CD_OFF_XLEN] = buffer1[ZIP_CD_OFF_XLEN + 1] = 0; - - // file comment length - buffer1[ZIP_CD_OFF_COMMLEN] = buffer1[ZIP_CD_OFF_COMMLEN + 1] = 0; - - // disk number start - buffer1[ZIP_CD_OFF_DISKSTART] = buffer1[ZIP_CD_OFF_DISKSTART + 1] = 0; - - // internal file attributes - buffer1[ZIP_CD_OFF_IATTR] = buffer1[ZIP_CD_OFF_IATTR + 1] = 0; - - // external file attributes - buffer1[ZIP_CD_OFF_EATTR] = - buffer1[ZIP_CD_OFF_EATTR + 1] = - buffer1[ZIP_CD_OFF_EATTR + 2] = - buffer1[ZIP_CD_OFF_EATTR + 3] = 0; - - // relative offset of local header [42->45] - setULong(h->lhOffset, buffer1, ZIP_CD_OFF_LHOFF); - - if (device->write(buffer1, ZIP_CD_SIZE) != ZIP_CD_SIZE) - { - //! \todo See if we can detect QFile objects using the Qt Meta Object System - /* - if (!device->remove()) - qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); - */ - return Zip::WriteFailed; - } - - // Write out filename - if ((unsigned int)device->write(fileNameBytes) != sz) - { - //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System - /* - if (!device->remove()) - qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); - */ - return Zip::WriteFailed; - } - - szCentralDir += (ZIP_CD_SIZE + sz); - - } // central dir headers loop - - - // Write end of central directory - - // signature - buffer1[0] = 'P'; - buffer1[1] = 'K'; - buffer1[2] = 0x05; - buffer1[3] = 0x06; - - // number of this disk - buffer1[ZIP_EOCD_OFF_DISKNUM] = buffer1[ZIP_EOCD_OFF_DISKNUM + 1] = 0; - - // number of disk with central directory - buffer1[ZIP_EOCD_OFF_CDDISKNUM] = buffer1[ZIP_EOCD_OFF_CDDISKNUM + 1] = 0; - - // number of entries in this disk - sz = headers->count(); - buffer1[ZIP_EOCD_OFF_ENTRIES] = sz & 0xFF; - buffer1[ZIP_EOCD_OFF_ENTRIES + 1] = (sz >> 8) & 0xFF; - - // total number of entries - buffer1[ZIP_EOCD_OFF_CDENTRIES] = buffer1[ZIP_EOCD_OFF_ENTRIES]; - buffer1[ZIP_EOCD_OFF_CDENTRIES + 1] = buffer1[ZIP_EOCD_OFF_ENTRIES + 1]; - - // size of central directory [12->15] - setULong(szCentralDir, buffer1, ZIP_EOCD_OFF_CDSIZE); - - // central dir offset [16->19] - setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF); - - // ZIP file comment length - QByteArray commentBytes = comment.toAscii(); - quint16 commentLength = commentBytes.size(); - - if (commentLength == 0) - { - buffer1[ZIP_EOCD_OFF_COMMLEN] = buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = 0; - } - else - { - buffer1[ZIP_EOCD_OFF_COMMLEN] = commentLength & 0xFF; - buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = (commentLength >> 8) & 0xFF; - } - - if (device->write(buffer1, ZIP_EOCD_SIZE) != ZIP_EOCD_SIZE) - { - //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System - /* - if (!device->remove()) - qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); - */ - return Zip::WriteFailed; - } - - if (commentLength != 0) - { - if ((unsigned int)device->write(commentBytes) != commentLength) - { - //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System - /* - if (!device->remove()) - qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); - */ - return Zip::WriteFailed; - } - } - - return Zip::Ok; -} - -//! \internal -void ZipPrivate::reset() -{ - comment.clear(); - - if (headers != 0) - { - qDeleteAll(*headers); - delete headers; - headers = 0; - } - - delete device; device = 0; -} - -//! \internal Returns the path of the parent directory -QString ZipPrivate::extractRoot(const QString& p) -{ - QDir d(QDir::cleanPath(p)); - if (!d.exists()) - return QString(); - - if (!d.cdUp()) - return QString(); - - return d.absolutePath(); -} +/**************************************************************************** +** Filename: zip.cpp +** Last updated [dd/mm/yyyy]: 01/02/2007 +** +** pkzip 2.0 file compression. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +#include "zip.h" +#include "zip_p.h" +#include "zipentry_p.h" + +// we only use this to seed the random number generator +#include + +#include +#include +#include +#include +#include +#include +#include + +// You can remove this #include if you replace the qDebug() statements. +#include + +//! Local header size (including signature, excluding variable length fields) +#define ZIP_LOCAL_HEADER_SIZE 30 +//! Encryption header size +#define ZIP_LOCAL_ENC_HEADER_SIZE 12 +//! Data descriptor size (signature included) +#define ZIP_DD_SIZE_WS 16 +//! Central Directory record size (signature included) +#define ZIP_CD_SIZE 46 +//! End of Central Directory record size (signature included) +#define ZIP_EOCD_SIZE 22 + +// Some offsets inside a local header record (signature included) +#define ZIP_LH_OFF_VERS 4 +#define ZIP_LH_OFF_GPFLAG 6 +#define ZIP_LH_OFF_CMET 8 +#define ZIP_LH_OFF_MODT 10 +#define ZIP_LH_OFF_MODD 12 +#define ZIP_LH_OFF_CRC 14 +#define ZIP_LH_OFF_CSIZE 18 +#define ZIP_LH_OFF_USIZE 22 +#define ZIP_LH_OFF_NAMELEN 26 +#define ZIP_LH_OFF_XLEN 28 + +// Some offsets inside a data descriptor record (including signature) +#define ZIP_DD_OFF_CRC32 4 +#define ZIP_DD_OFF_CSIZE 8 +#define ZIP_DD_OFF_USIZE 12 + +// Some offsets inside a Central Directory record (including signature) +#define ZIP_CD_OFF_MADEBY 4 +#define ZIP_CD_OFF_VERSION 6 +#define ZIP_CD_OFF_GPFLAG 8 +#define ZIP_CD_OFF_CMET 10 +#define ZIP_CD_OFF_MODT 12 +#define ZIP_CD_OFF_MODD 14 +#define ZIP_CD_OFF_CRC 16 +#define ZIP_CD_OFF_CSIZE 20 +#define ZIP_CD_OFF_USIZE 24 +#define ZIP_CD_OFF_NAMELEN 28 +#define ZIP_CD_OFF_XLEN 30 +#define ZIP_CD_OFF_COMMLEN 32 +#define ZIP_CD_OFF_DISKSTART 34 +#define ZIP_CD_OFF_IATTR 36 +#define ZIP_CD_OFF_EATTR 38 +#define ZIP_CD_OFF_LHOFF 42 + +// Some offsets inside a EOCD record (including signature) +#define ZIP_EOCD_OFF_DISKNUM 4 +#define ZIP_EOCD_OFF_CDDISKNUM 6 +#define ZIP_EOCD_OFF_ENTRIES 8 +#define ZIP_EOCD_OFF_CDENTRIES 10 +#define ZIP_EOCD_OFF_CDSIZE 12 +#define ZIP_EOCD_OFF_CDOFF 16 +#define ZIP_EOCD_OFF_COMMLEN 20 + +//! PKZip version for archives created by this API +#define ZIP_VERSION 0x14 + +//! Do not store very small files as the compression headers overhead would be to big +#define ZIP_COMPRESSION_THRESHOLD 60 + +//! This macro updates a one-char-only CRC; it's the Info-Zip macro re-adapted +#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) + +/*! + \class Zip zip.h + + \brief Zip file compression. + + Some quick usage examples. + + \verbatim + Suppose you have this directory structure: + + /root/dir1/ + /root/dir1/file1.1 + /root/dir1/file1.2 + /root/dir1/dir1.1/ + /root/dir1/dir1.2/file1.2.1 + + EXAMPLE 1: + myZipInstance.addDirectory("/root/dir1"); + + RESULT: + Beheaves like any common zip software and creates a zip file with this structure: + + dir1/ + dir1/file1.1 + dir1/file1.2 + dir1/dir1.1/ + dir1/dir1.2/file1.2.1 + + EXAMPLE 2: + myZipInstance.addDirectory("/root/dir1", "myRoot/myFolder"); + + RESULT: + Adds a custom root to the paths and creates a zip file with this structure: + + myRoot/myFolder/dir1/ + myRoot/myFolder/dir1/file1.1 + myRoot/myFolder/dir1/file1.2 + myRoot/myFolder/dir1/dir1.1/ + myRoot/myFolder/dir1/dir1.2/file1.2.1 + + EXAMPLE 3: + myZipInstance.addDirectory("/root/dir1", Zip::AbsolutePaths); + + NOTE: + Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH). + + RESULT: + Preserves absolute paths and creates a zip file with this structure: + + /root/dir1/ + /root/dir1/file1.1 + /root/dir1/file1.2 + /root/dir1/dir1.1/ + /root/dir1/dir1.2/file1.2.1 + + EXAMPLE 4: + myZipInstance.setPassword("hellopass"); + myZipInstance.addDirectory("/root/dir1", "/"); + + RESULT: + Adds and encrypts the files in /root/dir1, creating the following zip structure: + + /dir1/ + /dir1/file1.1 + /dir1/file1.2 + /dir1/dir1.1/ + /dir1/dir1.2/file1.2.1 + + \endverbatim +*/ + +/*! \enum Zip::ErrorCode The result of a compression operation. + \value Zip::Ok No error occurred. + \value Zip::ZlibInit Failed to init or load the zlib library. + \value Zip::ZlibError The zlib library returned some error. + \value Zip::FileExists The file already exists and will not be overwritten. + \value Zip::OpenFailed Unable to create or open a device. + \value Zip::NoOpenArchive CreateArchive() has not been called yet. + \value Zip::FileNotFound File or directory does not exist. + \value Zip::ReadFailed Reading of a file failed. + \value Zip::WriteFailed Writing of a file failed. + \value Zip::SeekFailed Seek failed. +*/ + +/*! \enum Zip::CompressionLevel Returns the result of a decompression operation. + \value Zip::Store No compression. + \value Zip::Deflate1 Deflate compression level 1(lowest compression). + \value Zip::Deflate1 Deflate compression level 2. + \value Zip::Deflate1 Deflate compression level 3. + \value Zip::Deflate1 Deflate compression level 4. + \value Zip::Deflate1 Deflate compression level 5. + \value Zip::Deflate1 Deflate compression level 6. + \value Zip::Deflate1 Deflate compression level 7. + \value Zip::Deflate1 Deflate compression level 8. + \value Zip::Deflate1 Deflate compression level 9 (maximum compression). + \value Zip::AutoCPU Adapt compression level to CPU speed (faster CPU => better compression). + \value Zip::AutoMIME Adapt compression level to MIME type of the file being compressed. + \value Zip::AutoFull Use both CPU and MIME type detection. +*/ + + +/************************************************************************ + Public interface +*************************************************************************/ + +/*! + Creates a new Zip file compressor. +*/ +Zip::Zip() +{ + d = new ZipPrivate; +} + +/*! + Closes any open archive and releases used resources. +*/ +Zip::~Zip() +{ + closeArchive(); + delete d; +} + +/*! + Returns true if there is an open archive. +*/ +bool Zip::isOpen() const +{ + return d->device != 0; +} + +/*! + Sets the password to be used for the next files being added! + Files added before calling this method will use the previously + set password (if any). + Closing the archive won't clear the password! +*/ +void Zip::setPassword(const QString& pwd) +{ + d->password = pwd; +} + +//! Convenience method, clears the current password. +void Zip::clearPassword() +{ + d->password.clear(); +} + +//! Returns the currently used password. +QString Zip::password() const +{ + return d->password; +} + +/*! + Attempts to create a new Zip archive. If \p overwrite is true and the file + already exist it will be overwritten. + Any open archive will be closed. + */ +Zip::ErrorCode Zip::createArchive(const QString& filename, bool overwrite) +{ + QFile* file = new QFile(filename); + + if (file->exists() && !overwrite) { + delete file; + return Zip::FileExists; + } + + if (!file->open(QIODevice::WriteOnly)) { + delete file; + return Zip::OpenFailed; + } + + Zip::ErrorCode ec = createArchive(file); + if (ec != Zip::Ok) { + file->remove(); + } + + return ec; +} + +/*! + Attempts to create a new Zip archive. If there is another open archive this will be closed. + \warning The class takes ownership of the device! + */ +Zip::ErrorCode Zip::createArchive(QIODevice* device) +{ + if (device == 0) + { + qDebug() << "Invalid device."; + return Zip::OpenFailed; + } + + return d->createArchive(device); +} + +/*! + Returns the current archive comment. +*/ +QString Zip::archiveComment() const +{ + return d->comment; +} + +/*! + Sets the comment for this archive. Note: createArchive() should have been + called before. +*/ +void Zip::setArchiveComment(const QString& comment) +{ + if (d->device != 0) + d->comment = comment; +} + +/*! + Convenience method, same as calling + Zip::addDirectory(const QString&,const QString&,CompressionLevel) + with an empty \p root parameter (or with the parent directory of \p path if the + AbsolutePaths options is set). + + The ExtractionOptions are checked in the order they are defined in the zip.h heaser file. + This means that the last one overwrites the previous one (if some conflict occurs), i.e. + Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths. + */ +Zip::ErrorCode Zip::addDirectory(const QString& path, CompressionOptions options, CompressionLevel level) +{ + return addDirectory(path, QString(), options, level); +} + +/*! + Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) + with the Zip::RelativePaths flag as compression option. + */ +Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionLevel level) +{ + return addDirectory(path, root, Zip::RelativePaths, level); +} + +/*! + Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) + with the Zip::IgnorePaths flag as compression option and an empty \p root parameter. +*/ +Zip::ErrorCode Zip::addDirectoryContents(const QString& path, CompressionLevel level) +{ + return addDirectory(path, QString(), IgnorePaths, level); +} + +/*! + Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) + with the Zip::IgnorePaths flag as compression option. +*/ +Zip::ErrorCode Zip::addDirectoryContents(const QString& path, const QString& root, CompressionLevel level) +{ + return addDirectory(path, root, IgnorePaths, level); +} + +/*! + Recursively adds files contained in \p dir to the archive, using \p root as name for the root folder. + Stops adding files if some error occurs. + + The ExtractionOptions are checked in the order they are defined in the zip.h heaser file. + This means that the last one overwrites the previous one (if some conflict occurs), i.e. + Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths. + + The \p root parameter is ignored with the Zip::IgnorePaths parameter and used as path prefix (a trailing / + is always added as directory separator!) otherwise (even with Zip::AbsolutePaths set!). +*/ +Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionOptions options, CompressionLevel level) +{ + // qDebug() << QString("addDir(path=%1, root=%2)").arg(path, root); + + // Bad boy didn't call createArchive() yet :) + if (d->device == 0) + return Zip::NoOpenArchive; + + QDir dir(path); + if (!dir.exists()) + return Zip::FileNotFound; + + // Remove any trailing separator + QString actualRoot = root.trimmed(); + + // Preserve Unix root + if (actualRoot != "/") + { + while (actualRoot.endsWith("/") || actualRoot.endsWith("\\")) + actualRoot.truncate(actualRoot.length() - 1); + } + + // QDir::cleanPath() fixes some issues with QDir::dirName() + QFileInfo current(QDir::cleanPath(path)); + + if (!actualRoot.isEmpty() && actualRoot != "/") + actualRoot.append("/"); + + /* This part is quite confusing and needs some test or check */ + /* An attempt to compress the / root directory evtl. using a root prefix should be a good test */ + if (options.testFlag(AbsolutePaths) && !options.testFlag(IgnorePaths)) + { + QString absolutePath = d->extractRoot(path); + if (!absolutePath.isEmpty() && absolutePath != "/") + absolutePath.append("/"); + actualRoot.append(absolutePath); + } + + if (!options.testFlag(IgnorePaths)) + { + actualRoot = actualRoot.append(QDir(current.absoluteFilePath()).dirName()); + actualRoot.append("/"); + } + + // actualRoot now contains the path of the file relative to the zip archive + // with a trailing / + + QFileInfoList list = dir.entryInfoList( + QDir::Files | + QDir::Dirs | + QDir::NoDotAndDotDot | + QDir::NoSymLinks); + + ErrorCode ec = Zip::Ok; + bool filesAdded = false; + + CompressionOptions recursionOptions; + if (options.testFlag(IgnorePaths)) + recursionOptions |= IgnorePaths; + else recursionOptions |= RelativePaths; + + for (int i = 0; i < list.size() && ec == Zip::Ok; ++i) + { + QFileInfo info = list.at(i); + + if (info.isDir()) + { + // Recursion :) + progress(); + ec = addDirectory(info.absoluteFilePath(), actualRoot, recursionOptions, level); + } + else + { + progress(); + ec = d->createEntry(info, actualRoot, level); + filesAdded = true; + } + } + + + // We need an explicit record for this dir + // Non-empty directories don't need it because they have a path component in the filename + if (!filesAdded && !options.testFlag(IgnorePaths)) + ec = d->createEntry(current, actualRoot, level); + + return ec; +} + +/*! + Closes the archive and writes any pending data. +*/ +Zip::ErrorCode Zip::closeArchive() +{ + Zip::ErrorCode ec = d->closeArchive(); + d->reset(); + return ec; +} + +/*! + Returns a locale translated error string for a given error code. +*/ +QString Zip::formatError(Zip::ErrorCode c) const +{ + switch (c) + { + case Ok: return QCoreApplication::translate("Zip", "ZIP operation completed successfully."); break; + case ZlibInit: return QCoreApplication::translate("Zip", "Failed to initialize or load zlib library."); break; + case ZlibError: return QCoreApplication::translate("Zip", "zlib library error."); break; + case OpenFailed: return QCoreApplication::translate("Zip", "Unable to create or open file."); break; + case NoOpenArchive: return QCoreApplication::translate("Zip", "No archive has been created yet."); break; + case FileNotFound: return QCoreApplication::translate("Zip", "File or directory does not exist."); break; + case ReadFailed: return QCoreApplication::translate("Zip", "File read error."); break; + case WriteFailed: return QCoreApplication::translate("Zip", "File write error."); break; + case SeekFailed: return QCoreApplication::translate("Zip", "File seek error."); break; + default: ; + } + + return QCoreApplication::translate("Zip", "Unknown error."); +} + + +/************************************************************************ + Private interface +*************************************************************************/ + +//! \internal +ZipPrivate::ZipPrivate() +{ + headers = 0; + device = 0; + + // keep an unsigned pointer so we avoid to over bloat the code with casts + uBuffer = (unsigned char*) buffer1; + crcTable = (quint32*) get_crc_table(); +} + +//! \internal +ZipPrivate::~ZipPrivate() +{ + closeArchive(); +} + +//! \internal +Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev) +{ + Q_ASSERT(dev != 0); + + if (device != 0) + closeArchive(); + + device = dev; + + if (!device->isOpen()) + { + if (!device->open(QIODevice::ReadOnly)) { + delete device; + device = 0; + qDebug() << "Unable to open device for writing."; + return Zip::OpenFailed; + } + } + + headers = new QMap; + return Zip::Ok; +} + +//! \internal Writes a new entry in the zip file. +Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level) +{ + //! \todo Automatic level detection (cpu, extension & file size) + + // Directories and very small files are always stored + // (small files would get bigger due to the compression headers overhead) + + // Need this for zlib + bool isPNGFile = false; + bool dirOnly = file.isDir(); + + QString entryName = root; + + // Directory entry + if (dirOnly) + level = Zip::Store; + else + { + entryName.append(file.fileName()); + + QString ext = file.completeSuffix().toLower(); + isPNGFile = ext == "png"; + + if (file.size() < ZIP_COMPRESSION_THRESHOLD) + level = Zip::Store; + else + switch (level) + { + case Zip::AutoCPU: + level = Zip::Deflate5; + break; + case Zip::AutoMIME: + level = detectCompressionByMime(ext); + break; + case Zip::AutoFull: + level = detectCompressionByMime(ext); + break; + default: + ; + } + } + + // entryName contains the path as it should be written + // in the zip file records + // qDebug() << QString("addDir(file=%1, root=%2, entry=%3)").arg(file.absoluteFilePath(), root, entryName); + + // create header and store it to write a central directory later + ZipEntryP* h = new ZipEntryP; + + h->compMethod = (level == Zip::Store) ? 0 : 0x0008; + + // Set encryption bit and set the data descriptor bit + // so we can use mod time instead of crc for password check + bool encrypt = !dirOnly && !password.isEmpty(); + if (encrypt) + h->gpFlag[0] |= 9; + + QDateTime dt = file.lastModified(); + QDate d = dt.date(); + h->modDate[1] = ((d.year() - 1980) << 1) & 254; + h->modDate[1] |= ((d.month() >> 3) & 1); + h->modDate[0] = ((d.month() & 7) << 5) & 224; + h->modDate[0] |= d.day(); + + QTime t = dt.time(); + h->modTime[1] = (t.hour() << 3) & 248; + h->modTime[1] |= ((t.minute() >> 3) & 7); + h->modTime[0] = ((t.minute() & 7) << 5) & 224; + h->modTime[0] |= t.second() / 2; + + h->szUncomp = dirOnly ? 0 : file.size(); + + // **** Write local file header **** + + // signature + buffer1[0] = 'P'; buffer1[1] = 'K'; + buffer1[2] = 0x3; buffer1[3] = 0x4; + + // version needed to extract + buffer1[ZIP_LH_OFF_VERS] = ZIP_VERSION; + buffer1[ZIP_LH_OFF_VERS + 1] = 0; + + // general purpose flag + buffer1[ZIP_LH_OFF_GPFLAG] = h->gpFlag[0]; + buffer1[ZIP_LH_OFF_GPFLAG + 1] = h->gpFlag[1]; + + // compression method + buffer1[ZIP_LH_OFF_CMET] = h->compMethod & 0xFF; + buffer1[ZIP_LH_OFF_CMET + 1] = (h->compMethod>>8) & 0xFF; + + // last mod file time + buffer1[ZIP_LH_OFF_MODT] = h->modTime[0]; + buffer1[ZIP_LH_OFF_MODT + 1] = h->modTime[1]; + + // last mod file date + buffer1[ZIP_LH_OFF_MODD] = h->modDate[0]; + buffer1[ZIP_LH_OFF_MODD + 1] = h->modDate[1]; + + // skip crc (4bytes) [14,15,16,17] + + // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21]) + buffer1[ZIP_LH_OFF_CSIZE] = + buffer1[ZIP_LH_OFF_CSIZE + 1] = + buffer1[ZIP_LH_OFF_CSIZE + 2] = + buffer1[ZIP_LH_OFF_CSIZE + 3] = 0; + + h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0; + + // uncompressed size [22,23,24,25] + setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE); + + // filename length + QByteArray entryNameBytes = entryName.toAscii(); + int sz = entryNameBytes.size(); + + buffer1[ZIP_LH_OFF_NAMELEN] = sz & 0xFF; + buffer1[ZIP_LH_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF; + + // extra field length + buffer1[ZIP_LH_OFF_XLEN] = buffer1[ZIP_LH_OFF_XLEN + 1] = 0; + + // Store offset to write crc and compressed size + h->lhOffset = device->pos(); + quint32 crcOffset = h->lhOffset + ZIP_LH_OFF_CRC; + + if (device->write(buffer1, ZIP_LOCAL_HEADER_SIZE) != ZIP_LOCAL_HEADER_SIZE) + { + delete h; + return Zip::WriteFailed; + } + + // Write out filename + if (device->write(entryNameBytes) != sz) + { + delete h; + return Zip::WriteFailed; + } + + // Encryption keys + quint32 keys[3] = { 0, 0, 0 }; + + if (encrypt) + { + // **** encryption header **** + + // XOR with PI to ensure better random numbers + // with poorly implemented rand() as suggested by Info-Zip + srand(time(NULL) ^ 3141592654UL); + int randByte; + + initKeys(keys); + for (int i=0; i<10; ++i) + { + randByte = (rand() >> 7) & 0xff; + buffer1[i] = decryptByte(keys[2]) ^ randByte; + updateKeys(keys, randByte); + } + + // Encrypt encryption header + initKeys(keys); + for (int i=0; i<10; ++i) + { + randByte = decryptByte(keys[2]); + updateKeys(keys, buffer1[i]); + buffer1[i] ^= randByte; + } + + // We don't know the CRC at this time, so we use the modification time + // as the last two bytes + randByte = decryptByte(keys[2]); + updateKeys(keys, h->modTime[0]); + buffer1[10] ^= randByte; + + randByte = decryptByte(keys[2]); + updateKeys(keys, h->modTime[1]); + buffer1[11] ^= randByte; + + // Write out encryption header + if (device->write(buffer1, ZIP_LOCAL_ENC_HEADER_SIZE) != ZIP_LOCAL_ENC_HEADER_SIZE) + { + delete h; + return Zip::WriteFailed; + } + } + + qint64 written = 0; + quint32 crc = crc32(0L, Z_NULL, 0); + + if (!dirOnly) + { + QFile actualFile(file.absoluteFilePath()); + if (!actualFile.open(QIODevice::ReadOnly)) + { + qDebug() << QString("An error occurred while opening %1").arg(file.absoluteFilePath()); + return Zip::OpenFailed; + } + + // Write file data + qint64 read = 0; + qint64 totRead = 0; + qint64 toRead = actualFile.size(); + + if (level == Zip::Store) + { + while ( (read = actualFile.read(buffer1, ZIP_READ_BUFFER)) > 0 ) + { + crc = crc32(crc, uBuffer, read); + + if (password != 0) + encryptBytes(keys, buffer1, read); + + if ( (written = device->write(buffer1, read)) != read ) + { + actualFile.close(); + delete h; + return Zip::WriteFailed; + } + } + } + else + { + z_stream zstr; + + // Initialize zalloc, zfree and opaque before calling the init function + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + + int zret; + + // Use deflateInit2 with negative windowBits to get raw compression + if ((zret = deflateInit2_( + &zstr, + (int)level, + Z_DEFLATED, + -MAX_WBITS, + 8, + isPNGFile ? Z_RLE : Z_DEFAULT_STRATEGY, + ZLIB_VERSION, + sizeof(z_stream) + )) != Z_OK ) + { + actualFile.close(); + qDebug() << "Could not initialize zlib for compression"; + delete h; + return Zip::ZlibError; + } + + qint64 compressed; + + int flush = Z_NO_FLUSH; + + do + { + read = actualFile.read(buffer1, ZIP_READ_BUFFER); + totRead += read; + + if (read == 0) + break; + if (read < 0) + { + actualFile.close(); + deflateEnd(&zstr); + qDebug() << QString("Error while reading %1").arg(file.absoluteFilePath()); + delete h; + return Zip::ReadFailed; + } + + crc = crc32(crc, uBuffer, read); + + zstr.next_in = (Bytef*) buffer1; + zstr.avail_in = (uInt)read; + + // Tell zlib if this is the last chunk we want to encode + // by setting the flush parameter to Z_FINISH + flush = (totRead == toRead) ? Z_FINISH : Z_NO_FLUSH; + + // Run deflate() on input until output buffer not full + // finish compression if all of source has been read in + do + { + zstr.next_out = (Bytef*) buffer2; + zstr.avail_out = ZIP_READ_BUFFER; + + zret = deflate(&zstr, flush); + // State not clobbered + Q_ASSERT(zret != Z_STREAM_ERROR); + + // Write compressed data to file and empty buffer + compressed = ZIP_READ_BUFFER - zstr.avail_out; + + if (password != 0) + encryptBytes(keys, buffer2, compressed); + + if (device->write(buffer2, compressed) != compressed) + { + deflateEnd(&zstr); + actualFile.close(); + qDebug() << QString("Error while writing %1").arg(file.absoluteFilePath()); + delete h; + return Zip::WriteFailed; + } + + written += compressed; + + } while (zstr.avail_out == 0); + + // All input will be used + Q_ASSERT(zstr.avail_in == 0); + + } while (flush != Z_FINISH); + + // Stream will be complete + Q_ASSERT(zret == Z_STREAM_END); + + deflateEnd(&zstr); + + } // if (level != STORE) + + actualFile.close(); + } + + // Store end of entry offset + quint32 current = device->pos(); + + // Update crc and compressed size in local header + if (!device->seek(crcOffset)) + { + delete h; + return Zip::SeekFailed; + } + + h->crc = dirOnly ? 0 : crc; + h->szComp += written; + + setULong(h->crc, buffer1, 0); + setULong(h->szComp, buffer1, 4); + if ( device->write(buffer1, 8) != 8) + { + delete h; + return Zip::WriteFailed; + } + + // Seek to end of entry + if (!device->seek(current)) + { + delete h; + return Zip::SeekFailed; + } + + if ((h->gpFlag[0] & 8) == 8) + { + // Write data descriptor + + // Signature: PK\7\8 + buffer1[0] = 'P'; + buffer1[1] = 'K'; + buffer1[2] = 0x07; + buffer1[3] = 0x08; + + // CRC + setULong(h->crc, buffer1, ZIP_DD_OFF_CRC32); + + // Compressed size + setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE); + + // Uncompressed size + setULong(h->szUncomp, buffer1, ZIP_DD_OFF_USIZE); + + if (device->write(buffer1, ZIP_DD_SIZE_WS) != ZIP_DD_SIZE_WS) + { + delete h; + return Zip::WriteFailed; + } + } + + headers->insert(entryName, h); + return Zip::Ok; +} + +//! \internal +int ZipPrivate::decryptByte(quint32 key2) const +{ + quint16 temp = ((quint16)(key2) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +//! \internal Writes an quint32 (4 bytes) to a byte array at given offset. +void ZipPrivate::setULong(quint32 v, char* buffer, unsigned int offset) +{ + buffer[offset+3] = ((v >> 24) & 0xFF); + buffer[offset+2] = ((v >> 16) & 0xFF); + buffer[offset+1] = ((v >> 8) & 0xFF); + buffer[offset] = (v & 0xFF); +} + +//! \internal Initializes decryption keys using a password. +void ZipPrivate::initKeys(quint32* keys) const +{ + // Encryption keys initialization constants are taken from the + // PKZip file format specification docs + keys[0] = 305419896L; + keys[1] = 591751049L; + keys[2] = 878082192L; + + QByteArray pwdBytes = password.toAscii(); + int sz = pwdBytes.size(); + const char* ascii = pwdBytes.data(); + + for (int i=0; i> 24); +} + +//! \internal Encrypts a byte array. +void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read) +{ + char t; + + for (int i=0; i<(int)read; ++i) + { + t = buffer[i]; + buffer[i] ^= decryptByte(keys[2]); + updateKeys(keys, t); + } +} + +//! \internal Detects the best compression level for a given file extension. +Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext) +{ + // files really hard to compress + if ((ext == "png") || + (ext == "jpg") || + (ext == "jpeg") || + (ext == "mp3") || + (ext == "ogg") || + (ext == "ogm") || + (ext == "avi") || + (ext == "mov") || + (ext == "rm") || + (ext == "ra") || + (ext == "zip") || + (ext == "rar") || + (ext == "bz2") || + (ext == "gz") || + (ext == "7z") || + (ext == "z") || + (ext == "jar") + ) return Zip::Store; + + // files slow and hard to compress + if ((ext == "exe") || + (ext == "bin") || + (ext == "rpm") || + (ext == "deb") + ) return Zip::Deflate2; + + return Zip::Deflate9; +} + +/*! + Closes the current archive and writes out pending data. +*/ +Zip::ErrorCode ZipPrivate::closeArchive() +{ + // Close current archive by writing out central directory + // and free up resources + + if (device == 0) + return Zip::Ok; + + if (headers == 0) + return Zip::Ok; + + const ZipEntryP* h; + + unsigned int sz; + quint32 szCentralDir = 0; + quint32 offCentralDir = device->pos(); + + for (QMap::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr) + { + h = itr.value(); + + // signature + buffer1[0] = 'P'; + buffer1[1] = 'K'; + buffer1[2] = 0x01; + buffer1[3] = 0x02; + + // version made by (currently only MS-DOS/FAT - no symlinks or other stuff supported) + buffer1[ZIP_CD_OFF_MADEBY] = buffer1[ZIP_CD_OFF_MADEBY + 1] = 0; + + // version needed to extract + buffer1[ZIP_CD_OFF_VERSION] = ZIP_VERSION; + buffer1[ZIP_CD_OFF_VERSION + 1] = 0; + + // general purpose flag + buffer1[ZIP_CD_OFF_GPFLAG] = h->gpFlag[0]; + buffer1[ZIP_CD_OFF_GPFLAG + 1] = h->gpFlag[1]; + + // compression method + buffer1[ZIP_CD_OFF_CMET] = h->compMethod & 0xFF; + buffer1[ZIP_CD_OFF_CMET + 1] = (h->compMethod >> 8) & 0xFF; + + // last mod file time + buffer1[ZIP_CD_OFF_MODT] = h->modTime[0]; + buffer1[ZIP_CD_OFF_MODT + 1] = h->modTime[1]; + + // last mod file date + buffer1[ZIP_CD_OFF_MODD] = h->modDate[0]; + buffer1[ZIP_CD_OFF_MODD + 1] = h->modDate[1]; + + // crc (4bytes) [16,17,18,19] + setULong(h->crc, buffer1, ZIP_CD_OFF_CRC); + + // compressed size (4bytes: [20,21,22,23]) + setULong(h->szComp, buffer1, ZIP_CD_OFF_CSIZE); + + // uncompressed size [24,25,26,27] + setULong(h->szUncomp, buffer1, ZIP_CD_OFF_USIZE); + + // filename + QByteArray fileNameBytes = itr.key().toAscii(); + sz = fileNameBytes.size(); + buffer1[ZIP_CD_OFF_NAMELEN] = sz & 0xFF; + buffer1[ZIP_CD_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF; + + // extra field length + buffer1[ZIP_CD_OFF_XLEN] = buffer1[ZIP_CD_OFF_XLEN + 1] = 0; + + // file comment length + buffer1[ZIP_CD_OFF_COMMLEN] = buffer1[ZIP_CD_OFF_COMMLEN + 1] = 0; + + // disk number start + buffer1[ZIP_CD_OFF_DISKSTART] = buffer1[ZIP_CD_OFF_DISKSTART + 1] = 0; + + // internal file attributes + buffer1[ZIP_CD_OFF_IATTR] = buffer1[ZIP_CD_OFF_IATTR + 1] = 0; + + // external file attributes + buffer1[ZIP_CD_OFF_EATTR] = + buffer1[ZIP_CD_OFF_EATTR + 1] = + buffer1[ZIP_CD_OFF_EATTR + 2] = + buffer1[ZIP_CD_OFF_EATTR + 3] = 0; + + // relative offset of local header [42->45] + setULong(h->lhOffset, buffer1, ZIP_CD_OFF_LHOFF); + + if (device->write(buffer1, ZIP_CD_SIZE) != ZIP_CD_SIZE) + { + //! \todo See if we can detect QFile objects using the Qt Meta Object System + /* + if (!device->remove()) + qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); + */ + return Zip::WriteFailed; + } + + // Write out filename + if ((unsigned int)device->write(fileNameBytes) != sz) + { + //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System + /* + if (!device->remove()) + qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); + */ + return Zip::WriteFailed; + } + + szCentralDir += (ZIP_CD_SIZE + sz); + + } // central dir headers loop + + + // Write end of central directory + + // signature + buffer1[0] = 'P'; + buffer1[1] = 'K'; + buffer1[2] = 0x05; + buffer1[3] = 0x06; + + // number of this disk + buffer1[ZIP_EOCD_OFF_DISKNUM] = buffer1[ZIP_EOCD_OFF_DISKNUM + 1] = 0; + + // number of disk with central directory + buffer1[ZIP_EOCD_OFF_CDDISKNUM] = buffer1[ZIP_EOCD_OFF_CDDISKNUM + 1] = 0; + + // number of entries in this disk + sz = headers->count(); + buffer1[ZIP_EOCD_OFF_ENTRIES] = sz & 0xFF; + buffer1[ZIP_EOCD_OFF_ENTRIES + 1] = (sz >> 8) & 0xFF; + + // total number of entries + buffer1[ZIP_EOCD_OFF_CDENTRIES] = buffer1[ZIP_EOCD_OFF_ENTRIES]; + buffer1[ZIP_EOCD_OFF_CDENTRIES + 1] = buffer1[ZIP_EOCD_OFF_ENTRIES + 1]; + + // size of central directory [12->15] + setULong(szCentralDir, buffer1, ZIP_EOCD_OFF_CDSIZE); + + // central dir offset [16->19] + setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF); + + // ZIP file comment length + QByteArray commentBytes = comment.toAscii(); + quint16 commentLength = commentBytes.size(); + + if (commentLength == 0) + { + buffer1[ZIP_EOCD_OFF_COMMLEN] = buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = 0; + } + else + { + buffer1[ZIP_EOCD_OFF_COMMLEN] = commentLength & 0xFF; + buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = (commentLength >> 8) & 0xFF; + } + + if (device->write(buffer1, ZIP_EOCD_SIZE) != ZIP_EOCD_SIZE) + { + //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System + /* + if (!device->remove()) + qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); + */ + return Zip::WriteFailed; + } + + if (commentLength != 0) + { + if ((unsigned int)device->write(commentBytes) != commentLength) + { + //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System + /* + if (!device->remove()) + qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); + */ + return Zip::WriteFailed; + } + } + + return Zip::Ok; +} + +//! \internal +void ZipPrivate::reset() +{ + comment.clear(); + + if (headers != 0) + { + qDeleteAll(*headers); + delete headers; + headers = 0; + } + + delete device; device = 0; +} + +//! \internal Returns the path of the parent directory +QString ZipPrivate::extractRoot(const QString& p) +{ + QDir d(QDir::cleanPath(p)); + if (!d.exists()) + return QString(); + + if (!d.cdUp()) + return QString(); + + return d.absolutePath(); +} diff --git a/rbutil/rbutilqt/zip/zip.h b/rbutil/rbutilqt/zip/zip.h index 44fdd08b5f..8f82f1738a 100644 --- a/rbutil/rbutilqt/zip/zip.h +++ b/rbutil/rbutilqt/zip/zip.h @@ -1,115 +1,115 @@ -/**************************************************************************** -** Filename: zip.h -** Last updated [dd/mm/yyyy]: 01/02/2007 -** -** pkzip 2.0 file compression. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -#ifndef OSDAB_ZIP__H -#define OSDAB_ZIP__H - -#include -#include - -#include - -class ZipPrivate; - -class QIODevice; -class QFile; -class QDir; -class QStringList; -class QString; - - -class Zip -{ -public: - enum ErrorCode - { - Ok, - ZlibInit, - ZlibError, - FileExists, - OpenFailed, - NoOpenArchive, - FileNotFound, - ReadFailed, - WriteFailed, - SeekFailed - }; - - enum CompressionLevel - { - Store, - Deflate1 = 1, Deflate2, Deflate3, Deflate4, - Deflate5, Deflate6, Deflate7, Deflate8, Deflate9, - AutoCPU, AutoMIME, AutoFull - }; - - enum CompressionOption - { - //! Does not preserve absolute paths in the zip file when adding a file/directory (default) - RelativePaths = 0x0001, - //! Preserve absolute paths - AbsolutePaths = 0x0002, - //! Do not store paths. All the files are put in the (evtl. user defined) root of the zip file - IgnorePaths = 0x0004 - }; - Q_DECLARE_FLAGS(CompressionOptions, CompressionOption) - - Zip(); - virtual ~Zip(); - - bool isOpen() const; - - void setPassword(const QString& pwd); - void clearPassword(); - QString password() const; - - ErrorCode createArchive(const QString& file, bool overwrite = true); - ErrorCode createArchive(QIODevice* device); - - QString archiveComment() const; - void setArchiveComment(const QString& comment); - - ErrorCode addDirectoryContents(const QString& path, CompressionLevel level = AutoFull); - ErrorCode addDirectoryContents(const QString& path, const QString& root, CompressionLevel level = AutoFull); - - ErrorCode addDirectory(const QString& path, CompressionOptions options = RelativePaths, CompressionLevel level = AutoFull); - ErrorCode addDirectory(const QString& path, const QString& root, CompressionLevel level = AutoFull); - ErrorCode addDirectory(const QString& path, const QString& root, CompressionOptions options = RelativePaths, CompressionLevel level = AutoFull); - - ErrorCode closeArchive(); - - QString formatError(ErrorCode c) const; - - virtual void progress() {} - -private: - ZipPrivate* d; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(Zip::CompressionOptions) - -#endif // OSDAB_ZIP__H +/**************************************************************************** +** Filename: zip.h +** Last updated [dd/mm/yyyy]: 01/02/2007 +** +** pkzip 2.0 file compression. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +#ifndef OSDAB_ZIP__H +#define OSDAB_ZIP__H + +#include +#include + +#include + +class ZipPrivate; + +class QIODevice; +class QFile; +class QDir; +class QStringList; +class QString; + + +class Zip +{ +public: + enum ErrorCode + { + Ok, + ZlibInit, + ZlibError, + FileExists, + OpenFailed, + NoOpenArchive, + FileNotFound, + ReadFailed, + WriteFailed, + SeekFailed + }; + + enum CompressionLevel + { + Store, + Deflate1 = 1, Deflate2, Deflate3, Deflate4, + Deflate5, Deflate6, Deflate7, Deflate8, Deflate9, + AutoCPU, AutoMIME, AutoFull + }; + + enum CompressionOption + { + //! Does not preserve absolute paths in the zip file when adding a file/directory (default) + RelativePaths = 0x0001, + //! Preserve absolute paths + AbsolutePaths = 0x0002, + //! Do not store paths. All the files are put in the (evtl. user defined) root of the zip file + IgnorePaths = 0x0004 + }; + Q_DECLARE_FLAGS(CompressionOptions, CompressionOption) + + Zip(); + virtual ~Zip(); + + bool isOpen() const; + + void setPassword(const QString& pwd); + void clearPassword(); + QString password() const; + + ErrorCode createArchive(const QString& file, bool overwrite = true); + ErrorCode createArchive(QIODevice* device); + + QString archiveComment() const; + void setArchiveComment(const QString& comment); + + ErrorCode addDirectoryContents(const QString& path, CompressionLevel level = AutoFull); + ErrorCode addDirectoryContents(const QString& path, const QString& root, CompressionLevel level = AutoFull); + + ErrorCode addDirectory(const QString& path, CompressionOptions options = RelativePaths, CompressionLevel level = AutoFull); + ErrorCode addDirectory(const QString& path, const QString& root, CompressionLevel level = AutoFull); + ErrorCode addDirectory(const QString& path, const QString& root, CompressionOptions options = RelativePaths, CompressionLevel level = AutoFull); + + ErrorCode closeArchive(); + + QString formatError(ErrorCode c) const; + + virtual void progress() {} + +private: + ZipPrivate* d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Zip::CompressionOptions) + +#endif // OSDAB_ZIP__H diff --git a/rbutil/rbutilqt/zip/zip_p.h b/rbutil/rbutilqt/zip/zip_p.h index b3c45ac7b8..8c57f99225 100644 --- a/rbutil/rbutilqt/zip/zip_p.h +++ b/rbutil/rbutilqt/zip/zip_p.h @@ -1,93 +1,93 @@ -/**************************************************************************** -** Filename: zip_p.h -** Last updated [dd/mm/yyyy]: 28/01/2007 -** -** pkzip 2.0 file compression. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Zip/UnZip API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef OSDAB_ZIP_P__H -#define OSDAB_ZIP_P__H - -#include "zip.h" -#include "zipentry_p.h" - -#include -#include - -/*! - zLib authors suggest using larger buffers (128K or 256K) for (de)compression (especially for inflate()) - we use a 256K buffer here - if you want to use this code on a pre-iceage mainframe please change it ;) -*/ -#define ZIP_READ_BUFFER (256*1024) - -class ZipPrivate -{ -public: - ZipPrivate(); - virtual ~ZipPrivate(); - - QMap* headers; - - QIODevice* device; - - char buffer1[ZIP_READ_BUFFER]; - char buffer2[ZIP_READ_BUFFER]; - - unsigned char* uBuffer; - - const quint32* crcTable; - - QString comment; - QString password; - - Zip::ErrorCode createArchive(QIODevice* device); - Zip::ErrorCode closeArchive(); - void reset(); - - bool zLibInit(); - - Zip::ErrorCode createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level); - Zip::CompressionLevel detectCompressionByMime(const QString& ext); - - inline void encryptBytes(quint32* keys, char* buffer, qint64 read); - - inline void setULong(quint32 v, char* buffer, unsigned int offset); - inline void updateKeys(quint32* keys, int c) const; - inline void initKeys(quint32* keys) const; - inline int decryptByte(quint32 key2) const; - - inline QString extractRoot(const QString& p); -}; - -#endif // OSDAB_ZIP_P__H +/**************************************************************************** +** Filename: zip_p.h +** Last updated [dd/mm/yyyy]: 28/01/2007 +** +** pkzip 2.0 file compression. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Zip/UnZip API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef OSDAB_ZIP_P__H +#define OSDAB_ZIP_P__H + +#include "zip.h" +#include "zipentry_p.h" + +#include +#include + +/*! + zLib authors suggest using larger buffers (128K or 256K) for (de)compression (especially for inflate()) + we use a 256K buffer here - if you want to use this code on a pre-iceage mainframe please change it ;) +*/ +#define ZIP_READ_BUFFER (256*1024) + +class ZipPrivate +{ +public: + ZipPrivate(); + virtual ~ZipPrivate(); + + QMap* headers; + + QIODevice* device; + + char buffer1[ZIP_READ_BUFFER]; + char buffer2[ZIP_READ_BUFFER]; + + unsigned char* uBuffer; + + const quint32* crcTable; + + QString comment; + QString password; + + Zip::ErrorCode createArchive(QIODevice* device); + Zip::ErrorCode closeArchive(); + void reset(); + + bool zLibInit(); + + Zip::ErrorCode createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level); + Zip::CompressionLevel detectCompressionByMime(const QString& ext); + + inline void encryptBytes(quint32* keys, char* buffer, qint64 read); + + inline void setULong(quint32 v, char* buffer, unsigned int offset); + inline void updateKeys(quint32* keys, int c) const; + inline void initKeys(quint32* keys) const; + inline int decryptByte(quint32 key2) const; + + inline QString extractRoot(const QString& p); +}; + +#endif // OSDAB_ZIP_P__H diff --git a/rbutil/rbutilqt/zip/zipentry_p.h b/rbutil/rbutilqt/zip/zipentry_p.h index a201d0ac6e..dfdcf174d0 100644 --- a/rbutil/rbutilqt/zip/zipentry_p.h +++ b/rbutil/rbutilqt/zip/zipentry_p.h @@ -1,78 +1,78 @@ -/**************************************************************************** -** Filename: ZipEntryP.h -** Last updated [dd/mm/yyyy]: 28/01/2007 -** -** Wrapper for a ZIP local header. -** -** Some of the code has been inspired by other open source projects, -** (mainly Info-Zip and Gilles Vollant's minizip). -** Compression and decompression actually uses the zlib library. -** -** Copyright (C) 2007 Angius Fabrizio. All rights reserved. -** -** This file is part of the OSDaB project (http://osdab.sourceforge.net/). -** -** This file may be distributed and/or modified under the terms of the -** GNU General Public License version 2 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -** See the file LICENSE.GPL that came with this software distribution or -** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. -** -**********************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Zip/UnZip API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef OSDAB_ZIPENTRY_P__H -#define OSDAB_ZIPENTRY_P__H - -#include -#include - -class ZipEntryP -{ -public: - ZipEntryP() - { - lhOffset = 0; - dataOffset = 0; - gpFlag[0] = gpFlag[1] = 0; - compMethod = 0; - modTime[0] = modTime[1] = 0; - modDate[0] = modDate[1] = 0; - crc = 0; - szComp = szUncomp = 0; - lhEntryChecked = false; - } - - quint32 lhOffset; // Offset of the local header record for this entry - quint32 dataOffset; // Offset of the file data for this entry - unsigned char gpFlag[2]; // General purpose flag - quint16 compMethod; // Compression method - unsigned char modTime[2]; // Last modified time - unsigned char modDate[2]; // Last modified date - quint32 crc; // CRC32 - quint32 szComp; // Compressed file size - quint32 szUncomp; // Uncompressed file size - QString comment; // File comment - - bool lhEntryChecked; // Is true if the local header record for this entry has been parsed - - inline bool isEncrypted() const { return gpFlag[0] & 0x01; } - inline bool hasDataDescriptor() const { return gpFlag[0] & 0x08; } -}; - -#endif // OSDAB_ZIPENTRY_P__H +/**************************************************************************** +** Filename: ZipEntryP.h +** Last updated [dd/mm/yyyy]: 28/01/2007 +** +** Wrapper for a ZIP local header. +** +** Some of the code has been inspired by other open source projects, +** (mainly Info-Zip and Gilles Vollant's minizip). +** Compression and decompression actually uses the zlib library. +** +** Copyright (C) 2007 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.sourceforge.net/). +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See the file LICENSE.GPL that came with this software distribution or +** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. +** +**********************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Zip/UnZip API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef OSDAB_ZIPENTRY_P__H +#define OSDAB_ZIPENTRY_P__H + +#include +#include + +class ZipEntryP +{ +public: + ZipEntryP() + { + lhOffset = 0; + dataOffset = 0; + gpFlag[0] = gpFlag[1] = 0; + compMethod = 0; + modTime[0] = modTime[1] = 0; + modDate[0] = modDate[1] = 0; + crc = 0; + szComp = szUncomp = 0; + lhEntryChecked = false; + } + + quint32 lhOffset; // Offset of the local header record for this entry + quint32 dataOffset; // Offset of the file data for this entry + unsigned char gpFlag[2]; // General purpose flag + quint16 compMethod; // Compression method + unsigned char modTime[2]; // Last modified time + unsigned char modDate[2]; // Last modified date + quint32 crc; // CRC32 + quint32 szComp; // Compressed file size + quint32 szUncomp; // Uncompressed file size + QString comment; // File comment + + bool lhEntryChecked; // Is true if the local header record for this entry has been parsed + + inline bool isEncrypted() const { return gpFlag[0] & 0x01; } + inline bool hasDataDescriptor() const { return gpFlag[0] & 0x08; } +}; + +#endif // OSDAB_ZIPENTRY_P__H -- cgit v1.2.3