From 781f8a6603ec2295afb5b175e5e2371a45c3ca02 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Mon, 2 Feb 2015 19:59:35 +0100 Subject: [PATCH] Oracle: support AllSets.json.zip; Fix #624 Imported unzip methods from OSDaB-Zip project; Adds a soft dependency on zlib to oracle; if lib if not found, oracle will continue to compile and work but without zip support. --- README.md | 4 + oracle/CMakeLists.txt | 15 + oracle/src/oraclewizard.cpp | 71 +- oracle/src/zip/unzip.cpp | 1430 ++++++++++++++++++++++++++++++ oracle/src/zip/unzip.h | 152 ++++ oracle/src/zip/unzip_p.h | 130 +++ oracle/src/zip/zip.cpp | 1619 ++++++++++++++++++++++++++++++++++ oracle/src/zip/zip.h | 158 ++++ oracle/src/zip/zip_p.h | 133 +++ oracle/src/zip/zipentry_p.h | 91 ++ oracle/src/zip/zipglobal.cpp | 152 ++++ oracle/src/zip/zipglobal.h | 77 ++ 12 files changed, 4029 insertions(+), 3 deletions(-) create mode 100755 oracle/src/zip/unzip.cpp create mode 100755 oracle/src/zip/unzip.h create mode 100755 oracle/src/zip/unzip_p.h create mode 100755 oracle/src/zip/zip.cpp create mode 100755 oracle/src/zip/zip.h create mode 100755 oracle/src/zip/zip_p.h create mode 100755 oracle/src/zip/zipentry_p.h create mode 100755 oracle/src/zip/zipglobal.cpp create mode 100755 oracle/src/zip/zipglobal.h diff --git a/README.md b/README.md index 4d95a67a..86338777 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ Dependencies: - [protobuf](http://code.google.com/p/protobuf/) - [CMake](http://www.cmake.org/) +Oracle can optionally use zlib to load zipped files: + +- [zlib](http://www.zlib.net/) + The server requires an additional dependency when compiled under Qt4: - [libgcrypt](http://www.gnu.org/software/libgcrypt/) diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 06a66a8c..67bc70b3 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -11,6 +11,8 @@ SET(oracle_SOURCES src/main.cpp src/oraclewizard.cpp src/oracleimporter.cpp + src/zip/unzip.cpp + src/zip/zipglobal.cpp ../cockatrice/src/carddatabase.cpp ../cockatrice/src/settingscache.cpp ../cockatrice/src/qt-json/json.cpp @@ -93,6 +95,15 @@ endif() INCLUDE_DIRECTORIES(../cockatrice/src) +# Libz is required to support zipped files +FIND_PACKAGE(ZLIB REQUIRED) +IF(ZLIB_FOUND) + INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) + ADD_DEFINITIONS("-DHAS_ZLIB") +ELSE() + MESSAGE(STATUS "Oracle: zlib not found; ZIP support disabled") +ENDIF() + # Build oracle binary and link it ADD_EXECUTABLE(oracle WIN32 MACOSX_BUNDLE ${oracle_SOURCES} ${oracle_RESOURCES_RCC} ${oracle_MOC_SRCS}) @@ -109,6 +120,10 @@ if(Qt5Widgets_FOUND) qt5_use_modules(oracle ${ORACLE_LIBS}) endif() +IF(ZLIB_FOUND) + TARGET_LINK_LIBRARIES(oracle ${ZLIB_LIBRARIES}) +ENDIF() + if(UNIX) if(APPLE) set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}") diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index e8608e01..f11341da 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -6,6 +6,7 @@ #include #endif #include +#include #include #include #include @@ -25,7 +26,14 @@ #include "oraclewizard.h" #include "oracleimporter.h" -#define ALLSETS_URL "http://mtgjson.com/json/AllSets.json" +#ifdef HAS_ZLIB + #include "zip/unzip.h" + #define ZIP_SIGNATURE "PK" + #define ALLSETS_URL "http://mtgjson.com/json/AllSets.json.zip" +#else + #define ALLSETS_URL "http://mtgjson.com/json/AllSets.json" +#endif + OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) @@ -141,7 +149,12 @@ void LoadSetsPage::actLoadSetsFile() { QFileDialog dialog(this, tr("Load sets file")); dialog.setFileMode(QFileDialog::ExistingFile); - dialog.setNameFilter("Sets JSON file (*.json)"); + +#ifdef HAS_ZLIB + dialog.setNameFilter(tr("Sets JSON file (*.json *.zip)")); +#else + dialog.setNameFilter(tr("Sets JSON file (*.json)")); +#endif if(!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) dialog.selectFile(fileLineEdit->text()); @@ -194,7 +207,7 @@ bool LoadSetsPage::validatePage() return false; } - if (!setsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (!setsFile.open(QIODevice::ReadOnly)) { QMessageBox::critical(0, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); return false; } @@ -256,6 +269,58 @@ void LoadSetsPage::readSetsFromByteArray(QByteArray data) progressLabel->show(); progressBar->show(); + // unzip the file if needed + if(data.startsWith(ZIP_SIGNATURE)) + { +#ifdef HAS_ZLIB + // zipped file + QBuffer *inBuffer = new QBuffer(&data); + QBuffer *outBuffer = new QBuffer(this); + QString fileName; + UnZip::ErrorCode ec; + UnZip uz; + + ec = uz.openArchive(inBuffer); + if (ec != UnZip::Ok) { + QMessageBox::critical(this, tr("Error"), tr("Failed to open Zip archive: %1").arg(uz.formatError(ec))); + + wizard()->enableButtons(); + setEnabled(true); + return; + } + + if(uz.fileList().size() != 1) + { + QMessageBox::critical(this, tr("Error"), tr("The Zip archive doesn't contain exactly one file")); + + wizard()->enableButtons(); + setEnabled(true); + return; + } + fileName = uz.fileList().at(0); + + outBuffer->open(QBuffer::ReadWrite); + ec = uz.extractFile(fileName, outBuffer); + if (ec != UnZip::Ok) { + QMessageBox::critical(this, tr("Error"), tr("Zip extraction failed: %1").arg(uz.formatError(ec))); + uz.closeArchive(); + + wizard()->enableButtons(); + setEnabled(true); + return; + } + + future = QtConcurrent::run(wizard()->importer, &OracleImporter::readSetsFromByteArray, outBuffer->data()); + watcher.setFuture(future); + return; +#else + QMessageBox::critical(this, tr("Error"), tr("Sorry, this version of Oracle does not support zipped files.")); + + wizard()->enableButtons(); + setEnabled(true); + return; +#endif + } // Start the computation. future = QtConcurrent::run(wizard()->importer, &OracleImporter::readSetsFromByteArray, data); watcher.setFuture(future); diff --git a/oracle/src/zip/unzip.cpp b/oracle/src/zip/unzip.cpp new file mode 100755 index 00000000..da250cee --- /dev/null +++ b/oracle/src/zip/unzip.cpp @@ -0,0 +1,1430 @@ +/**************************************************************************** +** Filename: unzip.cpp +** Last updated [dd/mm/yyyy]: 08/07/2010 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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. + \value UnZip::VerifyOnly Doesn't actually extract files. + \value UnZip::NoSilentDirectoryCreation Doesn't attempt to silently create missing output directories. +*/ + +//! 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_MADE 0 +#define UNZIP_CD_OFF_VERSION 2 +#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. + 0x14 = 2.0 --> full compatibility only up to this version; + later versions use unsupported features +*/ +#define UNZIP_VERSION 0x14 + +//! CRC32 routine +#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) + +OSDAB_BEGIN_NAMESPACE(Zip) + + +/************************************************************************ + ZipEntry +*************************************************************************/ + +/*! + 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() : + password(), + skipAllEncrypted(false), + headers(0), + device(0), + file(0), + uBuffer(0), + crcTable(0), + cdOffset(0), + eocdOffset(0), + cdEntryCount(0), + unsupportedEntryCount(0), + comment() +{ + uBuffer = (unsigned char*) buffer1; + crcTable = (quint32*) get_crc_table(); +} + +//! \internal +void UnzipPrivate::deviceDestroyed(QObject*) +{ + qDebug("Unexpected device destruction detected."); + do_closeArchive(); +} + +//! \internal Parses a Zip archive. +UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) +{ + Q_ASSERT(!device); + Q_ASSERT(dev); + + if (!(dev->isOpen() || dev->open(QIODevice::ReadOnly))) { + qDebug() << "Unable to open device for reading"; + return UnZip::OpenFailed; + } + + device = dev; + if (device != file) + connect(device, SIGNAL(destroyed(QObject*)), this, SLOT(deviceDestroyed(QObject*))); + + 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) { + if (headers) { + qDebug() << "Corrupted zip archive. Some files might be extracted."; + ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted; + break; + } else { + closeArchive(); + qDebug() << "Corrupted or invalid zip archive. Closing."; + ec = UnZip::Corrupted; + break; + } + } + + 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, const ZipEntryP& entry) +{ + Q_ASSERT(device); + + 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::fromLatin1(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() +{ + Q_ASSERT(device); + + 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; + } + + // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here. + offset -= 1 /*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() +{ + Q_ASSERT(device); + + // 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; + } + + if (!skipEntry && szName == 0) { + qDebug() << "Skipping file with no name."; + skipEntry = true; + } + + QString filename; + if (device->read(buffer2, szName) != szName) { + ec = UnZip::ReadFailed; + skipEntry = true; + } else { + filename = QString::fromLatin1(buffer2, szName); + } + + // Unsupported features if version is bigger than UNZIP_VERSION + if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION) { + QString v = QString::number(buffer1[UNZIP_CD_OFF_VERSION]); + if (v.length() == 2) + v.insert(1, QLatin1Char('.')); + v = QString::fromLatin1("Unsupported PKZip version (%1). Skipping file: %2") + .arg(v, filename.isEmpty() ? QString::fromLatin1("") : filename); + qDebug() << v.toLatin1().constData(); + skipEntry = true; + } + + if (skipEntry) { + if (ec == UnZip::Ok) { + if (!device->seek( device->pos() + skipLength )) + ec = UnZip::SeekFailed; + unsupportedEntryCount++; + } + + return ec; + } + + 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::fromLatin1(buffer2, szComment); + } + + h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET); + + if (!headers) + headers = new QMap(); + headers->insert(filename, h); + + return UnZip::Ok; +} + +//! \internal Closes the archive and resets the internal status. +void UnzipPrivate::closeArchive() +{ + if (!device) { + Q_ASSERT(!file); + return; + } + + if (device != file) + disconnect(device, 0, this, 0); + + do_closeArchive(); +} + +//! \internal +void UnzipPrivate::do_closeArchive() +{ + skipAllEncrypted = false; + + if (headers) { + if (headers) + qDeleteAll(*headers); + delete headers; + headers = 0; + } + + device = 0; + + if (file) + delete file; + file = 0; + + cdOffset = eocdOffset = 0; + cdEntryCount = 0; + unsupportedEntryCount = 0; + + comment.clear(); +} + +//! \internal +UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& entry, + const QDir& dir, UnZip::ExtractionOptions options) +{ + QString name(path); + QString dirname; + QString directory; + + const bool verify = (options & UnZip::VerifyOnly); + const int pos = name.lastIndexOf('/'); + + // This entry is for a directory + if (pos == name.length() - 1) { + if (verify) + return UnZip::Ok; + + if (options & 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 (verify) { + return extractFile(path, entry, 0, options); + } + + if (pos > 0) { + // get directory part + dirname = name.left(pos); + if (options & 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(); + } + + const bool silentDirectoryCreation = !(options & UnZip::NoSilentDirectoryCreation); + if (silentDirectoryCreation) { + if (!createDirectory(directory)) { + qDebug() << QString("Unable to create output directory %1").arg(directory); + return UnZip::CreateDirFailed; + } + } + + 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; + } + + UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options); + outFile.close(); + + const QDateTime lastModified = convertDateTime(entry.modDate, entry.modTime); + const bool setTimeOk = OSDAB_ZIP_MANGLE(setFileTimestamp)(name, lastModified); + if (!setTimeOk) { + qDebug() << QString("Unable to set last modified time on file: %1").arg(name); + } + + if (ec != UnZip::Ok) { + if (!outFile.remove()) + qDebug() << QString("Unable to remove corrupted file: %1").arg(name); + } + + return ec; +} + +//! \internal +UnZip::ErrorCode UnzipPrivate::extractStoredFile( + const quint32 szComp, quint32** keys, quint32& myCRC, QIODevice* outDev, + UnZip::ExtractionOptions options) +{ + const bool verify = (options & UnZip::VerifyOnly); + const bool isEncrypted = keys != 0; + + uInt rep = szComp / UNZIP_READ_BUFFER; + uInt rem = szComp % UNZIP_READ_BUFFER; + uInt cur = 0; + + // extract data + qint64 read; + quint64 tot = 0; + + while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 ) { + if (isEncrypted) + decryptBytes(*keys, buffer1, read); + + myCRC = crc32(myCRC, uBuffer, read); + if (!verify) { + if (outDev->write(buffer1, read) != read) + return UnZip::WriteFailed; + } + + cur++; + tot += read; + if (tot == szComp) + break; + } + + return (read < 0) + ? UnZip::ReadFailed + : UnZip::Ok; +} + +//! \internal +UnZip::ErrorCode UnzipPrivate::inflateFile( + const quint32 szComp, quint32** keys, quint32& myCRC, QIODevice* outDev, + UnZip::ExtractionOptions options) +{ + const bool verify = (options & UnZip::VerifyOnly); + const bool isEncrypted = keys != 0; + Q_ASSERT(verify ? true : outDev != 0); + + uInt rep = szComp / UNZIP_READ_BUFFER; + uInt rem = szComp % UNZIP_READ_BUFFER; + uInt cur = 0; + + // extract data + qint64 read; + quint64 tot = 0; + + /* 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) + break; + + if (read < 0) { + (void)inflateEnd(&zstr); + return UnZip::ReadFailed; + } + + if (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 (!verify) { + if (outDev->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); + return UnZip::Ok; +} + +//! \internal \p outDev is null if the VerifyOnly option is set +UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& entry, + QIODevice* outDev, UnZip::ExtractionOptions options) +{ + const bool verify = (options & UnZip::VerifyOnly); + + Q_UNUSED(options); + Q_ASSERT(device); + Q_ASSERT(verify ? true : outDev != 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]; + quint32 szComp = entry.szComp; + 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 + szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size + } + + if (szComp == 0) { + if (entry.crc != 0) + return UnZip::Corrupted; + return UnZip::Ok; + } + + quint32 myCRC = crc32(0L, Z_NULL, 0); + quint32* k = keys; + + UnZip::ErrorCode ec = UnZip::Ok; + if (entry.compMethod == 0) { + ec = extractStoredFile(szComp, entry.isEncrypted() ? &k : 0, myCRC, outDev, options); + } else if (entry.compMethod == 8) { + ec = inflateFile(szComp, entry.isEncrypted() ? &k : 0, myCRC, outDev, options); + } + + if (ec == UnZip::Ok && 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() && !d.mkpath(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.toLatin1(); + int sz = pwdBytes.size(); + const char* ascii = pwdBytes.data(); + + for (int i = 0; i < sz; ++i) + updateKeys(keys, (int)ascii[i]); +} + +/*! + \internal Attempts to test a password without actually extracting a file. + The \p file parameter can be used in the user interface or for debugging purposes + as it is the name of the encrypted file for wich the password is being tested. +*/ +UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header) +{ + Q_UNUSED(file); + Q_ASSERT(device); + + // read encryption keys + if (device->read(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; +} + + +/************************************************************************ + 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; +} + +/*! + Opens a zip archive and reads the files list. Closes any previously opened archive. +*/ +UnZip::ErrorCode UnZip::openArchive(const QString& filename) +{ + closeArchive(); + + // closeArchive will destroy the file + d->file = new QFile(filename); + + if (!d->file->exists()) { + delete d->file; + d->file = 0; + return UnZip::FileNotFound; + } + + if (!d->file->open(QIODevice::ReadOnly)) { + delete d->file; + d->file = 0; + return UnZip::OpenFailed; + } + + return d->openArchive(d->file); +} + +/*! + Opens a zip archive and reads the entries list. + Closes any previously opened archive. + \warning The class takes DOES NOT take ownership of the device. +*/ +UnZip::ErrorCode UnZip::openArchive(QIODevice* device) +{ + closeArchive(); + + if (!device) { + 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 +{ + 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 +{ + return d->headers ? d->headers->contains(file) : false; +} + +/*! + Returns complete paths of files and directories in this archive. +*/ +QStringList UnZip::fileList() const +{ + return d->headers ? d->headers->keys() : QStringList(); +} + +/*! + Returns information for each (correctly parsed) entry of this archive. +*/ +QList UnZip::entryList() const +{ + QList list; + if (!d->headers) + return list; + + 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::verifyArchive() +{ + return extractAll(QDir(), VerifyOnly); +} + +/*! + 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. + Stops extraction at the first error. +*/ +UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) +{ + // this should only happen if we didn't call openArchive() yet + if (!d->device) + return NoOpenArchive; + + if (!d->headers) + return Ok; + + ErrorCode ec = Ok; + + QMap::ConstIterator it = d->headers->constBegin(); + const QMap::ConstIterator end = d->headers->constEnd(); + while (it != end) { + ZipEntryP* entry = it.value(); + Q_ASSERT(entry != 0); + if ((entry->isEncrypted()) && d->skipAllEncrypted) { + ++it; + continue; + } + + bool skip = false; + ec = d->extractFile(it.key(), *entry, dir, options); + switch (ec) { + case Corrupted: + qDebug() << "Corrupted entry" << it.key(); + break; + case CreateDirFailed: + break; + case Skip: + skip = true; + break; + case SkipAll: + skip = true; + d->skipAllEncrypted = true; + break; + default: + ; + } + + if (ec != Ok && !skip) { + break; + } + + ++it; + } + + return ec; +} + +/*! + 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) +{ + if (!d->device) + return NoOpenArchive; + if (!d->headers) + return FileNotFound; + + 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* outDev, ExtractionOptions options) +{ + if (!d->device) + return NoOpenArchive; + if (!d->headers) + return FileNotFound; + if (!outDev) + 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, outDev, 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) +{ + if (!d->device) + return NoOpenArchive; + if (!d->headers) + return Ok; + + 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) +{ + if (!d->device) + return NoOpenArchive; + if (!d->headers) + return Ok; + + 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; +} + +OSDAB_END_NAMESPACE diff --git a/oracle/src/zip/unzip.h b/oracle/src/zip/unzip.h new file mode 100755 index 00000000..e095a228 --- /dev/null +++ b/oracle/src/zip/unzip.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** Filename: unzip.h +** Last updated [dd/mm/yyyy]: 27/03/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 "zipglobal.h" + +#include +#include +#include + +#include + +class QDir; +class QFile; +class QIODevice; +class QString; +class QStringList; + +OSDAB_BEGIN_NAMESPACE(Zip) + +class UnzipPrivate; + +class OSDAB_ZIP_EXPORT 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 + { + ExtractPaths = 0x0001, + SkipPaths = 0x0002, + VerifyOnly = 0x0004, + NoSilentDirectoryCreation = 0x0008 + }; + Q_DECLARE_FLAGS(ExtractionOptions, ExtractionOption) + + enum CompressionMethod + { + NoCompression, Deflated, UnknownCompression + }; + + enum FileType + { + File, Directory + }; + + 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 verifyArchive(); + + 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) + +OSDAB_END_NAMESPACE + +#endif // OSDAB_UNZIP__H diff --git a/oracle/src/zip/unzip_p.h b/oracle/src/zip/unzip_p.h new file mode 100755 index 00000000..21e9f2a2 --- /dev/null +++ b/oracle/src/zip/unzip_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** Filename: unzip_p.h +** Last updated [dd/mm/yyyy]: 27/03/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 +#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) + +OSDAB_BEGIN_NAMESPACE(Zip) + +class UnzipPrivate : public QObject +{ + Q_OBJECT + +public: + UnzipPrivate(); + + // Replace this with whatever else you use to store/retrieve the password. + QString password; + + bool skipAllEncrypted; + + QMap* headers; + + QIODevice* device; + QFile* file; + + 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, const ZipEntryP& entry); + + void closeArchive(); + + UnZip::ErrorCode extractFile(const QString& path, const ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options); + UnZip::ErrorCode extractFile(const QString& path, const 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; + +private slots: + void deviceDestroyed(QObject*); + +private: + UnZip::ErrorCode extractStoredFile(const quint32 szComp, quint32** keys, + quint32& myCRC, QIODevice* outDev, UnZip::ExtractionOptions options); + UnZip::ErrorCode inflateFile(const quint32 szComp, quint32** keys, + quint32& myCRC, QIODevice* outDev, UnZip::ExtractionOptions options); + void do_closeArchive(); +}; + +OSDAB_END_NAMESPACE + +#endif // OSDAB_UNZIP_P__H diff --git a/oracle/src/zip/zip.cpp b/oracle/src/zip/zip.cpp new file mode 100755 index 00000000..aa33bfc7 --- /dev/null +++ b/oracle/src/zip/zip.cpp @@ -0,0 +1,1619 @@ +/**************************************************************************** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 + + +/*! #define OSDAB_ZIP_NO_PNG_RLE to disable the use of Z_RLE compression strategy with + PNG files (achieves slightly better compression levels according to the authors). +*/ +// #define OSDAB_ZIP_NO_PNG_RLE + +#define OSDAB_ZIP_NO_DEBUG + +//! 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 + +/*! + \class Zip zip.h + + \brief Zip file compression. + + Some quick usage examples. + + \verbatim + Suppose you have this directory structure: + + /home/user/dir1/file1.1 + /home/user/dir1/file1.2 + /home/user/dir1/dir1.1/ + /home/user/dir1/dir1.2/file1.2.1 + + EXAMPLE 1: + myZipInstance.addDirectory("/home/user/dir1"); + + RESULT: + Beheaves like any common zip software and creates a zip file with this structure: + + dir1/file1.1 + dir1/file1.2 + dir1/dir1.1/ + dir1/dir1.2/file1.2.1 + + EXAMPLE 2: + myZipInstance.addDirectory("/home/user/dir1", "myRoot/myFolder"); + + RESULT: + Adds a custom root to the paths and creates a zip file with this structure: + + 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("/home/user/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: + + /home/user/dir1/file1.1 + /home/user/dir1/file1.2 + /home/user/dir1/dir1.1/ + /home/user/dir1/dir1.2/file1.2.1 + + EXAMPLE 4: + myZipInstance.setPassword("hellopass"); + myZipInstance.addDirectory("/home/user/dir1", "/"); + + RESULT: + Adds and encrypts the files in /home/user/dir1, creating the following zip structure: + + /dir1/file1.1 + /dir1/file1.2 + /dir1/dir1.1/ + /dir1/dir1.2/file1.2.1 + + EXAMPLE 5: + myZipInstance.addDirectory("/home/user/dir1", Zip::IgnoreRoot); + + RESULT: + Adds the files in /home/user/dir1 but doesn't create the top level + directory: + + file1.1 + file1.2 + dir1.1/ + dir1.2/file1.2.1 + + EXAMPLE 5: + myZipInstance.addDirectory("/home/user/dir1", "data/backup", Zip::IgnoreRoot); + + RESULT: + Adds the files in /home/user/dir1 but uses "data/backup" as top level + directory instead of "dir1": + + data/backup/file1.1 + data/backup/file1.2 + data/backup/dir1.1/ + data/backup/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. +*/ + +namespace { + +struct ZippedDir { + bool init; + QString actualRoot; + int files; + ZippedDir() : init(false), actualRoot(), files(0) {} +}; + +void checkRootPath(QString& path) +{ + const bool isUnixRoot = path.length() == 1 && path.at(0) == QLatin1Char('/'); + if (!path.isEmpty() && !isUnixRoot) { + while (path.endsWith(QLatin1String("\\"))) + path.truncate(path.length() - 1); + + int sepCount = 0; + for (int i = path.length()-1; i >= 0; --i) { + if (path.at(i) == QLatin1Char('/')) + ++sepCount; + else break; + } + + if (sepCount > 1) + path.truncate(path.length() - (sepCount-1)); + else if (sepCount == 0) + path.append(QLatin1String("/")); + } +} + +} + +////////////////////////////////////////////////////////////////////////// + +OSDAB_BEGIN_NAMESPACE(Zip) + +/************************************************************************ + Private interface +*************************************************************************/ + +//! \internal +ZipPrivate::ZipPrivate() : + headers(0), + device(0), + file(0), + uBuffer(0), + crcTable(0), + comment(), + password() +{ + // keep an unsigned pointer so we avoid to over bloat the code with casts + uBuffer = (unsigned char*) buffer1; + crcTable = get_crc_table(); +} + +//! \internal +ZipPrivate::~ZipPrivate() +{ + closeArchive(); +} + +//! \internal +Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev) +{ + Q_ASSERT(dev); + + if (device) + closeArchive(); + + device = dev; + if (device != file) + connect(device, SIGNAL(destroyed(QObject*)), this, SLOT(deviceDestroyed(QObject*))); + + 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 +void ZipPrivate::deviceDestroyed(QObject*) +{ + qDebug("Unexpected device destruction detected."); + do_closeArchive(); +} + +/*! Returns true if an entry for \p info has already been added. + Uses file size and lower case absolute path to compare entries. +*/ +bool ZipPrivate::containsEntry(const QFileInfo& info) const +{ + if (!headers || headers->isEmpty()) + return false; + + const qint64 sz = info.size(); + const QString path = info.absoluteFilePath().toLower(); + + QMap::ConstIterator b = headers->constBegin(); + const QMap::ConstIterator e = headers->constEnd(); + while (b != e) { + const ZipEntryP* e = b.value(); + if (e->fileSize == sz && e->absolutePath == path) + return true; + ++b; + } + + return false; +} + +//! \internal Actual implementation of the addDirectory* methods. +Zip::ErrorCode ZipPrivate::addDirectory(const QString& path, const QString& root, + Zip::CompressionOptions options, Zip::CompressionLevel level, int hierarchyLevel, + int* addedFiles) +{ + if (addedFiles) + ++(*addedFiles); + + // Bad boy didn't call createArchive() yet :) + if (!device) + return Zip::NoOpenArchive; + + QDir dir(path); + if (!dir.exists()) + return Zip::FileNotFound; + + // Remove any trailing separator + QString actualRoot = root.trimmed(); + + // Preserve Unix root but make sure the path ends only with a single + // unix like separator + ::checkRootPath(actualRoot); + + // QDir::cleanPath() fixes some issues with QDir::dirName() + QFileInfo current(QDir::cleanPath(path)); + + const bool path_absolute = options.testFlag(Zip::AbsolutePaths); + const bool path_ignore = options.testFlag(Zip::IgnorePaths); + const bool path_noroot = options.testFlag(Zip::IgnoreRoot); + + if (path_absolute && !path_ignore && !path_noroot) { + QString absolutePath = extractRoot(path, options); + if (!absolutePath.isEmpty() && absolutePath != QLatin1String("/")) + absolutePath.append(QLatin1String("/")); + actualRoot.append(absolutePath); + } + + const bool skipDirName = !hierarchyLevel && path_noroot; + if (!path_ignore && !skipDirName) { + actualRoot.append(QDir(current.absoluteFilePath()).dirName()); + actualRoot.append(QLatin1String("/")); + } + + // actualRoot now contains the path of the file relative to the zip archive + // with a trailing / + + const bool skipBad = options & Zip::SkipBadFiles; + const bool noDups = options & Zip::CheckForDuplicates; + + const QDir::Filters dir_filter = + QDir::Files | + QDir::Dirs | + QDir::NoDotAndDotDot | + QDir::NoSymLinks; + const QDir::SortFlags dir_sort = + QDir::DirsFirst; + QFileInfoList list = dir.entryInfoList(dir_filter, dir_sort); + + Zip::ErrorCode ec = Zip::Ok; + bool filesAdded = false; + + Zip::CompressionOptions recursionOptions; + if (path_ignore) + recursionOptions |= Zip::IgnorePaths; + else recursionOptions |= Zip::RelativePaths; + + for (int i = 0; i < list.size(); ++i) { + QFileInfo info = list.at(i); + const QString absPath = info.absoluteFilePath(); + if (noDups && containsEntry(info)) + continue; + if (info.isDir()) { + // Recursion + ec = addDirectory(absPath, actualRoot, recursionOptions, + level, hierarchyLevel + 1, addedFiles); + } else { + ec = createEntry(info, actualRoot, level); + if (ec == Zip::Ok) { + filesAdded = true; + if (addedFiles) + ++(*addedFiles); + } + } + + if (ec != Zip::Ok && !skipBad) { + break; + } + } + + // 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 && !path_ignore) + ec = createEntry(current, actualRoot, level); + + return ec; +} + +//! \internal Actual implementation of the addFile methods. +Zip::ErrorCode ZipPrivate::addFiles(const QStringList& files, const QString& root, + Zip::CompressionOptions options, Zip::CompressionLevel level, + int* addedFiles) +{ + if (addedFiles) + *addedFiles = 0; + + const bool skipBad = options & Zip::SkipBadFiles; + const bool noDups = options & Zip::CheckForDuplicates; + + // Bad boy didn't call createArchive() yet :) + if (!device) + return Zip::NoOpenArchive; + + QFileInfoList paths; + paths.reserve(files.size()); + for (int i = 0; i < files.size(); ++i) { + QFileInfo info(files.at(i)); + if (noDups && (paths.contains(info) || containsEntry(info))) + continue; + if (!info.exists() || !info.isReadable()) { + if (skipBad) { + continue; + } else { + return Zip::FileNotFound; + } + } + paths.append(info); + } + + if (paths.isEmpty()) + return Zip::Ok; + + // Remove any trailing separator + QString actualRoot = root.trimmed(); + + // Preserve Unix root but make sure the path ends only with a single + // unix like separator + ::checkRootPath(actualRoot); + + const bool path_absolute = options.testFlag(Zip::AbsolutePaths); + const bool path_ignore = options.testFlag(Zip::IgnorePaths); + const bool path_noroot = options.testFlag(Zip::IgnoreRoot); + + Zip::ErrorCode ec = Zip::Ok; + QHash dirMap; + + for (int i = 0; i < paths.size(); ++i) { + const QFileInfo& info = paths.at(i); + const QString path = QFileInfo(QDir::cleanPath(info.absolutePath())).absolutePath(); + + ZippedDir& zd = dirMap[path]; + if (!zd.init) { + zd.init = true; + zd.actualRoot = actualRoot; + if (path_absolute && !path_ignore && !path_noroot) { + QString absolutePath = extractRoot(path, options); + if (!absolutePath.isEmpty() && absolutePath != QLatin1String("/")) + absolutePath.append(QLatin1String("/")); + zd.actualRoot.append(absolutePath); + } + + if (!path_ignore && !path_noroot) { + zd.actualRoot.append(QDir(path).dirName()); + zd.actualRoot.append(QLatin1String("/")); + } + } + + // zd.actualRoot now contains the path of the file relative to the zip archive + // with a trailing / + + if (info.isDir()) { + // Recursion + ec = addDirectory(info.absoluteFilePath(), actualRoot, options, + level, 1, addedFiles); + } else { + ec = createEntry(info, actualRoot, level); + if (ec == Zip::Ok) { + ++zd.files; + if (addedFiles) + ++(*addedFiles); + } + } + + if (ec != Zip::Ok && !skipBad) { + break; + } + } + + // Create explicit records for empty directories + if (!path_ignore) { + QHash::ConstIterator b = dirMap.constBegin(); + const QHash::ConstIterator e = dirMap.constEnd(); + while (b != e) { + const ZippedDir& zd = b.value(); + if (zd.files <= 0) { + ec = createEntry(b.key(), zd.actualRoot, level); + } + ++b; + } + } + + return ec; +} + +//! \internal \p file must be a file and not a directory. +Zip::ErrorCode ZipPrivate::deflateFile(const QFileInfo& fileInfo, + quint32& crc, qint64& written, const Zip::CompressionLevel& level, quint32** keys) +{ + const QString path = fileInfo.absoluteFilePath(); + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << QString("An error occurred while opening %1").arg(path); + return Zip::OpenFailed; + } + + const Zip::ErrorCode ec = (level == Zip::Store) + ? storeFile(path, file, crc, written, keys) + : compressFile(path, file, crc, written, level, keys); + + file.close(); + return ec; +} + +//! \internal +Zip::ErrorCode ZipPrivate::storeFile(const QString& path, QIODevice& file, + quint32& crc, qint64& totalWritten, quint32** keys) +{ + Q_UNUSED(path); + + qint64 read = 0; + qint64 written = 0; + + const bool encrypt = keys != 0; + + totalWritten = 0; + crc = crc32(0L, Z_NULL, 0); + + while ( (read = file.read(buffer1, ZIP_READ_BUFFER)) > 0 ) { + crc = crc32(crc, uBuffer, read); + if (encrypt) + encryptBytes(*keys, buffer1, read); + written = device->write(buffer1, read); + totalWritten += written; + if (written != read) { + return Zip::WriteFailed; + } + } + + return Zip::Ok; +} + +//! \internal +int ZipPrivate::compressionStrategy(const QString& path, QIODevice& file) const +{ + Q_UNUSED(file); + +#ifndef OSDAB_ZIP_NO_PNG_RLE + return Z_DEFAULT_STRATEGY; +#endif + const bool isPng = path.endsWith(QLatin1String("png"), Qt::CaseInsensitive); + return isPng ? Z_RLE : Z_DEFAULT_STRATEGY; +} + +//! \internal +Zip::ErrorCode ZipPrivate::compressFile(const QString& path, QIODevice& file, + quint32& crc, qint64& totalWritten, const Zip::CompressionLevel& level, quint32** keys) +{ + qint64 read = 0; + qint64 written = 0; + + qint64 totRead = 0; + qint64 toRead = file.size(); + + const bool encrypt = keys != 0; + const int strategy = compressionStrategy(path, file); + + totalWritten = 0; + crc = crc32(0L, Z_NULL, 0); + + 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, // compression level + Z_DEFLATED, // method + -MAX_WBITS, // windowBits + 8, // memLevel + strategy, + ZLIB_VERSION, + sizeof(z_stream) + )) != Z_OK ) { + qDebug() << "Could not initialize zlib for compression"; + return Zip::ZlibError; + } + + qint64 compressed; + int flush = Z_NO_FLUSH; + do { + read = file.read(buffer1, ZIP_READ_BUFFER); + totRead += read; + if (!read) + break; + + if (read < 0) { + deflateEnd(&zstr); + qDebug() << QString("Error while reading %1").arg(path); + 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 (encrypt) + encryptBytes(*keys, buffer2, compressed); + + written = device->write(buffer2, compressed); + totalWritten += written; + + if (written != compressed) { + deflateEnd(&zstr); + qDebug() << QString("Error while writing %1").arg(path); + return Zip::WriteFailed; + } + + } 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); + + 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) +{ + const bool dirOnly = file.isDir(); + + // entryName contains the path as it should be written + // in the zip file records + const QString entryName = dirOnly + ? root + : root + file.fileName(); + + // Directory entry + if (dirOnly || file.size() < ZIP_COMPRESSION_THRESHOLD) { + level = Zip::Store; + } else { + switch (level) { + case Zip::AutoCPU: + level = Zip::Deflate5; +#ifndef OSDAB_ZIP_NO_DEBUG + qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); +#endif + break; + case Zip::AutoMIME: + level = detectCompressionByMime(file.completeSuffix().toLower()); +#ifndef OSDAB_ZIP_NO_DEBUG + qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); +#endif + break; + case Zip::AutoFull: + level = detectCompressionByMime(file.completeSuffix().toLower()); +#ifndef OSDAB_ZIP_NO_DEBUG + qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); +#endif + break; + default: ; + } + } + + + + // create header and store it to write a central directory later + QScopedPointer h(new ZipEntryP); + h->absolutePath = file.absoluteFilePath().toLower(); + h->fileSize = file.size(); + + // 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(); + dt = OSDAB_ZIP_MANGLE(fromFileTimestamp)(dt); + 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(); + + h->compMethod = (level == Zip::Store) ? 0 : 0x0008; + + // **** 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.toLatin1(); + 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) { + return Zip::WriteFailed; + } + + // Write out filename + if (device->write(entryNameBytes) != sz) { + 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) { + return Zip::WriteFailed; + } + } + + quint32 crc = 0; + qint64 written = 0; + + if (!dirOnly) { + quint32* k = keys; + const Zip::ErrorCode ec = deflateFile(file, crc, written, level, encrypt ? &k : 0); + if (ec != Zip::Ok) + return ec; + Q_ASSERT(!h.isNull()); + } + + // Store end of entry offset + quint32 current = device->pos(); + + // Update crc and compressed size in local header + if (!device->seek(crcOffset)) { + 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) { + return Zip::WriteFailed; + } + + // Seek to end of entry + if (!device->seek(current)) { + 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) { + return Zip::WriteFailed; + } + } + + headers->insert(entryName, h.take()); + 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.toLatin1(); + int sz = pwdBytes.size(); + const char* ascii = pwdBytes.data(); + + for (int i = 0; i < sz; ++i) + updateKeys(keys, (int)ascii[i]); +} + +//! Updates a one-char-only CRC; it's the Info-Zip macro re-adapted. +quint32 ZipPrivate::updateChecksum(const quint32& crc, const quint32& val) const +{ + return quint32(crcTable[quint32(crc^val) & 0xff] ^ crc_t(crc >> 8)); +} + +//! \internal Updates encryption keys. +void ZipPrivate::updateKeys(quint32* keys, int c) const +{ + keys[0] = updateChecksum(keys[0], c); + keys[1] += keys[0] & 0xff; + keys[1] = keys[1] * 134775813L + 1; + keys[2] = updateChecksum(keys[2], ((int)keys[1]) >> 24); +} + +//! \internal Encrypts a byte array. +void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read) +{ + char t; + + for (qint64 i = 0; i < read; ++i) { + t = buffer[i]; + buffer[i] ^= decryptByte(keys[2]); + updateKeys(keys, t); + } +} + +namespace { +struct KeywordHelper { + const QString needle; + inline KeywordHelper(const QString& keyword) : needle(keyword) {} +}; + +bool operator<(const KeywordHelper& helper, const char* keyword) { + return helper.needle.compare(QLatin1String(keyword)) < 0; +} + +bool operator<(const char* keyword, const KeywordHelper& helper) { + return helper.needle.compare(QLatin1String(keyword)) > 0; +} + +bool hasExtension(const QString& ext, const char* const* map, int max) { + const char* const* start = &map[0]; + const char* const* end = &map[max - 1]; + const char* const* kw = qBinaryFind(start, end, KeywordHelper(ext)); + return kw != end; +} +} + +//! \internal Detects the best compression level for a given file extension. +Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext) +{ + // NOTE: Keep the MAX_* and the number of strings in the map up to date. + // NOTE: Alphabetically sort the strings in the map -- we use a binary search! + + // Archives or files that will hardly compress + const int MAX_EXT1 = 14; + const char* const ext1[MAX_EXT1] = { + "7z", "bin", "deb", "exe", "gz", "gz2", "jar", "rar", "rpm", "tar", "tgz", "z", "zip", + 0 // # MAX_EXT1 + }; + + // Slow or usually large files that we should not spend to much time with + const int MAX_EXT2 = 24; + const char* const ext2[MAX_EXT2] = { + "asf", + "avi", + "divx", + "doc", + "docx", + "flv", + "gif", + "iso", + "jpg", + "jpeg", + "mka", + "mkv", + "mp3", + "mp4", + "mpeg", + "mpg", + "odt", + "ogg", + "ogm", + "ra", + "rm", + "wma", + "wmv", + 0 // # MAX_EXT2 + }; + + // Files with high compression ratio + const int MAX_EXT3 = 28; + const char* const ext3[MAX_EXT3] = { + "asp", "bat", "c", "conf", "cpp", "cpp", "css", "csv", "cxx", "h", "hpp", "htm", "html", "hxx", + "ini", "js", "php", "pl", "py", "rtf", "sh", "tsv", "txt", "vb", "vbs", "xml", "xst", + 0 // # MAX_EXT3 + }; + + const char* const* map = ext1; + if (hasExtension(ext, map, MAX_EXT1)) + return Zip::Store; + + map = ext2; + if (hasExtension(ext, map, MAX_EXT2)) + return Zip::Deflate2; + + map = ext3; + if (hasExtension(ext, map, MAX_EXT3)) + return Zip::Deflate9; + + return Zip::Deflate5; +} + +/*! + Closes the current archive and writes out pending data. +*/ +Zip::ErrorCode ZipPrivate::closeArchive() +{ + if (!device) { + Q_ASSERT(!file); + return Zip::Ok; + } + + if (device != file) + disconnect(device, 0, this, 0); + + return do_closeArchive(); +} + +//! \internal +Zip::ErrorCode ZipPrivate::do_closeArchive() +{ + // Close current archive by writing out central directory + // and free up resources + + if (!device && !headers) + return Zip::Ok; + + quint32 szCentralDir = 0; + quint32 offCentralDir = device->pos(); + Zip::ErrorCode c = Zip::Ok; + + if (headers && device) { + for (QMap::ConstIterator itr = headers->constBegin(); + itr != headers->constEnd(); ++itr) { + const QString fileName = itr.key(); + const ZipEntryP* h = itr.value(); + c = writeEntry(fileName, h, szCentralDir); + } + } + + if (c == Zip::Ok) + c = writeCentralDir(offCentralDir, szCentralDir); + + if (c != Zip::Ok) { + if (file) { + file->close(); + if (!file->remove()) { + qDebug() << "Failed to delete corrupt archive."; + } + } + } + + return c; +} + +//! \internal +Zip::ErrorCode ZipPrivate::writeEntry(const QString& fileName, const ZipEntryP* h, quint32& szCentralDir) +{ + unsigned int sz; + + Q_ASSERT(h && device && headers); + + // 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 = fileName.toLatin1(); + 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) { + return Zip::WriteFailed; + } + + // Write out filename + if ((unsigned int)device->write(fileNameBytes) != sz) { + return Zip::WriteFailed; + } + + szCentralDir += (ZIP_CD_SIZE + sz); + + return Zip::Ok; +} + +//! \internal +Zip::ErrorCode ZipPrivate::writeCentralDir(quint32 offCentralDir, quint32 szCentralDir) +{ + Q_ASSERT(device && headers); + + unsigned int sz; + + // 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.toLatin1(); + 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) { + return Zip::WriteFailed; + } + + if (commentLength != 0) { + if ((unsigned int)device->write(commentBytes) != commentLength) { + return Zip::WriteFailed; + } + } + + return Zip::Ok; +} + +//! \internal +void ZipPrivate::reset() +{ + comment.clear(); + + if (headers) { + qDeleteAll(*headers); + delete headers; + headers = 0; + } + + device = 0; + + if (file) + delete file; + file = 0; +} + +//! \internal Returns the path of the parent directory +QString ZipPrivate::extractRoot(const QString& p, Zip::CompressionOptions o) +{ + Q_UNUSED(o); + QDir d(QDir::cleanPath(p)); + if (!d.exists()) + return QString(); + + if (!d.cdUp()) + return QString(); + + return d.absolutePath(); +} + + +/************************************************************************ + 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; +} + +/*! + 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) +{ + closeArchive(); + Q_ASSERT(!d->device && !d->file); + + if (filename.isEmpty()) + return Zip::FileNotFound; + + d->file = new QFile(filename); + + if (d->file->exists() && !overwrite) { + delete d->file; + d->file = 0; + return Zip::FileExists; + } + + if (!d->file->open(QIODevice::WriteOnly)) { + delete d->file; + d->file = 0; + return Zip::OpenFailed; + } + + const Zip::ErrorCode ec = createArchive(d->file); + if (ec != Zip::Ok) { + closeArchive(); + } + + 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) { + 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) +{ + d->comment = comment; +} + +/*! + 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. + + The result is that all files found in \p path (and in subdirectories) are + added to the zip file without a directory entry. +*/ +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. + + The result is that all files found in \p path (and in subdirectories) are + added to the zip file without a directory entry (or within a directory + structure specified by \p root). +*/ +Zip::ErrorCode Zip::addDirectoryContents(const QString& path, const QString& root, CompressionLevel level) +{ + return addDirectory(path, root, IgnorePaths, level); +} + +/*! + Convenience method, same as calling + Zip::addDirectory(const QString&,const QString&,CompressionLevel) + with an empty \p root parameter and Zip::RelativePaths flag as compression option. + */ +Zip::ErrorCode Zip::addDirectory(const QString& path, CompressionLevel level) +{ + return addDirectory(path, QString(), Zip::RelativePaths, 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); +} + +/*! + 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!). + + If \p addedFiles is not null it is set to the number of successfully added + files. +*/ +Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, + CompressionOptions options, CompressionLevel level, int* addedFiles) +{ + const int hierarchyLev = 0; + return d->addDirectory(path, root, options, level, hierarchyLev, addedFiles); +} + +/*! + Convenience method, same as calling Zip::addFile(const QString&,const QString&,CompressionOptions,CompressionLevel) + with an empty \p root parameter and Zip::RelativePaths as compression option. + */ +Zip::ErrorCode Zip::addFile(const QString& path, CompressionLevel level) +{ + return addFile(path, QString(), Zip::RelativePaths, level); +} + +/*! + Convenience method, same as calling Zip::addFile(const QString&,const QString&,CompressionOptions,CompressionLevel) + with the Zip::RelativePaths flag as compression option. + */ +Zip::ErrorCode Zip::addFile(const QString& path, const QString& root, + CompressionLevel level) +{ + return addFile(path, root, Zip::RelativePaths, level); +} + +/*! + Adds the file at \p path to the archive, using \p root as name for the root folder. + If \p path points to a directory the behaviour is basically the same as + addDirectory(). + + 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::addFile(const QString& path, const QString& root, + CompressionOptions options, CompressionLevel level) +{ + if (path.isEmpty()) + return Zip::Ok; + return addFiles(QStringList() << path, root, options, level); +} + +/*! + Convenience method, same as calling Zip::addFiles(const QStringList&,const QString&,CompressionOptions,CompressionLevel) + with an empty \p root parameter and Zip::RelativePaths as compression option. + */ +Zip::ErrorCode Zip::addFiles(const QStringList& paths, CompressionLevel level) +{ + return addFiles(paths, QString(), Zip::RelativePaths, level); +} + +/*! + Convenience method, same as calling Zip::addFiles(const QStringList&,const QString&,CompressionOptions,CompressionLevel) + with the Zip::RelativePaths flag as compression option. + */ +Zip::ErrorCode Zip::addFiles(const QStringList& paths, const QString& root, + CompressionLevel level) +{ + return addFiles(paths, root, Zip::RelativePaths, level); +} + +/*! + Adds the files or directories in \p paths to the archive, using \p root as + name for the root folder. + This is similar to calling addFile or addDirectory for all the entries in + \p paths, except it is slightly faster. + + 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!). + + If \p addedFiles is not null it is set to the number of successfully added + files. +*/ +Zip::ErrorCode Zip::addFiles(const QStringList& paths, const QString& root, + CompressionOptions options, CompressionLevel level, int* addedFiles) +{ + return d->addFiles(paths, root, options, level, addedFiles); +} + +/*! + 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."); +} + +OSDAB_END_NAMESPACE diff --git a/oracle/src/zip/zip.h b/oracle/src/zip/zip.h new file mode 100755 index 00000000..79c512ce --- /dev/null +++ b/oracle/src/zip/zip.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** Filename: zip.h +** Last updated [dd/mm/yyyy]: 27/03/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 "zipglobal.h" + +#include +#include + +#include + +class QIODevice; +class QFile; +class QDir; +class QStringList; +class QString; + +OSDAB_BEGIN_NAMESPACE(Zip) + +class ZipPrivate; + +class OSDAB_ZIP_EXPORT Zip +{ +public: + enum ErrorCode + { + Ok, + ZlibInit, + ZlibError, + FileExists, + OpenFailed, + NoOpenArchive, + FileNotFound, + ReadFailed, + WriteFailed, + SeekFailed, + InternalError + }; + + 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 or 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, + /*! Works only with addDirectory(). Adds the directory's contents, + including subdirectories, but does not add an entry for the root + directory itself. */ + IgnoreRoot = 0x0008, + /*! Used only when compressing a directory or multiple files. + If set invalid or unreadable files are simply skipped. + */ + SkipBadFiles = 0x0020, + /*! Makes sure a file is never added twice to the same zip archive. + This check is only necessary in certain usage scenarios and given + that it slows down processing you need to enable it explicitly with + this flag. + */ + CheckForDuplicates = 0x0040 + }; + 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, + CompressionLevel level = AutoFull); + ErrorCode addDirectory(const QString& path, const QString& root, + CompressionLevel level = AutoFull); + ErrorCode addDirectory(const QString& path, const QString& root, + CompressionOptions options, CompressionLevel level = AutoFull, + int* addedFiles = 0); + + ErrorCode addFile(const QString& path, + CompressionLevel level = AutoFull); + ErrorCode addFile(const QString& path, const QString& root, + CompressionLevel level = AutoFull); + ErrorCode addFile(const QString& path, const QString& root, + CompressionOptions options, + CompressionLevel level = AutoFull); + + ErrorCode addFiles(const QStringList& paths, + CompressionLevel level = AutoFull); + ErrorCode addFiles(const QStringList& paths, const QString& root, + CompressionLevel level = AutoFull); + ErrorCode addFiles(const QStringList& paths, const QString& root, + CompressionOptions options, + CompressionLevel level = AutoFull, + int* addedFiles = 0); + + ErrorCode closeArchive(); + + QString formatError(ErrorCode c) const; + +private: + ZipPrivate* d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Zip::CompressionOptions) + +OSDAB_END_NAMESPACE + +#endif // OSDAB_ZIP__H diff --git a/oracle/src/zip/zip_p.h b/oracle/src/zip/zip_p.h new file mode 100755 index 00000000..81c2dacd --- /dev/null +++ b/oracle/src/zip/zip_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** Filename: zip_p.h +** Last updated [dd/mm/yyyy]: 27/03/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 +#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) + +OSDAB_BEGIN_NAMESPACE(Zip) + +class ZipPrivate : public QObject +{ + Q_OBJECT + +public: + // uLongf from zconf.h + typedef uLongf crc_t; + + ZipPrivate(); + virtual ~ZipPrivate(); + + QMap* headers; + + QIODevice* device; + QFile* file; + + char buffer1[ZIP_READ_BUFFER]; + char buffer2[ZIP_READ_BUFFER]; + + unsigned char* uBuffer; + + const crc_t* crcTable; + + QString comment; + QString password; + + Zip::ErrorCode createArchive(QIODevice* device); + Zip::ErrorCode closeArchive(); + void reset(); + + bool zLibInit(); + + bool containsEntry(const QFileInfo& info) const; + + Zip::ErrorCode addDirectory(const QString& path, const QString& root, + Zip::CompressionOptions options, Zip::CompressionLevel level, + int hierarchyLevel, int* addedFiles = 0); + Zip::ErrorCode addFiles(const QStringList& paths, const QString& root, + Zip::CompressionOptions options, Zip::CompressionLevel level, + int* addedFiles); + + Zip::ErrorCode createEntry(const QFileInfo& file, const QString& root, + Zip::CompressionLevel level); + Zip::CompressionLevel detectCompressionByMime(const QString& ext); + + inline quint32 updateChecksum(const quint32& crc, const quint32& val) const; + + 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, Zip::CompressionOptions o); + +private slots: + void deviceDestroyed(QObject*); + +private: + int compressionStrategy(const QString& path, QIODevice& file) const; + Zip::ErrorCode deflateFile(const QFileInfo& fileInfo, + quint32& crc, qint64& written, const Zip::CompressionLevel& level, quint32** keys); + Zip::ErrorCode storeFile(const QString& path, QIODevice& file, + quint32& crc, qint64& written, quint32** keys); + Zip::ErrorCode compressFile(const QString& path, QIODevice& file, + quint32& crc, qint64& written, const Zip::CompressionLevel& level, quint32** keys); + Zip::ErrorCode do_closeArchive(); + Zip::ErrorCode writeEntry(const QString& fileName, const ZipEntryP* h, quint32& szCentralDir); + Zip::ErrorCode writeCentralDir(quint32 offCentralDir, quint32 szCentralDir); +}; + +OSDAB_END_NAMESPACE + +#endif // OSDAB_ZIP_P__H diff --git a/oracle/src/zip/zipentry_p.h b/oracle/src/zip/zipentry_p.h new file mode 100755 index 00000000..5c355f59 --- /dev/null +++ b/oracle/src/zip/zipentry_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** Filename: ZipEntryP.h +** Last updated [dd/mm/yyyy]: 27/03/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 + +OSDAB_BEGIN_NAMESPACE(Zip) + +class ZipEntryP +{ +public: + ZipEntryP() : + lhOffset(0), + dataOffset(0), + gpFlag(), + compMethod(0), + modTime(), + modDate(), + crc(0), + szComp(0), + szUncomp(0), + absolutePath(), + fileSize(0), + lhEntryChecked(false) + { + gpFlag[0] = gpFlag[1] = 0; + modTime[0] = modTime[1] = 0; + modDate[0] = modDate[1] = 0; + } + + quint32 lhOffset; // Offset of the local header record for this entry + mutable 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 + + QString absolutePath; // Internal use + qint64 fileSize; // Internal use + + mutable 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; } +}; + +OSDAB_END_NAMESPACE + +#endif // OSDAB_ZIPENTRY_P__H diff --git a/oracle/src/zip/zipglobal.cpp b/oracle/src/zip/zipglobal.cpp new file mode 100755 index 00000000..23e30814 --- /dev/null +++ b/oracle/src/zip/zipglobal.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** Filename: zipglobal.cpp +** Last updated [dd/mm/yyyy]: 06/02/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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 "zipglobal.h" + +#if defined(Q_OS_WIN) || defined(Q_OS_WINCE) || defined(Q_OS_LINUX) || defined (Q_OS_MACX) +#define OSDAB_ZIP_HAS_UTC +#include +#else +#undef OSDAB_ZIP_HAS_UTC +#endif + +#if defined(Q_OS_WIN) +#include +#elif defined(Q_OS_LINUX) || defined(Q_OS_MACX) +#include +#endif + +OSDAB_BEGIN_NAMESPACE(Zip) + +/*! Returns the current UTC offset in seconds unless OSDAB_ZIP_NO_UTC is defined + and method is implemented for the current platform and 0 otherwise. +*/ +int OSDAB_ZIP_MANGLE(currentUtcOffset)() +{ +#if !(!defined OSDAB_ZIP_NO_UTC && defined OSDAB_ZIP_HAS_UTC) + return 0; +#else + time_t curr_time_t; + time(&curr_time_t); + +#if defined Q_OS_WIN + struct tm _tm_struct; + struct tm* tm_struct = &_tm_struct; +#else + struct tm* tm_struct = 0; +#endif + +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) + // use the reentrant version of localtime() where available + tzset(); + tm res; + tm_struct = gmtime_r(&curr_time_t, &res); +#elif defined Q_OS_WIN && !defined Q_CC_MINGW + if (gmtime_s(tm_struct, &curr_time_t)) + return 0; +#else + tm_struct = gmtime(&curr_time_t); +#endif + + if (!tm_struct) + return 0; + + const time_t global_time_t = mktime(tm_struct); + +#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) + // use the reentrant version of localtime() where available + tm_struct = localtime_r(&curr_time_t, &res); +#elif defined Q_OS_WIN && !defined Q_CC_MINGW + if (localtime_s(tm_struct, &curr_time_t)) + return 0; +#else + tm_struct = localtime(&curr_time_t); +#endif + + if (!tm_struct) + return 0; + + const time_t local_time_t = mktime(tm_struct); + + const int utcOffset = - qRound(difftime(global_time_t, local_time_t)); + return tm_struct->tm_isdst > 0 ? utcOffset + 3600 : utcOffset; +#endif // No UTC + + return 0; +} + +QDateTime OSDAB_ZIP_MANGLE(fromFileTimestamp)(const QDateTime& dateTime) +{ +#if !defined OSDAB_ZIP_NO_UTC && defined OSDAB_ZIP_HAS_UTC + const int utc = OSDAB_ZIP_MANGLE(currentUtcOffset)(); + return dateTime.toUTC().addSecs(utc); +#else + return dateTime; +#endif // OSDAB_ZIP_NO_UTC +} + +bool OSDAB_ZIP_MANGLE(setFileTimestamp)(const QString& fileName, const QDateTime& dateTime) +{ + if (fileName.isEmpty()) + return true; + +#ifdef Q_OS_WIN + HANDLE hFile = CreateFile(fileName.toStdWString().c_str(), + GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + + SYSTEMTIME st; + FILETIME ft, ftLastMod; + const QDate date = dateTime.date(); + const QTime time = dateTime.time(); + st.wYear = date.year(); + st.wMonth = date.month(); + st.wDay = date.day(); + st.wHour = time.hour(); + st.wMinute = time.minute(); + st.wSecond = time.second(); + st.wMilliseconds = time.msec(); + + SystemTimeToFileTime(&st, &ft); + LocalFileTimeToFileTime(&ft, &ftLastMod); + + const bool success = SetFileTime(hFile, NULL, NULL, &ftLastMod); + CloseHandle(hFile); + return success; + +#elif defined(Q_OS_LINUX) || defined(Q_OS_MACX) + + struct utimbuf t_buffer; + t_buffer.actime = t_buffer.modtime = dateTime.toTime_t(); + return utime(fileName.toLocal8Bit().constData(), &t_buffer) == 0; +#endif + + return true; +} +OSDAB_END_NAMESPACE diff --git a/oracle/src/zip/zipglobal.h b/oracle/src/zip/zipglobal.h new file mode 100755 index 00000000..0b4c94f6 --- /dev/null +++ b/oracle/src/zip/zipglobal.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** Filename: zipglobal.h +** Last updated [dd/mm/yyyy]: 27/03/2011 +** +** 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-2012 Angius Fabrizio. All rights reserved. +** +** This file is part of the OSDaB project (http://osdab.42cows.org/). +** +** 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_ZIPGLOBAL__H +#define OSDAB_ZIPGLOBAL__H + +#include +#include + +/* If you want to build the OSDaB Zip code as + a library, define OSDAB_ZIP_LIB in the library's .pro file and + in the libraries using it OR remove the #ifndef OSDAB_ZIP_LIB + define below and leave the #else body. Also remember to define + OSDAB_ZIP_BUILD_LIB in the library's project). +*/ + +#ifndef OSDAB_ZIP_LIB +# define OSDAB_ZIP_EXPORT +#else +# if defined(OSDAB_ZIP_BUILD_LIB) +# define OSDAB_ZIP_EXPORT Q_DECL_EXPORT +# else +# define OSDAB_ZIP_EXPORT Q_DECL_IMPORT +# endif +#endif + +#ifdef OSDAB_NAMESPACE +#define OSDAB_BEGIN_NAMESPACE(ModuleName) namespace Osdab { namespace ModuleName { +#else +#define OSDAB_BEGIN_NAMESPACE(ModuleName) +#endif + +#ifdef OSDAB_NAMESPACE +#define OSDAB_END_NAMESPACE } } +#else +#define OSDAB_END_NAMESPACE +#endif + +#ifndef OSDAB_NAMESPACE +#define OSDAB_ZIP_MANGLE(x) zip_##x +#else +#define OSDAB_ZIP_MANGLE(x) x +#endif + +OSDAB_BEGIN_NAMESPACE(Zip) + +OSDAB_ZIP_EXPORT int OSDAB_ZIP_MANGLE(currentUtcOffset)(); +OSDAB_ZIP_EXPORT QDateTime OSDAB_ZIP_MANGLE(fromFileTimestamp)(const QDateTime& dateTime); +OSDAB_ZIP_EXPORT bool OSDAB_ZIP_MANGLE(setFileTimestamp)(const QString& fileName, const QDateTime& dateTime); + +OSDAB_END_NAMESPACE + +#endif // OSDAB_ZIPGLOBAL__H