From f64d0ec71de9b26da11c906fb9eadf7448797d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Tue, 10 Jun 2025 19:00:53 +0200 Subject: [PATCH 1/7] Add precon import dialog. --- .../widgets/dialogs/dlg_import_precons.cpp | 683 +++++++ .../widgets/dialogs/dlg_import_precons.h | 116 ++ cockatrice/src/interface/window_main.cpp | 10 + cockatrice/src/interface/window_main.h | 3 +- .../src/utility/external/lzma/decompress.cpp | 250 +++ .../src/utility/external/lzma/decompress.h | 19 + .../src/utility/external/qt-json/json.cpp | 573 ++++++ .../src/utility/external/qt-json/json.h | 204 +++ cockatrice/src/utility/external/zip/unzip.cpp | 1425 +++++++++++++++ cockatrice/src/utility/external/zip/unzip.h | 155 ++ cockatrice/src/utility/external/zip/unzip_p.h | 130 ++ cockatrice/src/utility/external/zip/zip.cpp | 1619 +++++++++++++++++ cockatrice/src/utility/external/zip/zip.h | 158 ++ cockatrice/src/utility/external/zip/zip_p.h | 133 ++ .../src/utility/external/zip/zipentry_p.h | 91 + .../src/utility/external/zip/zipglobal.cpp | 150 ++ .../src/utility/external/zip/zipglobal.h | 77 + 17 files changed, 5795 insertions(+), 1 deletion(-) create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_import_precons.h create mode 100644 cockatrice/src/utility/external/lzma/decompress.cpp create mode 100644 cockatrice/src/utility/external/lzma/decompress.h create mode 100644 cockatrice/src/utility/external/qt-json/json.cpp create mode 100644 cockatrice/src/utility/external/qt-json/json.h create mode 100755 cockatrice/src/utility/external/zip/unzip.cpp create mode 100644 cockatrice/src/utility/external/zip/unzip.h create mode 100755 cockatrice/src/utility/external/zip/unzip_p.h create mode 100755 cockatrice/src/utility/external/zip/zip.cpp create mode 100755 cockatrice/src/utility/external/zip/zip.h create mode 100755 cockatrice/src/utility/external/zip/zip_p.h create mode 100755 cockatrice/src/utility/external/zip/zipentry_p.h create mode 100644 cockatrice/src/utility/external/zip/zipglobal.cpp create mode 100755 cockatrice/src/utility/external/zip/zipglobal.h diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp new file mode 100644 index 000000000..c80508e70 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp @@ -0,0 +1,683 @@ +#include "dlg_import_precons.h" + +#include "../deck/deck_loader.h" +#include "../settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAS_LZMA +#include "../../src/utility/external/lzma/decompress.h" +#endif + +#ifdef HAS_ZLIB +#include "../../src/utility/external/zip/unzip.h" +#endif + +#define ZIP_SIGNATURE "PK" +// Xz stream header: 0xFD + "7zXZ" +#define XZ_SIGNATURE "\xFD\x37\x7A\x58\x5A" +#define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/" +#define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json" + +#define ALLDECKS_URL_FALLBACK "https://mtgjson.com/api/v5/AllDeckFiles.zip" + +#ifdef HAS_LZMA +#define ALLDECKS_URL "https://mtgjson.com/api/v5/AllDeckFiles.tar.xz" +#elif defined(HAS_ZLIB) +#define ALLDECKS_URL "https://mtgjson.com/api/v5/AllDeckFiles.zip" +#else +#define ALLDECKS_URL "" +#endif + +DlgImportPrecons::DlgImportPrecons(QWidget *parent) : QWizard(parent) +{ + // define a dummy context that will be used where needed + QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); + + nam = new QNetworkAccessManager(this); + + addPage(new LoadPreconsPage); + addPage(new SavePreconsPage); + + retranslateUi(); +} + +void DlgImportPrecons::retranslateUi() +{ + setWindowTitle(tr("Preconstructed Deck Importer")); +} + +void DlgImportPrecons::accept() +{ + QDialog::accept(); +} + +void DlgImportPrecons::enableButtons() +{ + button(QWizard::NextButton)->setDisabled(false); + button(QWizard::BackButton)->setDisabled(false); +} + +void DlgImportPrecons::disableButtons() +{ + button(QWizard::NextButton)->setDisabled(true); + button(QWizard::BackButton)->setDisabled(true); +} + +LoadPreconsPage::LoadPreconsPage(QWidget *parent) : QWizardPage(parent) +{ + urlRadioButton = new QRadioButton(this); + fileRadioButton = new QRadioButton(this); + + urlLineEdit = new QLineEdit(this); + fileLineEdit = new QLineEdit(this); + + progressLabel = new QLabel(this); + progressBar = new QProgressBar(this); + + urlRadioButton->setChecked(true); + + urlButton = new QPushButton(this); + connect(urlButton, &QPushButton::clicked, this, &LoadPreconsPage::actRestoreDefaultUrl); + + fileButton = new QPushButton(this); + connect(fileButton, &QPushButton::clicked, this, &LoadPreconsPage::actLoadPreconsFile); + + auto *layout = new QGridLayout(this); + layout->addWidget(urlRadioButton, 0, 0); + layout->addWidget(urlLineEdit, 0, 1); + layout->addWidget(urlButton, 1, 1, Qt::AlignRight); + layout->addWidget(fileRadioButton, 2, 0); + layout->addWidget(fileLineEdit, 2, 1); + layout->addWidget(fileButton, 3, 1, Qt::AlignRight); + layout->addWidget(progressLabel, 4, 0); + layout->addWidget(progressBar, 4, 1); + + connect(&watcher, &QFutureWatcher::finished, this, &LoadPreconsPage::importFinished); + + setLayout(layout); + + retranslateUi(); +} + +void LoadPreconsPage::initializePage() +{ + urlLineEdit->setText(ALLDECKS_URL); + + progressLabel->hide(); + progressBar->hide(); +} + +void LoadPreconsPage::retranslateUi() +{ + setTitle(tr("Source selection")); + setSubTitle(tr("Please specify a compatible source for the list of preconstructed Decks. " + "You can specify a URL address that will be downloaded or " + "use an existing file from your computer.")); + + urlRadioButton->setText(tr("Download URL:")); + fileRadioButton->setText(tr("Local file:")); + urlButton->setText(tr("Restore default URL")); + fileButton->setText(tr("Choose file...")); +} + +void LoadPreconsPage::actRestoreDefaultUrl() +{ + urlLineEdit->setText(ALLDECKS_URL); +} + +void LoadPreconsPage::actLoadPreconsFile() +{ + QFileDialog dialog(this, tr("Load preconstructed Deck file")); + dialog.setFileMode(QFileDialog::ExistingFile); + + QString extensions = "*.json *.xml"; +#ifdef HAS_ZLIB + extensions += " *.zip"; +#endif +#ifdef HAS_LZMA + extensions += " *.xz"; +#endif + dialog.setNameFilter(tr("Precons file (%1)").arg(extensions)); + + if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) { + dialog.selectFile(fileLineEdit->text()); + } + + if (!dialog.exec()) { + return; + } + + fileLineEdit->setText(dialog.selectedFiles().at(0)); +} + +bool LoadPreconsPage::validatePage() +{ + // once the import is finished, we call next(); skip validation + if (dynamic_cast(wizard())->doneWithParsing) { + return true; + } + + if (urlRadioButton->isChecked()) { + const auto url = QUrl::fromUserInput(urlLineEdit->text()); + + if (!url.isValid()) { + QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid.")); + return false; + } + + progressLabel->setText(tr("Downloading (0MB)")); + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->show(); + progressBar->show(); + + dynamic_cast(wizard())->disableButtons(); + setEnabled(false); + + downloadPreconsFile(url); + } else if (fileRadioButton->isChecked()) { + QFile setsFile(fileLineEdit->text()); + if (!setsFile.exists()) { + QMessageBox::critical(this, tr("Error"), tr("Please choose a file.")); + return false; + } + + if (!setsFile.open(QIODevice::ReadOnly)) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); + return false; + } + + dynamic_cast(wizard())->disableButtons(); + setEnabled(false); + + dynamic_cast(wizard())->setCardSourceUrl(setsFile.fileName()); + + readPreconsFromByteArray(setsFile.readAll()); + } + + return false; +} + +void LoadPreconsPage::downloadPreconsFile(const QUrl &url) +{ + const auto urlString = url.toString(); + if (urlString == ALLDECKS_URL || urlString == ALLDECKS_URL_FALLBACK) { + const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); + auto *versionReply = dynamic_cast(wizard())->nam->get(QNetworkRequest(versionUrl)); + connect(versionReply, &QNetworkReply::finished, [versionReply]() { + if (versionReply->error() == QNetworkReply::NoError) { + auto jsonData = versionReply->readAll(); + QJsonParseError jsonError{}; + auto jsonResponse = QJsonDocument::fromJson(jsonData, &jsonError); + + if (jsonError.error == QJsonParseError::NoError) { + const auto jsonMap = jsonResponse.toVariant().toMap(); + + auto versionString = jsonMap.value("meta").toMap().value("version").toString(); + if (versionString.isEmpty()) { + versionString = "unknown"; + } + } + } + + versionReply->deleteLater(); + }); + } + + dynamic_cast(wizard())->setCardSourceUrl(url.toString()); + + auto *reply = dynamic_cast(wizard())->nam->get(QNetworkRequest(url)); + + connect(reply, &QNetworkReply::finished, this, &LoadPreconsPage::actDownloadFinishedPreconsFile); + connect(reply, &QNetworkReply::downloadProgress, this, &LoadPreconsPage::actDownloadProgressPreconsFile); +} + +void LoadPreconsPage::actDownloadProgressPreconsFile(qint64 received, qint64 total) +{ + if (total > 0) { + progressBar->setMaximum(static_cast(total)); + progressBar->setValue(static_cast(received)); + } + progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024))); +} + +void LoadPreconsPage::actDownloadFinishedPreconsFile() +{ + // check for a reply + auto *reply = dynamic_cast(sender()); + auto errorCode = reply->error(); + if (errorCode != QNetworkReply::NoError) { + QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); + + dynamic_cast(wizard())->enableButtons(); + setEnabled(true); + + reply->deleteLater(); + return; + } + + auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode == 301 || statusCode == 302) { + const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + qDebug() << "following redirect url:" << redirectUrl.toString(); + downloadPreconsFile(redirectUrl); + reply->deleteLater(); + return; + } + + progressLabel->hide(); + progressBar->hide(); + + readPreconsFromByteArray(reply->readAll()); + reply->deleteLater(); +} + +#include +#include +#include +#include + +bool LoadPreconsPage::parsePreconsFromByteArray(const QByteArray &data, QString folderPath) +{ + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) { + qWarning() << "JSON parse error:" << parseError.errorString(); + return false; + } + + QJsonObject rootObj = doc.object(); + QJsonObject preconData = rootObj.value("data").toObject(); + + QString deckName = preconData.value("name").toString(); + QString shortName = preconData.value("code").toString().toUpper(); + QString deckType = preconData.value("type").toString(); + QJsonArray mainBoard = preconData.value("mainBoard").toArray(); + int releaseYear = preconData.value("releaseDate").toString().split("-").at(0).toInt(); + + qInfo() << "Importing '" << deckName << "' from" << shortName; + + auto *precon = new DeckLoader(); + + for (const auto &cardVal : mainBoard) { + QJsonObject cardObj = cardVal.toObject(); + QString name = cardObj.value("name").toString(); + QString setCode = cardObj.value("setCode").toString(); + QString number = cardObj.value("number").toString(); + int count = cardObj.value("count").toInt(); + QString scryfallId = cardObj.value("identifiers").toObject().value("scryfallId").toString(); + + DecklistCardNode *addedCard = precon->addCard(name, "main", -1, setCode, number, scryfallId); + if (count != 1) { + addedCard->setNumber(count); + } + } + + precon->setName(deckName); + + QJsonArray commanderArray = preconData.value("commander").toArray(); + if (!commanderArray.isEmpty()) { + QJsonObject commanderObj = commanderArray.first().toObject(); + QString commanderName = commanderObj.value("name").toString(); + QString commanderId = commanderObj.value("identifiers").toObject().value("scryfallId").toString(); + precon->setBannerCard(QPair(commanderName, commanderId)); + } else { + qInfo() << "No commander data found."; + } + + QString dirPath = QDir::cleanPath(folderPath + QDir::separator() + deckType + QDir::separator() + + QString::number(releaseYear) + QDir::separator() + shortName); + + QString fullPath = QDir(dirPath).filePath(precon->getName()); + + QDir dir; + if (!dir.exists(dirPath)) { + if (!dir.mkpath(dirPath)) { + qWarning() << "Failed to create directory:" << dirPath; + return false; + } + } + + if (precon->getCardList().length() > 1) { + precon->saveToFile(fullPath + ".cod", DeckLoader::CockatriceFormat); + } + + return true; +} + +void LoadPreconsPage::readPreconsFromByteArray(QByteArray _data) +{ + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->setText(tr("Parsing file")); + progressLabel->show(); + progressBar->show(); + + dynamic_cast(wizard())->doneWithParsing = false; + dynamic_cast(wizard())->xmlData.clear(); + readPreconsFromByteArrayRef(_data); +} + +QString LoadPreconsPage::createTmpDirectory() +{ + QTemporaryDir tempDir; + if (tempDir.isValid()) { + return tempDir.path(); + } + QString tmpPath = QDir::cleanPath(SettingsCache::instance().getDeckPath() + "/Precons/tmp"); + QDir tmpDir(tmpPath); + if (!tmpDir.exists()) { + if (!QDir().mkpath(tmpPath)) { + qWarning() << "Failed to create temporary directory."; + return ""; + } + return tmpPath; + } + return tmpPath; +} + +void LoadPreconsPage::processTarArchive(const QByteArray &tarData) +{ + const int blockSize = 512; + int offset = 0; + + dynamic_cast(wizard())->setTempDir(createTmpDirectory()); + + while (offset + blockSize <= tarData.size()) { + QByteArray header = tarData.mid(offset, blockSize); + QString fileName = QString::fromLatin1(header.left(100).trimmed()); + if (fileName.isEmpty()) + break; + + QByteArray sizeField = header.mid(124, 12).trimmed(); + bool ok = false; + int fileSize = sizeField.toInt(&ok, 8); + if (!ok || fileSize < 0) + break; + + int fileStart = offset + blockSize; + QByteArray fileContents = tarData.mid(fileStart, fileSize); + + parsePreconsFromByteArray(fileContents, dynamic_cast(wizard())->getTempDir()); + + offset = fileStart + ((fileSize + blockSize - 1) / blockSize) * blockSize; + } + + dynamic_cast(wizard())->doneWithParsing = true; + + importFinished(); +} + +void LoadPreconsPage::readPreconsFromByteArrayRef(QByteArray &_data) +{ + // XZ-compressed TAR archive + if (_data.startsWith(XZ_SIGNATURE)) { +#ifdef HAS_LZMA + qInfo() << "Unzipping precon tar.xz file"; + auto *inBuffer = new QBuffer(&_data); + QByteArray tarData; + auto *outBuffer = new QBuffer(&tarData); + inBuffer->open(QBuffer::ReadOnly); + outBuffer->open(QBuffer::WriteOnly); + XzDecompressor xz; + if (!xz.decompress(inBuffer, outBuffer)) { + zipDownloadFailed(tr("Xz extraction failed.")); + return; + } + _data.clear(); + processTarArchive(tarData); + return; +#else + zipDownloadFailed(tr("Sorry, your computer does not support xz compressed files.")); + static_cast(wizard())->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + return; +#endif + } + + // ZIP archive + else if (_data.startsWith(ZIP_SIGNATURE)) { +#ifdef HAS_ZLIB + qInfo() << "Unzipping precon ZIP file"; + QBuffer inBuffer(&_data); + UnZip uz; + UnZip::ErrorCode ec = uz.openArchive(&inBuffer); + + if (ec != UnZip::Ok) { + zipDownloadFailed(tr("Failed to open Zip archive: %1.").arg(uz.formatError(ec))); + return; + } + + const QStringList files = uz.fileList(); + if (files.isEmpty()) { + zipDownloadFailed(tr("Zip extraction failed: the Zip archive is empty.")); + return; + } + + for (const QString &fileName : files) { + if (!fileName.endsWith(".json", Qt::CaseInsensitive)) + continue; + + QBuffer *outBuffer = new QBuffer(); + outBuffer->open(QIODevice::ReadWrite); + ec = uz.extractFile(fileName, outBuffer); + if (ec != UnZip::Ok) { + zipDownloadFailed(tr("Zip extraction failed for file %1: %2").arg(fileName, uz.formatError(ec))); + uz.closeArchive(); + return; + } + + outBuffer->seek(0); + delete outBuffer; + } + + uz.closeArchive(); + importFinished(); // Continue processing + return; +#else + zipDownloadFailed(tr("Sorry, your computer does not support zipped files.")); + static_cast(wizard())->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + return; +#endif + } + + // Raw JSON content + else if (_data.startsWith("{")) { + jsonData = std::move(_data); + watcher.setFuture(future); + } + + // XML content + else if (_data.startsWith("<")) { + dynamic_cast(wizard())->doneWithParsing = true; + dynamic_cast(wizard())->xmlData = std::move(_data); + importFinished(); + } + + // Unknown format + else { + static_cast(wizard())->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + QMessageBox::critical(this, tr("Error"), tr("Failed to interpret downloaded data.")); + } +} + +void LoadPreconsPage::zipDownloadFailed(const QString &message) +{ + dynamic_cast(wizard())->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + + QMessageBox::StandardButton reply; + reply = static_cast(QMessageBox::question( + this, tr("Error"), message + "
" + tr("Do you want to download the uncompressed file instead?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)); + + if (reply == QMessageBox::Yes) { + urlRadioButton->setChecked(true); + urlLineEdit->setText(ALLDECKS_URL_FALLBACK); + + wizard()->next(); + } +} + +void LoadPreconsPage::importFinished() +{ + dynamic_cast(wizard())->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + + if (dynamic_cast(wizard())->doneWithParsing || watcher.future().result()) { + wizard()->next(); + } else { + QMessageBox::critical( + this, tr("Error"), + tr("The file was retrieved successfully, but it does not contain any preconstructed decks data.")); + } +} + +SavePreconsPage::SavePreconsPage(QWidget *parent) : QWizardPage(parent) +{ + saveLabel = new QLabel(this); + + folderTreeWidget = new QTreeWidget(this); + folderTreeWidget->setHeaderHidden(true); + folderTreeWidget->setSelectionMode(QAbstractItemView::NoSelection); + folderTreeWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto *layout = new QGridLayout(this); + layout->addWidget(saveLabel, 0, 0); + layout->addWidget(folderTreeWidget, 1, 0); + + setLayout(layout); + + retranslateUi(); +} + +void SavePreconsPage::cleanupPage() +{ +} + +void SavePreconsPage::initializePage() +{ + QDir tempDir(dynamic_cast(wizard())->getTempDir()); + folderTreeWidget->clear(); + + for (const QString &dirName : tempDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString absPath = tempDir.absoluteFilePath(dirName); + QTreeWidgetItem *item = new QTreeWidgetItem(folderTreeWidget, QStringList() << dirName); + item->setData(0, Qt::UserRole, absPath); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate); + item->setCheckState(0, Qt::Unchecked); + + populateFolderTree(item, absPath); + } + + retranslateUi(); +} + +void SavePreconsPage::populateFolderTree(QTreeWidgetItem *parent, const QString &path) +{ + QDir dir(path); + for (const QString &subdir : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString absPath = dir.absoluteFilePath(subdir); + QTreeWidgetItem *child = new QTreeWidgetItem(parent, QStringList() << subdir); + child->setData(0, Qt::UserRole, absPath); + child->setFlags(child->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate); + child->setCheckState(0, Qt::Unchecked); + + populateFolderTree(child, absPath); + } +} + +void SavePreconsPage::retranslateUi() +{ + setTitle(tr("Precons imported")); + setSubTitle(tr("The following preconstructed deck types have been found:")); + + saveLabel->setText(tr("Select the product types you'd like to import and then press \"Save\" to store the imported " + "preconstructed decks in your deck folder. \n (Note: It is not recommended to import all " + "products unless you are sure your computer can handle it. \n It might cause Cockatrice to " + "load a very large amount of decks when using the visual deck storage")); + + setButtonText(QWizard::NextButton, tr("&Save")); +} + +bool SavePreconsPage::validatePage() +{ + for (int i = 0; i < folderTreeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem *item = folderTreeWidget->topLevelItem(i); + copyCheckedFolders(item); + } + + QDir(dynamic_cast(wizard())->getTempDir()).removeRecursively(); + return true; +} + +void SavePreconsPage::copyCheckedFolders(QTreeWidgetItem *item) +{ + Qt::CheckState state = item->checkState(0); + if (state == Qt::Unchecked) + return; + + QString srcPath = item->data(0, Qt::UserRole).toString(); + QString relativePath = QDir(dynamic_cast(wizard())->getTempDir()).relativeFilePath(srcPath); + QString destPath = QDir::cleanPath(SettingsCache::instance().getDeckPath() + QDir::separator() + "Precons" + + QDir::separator() + relativePath); + + if (!copyDirectory(srcPath, destPath)) + qWarning() << "Failed to copy" << srcPath; + + for (int i = 0; i < item->childCount(); ++i) { + copyCheckedFolders(item->child(i)); + } +} + +bool SavePreconsPage::copyDirectory(const QString &srcPath, const QString &destPath) +{ + QDir srcDir(srcPath); + if (!srcDir.exists()) + return false; + + QDir destDir; + if (!destDir.mkpath(destPath)) + return false; + + QFileInfoList entries = srcDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &entry : entries) { + QString src = entry.absoluteFilePath(); + QString dest = destPath + "/" + entry.fileName(); + + if (entry.isDir()) { + if (!copyDirectory(src, dest)) + return false; + } else { + if (!QFile::copy(src, dest)) + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.h b/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.h new file mode 100644 index 000000000..a2ad144cf --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.h @@ -0,0 +1,116 @@ +#ifndef DLG_IMPORT_PRECONS_H +#define DLG_IMPORT_PRECONS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DlgImportPrecons : public QWizard +{ + Q_OBJECT +public: + explicit DlgImportPrecons(QWidget *parent = nullptr); + void accept() override; + void enableButtons(); + void disableButtons(); + void retranslateUi(); + void setCardSourceUrl(const QString &sourceUrl) + { + cardSourceUrl = sourceUrl; + } + const QString &getCardSourceUrl() const + { + return cardSourceUrl; + } + void setTempDir(const QString &tempDir) + { + tmpDir = tempDir; + } + const QString &getTempDir() const + { + return tmpDir; + } + + QNetworkAccessManager *nam; + QByteArray xmlData; + bool doneWithParsing = false; + +private: + QString cardSourceUrl; + QString tmpDir; +}; + +class LoadPreconsPage : public QWizardPage +{ + Q_OBJECT +public: + explicit LoadPreconsPage(QWidget *parent = nullptr); + void retranslateUi(); + +protected: + void initializePage() override; + bool validatePage() override; + bool parsePreconsFromByteArray(const QByteArray &data, QString folderPath); + void readPreconsFromByteArray(QByteArray _data); + static QString createTmpDirectory(); + void processTarArchive(const QByteArray &tarData); + void readPreconsFromByteArrayRef(QByteArray &_data); + void downloadPreconsFile(const QUrl &url); + +private: + QRadioButton *urlRadioButton; + QRadioButton *fileRadioButton; + QLineEdit *urlLineEdit; + QLineEdit *fileLineEdit; + QPushButton *urlButton; + QPushButton *fileButton; + QLabel *progressLabel; + QProgressBar *progressBar; + + QFutureWatcher watcher; + QFuture future; + QByteArray jsonData; + +private slots: + void actLoadPreconsFile(); + void actRestoreDefaultUrl(); + void actDownloadProgressPreconsFile(qint64 received, qint64 total); + void actDownloadFinishedPreconsFile(); + void importFinished(); + void zipDownloadFailed(const QString &message); +}; + +class SavePreconsPage : public QWizardPage +{ + Q_OBJECT +public: + explicit SavePreconsPage(QWidget *parent = nullptr); + void retranslateUi(); + +private: + QTreeWidget *folderTreeWidget; + QLabel *saveLabel; + +protected: + void initializePage() override; + void populateFolderTree(QTreeWidgetItem *parent, const QString &path); + void onItemChanged(QTreeWidgetItem *item, int column); + void cleanupPage() override; + bool validatePage() override; + void copyCheckedFolders(QTreeWidgetItem *item); + bool copyDirectory(const QString &srcPath, const QString &destPath); +}; + +bool readPreconsFromByteArray(const QByteArray &data); + +#endif // DLG_IMPORT_PRECONS_H diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 622c04a08..218824945 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -696,6 +696,7 @@ void MainWindow::retranslateUi() aCheckCardUpdates->setText(tr("Check for Card Updates...")); aCheckCardUpdatesBackground->setText(tr("Check for Card Updates (Automatic)")); aStatusBar->setText(tr("Show Status Bar")); + aImportPrecons->setText(tr("Import preconstructed Decks...")); aViewLog->setText(tr("View &Debug Log")); aOpenSettingsFolder->setText(tr("Open Settings Folder")); @@ -754,6 +755,8 @@ void MainWindow::createActions() aStatusBar->setCheckable(true); aStatusBar->setChecked(SettingsCache::instance().getShowStatusBar()); connect(aStatusBar, &QAction::triggered, &SettingsCache::instance(), &SettingsCache::setShowStatusBar); + aImportPrecons = new QAction(this); + connect(aImportPrecons, &QAction::triggered, this, &MainWindow::actImportPrecons); aViewLog = new QAction(this); connect(aViewLog, &QAction::triggered, this, &MainWindow::actViewLog); aOpenSettingsFolder = new QAction(this); @@ -832,6 +835,7 @@ void MainWindow::createMenus() helpMenu->addAction(aUpdate); helpMenu->addAction(aCheckCardUpdates); helpMenu->addAction(aCheckCardUpdatesBackground); + helpMenu->addAction(aImportPrecons); helpMenu->addSeparator(); helpMenu->addAction(aStatusBar); helpMenu->addAction(aViewLog); @@ -1347,6 +1351,12 @@ void MainWindow::checkClientUpdatesFinished(bool needToUpdate, bool /* isCompati } } +void MainWindow::actImportPrecons() +{ + auto preconImporter = new DlgImportPrecons(this); + preconImporter->show(); +} + void MainWindow::refreshShortcuts() { ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 620a09467..382458bce 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -110,6 +110,7 @@ private slots: void cardDatabaseAllNewSetsEnabled(); void checkClientUpdatesFinished(bool needToUpdate, bool isCompatible, Release *release); + void actImportPrecons(); void actOpenCustomFolder(); void actOpenCustomsetsFolder(); @@ -147,7 +148,7 @@ private: QAction *aConnect, *aDisconnect, *aRegister, *aForgotPassword, *aSinglePlayer, *aWatchReplay, *aFullScreen; QAction *aManageSets, *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase; - QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aStatusBar, *aViewLog, + QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aImportPrecons, *aStatusBar, *aViewLog, *aOpenSettingsFolder; TabSupervisor *tabSupervisor; diff --git a/cockatrice/src/utility/external/lzma/decompress.cpp b/cockatrice/src/utility/external/lzma/decompress.cpp new file mode 100644 index 000000000..718cde207 --- /dev/null +++ b/cockatrice/src/utility/external/lzma/decompress.cpp @@ -0,0 +1,250 @@ +/* + * Simple routing to extract a single file from a xz archive + * Heavily based from doc/examples/02_decompress.c obtained from + * the official xz git repository: git.tukaani.org/xz.git + * The license from the original file header follows + * + * Author: Lasse Collin + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + + +#include +#include + +#include "decompress.h" + +XzDecompressor::XzDecompressor(QObject *parent) + : QObject(parent) +{ + +} + +bool XzDecompressor::decompress(QBuffer *in, QBuffer *out) +{ + lzma_stream strm = LZMA_STREAM_INIT; + bool success; + + if (!init_decoder(&strm)) { + return false; + } + + success = internal_decompress(&strm, in, out); + + // Free the memory allocated for the decoder. This only needs to be + // done after the last file. + lzma_end(&strm); + + return success; +} + +bool XzDecompressor::init_decoder(lzma_stream *strm) +{ + // Initialize a .xz decoder. The decoder supports a memory usage limit + // and a set of flags. + // + // The memory usage of the decompressor depends on the settings used + // to compress a .xz file. It can vary from less than a megabyte to + // a few gigabytes, but in practice (at least for now) it rarely + // exceeds 65 MiB because that's how much memory is required to + // decompress files created with "xz -9". Settings requiring more + // memory take extra effort to use and don't (at least for now) + // provide significantly better compression in most cases. + // + // Memory usage limit is useful if it is important that the + // decompressor won't consume gigabytes of memory. The need + // for limiting depends on the application. In this example, + // no memory usage limiting is used. This is done by setting + // the limit to UINT64_MAX. + // + // The .xz format allows concatenating compressed files as is: + // + // echo foo | xz > foobar.xz + // echo bar | xz >> foobar.xz + // + // When decompressing normal standalone .xz files, LZMA_CONCATENATED + // should always be used to support decompression of concatenated + // .xz files. If LZMA_CONCATENATED isn't used, the decoder will stop + // after the first .xz stream. This can be useful when .xz data has + // been embedded inside another file format. + // + // Flags other than LZMA_CONCATENATED are supported too, and can + // be combined with bitwise-or. See lzma/container.h + // (src/liblzma/api/lzma/container.h in the source package or e.g. + // /usr/include/lzma/container.h depending on the install prefix) + // for details. + lzma_ret ret = lzma_stream_decoder( + strm, UINT64_MAX, LZMA_CONCATENATED); + + // Return successfully if the initialization went fine. + if (ret == LZMA_OK) + return true; + + // Something went wrong. The possible errors are documented in + // lzma/container.h (src/liblzma/api/lzma/container.h in the source + // package or e.g. /usr/include/lzma/container.h depending on the + // install prefix). + // + // Note that LZMA_MEMLIMIT_ERROR is never possible here. If you + // specify a very tiny limit, the error will be delayed until + // the first headers have been parsed by a call to lzma_code(). + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: + msg = "Memory allocation failed"; + break; + + case LZMA_OPTIONS_ERROR: + msg = "Unsupported decompressor flags"; + break; + + default: + // This is most likely LZMA_PROG_ERROR indicating a bug in + // this program or in liblzma. It is inconvenient to have a + // separate error message for errors that should be impossible + // to occur, but knowing the error code is important for + // debugging. That's why it is good to print the error code + // at least when there is no good error message to show. + msg = "Unknown error, possibly a bug"; + break; + } + + qDebug() << "Error initializing the decoder:" << msg << "(error code " << ret << ")"; + return false; +} + + +bool XzDecompressor::internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out) +{ + // When LZMA_CONCATENATED flag was used when initializing the decoder, + // we need to tell lzma_code() when there will be no more input. + // This is done by setting action to LZMA_FINISH instead of LZMA_RUN + // in the same way as it is done when encoding. + // + // When LZMA_CONCATENATED isn't used, there is no need to use + // LZMA_FINISH to tell when all the input has been read, but it + // is still OK to use it if you want. When LZMA_CONCATENATED isn't + // used, the decoder will stop after the first .xz stream. In that + // case some unused data may be left in strm->next_in. + lzma_action action = LZMA_RUN; + + uint8_t inbuf[BUFSIZ]; + uint8_t outbuf[BUFSIZ]; + qint64 bytesAvailable; + + strm->next_in = NULL; + strm->avail_in = 0; + strm->next_out = outbuf; + strm->avail_out = sizeof(outbuf); + while (true) { + if (strm->avail_in == 0) { + strm->next_in = inbuf; + bytesAvailable = in->bytesAvailable(); + if(bytesAvailable == 0) { + // Once the end of the input file has been reached, + // we need to tell lzma_code() that no more input + // will be coming. As said before, this isn't required + // if the LZMA_CONCATENATED flag isn't used when + // initializing the decoder. + action = LZMA_FINISH; + } else if(bytesAvailable >= BUFSIZ) { + in->read((char*) inbuf, BUFSIZ); + strm->avail_in = BUFSIZ; + } else { + in->read((char*) inbuf, bytesAvailable); + strm->avail_in = bytesAvailable; + } + } + + lzma_ret ret = lzma_code(strm, action); + + if (strm->avail_out == 0 || ret == LZMA_STREAM_END) { + qint64 write_size = sizeof(outbuf) - strm->avail_out; + + if (out->write((char *) outbuf, write_size) != write_size) { + qDebug() << "Write error"; + return false; + } + + strm->next_out = outbuf; + strm->avail_out = sizeof(outbuf); + } + + if (ret != LZMA_OK) { + // Once everything has been decoded successfully, the + // return value of lzma_code() will be LZMA_STREAM_END. + // + // It is important to check for LZMA_STREAM_END. Do not + // assume that getting ret != LZMA_OK would mean that + // everything has gone well or that when you aren't + // getting more output it must have successfully + // decoded everything. + if (ret == LZMA_STREAM_END) + return true; + + // It's not LZMA_OK nor LZMA_STREAM_END, + // so it must be an error code. See lzma/base.h + // (src/liblzma/api/lzma/base.h in the source package + // or e.g. /usr/include/lzma/base.h depending on the + // install prefix) for the list and documentation of + // possible values. Many values listen in lzma_ret + // enumeration aren't possible in this example, but + // can be made possible by enabling memory usage limit + // or adding flags to the decoder initialization. + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: + msg = "Memory allocation failed"; + break; + + case LZMA_FORMAT_ERROR: + // .xz magic bytes weren't found. + msg = "The input is not in the .xz format"; + break; + + case LZMA_OPTIONS_ERROR: + // For example, the headers specify a filter + // that isn't supported by this liblzma + // version (or it hasn't been enabled when + // building liblzma, but no-one sane does + // that unless building liblzma for an + // embedded system). Upgrading to a newer + // liblzma might help. + // + // Note that it is unlikely that the file has + // accidentally became corrupt if you get this + // error. The integrity of the .xz headers is + // always verified with a CRC32, so + // unintentionally corrupt files can be + // distinguished from unsupported files. + msg = "Unsupported compression options"; + break; + + case LZMA_DATA_ERROR: + msg = "Compressed file is corrupt"; + break; + + case LZMA_BUF_ERROR: + // Typically this error means that a valid + // file has got truncated, but it might also + // be a damaged part in the file that makes + // the decoder think the file is truncated. + // If you prefer, you can use the same error + // message for this as for LZMA_DATA_ERROR. + msg = "Compressed file is truncated or " + "otherwise corrupt"; + break; + + default: + // This is most likely LZMA_PROG_ERROR. + msg = "Unknown error, possibly a bug"; + break; + } + + qDebug() << "Decoder error:" << msg << "(error code " << ret << ")"; + return false; + } + } +} + diff --git a/cockatrice/src/utility/external/lzma/decompress.h b/cockatrice/src/utility/external/lzma/decompress.h new file mode 100644 index 000000000..f0e315f8b --- /dev/null +++ b/cockatrice/src/utility/external/lzma/decompress.h @@ -0,0 +1,19 @@ +#ifndef XZ_DECOMPRESS_H +#define XZ_DECOMPRESS_H + +#include +#include + +class XzDecompressor : public QObject +{ + Q_OBJECT +public: + XzDecompressor(QObject *parent = 0); + ~XzDecompressor() { }; + bool decompress(QBuffer *in, QBuffer *out); +private: + bool init_decoder(lzma_stream *strm); + bool internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out); +}; + +#endif diff --git a/cockatrice/src/utility/external/qt-json/json.cpp b/cockatrice/src/utility/external/qt-json/json.cpp new file mode 100644 index 000000000..2fffd0f70 --- /dev/null +++ b/cockatrice/src/utility/external/qt-json/json.cpp @@ -0,0 +1,573 @@ +/* Copyright 2011 Eeli Reilin. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of Eeli Reilin. + */ + +/** + * \file json.cpp + */ + +#include "json.h" + +#include +#include + +namespace QtJson +{ + +static QString sanitizeString(QString str) +{ + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); +} + +static QByteArray join(const QList &list, const QByteArray &sep) +{ + QByteArray res; + for (const QByteArray &i : list) { + if (!res.isEmpty()) { + res += sep; + } + res += i; + } + return res; +} + +/** + * parse + */ +QVariant Json::parse(const QString &json) +{ + bool success = true; + return Json::parse(json, success); +} + +/** + * parse + */ +QVariant Json::parse(const QString &json, bool &success) +{ + success = true; + + // Return an empty QVariant if the JSON data is either null or empty + if (!json.isNull() || !json.isEmpty()) { + // We'll start from index 0 + int index = 0; + + // Parse the first value + QVariant value = Json::parseValue(json, index, success); + + // Return the parsed value + return value; + } else { + // Return the empty QVariant + return QVariant(); + } +} + +QByteArray Json::serialize(const QVariant &data) +{ + bool success = true; + return Json::serialize(data, success); +} + +QByteArray Json::serialize(const QVariant &data, bool &success) +{ + QByteArray str; + success = true; + + if (!data.isValid()) // invalid or null? + { + str = "null"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QVariantList) || + (data.typeId() == QMetaType::Type::QStringList)) // variant is a list? +#else + else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? +#endif + { + QList values; + const QVariantList list = data.toList(); + for (const QVariant &v : list) { + QByteArray serializedValue = serialize(v); + if (serializedValue.isNull()) { + success = false; + break; + } + values << serializedValue; + } + + str = "[ " + join(values, ", ") + " ]"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QVariantHash)) // variant is a list? +#else + else if (data.type() == QVariant::Hash) // variant is a hash? +#endif + { + const QVariantHash vhash = data.toHash(); + QHashIterator it(vhash); + str = "{ "; + QList pairs; + + while (it.hasNext()) { + it.next(); + QByteArray serializedValue = serialize(it.value()); + + if (serializedValue.isNull()) { + success = false; + break; + } + + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + + str += join(pairs, ", "); + str += " }"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QVariantMap)) // variant is a list? +#else + else if (data.type() == QVariant::Map) // variant is a map? +#endif + { + const QVariantMap vmap = data.toMap(); + QMapIterator it(vmap); + str = "{ "; + QList pairs; + while (it.hasNext()) { + it.next(); + QByteArray serializedValue = serialize(it.value()); + if (serializedValue.isNull()) { + success = false; + break; + } + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + str += join(pairs, ", "); + str += " }"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QString) || + (data.typeId() == QMetaType::Type::QByteArray)) // variant is a list? +#else + else if ((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array? +#endif + { + str = sanitizeString(data.toString()).toUtf8(); + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if (data.typeId() == QMetaType::Type::Double) +#else + else if (data.type() == QVariant::Double) // double? +#endif + { + str = QByteArray::number(data.toDouble(), 'g', 20); + if (!str.contains(".") && !str.contains("e")) { + str += ".0"; + } + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if (data.typeId() == QMetaType::Type::Bool) +#else + else if (data.type() == QVariant::Bool) // boolean value? +#endif + { + str = data.toBool() ? "true" : "false"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if (data.typeId() == QMetaType::Type::ULongLong) +#else + else if (data.type() == QVariant::ULongLong) // large unsigned number? +#endif + { + str = QByteArray::number(data.value()); + } else if (data.canConvert()) // any signed number? + { + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { + str = QString::number(data.value()).toUtf8(); + } else if (data.canConvert()) // can value be converted to string? + { + // this will catch QDate, QDateTime, QUrl, ... + str = sanitizeString(data.toString()).toUtf8(); + } else { + success = false; + } + if (success) { + return str; + } else { + return QByteArray(); + } +} + +/** + * parseValue + */ +QVariant Json::parseValue(const QString &json, int &index, bool &success) +{ + // Determine what kind of data we should parse by + // checking out the upcoming token + switch (Json::lookAhead(json, index)) { + case JsonTokenString: + return Json::parseString(json, index, success); + case JsonTokenNumber: + return Json::parseNumber(json, index); + case JsonTokenCurlyOpen: + return Json::parseObject(json, index, success); + case JsonTokenSquaredOpen: + return Json::parseArray(json, index, success); + case JsonTokenTrue: + Json::nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + Json::nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + Json::nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } + + // If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); +} + +/** + * parseObject + */ +QVariant Json::parseObject(const QString &json, int &index, bool &success) +{ + QVariantMap map; + int token; + + // Get rid of the whitespace and increment index + Json::nextToken(json, index); + + // Loop through all of the key/value pairs of the object + bool done = false; + while (!done) { + // Get the upcoming token + token = Json::lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantMap(); + } else if (token == JsonTokenComma) { + Json::nextToken(json, index); + } else if (token == JsonTokenCurlyClose) { + Json::nextToken(json, index); + return map; + } else { + // Parse the key/value pair's name + QString name = Json::parseString(json, index, success).toString(); + + if (!success) { + return QVariantMap(); + } + + // Get the next token + token = Json::nextToken(json, index); + + // If the next token is not a colon, flag the failure + // return an empty QVariant + if (token != JsonTokenColon) { + success = false; + return QVariant(QVariantMap()); + } + + // Parse the key/value pair's value + QVariant value = Json::parseValue(json, index, success); + + if (!success) { + return QVariantMap(); + } + + // Assign the value to the key in the map + map[name] = value; + } + } + + // Return the map successfully + return QVariant(map); +} + +/** + * parseArray + */ +QVariant Json::parseArray(const QString &json, int &index, bool &success) +{ + QVariantList list; + + Json::nextToken(json, index); + + bool done = false; + while (!done) { + int token = Json::lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantList(); + } else if (token == JsonTokenComma) { + Json::nextToken(json, index); + } else if (token == JsonTokenSquaredClose) { + Json::nextToken(json, index); + break; + } else { + QVariant value = Json::parseValue(json, index, success); + + if (!success) { + return QVariantList(); + } + + list.push_back(value); + } + } + + return QVariant(list); +} + +/** + * parseString + */ +QVariant Json::parseString(const QString &json, int &index, bool &success) +{ + QString s; + QChar c; + + Json::eatWhitespace(json, index); + + c = json[index++]; + + bool complete = false; + while (!complete) { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + complete = true; + break; + } else if (c == '\\') { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + s.append('\"'); + } else if (c == '\\') { + s.append('\\'); + } else if (c == '/') { + s.append('/'); + } else if (c == 'b') { + s.append('\b'); + } else if (c == 'f') { + s.append('\f'); + } else if (c == 'n') { + s.append('\n'); + } else if (c == 'r') { + s.append('\r'); + } else if (c == 't') { + s.append('\t'); + } else if (c == 'u') { + int remainingLength = json.size() - index; + + if (remainingLength >= 4) { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } else { + break; + } + } + } else { + s.append(c); + } + } + + if (!complete) { + success = false; + return QVariant(); + } + + return QVariant(s); +} + +/** + * parseNumber + */ +QVariant Json::parseNumber(const QString &json, int &index) +{ + Json::eatWhitespace(json, index); + + int lastIndex = Json::lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; + + numberStr = json.mid(index, charLength); + + index = lastIndex + 1; + + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(NULL)); + } else if (numberStr.startsWith('-')) { + return QVariant(numberStr.toLongLong(NULL)); + } else { + return QVariant(numberStr.toULongLong(NULL)); + } +} + +/** + * lastIndexOfNumber + */ +int Json::lastIndexOfNumber(const QString &json, int index) +{ + static const QString numericCharacters("0123456789+-.eE"); + int lastIndex; + + for (lastIndex = index; lastIndex < json.size(); lastIndex++) { + if (numericCharacters.indexOf(json[lastIndex]) == -1) { + break; + } + } + + return lastIndex - 1; +} + +/** + * eatWhitespace + */ +void Json::eatWhitespace(const QString &json, int &index) +{ + static const QString whitespaceChars(" \t\n\r"); + for (; index < json.size(); index++) { + if (whitespaceChars.indexOf(json[index]) == -1) { + break; + } + } +} + +/** + * lookAhead + */ +int Json::lookAhead(const QString &json, int index) +{ + int saveIndex = index; + return Json::nextToken(json, saveIndex); +} + +/** + * nextToken + */ +int Json::nextToken(const QString &json, int &index) +{ + Json::eatWhitespace(json, index); + + if (index == json.size()) { + return JsonTokenNone; + } + + QChar c = json[index]; + index++; + switch (c.toLatin1()) { + case '{': + return JsonTokenCurlyOpen; + case '}': + return JsonTokenCurlyClose; + case '[': + return JsonTokenSquaredOpen; + case ']': + return JsonTokenSquaredClose; + case ',': + return JsonTokenComma; + case '"': + return JsonTokenString; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return JsonTokenNumber; + case ':': + return JsonTokenColon; + } + + index--; + + int remainingLength = json.size() - index; + + // True + if (remainingLength >= 4) { + if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { + index += 4; + return JsonTokenTrue; + } + } + + // False + if (remainingLength >= 5) { + if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JsonTokenFalse; + } + } + + // Null + if (remainingLength >= 4) { + if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { + index += 4; + return JsonTokenNull; + } + } + + return JsonTokenNone; +} + +} // namespace QtJson diff --git a/cockatrice/src/utility/external/qt-json/json.h b/cockatrice/src/utility/external/qt-json/json.h new file mode 100644 index 000000000..cf0499d4e --- /dev/null +++ b/cockatrice/src/utility/external/qt-json/json.h @@ -0,0 +1,204 @@ +/* Copyright 2011 Eeli Reilin. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of Eeli Reilin. + */ + +/** + * \file json.h + */ + +#ifndef JSON_H +#define JSON_H + +#include +#include + +namespace QtJson +{ + +/** + * \enum JsonToken + */ +enum JsonToken +{ + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 +}; + +/** + * \class Json + * \brief A JSON data parser + * + * Json parses a JSON data into a QVariant hierarchy. + */ +class Json +{ + public: + /** + * Parse a JSON string + * + * \param json The JSON data + */ + static QVariant parse(const QString &json); + + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + static QVariant parse(const QString &json, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + */ + static QByteArray serialize(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation + */ + static QByteArray serialize(const QVariant &data, bool &success); + + private: + /** + * Parses a value starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the parse process + * + * \return QVariant The parsed value + */ + static QVariant parseValue(const QString &json, int &index, + bool &success); + + /** + * Parses an object starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the object parse + * + * \return QVariant The parsed object map + */ + static QVariant parseObject(const QString &json, int &index, + bool &success); + + /** + * Parses an array starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the array parse + * + * \return QVariant The parsed variant array + */ + static QVariant parseArray(const QString &json, int &index, + bool &success); + + /** + * Parses a string starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the string parse + * + * \return QVariant The parsed string + */ + static QVariant parseString(const QString &json, int &index, + bool &success); + + /** + * Parses a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return QVariant The parsed number + */ + static QVariant parseNumber(const QString &json, int &index); + + /** + * Get the last index of a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return The last index of the number + */ + static int lastIndexOfNumber(const QString &json, int index); + + /** + * Skip unwanted whitespace symbols starting from index + * + * \param json The JSON data + * \param index The start index + */ + static void eatWhitespace(const QString &json, int &index); + + /** + * Check what token lies ahead + * + * \param json The JSON data + * \param index The starting index + * + * \return int The upcoming token + */ + static int lookAhead(const QString &json, int index); + + /** + * Get the next JSON token + * + * \param json The JSON data + * \param index The starting index + * + * \return int The next JSON token + */ + static int nextToken(const QString &json, int &index); +}; + + +} //end namespace + +#endif //JSON_H diff --git a/cockatrice/src/utility/external/zip/unzip.cpp b/cockatrice/src/utility/external/zip/unzip.cpp new file mode 100755 index 000000000..1e5910051 --- /dev/null +++ b/cockatrice/src/utility/external/zip/unzip.cpp @@ -0,0 +1,1425 @@ +/**************************************************************************** +** 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 = 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.size() != 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; + + /* 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++; + + 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/cockatrice/src/utility/external/zip/unzip.h b/cockatrice/src/utility/external/zip/unzip.h new file mode 100644 index 000000000..ab57fdc36 --- /dev/null +++ b/cockatrice/src/utility/external/zip/unzip.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** 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; + +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/cockatrice/src/utility/external/zip/unzip_p.h b/cockatrice/src/utility/external/zip/unzip_p.h new file mode 100755 index 000000000..fca2b071d --- /dev/null +++ b/cockatrice/src/utility/external/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/cockatrice/src/utility/external/zip/zip.cpp b/cockatrice/src/utility/external/zip/zip.cpp new file mode 100755 index 000000000..5b0177293 --- /dev/null +++ b/cockatrice/src/utility/external/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/cockatrice/src/utility/external/zip/zip.h b/cockatrice/src/utility/external/zip/zip.h new file mode 100755 index 000000000..79f32ada5 --- /dev/null +++ b/cockatrice/src/utility/external/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/cockatrice/src/utility/external/zip/zip_p.h b/cockatrice/src/utility/external/zip/zip_p.h new file mode 100755 index 000000000..b8ec6564b --- /dev/null +++ b/cockatrice/src/utility/external/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/cockatrice/src/utility/external/zip/zipentry_p.h b/cockatrice/src/utility/external/zip/zipentry_p.h new file mode 100755 index 000000000..f0675b6b6 --- /dev/null +++ b/cockatrice/src/utility/external/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/cockatrice/src/utility/external/zip/zipglobal.cpp b/cockatrice/src/utility/external/zip/zipglobal.cpp new file mode 100644 index 000000000..e972005f5 --- /dev/null +++ b/cockatrice/src/utility/external/zip/zipglobal.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** 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_MACOS) +#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_MACOS) +#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 +} + +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 = + CreateFileW(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_MACOS) + + struct utimbuf t_buffer; + t_buffer.actime = t_buffer.modtime = dateTime.toSecsSinceEpoch(); + return utime(fileName.toLocal8Bit().constData(), &t_buffer) == 0; +#endif + + return true; +} +OSDAB_END_NAMESPACE diff --git a/cockatrice/src/utility/external/zip/zipglobal.h b/cockatrice/src/utility/external/zip/zipglobal.h new file mode 100755 index 000000000..e7ff33105 --- /dev/null +++ b/cockatrice/src/utility/external/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 From 1379464840013937ab18589aa6221df8e9db238d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 11 Jun 2025 02:28:40 +0200 Subject: [PATCH 2/7] Add zip and LZMA stuff. Took 1 minute Took 43 seconds --- cockatrice/src/lzma/decompress.cpp | 250 +++++ cockatrice/src/lzma/decompress.h | 19 + cockatrice/src/zip/unzip.cpp | 1425 ++++++++++++++++++++++++ cockatrice/src/zip/unzip.h | 155 +++ cockatrice/src/zip/unzip_p.h | 130 +++ cockatrice/src/zip/zip.cpp | 1619 ++++++++++++++++++++++++++++ cockatrice/src/zip/zip.h | 158 +++ cockatrice/src/zip/zip_p.h | 133 +++ cockatrice/src/zip/zipentry_p.h | 91 ++ cockatrice/src/zip/zipglobal.cpp | 150 +++ cockatrice/src/zip/zipglobal.h | 77 ++ 11 files changed, 4207 insertions(+) create mode 100644 cockatrice/src/lzma/decompress.cpp create mode 100644 cockatrice/src/lzma/decompress.h create mode 100755 cockatrice/src/zip/unzip.cpp create mode 100644 cockatrice/src/zip/unzip.h create mode 100755 cockatrice/src/zip/unzip_p.h create mode 100755 cockatrice/src/zip/zip.cpp create mode 100755 cockatrice/src/zip/zip.h create mode 100755 cockatrice/src/zip/zip_p.h create mode 100755 cockatrice/src/zip/zipentry_p.h create mode 100644 cockatrice/src/zip/zipglobal.cpp create mode 100755 cockatrice/src/zip/zipglobal.h diff --git a/cockatrice/src/lzma/decompress.cpp b/cockatrice/src/lzma/decompress.cpp new file mode 100644 index 000000000..718cde207 --- /dev/null +++ b/cockatrice/src/lzma/decompress.cpp @@ -0,0 +1,250 @@ +/* + * Simple routing to extract a single file from a xz archive + * Heavily based from doc/examples/02_decompress.c obtained from + * the official xz git repository: git.tukaani.org/xz.git + * The license from the original file header follows + * + * Author: Lasse Collin + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + + +#include +#include + +#include "decompress.h" + +XzDecompressor::XzDecompressor(QObject *parent) + : QObject(parent) +{ + +} + +bool XzDecompressor::decompress(QBuffer *in, QBuffer *out) +{ + lzma_stream strm = LZMA_STREAM_INIT; + bool success; + + if (!init_decoder(&strm)) { + return false; + } + + success = internal_decompress(&strm, in, out); + + // Free the memory allocated for the decoder. This only needs to be + // done after the last file. + lzma_end(&strm); + + return success; +} + +bool XzDecompressor::init_decoder(lzma_stream *strm) +{ + // Initialize a .xz decoder. The decoder supports a memory usage limit + // and a set of flags. + // + // The memory usage of the decompressor depends on the settings used + // to compress a .xz file. It can vary from less than a megabyte to + // a few gigabytes, but in practice (at least for now) it rarely + // exceeds 65 MiB because that's how much memory is required to + // decompress files created with "xz -9". Settings requiring more + // memory take extra effort to use and don't (at least for now) + // provide significantly better compression in most cases. + // + // Memory usage limit is useful if it is important that the + // decompressor won't consume gigabytes of memory. The need + // for limiting depends on the application. In this example, + // no memory usage limiting is used. This is done by setting + // the limit to UINT64_MAX. + // + // The .xz format allows concatenating compressed files as is: + // + // echo foo | xz > foobar.xz + // echo bar | xz >> foobar.xz + // + // When decompressing normal standalone .xz files, LZMA_CONCATENATED + // should always be used to support decompression of concatenated + // .xz files. If LZMA_CONCATENATED isn't used, the decoder will stop + // after the first .xz stream. This can be useful when .xz data has + // been embedded inside another file format. + // + // Flags other than LZMA_CONCATENATED are supported too, and can + // be combined with bitwise-or. See lzma/container.h + // (src/liblzma/api/lzma/container.h in the source package or e.g. + // /usr/include/lzma/container.h depending on the install prefix) + // for details. + lzma_ret ret = lzma_stream_decoder( + strm, UINT64_MAX, LZMA_CONCATENATED); + + // Return successfully if the initialization went fine. + if (ret == LZMA_OK) + return true; + + // Something went wrong. The possible errors are documented in + // lzma/container.h (src/liblzma/api/lzma/container.h in the source + // package or e.g. /usr/include/lzma/container.h depending on the + // install prefix). + // + // Note that LZMA_MEMLIMIT_ERROR is never possible here. If you + // specify a very tiny limit, the error will be delayed until + // the first headers have been parsed by a call to lzma_code(). + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: + msg = "Memory allocation failed"; + break; + + case LZMA_OPTIONS_ERROR: + msg = "Unsupported decompressor flags"; + break; + + default: + // This is most likely LZMA_PROG_ERROR indicating a bug in + // this program or in liblzma. It is inconvenient to have a + // separate error message for errors that should be impossible + // to occur, but knowing the error code is important for + // debugging. That's why it is good to print the error code + // at least when there is no good error message to show. + msg = "Unknown error, possibly a bug"; + break; + } + + qDebug() << "Error initializing the decoder:" << msg << "(error code " << ret << ")"; + return false; +} + + +bool XzDecompressor::internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out) +{ + // When LZMA_CONCATENATED flag was used when initializing the decoder, + // we need to tell lzma_code() when there will be no more input. + // This is done by setting action to LZMA_FINISH instead of LZMA_RUN + // in the same way as it is done when encoding. + // + // When LZMA_CONCATENATED isn't used, there is no need to use + // LZMA_FINISH to tell when all the input has been read, but it + // is still OK to use it if you want. When LZMA_CONCATENATED isn't + // used, the decoder will stop after the first .xz stream. In that + // case some unused data may be left in strm->next_in. + lzma_action action = LZMA_RUN; + + uint8_t inbuf[BUFSIZ]; + uint8_t outbuf[BUFSIZ]; + qint64 bytesAvailable; + + strm->next_in = NULL; + strm->avail_in = 0; + strm->next_out = outbuf; + strm->avail_out = sizeof(outbuf); + while (true) { + if (strm->avail_in == 0) { + strm->next_in = inbuf; + bytesAvailable = in->bytesAvailable(); + if(bytesAvailable == 0) { + // Once the end of the input file has been reached, + // we need to tell lzma_code() that no more input + // will be coming. As said before, this isn't required + // if the LZMA_CONCATENATED flag isn't used when + // initializing the decoder. + action = LZMA_FINISH; + } else if(bytesAvailable >= BUFSIZ) { + in->read((char*) inbuf, BUFSIZ); + strm->avail_in = BUFSIZ; + } else { + in->read((char*) inbuf, bytesAvailable); + strm->avail_in = bytesAvailable; + } + } + + lzma_ret ret = lzma_code(strm, action); + + if (strm->avail_out == 0 || ret == LZMA_STREAM_END) { + qint64 write_size = sizeof(outbuf) - strm->avail_out; + + if (out->write((char *) outbuf, write_size) != write_size) { + qDebug() << "Write error"; + return false; + } + + strm->next_out = outbuf; + strm->avail_out = sizeof(outbuf); + } + + if (ret != LZMA_OK) { + // Once everything has been decoded successfully, the + // return value of lzma_code() will be LZMA_STREAM_END. + // + // It is important to check for LZMA_STREAM_END. Do not + // assume that getting ret != LZMA_OK would mean that + // everything has gone well or that when you aren't + // getting more output it must have successfully + // decoded everything. + if (ret == LZMA_STREAM_END) + return true; + + // It's not LZMA_OK nor LZMA_STREAM_END, + // so it must be an error code. See lzma/base.h + // (src/liblzma/api/lzma/base.h in the source package + // or e.g. /usr/include/lzma/base.h depending on the + // install prefix) for the list and documentation of + // possible values. Many values listen in lzma_ret + // enumeration aren't possible in this example, but + // can be made possible by enabling memory usage limit + // or adding flags to the decoder initialization. + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: + msg = "Memory allocation failed"; + break; + + case LZMA_FORMAT_ERROR: + // .xz magic bytes weren't found. + msg = "The input is not in the .xz format"; + break; + + case LZMA_OPTIONS_ERROR: + // For example, the headers specify a filter + // that isn't supported by this liblzma + // version (or it hasn't been enabled when + // building liblzma, but no-one sane does + // that unless building liblzma for an + // embedded system). Upgrading to a newer + // liblzma might help. + // + // Note that it is unlikely that the file has + // accidentally became corrupt if you get this + // error. The integrity of the .xz headers is + // always verified with a CRC32, so + // unintentionally corrupt files can be + // distinguished from unsupported files. + msg = "Unsupported compression options"; + break; + + case LZMA_DATA_ERROR: + msg = "Compressed file is corrupt"; + break; + + case LZMA_BUF_ERROR: + // Typically this error means that a valid + // file has got truncated, but it might also + // be a damaged part in the file that makes + // the decoder think the file is truncated. + // If you prefer, you can use the same error + // message for this as for LZMA_DATA_ERROR. + msg = "Compressed file is truncated or " + "otherwise corrupt"; + break; + + default: + // This is most likely LZMA_PROG_ERROR. + msg = "Unknown error, possibly a bug"; + break; + } + + qDebug() << "Decoder error:" << msg << "(error code " << ret << ")"; + return false; + } + } +} + diff --git a/cockatrice/src/lzma/decompress.h b/cockatrice/src/lzma/decompress.h new file mode 100644 index 000000000..f0e315f8b --- /dev/null +++ b/cockatrice/src/lzma/decompress.h @@ -0,0 +1,19 @@ +#ifndef XZ_DECOMPRESS_H +#define XZ_DECOMPRESS_H + +#include +#include + +class XzDecompressor : public QObject +{ + Q_OBJECT +public: + XzDecompressor(QObject *parent = 0); + ~XzDecompressor() { }; + bool decompress(QBuffer *in, QBuffer *out); +private: + bool init_decoder(lzma_stream *strm); + bool internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out); +}; + +#endif diff --git a/cockatrice/src/zip/unzip.cpp b/cockatrice/src/zip/unzip.cpp new file mode 100755 index 000000000..1e5910051 --- /dev/null +++ b/cockatrice/src/zip/unzip.cpp @@ -0,0 +1,1425 @@ +/**************************************************************************** +** 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 = 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.size() != 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; + + /* 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++; + + 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/cockatrice/src/zip/unzip.h b/cockatrice/src/zip/unzip.h new file mode 100644 index 000000000..ab57fdc36 --- /dev/null +++ b/cockatrice/src/zip/unzip.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** 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; + +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/cockatrice/src/zip/unzip_p.h b/cockatrice/src/zip/unzip_p.h new file mode 100755 index 000000000..fca2b071d --- /dev/null +++ b/cockatrice/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/cockatrice/src/zip/zip.cpp b/cockatrice/src/zip/zip.cpp new file mode 100755 index 000000000..5b0177293 --- /dev/null +++ b/cockatrice/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/cockatrice/src/zip/zip.h b/cockatrice/src/zip/zip.h new file mode 100755 index 000000000..79f32ada5 --- /dev/null +++ b/cockatrice/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/cockatrice/src/zip/zip_p.h b/cockatrice/src/zip/zip_p.h new file mode 100755 index 000000000..b8ec6564b --- /dev/null +++ b/cockatrice/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/cockatrice/src/zip/zipentry_p.h b/cockatrice/src/zip/zipentry_p.h new file mode 100755 index 000000000..f0675b6b6 --- /dev/null +++ b/cockatrice/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/cockatrice/src/zip/zipglobal.cpp b/cockatrice/src/zip/zipglobal.cpp new file mode 100644 index 000000000..e972005f5 --- /dev/null +++ b/cockatrice/src/zip/zipglobal.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** 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_MACOS) +#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_MACOS) +#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 +} + +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 = + CreateFileW(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_MACOS) + + struct utimbuf t_buffer; + t_buffer.actime = t_buffer.modtime = dateTime.toSecsSinceEpoch(); + return utime(fileName.toLocal8Bit().constData(), &t_buffer) == 0; +#endif + + return true; +} +OSDAB_END_NAMESPACE diff --git a/cockatrice/src/zip/zipglobal.h b/cockatrice/src/zip/zipglobal.h new file mode 100755 index 000000000..e7ff33105 --- /dev/null +++ b/cockatrice/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 From c55af67325fea0d9b3c2a02a445d18f56d3d8480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 11 Jun 2025 02:33:46 +0200 Subject: [PATCH 3/7] Include qt-json. Took 5 seconds --- cockatrice/src/qt-json/json.cpp | 573 ++++++++++++++++++++++++++++++++ cockatrice/src/qt-json/json.h | 204 ++++++++++++ 2 files changed, 777 insertions(+) create mode 100644 cockatrice/src/qt-json/json.cpp create mode 100644 cockatrice/src/qt-json/json.h diff --git a/cockatrice/src/qt-json/json.cpp b/cockatrice/src/qt-json/json.cpp new file mode 100644 index 000000000..2fffd0f70 --- /dev/null +++ b/cockatrice/src/qt-json/json.cpp @@ -0,0 +1,573 @@ +/* Copyright 2011 Eeli Reilin. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of Eeli Reilin. + */ + +/** + * \file json.cpp + */ + +#include "json.h" + +#include +#include + +namespace QtJson +{ + +static QString sanitizeString(QString str) +{ + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); +} + +static QByteArray join(const QList &list, const QByteArray &sep) +{ + QByteArray res; + for (const QByteArray &i : list) { + if (!res.isEmpty()) { + res += sep; + } + res += i; + } + return res; +} + +/** + * parse + */ +QVariant Json::parse(const QString &json) +{ + bool success = true; + return Json::parse(json, success); +} + +/** + * parse + */ +QVariant Json::parse(const QString &json, bool &success) +{ + success = true; + + // Return an empty QVariant if the JSON data is either null or empty + if (!json.isNull() || !json.isEmpty()) { + // We'll start from index 0 + int index = 0; + + // Parse the first value + QVariant value = Json::parseValue(json, index, success); + + // Return the parsed value + return value; + } else { + // Return the empty QVariant + return QVariant(); + } +} + +QByteArray Json::serialize(const QVariant &data) +{ + bool success = true; + return Json::serialize(data, success); +} + +QByteArray Json::serialize(const QVariant &data, bool &success) +{ + QByteArray str; + success = true; + + if (!data.isValid()) // invalid or null? + { + str = "null"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QVariantList) || + (data.typeId() == QMetaType::Type::QStringList)) // variant is a list? +#else + else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? +#endif + { + QList values; + const QVariantList list = data.toList(); + for (const QVariant &v : list) { + QByteArray serializedValue = serialize(v); + if (serializedValue.isNull()) { + success = false; + break; + } + values << serializedValue; + } + + str = "[ " + join(values, ", ") + " ]"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QVariantHash)) // variant is a list? +#else + else if (data.type() == QVariant::Hash) // variant is a hash? +#endif + { + const QVariantHash vhash = data.toHash(); + QHashIterator it(vhash); + str = "{ "; + QList pairs; + + while (it.hasNext()) { + it.next(); + QByteArray serializedValue = serialize(it.value()); + + if (serializedValue.isNull()) { + success = false; + break; + } + + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + + str += join(pairs, ", "); + str += " }"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QVariantMap)) // variant is a list? +#else + else if (data.type() == QVariant::Map) // variant is a map? +#endif + { + const QVariantMap vmap = data.toMap(); + QMapIterator it(vmap); + str = "{ "; + QList pairs; + while (it.hasNext()) { + it.next(); + QByteArray serializedValue = serialize(it.value()); + if (serializedValue.isNull()) { + success = false; + break; + } + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + str += join(pairs, ", "); + str += " }"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if ((data.typeId() == QMetaType::Type::QString) || + (data.typeId() == QMetaType::Type::QByteArray)) // variant is a list? +#else + else if ((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array? +#endif + { + str = sanitizeString(data.toString()).toUtf8(); + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if (data.typeId() == QMetaType::Type::Double) +#else + else if (data.type() == QVariant::Double) // double? +#endif + { + str = QByteArray::number(data.toDouble(), 'g', 20); + if (!str.contains(".") && !str.contains("e")) { + str += ".0"; + } + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if (data.typeId() == QMetaType::Type::Bool) +#else + else if (data.type() == QVariant::Bool) // boolean value? +#endif + { + str = data.toBool() ? "true" : "false"; + } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + else if (data.typeId() == QMetaType::Type::ULongLong) +#else + else if (data.type() == QVariant::ULongLong) // large unsigned number? +#endif + { + str = QByteArray::number(data.value()); + } else if (data.canConvert()) // any signed number? + { + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { + str = QString::number(data.value()).toUtf8(); + } else if (data.canConvert()) // can value be converted to string? + { + // this will catch QDate, QDateTime, QUrl, ... + str = sanitizeString(data.toString()).toUtf8(); + } else { + success = false; + } + if (success) { + return str; + } else { + return QByteArray(); + } +} + +/** + * parseValue + */ +QVariant Json::parseValue(const QString &json, int &index, bool &success) +{ + // Determine what kind of data we should parse by + // checking out the upcoming token + switch (Json::lookAhead(json, index)) { + case JsonTokenString: + return Json::parseString(json, index, success); + case JsonTokenNumber: + return Json::parseNumber(json, index); + case JsonTokenCurlyOpen: + return Json::parseObject(json, index, success); + case JsonTokenSquaredOpen: + return Json::parseArray(json, index, success); + case JsonTokenTrue: + Json::nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + Json::nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + Json::nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } + + // If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); +} + +/** + * parseObject + */ +QVariant Json::parseObject(const QString &json, int &index, bool &success) +{ + QVariantMap map; + int token; + + // Get rid of the whitespace and increment index + Json::nextToken(json, index); + + // Loop through all of the key/value pairs of the object + bool done = false; + while (!done) { + // Get the upcoming token + token = Json::lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantMap(); + } else if (token == JsonTokenComma) { + Json::nextToken(json, index); + } else if (token == JsonTokenCurlyClose) { + Json::nextToken(json, index); + return map; + } else { + // Parse the key/value pair's name + QString name = Json::parseString(json, index, success).toString(); + + if (!success) { + return QVariantMap(); + } + + // Get the next token + token = Json::nextToken(json, index); + + // If the next token is not a colon, flag the failure + // return an empty QVariant + if (token != JsonTokenColon) { + success = false; + return QVariant(QVariantMap()); + } + + // Parse the key/value pair's value + QVariant value = Json::parseValue(json, index, success); + + if (!success) { + return QVariantMap(); + } + + // Assign the value to the key in the map + map[name] = value; + } + } + + // Return the map successfully + return QVariant(map); +} + +/** + * parseArray + */ +QVariant Json::parseArray(const QString &json, int &index, bool &success) +{ + QVariantList list; + + Json::nextToken(json, index); + + bool done = false; + while (!done) { + int token = Json::lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantList(); + } else if (token == JsonTokenComma) { + Json::nextToken(json, index); + } else if (token == JsonTokenSquaredClose) { + Json::nextToken(json, index); + break; + } else { + QVariant value = Json::parseValue(json, index, success); + + if (!success) { + return QVariantList(); + } + + list.push_back(value); + } + } + + return QVariant(list); +} + +/** + * parseString + */ +QVariant Json::parseString(const QString &json, int &index, bool &success) +{ + QString s; + QChar c; + + Json::eatWhitespace(json, index); + + c = json[index++]; + + bool complete = false; + while (!complete) { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + complete = true; + break; + } else if (c == '\\') { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + s.append('\"'); + } else if (c == '\\') { + s.append('\\'); + } else if (c == '/') { + s.append('/'); + } else if (c == 'b') { + s.append('\b'); + } else if (c == 'f') { + s.append('\f'); + } else if (c == 'n') { + s.append('\n'); + } else if (c == 'r') { + s.append('\r'); + } else if (c == 't') { + s.append('\t'); + } else if (c == 'u') { + int remainingLength = json.size() - index; + + if (remainingLength >= 4) { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } else { + break; + } + } + } else { + s.append(c); + } + } + + if (!complete) { + success = false; + return QVariant(); + } + + return QVariant(s); +} + +/** + * parseNumber + */ +QVariant Json::parseNumber(const QString &json, int &index) +{ + Json::eatWhitespace(json, index); + + int lastIndex = Json::lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; + + numberStr = json.mid(index, charLength); + + index = lastIndex + 1; + + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(NULL)); + } else if (numberStr.startsWith('-')) { + return QVariant(numberStr.toLongLong(NULL)); + } else { + return QVariant(numberStr.toULongLong(NULL)); + } +} + +/** + * lastIndexOfNumber + */ +int Json::lastIndexOfNumber(const QString &json, int index) +{ + static const QString numericCharacters("0123456789+-.eE"); + int lastIndex; + + for (lastIndex = index; lastIndex < json.size(); lastIndex++) { + if (numericCharacters.indexOf(json[lastIndex]) == -1) { + break; + } + } + + return lastIndex - 1; +} + +/** + * eatWhitespace + */ +void Json::eatWhitespace(const QString &json, int &index) +{ + static const QString whitespaceChars(" \t\n\r"); + for (; index < json.size(); index++) { + if (whitespaceChars.indexOf(json[index]) == -1) { + break; + } + } +} + +/** + * lookAhead + */ +int Json::lookAhead(const QString &json, int index) +{ + int saveIndex = index; + return Json::nextToken(json, saveIndex); +} + +/** + * nextToken + */ +int Json::nextToken(const QString &json, int &index) +{ + Json::eatWhitespace(json, index); + + if (index == json.size()) { + return JsonTokenNone; + } + + QChar c = json[index]; + index++; + switch (c.toLatin1()) { + case '{': + return JsonTokenCurlyOpen; + case '}': + return JsonTokenCurlyClose; + case '[': + return JsonTokenSquaredOpen; + case ']': + return JsonTokenSquaredClose; + case ',': + return JsonTokenComma; + case '"': + return JsonTokenString; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return JsonTokenNumber; + case ':': + return JsonTokenColon; + } + + index--; + + int remainingLength = json.size() - index; + + // True + if (remainingLength >= 4) { + if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { + index += 4; + return JsonTokenTrue; + } + } + + // False + if (remainingLength >= 5) { + if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JsonTokenFalse; + } + } + + // Null + if (remainingLength >= 4) { + if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { + index += 4; + return JsonTokenNull; + } + } + + return JsonTokenNone; +} + +} // namespace QtJson diff --git a/cockatrice/src/qt-json/json.h b/cockatrice/src/qt-json/json.h new file mode 100644 index 000000000..cf0499d4e --- /dev/null +++ b/cockatrice/src/qt-json/json.h @@ -0,0 +1,204 @@ +/* Copyright 2011 Eeli Reilin. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of Eeli Reilin. + */ + +/** + * \file json.h + */ + +#ifndef JSON_H +#define JSON_H + +#include +#include + +namespace QtJson +{ + +/** + * \enum JsonToken + */ +enum JsonToken +{ + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 +}; + +/** + * \class Json + * \brief A JSON data parser + * + * Json parses a JSON data into a QVariant hierarchy. + */ +class Json +{ + public: + /** + * Parse a JSON string + * + * \param json The JSON data + */ + static QVariant parse(const QString &json); + + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + static QVariant parse(const QString &json, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + */ + static QByteArray serialize(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation + */ + static QByteArray serialize(const QVariant &data, bool &success); + + private: + /** + * Parses a value starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the parse process + * + * \return QVariant The parsed value + */ + static QVariant parseValue(const QString &json, int &index, + bool &success); + + /** + * Parses an object starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the object parse + * + * \return QVariant The parsed object map + */ + static QVariant parseObject(const QString &json, int &index, + bool &success); + + /** + * Parses an array starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the array parse + * + * \return QVariant The parsed variant array + */ + static QVariant parseArray(const QString &json, int &index, + bool &success); + + /** + * Parses a string starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the string parse + * + * \return QVariant The parsed string + */ + static QVariant parseString(const QString &json, int &index, + bool &success); + + /** + * Parses a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return QVariant The parsed number + */ + static QVariant parseNumber(const QString &json, int &index); + + /** + * Get the last index of a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return The last index of the number + */ + static int lastIndexOfNumber(const QString &json, int index); + + /** + * Skip unwanted whitespace symbols starting from index + * + * \param json The JSON data + * \param index The start index + */ + static void eatWhitespace(const QString &json, int &index); + + /** + * Check what token lies ahead + * + * \param json The JSON data + * \param index The starting index + * + * \return int The upcoming token + */ + static int lookAhead(const QString &json, int index); + + /** + * Get the next JSON token + * + * \param json The JSON data + * \param index The starting index + * + * \return int The next JSON token + */ + static int nextToken(const QString &json, int &index); +}; + + +} //end namespace + +#endif //JSON_H From e08f28b110c0b1ac6027f0acdb23de12042af688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 11 Jul 2025 00:19:47 +0200 Subject: [PATCH 4/7] Move stuff to src/utility/external Took 10 minutes Took 4 seconds --- cockatrice/src/lzma/decompress.cpp | 250 ----- cockatrice/src/lzma/decompress.h | 19 - cockatrice/src/qt-json/json.cpp | 573 ---------- cockatrice/src/qt-json/json.h | 204 ---- cockatrice/src/zip/unzip.cpp | 1425 ------------------------ cockatrice/src/zip/unzip.h | 155 --- cockatrice/src/zip/unzip_p.h | 130 --- cockatrice/src/zip/zip.cpp | 1619 ---------------------------- cockatrice/src/zip/zip.h | 158 --- cockatrice/src/zip/zip_p.h | 133 --- cockatrice/src/zip/zipentry_p.h | 91 -- cockatrice/src/zip/zipglobal.cpp | 150 --- cockatrice/src/zip/zipglobal.h | 77 -- 13 files changed, 4984 deletions(-) delete mode 100644 cockatrice/src/lzma/decompress.cpp delete mode 100644 cockatrice/src/lzma/decompress.h delete mode 100644 cockatrice/src/qt-json/json.cpp delete mode 100644 cockatrice/src/qt-json/json.h delete mode 100755 cockatrice/src/zip/unzip.cpp delete mode 100644 cockatrice/src/zip/unzip.h delete mode 100755 cockatrice/src/zip/unzip_p.h delete mode 100755 cockatrice/src/zip/zip.cpp delete mode 100755 cockatrice/src/zip/zip.h delete mode 100755 cockatrice/src/zip/zip_p.h delete mode 100755 cockatrice/src/zip/zipentry_p.h delete mode 100644 cockatrice/src/zip/zipglobal.cpp delete mode 100755 cockatrice/src/zip/zipglobal.h diff --git a/cockatrice/src/lzma/decompress.cpp b/cockatrice/src/lzma/decompress.cpp deleted file mode 100644 index 718cde207..000000000 --- a/cockatrice/src/lzma/decompress.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Simple routing to extract a single file from a xz archive - * Heavily based from doc/examples/02_decompress.c obtained from - * the official xz git repository: git.tukaani.org/xz.git - * The license from the original file header follows - * - * Author: Lasse Collin - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - - -#include -#include - -#include "decompress.h" - -XzDecompressor::XzDecompressor(QObject *parent) - : QObject(parent) -{ - -} - -bool XzDecompressor::decompress(QBuffer *in, QBuffer *out) -{ - lzma_stream strm = LZMA_STREAM_INIT; - bool success; - - if (!init_decoder(&strm)) { - return false; - } - - success = internal_decompress(&strm, in, out); - - // Free the memory allocated for the decoder. This only needs to be - // done after the last file. - lzma_end(&strm); - - return success; -} - -bool XzDecompressor::init_decoder(lzma_stream *strm) -{ - // Initialize a .xz decoder. The decoder supports a memory usage limit - // and a set of flags. - // - // The memory usage of the decompressor depends on the settings used - // to compress a .xz file. It can vary from less than a megabyte to - // a few gigabytes, but in practice (at least for now) it rarely - // exceeds 65 MiB because that's how much memory is required to - // decompress files created with "xz -9". Settings requiring more - // memory take extra effort to use and don't (at least for now) - // provide significantly better compression in most cases. - // - // Memory usage limit is useful if it is important that the - // decompressor won't consume gigabytes of memory. The need - // for limiting depends on the application. In this example, - // no memory usage limiting is used. This is done by setting - // the limit to UINT64_MAX. - // - // The .xz format allows concatenating compressed files as is: - // - // echo foo | xz > foobar.xz - // echo bar | xz >> foobar.xz - // - // When decompressing normal standalone .xz files, LZMA_CONCATENATED - // should always be used to support decompression of concatenated - // .xz files. If LZMA_CONCATENATED isn't used, the decoder will stop - // after the first .xz stream. This can be useful when .xz data has - // been embedded inside another file format. - // - // Flags other than LZMA_CONCATENATED are supported too, and can - // be combined with bitwise-or. See lzma/container.h - // (src/liblzma/api/lzma/container.h in the source package or e.g. - // /usr/include/lzma/container.h depending on the install prefix) - // for details. - lzma_ret ret = lzma_stream_decoder( - strm, UINT64_MAX, LZMA_CONCATENATED); - - // Return successfully if the initialization went fine. - if (ret == LZMA_OK) - return true; - - // Something went wrong. The possible errors are documented in - // lzma/container.h (src/liblzma/api/lzma/container.h in the source - // package or e.g. /usr/include/lzma/container.h depending on the - // install prefix). - // - // Note that LZMA_MEMLIMIT_ERROR is never possible here. If you - // specify a very tiny limit, the error will be delayed until - // the first headers have been parsed by a call to lzma_code(). - const char *msg; - switch (ret) { - case LZMA_MEM_ERROR: - msg = "Memory allocation failed"; - break; - - case LZMA_OPTIONS_ERROR: - msg = "Unsupported decompressor flags"; - break; - - default: - // This is most likely LZMA_PROG_ERROR indicating a bug in - // this program or in liblzma. It is inconvenient to have a - // separate error message for errors that should be impossible - // to occur, but knowing the error code is important for - // debugging. That's why it is good to print the error code - // at least when there is no good error message to show. - msg = "Unknown error, possibly a bug"; - break; - } - - qDebug() << "Error initializing the decoder:" << msg << "(error code " << ret << ")"; - return false; -} - - -bool XzDecompressor::internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out) -{ - // When LZMA_CONCATENATED flag was used when initializing the decoder, - // we need to tell lzma_code() when there will be no more input. - // This is done by setting action to LZMA_FINISH instead of LZMA_RUN - // in the same way as it is done when encoding. - // - // When LZMA_CONCATENATED isn't used, there is no need to use - // LZMA_FINISH to tell when all the input has been read, but it - // is still OK to use it if you want. When LZMA_CONCATENATED isn't - // used, the decoder will stop after the first .xz stream. In that - // case some unused data may be left in strm->next_in. - lzma_action action = LZMA_RUN; - - uint8_t inbuf[BUFSIZ]; - uint8_t outbuf[BUFSIZ]; - qint64 bytesAvailable; - - strm->next_in = NULL; - strm->avail_in = 0; - strm->next_out = outbuf; - strm->avail_out = sizeof(outbuf); - while (true) { - if (strm->avail_in == 0) { - strm->next_in = inbuf; - bytesAvailable = in->bytesAvailable(); - if(bytesAvailable == 0) { - // Once the end of the input file has been reached, - // we need to tell lzma_code() that no more input - // will be coming. As said before, this isn't required - // if the LZMA_CONCATENATED flag isn't used when - // initializing the decoder. - action = LZMA_FINISH; - } else if(bytesAvailable >= BUFSIZ) { - in->read((char*) inbuf, BUFSIZ); - strm->avail_in = BUFSIZ; - } else { - in->read((char*) inbuf, bytesAvailable); - strm->avail_in = bytesAvailable; - } - } - - lzma_ret ret = lzma_code(strm, action); - - if (strm->avail_out == 0 || ret == LZMA_STREAM_END) { - qint64 write_size = sizeof(outbuf) - strm->avail_out; - - if (out->write((char *) outbuf, write_size) != write_size) { - qDebug() << "Write error"; - return false; - } - - strm->next_out = outbuf; - strm->avail_out = sizeof(outbuf); - } - - if (ret != LZMA_OK) { - // Once everything has been decoded successfully, the - // return value of lzma_code() will be LZMA_STREAM_END. - // - // It is important to check for LZMA_STREAM_END. Do not - // assume that getting ret != LZMA_OK would mean that - // everything has gone well or that when you aren't - // getting more output it must have successfully - // decoded everything. - if (ret == LZMA_STREAM_END) - return true; - - // It's not LZMA_OK nor LZMA_STREAM_END, - // so it must be an error code. See lzma/base.h - // (src/liblzma/api/lzma/base.h in the source package - // or e.g. /usr/include/lzma/base.h depending on the - // install prefix) for the list and documentation of - // possible values. Many values listen in lzma_ret - // enumeration aren't possible in this example, but - // can be made possible by enabling memory usage limit - // or adding flags to the decoder initialization. - const char *msg; - switch (ret) { - case LZMA_MEM_ERROR: - msg = "Memory allocation failed"; - break; - - case LZMA_FORMAT_ERROR: - // .xz magic bytes weren't found. - msg = "The input is not in the .xz format"; - break; - - case LZMA_OPTIONS_ERROR: - // For example, the headers specify a filter - // that isn't supported by this liblzma - // version (or it hasn't been enabled when - // building liblzma, but no-one sane does - // that unless building liblzma for an - // embedded system). Upgrading to a newer - // liblzma might help. - // - // Note that it is unlikely that the file has - // accidentally became corrupt if you get this - // error. The integrity of the .xz headers is - // always verified with a CRC32, so - // unintentionally corrupt files can be - // distinguished from unsupported files. - msg = "Unsupported compression options"; - break; - - case LZMA_DATA_ERROR: - msg = "Compressed file is corrupt"; - break; - - case LZMA_BUF_ERROR: - // Typically this error means that a valid - // file has got truncated, but it might also - // be a damaged part in the file that makes - // the decoder think the file is truncated. - // If you prefer, you can use the same error - // message for this as for LZMA_DATA_ERROR. - msg = "Compressed file is truncated or " - "otherwise corrupt"; - break; - - default: - // This is most likely LZMA_PROG_ERROR. - msg = "Unknown error, possibly a bug"; - break; - } - - qDebug() << "Decoder error:" << msg << "(error code " << ret << ")"; - return false; - } - } -} - diff --git a/cockatrice/src/lzma/decompress.h b/cockatrice/src/lzma/decompress.h deleted file mode 100644 index f0e315f8b..000000000 --- a/cockatrice/src/lzma/decompress.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef XZ_DECOMPRESS_H -#define XZ_DECOMPRESS_H - -#include -#include - -class XzDecompressor : public QObject -{ - Q_OBJECT -public: - XzDecompressor(QObject *parent = 0); - ~XzDecompressor() { }; - bool decompress(QBuffer *in, QBuffer *out); -private: - bool init_decoder(lzma_stream *strm); - bool internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out); -}; - -#endif diff --git a/cockatrice/src/qt-json/json.cpp b/cockatrice/src/qt-json/json.cpp deleted file mode 100644 index 2fffd0f70..000000000 --- a/cockatrice/src/qt-json/json.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* Copyright 2011 Eeli Reilin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation - * are those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Eeli Reilin. - */ - -/** - * \file json.cpp - */ - -#include "json.h" - -#include -#include - -namespace QtJson -{ - -static QString sanitizeString(QString str) -{ - str.replace(QLatin1String("\\"), QLatin1String("\\\\")); - str.replace(QLatin1String("\""), QLatin1String("\\\"")); - str.replace(QLatin1String("\b"), QLatin1String("\\b")); - str.replace(QLatin1String("\f"), QLatin1String("\\f")); - str.replace(QLatin1String("\n"), QLatin1String("\\n")); - str.replace(QLatin1String("\r"), QLatin1String("\\r")); - str.replace(QLatin1String("\t"), QLatin1String("\\t")); - return QString(QLatin1String("\"%1\"")).arg(str); -} - -static QByteArray join(const QList &list, const QByteArray &sep) -{ - QByteArray res; - for (const QByteArray &i : list) { - if (!res.isEmpty()) { - res += sep; - } - res += i; - } - return res; -} - -/** - * parse - */ -QVariant Json::parse(const QString &json) -{ - bool success = true; - return Json::parse(json, success); -} - -/** - * parse - */ -QVariant Json::parse(const QString &json, bool &success) -{ - success = true; - - // Return an empty QVariant if the JSON data is either null or empty - if (!json.isNull() || !json.isEmpty()) { - // We'll start from index 0 - int index = 0; - - // Parse the first value - QVariant value = Json::parseValue(json, index, success); - - // Return the parsed value - return value; - } else { - // Return the empty QVariant - return QVariant(); - } -} - -QByteArray Json::serialize(const QVariant &data) -{ - bool success = true; - return Json::serialize(data, success); -} - -QByteArray Json::serialize(const QVariant &data, bool &success) -{ - QByteArray str; - success = true; - - if (!data.isValid()) // invalid or null? - { - str = "null"; - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if ((data.typeId() == QMetaType::Type::QVariantList) || - (data.typeId() == QMetaType::Type::QStringList)) // variant is a list? -#else - else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? -#endif - { - QList values; - const QVariantList list = data.toList(); - for (const QVariant &v : list) { - QByteArray serializedValue = serialize(v); - if (serializedValue.isNull()) { - success = false; - break; - } - values << serializedValue; - } - - str = "[ " + join(values, ", ") + " ]"; - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if ((data.typeId() == QMetaType::Type::QVariantHash)) // variant is a list? -#else - else if (data.type() == QVariant::Hash) // variant is a hash? -#endif - { - const QVariantHash vhash = data.toHash(); - QHashIterator it(vhash); - str = "{ "; - QList pairs; - - while (it.hasNext()) { - it.next(); - QByteArray serializedValue = serialize(it.value()); - - if (serializedValue.isNull()) { - success = false; - break; - } - - pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; - } - - str += join(pairs, ", "); - str += " }"; - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if ((data.typeId() == QMetaType::Type::QVariantMap)) // variant is a list? -#else - else if (data.type() == QVariant::Map) // variant is a map? -#endif - { - const QVariantMap vmap = data.toMap(); - QMapIterator it(vmap); - str = "{ "; - QList pairs; - while (it.hasNext()) { - it.next(); - QByteArray serializedValue = serialize(it.value()); - if (serializedValue.isNull()) { - success = false; - break; - } - pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; - } - str += join(pairs, ", "); - str += " }"; - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if ((data.typeId() == QMetaType::Type::QString) || - (data.typeId() == QMetaType::Type::QByteArray)) // variant is a list? -#else - else if ((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array? -#endif - { - str = sanitizeString(data.toString()).toUtf8(); - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if (data.typeId() == QMetaType::Type::Double) -#else - else if (data.type() == QVariant::Double) // double? -#endif - { - str = QByteArray::number(data.toDouble(), 'g', 20); - if (!str.contains(".") && !str.contains("e")) { - str += ".0"; - } - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if (data.typeId() == QMetaType::Type::Bool) -#else - else if (data.type() == QVariant::Bool) // boolean value? -#endif - { - str = data.toBool() ? "true" : "false"; - } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - else if (data.typeId() == QMetaType::Type::ULongLong) -#else - else if (data.type() == QVariant::ULongLong) // large unsigned number? -#endif - { - str = QByteArray::number(data.value()); - } else if (data.canConvert()) // any signed number? - { - str = QByteArray::number(data.value()); - } else if (data.canConvert()) { - str = QString::number(data.value()).toUtf8(); - } else if (data.canConvert()) // can value be converted to string? - { - // this will catch QDate, QDateTime, QUrl, ... - str = sanitizeString(data.toString()).toUtf8(); - } else { - success = false; - } - if (success) { - return str; - } else { - return QByteArray(); - } -} - -/** - * parseValue - */ -QVariant Json::parseValue(const QString &json, int &index, bool &success) -{ - // Determine what kind of data we should parse by - // checking out the upcoming token - switch (Json::lookAhead(json, index)) { - case JsonTokenString: - return Json::parseString(json, index, success); - case JsonTokenNumber: - return Json::parseNumber(json, index); - case JsonTokenCurlyOpen: - return Json::parseObject(json, index, success); - case JsonTokenSquaredOpen: - return Json::parseArray(json, index, success); - case JsonTokenTrue: - Json::nextToken(json, index); - return QVariant(true); - case JsonTokenFalse: - Json::nextToken(json, index); - return QVariant(false); - case JsonTokenNull: - Json::nextToken(json, index); - return QVariant(); - case JsonTokenNone: - break; - } - - // If there were no tokens, flag the failure and return an empty QVariant - success = false; - return QVariant(); -} - -/** - * parseObject - */ -QVariant Json::parseObject(const QString &json, int &index, bool &success) -{ - QVariantMap map; - int token; - - // Get rid of the whitespace and increment index - Json::nextToken(json, index); - - // Loop through all of the key/value pairs of the object - bool done = false; - while (!done) { - // Get the upcoming token - token = Json::lookAhead(json, index); - - if (token == JsonTokenNone) { - success = false; - return QVariantMap(); - } else if (token == JsonTokenComma) { - Json::nextToken(json, index); - } else if (token == JsonTokenCurlyClose) { - Json::nextToken(json, index); - return map; - } else { - // Parse the key/value pair's name - QString name = Json::parseString(json, index, success).toString(); - - if (!success) { - return QVariantMap(); - } - - // Get the next token - token = Json::nextToken(json, index); - - // If the next token is not a colon, flag the failure - // return an empty QVariant - if (token != JsonTokenColon) { - success = false; - return QVariant(QVariantMap()); - } - - // Parse the key/value pair's value - QVariant value = Json::parseValue(json, index, success); - - if (!success) { - return QVariantMap(); - } - - // Assign the value to the key in the map - map[name] = value; - } - } - - // Return the map successfully - return QVariant(map); -} - -/** - * parseArray - */ -QVariant Json::parseArray(const QString &json, int &index, bool &success) -{ - QVariantList list; - - Json::nextToken(json, index); - - bool done = false; - while (!done) { - int token = Json::lookAhead(json, index); - - if (token == JsonTokenNone) { - success = false; - return QVariantList(); - } else if (token == JsonTokenComma) { - Json::nextToken(json, index); - } else if (token == JsonTokenSquaredClose) { - Json::nextToken(json, index); - break; - } else { - QVariant value = Json::parseValue(json, index, success); - - if (!success) { - return QVariantList(); - } - - list.push_back(value); - } - } - - return QVariant(list); -} - -/** - * parseString - */ -QVariant Json::parseString(const QString &json, int &index, bool &success) -{ - QString s; - QChar c; - - Json::eatWhitespace(json, index); - - c = json[index++]; - - bool complete = false; - while (!complete) { - if (index == json.size()) { - break; - } - - c = json[index++]; - - if (c == '\"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.size()) { - break; - } - - c = json[index++]; - - if (c == '\"') { - s.append('\"'); - } else if (c == '\\') { - s.append('\\'); - } else if (c == '/') { - s.append('/'); - } else if (c == 'b') { - s.append('\b'); - } else if (c == 'f') { - s.append('\f'); - } else if (c == 'n') { - s.append('\n'); - } else if (c == 'r') { - s.append('\r'); - } else if (c == 't') { - s.append('\t'); - } else if (c == 'u') { - int remainingLength = json.size() - index; - - if (remainingLength >= 4) { - QString unicodeStr = json.mid(index, 4); - - int symbol = unicodeStr.toInt(0, 16); - - s.append(QChar(symbol)); - - index += 4; - } else { - break; - } - } - } else { - s.append(c); - } - } - - if (!complete) { - success = false; - return QVariant(); - } - - return QVariant(s); -} - -/** - * parseNumber - */ -QVariant Json::parseNumber(const QString &json, int &index) -{ - Json::eatWhitespace(json, index); - - int lastIndex = Json::lastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - QString numberStr; - - numberStr = json.mid(index, charLength); - - index = lastIndex + 1; - - if (numberStr.contains('.')) { - return QVariant(numberStr.toDouble(NULL)); - } else if (numberStr.startsWith('-')) { - return QVariant(numberStr.toLongLong(NULL)); - } else { - return QVariant(numberStr.toULongLong(NULL)); - } -} - -/** - * lastIndexOfNumber - */ -int Json::lastIndexOfNumber(const QString &json, int index) -{ - static const QString numericCharacters("0123456789+-.eE"); - int lastIndex; - - for (lastIndex = index; lastIndex < json.size(); lastIndex++) { - if (numericCharacters.indexOf(json[lastIndex]) == -1) { - break; - } - } - - return lastIndex - 1; -} - -/** - * eatWhitespace - */ -void Json::eatWhitespace(const QString &json, int &index) -{ - static const QString whitespaceChars(" \t\n\r"); - for (; index < json.size(); index++) { - if (whitespaceChars.indexOf(json[index]) == -1) { - break; - } - } -} - -/** - * lookAhead - */ -int Json::lookAhead(const QString &json, int index) -{ - int saveIndex = index; - return Json::nextToken(json, saveIndex); -} - -/** - * nextToken - */ -int Json::nextToken(const QString &json, int &index) -{ - Json::eatWhitespace(json, index); - - if (index == json.size()) { - return JsonTokenNone; - } - - QChar c = json[index]; - index++; - switch (c.toLatin1()) { - case '{': - return JsonTokenCurlyOpen; - case '}': - return JsonTokenCurlyClose; - case '[': - return JsonTokenSquaredOpen; - case ']': - return JsonTokenSquaredClose; - case ',': - return JsonTokenComma; - case '"': - return JsonTokenString; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return JsonTokenNumber; - case ':': - return JsonTokenColon; - } - - index--; - - int remainingLength = json.size() - index; - - // True - if (remainingLength >= 4) { - if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { - index += 4; - return JsonTokenTrue; - } - } - - // False - if (remainingLength >= 5) { - if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return JsonTokenFalse; - } - } - - // Null - if (remainingLength >= 4) { - if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { - index += 4; - return JsonTokenNull; - } - } - - return JsonTokenNone; -} - -} // namespace QtJson diff --git a/cockatrice/src/qt-json/json.h b/cockatrice/src/qt-json/json.h deleted file mode 100644 index cf0499d4e..000000000 --- a/cockatrice/src/qt-json/json.h +++ /dev/null @@ -1,204 +0,0 @@ -/* Copyright 2011 Eeli Reilin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation - * are those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Eeli Reilin. - */ - -/** - * \file json.h - */ - -#ifndef JSON_H -#define JSON_H - -#include -#include - -namespace QtJson -{ - -/** - * \enum JsonToken - */ -enum JsonToken -{ - JsonTokenNone = 0, - JsonTokenCurlyOpen = 1, - JsonTokenCurlyClose = 2, - JsonTokenSquaredOpen = 3, - JsonTokenSquaredClose = 4, - JsonTokenColon = 5, - JsonTokenComma = 6, - JsonTokenString = 7, - JsonTokenNumber = 8, - JsonTokenTrue = 9, - JsonTokenFalse = 10, - JsonTokenNull = 11 -}; - -/** - * \class Json - * \brief A JSON data parser - * - * Json parses a JSON data into a QVariant hierarchy. - */ -class Json -{ - public: - /** - * Parse a JSON string - * - * \param json The JSON data - */ - static QVariant parse(const QString &json); - - /** - * Parse a JSON string - * - * \param json The JSON data - * \param success The success of the parsing - */ - static QVariant parse(const QString &json, bool &success); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - */ - static QByteArray serialize(const QVariant &data); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QByteArray Textual JSON representation - */ - static QByteArray serialize(const QVariant &data, bool &success); - - private: - /** - * Parses a value starting from index - * - * \param json The JSON data - * \param index The start index - * \param success The success of the parse process - * - * \return QVariant The parsed value - */ - static QVariant parseValue(const QString &json, int &index, - bool &success); - - /** - * Parses an object starting from index - * - * \param json The JSON data - * \param index The start index - * \param success The success of the object parse - * - * \return QVariant The parsed object map - */ - static QVariant parseObject(const QString &json, int &index, - bool &success); - - /** - * Parses an array starting from index - * - * \param json The JSON data - * \param index The starting index - * \param success The success of the array parse - * - * \return QVariant The parsed variant array - */ - static QVariant parseArray(const QString &json, int &index, - bool &success); - - /** - * Parses a string starting from index - * - * \param json The JSON data - * \param index The starting index - * \param success The success of the string parse - * - * \return QVariant The parsed string - */ - static QVariant parseString(const QString &json, int &index, - bool &success); - - /** - * Parses a number starting from index - * - * \param json The JSON data - * \param index The starting index - * - * \return QVariant The parsed number - */ - static QVariant parseNumber(const QString &json, int &index); - - /** - * Get the last index of a number starting from index - * - * \param json The JSON data - * \param index The starting index - * - * \return The last index of the number - */ - static int lastIndexOfNumber(const QString &json, int index); - - /** - * Skip unwanted whitespace symbols starting from index - * - * \param json The JSON data - * \param index The start index - */ - static void eatWhitespace(const QString &json, int &index); - - /** - * Check what token lies ahead - * - * \param json The JSON data - * \param index The starting index - * - * \return int The upcoming token - */ - static int lookAhead(const QString &json, int index); - - /** - * Get the next JSON token - * - * \param json The JSON data - * \param index The starting index - * - * \return int The next JSON token - */ - static int nextToken(const QString &json, int &index); -}; - - -} //end namespace - -#endif //JSON_H diff --git a/cockatrice/src/zip/unzip.cpp b/cockatrice/src/zip/unzip.cpp deleted file mode 100755 index 1e5910051..000000000 --- a/cockatrice/src/zip/unzip.cpp +++ /dev/null @@ -1,1425 +0,0 @@ -/**************************************************************************** -** 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 = 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.size() != 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; - - /* 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++; - - 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/cockatrice/src/zip/unzip.h b/cockatrice/src/zip/unzip.h deleted file mode 100644 index ab57fdc36..000000000 --- a/cockatrice/src/zip/unzip.h +++ /dev/null @@ -1,155 +0,0 @@ -/**************************************************************************** -** 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; - -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/cockatrice/src/zip/unzip_p.h b/cockatrice/src/zip/unzip_p.h deleted file mode 100755 index fca2b071d..000000000 --- a/cockatrice/src/zip/unzip_p.h +++ /dev/null @@ -1,130 +0,0 @@ -/**************************************************************************** -** 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/cockatrice/src/zip/zip.cpp b/cockatrice/src/zip/zip.cpp deleted file mode 100755 index 5b0177293..000000000 --- a/cockatrice/src/zip/zip.cpp +++ /dev/null @@ -1,1619 +0,0 @@ -/**************************************************************************** -** 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/cockatrice/src/zip/zip.h b/cockatrice/src/zip/zip.h deleted file mode 100755 index 79f32ada5..000000000 --- a/cockatrice/src/zip/zip.h +++ /dev/null @@ -1,158 +0,0 @@ -/**************************************************************************** -** 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/cockatrice/src/zip/zip_p.h b/cockatrice/src/zip/zip_p.h deleted file mode 100755 index b8ec6564b..000000000 --- a/cockatrice/src/zip/zip_p.h +++ /dev/null @@ -1,133 +0,0 @@ -/**************************************************************************** -** 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/cockatrice/src/zip/zipentry_p.h b/cockatrice/src/zip/zipentry_p.h deleted file mode 100755 index f0675b6b6..000000000 --- a/cockatrice/src/zip/zipentry_p.h +++ /dev/null @@ -1,91 +0,0 @@ -/**************************************************************************** -** 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/cockatrice/src/zip/zipglobal.cpp b/cockatrice/src/zip/zipglobal.cpp deleted file mode 100644 index e972005f5..000000000 --- a/cockatrice/src/zip/zipglobal.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/**************************************************************************** -** 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_MACOS) -#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_MACOS) -#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 -} - -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 = - CreateFileW(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_MACOS) - - struct utimbuf t_buffer; - t_buffer.actime = t_buffer.modtime = dateTime.toSecsSinceEpoch(); - return utime(fileName.toLocal8Bit().constData(), &t_buffer) == 0; -#endif - - return true; -} -OSDAB_END_NAMESPACE diff --git a/cockatrice/src/zip/zipglobal.h b/cockatrice/src/zip/zipglobal.h deleted file mode 100755 index e7ff33105..000000000 --- a/cockatrice/src/zip/zipglobal.h +++ /dev/null @@ -1,77 +0,0 @@ -/**************************************************************************** -** 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 From be00edd581d4c1bb20604e74f490d3796552f4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Thu, 20 Nov 2025 14:38:26 +0100 Subject: [PATCH 5/7] Adjust to rebase. Took 7 minutes --- cockatrice/CMakeLists.txt | 1 + .../widgets/dialogs/dlg_import_precons.cpp | 17 +- cockatrice/src/interface/window_main.cpp | 1 + .../src/utility/external/lzma/decompress.cpp | 394 +++--- .../src/utility/external/lzma/decompress.h | 5 +- .../src/utility/external/qt-json/json.cpp | 12 +- .../src/utility/external/qt-json/json.h | 287 ++--- cockatrice/src/utility/external/zip/unzip.cpp | 328 ++--- cockatrice/src/utility/external/zip/unzip_p.h | 96 +- cockatrice/src/utility/external/zip/zip.cpp | 1148 ++++++++--------- cockatrice/src/utility/external/zip/zip.h | 132 +- cockatrice/src/utility/external/zip/zip_p.h | 97 +- .../src/utility/external/zip/zipentry_p.h | 54 +- .../src/utility/external/zip/zipglobal.h | 26 +- 14 files changed, 1315 insertions(+), 1283 deletions(-) mode change 100755 => 100644 cockatrice/src/utility/external/zip/unzip.cpp mode change 100755 => 100644 cockatrice/src/utility/external/zip/unzip_p.h mode change 100755 => 100644 cockatrice/src/utility/external/zip/zip.cpp mode change 100755 => 100644 cockatrice/src/utility/external/zip/zip.h mode change 100755 => 100644 cockatrice/src/utility/external/zip/zip_p.h mode change 100755 => 100644 cockatrice/src/utility/external/zip/zipentry_p.h mode change 100755 => 100644 cockatrice/src/utility/external/zip/zipglobal.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 03a64ba3a..d1b2deef9 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -32,6 +32,7 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp src/interface/widgets/dialogs/dlg_forgot_password_request.cpp src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp + src/interface/widgets/dialogs/dlg_import_precons.cpp src/interface/widgets/dialogs/dlg_load_deck.cpp src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp index c80508e70..88329df41 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_import_precons.cpp @@ -1,7 +1,8 @@ #include "dlg_import_precons.h" -#include "../deck/deck_loader.h" +#include "../../deck_loader/deck_loader.h" #include "../settings/cache_settings.h" +#include "libcockatrice/deck_list/deck_list_card_node.h" #include #include @@ -11,7 +12,7 @@ #include #include #include -#include +#include #ifdef HAS_LZMA #include "../../src/utility/external/lzma/decompress.h" @@ -309,7 +310,7 @@ bool LoadPreconsPage::parsePreconsFromByteArray(const QByteArray &data, QString qInfo() << "Importing '" << deckName << "' from" << shortName; - auto *precon = new DeckLoader(); + auto *precon = new DeckLoader(this); for (const auto &cardVal : mainBoard) { QJsonObject cardObj = cardVal.toObject(); @@ -319,20 +320,20 @@ bool LoadPreconsPage::parsePreconsFromByteArray(const QByteArray &data, QString int count = cardObj.value("count").toInt(); QString scryfallId = cardObj.value("identifiers").toObject().value("scryfallId").toString(); - DecklistCardNode *addedCard = precon->addCard(name, "main", -1, setCode, number, scryfallId); + DecklistCardNode *addedCard = precon->getDeckList()->addCard(name, "main", -1, setCode, number, scryfallId); if (count != 1) { addedCard->setNumber(count); } } - precon->setName(deckName); + precon->getDeckList()->setName(deckName); QJsonArray commanderArray = preconData.value("commander").toArray(); if (!commanderArray.isEmpty()) { QJsonObject commanderObj = commanderArray.first().toObject(); QString commanderName = commanderObj.value("name").toString(); QString commanderId = commanderObj.value("identifiers").toObject().value("scryfallId").toString(); - precon->setBannerCard(QPair(commanderName, commanderId)); + precon->getDeckList()->setBannerCard({commanderName, commanderId}); } else { qInfo() << "No commander data found."; } @@ -340,7 +341,7 @@ bool LoadPreconsPage::parsePreconsFromByteArray(const QByteArray &data, QString QString dirPath = QDir::cleanPath(folderPath + QDir::separator() + deckType + QDir::separator() + QString::number(releaseYear) + QDir::separator() + shortName); - QString fullPath = QDir(dirPath).filePath(precon->getName()); + QString fullPath = QDir(dirPath).filePath(precon->getDeckList()->getName()); QDir dir; if (!dir.exists(dirPath)) { @@ -350,7 +351,7 @@ bool LoadPreconsPage::parsePreconsFromByteArray(const QByteArray &data, QString } } - if (precon->getCardList().length() > 1) { + if (precon->getDeckList()->getCardList().length() > 1) { precon->saveToFile(fullPath + ".cod", DeckLoader::CockatriceFormat); } diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 218824945..a93b5e747 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -39,6 +39,7 @@ #include "../main.h" #include "logger.h" #include "version_string.h" +#include "widgets/dialogs/dlg_import_precons.h" #include "widgets/utility/get_text_with_max.h" #include diff --git a/cockatrice/src/utility/external/lzma/decompress.cpp b/cockatrice/src/utility/external/lzma/decompress.cpp index 718cde207..9fc67587c 100644 --- a/cockatrice/src/utility/external/lzma/decompress.cpp +++ b/cockatrice/src/utility/external/lzma/decompress.cpp @@ -9,242 +9,236 @@ * You can do whatever you want with this file. */ - -#include -#include - #include "decompress.h" -XzDecompressor::XzDecompressor(QObject *parent) - : QObject(parent) -{ +#include +#include +XzDecompressor::XzDecompressor(QObject *parent) : QObject(parent) +{ } bool XzDecompressor::decompress(QBuffer *in, QBuffer *out) { - lzma_stream strm = LZMA_STREAM_INIT; - bool success; + lzma_stream strm = LZMA_STREAM_INIT; + bool success; - if (!init_decoder(&strm)) { - return false; - } + if (!init_decoder(&strm)) { + return false; + } - success = internal_decompress(&strm, in, out); + success = internal_decompress(&strm, in, out); - // Free the memory allocated for the decoder. This only needs to be - // done after the last file. - lzma_end(&strm); + // Free the memory allocated for the decoder. This only needs to be + // done after the last file. + lzma_end(&strm); - return success; + return success; } bool XzDecompressor::init_decoder(lzma_stream *strm) { - // Initialize a .xz decoder. The decoder supports a memory usage limit - // and a set of flags. - // - // The memory usage of the decompressor depends on the settings used - // to compress a .xz file. It can vary from less than a megabyte to - // a few gigabytes, but in practice (at least for now) it rarely - // exceeds 65 MiB because that's how much memory is required to - // decompress files created with "xz -9". Settings requiring more - // memory take extra effort to use and don't (at least for now) - // provide significantly better compression in most cases. - // - // Memory usage limit is useful if it is important that the - // decompressor won't consume gigabytes of memory. The need - // for limiting depends on the application. In this example, - // no memory usage limiting is used. This is done by setting - // the limit to UINT64_MAX. - // - // The .xz format allows concatenating compressed files as is: - // - // echo foo | xz > foobar.xz - // echo bar | xz >> foobar.xz - // - // When decompressing normal standalone .xz files, LZMA_CONCATENATED - // should always be used to support decompression of concatenated - // .xz files. If LZMA_CONCATENATED isn't used, the decoder will stop - // after the first .xz stream. This can be useful when .xz data has - // been embedded inside another file format. - // - // Flags other than LZMA_CONCATENATED are supported too, and can - // be combined with bitwise-or. See lzma/container.h - // (src/liblzma/api/lzma/container.h in the source package or e.g. - // /usr/include/lzma/container.h depending on the install prefix) - // for details. - lzma_ret ret = lzma_stream_decoder( - strm, UINT64_MAX, LZMA_CONCATENATED); + // Initialize a .xz decoder. The decoder supports a memory usage limit + // and a set of flags. + // + // The memory usage of the decompressor depends on the settings used + // to compress a .xz file. It can vary from less than a megabyte to + // a few gigabytes, but in practice (at least for now) it rarely + // exceeds 65 MiB because that's how much memory is required to + // decompress files created with "xz -9". Settings requiring more + // memory take extra effort to use and don't (at least for now) + // provide significantly better compression in most cases. + // + // Memory usage limit is useful if it is important that the + // decompressor won't consume gigabytes of memory. The need + // for limiting depends on the application. In this example, + // no memory usage limiting is used. This is done by setting + // the limit to UINT64_MAX. + // + // The .xz format allows concatenating compressed files as is: + // + // echo foo | xz > foobar.xz + // echo bar | xz >> foobar.xz + // + // When decompressing normal standalone .xz files, LZMA_CONCATENATED + // should always be used to support decompression of concatenated + // .xz files. If LZMA_CONCATENATED isn't used, the decoder will stop + // after the first .xz stream. This can be useful when .xz data has + // been embedded inside another file format. + // + // Flags other than LZMA_CONCATENATED are supported too, and can + // be combined with bitwise-or. See lzma/container.h + // (src/liblzma/api/lzma/container.h in the source package or e.g. + // /usr/include/lzma/container.h depending on the install prefix) + // for details. + lzma_ret ret = lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED); - // Return successfully if the initialization went fine. - if (ret == LZMA_OK) - return true; + // Return successfully if the initialization went fine. + if (ret == LZMA_OK) + return true; - // Something went wrong. The possible errors are documented in - // lzma/container.h (src/liblzma/api/lzma/container.h in the source - // package or e.g. /usr/include/lzma/container.h depending on the - // install prefix). - // - // Note that LZMA_MEMLIMIT_ERROR is never possible here. If you - // specify a very tiny limit, the error will be delayed until - // the first headers have been parsed by a call to lzma_code(). - const char *msg; - switch (ret) { - case LZMA_MEM_ERROR: - msg = "Memory allocation failed"; - break; + // Something went wrong. The possible errors are documented in + // lzma/container.h (src/liblzma/api/lzma/container.h in the source + // package or e.g. /usr/include/lzma/container.h depending on the + // install prefix). + // + // Note that LZMA_MEMLIMIT_ERROR is never possible here. If you + // specify a very tiny limit, the error will be delayed until + // the first headers have been parsed by a call to lzma_code(). + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: + msg = "Memory allocation failed"; + break; - case LZMA_OPTIONS_ERROR: - msg = "Unsupported decompressor flags"; - break; + case LZMA_OPTIONS_ERROR: + msg = "Unsupported decompressor flags"; + break; - default: - // This is most likely LZMA_PROG_ERROR indicating a bug in - // this program or in liblzma. It is inconvenient to have a - // separate error message for errors that should be impossible - // to occur, but knowing the error code is important for - // debugging. That's why it is good to print the error code - // at least when there is no good error message to show. - msg = "Unknown error, possibly a bug"; - break; - } + default: + // This is most likely LZMA_PROG_ERROR indicating a bug in + // this program or in liblzma. It is inconvenient to have a + // separate error message for errors that should be impossible + // to occur, but knowing the error code is important for + // debugging. That's why it is good to print the error code + // at least when there is no good error message to show. + msg = "Unknown error, possibly a bug"; + break; + } - qDebug() << "Error initializing the decoder:" << msg << "(error code " << ret << ")"; - return false; + qDebug() << "Error initializing the decoder:" << msg << "(error code " << ret << ")"; + return false; } - bool XzDecompressor::internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out) { - // When LZMA_CONCATENATED flag was used when initializing the decoder, - // we need to tell lzma_code() when there will be no more input. - // This is done by setting action to LZMA_FINISH instead of LZMA_RUN - // in the same way as it is done when encoding. - // - // When LZMA_CONCATENATED isn't used, there is no need to use - // LZMA_FINISH to tell when all the input has been read, but it - // is still OK to use it if you want. When LZMA_CONCATENATED isn't - // used, the decoder will stop after the first .xz stream. In that - // case some unused data may be left in strm->next_in. - lzma_action action = LZMA_RUN; + // When LZMA_CONCATENATED flag was used when initializing the decoder, + // we need to tell lzma_code() when there will be no more input. + // This is done by setting action to LZMA_FINISH instead of LZMA_RUN + // in the same way as it is done when encoding. + // + // When LZMA_CONCATENATED isn't used, there is no need to use + // LZMA_FINISH to tell when all the input has been read, but it + // is still OK to use it if you want. When LZMA_CONCATENATED isn't + // used, the decoder will stop after the first .xz stream. In that + // case some unused data may be left in strm->next_in. + lzma_action action = LZMA_RUN; - uint8_t inbuf[BUFSIZ]; - uint8_t outbuf[BUFSIZ]; - qint64 bytesAvailable; + uint8_t inbuf[BUFSIZ]; + uint8_t outbuf[BUFSIZ]; + qint64 bytesAvailable; - strm->next_in = NULL; - strm->avail_in = 0; - strm->next_out = outbuf; - strm->avail_out = sizeof(outbuf); - while (true) { - if (strm->avail_in == 0) { - strm->next_in = inbuf; - bytesAvailable = in->bytesAvailable(); - if(bytesAvailable == 0) { - // Once the end of the input file has been reached, - // we need to tell lzma_code() that no more input - // will be coming. As said before, this isn't required - // if the LZMA_CONCATENATED flag isn't used when - // initializing the decoder. - action = LZMA_FINISH; - } else if(bytesAvailable >= BUFSIZ) { - in->read((char*) inbuf, BUFSIZ); - strm->avail_in = BUFSIZ; - } else { - in->read((char*) inbuf, bytesAvailable); - strm->avail_in = bytesAvailable; - } - } + strm->next_in = NULL; + strm->avail_in = 0; + strm->next_out = outbuf; + strm->avail_out = sizeof(outbuf); + while (true) { + if (strm->avail_in == 0) { + strm->next_in = inbuf; + bytesAvailable = in->bytesAvailable(); + if (bytesAvailable == 0) { + // Once the end of the input file has been reached, + // we need to tell lzma_code() that no more input + // will be coming. As said before, this isn't required + // if the LZMA_CONCATENATED flag isn't used when + // initializing the decoder. + action = LZMA_FINISH; + } else if (bytesAvailable >= BUFSIZ) { + in->read((char *)inbuf, BUFSIZ); + strm->avail_in = BUFSIZ; + } else { + in->read((char *)inbuf, bytesAvailable); + strm->avail_in = bytesAvailable; + } + } - lzma_ret ret = lzma_code(strm, action); + lzma_ret ret = lzma_code(strm, action); - if (strm->avail_out == 0 || ret == LZMA_STREAM_END) { - qint64 write_size = sizeof(outbuf) - strm->avail_out; + if (strm->avail_out == 0 || ret == LZMA_STREAM_END) { + qint64 write_size = sizeof(outbuf) - strm->avail_out; - if (out->write((char *) outbuf, write_size) != write_size) { - qDebug() << "Write error"; - return false; - } + if (out->write((char *)outbuf, write_size) != write_size) { + qDebug() << "Write error"; + return false; + } - strm->next_out = outbuf; - strm->avail_out = sizeof(outbuf); - } + strm->next_out = outbuf; + strm->avail_out = sizeof(outbuf); + } - if (ret != LZMA_OK) { - // Once everything has been decoded successfully, the - // return value of lzma_code() will be LZMA_STREAM_END. - // - // It is important to check for LZMA_STREAM_END. Do not - // assume that getting ret != LZMA_OK would mean that - // everything has gone well or that when you aren't - // getting more output it must have successfully - // decoded everything. - if (ret == LZMA_STREAM_END) - return true; + if (ret != LZMA_OK) { + // Once everything has been decoded successfully, the + // return value of lzma_code() will be LZMA_STREAM_END. + // + // It is important to check for LZMA_STREAM_END. Do not + // assume that getting ret != LZMA_OK would mean that + // everything has gone well or that when you aren't + // getting more output it must have successfully + // decoded everything. + if (ret == LZMA_STREAM_END) + return true; - // It's not LZMA_OK nor LZMA_STREAM_END, - // so it must be an error code. See lzma/base.h - // (src/liblzma/api/lzma/base.h in the source package - // or e.g. /usr/include/lzma/base.h depending on the - // install prefix) for the list and documentation of - // possible values. Many values listen in lzma_ret - // enumeration aren't possible in this example, but - // can be made possible by enabling memory usage limit - // or adding flags to the decoder initialization. - const char *msg; - switch (ret) { - case LZMA_MEM_ERROR: - msg = "Memory allocation failed"; - break; + // It's not LZMA_OK nor LZMA_STREAM_END, + // so it must be an error code. See lzma/base.h + // (src/liblzma/api/lzma/base.h in the source package + // or e.g. /usr/include/lzma/base.h depending on the + // install prefix) for the list and documentation of + // possible values. Many values listen in lzma_ret + // enumeration aren't possible in this example, but + // can be made possible by enabling memory usage limit + // or adding flags to the decoder initialization. + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: + msg = "Memory allocation failed"; + break; - case LZMA_FORMAT_ERROR: - // .xz magic bytes weren't found. - msg = "The input is not in the .xz format"; - break; + case LZMA_FORMAT_ERROR: + // .xz magic bytes weren't found. + msg = "The input is not in the .xz format"; + break; - case LZMA_OPTIONS_ERROR: - // For example, the headers specify a filter - // that isn't supported by this liblzma - // version (or it hasn't been enabled when - // building liblzma, but no-one sane does - // that unless building liblzma for an - // embedded system). Upgrading to a newer - // liblzma might help. - // - // Note that it is unlikely that the file has - // accidentally became corrupt if you get this - // error. The integrity of the .xz headers is - // always verified with a CRC32, so - // unintentionally corrupt files can be - // distinguished from unsupported files. - msg = "Unsupported compression options"; - break; + case LZMA_OPTIONS_ERROR: + // For example, the headers specify a filter + // that isn't supported by this liblzma + // version (or it hasn't been enabled when + // building liblzma, but no-one sane does + // that unless building liblzma for an + // embedded system). Upgrading to a newer + // liblzma might help. + // + // Note that it is unlikely that the file has + // accidentally became corrupt if you get this + // error. The integrity of the .xz headers is + // always verified with a CRC32, so + // unintentionally corrupt files can be + // distinguished from unsupported files. + msg = "Unsupported compression options"; + break; - case LZMA_DATA_ERROR: - msg = "Compressed file is corrupt"; - break; + case LZMA_DATA_ERROR: + msg = "Compressed file is corrupt"; + break; - case LZMA_BUF_ERROR: - // Typically this error means that a valid - // file has got truncated, but it might also - // be a damaged part in the file that makes - // the decoder think the file is truncated. - // If you prefer, you can use the same error - // message for this as for LZMA_DATA_ERROR. - msg = "Compressed file is truncated or " - "otherwise corrupt"; - break; + case LZMA_BUF_ERROR: + // Typically this error means that a valid + // file has got truncated, but it might also + // be a damaged part in the file that makes + // the decoder think the file is truncated. + // If you prefer, you can use the same error + // message for this as for LZMA_DATA_ERROR. + msg = "Compressed file is truncated or " + "otherwise corrupt"; + break; - default: - // This is most likely LZMA_PROG_ERROR. - msg = "Unknown error, possibly a bug"; - break; - } + default: + // This is most likely LZMA_PROG_ERROR. + msg = "Unknown error, possibly a bug"; + break; + } - qDebug() << "Decoder error:" << msg << "(error code " << ret << ")"; - return false; - } - } + qDebug() << "Decoder error:" << msg << "(error code " << ret << ")"; + return false; + } + } } - diff --git a/cockatrice/src/utility/external/lzma/decompress.h b/cockatrice/src/utility/external/lzma/decompress.h index f0e315f8b..56817a577 100644 --- a/cockatrice/src/utility/external/lzma/decompress.h +++ b/cockatrice/src/utility/external/lzma/decompress.h @@ -1,16 +1,17 @@ #ifndef XZ_DECOMPRESS_H #define XZ_DECOMPRESS_H -#include #include +#include class XzDecompressor : public QObject { Q_OBJECT public: XzDecompressor(QObject *parent = 0); - ~XzDecompressor() { }; + ~XzDecompressor() {}; bool decompress(QBuffer *in, QBuffer *out); + private: bool init_decoder(lzma_stream *strm); bool internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out); diff --git a/cockatrice/src/utility/external/qt-json/json.cpp b/cockatrice/src/utility/external/qt-json/json.cpp index 2fffd0f70..e365c4082 100644 --- a/cockatrice/src/utility/external/qt-json/json.cpp +++ b/cockatrice/src/utility/external/qt-json/json.cpp @@ -113,7 +113,7 @@ QByteArray Json::serialize(const QVariant &data, bool &success) else if ((data.typeId() == QMetaType::Type::QVariantList) || (data.typeId() == QMetaType::Type::QStringList)) // variant is a list? #else - else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? + else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? #endif { QList values; @@ -132,7 +132,7 @@ QByteArray Json::serialize(const QVariant &data, bool &success) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) else if ((data.typeId() == QMetaType::Type::QVariantHash)) // variant is a list? #else - else if (data.type() == QVariant::Hash) // variant is a hash? + else if (data.type() == QVariant::Hash) // variant is a hash? #endif { const QVariantHash vhash = data.toHash(); @@ -158,7 +158,7 @@ QByteArray Json::serialize(const QVariant &data, bool &success) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) else if ((data.typeId() == QMetaType::Type::QVariantMap)) // variant is a list? #else - else if (data.type() == QVariant::Map) // variant is a map? + else if (data.type() == QVariant::Map) // variant is a map? #endif { const QVariantMap vmap = data.toMap(); @@ -189,7 +189,7 @@ QByteArray Json::serialize(const QVariant &data, bool &success) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) else if (data.typeId() == QMetaType::Type::Double) #else - else if (data.type() == QVariant::Double) // double? + else if (data.type() == QVariant::Double) // double? #endif { str = QByteArray::number(data.toDouble(), 'g', 20); @@ -200,7 +200,7 @@ QByteArray Json::serialize(const QVariant &data, bool &success) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) else if (data.typeId() == QMetaType::Type::Bool) #else - else if (data.type() == QVariant::Bool) // boolean value? + else if (data.type() == QVariant::Bool) // boolean value? #endif { str = data.toBool() ? "true" : "false"; @@ -208,7 +208,7 @@ QByteArray Json::serialize(const QVariant &data, bool &success) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) else if (data.typeId() == QMetaType::Type::ULongLong) #else - else if (data.type() == QVariant::ULongLong) // large unsigned number? + else if (data.type() == QVariant::ULongLong) // large unsigned number? #endif { str = QByteArray::number(data.value()); diff --git a/cockatrice/src/utility/external/qt-json/json.h b/cockatrice/src/utility/external/qt-json/json.h index cf0499d4e..2fdb08c63 100644 --- a/cockatrice/src/utility/external/qt-json/json.h +++ b/cockatrice/src/utility/external/qt-json/json.h @@ -1,28 +1,28 @@ /* Copyright 2011 Eeli Reilin. All rights reserved. * - * Redistribution and use in source and binary forms, with or without + * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - * 1. Redistributions of source code must retain the above copyright notice, + * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation + * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * The views and conclusions contained in the software and documentation - * are those of the authors and should not be interpreted as representing + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing * official policies, either expressed or implied, of Eeli Reilin. */ @@ -33,8 +33,8 @@ #ifndef JSON_H #define JSON_H -#include #include +#include namespace QtJson { @@ -44,18 +44,18 @@ namespace QtJson */ enum JsonToken { - JsonTokenNone = 0, - JsonTokenCurlyOpen = 1, - JsonTokenCurlyClose = 2, - JsonTokenSquaredOpen = 3, - JsonTokenSquaredClose = 4, - JsonTokenColon = 5, - JsonTokenComma = 6, - JsonTokenString = 7, - JsonTokenNumber = 8, - JsonTokenTrue = 9, - JsonTokenFalse = 10, - JsonTokenNull = 11 + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 }; /** @@ -66,139 +66,134 @@ enum JsonToken */ class Json { - public: - /** - * Parse a JSON string - * - * \param json The JSON data - */ - static QVariant parse(const QString &json); +public: + /** + * Parse a JSON string + * + * \param json The JSON data + */ + static QVariant parse(const QString &json); - /** - * Parse a JSON string - * - * \param json The JSON data - * \param success The success of the parsing - */ - static QVariant parse(const QString &json, bool &success); + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + static QVariant parse(const QString &json, bool &success); - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - */ - static QByteArray serialize(const QVariant &data); + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + */ + static QByteArray serialize(const QVariant &data); - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QByteArray Textual JSON representation - */ - static QByteArray serialize(const QVariant &data, bool &success); + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation + */ + static QByteArray serialize(const QVariant &data, bool &success); - private: - /** - * Parses a value starting from index - * - * \param json The JSON data - * \param index The start index - * \param success The success of the parse process - * - * \return QVariant The parsed value - */ - static QVariant parseValue(const QString &json, int &index, - bool &success); +private: + /** + * Parses a value starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the parse process + * + * \return QVariant The parsed value + */ + static QVariant parseValue(const QString &json, int &index, bool &success); - /** - * Parses an object starting from index - * - * \param json The JSON data - * \param index The start index - * \param success The success of the object parse - * - * \return QVariant The parsed object map - */ - static QVariant parseObject(const QString &json, int &index, - bool &success); + /** + * Parses an object starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the object parse + * + * \return QVariant The parsed object map + */ + static QVariant parseObject(const QString &json, int &index, bool &success); - /** - * Parses an array starting from index - * - * \param json The JSON data - * \param index The starting index - * \param success The success of the array parse - * - * \return QVariant The parsed variant array - */ - static QVariant parseArray(const QString &json, int &index, - bool &success); + /** + * Parses an array starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the array parse + * + * \return QVariant The parsed variant array + */ + static QVariant parseArray(const QString &json, int &index, bool &success); - /** - * Parses a string starting from index - * - * \param json The JSON data - * \param index The starting index - * \param success The success of the string parse - * - * \return QVariant The parsed string - */ - static QVariant parseString(const QString &json, int &index, - bool &success); + /** + * Parses a string starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the string parse + * + * \return QVariant The parsed string + */ + static QVariant parseString(const QString &json, int &index, bool &success); - /** - * Parses a number starting from index - * - * \param json The JSON data - * \param index The starting index - * - * \return QVariant The parsed number - */ - static QVariant parseNumber(const QString &json, int &index); + /** + * Parses a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return QVariant The parsed number + */ + static QVariant parseNumber(const QString &json, int &index); - /** - * Get the last index of a number starting from index - * - * \param json The JSON data - * \param index The starting index - * - * \return The last index of the number - */ - static int lastIndexOfNumber(const QString &json, int index); + /** + * Get the last index of a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return The last index of the number + */ + static int lastIndexOfNumber(const QString &json, int index); - /** - * Skip unwanted whitespace symbols starting from index - * - * \param json The JSON data - * \param index The start index - */ - static void eatWhitespace(const QString &json, int &index); + /** + * Skip unwanted whitespace symbols starting from index + * + * \param json The JSON data + * \param index The start index + */ + static void eatWhitespace(const QString &json, int &index); - /** - * Check what token lies ahead - * - * \param json The JSON data - * \param index The starting index - * - * \return int The upcoming token - */ - static int lookAhead(const QString &json, int index); + /** + * Check what token lies ahead + * + * \param json The JSON data + * \param index The starting index + * + * \return int The upcoming token + */ + static int lookAhead(const QString &json, int index); - /** - * Get the next JSON token - * - * \param json The JSON data - * \param index The starting index - * - * \return int The next JSON token - */ - static int nextToken(const QString &json, int &index); + /** + * Get the next JSON token + * + * \param json The JSON data + * \param index The starting index + * + * \return int The next JSON token + */ + static int nextToken(const QString &json, int &index); }; +} // namespace QtJson -} //end namespace - -#endif //JSON_H +#endif // JSON_H diff --git a/cockatrice/src/utility/external/zip/unzip.cpp b/cockatrice/src/utility/external/zip/unzip.cpp old mode 100755 new mode 100644 index 1e5910051..7378640ef --- a/cockatrice/src/utility/external/zip/unzip.cpp +++ b/cockatrice/src/utility/external/zip/unzip.cpp @@ -26,6 +26,7 @@ **********************************************************************/ #include "unzip.h" + #include "unzip_p.h" #include "zipentry_p.h" @@ -64,7 +65,8 @@ \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::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. @@ -133,11 +135,10 @@ #define UNZIP_VERSION 0x14 //! CRC32 routine -#define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) +#define CRC32(c, b) crcTable[((int)c ^ b) & 0xff] ^ (c >> 8) OSDAB_BEGIN_NAMESPACE(Zip) - /************************************************************************ ZipEntry *************************************************************************/ @@ -153,39 +154,28 @@ UnZip::ZipEntry::ZipEntry() 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() +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(); + uBuffer = (unsigned char *)buffer1; + crcTable = (quint32 *)get_crc_table(); } //! \internal -void UnzipPrivate::deviceDestroyed(QObject*) +void UnzipPrivate::deviceDestroyed(QObject *) { qDebug("Unexpected device destruction detected."); do_closeArchive(); } //! \internal Parses a Zip archive. -UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) +UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice *dev) { Q_ASSERT(!device); Q_ASSERT(dev); @@ -197,7 +187,7 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) device = dev; if (device != file) - connect(device, SIGNAL(destroyed(QObject*)), this, SLOT(deviceDestroyed(QObject*))); + connect(device, SIGNAL(destroyed(QObject *)), this, SLOT(deviceDestroyed(QObject *))); UnZip::ErrorCode ec; @@ -228,7 +218,7 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) } } - if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) ) + if (!(buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02)) break; if ((ec = parseCentralDirectoryRecord()) != UnZip::Ok) @@ -262,7 +252,7 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) file name (variable size) extra field (variable size) */ -UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, const ZipEntryP& entry) +UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString &path, const ZipEntryP &entry) { Q_ASSERT(device); @@ -302,8 +292,7 @@ UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, const checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD]; if (!checkFailed) checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1]; - if (!hasDataDescriptor) - { + if (!hasDataDescriptor) { if (!checkFailed) checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32); if (!checkFailed) @@ -361,11 +350,9 @@ UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, const } // 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) - ) + 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; } @@ -403,7 +390,7 @@ UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() if (length < UNZIP_EOCD_SIZE) return UnZip::InvalidArchive; - if (!device->seek( offset )) + if (!device->seek(offset)) return UnZip::SeekFailed; if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) @@ -416,22 +403,22 @@ UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() eocdOffset = offset; } else { qint64 read; - char* p = 0; + char *p = 0; offset -= UNZIP_EOCD_SIZE; if (offset <= 0) return UnZip::InvalidArchive; - if (!device->seek( offset )) + if (!device->seek(offset)) return UnZip::SeekFailed; while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0) { - if ( (p = strstr(buffer1, "PK\5\6")) != 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) ); + device->seek(offset + (p - buffer1)); eocdFound = true; eocdOffset = offset + (p - buffer1); @@ -447,7 +434,7 @@ UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() if (offset <= 0) return UnZip::InvalidArchive; - if (!device->seek( offset )) + if (!device->seek(offset)) return UnZip::SeekFailed; } } @@ -456,13 +443,13 @@ UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() return UnZip::InvalidArchive; // Parse EOCD to locate CD offset - offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4); + offset = getULong((const unsigned char *)buffer1, UNZIP_EOCD_OFF_CDOFF + 4); cdOffset = offset; - cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4); + cdEntryCount = getUShort((const unsigned char *)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4); - quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4); + quint16 commentLength = getUShort((const unsigned char *)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4); if (commentLength != 0) { QByteArray c = device->read(commentLength); if (c.size() != commentLength) @@ -472,7 +459,7 @@ UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() } // Seek to the start of the CD record - if (!device->seek( cdOffset )) + if (!device->seek(cdOffset)) return UnZip::SeekFailed; return UnZip::Ok; @@ -561,14 +548,14 @@ UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() 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); + .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 )) + if (!device->seek(device->pos() + skipLength)) ec = UnZip::SeekFailed; unsupportedEntryCount++; } @@ -576,7 +563,7 @@ UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() return ec; } - ZipEntryP* h = new ZipEntryP; + ZipEntryP *h = new ZipEntryP; h->compMethod = compMethod; h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG]; @@ -594,7 +581,7 @@ UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() // Skip extra field (if any) if (szExtra != 0) { - if (!device->seek( device->pos() + szExtra )) { + if (!device->seek(device->pos() + szExtra)) { delete h; return UnZip::SeekFailed; } @@ -613,7 +600,7 @@ UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET); if (!headers) - headers = new QMap(); + headers = new QMap(); headers->insert(filename, h); return UnZip::Ok; @@ -659,8 +646,10 @@ void UnzipPrivate::do_closeArchive() } //! \internal -UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& entry, - const QDir& dir, UnZip::ExtractionOptions options) +UnZip::ErrorCode UnzipPrivate::extractFile(const QString &path, + const ZipEntryP &entry, + const QDir &dir, + UnZip::ExtractionOptions options) { QString name(path); QString dirname; @@ -742,9 +731,11 @@ UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& } //! \internal -UnZip::ErrorCode UnzipPrivate::extractStoredFile( - const quint32 szComp, quint32** keys, quint32& myCRC, QIODevice* outDev, - UnZip::ExtractionOptions options) +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; @@ -757,7 +748,7 @@ UnZip::ErrorCode UnzipPrivate::extractStoredFile( qint64 read; quint64 tot = 0; - while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 ) { + while ((read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0) { if (isEncrypted) decryptBytes(*keys, buffer1, read); @@ -773,15 +764,15 @@ UnZip::ErrorCode UnzipPrivate::extractStoredFile( break; } - return (read < 0) - ? UnZip::ReadFailed - : UnZip::Ok; + return (read < 0) ? UnZip::ReadFailed : UnZip::Ok; } //! \internal -UnZip::ErrorCode UnzipPrivate::inflateFile( - const quint32 szComp, quint32** keys, quint32& myCRC, QIODevice* outDev, - UnZip::ExtractionOptions options) +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; @@ -805,7 +796,7 @@ UnZip::ErrorCode UnzipPrivate::inflateFile( int zret; // Use inflateInit2 with negative windowBits to get raw decompression - if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK ) + if ((zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK) return UnZip::ZlibError; int szDecomp; @@ -826,24 +817,24 @@ UnZip::ErrorCode UnzipPrivate::inflateFile( cur++; - zstr.avail_in = (uInt) read; - zstr.next_in = (Bytef*) buffer1; + 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;; + 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: - ; + 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; @@ -854,7 +845,7 @@ UnZip::ErrorCode UnzipPrivate::inflateFile( } } - myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp); + myCRC = crc32(myCRC, (const Bytef *)buffer2, szDecomp); } while (zstr.avail_out == 0); @@ -865,8 +856,10 @@ UnZip::ErrorCode UnzipPrivate::inflateFile( } //! \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) +UnZip::ErrorCode UnzipPrivate::extractFile(const QString &path, + const ZipEntryP &entry, + QIODevice *outDev, + UnZip::ExtractionOptions options) { const bool verify = (options & UnZip::VerifyOnly); @@ -889,11 +882,10 @@ UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& quint32 szComp = entry.szComp; if (entry.isEncrypted()) { UnZip::ErrorCode e = testPassword(keys, path, entry); - if (e != UnZip::Ok) - { + if (e != UnZip::Ok) { qDebug() << QString("Unable to decrypt %1").arg(path); return e; - }//! Encryption header size + } //! Encryption header size szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size } @@ -904,7 +896,7 @@ UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& } quint32 myCRC = crc32(0L, Z_NULL, 0); - quint32* k = keys; + quint32 *k = keys; UnZip::ErrorCode ec = UnZip::Ok; if (entry.compMethod == 0) { @@ -920,7 +912,7 @@ UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, const ZipEntryP& } //! \internal Creates a new directory and all the needed parent directories. -bool UnzipPrivate::createDirectory(const QString& path) +bool UnzipPrivate::createDirectory(const QString &path) { QDir d(path); if (!d.exists() && !d.mkpath(path)) { @@ -934,12 +926,12 @@ bool UnzipPrivate::createDirectory(const QString& path) /*! \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 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); + 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; } @@ -947,16 +939,16 @@ quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const /*! \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 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); + 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; } @@ -964,9 +956,9 @@ quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const /*! \internal Reads an quint16 (2 bytes) from a byte array starting at given offset. */ -quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const +quint16 UnzipPrivate::getUShort(const unsigned char *data, quint32 offset) const { - return (quint16) data[offset] | (((quint16)data[offset+1]) << 8); + return (quint16)data[offset] | (((quint16)data[offset + 1]) << 8); } /*! @@ -981,7 +973,7 @@ int UnzipPrivate::decryptByte(quint32 key2) const /*! \internal Update the encryption keys with the next byte of plain text */ -void UnzipPrivate::updateKeys(quint32* keys, int c) const +void UnzipPrivate::updateKeys(quint32 *keys, int c) const { keys[0] = CRC32(keys[0], c); keys[1] += keys[0] & 0xff; @@ -993,7 +985,7 @@ void UnzipPrivate::updateKeys(quint32* keys, int c) const \internal Initialize the encryption keys and the random header according to the given password. */ -void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const +void UnzipPrivate::initKeys(const QString &pwd, quint32 *keys) const { keys[0] = 305419896L; keys[1] = 591751049L; @@ -1001,7 +993,7 @@ void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const QByteArray pwdBytes = pwd.toLatin1(); int sz = pwdBytes.size(); - const char* ascii = pwdBytes.data(); + const char *ascii = pwdBytes.data(); for (int i = 0; i < sz; ++i) updateKeys(keys, (int)ascii[i]); @@ -1012,7 +1004,7 @@ void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const 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) +UnZip::ErrorCode UnzipPrivate::testPassword(quint32 *keys, const QString &_file, const ZipEntryP &header) { Q_UNUSED(_file); Q_ASSERT(device); @@ -1032,7 +1024,7 @@ UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString&_file, /*! \internal Tests a set of keys on the encryption header. */ -bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys) +bool UnzipPrivate::testKeys(const ZipEntryP &header, quint32 *keys) { char lastByte; @@ -1051,7 +1043,7 @@ bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys) /*! \internal Decrypts an array of bytes long \p read. */ -void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 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])); @@ -1081,7 +1073,6 @@ QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsig return dt; } - /************************************************************************ Public interface *************************************************************************/ @@ -1113,7 +1104,7 @@ bool UnZip::isOpen() const /*! Opens a zip archive and reads the files list. Closes any previously opened archive. */ -UnZip::ErrorCode UnZip::openArchive(const QString& filename) +UnZip::ErrorCode UnZip::openArchive(const QString &filename) { closeArchive(); @@ -1140,7 +1131,7 @@ UnZip::ErrorCode UnZip::openArchive(const QString& filename) Closes any previously opened archive. \warning The class takes DOES NOT take ownership of the device. */ -UnZip::ErrorCode UnZip::openArchive(QIODevice* device) +UnZip::ErrorCode UnZip::openArchive(QIODevice *device) { closeArchive(); @@ -1170,25 +1161,56 @@ QString UnZip::archiveComment() const */ 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: ; + 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."); @@ -1197,7 +1219,7 @@ QString UnZip::formatError(UnZip::ErrorCode c) const /*! Returns true if the archive contains a file with the given path and name. */ -bool UnZip::contains(const QString& file) const +bool UnZip::contains(const QString &file) const { return d->headers ? d->headers->contains(file) : false; } @@ -1219,9 +1241,8 @@ QList UnZip::entryList() const if (!d->headers) return list; - for (QMap::ConstIterator it = d->headers->constBegin(); - it != d->headers->constEnd(); ++it) { - const ZipEntryP* entry = it.value(); + for (QMap::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it) { + const ZipEntryP *entry = it.value(); Q_ASSERT(entry != 0); ZipEntry z; @@ -1256,7 +1277,7 @@ UnZip::ErrorCode UnZip::verifyArchive() /*! Extracts the whole archive to a directory. */ -UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options) +UnZip::ErrorCode UnZip::extractAll(const QString &dirname, ExtractionOptions options) { return extractAll(QDir(dirname), options); } @@ -1265,7 +1286,7 @@ UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions opt Extracts the whole archive to a directory. Stops extraction at the first error. */ -UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) +UnZip::ErrorCode UnZip::extractAll(const QDir &dir, ExtractionOptions options) { // this should only happen if we didn't call openArchive() yet if (!d->device) @@ -1276,10 +1297,10 @@ UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) ErrorCode ec = Ok; - QMap::ConstIterator it = d->headers->constBegin(); - const QMap::ConstIterator end = d->headers->constEnd(); + QMap::ConstIterator it = d->headers->constBegin(); + const QMap::ConstIterator end = d->headers->constEnd(); while (it != end) { - ZipEntryP* entry = it.value(); + ZipEntryP *entry = it.value(); Q_ASSERT(entry != 0); if ((entry->isEncrypted()) && d->skipAllEncrypted) { ++it; @@ -1289,20 +1310,19 @@ UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) 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: - ; + 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) { @@ -1318,7 +1338,7 @@ UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) /*! Extracts a single file to a directory. */ -UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options) +UnZip::ErrorCode UnZip::extractFile(const QString &filename, const QString &dirname, ExtractionOptions options) { return extractFile(filename, QDir(dirname), options); } @@ -1326,16 +1346,16 @@ UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirn /*! Extracts a single file to a directory. */ -UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options) +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); + QMap::Iterator itr = d->headers->find(filename); if (itr != d->headers->end()) { - ZipEntryP* entry = itr.value(); + ZipEntryP *entry = itr.value(); Q_ASSERT(entry != 0); return d->extractFile(itr.key(), *entry, dir, options); } @@ -1346,7 +1366,7 @@ UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, Ex /*! Extracts a single file to a directory. */ -UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* outDev, ExtractionOptions options) +UnZip::ErrorCode UnZip::extractFile(const QString &filename, QIODevice *outDev, ExtractionOptions options) { if (!d->device) return NoOpenArchive; @@ -1355,9 +1375,9 @@ UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* outDev, if (!outDev) return InvalidDevice; - QMap::Iterator itr = d->headers->find(filename); + QMap::Iterator itr = d->headers->find(filename); if (itr != d->headers->end()) { - ZipEntryP* entry = itr.value(); + ZipEntryP *entry = itr.value(); Q_ASSERT(entry != 0); return d->extractFile(itr.key(), *entry, outDev, options); } @@ -1369,7 +1389,7 @@ UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* outDev, 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) +UnZip::ErrorCode UnZip::extractFiles(const QStringList &filenames, const QString &dirname, ExtractionOptions options) { if (!d->device) return NoOpenArchive; @@ -1394,7 +1414,7 @@ UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString 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) +UnZip::ErrorCode UnZip::extractFiles(const QStringList &filenames, const QDir &dir, ExtractionOptions options) { if (!d->device) return NoOpenArchive; @@ -1417,7 +1437,7 @@ UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& d /*! Remove/replace this method to add your own password retrieval routine. */ -void UnZip::setPassword(const QString& pwd) +void UnZip::setPassword(const QString &pwd) { d->password = pwd; } diff --git a/cockatrice/src/utility/external/zip/unzip_p.h b/cockatrice/src/utility/external/zip/unzip_p.h old mode 100755 new mode 100644 index fca2b071d..2ea18b93d --- a/cockatrice/src/utility/external/zip/unzip_p.h +++ b/cockatrice/src/utility/external/zip/unzip_p.h @@ -47,7 +47,7 @@ // 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) +#define UNZIP_READ_BUFFER (256 * 1024) OSDAB_BEGIN_NAMESPACE(Zip) @@ -56,72 +56,80 @@ class UnzipPrivate : public QObject Q_OBJECT public: - UnzipPrivate(); + UnzipPrivate(); - // Replace this with whatever else you use to store/retrieve the password. - QString password; + // Replace this with whatever else you use to store/retrieve the password. + QString password; - bool skipAllEncrypted; + bool skipAllEncrypted; - QMap* headers; + QMap *headers; - QIODevice* device; - QFile* file; + QIODevice *device; + QFile *file; - char buffer1[UNZIP_READ_BUFFER]; - char buffer2[UNZIP_READ_BUFFER]; + char buffer1[UNZIP_READ_BUFFER]; + char buffer2[UNZIP_READ_BUFFER]; - unsigned char* uBuffer; - const quint32* crcTable; + unsigned char *uBuffer; + const quint32 *crcTable; - // Central Directory (CD) offset - quint32 cdOffset; - // End of Central Directory (EOCD) offset - quint32 eocdOffset; + // 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; + // 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; + // The number of detected entries that have been skipped because of a non compatible format + quint16 unsupportedEntryCount; - QString comment; + QString comment; - UnZip::ErrorCode openArchive(QIODevice* device); + UnZip::ErrorCode openArchive(QIODevice *device); - UnZip::ErrorCode seekToCentralDirectory(); - UnZip::ErrorCode parseCentralDirectoryRecord(); - UnZip::ErrorCode parseLocalHeaderRecord(const QString& path, const ZipEntryP& entry); + UnZip::ErrorCode seekToCentralDirectory(); + UnZip::ErrorCode parseCentralDirectoryRecord(); + UnZip::ErrorCode parseLocalHeaderRecord(const QString &path, const ZipEntryP &entry); - void closeArchive(); + 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 + 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); + UnZip::ErrorCode testPassword(quint32 *keys, const QString &_file, const ZipEntryP &header); + bool testKeys(const ZipEntryP &header, quint32 *keys); - bool createDirectory(const QString& path); + bool createDirectory(const QString &path); - inline void decryptBytes(quint32* keys, char* buffer, qint64 read); + 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 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; + inline QDateTime convertDateTime(const unsigned char date[2], const unsigned char time[2]) const; private slots: - void deviceDestroyed(QObject*); + 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); + 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(); }; diff --git a/cockatrice/src/utility/external/zip/zip.cpp b/cockatrice/src/utility/external/zip/zip.cpp old mode 100755 new mode 100644 index 5b0177293..2e662360b --- a/cockatrice/src/utility/external/zip/zip.cpp +++ b/cockatrice/src/utility/external/zip/zip.cpp @@ -26,12 +26,11 @@ **********************************************************************/ #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 @@ -39,11 +38,11 @@ #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). */ @@ -113,14 +112,14 @@ #define ZIP_COMPRESSION_THRESHOLD 60 /*! - \class Zip zip.h + \class Zip zip.h - \brief Zip file compression. + \brief Zip file compression. - Some quick usage examples. + Some quick usage examples. - \verbatim - Suppose you have this directory structure: + \verbatim + Suppose you have this directory structure: /home/user/dir1/file1.1 /home/user/dir1/file1.2 @@ -130,50 +129,50 @@ EXAMPLE 1: myZipInstance.addDirectory("/home/user/dir1"); - RESULT: - Beheaves like any common zip software and creates a zip file with this structure: + 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 + dir1/file1.1 + dir1/file1.2 + dir1/dir1.1/ + dir1/dir1.2/file1.2.1 - EXAMPLE 2: + 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: + 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 + 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: + EXAMPLE 3: myZipInstance.addDirectory("/home/user/dir1", Zip::AbsolutePaths); - NOTE: - Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH). + NOTE: + Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH). - RESULT: - Preserves absolute paths and creates a zip file with this structure: + 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"); + EXAMPLE 4: + myZipInstance.setPassword("hellopass"); myZipInstance.addDirectory("/home/user/dir1", "/"); - RESULT: + 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 + /dir1/file1.1 + /dir1/file1.2 + /dir1/dir1.1/ + /dir1/dir1.2/file1.2.1 EXAMPLE 5: myZipInstance.addDirectory("/home/user/dir1", Zip::IgnoreRoot); @@ -199,48 +198,52 @@ data/backup/dir1.1/ data/backup/dir1.2/file1.2.1 - \endverbatim + \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. + \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. + \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 { +namespace +{ -struct ZippedDir { +struct ZippedDir +{ bool init; QString actualRoot; int files; - ZippedDir() : init(false), actualRoot(), files(0) {} + ZippedDir() : init(false), actualRoot(), files(0) + { + } }; -void checkRootPath(QString& path) +void checkRootPath(QString &path) { const bool isUnixRoot = path.length() == 1 && path.at(0) == QLatin1Char('/'); if (!path.isEmpty() && !isUnixRoot) { @@ -248,20 +251,21 @@ void checkRootPath(QString& path) path.truncate(path.length() - 1); int sepCount = 0; - for (int i = path.length()-1; i >= 0; --i) { + for (int i = path.length() - 1; i >= 0; --i) { if (path.at(i) == QLatin1Char('/')) ++sepCount; - else break; + else + break; } if (sepCount > 1) - path.truncate(path.length() - (sepCount-1)); + path.truncate(path.length() - (sepCount - 1)); else if (sepCount == 0) path.append(QLatin1String("/")); } } -} +} // namespace ////////////////////////////////////////////////////////////////////////// @@ -272,53 +276,46 @@ OSDAB_BEGIN_NAMESPACE(Zip) *************************************************************************/ //! \internal -ZipPrivate::ZipPrivate() : - headers(0), - device(0), - file(0), - uBuffer(0), - crcTable(0), - comment(), - password() +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; + // 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(); + closeArchive(); } //! \internal -Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev) +Zip::ErrorCode ZipPrivate::createArchive(QIODevice *dev) { - Q_ASSERT(dev); + Q_ASSERT(dev); - if (device) - closeArchive(); + if (device) + closeArchive(); - device = dev; + device = dev; if (device != file) - connect(device, SIGNAL(destroyed(QObject*)), this, SLOT(deviceDestroyed(QObject*))); + 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; - } - } + 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; + headers = new QMap; + return Zip::Ok; } //! \internal -void ZipPrivate::deviceDestroyed(QObject*) +void ZipPrivate::deviceDestroyed(QObject *) { qDebug("Unexpected device destruction detected."); do_closeArchive(); @@ -327,7 +324,7 @@ void ZipPrivate::deviceDestroyed(QObject*) /*! 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 +bool ZipPrivate::containsEntry(const QFileInfo &info) const { if (!headers || headers->isEmpty()) return false; @@ -335,10 +332,10 @@ bool ZipPrivate::containsEntry(const QFileInfo& info) const const qint64 sz = info.size(); const QString path = info.absoluteFilePath().toLower(); - QMap::ConstIterator b = headers->constBegin(); - const QMap::ConstIterator e = headers->constEnd(); + QMap::ConstIterator b = headers->constBegin(); + const QMap::ConstIterator e = headers->constEnd(); while (b != e) { - const ZipEntryP* e = b.value(); + const ZipEntryP *e = b.value(); if (e->fileSize == sz && e->absolutePath == path) return true; ++b; @@ -348,9 +345,12 @@ bool ZipPrivate::containsEntry(const QFileInfo& info) const } //! \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) +Zip::ErrorCode ZipPrivate::addDirectory(const QString &path, + const QString &root, + Zip::CompressionOptions options, + Zip::CompressionLevel level, + int hierarchyLevel, + int *addedFiles) { if (addedFiles) ++(*addedFiles); @@ -396,13 +396,8 @@ Zip::ErrorCode ZipPrivate::addDirectory(const QString& path, const QString& root 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; + 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; @@ -411,7 +406,8 @@ Zip::ErrorCode ZipPrivate::addDirectory(const QString& path, const QString& root Zip::CompressionOptions recursionOptions; if (path_ignore) recursionOptions |= Zip::IgnorePaths; - else recursionOptions |= Zip::RelativePaths; + else + recursionOptions |= Zip::RelativePaths; for (int i = 0; i < list.size(); ++i) { QFileInfo info = list.at(i); @@ -420,8 +416,7 @@ Zip::ErrorCode ZipPrivate::addDirectory(const QString& path, const QString& root continue; if (info.isDir()) { // Recursion - ec = addDirectory(absPath, actualRoot, recursionOptions, - level, hierarchyLevel + 1, addedFiles); + ec = addDirectory(absPath, actualRoot, recursionOptions, level, hierarchyLevel + 1, addedFiles); } else { ec = createEntry(info, actualRoot, level); if (ec == Zip::Ok) { @@ -432,7 +427,7 @@ Zip::ErrorCode ZipPrivate::addDirectory(const QString& path, const QString& root } if (ec != Zip::Ok && !skipBad) { - break; + break; } } @@ -445,9 +440,11 @@ Zip::ErrorCode ZipPrivate::addDirectory(const QString& path, const QString& root } //! \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) +Zip::ErrorCode ZipPrivate::addFiles(const QStringList &files, + const QString &root, + Zip::CompressionOptions options, + Zip::CompressionLevel level, + int *addedFiles) { if (addedFiles) *addedFiles = 0; @@ -493,10 +490,10 @@ Zip::ErrorCode ZipPrivate::addFiles(const QStringList& files, const QString& roo QHash dirMap; for (int i = 0; i < paths.size(); ++i) { - const QFileInfo& info = paths.at(i); + const QFileInfo &info = paths.at(i); const QString path = QFileInfo(QDir::cleanPath(info.absolutePath())).absolutePath(); - ZippedDir& zd = dirMap[path]; + ZippedDir &zd = dirMap[path]; if (!zd.init) { zd.init = true; zd.actualRoot = actualRoot; @@ -518,8 +515,7 @@ Zip::ErrorCode ZipPrivate::addFiles(const QStringList& files, const QString& roo if (info.isDir()) { // Recursion - ec = addDirectory(info.absoluteFilePath(), actualRoot, options, - level, 1, addedFiles); + ec = addDirectory(info.absoluteFilePath(), actualRoot, options, level, 1, addedFiles); } else { ec = createEntry(info, actualRoot, level); if (ec == Zip::Ok) { @@ -530,7 +526,7 @@ Zip::ErrorCode ZipPrivate::addFiles(const QStringList& files, const QString& roo } if (ec != Zip::Ok && !skipBad) { - break; + break; } } @@ -539,7 +535,7 @@ Zip::ErrorCode ZipPrivate::addFiles(const QStringList& files, const QString& roo QHash::ConstIterator b = dirMap.constBegin(); const QHash::ConstIterator e = dirMap.constEnd(); while (b != e) { - const ZippedDir& zd = b.value(); + const ZippedDir &zd = b.value(); if (zd.files <= 0) { ec = createEntry(b.key(), zd.actualRoot, level); } @@ -551,8 +547,11 @@ Zip::ErrorCode ZipPrivate::addFiles(const QStringList& files, const QString& roo } //! \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) +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); @@ -561,17 +560,16 @@ Zip::ErrorCode ZipPrivate::deflateFile(const QFileInfo& fileInfo, return Zip::OpenFailed; } - const Zip::ErrorCode ec = (level == Zip::Store) - ? storeFile(path, file, crc, written, keys) - : compressFile(path, file, crc, written, level, keys); + 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) +Zip::ErrorCode +ZipPrivate::storeFile(const QString &path, QIODevice &file, quint32 &crc, qint64 &totalWritten, quint32 **keys) { Q_UNUSED(path); @@ -583,7 +581,7 @@ Zip::ErrorCode ZipPrivate::storeFile(const QString& path, QIODevice& file, totalWritten = 0; crc = crc32(0L, Z_NULL, 0); - while ( (read = file.read(buffer1, ZIP_READ_BUFFER)) > 0 ) { + while ((read = file.read(buffer1, ZIP_READ_BUFFER)) > 0) { crc = crc32(crc, uBuffer, read); if (encrypt) encryptBytes(*keys, buffer1, read); @@ -598,7 +596,7 @@ Zip::ErrorCode ZipPrivate::storeFile(const QString& path, QIODevice& file, } //! \internal -int ZipPrivate::compressionStrategy(const QString& path, QIODevice& file) const +int ZipPrivate::compressionStrategy(const QString &path, QIODevice &file) const { Q_UNUSED(file); @@ -610,8 +608,12 @@ int ZipPrivate::compressionStrategy(const QString& path, QIODevice& file) const } //! \internal -Zip::ErrorCode ZipPrivate::compressFile(const QString& path, QIODevice& file, - quint32& crc, qint64& totalWritten, const Zip::CompressionLevel& level, quint32** keys) +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; @@ -635,16 +637,12 @@ Zip::ErrorCode ZipPrivate::compressFile(const QString& path, QIODevice& file, 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 ) { + 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; } @@ -665,7 +663,7 @@ Zip::ErrorCode ZipPrivate::compressFile(const QString& path, QIODevice& file, crc = crc32(crc, uBuffer, read); - zstr.next_in = (Bytef*) buffer1; + zstr.next_in = (Bytef *)buffer1; zstr.avail_in = (uInt)read; // Tell zlib if this is the last chunk we want to encode @@ -675,7 +673,7 @@ Zip::ErrorCode ZipPrivate::compressFile(const QString& path, QIODevice& file, // 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.next_out = (Bytef *)buffer2; zstr.avail_out = ZIP_READ_BUFFER; zret = deflate(&zstr, flush); @@ -712,372 +710,353 @@ Zip::ErrorCode ZipPrivate::compressFile(const QString& path, QIODevice& file, } //! \internal Writes a new entry in the zip file. -Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, - Zip::CompressionLevel level) +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(); + const QString entryName = dirOnly ? root : root + file.fileName(); // Directory entry if (dirOnly || file.size() < ZIP_COMPRESSION_THRESHOLD) { - level = Zip::Store; + level = Zip::Store; } else { switch (level) { - case Zip::AutoCPU: - level = Zip::Deflate5; + case Zip::AutoCPU: + level = Zip::Deflate5; #ifndef OSDAB_ZIP_NO_DEBUG - qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); + qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); #endif - break; - case Zip::AutoMIME: - level = detectCompressionByMime(file.completeSuffix().toLower()); + 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); + qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); #endif - break; - case Zip::AutoFull: - level = detectCompressionByMime(file.completeSuffix().toLower()); + 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); + qDebug("Compression level for '%s': %d", entryName.toLatin1().constData(), (int)level); #endif - break; - default: ; + break; + default:; } } - - - // create header and store it to write a central directory later + // 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; + // 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(); + 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; + 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->szUncomp = dirOnly ? 0 : file.size(); h->compMethod = (level == Zip::Store) ? 0 : 0x0008; - // **** Write local file header **** + // **** Write local file header **** - // signature - buffer1[0] = 'P'; buffer1[1] = 'K'; - buffer1[2] = 0x3; buffer1[3] = 0x4; + // 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; + // 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]; + // 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; + // 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 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]; + // 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 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; + // 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; + h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0; - // uncompressed size [22,23,24,25] - setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE); + // uncompressed size [22,23,24,25] + setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE); - // filename length - QByteArray entryNameBytes = entryName.toLatin1(); - int sz = entryNameBytes.size(); + // 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; + 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; + // 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; + // 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) { + if (device->write(buffer1, ZIP_LOCAL_HEADER_SIZE) != ZIP_LOCAL_HEADER_SIZE) { return Zip::WriteFailed; - } + } - // Write out filename - if (device->write(entryNameBytes) != sz) { + // Write out filename + if (device->write(entryNameBytes) != sz) { return Zip::WriteFailed; - } + } - // Encryption keys - quint32 keys[3] = { 0, 0, 0 }; + // Encryption keys + quint32 keys[3] = {0, 0, 0}; - if (encrypt) { - // **** encryption header **** + 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; + // 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); - } + 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; - } + // 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; + // 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; + 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) { + // 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; + 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(); + // Store end of entry offset + quint32 current = device->pos(); - // Update crc and compressed size in local header - if (!device->seek(crcOffset)) { + // Update crc and compressed size in local header + if (!device->seek(crcOffset)) { return Zip::SeekFailed; - } + } - h->crc = dirOnly ? 0 : crc; - h->szComp += written; + h->crc = dirOnly ? 0 : crc; + h->szComp += written; - setULong(h->crc, buffer1, 0); - setULong(h->szComp, buffer1, 4); - if ( device->write(buffer1, 8) != 8) { + setULong(h->crc, buffer1, 0); + setULong(h->szComp, buffer1, 4); + if (device->write(buffer1, 8) != 8) { return Zip::WriteFailed; - } + } - // Seek to end of entry + // Seek to end of entry if (!device->seek(current)) { - return Zip::SeekFailed; - } + return Zip::SeekFailed; + } - if ((h->gpFlag[0] & 8) == 8) { - // Write data descriptor + 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; + // 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); + // CRC + setULong(h->crc, buffer1, ZIP_DD_OFF_CRC32); - // Compressed size - setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE); + // Compressed size + setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE); - // Uncompressed size - setULong(h->szUncomp, buffer1, ZIP_DD_OFF_USIZE); + // 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; - } - } + return Zip::WriteFailed; + } + } headers->insert(entryName, h.take()); - return Zip::Ok; + return Zip::Ok; } //! \internal int ZipPrivate::decryptByte(quint32 key2) const { quint16 temp = ((quint16)(key2) & 0xffff) | 2; - return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); + 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) +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); + 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 +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; + // 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(); + 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]); + 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 +quint32 ZipPrivate::updateChecksum(const quint32 &crc, const quint32 &val) const { - return quint32(crcTable[quint32(crc^val) & 0xff] ^ crc_t(crc >> 8)); + return quint32(crcTable[quint32(crc ^ val) & 0xff] ^ crc_t(crc >> 8)); } //! \internal Updates encryption keys. -void ZipPrivate::updateKeys(quint32* keys, int c) const +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[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) +void ZipPrivate::encryptBytes(quint32 *keys, char *buffer, qint64 read) { - char t; + char t; for (qint64 i = 0; i < read; ++i) { - t = buffer[i]; - buffer[i] ^= decryptByte(keys[2]); - updateKeys(keys, t); - } + t = buffer[i]; + buffer[i] ^= decryptByte(keys[2]); + updateKeys(keys, t); + } } -namespace { -struct KeywordHelper { +namespace +{ +struct KeywordHelper +{ const QString needle; - inline KeywordHelper(const QString& keyword) : needle(keyword) {} + inline KeywordHelper(const QString &keyword) : needle(keyword) + { + } }; -bool operator<(const KeywordHelper& helper, const char* keyword) { +bool operator<(const KeywordHelper &helper, const char *keyword) +{ return helper.needle.compare(QLatin1String(keyword)) < 0; } -bool operator<(const char* keyword, const KeywordHelper& helper) { +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)); +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; } -} +} // namespace //! \internal Detects the best compression level for a given file extension. -Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext) +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] = { + 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", + 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", + 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; + const char *const *map = ext1; if (hasExtension(ext, map, MAX_EXT1)) return Zip::Store; @@ -1093,7 +1072,7 @@ Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext) } /*! - Closes the current archive and writes out pending data. + Closes the current archive and writes out pending data. */ Zip::ErrorCode ZipPrivate::closeArchive() { @@ -1111,21 +1090,20 @@ Zip::ErrorCode ZipPrivate::closeArchive() //! \internal Zip::ErrorCode ZipPrivate::do_closeArchive() { - // Close current archive by writing out central directory - // and free up resources + // Close current archive by writing out central directory + // and free up resources - if (!device && !headers) - return Zip::Ok; + if (!device && !headers) + return Zip::Ok; - quint32 szCentralDir = 0; + quint32 szCentralDir = 0; quint32 offCentralDir = device->pos(); - Zip::ErrorCode c = Zip::Ok; + Zip::ErrorCode c = Zip::Ok; if (headers && device) { - for (QMap::ConstIterator itr = headers->constBegin(); - itr != headers->constEnd(); ++itr) { + for (QMap::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr) { const QString fileName = itr.key(); - const ZipEntryP* h = itr.value(); + const ZipEntryP *h = itr.value(); c = writeEntry(fileName, h, szCentralDir); } } @@ -1142,91 +1120,89 @@ Zip::ErrorCode ZipPrivate::do_closeArchive() } } - return c; + return c; } //! \internal -Zip::ErrorCode ZipPrivate::writeEntry(const QString& fileName, const ZipEntryP* h, quint32& szCentralDir) +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; + 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 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; + // 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]; + // 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; + // 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 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]; + // 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); + // 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); + // 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); + // 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; + // 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; + // 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; + // 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; + // 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; + // 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; + // 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); + // 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; - } + 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; - } + // Write out filename + if ((unsigned int)device->write(fileNameBytes) != sz) { + return Zip::WriteFailed; + } - szCentralDir += (ZIP_CD_SIZE + sz); + szCentralDir += (ZIP_CD_SIZE + sz); return Zip::Ok; } @@ -1237,54 +1213,54 @@ Zip::ErrorCode ZipPrivate::writeCentralDir(quint32 offCentralDir, quint32 szCent Q_ASSERT(device && headers); unsigned int sz; - + // signature - buffer1[0] = 'P'; - buffer1[1] = 'K'; - buffer1[2] = 0x05; - buffer1[3] = 0x06; + 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 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 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(); + // 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; + 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]; + // 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); + // 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); + // central dir offset [16->19] + setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF); - // ZIP file comment length - QByteArray commentBytes = comment.toLatin1(); - quint16 commentLength = commentBytes.size(); + // 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 (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 (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; - } - } + if (commentLength != 0) { + if ((unsigned int)device->write(commentBytes) != commentLength) { + return Zip::WriteFailed; + } + } return Zip::Ok; } @@ -1292,15 +1268,15 @@ Zip::ErrorCode ZipPrivate::writeCentralDir(quint32 offCentralDir, quint32 szCent //! \internal void ZipPrivate::reset() { - comment.clear(); + comment.clear(); - if (headers) { - qDeleteAll(*headers); - delete headers; - headers = 0; - } + if (headers) { + qDeleteAll(*headers); + delete headers; + headers = 0; + } - device = 0; + device = 0; if (file) delete file; @@ -1308,77 +1284,76 @@ void ZipPrivate::reset() } //! \internal Returns the path of the parent directory -QString ZipPrivate::extractRoot(const QString& p, Zip::CompressionOptions o) +QString ZipPrivate::extractRoot(const QString &p, Zip::CompressionOptions o) { Q_UNUSED(o); - QDir d(QDir::cleanPath(p)); - if (!d.exists()) - return QString(); + QDir d(QDir::cleanPath(p)); + if (!d.exists()) + return QString(); - if (!d.cdUp()) - return QString(); + if (!d.cdUp()) + return QString(); - return d.absolutePath(); + return d.absolutePath(); } - /************************************************************************ Public interface *************************************************************************/ /*! - Creates a new Zip file compressor. + Creates a new Zip file compressor. */ Zip::Zip() : d(new ZipPrivate) { } /*! - Closes any open archive and releases used resources. + Closes any open archive and releases used resources. */ Zip::~Zip() { - closeArchive(); - delete d; + closeArchive(); + delete d; } /*! - Returns true if there is an open archive. + Returns true if there is an open archive. */ bool Zip::isOpen() const { - return d->device; + 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! + 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) +void Zip::setPassword(const QString &pwd) { - d->password = pwd; + d->password = pwd; } //! Convenience method, clears the current password. void Zip::clearPassword() { - d->password.clear(); + d->password.clear(); } //! Returns the currently used password. QString Zip::password() const { - return d->password; + 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. + 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) +Zip::ErrorCode Zip::createArchive(const QString &filename, bool overwrite) { closeArchive(); Q_ASSERT(!d->device && !d->file); @@ -1386,119 +1361,123 @@ Zip::ErrorCode Zip::createArchive(const QString& filename, bool overwrite) if (filename.isEmpty()) return Zip::FileNotFound; - d->file = new QFile(filename); + d->file = new QFile(filename); - if (d->file->exists() && !overwrite) { + if (d->file->exists() && !overwrite) { delete d->file; d->file = 0; - return Zip::FileExists; - } + return Zip::FileExists; + } - if (!d->file->open(QIODevice::WriteOnly)) { + if (!d->file->open(QIODevice::WriteOnly)) { delete d->file; d->file = 0; - return Zip::OpenFailed; - } + return Zip::OpenFailed; + } - const Zip::ErrorCode ec = createArchive(d->file); - if (ec != Zip::Ok) { - closeArchive(); - } + const Zip::ErrorCode ec = createArchive(d->file); + if (ec != Zip::Ok) { + closeArchive(); + } - return ec; + 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! + 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) +Zip::ErrorCode Zip::createArchive(QIODevice *device) { - if (!device) { - qDebug() << "Invalid device."; - return Zip::OpenFailed; - } + if (!device) { + qDebug() << "Invalid device."; + return Zip::OpenFailed; + } - return d->createArchive(device); + return d->createArchive(device); } /*! - Returns the current archive comment. + Returns the current archive comment. */ QString Zip::archiveComment() const { - return d->comment; + return d->comment; } /*! - Sets the comment for this archive. Note: createArchive() should have been - called before. + Sets the comment for this archive. Note: createArchive() should have been + called before. */ -void Zip::setArchiveComment(const QString& comment) +void Zip::setArchiveComment(const QString &comment) { - d->comment = 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. + 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) +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. + 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) +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) + 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) +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. + 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) +Zip::ErrorCode Zip::addDirectory(const QString &path, const QString &root, CompressionLevel level) { - return addDirectory(path, root, Zip::RelativePaths, 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. + 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 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!). + 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) +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); @@ -1508,7 +1487,7 @@ Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, 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) +Zip::ErrorCode Zip::addFile(const QString &path, CompressionLevel level) { return addFile(path, QString(), Zip::RelativePaths, level); } @@ -1517,8 +1496,7 @@ Zip::ErrorCode Zip::addFile(const QString& path, CompressionLevel 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) +Zip::ErrorCode Zip::addFile(const QString &path, const QString &root, CompressionLevel level) { return addFile(path, root, Zip::RelativePaths, level); } @@ -1535,8 +1513,8 @@ Zip::ErrorCode Zip::addFile(const QString& path, const QString& root, 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) +Zip::ErrorCode +Zip::addFile(const QString &path, const QString &root, CompressionOptions options, CompressionLevel level) { if (path.isEmpty()) return Zip::Ok; @@ -1544,20 +1522,20 @@ Zip::ErrorCode Zip::addFile(const QString& path, const QString& root, } /*! - 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. + 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) +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. + 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) +Zip::ErrorCode Zip::addFiles(const QStringList &paths, const QString &root, CompressionLevel level) { return addFiles(paths, root, Zip::RelativePaths, level); } @@ -1578,42 +1556,62 @@ Zip::ErrorCode Zip::addFiles(const QStringList& paths, const QString& root, 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) +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. + Closes the archive and writes any pending data. */ Zip::ErrorCode Zip::closeArchive() { - Zip::ErrorCode ec = d->closeArchive(); - d->reset(); - return ec; + Zip::ErrorCode ec = d->closeArchive(); + d->reset(); + return ec; } /*! - Returns a locale translated error string for a given error code. + 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: ; - } + 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."); + return QCoreApplication::translate("Zip", "Unknown error."); } OSDAB_END_NAMESPACE diff --git a/cockatrice/src/utility/external/zip/zip.h b/cockatrice/src/utility/external/zip/zip.h old mode 100755 new mode 100644 index 79f32ada5..965dec523 --- a/cockatrice/src/utility/external/zip/zip.h +++ b/cockatrice/src/utility/external/zip/zip.h @@ -32,7 +32,6 @@ #include #include - #include class QIODevice; @@ -48,36 +47,45 @@ class ZipPrivate; class OSDAB_ZIP_EXPORT Zip { public: - enum ErrorCode - { - Ok, - ZlibInit, - ZlibError, - FileExists, - OpenFailed, - NoOpenArchive, - FileNotFound, - ReadFailed, - WriteFailed, + 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 CompressionLevel + { + Store, + Deflate1 = 1, + Deflate2, + Deflate3, + Deflate4, + Deflate5, + Deflate6, + Deflate7, + Deflate8, + Deflate9, + AutoCPU, + AutoMIME, + AutoFull + }; - enum CompressionOption - { + enum CompressionOption + { /*! Does not preserve absolute paths in the zip file when adding a file or directory (default) */ - RelativePaths = 0x0001, + RelativePaths = 0x0001, /*! Preserve absolute paths */ - AbsolutePaths = 0x0002, + AbsolutePaths = 0x0002, /*! Do not store paths. All the files are put in the (evtl. user defined) root of the zip file */ IgnorePaths = 0x0004, @@ -95,60 +103,54 @@ public: this flag. */ CheckForDuplicates = 0x0040 - }; - Q_DECLARE_FLAGS(CompressionOptions, CompressionOption) + }; + Q_DECLARE_FLAGS(CompressionOptions, CompressionOption) - Zip(); - virtual ~Zip(); + Zip(); + virtual ~Zip(); - bool isOpen() const; + bool isOpen() const; - void setPassword(const QString& pwd); - void clearPassword(); - QString password() const; + void setPassword(const QString &pwd); + void clearPassword(); + QString password() const; - ErrorCode createArchive(const QString& file, bool overwrite = true); - ErrorCode createArchive(QIODevice* device); + ErrorCode createArchive(const QString &file, bool overwrite = true); + ErrorCode createArchive(QIODevice *device); - QString archiveComment() const; - void setArchiveComment(const QString& comment); + 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 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 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 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 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(); + ErrorCode closeArchive(); - QString formatError(ErrorCode c) const; + QString formatError(ErrorCode c) const; private: - ZipPrivate* d; + ZipPrivate *d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Zip::CompressionOptions) diff --git a/cockatrice/src/utility/external/zip/zip_p.h b/cockatrice/src/utility/external/zip/zip_p.h old mode 100755 new mode 100644 index b8ec6564b..94f59f0d8 --- a/cockatrice/src/utility/external/zip/zip_p.h +++ b/cockatrice/src/utility/external/zip/zip_p.h @@ -45,14 +45,13 @@ #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 ;) + 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) +#define ZIP_READ_BUFFER (256 * 1024) OSDAB_BEGIN_NAMESPACE(Zip) @@ -64,67 +63,77 @@ public: // uLongf from zconf.h typedef uLongf crc_t; - ZipPrivate(); - virtual ~ZipPrivate(); + ZipPrivate(); + virtual ~ZipPrivate(); - QMap* headers; + QMap *headers; - QIODevice* device; - QFile* file; + QIODevice *device; + QFile *file; - char buffer1[ZIP_READ_BUFFER]; - char buffer2[ZIP_READ_BUFFER]; + char buffer1[ZIP_READ_BUFFER]; + char buffer2[ZIP_READ_BUFFER]; - unsigned char* uBuffer; + unsigned char *uBuffer; - const crc_t* crcTable; + const crc_t *crcTable; - QString comment; - QString password; + QString comment; + QString password; - Zip::ErrorCode createArchive(QIODevice* device); - Zip::ErrorCode closeArchive(); - void reset(); + Zip::ErrorCode createArchive(QIODevice *device); + Zip::ErrorCode closeArchive(); + void reset(); - bool zLibInit(); + bool zLibInit(); - bool containsEntry(const QFileInfo& info) const; + 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 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); + 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 quint32 updateChecksum(const quint32 &crc, const quint32 &val) const; - inline void encryptBytes(quint32* keys, char* buffer, qint64 read); + 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 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); + inline QString extractRoot(const QString &p, Zip::CompressionOptions o); private slots: - void deviceDestroyed(QObject*); + 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); + 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 writeEntry(const QString &fileName, const ZipEntryP *h, quint32 &szCentralDir); Zip::ErrorCode writeCentralDir(quint32 offCentralDir, quint32 szCentralDir); }; diff --git a/cockatrice/src/utility/external/zip/zipentry_p.h b/cockatrice/src/utility/external/zip/zipentry_p.h old mode 100755 new mode 100644 index f0675b6b6..31339e049 --- a/cockatrice/src/utility/external/zip/zipentry_p.h +++ b/cockatrice/src/utility/external/zip/zipentry_p.h @@ -47,43 +47,39 @@ 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) + 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 + 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 + QString absolutePath; // Internal use + qint64 fileSize; // Internal use - mutable bool lhEntryChecked; // Is true if the local header record for this entry has been parsed + 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; } + inline bool isEncrypted() const + { + return gpFlag[0] & 0x01; + } + inline bool hasDataDescriptor() const + { + return gpFlag[0] & 0x08; + } }; OSDAB_END_NAMESPACE diff --git a/cockatrice/src/utility/external/zip/zipglobal.h b/cockatrice/src/utility/external/zip/zipglobal.h old mode 100755 new mode 100644 index e7ff33105..de2269c6a --- a/cockatrice/src/utility/external/zip/zipglobal.h +++ b/cockatrice/src/utility/external/zip/zipglobal.h @@ -39,23 +39,29 @@ */ #ifndef OSDAB_ZIP_LIB -# define OSDAB_ZIP_EXPORT +#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 +#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 { +#define OSDAB_BEGIN_NAMESPACE(ModuleName) \ + namespace Osdab \ + { \ + namespace ModuleName \ + { #else #define OSDAB_BEGIN_NAMESPACE(ModuleName) #endif #ifdef OSDAB_NAMESPACE -#define OSDAB_END_NAMESPACE } } +#define OSDAB_END_NAMESPACE \ + } \ + } #else #define OSDAB_END_NAMESPACE #endif @@ -69,8 +75,8 @@ 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_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 From 3613b6c696262737c0d575f14b62daa5566d4d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Thu, 20 Nov 2025 14:47:36 +0100 Subject: [PATCH 6/7] Update CMakeLists.txt for zip and lzma support. Took 9 minutes --- cockatrice/CMakeLists.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index d1b2deef9..b7a68ac4d 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -306,6 +306,30 @@ if(APPLE) set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns) endif(APPLE) +# Libz is required to support zipped files +find_package(ZLIB) +if(ZLIB_FOUND) + include_directories(${ZLIB_INCLUDE_DIRS}) + add_definitions("-DHAS_ZLIB") + + set(cockatrice_SOURCES ${cockatrice_SOURCES} src/utility/external/zip/unzip.cpp + src/utility/external/zip/zipglobal.cpp + ) +else() + message(STATUS "Cockatrice: zlib not found; ZIP support disabled") +endif() + +# LibLZMA is required to support xz files +find_package(LibLZMA) +if(LIBLZMA_FOUND) + include_directories(${LIBLZMA_INCLUDE_DIRS}) + add_definitions("-DHAS_LZMA") + + set(cockatrice_SOURCES ${cockatrice_SOURCES} src/utility/external/lzma/decompress.cpp) +else() + message(STATUS "Cockatrice: LibLZMA not found; xz support disabled") +endif() + if(Qt6_FOUND) qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES}) elseif(Qt5_FOUND) From acf42b4e55134042e36cb03ac9c811ba0cf617cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Thu, 20 Nov 2025 14:52:32 +0100 Subject: [PATCH 7/7] Link zip and lzma libs. Took 5 minutes --- cockatrice/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index b7a68ac4d..b98640011 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -412,6 +412,14 @@ else() ) endif() +if(ZLIB_FOUND) + target_link_libraries(cockatrice PRIVATE ${ZLIB_LIBRARIES}) +endif() + +if(LIBLZMA_FOUND) + target_link_libraries(cockatrice PRIVATE ${LIBLZMA_LIBRARIES}) +endif() + if(UNIX) if(APPLE) set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}")