diff --git a/autotests/plugins/cli7zplugin/cli7ztest.cpp b/autotests/plugins/cli7zplugin/cli7ztest.cpp index c467cd68..feca867d 100644 --- a/autotests/plugins/cli7zplugin/cli7ztest.cpp +++ b/autotests/plugins/cli7zplugin/cli7ztest.cpp @@ -1,501 +1,500 @@ /* * Copyright (c) 2016 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 "cli7ztest.h" #include "cliplugin.h" #include "archive_kerfuffle.h" #include "jobs.h" #include "testhelper.h" #include #include #include #include #include QTEST_GUILESS_MAIN(Cli7zTest) using namespace Kerfuffle; void Cli7zTest::initTestCase() { m_plugin = new Plugin(this); foreach (Plugin *plugin, m_pluginManger.availablePlugins()) { if (plugin->metaData().pluginId() == QStringLiteral("kerfuffle_cli7z")) { m_plugin = plugin; return; } } } void Cli7zTest::testArchive_data() { QTest::addColumn("archivePath"); QTest::addColumn("expectedFileName"); QTest::addColumn("isReadOnly"); QTest::addColumn("isSingleFolder"); QTest::addColumn("expectedEncryptionType"); QTest::addColumn("expectedSubfolderName"); QString archivePath = QFINDTESTDATA("data/one_toplevel_folder.7z"); QTest::newRow("archive with one top-level folder") << archivePath << QFileInfo(archivePath).fileName() << false << true << Archive::Unencrypted << QStringLiteral("A"); } void Cli7zTest::testArchive() { if (!m_plugin->isValid()) { QSKIP("cli7z plugin not available. Skipping test.", SkipSingle); } QFETCH(QString, archivePath); auto loadJob = Archive::load(archivePath, m_plugin, this); QVERIFY(loadJob); TestHelper::startAndWaitForResult(loadJob); auto archive = loadJob->archive(); QVERIFY(archive); if (!archive->isValid()) { QSKIP("Could not load the cli7z plugin. Skipping test.", SkipSingle); } QFETCH(QString, expectedFileName); QCOMPARE(QFileInfo(archive->fileName()).fileName(), expectedFileName); QFETCH(bool, isReadOnly); QCOMPARE(archive->isReadOnly(), isReadOnly); QFETCH(bool, isSingleFolder); QCOMPARE(archive->isSingleFolder(), isSingleFolder); QFETCH(Archive::EncryptionType, expectedEncryptionType); QCOMPARE(archive->encryptionType(), expectedEncryptionType); QFETCH(QString, expectedSubfolderName); QCOMPARE(archive->subfolderName(), expectedSubfolderName); } void Cli7zTest::testList_data() { QTest::addColumn("outputTextFile"); QTest::addColumn("expectedEntriesCount"); QTest::addColumn("isMultiVolume"); // Is zero for non-multi-volume archives: QTest::addColumn("numberOfVolumes"); QTest::addColumn("compressionMethods"); QTest::addColumn("encryptionMethod"); // Index of some entry to be tested. QTest::addColumn("someEntryIndex"); // Entry metadata. QTest::addColumn("expectedName"); QTest::addColumn("isDirectory"); QTest::addColumn("isPasswordProtected"); QTest::addColumn("expectedSize"); QTest::addColumn("expectedTimestamp"); // p7zip version 16.02 tests QTest::newRow("normal-file-1602") << QFINDTESTDATA("data/archive-with-symlink-1602.txt") << 10 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QString() << 4 << QStringLiteral("testarchive/dir2/file2.txt") << false << false << (qulonglong) 32 << QStringLiteral("2015-05-17T20:41:48"); QTest::newRow("encrypted-1602") << QFINDTESTDATA("data/archive-encrypted-1602.txt") << 4 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QStringLiteral("7zAES") << 1 << QStringLiteral("file2.txt") << false << true << (qulonglong) 14 << QStringLiteral("2016-03-02T22:37:55"); QTest::newRow("multi-volume-1602") << QFINDTESTDATA("data/archive-multivol-1602.txt") << 2 << true << 5 << QStringList{QStringLiteral("LZMA2")} << QString() << 1 << QStringLiteral("largefile2") << false << false << (qulonglong) 2097152 << QStringLiteral("2016-07-17T11:26:19"); QTest::newRow("zip-with-AES256-encryption") << QFINDTESTDATA("data/archive-zip-AES256-1602.txt") << 4 << false << 0 << QStringList{QStringLiteral("Store"), QStringLiteral("Store"), QStringLiteral("Deflate"), QStringLiteral("Deflate")} << QStringLiteral("AES256") << 3 << QStringLiteral("testarchive/file2.txt") << false << true << (qulonglong) 33 << QStringLiteral("2016-11-06T21:17:02"); // p7zip version 15.14 tests QTest::newRow("normal-file-1514") << QFINDTESTDATA("data/archive-with-symlink-1514.txt") << 10 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QString() << 4 << QStringLiteral("testarchive/dir2/file2.txt") << false << false << (qulonglong) 32 << QStringLiteral("2015-05-17T19:41:48"); QTest::newRow("encrypted-1514") << QFINDTESTDATA("data/archive-encrypted-1514.txt") << 9 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QStringLiteral("7zAES") << 3 << QStringLiteral("testarchive/dir1/file1.txt") << false << true << (qulonglong) 32 << QStringLiteral("2015-05-17T19:41:48"); // p7zip version 15.09 tests QTest::newRow("normal-file-1509") << QFINDTESTDATA("data/archive-with-symlink-1509.txt") << 10 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QString() << 4 << QStringLiteral("testarchive/dir2/file2.txt") << false << false << (qulonglong) 32 << QStringLiteral("2015-05-17T19:41:48"); QTest::newRow("encrypted-1509") << QFINDTESTDATA("data/archive-encrypted-1509.txt") << 9 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QStringLiteral("7zAES") << 3 << QStringLiteral("testarchive/dir1/file1.txt") << false << true << (qulonglong) 32 << QStringLiteral("2015-05-17T19:41:48"); // p7zip version 9.38.1 tests QTest::newRow("normal-file-9381") << QFINDTESTDATA("data/archive-with-symlink-9381.txt") << 10 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QString() << 4 << QStringLiteral("testarchive/dir2/file2.txt") << false << false << (qulonglong) 32 << QStringLiteral("2015-05-17T19:41:48"); QTest::newRow("encrypted-9381") << QFINDTESTDATA("data/archive-encrypted-9381.txt") << 9 << false << 0 << QStringList{QStringLiteral("LZMA2")} << QStringLiteral("7zAES") << 3 << QStringLiteral("testarchive/dir1/file1.txt") << false << true << (qulonglong) 32 << QStringLiteral("2015-05-17T19:41:48"); } void Cli7zTest::testList() { qRegisterMetaType("Archive::Entry*"); CliPlugin *plugin = new CliPlugin(this, {QStringLiteral("dummy.7z"), QVariant::fromValue(m_plugin->metaData())}); QSignalSpy signalSpyEntry(plugin, &CliPlugin::entry); QSignalSpy signalSpyCompMethod(plugin, &CliPlugin::compressionMethodFound); QSignalSpy signalSpyEncMethod(plugin, &CliPlugin::encryptionMethodFound); QFETCH(QString, outputTextFile); QFETCH(int, expectedEntriesCount); QFile outputText(outputTextFile); QVERIFY(outputText.open(QIODevice::ReadOnly)); QTextStream outputStream(&outputText); while (!outputStream.atEnd()) { const QString line(outputStream.readLine()); QVERIFY(plugin->readListLine(line)); } QCOMPARE(signalSpyEntry.count(), expectedEntriesCount); QFETCH(bool, isMultiVolume); QCOMPARE(plugin->isMultiVolume(), isMultiVolume); QFETCH(int, numberOfVolumes); QCOMPARE(plugin->numberOfVolumes(), numberOfVolumes); QFETCH(QStringList, compressionMethods); QCOMPARE(signalSpyCompMethod.count(), compressionMethods.count()); QStringList combinedCompMethods; for (int i = 0; i < signalSpyCompMethod.count(); i++) { combinedCompMethods.append(signalSpyCompMethod.at(i).at(0).toString()); } QCOMPARE(combinedCompMethods, compressionMethods); QFETCH(QString, encryptionMethod); if (encryptionMethod.isEmpty()) { QCOMPARE(signalSpyEncMethod.count(), 0); } else { QVERIFY(signalSpyEncMethod.count() > 0); QCOMPARE(signalSpyEncMethod.at(0).at(0).toString(), encryptionMethod); } QFETCH(int, someEntryIndex); QVERIFY(someEntryIndex < signalSpyEntry.count()); Archive::Entry *entry = signalSpyEntry.at(someEntryIndex).at(0).value(); QFETCH(QString, expectedName); QCOMPARE(entry->fullPath(), expectedName); QFETCH(bool, isDirectory); QCOMPARE(entry->isDir(), isDirectory); QFETCH(bool, isPasswordProtected); QCOMPARE(entry->property("isPasswordProtected").toBool(), isPasswordProtected); QFETCH(qulonglong, expectedSize); QCOMPARE(entry->property("size").toULongLong(), expectedSize); QFETCH(QString, expectedTimestamp); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) QEXPECT_FAIL("", "Something changed since Qt 5.11, needs investigation.", Continue); #endif QCOMPARE(entry->property("timestamp").toString(), expectedTimestamp); plugin->deleteLater(); } void Cli7zTest::testListArgs_data() { QTest::addColumn("archiveName"); QTest::addColumn("password"); QTest::addColumn("expectedArgs"); QTest::newRow("unencrypted") << QStringLiteral("/tmp/foo.7z") << QString() << QStringList { QStringLiteral("l"), QStringLiteral("-slt"), QStringLiteral("/tmp/foo.7z") }; QTest::newRow("header-encrypted") << QStringLiteral("/tmp/foo.7z") << QStringLiteral("1234") << QStringList { QStringLiteral("l"), QStringLiteral("-slt"), QStringLiteral("-p1234"), QStringLiteral("/tmp/foo.7z") }; } void Cli7zTest::testListArgs() { if (!m_plugin->isValid()) { QSKIP("cli7z plugin not available. Skipping test.", SkipSingle); } QFETCH(QString, archiveName); CliPlugin *plugin = new CliPlugin(this, {QVariant(archiveName), QVariant::fromValue(m_plugin->metaData())}); QVERIFY(plugin); QFETCH(QString, password); const auto replacedArgs = plugin->cliProperties()->listArgs(archiveName, password); QFETCH(QStringList, expectedArgs); QCOMPARE(replacedArgs, expectedArgs); plugin->deleteLater(); } void Cli7zTest::testAddArgs_data() { QTest::addColumn("archiveName"); QTest::addColumn("password"); QTest::addColumn("encryptHeader"); QTest::addColumn("compressionLevel"); QTest::addColumn("compressionMethod"); QTest::addColumn("volumeSize"); QTest::addColumn("expectedArgs"); QTest::newRow("unencrypted") << QStringLiteral("/tmp/foo.7z") << QString() << false << 5 << QStringLiteral("LZMA2") << 0UL << QStringList { QStringLiteral("a"), QStringLiteral("-l"), QStringLiteral("-mx=5"), QStringLiteral("-m0=LZMA2"), QStringLiteral("/tmp/foo.7z") }; QTest::newRow("encrypted") << QStringLiteral("/tmp/foo.7z") << QStringLiteral("1234") << false << 5 << QStringLiteral("LZMA2") << 0UL << QStringList { QStringLiteral("a"), QStringLiteral("-l"), QStringLiteral("-p1234"), QStringLiteral("-mx=5"), QStringLiteral("-m0=LZMA2"), QStringLiteral("/tmp/foo.7z") }; QTest::newRow("header-encrypted") << QStringLiteral("/tmp/foo.7z") << QStringLiteral("1234") << true << 5 << QStringLiteral("LZMA2") << 0UL << QStringList { QStringLiteral("a"), QStringLiteral("-l"), QStringLiteral("-p1234"), QStringLiteral("-mhe=on"), QStringLiteral("-mx=5"), QStringLiteral("-m0=LZMA2"), QStringLiteral("/tmp/foo.7z") }; QTest::newRow("multi-volume") << QStringLiteral("/tmp/foo.7z") << QString() << false << 5 << QStringLiteral("LZMA2") << 2500UL << QStringList { QStringLiteral("a"), QStringLiteral("-l"), QStringLiteral("-mx=5"), QStringLiteral("-m0=LZMA2"), QStringLiteral("-v2500k"), QStringLiteral("/tmp/foo.7z") }; QTest::newRow("comp-method-bzip2") << QStringLiteral("/tmp/foo.7z") << QString() << false << 5 << QStringLiteral("BZip2") << 0UL << QStringList { QStringLiteral("a"), QStringLiteral("-l"), QStringLiteral("-mx=5"), QStringLiteral("-m0=BZip2"), QStringLiteral("/tmp/foo.7z") }; } void Cli7zTest::testAddArgs() { if (!m_plugin->isValid()) { QSKIP("cli7z plugin not available. Skipping test.", SkipSingle); } QFETCH(QString, archiveName); CliPlugin *plugin = new CliPlugin(this, {QVariant(archiveName), QVariant::fromValue(m_plugin->metaData())}); QVERIFY(plugin); QFETCH(QString, password); QFETCH(bool, encryptHeader); QFETCH(int, compressionLevel); QFETCH(ulong, volumeSize); QFETCH(QString, compressionMethod); const auto replacedArgs = plugin->cliProperties()->addArgs(archiveName, {}, password, encryptHeader, compressionLevel, compressionMethod, QString(), volumeSize); QFETCH(QStringList, expectedArgs); QCOMPARE(replacedArgs, expectedArgs); plugin->deleteLater(); } void Cli7zTest::testExtractArgs_data() { QTest::addColumn("archiveName"); QTest::addColumn>("files"); QTest::addColumn("preservePaths"); QTest::addColumn("password"); QTest::addColumn("expectedArgs"); QTest::newRow("preserve paths, encrypted") << QStringLiteral("/tmp/foo.7z") << QVector { new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")), new Archive::Entry(this, QStringLiteral("c.txt"), QString()) } << true << QStringLiteral("1234") << QStringList { QStringLiteral("x"), QStringLiteral("-p1234"), QStringLiteral("/tmp/foo.7z"), QStringLiteral("aDir/textfile2.txt"), QStringLiteral("c.txt"), }; QTest::newRow("preserve paths, unencrypted") << QStringLiteral("/tmp/foo.7z") << QVector { new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")), new Archive::Entry(this, QStringLiteral("c.txt"), QString()) } << true << QString() << QStringList { QStringLiteral("x"), QStringLiteral("/tmp/foo.7z"), QStringLiteral("aDir/textfile2.txt"), QStringLiteral("c.txt"), }; QTest::newRow("without paths, encrypted") << QStringLiteral("/tmp/foo.7z") << QVector { new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")), new Archive::Entry(this, QStringLiteral("c.txt"), QString()) } << false << QStringLiteral("1234") << QStringList { QStringLiteral("e"), QStringLiteral("-p1234"), QStringLiteral("/tmp/foo.7z"), QStringLiteral("aDir/textfile2.txt"), QStringLiteral("c.txt"), }; QTest::newRow("without paths, unencrypted") << QStringLiteral("/tmp/foo.7z") << QVector { new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")), new Archive::Entry(this, QStringLiteral("c.txt"), QString()) } << false << QString() << QStringList { QStringLiteral("e"), QStringLiteral("/tmp/foo.7z"), QStringLiteral("aDir/textfile2.txt"), QStringLiteral("c.txt"), }; } void Cli7zTest::testExtractArgs() { if (!m_plugin->isValid()) { QSKIP("cli7z plugin not available. Skipping test.", SkipSingle); } QFETCH(QString, archiveName); CliPlugin *plugin = new CliPlugin(this, {QVariant(archiveName), QVariant::fromValue(m_plugin->metaData())}); QVERIFY(plugin); QFETCH(QVector, files); QStringList filesList; foreach (const Archive::Entry *e, files) { filesList << e->fullPath(NoTrailingSlash); } QFETCH(bool, preservePaths); QFETCH(QString, password); const auto replacedArgs = plugin->cliProperties()->extractArgs(archiveName, filesList, preservePaths, password); QFETCH(QStringList, expectedArgs); QCOMPARE(replacedArgs, expectedArgs); plugin->deleteLater(); } void Cli7zTest::testRDAAttributes() { if (!m_plugin->isValid()) { QSKIP("cli7z plugin not available. Skipping test.", SkipSingle); } auto loadJob = Archive::load(QFINDTESTDATA("data/RDA-attributes.zip"), m_plugin, this); QVERIFY(loadJob); int numberOfFolders = 0; connect(loadJob, &Job::newEntry, this, [&numberOfFolders](Archive::Entry *entry) { if (entry->isDir()) { numberOfFolders++; } }); TestHelper::startAndWaitForResult(loadJob); - QEXPECT_FAIL("", "Folders with RDA attributes are currently not parsed, see https://phabricator.kde.org/D18562", Continue); QCOMPARE(numberOfFolders, 2); } diff --git a/plugins/cli7zplugin/cliplugin.cpp b/plugins/cli7zplugin/cliplugin.cpp index ef6c7948..c556eb96 100644 --- a/plugins/cli7zplugin/cliplugin.cpp +++ b/plugins/cli7zplugin/cliplugin.cpp @@ -1,335 +1,351 @@ /* * ark -- archiver for the KDE project * * Copyright (C) 2009 Harald Hvaal * Copyright (C) 2009-2011 Raphael Kubo da Costa * Copyright (c) 2016 Vladyslav Batyrenko * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "cliplugin.h" #include "ark_debug.h" #include "cliinterface.h" #include #include #include #include #include using namespace Kerfuffle; K_PLUGIN_CLASS_WITH_JSON(CliPlugin, "kerfuffle_cli7z.json") CliPlugin::CliPlugin(QObject *parent, const QVariantList & args) : CliInterface(parent, args) , m_archiveType(ArchiveType7z) , m_parseState(ParseStateTitle) , m_linesComment(0) , m_isFirstInformationEntry(true) { qCDebug(ARK) << "Loaded cli_7z plugin"; setupCliProperties(); } CliPlugin::~CliPlugin() { } void CliPlugin::resetParsing() { m_parseState = ParseStateTitle; m_comment.clear(); m_numberOfVolumes = 0; } void CliPlugin::setupCliProperties() { qCDebug(ARK) << "Setting up parameters..."; m_cliProps->setProperty("captureProgress", false); m_cliProps->setProperty("addProgram", QStringLiteral("7z")); m_cliProps->setProperty("addSwitch", QStringList{QStringLiteral("a"), QStringLiteral("-l")}); m_cliProps->setProperty("deleteProgram", QStringLiteral("7z")); m_cliProps->setProperty("deleteSwitch", QStringLiteral("d")); m_cliProps->setProperty("extractProgram", QStringLiteral("7z")); m_cliProps->setProperty("extractSwitch", QStringList{QStringLiteral("x")}); m_cliProps->setProperty("extractSwitchNoPreserve", QStringList{QStringLiteral("e")}); m_cliProps->setProperty("listProgram", QStringLiteral("7z")); m_cliProps->setProperty("listSwitch", QStringList{QStringLiteral("l"), QStringLiteral("-slt")}); m_cliProps->setProperty("moveProgram", QStringLiteral("7z")); m_cliProps->setProperty("moveSwitch", QStringLiteral("rn")); m_cliProps->setProperty("testProgram", QStringLiteral("7z")); m_cliProps->setProperty("testSwitch", QStringLiteral("t")); m_cliProps->setProperty("passwordSwitch", QStringList{QStringLiteral("-p$Password")}); m_cliProps->setProperty("passwordSwitchHeaderEnc", QStringList{QStringLiteral("-p$Password"), QStringLiteral("-mhe=on")}); m_cliProps->setProperty("compressionLevelSwitch", QStringLiteral("-mx=$CompressionLevel")); m_cliProps->setProperty("compressionMethodSwitch", QHash{{QStringLiteral("application/x-7z-compressed"), QStringLiteral("-m0=$CompressionMethod")}, {QStringLiteral("application/zip"), QStringLiteral("-mm=$CompressionMethod")}}); m_cliProps->setProperty("encryptionMethodSwitch", QHash{{QStringLiteral("application/x-7z-compressed"), QString()}, {QStringLiteral("application/zip"), QStringLiteral("-mem=$EncryptionMethod")}}); m_cliProps->setProperty("multiVolumeSwitch", QStringLiteral("-v$VolumeSizek")); m_cliProps->setProperty("passwordPromptPatterns", QStringList{QStringLiteral("Enter password \\(will not be echoed\\)")}); m_cliProps->setProperty("wrongPasswordPatterns", QStringList{QStringLiteral("Wrong password")}); m_cliProps->setProperty("testPassedPatterns", QStringList{QStringLiteral("^Everything is Ok$")}); m_cliProps->setProperty("fileExistsPatterns", QStringList{QStringLiteral("^\\(Y\\)es / \\(N\\)o / \\(A\\)lways / \\(S\\)kip all / A\\(u\\)to rename all / \\(Q\\)uit\\? $"), QStringLiteral("^\\? \\(Y\\)es / \\(N\\)o / \\(A\\)lways / \\(S\\)kip all / A\\(u\\)to rename all / \\(Q\\)uit\\? $")}); m_cliProps->setProperty("fileExistsFileName", QStringList{QStringLiteral("^file \\./(.*)$"), QStringLiteral("^ Path: \\./(.*)$")}); m_cliProps->setProperty("fileExistsInput", QStringList{QStringLiteral("Y"), //Overwrite QStringLiteral("N"), //Skip QStringLiteral("A"), //Overwrite all QStringLiteral("S"), //Autoskip QStringLiteral("Q")}); //Cancel m_cliProps->setProperty("corruptArchivePatterns", QStringList{QStringLiteral("Unexpected end of archive"), QStringLiteral("Headers Error")}); m_cliProps->setProperty("diskFullPatterns", QStringList{QStringLiteral("No space left on device")}); m_cliProps->setProperty("multiVolumeSuffix", QStringList{QStringLiteral("$Suffix.001")}); } +void CliPlugin::fixDirectoryFullName() +{ + if (m_currentArchiveEntry->isDir()) { + const QString directoryName = m_currentArchiveEntry->fullPath(); + if (!directoryName.endsWith(QLatin1Char('/'))) { + m_currentArchiveEntry->setProperty("fullPath", QString(directoryName + QLatin1Char('/'))); + } + } +} + bool CliPlugin::readListLine(const QString& line) { static const QLatin1String archiveInfoDelimiter1("--"); // 7z 9.13+ static const QLatin1String archiveInfoDelimiter2("----"); // 7z 9.04 static const QLatin1String entryInfoDelimiter("----------"); const QRegularExpression rxComment(QStringLiteral("Comment = .+$")); const QRegularExpression rxListFailed(QStringLiteral("Open ERROR: Can not open the file as \\[7z\\] archive")); if (rxListFailed.match(line).hasMatch()) { emit error(i18n("Listing the archive failed.")); return false; } if (m_parseState == ParseStateTitle) { const QRegularExpression rxVersionLine(QStringLiteral("^p7zip Version ([\\d\\.]+) .*$")); QRegularExpressionMatch matchVersion = rxVersionLine.match(line); if (matchVersion.hasMatch()) { m_parseState = ParseStateHeader; const QString p7zipVersion = matchVersion.captured(1); qCDebug(ARK) << "p7zip version" << p7zipVersion << "detected"; } } else if (m_parseState == ParseStateHeader) { if (line.startsWith(QStringLiteral("Listing archive:"))) { qCDebug(ARK) << "Archive name: " << line.right(line.size() - 16).trimmed(); } else if ((line == archiveInfoDelimiter1) || (line == archiveInfoDelimiter2)) { m_parseState = ParseStateArchiveInformation; } else if (line.contains(QStringLiteral("Error: "))) { qCWarning(ARK) << line.mid(7); } } else if (m_parseState == ParseStateArchiveInformation) { if (line == entryInfoDelimiter) { m_parseState = ParseStateEntryInformation; } else if (line.startsWith(QStringLiteral("Type = "))) { const QString type = line.mid(7).trimmed(); qCDebug(ARK) << "Archive type: " << type; if (type == QLatin1String("7z")) { m_archiveType = ArchiveType7z; } else if (type == QLatin1String("bzip2")) { m_archiveType = ArchiveTypeBZip2; } else if (type == QLatin1String("gzip")) { m_archiveType = ArchiveTypeGZip; } else if (type == QLatin1String("xz")) { m_archiveType = ArchiveTypeXz; } else if (type == QLatin1String("tar")) { m_archiveType = ArchiveTypeTar; } else if (type == QLatin1String("zip")) { m_archiveType = ArchiveTypeZip; } else if (type == QLatin1String("Rar")) { m_archiveType = ArchiveTypeRar; } else if (type == QLatin1String("Split")) { setMultiVolume(true); } else { // Should not happen qCWarning(ARK) << "Unsupported archive type"; return false; } } else if (line.startsWith(QStringLiteral("Volumes = "))) { m_numberOfVolumes = line.section(QLatin1Char('='), 1).trimmed().toInt(); } else if (line.startsWith(QStringLiteral("Method = "))) { QStringList methods = line.section(QLatin1Char('='), 1).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts); handleMethods(methods); } else if (rxComment.match(line).hasMatch()) { m_parseState = ParseStateComment; m_comment.append(line.section(QLatin1Char('='), 1) + QLatin1Char('\n')); } } else if (m_parseState == ParseStateComment) { if (line == entryInfoDelimiter) { m_parseState = ParseStateEntryInformation; if (!m_comment.trimmed().isEmpty()) { m_comment = m_comment.trimmed(); m_linesComment = m_comment.count(QLatin1Char('\n')) + 1; qCDebug(ARK) << "Found a comment with" << m_linesComment << "lines"; } } else { m_comment.append(line + QLatin1Char('\n')); } } else if (m_parseState == ParseStateEntryInformation) { if (m_isFirstInformationEntry) { m_isFirstInformationEntry = false; m_currentArchiveEntry = new Archive::Entry(this); m_currentArchiveEntry->compressedSizeIsSet = false; } if (line.startsWith(QStringLiteral("Path = "))) { const QString entryFilename = QDir::fromNativeSeparators(line.mid(7).trimmed()); m_currentArchiveEntry->setProperty("fullPath", entryFilename); } else if (line.startsWith(QStringLiteral("Size = "))) { m_currentArchiveEntry->setProperty("size", line.mid(7).trimmed()); } else if (line.startsWith(QStringLiteral("Packed Size = "))) { // #236696: 7z files only show a single Packed Size value // corresponding to the whole archive. if (m_archiveType != ArchiveType7z) { m_currentArchiveEntry->compressedSizeIsSet = true; m_currentArchiveEntry->setProperty("compressedSize", line.mid(14).trimmed()); } } else if (line.startsWith(QStringLiteral("Modified = "))) { m_currentArchiveEntry->setProperty("timestamp", QDateTime::fromString(line.mid(11).trimmed(), QStringLiteral("yyyy-MM-dd hh:mm:ss"))); + } else if (line.startsWith(QStringLiteral("Folder = "))) { + const QString isDirectoryStr = line.mid(9).trimmed(); + Q_ASSERT(isDirectoryStr == QStringLiteral("+") || isDirectoryStr == QStringLiteral("-")); + const bool isDirectory = isDirectoryStr.startsWith(QLatin1Char('+')); + m_currentArchiveEntry->setProperty("isDirectory", isDirectory); + fixDirectoryFullName(); } else if (line.startsWith(QStringLiteral("Attributes = "))) { const QString attributes = line.mid(13).trimmed(); + if (attributes.contains(QLatin1Char('D'))) { + m_currentArchiveEntry->setProperty("isDirectory", true); + fixDirectoryFullName(); + } - const bool isDirectory = attributes.startsWith(QLatin1Char('D')); - m_currentArchiveEntry->setProperty("isDirectory", isDirectory); - if (isDirectory) { - const QString directoryName = - m_currentArchiveEntry->fullPath(); - if (!directoryName.endsWith(QLatin1Char('/'))) { - const bool isPasswordProtected = (line.at(12) == QLatin1Char('+')); - m_currentArchiveEntry->setProperty("fullPath", QString(directoryName + QLatin1Char('/'))); - m_currentArchiveEntry->setProperty("isPasswordProtected", isPasswordProtected); - } + if (attributes.contains(QLatin1Char('_'))) { + // Unix attributes + m_currentArchiveEntry->setProperty("permissions", + attributes.mid(attributes.indexOf(QLatin1Char(' ')) + 1)); + } else { + // FAT attributes + m_currentArchiveEntry->setProperty("permissions", attributes); } - m_currentArchiveEntry->setProperty("permissions", attributes.mid(1)); } else if (line.startsWith(QStringLiteral("CRC = "))) { m_currentArchiveEntry->setProperty("CRC", line.mid(6).trimmed()); } else if (line.startsWith(QStringLiteral("Method = "))) { m_currentArchiveEntry->setProperty("method", line.mid(9).trimmed()); // For zip archives we need to check method for each entry. if (m_archiveType == ArchiveTypeZip) { QStringList methods = line.section(QLatin1Char('='), 1).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts); handleMethods(methods); } } else if (line.startsWith(QStringLiteral("Encrypted = ")) && line.size() >= 13) { m_currentArchiveEntry->setProperty("isPasswordProtected", line.at(12) == QLatin1Char('+')); } else if (line.startsWith(QStringLiteral("Block = ")) || line.startsWith(QStringLiteral("Version = "))) { m_isFirstInformationEntry = true; if (!m_currentArchiveEntry->fullPath().isEmpty()) { emit entry(m_currentArchiveEntry); } else { delete m_currentArchiveEntry; } m_currentArchiveEntry = nullptr; } } return true; } bool CliPlugin::readExtractLine(const QString &line) { const QRegularExpression rxUnknownError(QStringLiteral("ERROR: E_FAIL")); const QRegularExpression rxBadCRC(QStringLiteral("ERROR: CRC Failed")); if (rxUnknownError.match(line).hasMatch()) { emit error(i18n("Extraction failed due to an unknown error.")); return false; } if (rxBadCRC.match(line).hasMatch()) { emit error(i18n("Extraction failed due to one or more corrupt files. Any extracted files may be damaged.")); return false; } return true; } bool CliPlugin::readDeleteLine(const QString &line) { QRegularExpression rx(QStringLiteral("Error: .+ is not supported archive")); if (rx.match(line).hasMatch()) { emit error(i18n("Delete operation failed. Try upgrading p7zip or disabling the p7zip plugin in the configuration dialog.")); return false; } return true; } void CliPlugin::handleMethods(const QStringList &methods) { foreach (const QString &method, methods) { QRegularExpression rxEncMethod(QStringLiteral("^(7zAES|AES-128|AES-192|AES-256|ZipCrypto)$")); if (rxEncMethod.match(method).hasMatch()) { QRegularExpression rxAESMethods(QStringLiteral("^(AES-128|AES-192|AES-256)$")); if (rxAESMethods.match(method).hasMatch()) { // Remove dash for AES methods. emit encryptionMethodFound(QString(method).remove(QLatin1Char('-'))); } else { emit encryptionMethodFound(method); } continue; } // LZMA methods are output with some trailing numbers by 7z representing dictionary/block sizes. // We are not interested in these, so remove them. if (method.startsWith(QLatin1String("LZMA2"))) { emit compressionMethodFound(method.left(5)); } else if (method.startsWith(QLatin1String("LZMA"))) { emit compressionMethodFound(method.left(4)); } else if (method == QLatin1String("xz")) { emit compressionMethodFound(method.toUpper()); } else { emit compressionMethodFound(method); } } } #include "cliplugin.moc" diff --git a/plugins/cli7zplugin/cliplugin.h b/plugins/cli7zplugin/cliplugin.h index 0daae358..0368a18c 100644 --- a/plugins/cli7zplugin/cliplugin.h +++ b/plugins/cli7zplugin/cliplugin.h @@ -1,70 +1,71 @@ /* * ark -- archiver for the KDE project * * Copyright (C) 2009 Harald Hvaal * Copyright (C) 2009-2010 Raphael Kubo da Costa * Copyright (c) 2016 Vladyslav Batyrenko * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef CLIPLUGIN_H #define CLIPLUGIN_H #include "cliinterface.h" class CliPlugin : public Kerfuffle::CliInterface { Q_OBJECT public: explicit CliPlugin(QObject *parent, const QVariantList & args); ~CliPlugin() override; void resetParsing() override; bool readListLine(const QString &line) override; bool readExtractLine(const QString &line) override; bool readDeleteLine(const QString &line) override; private: enum ArchiveType { ArchiveType7z = 0, ArchiveTypeBZip2, ArchiveTypeGZip, ArchiveTypeXz, ArchiveTypeTar, ArchiveTypeZip, ArchiveTypeRar } m_archiveType; enum ParseState { ParseStateTitle = 0, ParseStateHeader, ParseStateArchiveInformation, ParseStateComment, ParseStateEntryInformation } m_parseState; void setupCliProperties(); void handleMethods(const QStringList &methods); + void fixDirectoryFullName(); int m_linesComment; Kerfuffle::Archive::Entry *m_currentArchiveEntry; bool m_isFirstInformationEntry; }; #endif // CLIPLUGIN_H