diff --git a/autotests/kerfuffle/data/bug_#394542.zip b/autotests/kerfuffle/data/bug_#394542.zip new file mode 100644 index 00000000..a0dc7e35 Binary files /dev/null and b/autotests/kerfuffle/data/bug_#394542.zip differ diff --git a/autotests/kerfuffle/extracttest.cpp b/autotests/kerfuffle/extracttest.cpp index 1e021dec..981ee5af 100644 --- a/autotests/kerfuffle/extracttest.cpp +++ b/autotests/kerfuffle/extracttest.cpp @@ -1,520 +1,530 @@ /* * Copyright (c) 2010-2011 Raphael Kubo da Costa * Copyright (c) 2016 Elvis Angelaccio * * 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 THE AUTHOR ``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 THE AUTHOR 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. */ #include "archive_kerfuffle.h" #include "pluginmanager.h" #include "jobs.h" #include "testhelper.h" #include #include #include #include #include using namespace Kerfuffle; class ExtractTest : public QObject { Q_OBJECT private Q_SLOTS: void testExtraction_data(); void testExtraction(); void testPreservePermissions_data(); void testPreservePermissions(); private: PluginManager m_pluginManager; }; QTEST_GUILESS_MAIN(ExtractTest) void ExtractTest::testExtraction_data() { QTest::addColumn("archivePath"); QTest::addColumn>("entriesToExtract"); QTest::addColumn("extractionOptions"); QTest::addColumn("expectedExtractedEntriesCount"); ExtractionOptions optionsPreservePaths; ExtractionOptions optionsNoPaths; optionsNoPaths.setPreservePaths(false); ExtractionOptions dragAndDropOptions; dragAndDropOptions.setDragAndDropEnabled(true); QString archivePath = QFINDTESTDATA("data/simplearchive.tar.gz"); QTest::newRow("extract the whole simplearchive.tar.gz") << archivePath << QVector() << optionsPreservePaths << 4; archivePath = QFINDTESTDATA("data/simplearchive.tar.gz"); QTest::newRow("extract selected entries from a tar.gz, without paths") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")), new Archive::Entry(this, QStringLiteral("c.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.gz"); QTest::newRow("extract selected entries from a tar.gz, preserve paths") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")), new Archive::Entry(this, QStringLiteral("c.txt"), QString()) } << optionsPreservePaths << 3; archivePath = QFINDTESTDATA("data/simplearchive.tar.gz"); QTest::newRow("extract selected entries from a tar.gz, drag-and-drop") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("c.txt"), QString()), new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir/")) } << dragAndDropOptions << 2; archivePath = QFINDTESTDATA("data/one_toplevel_folder.zip"); QTest::newRow("extract the whole one_toplevel_folder.zip") << archivePath << QVector() << optionsPreservePaths << 9; archivePath = QFINDTESTDATA("data/one_toplevel_folder.zip"); QTest::newRow("extract selected entries from a zip, without paths") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A")), new Archive::Entry(this, QStringLiteral("A/B/test1.txt"), QStringLiteral("A/B")) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/one_toplevel_folder.zip"); QTest::newRow("extract selected entries from a zip, preserve paths") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A")), new Archive::Entry(this, QStringLiteral("A/B/test1.txt"), QStringLiteral("A/B")) } << optionsPreservePaths << 4; archivePath = QFINDTESTDATA("data/one_toplevel_folder.zip"); QTest::newRow("extract selected entries from a zip, drag-and-drop") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A/")), new Archive::Entry(this, QStringLiteral("A/B/C/"), QStringLiteral("A/B/")), new Archive::Entry(this, QStringLiteral("A/B/C/test1.txt"), QStringLiteral("A/B/")), new Archive::Entry(this, QStringLiteral("A/B/C/test2.txt"), QStringLiteral("A/B/")) } << dragAndDropOptions << 4; archivePath = QFINDTESTDATA("data/one_toplevel_folder.7z"); QTest::newRow("extract the whole one_toplevel_folder.7z") << archivePath << QVector() << optionsPreservePaths << 9; archivePath = QFINDTESTDATA("data/one_toplevel_folder.7z"); QTest::newRow("extract selected entries from a 7z, without paths") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A")), new Archive::Entry(this, QStringLiteral("A/B/test1.txt"), QStringLiteral("A/B")) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/one_toplevel_folder.7z"); QTest::newRow("extract selected entries from a 7z, preserve paths") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A")), new Archive::Entry(this, QStringLiteral("A/B/test1.txt"), QStringLiteral("A/B")) } << optionsPreservePaths << 4; archivePath = QFINDTESTDATA("data/one_toplevel_folder.7z"); QTest::newRow("extract selected entries from a 7z, drag-and-drop") << archivePath << QVector {new Archive::Entry(this, QStringLiteral("A/B/test2.txt"), QStringLiteral("A/B/"))} << dragAndDropOptions << 1; archivePath = QFINDTESTDATA("data/empty_folders.zip"); QTest::newRow("zip with empty folders") << archivePath << QVector() << optionsPreservePaths << 5; archivePath = QFINDTESTDATA("data/empty_folders.tar.gz"); QTest::newRow("tar with empty folders") << archivePath << QVector() << optionsPreservePaths << 5; archivePath = QFINDTESTDATA("data/simplearchive.tar.bz2"); QTest::newRow("extract selected entries from a bzip2-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.bz2"); QTest::newRow("extract all entries from a bzip2-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; archivePath = QFINDTESTDATA("data/simplearchive.tar.xz"); QTest::newRow("extract selected entries from a xz-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.xz"); QTest::newRow("extract all entries from a xz-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; archivePath = QFINDTESTDATA("data/simplearchive.tar.lzma"); QTest::newRow("extract selected entries from a lzma-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.lzma"); QTest::newRow("extract all entries from a lzma-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; archivePath = QFINDTESTDATA("data/simplearchive.tar.Z"); QTest::newRow("extract selected entries from a compress (.Z)-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.Z"); QTest::newRow("extract all entries from a compress (.Z)-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; archivePath = QFINDTESTDATA("data/simplearchive.tar.lz"); QTest::newRow("extract selected entries from a lzipped tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.lz"); QTest::newRow("extract all entries from a lzipped tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; // Only run tests if tar.lzo format is available if (PluginManager().supportedMimeTypes().contains(QStringLiteral("application/x-tzo"))) { archivePath = QFINDTESTDATA("data/simplearchive.tar.lzo"); QTest::newRow("extract selected entries from a lzop-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.lzo"); QTest::newRow("extract all entries from a lzop-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; } // Only run test for lrzipped tar if lrzip executable is found in path. if (!QStandardPaths::findExecutable(QStringLiteral("lrzip")).isEmpty()) { archivePath = QFINDTESTDATA("data/simplearchive.tar.lrz"); QTest::newRow("extract selected entries from a lrzip-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.lrz"); QTest::newRow("extract all entries from a lrzip-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; } else { qDebug() << "lrzip executable not found in path. Skipping lrzip test."; } // Only run test for lz4-compressed tar if lz4 executable is found in path. if (!QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) { archivePath = QFINDTESTDATA("data/simplearchive.tar.lz4"); QTest::newRow("extract selected entries from a lz4-compressed tarball without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("file3.txt"), QString()), new Archive::Entry(this, QStringLiteral("dir2/file22.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.tar.lz4"); QTest::newRow("extract all entries from a lz4-compressed tarball with path") << archivePath << QVector() << optionsPreservePaths << 7; } else { qDebug() << "lz4 executable not found in path. Skipping lz4 test."; } archivePath = QFINDTESTDATA("data/simplearchive.xar"); QTest::newRow("extract selected entries from a xar archive without path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("dir1/file11.txt"), QString()), new Archive::Entry(this, QStringLiteral("file4.txt"), QString()) } << optionsNoPaths << 2; archivePath = QFINDTESTDATA("data/simplearchive.xar"); QTest::newRow("extract all entries from a xar archive with path") << archivePath << QVector() << optionsPreservePaths << 6; archivePath = QFINDTESTDATA("data/hello-1.0-x86_64.AppImage"); QTest::newRow("extract all entries from an AppImage with path") << archivePath << QVector() << optionsPreservePaths << 7; archivePath = QFINDTESTDATA("data/hello-1.0-x86_64.AppImage"); QTest::newRow("extract selected entries from an AppImage with path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("usr/bin/hello"), QString()) } << optionsPreservePaths << 3; archivePath = QFINDTESTDATA("data/archive-multivolume.7z.001"); QTest::newRow("extract all entries from a multivolume 7z archive with path") << archivePath << QVector() << optionsPreservePaths << 3; archivePath = QFINDTESTDATA("data/archive-multivolume.part1.rar"); QTest::newRow("extract all entries from a multivolume rar archive with path") << archivePath << QVector() << optionsPreservePaths << 3; archivePath = QFINDTESTDATA("data/firmware-pine64-20160329-6.1.aarch64.rpm"); QTest::newRow("extract selected entries from rpm with path") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("boot/sunxi-spl.bin"), QString()) } << optionsPreservePaths << 2; archivePath = QFINDTESTDATA("data/firmware-pine64-20160329-6.1.aarch64.rpm"); QTest::newRow("#369535: broken drag-and-drop from rpm") << archivePath << QVector { new Archive::Entry(this, QStringLiteral("boot/sunxi-spl.bin"), QStringLiteral("boot/")), new Archive::Entry(this, QStringLiteral("boot/u-boot.img"), QStringLiteral("boot/")) } << dragAndDropOptions << 2; + + archivePath = QFINDTESTDATA("data/bug_#394542.zip"); + QTest::newRow("#394542: libzip doesn't extract selected folder") + << archivePath + << QVector { + new Archive::Entry(this, QStringLiteral("2017 - 05/")), + new Archive::Entry(this, QStringLiteral("2017 - 05/uffdå")) + } + << optionsPreservePaths + << 2; } void ExtractTest::testExtraction() { QFETCH(QString, archivePath); auto loadJob = Archive::load(archivePath, this); QVERIFY(loadJob); loadJob->setAutoDelete(false); TestHelper::startAndWaitForResult(loadJob); auto archive = loadJob->archive(); QVERIFY(archive); if (!archive->isValid()) { QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle); } QTemporaryDir destDir; if (!destDir.isValid()) { QSKIP("Could not create a temporary directory for extraction. Skipping test.", SkipSingle); } QFETCH(QVector, entriesToExtract); QFETCH(ExtractionOptions, extractionOptions); auto extractionJob = archive->extractFiles(entriesToExtract, destDir.path(), extractionOptions); QVERIFY(extractionJob); extractionJob->setAutoDelete(false); TestHelper::startAndWaitForResult(extractionJob); QFETCH(int, expectedExtractedEntriesCount); int extractedEntriesCount = 0; QDirIterator dirIt(destDir.path(), QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (dirIt.hasNext()) { extractedEntriesCount++; dirIt.next(); } QCOMPARE(extractedEntriesCount, expectedExtractedEntriesCount); loadJob->deleteLater(); extractionJob->deleteLater(); archive->deleteLater(); } void ExtractTest::testPreservePermissions_data() { QTest::addColumn("archiveName"); QTest::addColumn("plugin"); QTest::addColumn("testFile"); QTest::addColumn("expectedPermissions"); // Repeat the same test case for each format and for each plugin supporting the format. foreach (const QString &format, TestHelper::testFormats()) { const QString filename = QFINDTESTDATA(QStringLiteral("data/test_permissions.%1").arg(format)); const auto mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); const auto plugins = m_pluginManager.preferredWritePluginsFor(mime); foreach (const auto plugin, plugins) { QTest::newRow(QStringLiteral("test preserve 0755 permissions (%1, %2)").arg(format, plugin->metaData().pluginId()).toUtf8()) << filename << plugin << QStringLiteral("0755.sh") << 0755; } } } void ExtractTest::testPreservePermissions() { QFETCH(QString, archiveName); QFETCH(Plugin*, plugin); QVERIFY(plugin); auto loadJob = Archive::load(archiveName, plugin); QVERIFY(loadJob); loadJob->setAutoDelete(false); TestHelper::startAndWaitForResult(loadJob); auto archive = loadJob->archive(); QVERIFY(archive); if (!archive->isValid()) { QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle); } QTemporaryDir destDir; if (!destDir.isValid()) { QSKIP("Could not create a temporary directory for extraction. Skipping test.", SkipSingle); } auto extractionJob = archive->extractFiles({}, destDir.path()); QVERIFY(extractionJob); extractionJob->setAutoDelete(false); TestHelper::startAndWaitForResult(extractionJob); // Check whether extraction preserved the original permissions. QFETCH(QString, testFile); QFile file(QStringLiteral("%1/%2").arg(destDir.path(), testFile)); QVERIFY(file.exists()); QFETCH(int, expectedPermissions); const auto expectedQtPermissions = KIO::convertPermissions(expectedPermissions); // On Linux we get also the XXXUser flags which are ignored by KIO::convertPermissions(), // so we need to remove them before the comparison with the expected permissions. QCOMPARE(file.permissions() & expectedQtPermissions, expectedQtPermissions); loadJob->deleteLater(); extractionJob->deleteLater(); archive->deleteLater(); } #include "extracttest.moc" diff --git a/plugins/libzipplugin/libzipplugin.cpp b/plugins/libzipplugin/libzipplugin.cpp index be260b39..a92d691d 100644 --- a/plugins/libzipplugin/libzipplugin.cpp +++ b/plugins/libzipplugin/libzipplugin.cpp @@ -1,995 +1,999 @@ /* * Copyright (c) 2017 Ragnar Thomsen * * 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 THE AUTHOR ``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 THE AUTHOR 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. */ #include "libzipplugin.h" #include "ark_debug.h" #include "queries.h" #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(LibZipPluginFactory, "kerfuffle_libzip.json", registerPlugin();) // This is needed for hooking a C callback to a C++ non-static member // function. template struct Callback; template struct Callback { template static Ret callback(Args... args) { return func(args...); } static std::function func; }; // Initialize the static member. template std::function Callback::func; LibzipPlugin::LibzipPlugin(QObject *parent, const QVariantList & args) : ReadWriteArchiveInterface(parent, args) , m_overwriteAll(false) , m_skipAll(false) , m_listAfterAdd(false) { qCDebug(ARK) << "Initializing libzip plugin"; } LibzipPlugin::~LibzipPlugin() { foreach (const auto e, m_emittedEntries) { // Entries might be passed to pending slots, so we just schedule their deletion. e->deleteLater(); } } bool LibzipPlugin::list() { qCDebug(ARK) << "Listing archive contents for:" << QFile::encodeName(filename()); setOperationMode(List); m_numberOfEntries = 0; int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), ZIP_RDONLY, &errcode); zip_error_init_with_code(&err, errcode); if (!archive) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } // Fetch archive comment. m_comment = QString::fromUtf8(zip_get_archive_comment(archive, nullptr, ZIP_FL_ENC_GUESS)); // Get number of archive entries. const auto nofEntries = zip_get_num_entries(archive, 0); qCDebug(ARK) << "Found entries:" << nofEntries; // Loop through all archive entries. for (int i = 0; i < nofEntries; i++) { if (QThread::currentThread()->isInterruptionRequested()) { break; } emitEntryForIndex(archive, i); if (m_listAfterAdd) { // Start at 50%. emit progress(0.5 + (0.5 * float(i + 1) / nofEntries)); } else { emit progress(float(i + 1) / nofEntries); } } zip_close(archive); m_listAfterAdd = false; return true; } bool LibzipPlugin::addFiles(const QVector &files, const Archive::Entry *destination, const CompressionOptions& options, uint numberOfEntriesToAdd) { Q_UNUSED(numberOfEntriesToAdd) setOperationMode(Add); int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), ZIP_CREATE, &errcode); zip_error_init_with_code(&err, errcode); if (!archive) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } uint i = 0; foreach (const Archive::Entry* e, files) { if (QThread::currentThread()->isInterruptionRequested()) { break; } // If entry is a directory, traverse and add all its files and subfolders. if (QFileInfo(e->fullPath()).isDir()) { if (!writeEntry(archive, e->fullPath(), destination, options, true)) { return false; } QDirIterator it(e->fullPath(), QDir::AllEntries | QDir::Readable | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (!QThread::currentThread()->isInterruptionRequested() && it.hasNext()) { const QString path = it.next(); if (QFileInfo(path).isDir()) { if (!writeEntry(archive, path, destination, options, true)) { return false; } } else { if (!writeEntry(archive, path, destination, options)) { return false; } } i++; } } else { if (!writeEntry(archive, e->fullPath(), destination, options)) { return false; } } i++; } qCDebug(ARK) << "Added" << i << "entries"; // Register the callback function to get progress feedback. Callback::func = std::bind(&LibzipPlugin::progressEmitted, this, std::placeholders::_1); void (*c_func)(double) = static_cast(Callback::callback); zip_register_progress_callback(archive, c_func); qCDebug(ARK) << "Writing entries to disk..."; if (zip_close(archive)) { qCCritical(ARK) << "Failed to write archive"; emit error(xi18n("Failed to write archive.")); return false; } // We list the entire archive after adding files to ensure entry // properties are up-to-date. m_listAfterAdd = true; list(); return true; } void LibzipPlugin::progressEmitted(double pct) { // Go from 0 to 50%. The second half is the subsequent listing. emit progress(0.5 * pct); } bool LibzipPlugin::writeEntry(zip_t *archive, const QString &file, const Archive::Entry* destination, const CompressionOptions& options, bool isDir) { Q_ASSERT(archive); QByteArray destFile; if (destination) { destFile = QString(destination->fullPath() + file).toUtf8(); } else { destFile = file.toUtf8(); } qlonglong index; if (isDir) { index = zip_dir_add(archive, destFile, ZIP_FL_ENC_GUESS); if (index == -1) { // If directory already exists in archive, we get an error. qCWarning(ARK) << "Failed to add dir " << file << ":" << zip_strerror(archive); return true; } } else { zip_source_t *src = zip_source_file(archive, QFile::encodeName(file).constData(), 0, -1); Q_ASSERT(src); index = zip_file_add(archive, destFile, src, ZIP_FL_ENC_GUESS | ZIP_FL_OVERWRITE); if (index == -1) { zip_source_free(src); qCCritical(ARK) << "Could not add entry" << file << ":" << zip_strerror(archive); emit error(xi18n("Failed to add entry: %1", QString::fromUtf8(zip_strerror(archive)))); return false; } } #ifndef Q_OS_WIN // Set permissions. QT_STATBUF result; if (QT_STAT(QFile::encodeName(file), &result) != 0) { qCWarning(ARK) << "Failed to read permissions for:" << file; } else { zip_uint32_t attributes = result.st_mode << 16; if (zip_file_set_external_attributes(archive, index, ZIP_FL_UNCHANGED, ZIP_OPSYS_UNIX, attributes) != 0) { qCWarning(ARK) << "Failed to set external attributes for:" << file; } } #endif if (!password().isEmpty()) { Q_ASSERT(!options.encryptionMethod().isEmpty()); if (options.encryptionMethod() == QLatin1String("AES128")) { zip_file_set_encryption(archive, index, ZIP_EM_AES_128, password().toUtf8()); } else if (options.encryptionMethod() == QLatin1String("AES192")) { zip_file_set_encryption(archive, index, ZIP_EM_AES_192, password().toUtf8()); } else if (options.encryptionMethod() == QLatin1String("AES256")) { zip_file_set_encryption(archive, index, ZIP_EM_AES_256, password().toUtf8()); } } // Set compression level and method. zip_int32_t compMethod = ZIP_CM_DEFAULT; if (!options.compressionMethod().isEmpty()) { if (options.compressionMethod() == QLatin1String("Deflate")) { compMethod = ZIP_CM_DEFLATE; } else if (options.compressionMethod() == QLatin1String("BZip2")) { compMethod = ZIP_CM_BZIP2; } else if (options.compressionMethod() == QLatin1String("Store")) { compMethod = ZIP_CM_STORE; } } const int compLevel = options.isCompressionLevelSet() ? options.compressionLevel() : 6; if (zip_set_file_compression(archive, index, compMethod, compLevel) != 0) { qCCritical(ARK) << "Could not set compression options for" << file << ":" << zip_strerror(archive); emit error(xi18n("Failed to set compression options for entry: %1", QString::fromUtf8(zip_strerror(archive)))); return false; } return true; } bool LibzipPlugin::emitEntryForIndex(zip_t *archive, qlonglong index) { Q_ASSERT(archive); zip_stat_t statBuffer; if (zip_stat_index(archive, index, ZIP_FL_ENC_GUESS, &statBuffer)) { qCCritical(ARK) << "Failed to read stat for index" << index; return false; } auto e = new Archive::Entry(); if (statBuffer.valid & ZIP_STAT_NAME) { e->setFullPath(QString::fromUtf8(statBuffer.name)); } if (e->fullPath(PathFormat::WithTrailingSlash).endsWith(QDir::separator())) { e->setProperty("isDirectory", true); } if (statBuffer.valid & ZIP_STAT_MTIME) { e->setProperty("timestamp", QDateTime::fromTime_t(statBuffer.mtime)); } if (statBuffer.valid & ZIP_STAT_SIZE) { e->setProperty("size", (qulonglong)statBuffer.size); } if (statBuffer.valid & ZIP_STAT_COMP_SIZE) { e->setProperty("compressedSize", (qlonglong)statBuffer.comp_size); } if (statBuffer.valid & ZIP_STAT_CRC) { if (!e->isDir()) { e->setProperty("CRC", QString::number((qulonglong)statBuffer.crc, 16).toUpper()); } } if (statBuffer.valid & ZIP_STAT_COMP_METHOD) { switch(statBuffer.comp_method) { case ZIP_CM_STORE: e->setProperty("method", QStringLiteral("Store")); emit compressionMethodFound(QStringLiteral("Store")); break; case ZIP_CM_DEFLATE: e->setProperty("method", QStringLiteral("Deflate")); emit compressionMethodFound(QStringLiteral("Deflate")); break; case ZIP_CM_DEFLATE64: e->setProperty("method", QStringLiteral("Deflate64")); emit compressionMethodFound(QStringLiteral("Deflate64")); break; case ZIP_CM_BZIP2: e->setProperty("method", QStringLiteral("BZip2")); emit compressionMethodFound(QStringLiteral("BZip2")); break; case ZIP_CM_LZMA: e->setProperty("method", QStringLiteral("LZMA")); emit compressionMethodFound(QStringLiteral("LZMA")); break; case ZIP_CM_XZ: e->setProperty("method", QStringLiteral("XZ")); emit compressionMethodFound(QStringLiteral("XZ")); break; } } if (statBuffer.valid & ZIP_STAT_ENCRYPTION_METHOD) { if (statBuffer.encryption_method != ZIP_EM_NONE) { e->setProperty("isPasswordProtected", true); switch(statBuffer.encryption_method) { case ZIP_EM_TRAD_PKWARE: emit encryptionMethodFound(QStringLiteral("ZipCrypto")); break; case ZIP_EM_AES_128: emit encryptionMethodFound(QStringLiteral("AES128")); break; case ZIP_EM_AES_192: emit encryptionMethodFound(QStringLiteral("AES192")); break; case ZIP_EM_AES_256: emit encryptionMethodFound(QStringLiteral("AES256")); break; } } } // Read external attributes, which contains the file permissions. zip_uint8_t opsys; zip_uint32_t attributes; if (zip_file_get_external_attributes(archive, index, ZIP_FL_UNCHANGED, &opsys, &attributes) == -1) { qCCritical(ARK) << "Could not read external attributes for entry:" << QString::fromUtf8(statBuffer.name); emit error(xi18n("Failed to read metadata for entry: %1", QString::fromUtf8(statBuffer.name))); return false; } // Set permissions. switch (opsys) { case ZIP_OPSYS_UNIX: // Unix permissions are stored in the leftmost 16 bits of the external file attribute. e->setProperty("permissions", permissionsToString(attributes >> 16)); break; default: // TODO: non-UNIX. break; } emit entry(e); m_emittedEntries << e; return true; } bool LibzipPlugin::deleteFiles(const QVector &files) { setOperationMode(Delete); int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), 0, &errcode); zip_error_init_with_code(&err, errcode); if (archive == nullptr) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } qulonglong i = 0; foreach (const Archive::Entry* e, files) { if (QThread::currentThread()->isInterruptionRequested()) { break; } const qlonglong index = zip_name_locate(archive, e->fullPath().toUtf8(), ZIP_FL_ENC_GUESS); if (index == -1) { qCCritical(ARK) << "Could not find entry to delete:" << e->fullPath(); emit error(xi18n("Failed to delete entry: %1", e->fullPath())); return false; } if (zip_delete(archive, index) == -1) { qCCritical(ARK) << "Could not delete entry" << e->fullPath() << ":" << zip_strerror(archive); emit error(xi18n("Failed to delete entry: %1", QString::fromUtf8(zip_strerror(archive)))); return false; } emit entryRemoved(e->fullPath()); emit progress(float(++i) / files.size()); } qCDebug(ARK) << "Deleted" << i << "entries"; if (zip_close(archive)) { qCCritical(ARK) << "Failed to write archive"; emit error(xi18n("Failed to write archive.")); return false; } return true; } bool LibzipPlugin::addComment(const QString& comment) { setOperationMode(Comment); int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), 0, &errcode); zip_error_init_with_code(&err, errcode); if (archive == nullptr) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } // Set archive comment. if (zip_set_archive_comment(archive, comment.toUtf8(), comment.length())) { qCCritical(ARK) << "Failed to set comment:" << zip_strerror(archive); return false; } if (zip_close(archive)) { qCCritical(ARK) << "Failed to write archive"; emit error(xi18n("Failed to write archive.")); return false; } return true; } bool LibzipPlugin::testArchive() { qCDebug(ARK) << "Testing archive"; setOperationMode(Test); int errcode = 0; zip_error_t err; // Open archive performing extra consistency checks. zip_t *archive = zip_open(QFile::encodeName(filename()), ZIP_CHECKCONS, &errcode); zip_error_init_with_code(&err, errcode); if (archive == nullptr) { qCCritical(ARK) << "Failed to open archive:" << zip_error_strerror(&err); return false; } // Check CRC-32 for each archive entry. const int nofEntries = zip_get_num_entries(archive, 0); for (int i = 0; i < nofEntries; i++) { if (QThread::currentThread()->isInterruptionRequested()) { return false; } // Get statistic for entry. Used to get entry size. zip_stat_t statBuffer; if (zip_stat_index(archive, i, 0, &statBuffer) != 0) { qCCritical(ARK) << "Failed to read stat for" << statBuffer.name; return false; } zip_file *zipFile = zip_fopen_index(archive, i, 0); std::unique_ptr buf(new uchar[statBuffer.size]); const int len = zip_fread(zipFile, buf.get(), statBuffer.size); if (len == -1 || uint(len) != statBuffer.size) { qCCritical(ARK) << "Failed to read data for" << statBuffer.name; return false; } if (statBuffer.crc != crc32(0, &buf.get()[0], len)) { qCCritical(ARK) << "CRC check failed for" << statBuffer.name; return false; } emit progress(float(i) / nofEntries); } zip_close(archive); emit testSuccess(); return true; } bool LibzipPlugin::doKill() { QMutexLocker mutexLocker(&m_mutex); switch (m_operationMode) { case Add: case Copy: case Delete: case Move: return false; default: break; } return true; } bool LibzipPlugin::extractFiles(const QVector &files, const QString& destinationDirectory, const ExtractionOptions& options) { qCDebug(ARK) << "Extracting files to:" << destinationDirectory; setOperationMode(Extract); const bool extractAll = files.isEmpty(); const bool removeRootNode = options.isDragAndDropEnabled(); int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), ZIP_RDONLY, &errcode); zip_error_init_with_code(&err, errcode); if (archive == nullptr) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } // Set password if known. if (!password().isEmpty()) { qCDebug(ARK) << "Password already known. Setting..."; zip_set_default_password(archive, password().toUtf8()); } // Get number of archive entries. const qlonglong nofEntries = extractAll ? zip_get_num_entries(archive, 0) : files.size(); // Extract entries. m_overwriteAll = false; // Whether to overwrite all files m_skipAll = false; // Whether to skip all files if (extractAll) { // We extract all entries. for (qlonglong i = 0; i < nofEntries; i++) { if (QThread::currentThread()->isInterruptionRequested()) { break; } if (!extractEntry(archive, QDir::fromNativeSeparators(QString::fromUtf8(zip_get_name(archive, i, ZIP_FL_ENC_GUESS))), QString(), destinationDirectory, options.preservePaths(), removeRootNode)) { qCDebug(ARK) << "Extraction failed"; return false; } emit progress(float(i + 1) / nofEntries); } } else { // We extract only the entries in files. qulonglong i = 0; foreach (const Archive::Entry* e, files) { if (QThread::currentThread()->isInterruptionRequested()) { break; } if (!extractEntry(archive, e->fullPath(), e->rootNode, destinationDirectory, options.preservePaths(), removeRootNode)) { qCDebug(ARK) << "Extraction failed"; return false; } emit progress(float(++i) / nofEntries); } } zip_close(archive); return true; } bool LibzipPlugin::extractEntry(zip_t *archive, const QString &entry, const QString &rootNode, const QString &destDir, bool preservePaths, bool removeRootNode) { const bool isDirectory = entry.endsWith(QDir::separator()); // Add trailing slash to destDir if not present. QString destDirCorrected(destDir); if (!destDir.endsWith(QDir::separator())) { destDirCorrected.append(QDir::separator()); } // Remove rootnode if supplied and set destination path. QString destination; if (preservePaths) { if (!removeRootNode || rootNode.isEmpty()) { destination = destDirCorrected + entry; } else { QString truncatedEntry = entry; truncatedEntry.remove(0, rootNode.size()); destination = destDirCorrected + truncatedEntry; } } else { if (isDirectory) { qCDebug(ARK) << "Skipping directory:" << entry; return true; } destination = destDirCorrected + QFileInfo(entry).fileName(); } // Store parent mtime. QString parentDir; if (isDirectory) { QDir pDir = QFileInfo(destination).dir(); pDir.cdUp(); parentDir = pDir.path(); } else { parentDir = QFileInfo(destination).path(); } // For top-level items, don't restore parent dir mtime. const bool restoreParentMtime = (parentDir + QDir::separator() != destDirCorrected); time_t parent_mtime; if (restoreParentMtime) { parent_mtime = QFileInfo(parentDir).lastModified().toMSecsSinceEpoch() / 1000; } // Create parent directories for files. For directories create them. if (!QDir().mkpath(QFileInfo(destination).path())) { qCDebug(ARK) << "Failed to create directory:" << QFileInfo(destination).path(); emit error(xi18n("Failed to create directory: %1", QFileInfo(destination).path())); return false; } // Get statistic for entry. Used to get entry size and mtime. zip_stat_t statBuffer; if (zip_stat(archive, entry.toUtf8(), 0, &statBuffer) != 0) { + if (isDirectory && zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_NOENT) { + qCWarning(ARK) << "Skipping folder without entry:" << entry; + return true; + } qCCritical(ARK) << "Failed to read stat for entry" << entry; return false; } if (!isDirectory) { // Handle existing destination files. QString renamedEntry = entry; while (!m_overwriteAll && QFileInfo::exists(destination)) { if (m_skipAll) { return true; } else { Kerfuffle::OverwriteQuery query(renamedEntry); emit userQuery(&query); query.waitForResponse(); if (query.responseCancelled()) { emit cancelled(); return false; } else if (query.responseSkip()) { return true; } else if (query.responseAutoSkip()) { m_skipAll = true; return true; } else if (query.responseRename()) { const QString newName(query.newFilename()); destination = QFileInfo(destination).path() + QDir::separator() + QFileInfo(newName).fileName(); renamedEntry = QFileInfo(entry).path() + QDir::separator() + QFileInfo(newName).fileName(); } else if (query.responseOverwriteAll()) { m_overwriteAll = true; break; } else if (query.responseOverwrite()) { break; } } } // Handle password-protected files. zip_file *zipFile = nullptr; bool firstTry = true; while (!zipFile) { zipFile = zip_fopen(archive, entry.toUtf8(), 0); if (zipFile) { break; } else if (zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_NOPASSWD || zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_WRONGPASSWD) { Kerfuffle::PasswordNeededQuery query(filename(), !firstTry); emit userQuery(&query); query.waitForResponse(); if (query.responseCancelled()) { emit cancelled(); return false; } setPassword(query.password()); if (zip_set_default_password(archive, password().toUtf8())) { qCDebug(ARK) << "Failed to set password for:" << entry; } firstTry = false; } else { qCCritical(ARK) << "Failed to open file:" << zip_strerror(archive); emit error(xi18n("Failed to open '%1':%2", entry, QString::fromUtf8(zip_strerror(archive)))); return false; } } QFile file(destination); if (!file.open(QIODevice::WriteOnly)) { qCCritical(ARK) << "Failed to open file for writing"; emit error(xi18n("Failed to open file for writing: %1", destination)); return false; } QDataStream out(&file); // Write archive entry to file. We use a read/write buffer of 1000 chars. qulonglong sum = 0; char buf[1000]; while (sum != statBuffer.size) { const auto readBytes = zip_fread(zipFile, buf, 1000); if (readBytes < 0) { qCCritical(ARK) << "Failed to read data"; emit error(xi18n("Failed to read data for entry: %1", entry)); return false; } if (out.writeRawData(buf, readBytes) != readBytes) { qCCritical(ARK) << "Failed to write data"; emit error(xi18n("Failed to write data for entry: %1", entry)); return false; } sum += readBytes; } const auto index = zip_name_locate(archive, entry.toUtf8(), ZIP_FL_ENC_GUESS); if (index == -1) { qCCritical(ARK) << "Could not locate entry:" << entry; emit error(xi18n("Failed to locate entry: %1", entry)); return false; } zip_uint8_t opsys; zip_uint32_t attributes; if (zip_file_get_external_attributes(archive, index, ZIP_FL_UNCHANGED, &opsys, &attributes) == -1) { qCCritical(ARK) << "Could not read external attributes for entry:" << entry; emit error(xi18n("Failed to read metadata for entry: %1", entry)); return false; } // Inspired by fuse-zip source code: fuse-zip/lib/fileNode.cpp switch (opsys) { case ZIP_OPSYS_UNIX: // Unix permissions are stored in the leftmost 16 bits of the external file attribute. file.setPermissions(KIO::convertPermissions(attributes >> 16)); break; default: // TODO: non-UNIX. break; } file.close(); } // Set mtime for entry. utimbuf times; times.modtime = statBuffer.mtime; if (utime(destination.toUtf8(), ×) != 0) { qCWarning(ARK) << "Failed to restore mtime:" << destination; } if (restoreParentMtime) { // Restore mtime for parent dir. times.modtime = parent_mtime; if (utime(parentDir.toUtf8(), ×) != 0) { qCWarning(ARK) << "Failed to restore mtime for parent dir of:" << destination; } } return true; } bool LibzipPlugin::moveFiles(const QVector &files, Archive::Entry *destination, const CompressionOptions &options) { Q_UNUSED(options) setOperationMode(Move); int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), 0, &errcode); zip_error_init_with_code(&err, errcode); if (archive == nullptr) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } QStringList filePaths = entryFullPaths(files); filePaths.sort(); const QStringList destPaths = entryPathsFromDestination(filePaths, destination, entriesWithoutChildren(files).count()); int i; for (i = 0; i < filePaths.size(); ++i) { const int index = zip_name_locate(archive, filePaths.at(i).toUtf8(), ZIP_FL_ENC_GUESS); if (index == -1) { qCCritical(ARK) << "Could not find entry to move:" << filePaths.at(i); emit error(xi18n("Failed to move entry: %1", filePaths.at(i))); return false; } if (zip_file_rename(archive, index, destPaths.at(i).toUtf8(), ZIP_FL_ENC_GUESS) == -1) { qCCritical(ARK) << "Could not move entry:" << filePaths.at(i); emit error(xi18n("Failed to move entry: %1", filePaths.at(i))); return false; } emit entryRemoved(filePaths.at(i)); emitEntryForIndex(archive, index); emit progress(i/filePaths.count()); } if (zip_close(archive)) { qCCritical(ARK) << "Failed to write archive"; emit error(xi18n("Failed to write archive.")); return false; } qCDebug(ARK) << "Moved" << i << "entries"; return true; } bool LibzipPlugin::copyFiles(const QVector &files, Archive::Entry *destination, const CompressionOptions &options) { Q_UNUSED(options) setOperationMode(Copy); int errcode = 0; zip_error_t err; // Open archive. zip_t *archive = zip_open(QFile::encodeName(filename()), 0, &errcode); zip_error_init_with_code(&err, errcode); if (archive == nullptr) { qCCritical(ARK) << "Failed to open archive. Code:" << errcode; emit error(xi18n("Failed to open archive: %1", QString::fromUtf8(zip_error_strerror(&err)))); return false; } const QStringList filePaths = entryFullPaths(files); const QStringList destPaths = entryPathsFromDestination(filePaths, destination, 0); int i; for (i = 0; i < filePaths.size(); ++i) { QString dest = destPaths.at(i); if (dest.endsWith(QDir::separator())) { if (zip_dir_add(archive, dest.toUtf8(), ZIP_FL_ENC_GUESS) == -1) { // If directory already exists in archive, we get an error. qCWarning(ARK) << "Failed to add dir " << dest << ":" << zip_strerror(archive); continue; } } const int srcIndex = zip_name_locate(archive, filePaths.at(i).toUtf8(), ZIP_FL_ENC_GUESS); if (srcIndex == -1) { qCCritical(ARK) << "Could not find entry to copy:" << filePaths.at(i); emit error(xi18n("Failed to copy entry: %1", filePaths.at(i))); return false; } zip_source_t *src = zip_source_zip(archive, archive, srcIndex, 0, 0, -1); if (!src) { qCCritical(ARK) << "Failed to create source for:" << filePaths.at(i); return false; } const int destIndex = zip_file_add(archive, dest.toUtf8(), src, ZIP_FL_ENC_GUESS | ZIP_FL_OVERWRITE); if (destIndex == -1) { zip_source_free(src); qCCritical(ARK) << "Could not add entry" << dest << ":" << zip_strerror(archive); emit error(xi18n("Failed to add entry: %1", QString::fromUtf8(zip_strerror(archive)))); return false; } // Get permissions from source entry. zip_uint8_t opsys; zip_uint32_t attributes; if (zip_file_get_external_attributes(archive, srcIndex, ZIP_FL_UNCHANGED, &opsys, &attributes) == -1) { qCCritical(ARK) << "Failed to read external attributes for source:" << filePaths.at(i); emit error(xi18n("Failed to read metadata for entry: %1", filePaths.at(i))); return false; } // Set permissions on dest entry. if (zip_file_set_external_attributes(archive, destIndex, ZIP_FL_UNCHANGED, opsys, attributes) != 0) { qCCritical(ARK) << "Failed to set external attributes for destination:" << dest; emit error(xi18n("Failed to set metadata for entry: %1", dest)); return false; } } // Register the callback function to get progress feedback. Callback::func = std::bind(&LibzipPlugin::progressEmitted, this, std::placeholders::_1); void (*c_func)(double) = static_cast(Callback::callback); zip_register_progress_callback(archive, c_func); if (zip_close(archive)) { qCCritical(ARK) << "Failed to write archive"; emit error(xi18n("Failed to write archive.")); return false; } // List the archive to update the model. m_listAfterAdd = true; list(); qCDebug(ARK) << "Copied" << i << "entries"; return true; } QString LibzipPlugin::permissionsToString(const mode_t &perm) { QString modeval; if ((perm & S_IFMT) == S_IFDIR) { modeval.append(QLatin1Char('d')); } else if ((perm & S_IFMT) == S_IFLNK) { modeval.append(QLatin1Char('l')); } else { modeval.append(QLatin1Char('-')); } modeval.append((perm & S_IRUSR) ? QLatin1Char('r') : QLatin1Char('-')); modeval.append((perm & S_IWUSR) ? QLatin1Char('w') : QLatin1Char('-')); if ((perm & S_ISUID) && (perm & S_IXUSR)) { modeval.append(QLatin1Char('s')); } else if ((perm & S_ISUID)) { modeval.append(QLatin1Char('S')); } else if ((perm & S_IXUSR)) { modeval.append(QLatin1Char('x')); } else { modeval.append(QLatin1Char('-')); } modeval.append((perm & S_IRGRP) ? QLatin1Char('r') : QLatin1Char('-')); modeval.append((perm & S_IWGRP) ? QLatin1Char('w') : QLatin1Char('-')); if ((perm & S_ISGID) && (perm & S_IXGRP)) { modeval.append(QLatin1Char('s')); } else if ((perm & S_ISGID)) { modeval.append(QLatin1Char('S')); } else if ((perm & S_IXGRP)) { modeval.append(QLatin1Char('x')); } else { modeval.append(QLatin1Char('-')); } modeval.append((perm & S_IROTH) ? QLatin1Char('r') : QLatin1Char('-')); modeval.append((perm & S_IWOTH) ? QLatin1Char('w') : QLatin1Char('-')); if ((perm & S_ISVTX) && (perm & S_IXOTH)) { modeval.append(QLatin1Char('t')); } else if ((perm & S_ISVTX)) { modeval.append(QLatin1Char('T')); } else if ((perm & S_IXOTH)) { modeval.append(QLatin1Char('x')); } else { modeval.append(QLatin1Char('-')); } return modeval; } void LibzipPlugin::setOperationMode(ReadWriteArchiveInterface::OperationMode operationMode) { QMutexLocker mutexLocker(&m_mutex); m_operationMode = operationMode; } #include "libzipplugin.moc"