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