diff --git a/CMakeLists.txt b/CMakeLists.txt index ff7d84c..2b11a63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,109 +1,109 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.56.0") # handled by release scripts project(KArchive VERSION ${KF5_VERSION}) include(FeatureSummary) find_package(ECM 5.55.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(GenerateExportHeader) set(REQUIRED_QT_VERSION 5.10.0) find_package(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) find_package(ZLIB) set_package_properties(ZLIB PROPERTIES URL "http://www.zlib.net" DESCRIPTION "Support for gzip compressed files and data streams" TYPE REQUIRED PURPOSE "Required by the core KDE libraries and some critical kioslaves" ) find_package(BZip2) set_package_properties(BZip2 PROPERTIES URL "https://sourceware.org/bzip2/" DESCRIPTION "Support for BZip2 compressed files and data streams" TYPE RECOMMENDED PURPOSE "Support for BZip2 compressed files and data streams" ) find_package(LibLZMA) set_package_properties(LibLZMA PROPERTIES URL "http://tukaani.org/xz/" DESCRIPTION "Support for xz compressed files and data streams" PURPOSE "Support for xz compressed files and data streams" ) include_directories( ${ZLIB_INCLUDE_DIR} ) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMQtDeclareLoggingCategory) include(ECMAddQch) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KARCHIVE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/karchive_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfigVersion.cmake" SOVERSION 5) - +add_definitions(-DQT_NO_FOREACH) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Archive") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Archive_QCH FILE KF5ArchiveQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ArchiveQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5ArchiveConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/karchive_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel) install(EXPORT KF5ArchiveTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5ArchiveTargets.cmake NAMESPACE KF5::) install(FILES karchive.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/karchivetest.cpp b/autotests/karchivetest.cpp index 730084f..085aaa9 100644 --- a/autotests/karchivetest.cpp +++ b/autotests/karchivetest.cpp @@ -1,1459 +1,1459 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 David Faure Copyright (C) 2012 Mario Bensi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "karchivetest.h" #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include // symlink #include #endif #ifdef Q_OS_WIN #include #include #else #include #include #endif QTEST_MAIN(KArchiveTest) void initLocale() { qputenv("LC_ALL", "en_US.UTF-8"); // KArchive uses QFile::decodeName, and our tests use utf8 encoding for filenames } Q_CONSTRUCTOR_FUNCTION(initLocale) static const int SIZE1 = 100; /** * Writes test fileset specified archive * @param archive archive */ static void writeTestFilesToArchive(KArchive *archive) { QVERIFY(archive->writeFile("empty", "", 0100644, "weis", "users")); QVERIFY(archive->writeFile("test1", QByteArray("Hallo"), 0100440, QString("weis"), QString("users"))); // Now let's try with the prepareWriting/writeData/finishWriting API QVERIFY(archive->prepareWriting("test2", "weis", "users", 8)); QVERIFY(archive->writeData("Hallo ", 6)); QVERIFY(archive->writeData("Du", 2)); QVERIFY(archive->finishWriting(8)); // Add local file QFile localFile(QStringLiteral("test3")); QVERIFY(localFile.open(QIODevice::WriteOnly)); QVERIFY(localFile.write("Noch so einer", 13) == 13); localFile.close(); QVERIFY(archive->addLocalFile("test3", "z/test3")); // writeFile API QVERIFY(archive->writeFile("my/dir/test3", "I do not speak German\nDavid.", 0100644, "dfaure", "hackers")); // Now a medium file : 100 null bytes char medium[SIZE1]; memset(medium, 0, SIZE1); QVERIFY(archive->writeFile("mediumfile", QByteArray(medium, SIZE1))); // Another one, with an absolute path QVERIFY(archive->writeFile("/dir/subdir/mediumfile2", QByteArray(medium, SIZE1))); // Now a huge file : 20000 null bytes int n = 20000; char *huge = new char[n]; memset(huge, 0, n); QVERIFY(archive->writeFile("hugefile", QByteArray(huge, n))); delete [] huge; // Now an empty directory QVERIFY(archive->writeDir("aaaemptydir")); #ifndef Q_OS_WIN // Add local symlink QVERIFY(archive->addLocalFile("test3_symlink", "z/test3_symlink")); #endif // Add executable QVERIFY(archive->writeFile("executableAll", "#!/bin/sh\necho hi", 0100755)); } static QString getCurrentUserName() { #if defined(Q_OS_UNIX) struct passwd *pw = getpwuid(getuid()); return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid()); #elif defined(Q_OS_WIN) wchar_t buffer[255]; DWORD size = 255; bool ok = GetUserNameW(buffer, &size); if (!ok) { return QString(); } return QString::fromWCharArray(buffer); #else return QString(); #endif } static QString getCurrentGroupName() { #if defined(Q_OS_UNIX) struct group *grp = getgrgid(getgid()); return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid()); #elif defined(Q_OS_WIN) return QString(); #else return QString(); #endif } enum ListingFlags { WithUserGroup = 1, WithTime = 0x02 }; // ListingFlags static QStringList recursiveListEntries(const KArchiveDirectory *dir, const QString &path, int listingFlags) { QStringList ret; QStringList l = dir->entries(); l.sort(); - Q_FOREACH (const QString &it, l) { + for (const QString &it : qAsConst(l)) { const KArchiveEntry *entry = dir->entry(it); QString descr; descr += QStringLiteral("mode=") + QString::number(entry->permissions(), 8) + ' '; if (listingFlags & WithUserGroup) { descr += QStringLiteral("user=") + entry->user() + ' '; descr += QStringLiteral("group=") + entry->group() + ' '; } descr += QStringLiteral("path=") + path + (it) + ' '; descr += QStringLiteral("type=") + (entry->isDirectory() ? "dir" : "file"); if (entry->isFile()) { descr += QStringLiteral(" size=") + QString::number(static_cast(entry)->size()); } if (!entry->symLinkTarget().isEmpty()) { descr += QStringLiteral(" symlink=") + entry->symLinkTarget(); } if (listingFlags & WithTime) { descr += QStringLiteral(" time=") + entry->date().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss")); } //qDebug() << descr; ret.append(descr); if (entry->isDirectory()) { ret += recursiveListEntries((KArchiveDirectory *)entry, path + it + '/', listingFlags); } } return ret; } /** * Verifies contents of specified archive against test fileset * @param archive archive */ static void testFileData(KArchive *archive) { const KArchiveDirectory *dir = archive->directory(); const KArchiveFile *f = dir->file(QStringLiteral("z/test3")); QByteArray arr(f->data()); QCOMPARE(arr.size(), 13); QCOMPARE(arr, QByteArray("Noch so einer")); // Now test using createDevice() QIODevice *dev = f->createDevice(); QByteArray contents = dev->readAll(); QCOMPARE(contents, arr); delete dev; dev = f->createDevice(); contents = dev->read(5); // test reading in two chunks QCOMPARE(contents.size(), 5); contents += dev->read(50); QCOMPARE(contents.size(), 13); QCOMPARE(QString::fromLatin1(contents.constData()), QString::fromLatin1(arr.constData())); delete dev; // test read/seek/peek work fine f = dir->file(QStringLiteral("test1")); dev = f->createDevice(); contents = dev->peek(4); QCOMPARE(contents, QByteArray("Hall")); contents = dev->peek(2); QCOMPARE(contents, QByteArray("Ha")); dev->seek(2); contents = dev->peek(2); QCOMPARE(contents, QByteArray("ll")); dev->seek(0); contents = dev->read(2); QCOMPARE(contents, QByteArray("Ha")); contents = dev->peek(2); QCOMPARE(contents, QByteArray("ll")); dev->seek(1); contents = dev->read(1); QCOMPARE(contents, QByteArray("a")); dev->seek(4); contents = dev->read(1); QCOMPARE(contents, QByteArray("o")); const KArchiveEntry *e = dir->entry(QStringLiteral("mediumfile")); QVERIFY(e && e->isFile()); f = (KArchiveFile *)e; QCOMPARE(f->data().size(), SIZE1); f = dir->file(QStringLiteral("hugefile")); QCOMPARE(f->data().size(), 20000); e = dir->entry(QStringLiteral("aaaemptydir")); QVERIFY(e && e->isDirectory()); QVERIFY(!dir->file("aaaemptydir")); e = dir->entry(QStringLiteral("my/dir/test3")); QVERIFY(e && e->isFile()); f = (KArchiveFile *)e; dev = f->createDevice(); QByteArray firstLine = dev->readLine(); QCOMPARE(QString::fromLatin1(firstLine.constData()), QString::fromLatin1("I do not speak German\n")); QByteArray secondLine = dev->read(100); QCOMPARE(QString::fromLatin1(secondLine.constData()), QString::fromLatin1("David.")); delete dev; #ifndef Q_OS_WIN e = dir->entry(QStringLiteral("z/test3_symlink")); QVERIFY(e); QVERIFY(e->isFile()); QCOMPARE(e->symLinkTarget(), QString("test3")); #endif // Test "./" prefix for KOffice (xlink:href="./ObjectReplacements/Object 1") e = dir->entry(QStringLiteral("./hugefile")); QVERIFY(e && e->isFile()); e = dir->entry(QStringLiteral("./my/dir/test3")); QVERIFY(e && e->isFile()); // Test directory entries e = dir->entry(QStringLiteral("my")); QVERIFY(e && e->isDirectory()); e = dir->entry(QStringLiteral("my/")); QVERIFY(e && e->isDirectory()); e = dir->entry(QStringLiteral("./my/")); QVERIFY(e && e->isDirectory()); } static void testReadWrite(KArchive *archive) { QVERIFY(archive->writeFile("newfile", "New File", 0100440, "dfaure", "users")); } #ifdef Q_OS_WIN extern Q_CORE_EXPORT int qt_ntfs_permission_lookup; #endif static void testCopyTo(KArchive *archive) { const KArchiveDirectory *dir = archive->directory(); QTemporaryDir tmpDir; const QString dirName = tmpDir.path() + '/'; QVERIFY(dir->copyTo(dirName)); QVERIFY(QFile::exists(dirName + "dir")); QVERIFY(QFileInfo(dirName + "dir").isDir()); QFileInfo fileInfo1(dirName + "dir/subdir/mediumfile2"); QVERIFY(fileInfo1.exists()); QVERIFY(fileInfo1.isFile()); QCOMPARE(fileInfo1.size(), Q_INT64_C(100)); QFileInfo fileInfo2(dirName + "hugefile"); QVERIFY(fileInfo2.exists()); QVERIFY(fileInfo2.isFile()); QCOMPARE(fileInfo2.size(), Q_INT64_C(20000)); QFileInfo fileInfo3(dirName + "mediumfile"); QVERIFY(fileInfo3.exists()); QVERIFY(fileInfo3.isFile()); QCOMPARE(fileInfo3.size(), Q_INT64_C(100)); QFileInfo fileInfo4(dirName + "my/dir/test3"); QVERIFY(fileInfo4.exists()); QVERIFY(fileInfo4.isFile()); QCOMPARE(fileInfo4.size(), Q_INT64_C(28)); #ifndef Q_OS_WIN const QString fileName = dirName + "z/test3_symlink"; const QFileInfo fileInfo5(fileName); QVERIFY(fileInfo5.exists()); QVERIFY(fileInfo5.isFile()); // Do not use fileInfo.readLink() for unix symlinks // It returns the -full- path to the target, while we want the target string "as is". QString symLinkTarget; const QByteArray encodedFileName = QFile::encodeName(fileName); QByteArray s; #if defined(PATH_MAX) s.resize(PATH_MAX + 1); #else int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX); if (path_max <= 0) { path_max = 4096; } s.resize(path_max); #endif int len = readlink(encodedFileName.data(), s.data(), s.size() - 1); if (len >= 0) { s[len] = '\0'; symLinkTarget = QFile::decodeName(s.constData()); } QCOMPARE(symLinkTarget, QString("test3")); #endif #ifdef Q_OS_WIN QScopedValueRollback ntfsMode(qt_ntfs_permission_lookup); qt_ntfs_permission_lookup++; #endif QVERIFY(QFileInfo(dirName + "executableAll").permissions() & (QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther)); } /** * Prepares dataset for archive filter tests */ void KArchiveTest::setupData() { QTest::addColumn("fileName"); QTest::addColumn("mimeType"); QTest::newRow(".tar.gz") << "karchivetest.tar.gz" << "application/x-gzip"; #if HAVE_BZIP2_SUPPORT QTest::newRow(".tar.bz2") << "karchivetest.tar.bz2" << "application/x-bzip"; #endif #if HAVE_XZ_SUPPORT QTest::newRow(".tar.lzma") << "karchivetest.tar.lzma" << "application/x-lzma"; QTest::newRow(".tar.xz") << "karchivetest.tar.xz" << "application/x-xz"; #endif } /** * @see QTest::initTestCase() */ void KArchiveTest::initTestCase() { #ifndef Q_OS_WIN // Prepare local symlink QFile::remove(QStringLiteral("test3_symlink")); if (::symlink("test3", "test3_symlink") != 0) { qDebug() << errno; QVERIFY(false); } #endif } void KArchiveTest::testEmptyFilename() { QTest::ignoreMessage(QtWarningMsg, "KArchive: No file name specified"); KTar tar(QLatin1String("")); QVERIFY(!tar.open(QIODevice::ReadOnly)); QCOMPARE(tar.errorString(), tr("No filename or device was specified")); } void KArchiveTest::testNullDevice() { QIODevice *nil = nullptr; QTest::ignoreMessage(QtWarningMsg, "KArchive: Null device specified"); KTar tar(nil); QVERIFY(!tar.open(QIODevice::ReadOnly)); QCOMPARE(tar.errorString(), tr("No filename or device was specified")); } void KArchiveTest::testNonExistentFile() { KTar tar(QStringLiteral("nonexistent.tar.gz")); QVERIFY(!tar.open(QIODevice::ReadOnly)); QCOMPARE(tar.errorString(), tr("File %1 does not exist").arg("nonexistent.tar.gz")); } void KArchiveTest::testCreateTar_data() { QTest::addColumn("fileName"); QTest::newRow(".tar") << "karchivetest.tar"; } /** * @dataProvider testCreateTar_data */ void KArchiveTest::testCreateTar() { QFETCH(QString, fileName); // With tempfile: 0.7-0.8 ms, 994236 instr. loads // Without tempfile: 0.81 ms, 992541 instr. loads // Note: use ./karchivetest 2>&1 | grep ms // to avoid being slowed down by the kDebugs. QBENCHMARK { KTar tar(fileName); QVERIFY(tar.open(QIODevice::WriteOnly)); writeTestFilesToArchive(&tar); QVERIFY(tar.close()); QFileInfo fileInfo(QFile::encodeName(fileName)); QVERIFY(fileInfo.exists()); // We can't check for an exact size because of the addLocalFile, whose data is system-dependent QVERIFY(fileInfo.size() > 450); } // NOTE The only .tar test, cleanup here //QFile::remove(fileName); } /** * @dataProvider setupData */ void KArchiveTest::testCreateTarXXX() { QFETCH(QString, fileName); // With tempfile: 1.3-1.7 ms, 2555089 instr. loads // Without tempfile: 0.87 ms, 987915 instr. loads QBENCHMARK { KTar tar(fileName); QVERIFY(tar.open(QIODevice::WriteOnly)); writeTestFilesToArchive(&tar); QVERIFY(tar.close()); QFileInfo fileInfo(QFile::encodeName(fileName)); QVERIFY(fileInfo.exists()); // We can't check for an exact size because of the addLocalFile, whose data is system-dependent QVERIFY(fileInfo.size() > 350); } } //static void compareEntryWithTimestamp(const QString &dateString, const QString &expectedDateString, const QDateTime &expectedDateTime) // Made it a macro so that line numbers are meaningful on failure #define compareEntryWithTimestamp(dateString, expectedDateString, expectedDateTime) \ { \ /* Take the time from the listing and chop it off */ \ const QDateTime dt = QDateTime::fromString(dateString.right(19), "dd.MM.yyyy hh:mm:ss"); \ QString _str(dateString); \ _str.chop(25); \ QCOMPARE(_str, expectedDateString); \ \ /* Compare the times separately with allowed 2 sec diversion */ \ if (dt.secsTo(expectedDateTime) > 2) { qWarning() << dt << "is too different from" << expectedDateTime; } \ QVERIFY(dt.secsTo(expectedDateTime) <= 2); \ } /** * @dataProvider setupData */ void KArchiveTest::testReadTar() // testCreateTarGz must have been run first. { QFETCH(QString, fileName); QFileInfo localFileData(QStringLiteral("test3")); const QString systemUserName = getCurrentUserName(); const QString systemGroupName = getCurrentGroupName(); const QString owner = localFileData.owner(); const QString group = localFileData.group(); const QString emptyTime = QDateTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss")); const QDateTime creationTime = QFileInfo(fileName).created(); // 1.6-1.7 ms per interaction, 2908428 instruction loads // After the "no tempfile when writing fix" this went down // to 0.9-1.0 ms, 1689059 instruction loads. // I guess it finds the data in the kernel cache now that no KTempFile is // used when writing. QBENCHMARK { KTar tar(fileName); QVERIFY(tar.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup | WithTime); #ifndef Q_OS_WIN const int expectedCount = 16; #else const int expectedCount = 15; #endif if (listing.count() != expectedCount) { qWarning() << listing; } QCOMPARE(listing.count(), expectedCount); compareEntryWithTimestamp(listing[0], QString("mode=40755 user= group= path=aaaemptydir type=dir"), creationTime); QCOMPARE(listing[1], QString("mode=40777 user=%1 group=%2 path=dir type=dir time=%3").arg(systemUserName).arg(systemGroupName).arg(emptyTime)); QCOMPARE(listing[2], QString("mode=40777 user=%1 group=%2 path=dir/subdir type=dir time=%3").arg(systemUserName).arg(systemGroupName).arg(emptyTime)); compareEntryWithTimestamp(listing[3], QString("mode=100644 user= group= path=dir/subdir/mediumfile2 type=file size=100"), creationTime); compareEntryWithTimestamp(listing[4], QString("mode=100644 user=weis group=users path=empty type=file size=0"), creationTime); compareEntryWithTimestamp(listing[5], QString("mode=100755 user= group= path=executableAll type=file size=17"), creationTime); compareEntryWithTimestamp(listing[6], QString("mode=100644 user= group= path=hugefile type=file size=20000"), creationTime); compareEntryWithTimestamp(listing[7], QString("mode=100644 user= group= path=mediumfile type=file size=100"), creationTime); QCOMPARE(listing[8], QString("mode=40777 user=%1 group=%2 path=my type=dir time=").arg(systemUserName).arg(systemGroupName)); QCOMPARE(listing[9], QString("mode=40777 user=%1 group=%2 path=my/dir type=dir time=").arg(systemUserName).arg(systemGroupName)); compareEntryWithTimestamp(listing[10], QString("mode=100644 user=dfaure group=hackers path=my/dir/test3 type=file size=28"), creationTime); compareEntryWithTimestamp(listing[11], QString("mode=100440 user=weis group=users path=test1 type=file size=5"), creationTime); compareEntryWithTimestamp(listing[12], QString("mode=100644 user=weis group=users path=test2 type=file size=8"), creationTime); QCOMPARE(listing[13], QString("mode=40777 user=%1 group=%2 path=z type=dir time=").arg(systemUserName).arg(systemGroupName)); // This one was added with addLocalFile, so ignore mode. QString str = listing[14]; str.replace(QRegExp(QStringLiteral("mode.*user=")), QStringLiteral("user=")); compareEntryWithTimestamp(str, QString("user=%1 group=%2 path=z/test3 type=file size=13").arg(owner).arg(group), creationTime); #ifndef Q_OS_WIN str = listing[15]; str.replace(QRegExp(QStringLiteral("mode.*path=")), QStringLiteral("path=")); compareEntryWithTimestamp(str, QString("path=z/test3_symlink type=file size=0 symlink=test3"), creationTime); #endif QVERIFY(tar.close()); } } /** * This tests the decompression using kfilterdev, basically. * To debug KTarPrivate::fillTempFile(). * * @dataProvider setupData */ void KArchiveTest::testUncompress() { QFETCH(QString, fileName); QFETCH(QString, mimeType); // testCreateTar must have been run first. QVERIFY(QFile::exists(fileName)); KFilterDev filterDev(fileName); QByteArray buffer; buffer.resize(8 * 1024); //qDebug() << "buffer.size()=" << buffer.size(); QVERIFY(filterDev.open(QIODevice::ReadOnly)); qint64 totalSize = 0; qint64 len = -1; while (!filterDev.atEnd() && len != 0) { len = filterDev.read(buffer.data(), buffer.size()); QVERIFY(len >= 0); totalSize += len; // qDebug() << "read len=" << len << " totalSize=" << totalSize; } filterDev.close(); // qDebug() << "totalSize=" << totalSize; QVERIFY(totalSize > 26000); // 27648 here when using gunzip } /** * @dataProvider setupData */ void KArchiveTest::testTarFileData() { QFETCH(QString, fileName); // testCreateTar must have been run first. KTar tar(fileName); QVERIFY(tar.open(QIODevice::ReadOnly)); testFileData(&tar); QVERIFY(tar.close()); } /** * @dataProvider setupData */ void KArchiveTest::testTarCopyTo() { QFETCH(QString, fileName); // testCreateTar must have been run first. KTar tar(fileName); QVERIFY(tar.open(QIODevice::ReadOnly)); testCopyTo(&tar); QVERIFY(tar.close()); } /** * @dataProvider setupData */ void KArchiveTest::testTarReadWrite() { QFETCH(QString, fileName); // testCreateTar must have been run first. KTar tar(fileName); QVERIFY(tar.open(QIODevice::ReadWrite)); testReadWrite(&tar); testFileData(&tar); QVERIFY(tar.close()); // Reopen it and check it { KTar tar(fileName); QVERIFY(tar.open(QIODevice::ReadOnly)); testFileData(&tar); const KArchiveDirectory *dir = tar.directory(); const KArchiveEntry *e = dir->entry(QStringLiteral("newfile")); QVERIFY(e && e->isFile()); const KArchiveFile *f = (KArchiveFile *)e; QCOMPARE(f->data().size(), 8); } // NOTE This is the last test for this dataset. so cleanup here QFile::remove(fileName); } void KArchiveTest::testTarMaxLength_data() { QTest::addColumn("fileName"); QTest::newRow("maxlength.tar.gz") << "karchivetest-maxlength.tar.gz"; } /** * @dataProvider testTarMaxLength_data */ void KArchiveTest::testTarMaxLength() { QFETCH(QString, fileName); KTar tar(fileName); QVERIFY(tar.open(QIODevice::WriteOnly)); // Generate long filenames of each possible length bigger than 98... // Also exceed 512 byte block size limit to see how well the ././@LongLink // implementation fares for (int i = 98; i < 514; i++) { QString str, num; str.fill('a', i - 10); num.setNum(i); num = num.rightJustified(10, '0'); tar.writeFile(str + num, "hum"); } // Result of this test : works perfectly now (failed at 482 formerly and // before that at 154). QVERIFY(tar.close()); QVERIFY(tar.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup); QCOMPARE(listing[0], QString("mode=100644 user= group= path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3")); QCOMPARE(listing[3], QString("mode=100644 user= group= path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3")); QCOMPARE(listing[4], QString("mode=100644 user= group= path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3")); QCOMPARE(listing.count(), 416); QVERIFY(tar.close()); // NOTE Cleanup here QFile::remove(fileName); } void KArchiveTest::testTarGlobalHeader() { KTar tar(QFINDTESTDATA(QLatin1String("global_header_test.tar.gz"))); QVERIFY2(tar.open(QIODevice::ReadOnly), "global_header_test.tar.gz"); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup); QCOMPARE(listing.count(), 2); QCOMPARE(listing[0], QString("mode=40775 user=root group=root path=Test type=dir")); QCOMPARE(listing[1], QString("mode=664 user=root group=root path=Test/test.txt type=file size=0")); QVERIFY(tar.close()); } void KArchiveTest::testTarPrefix() { KTar tar(QFINDTESTDATA(QLatin1String("tar_prefix_test.tar.gz"))); QVERIFY2(tar.open(QIODevice::ReadOnly), "tar_prefix_test.tar.gz"); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup); QCOMPARE(listing[0], QString("mode=40775 user=root group=root path=Test type=dir")); QCOMPARE(listing[1], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7 type=dir")); QCOMPARE(listing[2], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples type=dir")); QCOMPARE(listing[3], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator type=dir")); QCOMPARE(listing[4], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original type=dir")); QCOMPARE(listing[5], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java type=dir")); QCOMPARE(listing[6], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com type=dir")); QCOMPARE(listing[7], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech type=dir")); QCOMPARE(listing[8], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples type=dir")); QCOMPARE(listing[9], QString("mode=664 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples/GeneratorExample.html type=file size=43086")); QCOMPARE(listing.count(), 10); QVERIFY(tar.close()); } void KArchiveTest::testTarDirectoryForgotten() { KTar tar(QFINDTESTDATA(QLatin1String("tar_directory_forgotten.tar.gz"))); QVERIFY2(tar.open(QIODevice::ReadOnly), "tar_directory_forgotten.tar.gz"); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup); QVERIFY(listing[9].contains("trolltech/examples/generator")); QVERIFY(listing[10].contains("trolltech/examples/generator/GeneratorExample.html")); QCOMPARE(listing.count(), 11); QVERIFY(tar.close()); } void KArchiveTest::testTarRootDir() // bug 309463 { KTar tar(QFINDTESTDATA(QLatin1String("tar_rootdir.tar.gz"))); QVERIFY2(tar.open(QIODevice::ReadOnly), qPrintable(tar.fileName())); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup); //qDebug() << listing.join("\n"); QVERIFY(listing[0].contains("%{APPNAME}.cpp")); QVERIFY(listing[1].contains("%{APPNAME}.h")); QVERIFY(listing[5].contains("main.cpp")); QCOMPARE(listing.count(), 10); } void KArchiveTest::testTarLongNonASCIINames() // bug 266141 { const QString tarName = QString("karchive-long-non-ascii-names.tar"); const QString longName = QString("раз-два-три-четыре-пять-вышел-зайчик-погулять-вдруг-охотник-" "выбегает-прямо-в-зайчика.txt"); { KTar tar(tarName); QVERIFY(tar.open(QIODevice::WriteOnly)); QVERIFY(tar.writeFile(longName, "", 0644, "user", "users")); QVERIFY(tar.close()); } { KTar tar(tarName); QVERIFY(tar.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QString(""), 0); const QString expectedListingEntry = QString("mode=644 path=") + longName + QString(" type=file size=0"); QCOMPARE(listing.count(), 1); QCOMPARE(listing[0], expectedListingEntry); QVERIFY(tar.close()); } } void KArchiveTest::testTarShortNonASCIINames() // bug 266141 { KTar tar(QFINDTESTDATA(QString("tar_non_ascii_file_name.tar.gz"))); QVERIFY(tar.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QString(""), 0); QCOMPARE(listing.count(), 1); QCOMPARE(listing[0], QString("mode=644 path=абвгдеёжзийклмнопрстуфхцчшщъыьэюя.txt" " type=file size=0")); QVERIFY(tar.close()); } void KArchiveTest::testTarDirectoryTwice() // bug 206994 { KTar tar(QFINDTESTDATA(QLatin1String("tar_directory_twice.tar.gz"))); QVERIFY(tar.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = tar.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup); //qDebug() << listing.join("\n"); QVERIFY(listing[0].contains("path=d")); QVERIFY(listing[1].contains("path=d/f1.txt")); QVERIFY(listing[2].contains("path=d/f2.txt")); QCOMPARE(listing.count(), 3); } void KArchiveTest::testTarIgnoreRelativePathOutsideArchive() { #if HAVE_BZIP2_SUPPORT // This test extracts a Tar archive that contains a relative path "../foo" pointing // outside of the archive directory. For security reasons extractions should only // be allowed within the extracted directory as long as not specifically asked. KTar tar(QFINDTESTDATA(QLatin1String("tar_relative_path_outside_archive.tar.bz2"))); QVERIFY(tar.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = tar.directory(); QTemporaryDir tmpDir; const QString dirName = tmpDir.path() + "/subdir"; // use a subdir so /tmp/foo doesn't break the test :) QDir().mkdir(dirName); QVERIFY(dir->copyTo(dirName)); QVERIFY(!QFile::exists(dirName + "../foo")); QVERIFY(QFile::exists(dirName + "/foo")); #else QSKIP("Test data is in bz2 format and karchive is built without bzip2 format"); #endif } /// static const char s_zipFileName[] = "karchivetest.zip"; static const char s_zipMaxLengthFileName[] = "karchivetest-maxlength.zip"; static const char s_zipLocaleFileName[] = "karchivetest-locale.zip"; static const char s_zipMimeType[] = "application/vnd.oasis.opendocument.text"; void KArchiveTest::testCreateZip() { KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::WriteOnly)); zip.setExtraField(KZip::NoExtraField); zip.setCompression(KZip::NoCompression); QByteArray zipMimeType(s_zipMimeType); zip.writeFile(QStringLiteral("mimetype"), zipMimeType); zip.setCompression(KZip::DeflateCompression); writeTestFilesToArchive(&zip); QVERIFY(zip.close()); QFile zipFile(QFile::encodeName(s_zipFileName)); QFileInfo fileInfo(zipFile); QVERIFY(fileInfo.exists()); QVERIFY(fileInfo.size() > 300); // Check that the header with no-compression and no-extrafield worked. // (This is for the "magic" for koffice documents) QVERIFY(zipFile.open(QIODevice::ReadOnly)); QByteArray arr = zipFile.read(4); QCOMPARE(arr, QByteArray("PK\003\004")); QVERIFY(zipFile.seek(30)); arr = zipFile.read(8); QCOMPARE(arr, QByteArray("mimetype")); arr = zipFile.read(zipMimeType.size()); QCOMPARE(arr, zipMimeType); } void KArchiveTest::testCreateZipError() { // Giving a directory name to kzip must give an error case in close(), see #136630. // Otherwise we just lose data. KZip zip(QDir::currentPath()); QVERIFY(!zip.open(QIODevice::WriteOnly)); QCOMPARE( zip.errorString(), tr("QSaveFile creation for %1 failed: Filename refers to a directory") .arg(QDir::currentPath())); } void KArchiveTest::testReadZipError() { QFile brokenZip(QStringLiteral("broken.zip")); QVERIFY(brokenZip.open(QIODevice::WriteOnly)); // incomplete magic brokenZip.write(QByteArray("PK\003")); brokenZip.close(); { KZip zip(QStringLiteral("broken.zip")); QVERIFY(!zip.open(QIODevice::ReadOnly)); QCOMPARE( zip.errorString(), tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(1)); QVERIFY(brokenZip.open(QIODevice::WriteOnly | QIODevice::Append)); // add rest of magic, but still incomplete header brokenZip.write(QByteArray("\004\000\000\000\000")); brokenZip.close(); QVERIFY(!zip.open(QIODevice::ReadOnly)); QCOMPARE( zip.errorString(), tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(4)); } QVERIFY(brokenZip.remove()); } void KArchiveTest::testReadZip() { // testCreateZip must have been run first. KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QVERIFY(dir != nullptr); // ZIP has no support for per-file user/group, so omit them from the listing const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); #ifndef Q_OS_WIN QCOMPARE(listing.count(), 17); #else QCOMPARE(listing.count(), 16); #endif QCOMPARE(listing[0], QString("mode=40755 path=aaaemptydir type=dir")); QCOMPARE(listing[1], QString("mode=40777 path=dir type=dir")); QCOMPARE(listing[2], QString("mode=40777 path=dir/subdir type=dir")); QCOMPARE(listing[3], QString("mode=100644 path=dir/subdir/mediumfile2 type=file size=100")); QCOMPARE(listing[4], QString("mode=100644 path=empty type=file size=0")); QCOMPARE(listing[5], QString("mode=100755 path=executableAll type=file size=17")); QCOMPARE(listing[6], QString("mode=100644 path=hugefile type=file size=20000")); QCOMPARE(listing[7], QString("mode=100644 path=mediumfile type=file size=100")); QCOMPARE(listing[8], QString("mode=100644 path=mimetype type=file size=%1").arg(strlen(s_zipMimeType))); QCOMPARE(listing[9], QString("mode=40777 path=my type=dir")); QCOMPARE(listing[10], QString("mode=40777 path=my/dir type=dir")); QCOMPARE(listing[11], QString("mode=100644 path=my/dir/test3 type=file size=28")); QCOMPARE(listing[12], QString("mode=100440 path=test1 type=file size=5")); QCOMPARE(listing[13], QString("mode=100644 path=test2 type=file size=8")); QCOMPARE(listing[14], QString("mode=40777 path=z type=dir")); // This one was added with addLocalFile, so ignore mode QString str = listing[15]; str.replace(QRegExp(QStringLiteral("mode.*path=")), QStringLiteral("path=")); QCOMPARE(str, QString("path=z/test3 type=file size=13")); #ifndef Q_OS_WIN str = listing[16]; str.replace(QRegExp(QStringLiteral("mode.*path=")), QStringLiteral("path=")); QCOMPARE(str, QString("path=z/test3_symlink type=file size=5 symlink=test3")); #endif QVERIFY(zip.close()); } void KArchiveTest::testZipFileData() { // testCreateZip must have been run first. KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::ReadOnly)); testFileData(&zip); QVERIFY(zip.close()); } void KArchiveTest::testZipCopyTo() { // testCreateZip must have been run first. KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::ReadOnly)); testCopyTo(&zip); QVERIFY(zip.close()); } void KArchiveTest::testZipMaxLength() { KZip zip(s_zipMaxLengthFileName); QVERIFY(zip.open(QIODevice::WriteOnly)); // Similar to testTarMaxLength just to make sure, but of course zip doesn't have // those limitations in the first place. for (int i = 98; i < 514; i++) { QString str, num; str.fill('a', i - 10); num.setNum(i); num = num.rightJustified(10, '0'); zip.writeFile(str + num, "hum"); } QVERIFY(zip.close()); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); QCOMPARE(listing[0], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3")); QCOMPARE(listing[3], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3")); QCOMPARE(listing[4], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3")); QCOMPARE(listing.count(), 514 - 98); QVERIFY(zip.close()); } void KArchiveTest::testZipWithNonLatinFileNames() { KZip zip(s_zipLocaleFileName); QVERIFY(zip.open(QIODevice::WriteOnly)); const QByteArray fileData("Test of data with a russian file name"); const QString fileName = QStringLiteral("Архитектура.okular"); const QString recodedFileName = QFile::decodeName(QFile::encodeName(fileName)); QVERIFY(zip.writeFile(fileName, fileData)); QVERIFY(zip.close()); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); QCOMPARE(listing.count(), 1); QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(recodedFileName).arg(fileData.size())); const KArchiveFile *fileEntry = static_cast< const KArchiveFile *>(dir->entry(dir->entries()[0])); QCOMPARE(fileEntry->data(), fileData); } void KArchiveTest::testZipWithOverwrittenFileName() { KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::WriteOnly)); const QByteArray fileData1("There could be a fire, if there is smoke."); const QString fileName = QStringLiteral("wisdom"); QVERIFY(zip.writeFile(fileName, fileData1, 0100644, "konqi", "dragons")); // now overwrite it const QByteArray fileData2("If there is smoke, there could be a fire."); QVERIFY(zip.writeFile(fileName, fileData2, 0100644, "konqi", "dragons")); QVERIFY(zip.close()); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); QCOMPARE(listing.count(), 1); QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(fileName).arg(fileData2.size())); const KArchiveFile *fileEntry = static_cast< const KArchiveFile *>(dir->entry(dir->entries()[0])); QCOMPARE(fileEntry->data(), fileData2); } static bool writeFile(const QString &dirName, const QString &fileName, const QByteArray &data) { Q_ASSERT(dirName.endsWith('/')); QFile file(dirName + fileName); if (!file.open(QIODevice::WriteOnly)) { return false; } file.write(data); return true; } void KArchiveTest::testZipAddLocalDirectory() { // Prepare local dir QTemporaryDir tmpDir; const QString dirName = tmpDir.path() + '/'; const QByteArray file1Data = "Hello Shantanu"; const QString file1 = QStringLiteral("file1"); QVERIFY(writeFile(dirName, file1, file1Data)); { KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::WriteOnly)); QVERIFY(zip.addLocalDirectory(dirName, ".")); QVERIFY(zip.close()); } { KZip zip(s_zipFileName); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QVERIFY(dir != nullptr); const KArchiveEntry *e = dir->entry(file1); QVERIFY(e && e->isFile()); const KArchiveFile *f = (KArchiveFile *)e; QCOMPARE(f->data(), file1Data); } } void KArchiveTest::testZipReadRedundantDataDescriptor_data() { QTest::addColumn("fileName"); QTest::newRow("noSignature") << "data/redundantDataDescriptorsNoSignature.zip"; QTest::newRow("withSignature") << "data/redundantDataDescriptorsWithSignature.zip"; } /** * @dataProvider testZipReadRedundantDataDescriptor_data */ void KArchiveTest::testZipReadRedundantDataDescriptor() { QFETCH(QString, fileName); const QString redundantDataDescriptorZipFileName = QFINDTESTDATA(fileName); QVERIFY(!redundantDataDescriptorZipFileName.isEmpty()); KZip zip(redundantDataDescriptorZipFileName); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QVERIFY(dir != nullptr); const QByteArray fileData("aaaaaaaaaaaaaaa"); // ZIP has no support for per-file user/group, so omit them from the listing const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); QCOMPARE(listing.count(), 2); QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=compressed type=file size=%2").arg(fileData.size())); QCOMPARE(listing[1], QString::fromUtf8("mode=100644 path=uncompressed type=file size=%2").arg(fileData.size())); const KArchiveFile *fileEntry = static_cast< const KArchiveFile *>(dir->entry(dir->entries()[0])); QCOMPARE(fileEntry->data(), fileData); fileEntry = static_cast< const KArchiveFile *>(dir->entry(dir->entries()[1])); QCOMPARE(fileEntry->data(), fileData); } void KArchiveTest::testZipDirectoryPermissions() { QString fileName = QFINDTESTDATA("data/dirpermissions.zip"); QVERIFY(!fileName.isEmpty()); KZip zip(fileName); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); const QStringList listing = recursiveListEntries(dir, QString(), 0); QCOMPARE(listing.join(' '), QString::fromUtf8("mode=40700 path=700 type=dir mode=40750 path=750 type=dir mode=40755 path=755 type=dir")); } void KArchiveTest::testZipUnusualButValid() { QString fileName = QFINDTESTDATA("data/unusual_but_valid_364071.zip"); QVERIFY(!fileName.isEmpty()); KZip zip(fileName); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); const QStringList listing = recursiveListEntries(dir, QString(), 0); QCOMPARE(listing.join(' '), QLatin1String("mode=40744 path=test type=dir mode=744 path=test/os-release type=file size=199")); } void KArchiveTest::testZipDuplicateNames() { QString fileName = QFINDTESTDATA("data/out.epub"); QVERIFY(!fileName.isEmpty()); KZip zip(fileName); QVERIFY(zip.open(QIODevice::ReadOnly)); int metaInfCount = 0; for (const QString &entryName : zip.directory()->entries()) { if (entryName.startsWith("META-INF")) { metaInfCount++; } } QVERIFY2(metaInfCount == 1, "Archive root directory contains duplicates"); } void KArchiveTest::testRcc() { const QString rccFile = QFINDTESTDATA("runtime_resource.rcc"); // was copied from qtbase/tests/auto/corelib/io/qresourceengine QVERIFY(!rccFile.isEmpty()); KRcc rcc(rccFile); QVERIFY(rcc.open(QIODevice::ReadOnly)); const KArchiveDirectory *rootDir = rcc.directory(); QVERIFY(rootDir != nullptr); const KArchiveEntry *rrEntry = rootDir->entry(QStringLiteral("runtime_resource")); QVERIFY(rrEntry && rrEntry->isDirectory()); const KArchiveDirectory *rrDir = static_cast(rrEntry); const KArchiveEntry *fileEntry = rrDir->entry(QStringLiteral("search_file.txt")); QVERIFY(fileEntry && fileEntry->isFile()); const KArchiveFile *searchFile = static_cast(fileEntry); const QByteArray fileData = searchFile->data(); QCOMPARE(QString::fromLatin1(fileData), QString::fromLatin1("root\n")); } /** * @see QTest::cleanupTestCase() */ void KArchiveTest::cleanupTestCase() { QFile::remove(s_zipMaxLengthFileName); QFile::remove(s_zipFileName); QFile::remove(s_zipLocaleFileName); #ifndef Q_OS_WIN QFile::remove(QStringLiteral("test3_symlink")); #endif } /// #if HAVE_XZ_SUPPORT /** * Prepares dataset for archive filter tests */ void KArchiveTest::setup7ZipData() { QTest::addColumn("fileName"); QTest::newRow(".7z") << "karchivetest.7z"; } /** * @dataProvider testCreate7Zip_data */ void KArchiveTest::testCreate7Zip() { QFETCH(QString, fileName); QBENCHMARK { K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::WriteOnly)); writeTestFilesToArchive(&k7zip); QVERIFY(k7zip.close()); QFileInfo fileInfo(QFile::encodeName(fileName)); QVERIFY(fileInfo.exists()); //qDebug() << "fileInfo.size()" << fileInfo.size(); // We can't check for an exact size because of the addLocalFile, whose data is system-dependent QVERIFY(fileInfo.size() > 390); } } /** * @dataProvider setupData */ void KArchiveTest::testRead7Zip() // testCreate7Zip must have been run first. { QFETCH(QString, fileName); QBENCHMARK { K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = k7zip.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); #ifndef Q_OS_WIN QCOMPARE(listing.count(), 16); #else QCOMPARE(listing.count(), 15); #endif QCOMPARE(listing[0], QString("mode=40755 path=aaaemptydir type=dir")); QCOMPARE(listing[1], QString("mode=40777 path=dir type=dir")); QCOMPARE(listing[2], QString("mode=40777 path=dir/subdir type=dir")); QCOMPARE(listing[3], QString("mode=100644 path=dir/subdir/mediumfile2 type=file size=100")); QCOMPARE(listing[4], QString("mode=100644 path=empty type=file size=0")); QCOMPARE(listing[5], QString("mode=100755 path=executableAll type=file size=17")); QCOMPARE(listing[6], QString("mode=100644 path=hugefile type=file size=20000")); QCOMPARE(listing[7], QString("mode=100644 path=mediumfile type=file size=100")); QCOMPARE(listing[8], QString("mode=40777 path=my type=dir")); QCOMPARE(listing[9], QString("mode=40777 path=my/dir type=dir")); QCOMPARE(listing[10], QString("mode=100644 path=my/dir/test3 type=file size=28")); QCOMPARE(listing[11], QString("mode=100440 path=test1 type=file size=5")); QCOMPARE(listing[12], QString("mode=100644 path=test2 type=file size=8")); QCOMPARE(listing[13], QString("mode=40777 path=z type=dir")); // This one was added with addLocalFile, so ignore mode/user/group. QString str = listing[14]; str.replace(QRegExp(QStringLiteral("mode.*path=")), QStringLiteral("path=")); QCOMPARE(str, QString("path=z/test3 type=file size=13")); #ifndef Q_OS_WIN str = listing[15]; str.replace(QRegExp(QStringLiteral("mode.*path=")), QStringLiteral("path=")); QCOMPARE(str, QString("path=z/test3_symlink type=file size=0 symlink=test3")); #endif QVERIFY(k7zip.close()); } } /** * @dataProvider setupData */ void KArchiveTest::test7ZipFileData() { QFETCH(QString, fileName); // testCreate7Zip must have been run first. K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::ReadOnly)); testFileData(&k7zip); QVERIFY(k7zip.close()); } /** * @dataProvider setupData */ void KArchiveTest::test7ZipCopyTo() { QFETCH(QString, fileName); // testCreateTar must have been run first. K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::ReadOnly)); testCopyTo(&k7zip); QVERIFY(k7zip.close()); } /** * @dataProvider setupData */ void KArchiveTest::test7ZipReadWrite() { QFETCH(QString, fileName); // testCreate7zip must have been run first. K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::ReadWrite)); testReadWrite(&k7zip); testFileData(&k7zip); QVERIFY(k7zip.close()); // Reopen it and check it { K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::ReadOnly)); testFileData(&k7zip); const KArchiveDirectory *dir = k7zip.directory(); const KArchiveEntry *e = dir->entry(QStringLiteral("newfile")); QVERIFY(e && e->isFile()); const KArchiveFile *f = (KArchiveFile *)e; QCOMPARE(f->data().size(), 8); } // NOTE This is the last test for this dataset. so cleanup here QFile::remove(fileName); } /** * @dataProvider test7ZipMaxLength_data */ void KArchiveTest::test7ZipMaxLength() { QFETCH(QString, fileName); K7Zip k7zip(fileName); QVERIFY(k7zip.open(QIODevice::WriteOnly)); // Generate long filenames of each possible length bigger than 98... for (int i = 98; i < 514; i++) { QString str, num; str.fill('a', i - 10); num.setNum(i); num = num.rightJustified(10, '0'); k7zip.writeFile(str + num, "hum"); } QVERIFY(k7zip.close()); QVERIFY(k7zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = k7zip.directory(); QVERIFY(dir != nullptr); const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0); QCOMPARE(listing[0], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3")); QCOMPARE(listing[3], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3")); QCOMPARE(listing[4], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3")); QCOMPARE(listing.count(), 416); QVERIFY(k7zip.close()); // NOTE Cleanup here QFile::remove(fileName); } #endif diff --git a/autotests/kcompressiondevicetest.cpp b/autotests/kcompressiondevicetest.cpp index d633d71..12fd095 100644 --- a/autotests/kcompressiondevicetest.cpp +++ b/autotests/kcompressiondevicetest.cpp @@ -1,192 +1,192 @@ /* This file is part of the KDE project Copyright (C) 2015 Luiz Romário Santana Rios This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcompressiondevicetest.h" #include #include #include #include #include #include #include #include #include QTEST_MAIN(KCompressionDeviceTest) static QString archiveFileName(const QString &extension) { return QFINDTESTDATA(QString("kcompressiondevice_test.%1").arg(extension)); } QNetworkReply *KCompressionDeviceTest::getArchive(const QString &extension) { const QString kcompressionTest = archiveFileName(extension); QNetworkReply *r = qnam.get(QNetworkRequest(QUrl::fromLocalFile(kcompressionTest))); QEventLoop l; connect(&qnam, &QNetworkAccessManager::finished, &l, &QEventLoop::quit); l.exec(); return r; } QString KCompressionDeviceTest::formatExtension(KCompressionDevice::CompressionType type) const { switch (type) { case KCompressionDevice::GZip: return "tar.gz"; case KCompressionDevice::BZip2: return "tar.bz2"; case KCompressionDevice::Xz: return "tar.xz"; case KCompressionDevice::None: return QString(); } return QString(); // silence compiler warning } void KCompressionDeviceTest::setDeviceToArchive( QIODevice *d, KCompressionDevice::CompressionType type) { KCompressionDevice *devRawPtr = new KCompressionDevice(d, true, type); archive.reset(new KTar(devRawPtr)); device.reset(devRawPtr); } void KCompressionDeviceTest::testBufferedDevice(KCompressionDevice::CompressionType type) { QNetworkReply *r = getArchive(formatExtension(type)); const QByteArray data = r->readAll(); QVERIFY(!data.isEmpty()); const int expectedSize = QFileInfo(archiveFileName(formatExtension(type))).size(); QVERIFY(expectedSize > 0); QCOMPARE(data.size(), expectedSize); QBuffer *b = new QBuffer; b->setData(data); setDeviceToArchive(b, type); testExtraction(); } void KCompressionDeviceTest::testExtraction() { QTemporaryDir temp; QString oldCurrentDir = QDir::currentPath(); QDir::setCurrent(temp.path()); QVERIFY(archive->open(QIODevice::ReadOnly)); QVERIFY(archive->directory()->copyTo(".")); QVERIFY(QDir("examples").exists()); QVERIFY(QDir("examples/bzip2gzip").exists()); QVERIFY(QDir("examples/helloworld").exists()); QVERIFY(QDir("examples/tarlocalfiles").exists()); QVERIFY(QDir("examples/unzipper").exists()); QVector fileList; fileList << QLatin1String("examples/bzip2gzip/CMakeLists.txt") << QLatin1String("examples/bzip2gzip/main.cpp") << QLatin1String("examples/helloworld/CMakeLists.txt") << QLatin1String("examples/helloworld/helloworld.pro") << QLatin1String("examples/helloworld/main.cpp") << QLatin1String("examples/tarlocalfiles/CMakeLists.txt") << QLatin1String("examples/tarlocalfiles/main.cpp") << QLatin1String("examples/unzipper/CMakeLists.txt") << QLatin1String("examples/unzipper/main.cpp"); - foreach (const QString& s, fileList) { + for (const QString& s : qAsConst(fileList)) { QFileInfo extractedFile(s); QFileInfo sourceFile(QFINDTESTDATA("../" + s)); QVERIFY(extractedFile.exists()); QCOMPARE(extractedFile.size(), sourceFile.size()); } QDir::setCurrent(oldCurrentDir); } void KCompressionDeviceTest::regularKTarUsage() { archive.reset(new KTar(QFINDTESTDATA("kcompressiondevice_test.tar.gz"))); device.reset(); testExtraction(); } void KCompressionDeviceTest::testGZipBufferedDevice() { testBufferedDevice(KCompressionDevice::GZip); } void KCompressionDeviceTest::testBZip2BufferedDevice() { #if HAVE_BZIP2_SUPPORT testBufferedDevice(KCompressionDevice::BZip2); #else QSKIP("This test needs bzip2 support"); #endif } void KCompressionDeviceTest::testXzBufferedDevice() { #if HAVE_XZ_SUPPORT testBufferedDevice(KCompressionDevice::Xz); #else QSKIP("This test needs xz support"); #endif } void KCompressionDeviceTest::testWriteErrorOnOpen() { // GIVEN QString fileName("/I/dont/exist/kcompressiondevicetest-write.gz"); KCompressionDevice dev(fileName, KCompressionDevice::GZip); // WHEN QVERIFY(!dev.open(QIODevice::WriteOnly)); // THEN QCOMPARE(dev.error(), QFileDevice::OpenError); #ifdef Q_OS_WIN QCOMPARE(dev.errorString(), QStringLiteral("The system cannot find the path specified.")); #else QCOMPARE(dev.errorString(), QStringLiteral("No such file or directory")); #endif } void KCompressionDeviceTest::testWriteErrorOnClose() { // GIVEN QFile file("kcompressiondevicetest-write.gz"); KCompressionDevice dev(&file, false, KCompressionDevice::GZip); QVERIFY(dev.open(QIODevice::WriteOnly)); const QByteArray data = "Hello world"; QCOMPARE(dev.write(data), data.size()); // This is nasty, it's just a way to try and trigger an error on flush, without filling up a partition first ;) file.close(); QVERIFY(file.open(QIODevice::ReadOnly)); QTest::ignoreMessage(QtWarningMsg, "QIODevice::write (QFile, \"kcompressiondevicetest-write.gz\"): ReadOnly device"); // WHEN dev.close(); // I want a QVERIFY here... https://bugreports.qt.io/browse/QTBUG-70033 // THEN QCOMPARE(int(dev.error()), int(QFileDevice::WriteError)); } diff --git a/src/k7zip.cpp b/src/k7zip.cpp index ee01573..bcc3029 100644 --- a/src/k7zip.cpp +++ b/src/k7zip.cpp @@ -1,2984 +1,2984 @@ /* This file is part of the KDE libraries Copyright (C) 2011 Mario Bensi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "k7zip.h" #include "karchive_p.h" #include "loggingcategory.h" #include #include #include #include #include #include "kcompressiondevice.h" #include #include #include "klimitediodevice_p.h" #include // time() #include #include "zlib.h" #ifndef QT_STAT_LNK # define QT_STAT_LNK 0120000 #endif // QT_STAT_LNK //////////////////////////////////////////////////////////////////////// /////////////////////////// K7Zip ////////////////////////////////////// //////////////////////////////////////////////////////////////////////// #define BUFFER_SIZE 8*1024 static const unsigned char k7zip_signature[6] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; //static const unsigned char XZ_HEADER_MAGIC[6] = { 0xFD, '7', 'z', 'X', 'Z', 0x00 }; #define GetUi16(p, offset) (((unsigned char)p[offset+0]) | (((unsigned char)p[1]) << 8)) #define GetUi32(p, offset) ( \ ((unsigned char)p[offset+0]) | \ (((unsigned char)p[offset+1]) << 8) | \ (((unsigned char)p[offset+2]) << 16) | \ (((unsigned char)p[offset+3]) << 24)) #define GetUi64(p, offset) ((quint32)GetUi32(p, offset) | (((quint64)GetUi32(p, offset + 4)) << 32)) #define LZMA2_DIC_SIZE_FROM_PROP(p) (((quint32)2 | ((p) & 1)) << ((p) / 2 + 11)) #define FILE_ATTRIBUTE_READONLY 1 #define FILE_ATTRIBUTE_HIDDEN 2 #define FILE_ATTRIBUTE_SYSTEM 4 #define FILE_ATTRIBUTE_DIRECTORY 16 #define FILE_ATTRIBUTE_ARCHIVE 32 #define FILE_ATTRIBUTE_DEVICE 64 #define FILE_ATTRIBUTE_NORMAL 128 #define FILE_ATTRIBUTE_TEMPORARY 256 #define FILE_ATTRIBUTE_SPARSE_FILE 512 #define FILE_ATTRIBUTE_REPARSE_POINT 1024 #define FILE_ATTRIBUTE_COMPRESSED 2048 #define FILE_ATTRIBUTE_OFFLINE 0x1000 #define FILE_ATTRIBUTE_ENCRYPTED 0x4000 #define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */ enum HeaderType { kEnd, kHeader, kArchiveProperties, kAdditionalStreamsInfo, kMainStreamsInfo, kFilesInfo, kPackInfo, kUnpackInfo, kSubStreamsInfo, kSize, kCRC, kFolder, kCodersUnpackSize, kNumUnpackStream, kEmptyStream, kEmptyFile, kAnti, kName, kCTime, kATime, kMTime, kAttributes, kComment, kEncodedHeader, kStartPos, kDummy }; // Method ID // static const quint64 k_Copy = 0x00; // static const quint64 k_Delta = 0x03; // static const quint64 k_x86 = 0x04; //BCJ // static const quint64 k_PPC = 0x05; // BIG Endian // static const quint64 k_IA64 = 0x06; // static const quint64 k_ARM = 0x07; // little Endian // static const quint64 k_ARM_Thumb = 0x08; // little Endian // static const quint64 k_SPARC = 0x09; static const quint64 k_LZMA2 = 0x21; // static const quint64 k_Swap2 = 0x020302; // static const quint64 k_Swap4 = 0x020304; static const quint64 k_LZMA = 0x030101; static const quint64 k_BCJ = 0x03030103; static const quint64 k_BCJ2 = 0x0303011B; // static const quint64 k_7zPPC = 0x03030205; // static const quint64 k_Alpha = 0x03030301; // static const quint64 k_7zIA64 = 0x03030401; // static const quint64 k_7zARM = 0x03030501; // static const quint64 k_M68 = 0x03030605; //Big Endian // static const quint64 k_ARMT = 0x03030701; // static const quint64 k_7zSPARC = 0x03030805; static const quint64 k_PPMD = 0x030401; // static const quint64 k_Experimental = 0x037F01; // static const quint64 k_Shrink = 0x040101; // static const quint64 k_Implode = 0x040106; // static const quint64 k_Deflate = 0x040108; // static const quint64 k_Deflate64 = 0x040109; // static const quint64 k_Imploding = 0x040110; // static const quint64 k_Jpeg = 0x040160; // static const quint64 k_WavPack = 0x040161; // static const quint64 k_PPMd = 0x040162; // static const quint64 k_wzAES = 0x040163; static const quint64 k_BZip2 = 0x040202; // static const quint64 k_Rar15 = 0x040301; // static const quint64 k_Rar20 = 0x040302; // static const quint64 k_Rar29 = 0x040303; // static const quint64 k_Arj = 0x040401; //1 2 3 // static const quint64 k_Arj4 = 0x040402; // static const quint64 k_Z = 0x0405; // static const quint64 k_Lzh = 0x0406; // static const quint64 k_Cab = 0x0408; // static const quint64 k_DeflateNSIS = 0x040901; // static const quint64 k_Bzip2NSIS = 0x040902; static const quint64 k_AES = 0x06F10701; /** * A K7ZipFileEntry represents a file in a 7zip archive. */ class KARCHIVE_EXPORT K7ZipFileEntry : public KArchiveFile { public: K7ZipFileEntry(K7Zip *zip, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink, qint64 pos, qint64 size, const QByteArray &data); ~K7ZipFileEntry(); /** * @return the content of this file. * Call data() with care (only once per file), this data isn't cached. */ QByteArray data() const override; /** * This method returns QIODevice (internal class: KLimitedIODevice) * on top of the underlying QIODevice. This is obviously for reading only. * * WARNING: Note that the ownership of the device is being transferred to the caller, * who will have to delete it. * * The returned device auto-opens (in readonly mode), no need to open it. * @return the QIODevice of the file */ QIODevice *createDevice() const override; private: const QByteArray m_data; QBuffer *m_buffer; }; K7ZipFileEntry::K7ZipFileEntry(K7Zip *zip, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink, qint64 pos, qint64 size, const QByteArray &data) : KArchiveFile(zip, name, access, date, user, group, symlink, pos, size) , m_data(data) , m_buffer(new QBuffer) { m_buffer->setData(m_data); m_buffer->open(QIODevice::ReadOnly); } K7ZipFileEntry::~K7ZipFileEntry() { delete m_buffer; } QByteArray K7ZipFileEntry::data() const { return m_data.mid(position(), size()); } QIODevice *K7ZipFileEntry::createDevice() const { return new KLimitedIODevice(m_buffer, position(), size()); } class FileInfo { public: FileInfo() : size(0) , attributes(0) , crc(0) , attribDefined(false) , crcDefined(false) , hasStream(false) , isDir(false) { } QString path; quint64 size; quint32 attributes; quint32 crc; bool attribDefined; bool crcDefined; bool hasStream; bool isDir; }; class Folder { public: class FolderInfo { public: FolderInfo() : numInStreams(0) , numOutStreams(0) , methodID(0) { } bool isSimpleCoder() const { return (numInStreams == 1) && (numOutStreams == 1); } int numInStreams; int numOutStreams; QVector properties; quint64 methodID; }; Folder() : unpackCRCDefined(false) , unpackCRC(0) { } ~Folder() { qDeleteAll(folderInfos); } Q_DISABLE_COPY(Folder) quint64 getUnpackSize() const { if (unpackSizes.isEmpty()) { return 0; } for (int i = unpackSizes.size() - 1; i >= 0; i--) { if (findBindPairForOutStream(i) < 0) { return unpackSizes.at(i); } } return 0; } int getNumOutStreams() const { int result = 0; for (int i = 0; i < folderInfos.size(); i++) { result += folderInfos.at(i)->numOutStreams; } return result; } quint32 getCoderInStreamIndex(quint32 coderIndex) const { quint32 streamIndex = 0; for (quint32 i = 0; i < coderIndex; i++) { streamIndex += folderInfos.at(i)->numInStreams; } return streamIndex; } quint32 getCoderOutStreamIndex(quint32 coderIndex) const { quint32 streamIndex = 0; for (quint32 i = 0; i < coderIndex; i++) { streamIndex += folderInfos.at(i)->numOutStreams; } return streamIndex; } int findBindPairForInStream(size_t inStreamIndex) const { for (int i = 0; i < inIndexes.size(); i++) { if (inIndexes[i] == inStreamIndex) { return i; } } return -1; } int findBindPairForOutStream(size_t outStreamIndex) const { for (int i = 0; i < outIndexes.size(); i++) { if (outIndexes[i] == outStreamIndex) { return i; } } return -1; } int findPackStreamArrayIndex(size_t inStreamIndex) const { for (int i = 0; i < packedStreams.size(); i++) { if (packedStreams[i] == inStreamIndex) { return i; } } return -1; } void findInStream(quint32 streamIndex, quint32 &coderIndex, quint32 &coderStreamIndex) const { for (coderIndex = 0; coderIndex < (quint32)folderInfos.size(); coderIndex++) { quint32 curSize = folderInfos[coderIndex]->numInStreams; if (streamIndex < curSize) { coderStreamIndex = streamIndex; return; } streamIndex -= curSize; } } void findOutStream(quint32 streamIndex, quint32 &coderIndex, quint32 &coderStreamIndex) const { for (coderIndex = 0; coderIndex < (quint32)folderInfos.size(); coderIndex++) { quint32 curSize = folderInfos[coderIndex]->numOutStreams; if (streamIndex < curSize) { coderStreamIndex = streamIndex; return; } streamIndex -= curSize; } } bool isEncrypted() const { for (int i = folderInfos.size() - 1; i >= 0; i--) { if (folderInfos.at(i)->methodID == k_AES) { return true; } } return false; } //bool CheckStructure() const; bool unpackCRCDefined; quint32 unpackCRC; QVector folderInfos; QVector inIndexes; QVector outIndexes; QVector packedStreams; QVector unpackSizes; }; class Q_DECL_HIDDEN K7Zip::K7ZipPrivate { public: K7ZipPrivate(K7Zip *parent) : q(parent) , packPos(0) , numPackStreams(0) , buffer(nullptr) , pos(0) , end(0) , headerSize(0) , countSize(0) , m_currentFile(nullptr) { } ~K7ZipPrivate() { qDeleteAll(folders); qDeleteAll(fileInfos); } K7Zip *q; QVector packCRCsDefined; QVector packCRCs; QVector numUnpackStreamsInFolders; QVector folders; QVector fileInfos; // File informations QVector cTimesDefined; QVector cTimes; QVector aTimesDefined; QVector aTimes; QVector mTimesDefined; QVector mTimes; QVector startPositionsDefined; QVector startPositions; QVector fileInfoPopIDs; quint64 packPos; quint64 numPackStreams; QVector packSizes; QVector unpackSizes; QVector digestsDefined; QVector digests; QVector isAnti; const char *buffer; quint64 pos; quint64 end; quint64 headerSize; quint64 countSize; //Write QByteArray header; QByteArray outData; // Store data in this buffer before compress and write in archive. K7ZipFileEntry *m_currentFile; QVector m_entryList; void clear() { packCRCsDefined.clear(); packCRCs.clear(); numUnpackStreamsInFolders.clear(); qDeleteAll(folders); folders.clear(); qDeleteAll(fileInfos); fileInfos.clear(); cTimesDefined.clear(); cTimes.clear(); aTimesDefined.clear(); aTimes.clear(); mTimesDefined.clear(); mTimes.clear(); startPositionsDefined.clear(); startPositions.clear(); fileInfoPopIDs.clear(); packSizes.clear(); unpackSizes.clear(); digestsDefined.clear(); digests.clear(); isAnti.clear(); buffer = nullptr; pos = 0; end = 0; headerSize = 0; countSize = 0; } // Read int readByte(); quint32 readUInt32(); quint64 readUInt64(); quint64 readNumber(); QString readString(); void readHashDigests(int numItems, QVector &digestsDefined, QVector &digests); void readBoolVector(int numItems, QVector &v); void readBoolVector2(int numItems, QVector &v); void skipData(int size); bool findAttribute(int attribute); bool readUInt64DefVector(int numFiles, QVector &values, QVector &defined); Folder *folderItem(); bool readMainStreamsInfo(); bool readPackInfo(); bool readUnpackInfo(); bool readSubStreamsInfo(); QByteArray readAndDecodePackedStreams(bool readMainStreamInfo = true); //Write void createItemsFromEntities(const KArchiveDirectory *, const QString &, QByteArray &); void writeByte(unsigned char b); void writeNumber(quint64 value); void writeBoolVector(const QVector &boolVector); void writeUInt32(quint32 value); void writeUInt64(quint64 value); void writeHashDigests(const QVector &digestsDefined, const QVector &digests); void writeAlignedBoolHeader(const QVector &v, int numDefined, int type, unsigned itemSize); void writeUInt64DefVector(const QVector &v, const QVector &defined, int type); void writeFolder(const Folder *folder); void writePackInfo(quint64 dataOffset, QVector &packedSizes, QVector &packedCRCsDefined, QVector &packedCRCs); void writeUnpackInfo(const QVector &folderItems); void writeSubStreamsInfo(const QVector &unpackSizes, const QVector &digestsDefined, const QVector &digests); void writeHeader(quint64 &headerOffset); void writeSignature(); void writeStartHeader(const quint64 nextHeaderSize, const quint32 nextHeaderCRC, const quint64 nextHeaderOffset); QByteArray encodeStream(QVector &packSizes, QVector &folds); }; K7Zip::K7Zip(const QString &fileName) : KArchive(fileName) , d(new K7ZipPrivate(this)) { } K7Zip::K7Zip(QIODevice *dev) : KArchive(dev) , d(new K7ZipPrivate(this)) { Q_ASSERT(dev); } K7Zip::~K7Zip() { if (isOpen()) { close(); } delete d; } int K7Zip::K7ZipPrivate::readByte() { if (!buffer || pos + 1 > end) { return -1; } return buffer[pos++]; } quint32 K7Zip::K7ZipPrivate::readUInt32() { if (!buffer || (quint64)(pos + 4) > end) { qCDebug(KArchiveLog) << "error size"; return 0; } quint32 res = GetUi32(buffer, pos); pos += 4; return res; } quint64 K7Zip::K7ZipPrivate::readUInt64() { if (!buffer || (quint64)(pos + 8) > end) { qCDebug(KArchiveLog) << "error size"; return 0; } quint64 res = GetUi64(buffer, pos); pos += 8; return res; } quint64 K7Zip::K7ZipPrivate::readNumber() { if (!buffer || (quint64)(pos + 8) > end) { return 0; } unsigned char firstByte = buffer[pos++]; unsigned char mask = 0x80; quint64 value = 0; for (int i = 0; i < 8; i++) { if ((firstByte & mask) == 0) { quint64 highPart = firstByte & (mask - 1); value += (highPart << (i * 8)); return value; } value |= ((unsigned char)buffer[pos++] << (8 * i)); mask >>= 1; } return value; } QString K7Zip::K7ZipPrivate::readString() { if (!buffer) { return QString(); } const char *buf = buffer + pos; size_t rem = (end - pos) / 2 * 2; { size_t i; for (i = 0; i < rem; i += 2) { if (buf[i] == 0 && buf[i + 1] == 0) { break; } } if (i == rem) { qCDebug(KArchiveLog) << "read string error"; return QString(); } rem = i; } int len = (int)(rem / 2); if (len < 0 || (size_t)len * 2 != rem) { qCDebug(KArchiveLog) << "read string unsupported"; return QString(); } QString p; for (int i = 0; i < len; i++, buf += 2) { p += (wchar_t)GetUi16(buf, 0); } pos += rem + 2; return p; } void K7Zip::K7ZipPrivate::skipData(int size) { if (!buffer || pos + size > end) { return; } pos += size; } bool K7Zip::K7ZipPrivate::findAttribute(int attribute) { if (!buffer) { return false; } for (;;) { int type = readByte(); if (type == attribute) { return true; } if (type == kEnd) { return false; } skipData(readNumber()); } } void K7Zip::K7ZipPrivate::readBoolVector(int numItems, QVector &v) { if (!buffer) { return; } unsigned char b = 0; unsigned char mask = 0; for (int i = 0; i < numItems; i++) { if (mask == 0) { b = readByte(); mask = 0x80; } v.append((b & mask) != 0); mask >>= 1; } } void K7Zip::K7ZipPrivate::readBoolVector2(int numItems, QVector &v) { if (!buffer) { return; } int allAreDefined = readByte(); if (allAreDefined == 0) { readBoolVector(numItems, v); return; } for (int i = 0; i < numItems; i++) { v.append(true); } } void K7Zip::K7ZipPrivate::readHashDigests(int numItems, QVector &digestsDefined, QVector &digests) { if (!buffer) { return; } readBoolVector2(numItems, digestsDefined); for (int i = 0; i < numItems; i++) { quint32 crc = 0; if (digestsDefined[i]) { crc = GetUi32(buffer, pos); pos += 4; } digests.append(crc); } } Folder *K7Zip::K7ZipPrivate::folderItem() { if (!buffer) { return nullptr; } Folder *folder = new Folder; int numCoders = readNumber(); quint64 numInStreamsTotal = 0; quint64 numOutStreamsTotal = 0; for (int i = 0; i < numCoders; ++i) { //BYTE // { // 0:3 CodecIdSize // 4: Is Complex Coder // 5: There Are Attributes // 6: Reserved // 7: There are more alternative methods. (Not used // anymore, must be 0). // } unsigned char coderInfo = readByte(); int codecIdSize = (coderInfo & 0xF); if (codecIdSize > 8) { qCDebug(KArchiveLog) << "unsupported codec id size"; delete folder; return nullptr; } Folder::FolderInfo *info = new Folder::FolderInfo(); std::unique_ptr codecID(new unsigned char[codecIdSize]); for (int i = 0; i < codecIdSize; ++i) { codecID[i] = readByte(); } int id = 0; for (int j = 0; j < codecIdSize; j++) { id |= codecID[codecIdSize - 1 - j] << (8 * j); } info->methodID = id; //if (Is Complex Coder) if ((coderInfo & 0x10) != 0) { info->numInStreams = readNumber(); info->numOutStreams = readNumber(); } else { info->numInStreams = 1; info->numOutStreams = 1; } //if (There Are Attributes) if ((coderInfo & 0x20) != 0) { int propertiesSize = readNumber(); for (int i = 0; i < propertiesSize; ++i) { info->properties.append(readByte()); } } if ((coderInfo & 0x80) != 0) { qCDebug(KArchiveLog) << "unsupported"; delete info; delete folder; return nullptr; } numInStreamsTotal += info->numInStreams; numOutStreamsTotal += info->numOutStreams; folder->folderInfos.append(info); } int numBindPairs = numOutStreamsTotal - 1; for (int i = 0; i < numBindPairs; i++) { folder->inIndexes.append(readNumber()); folder->outIndexes.append(readNumber()); } int numPackedStreams = numInStreamsTotal - numBindPairs; if (numPackedStreams > 1) { for (int i = 0; i < numPackedStreams; ++i) { folder->packedStreams.append(readNumber()); } } else { if (numPackedStreams == 1) { for (quint64 i = 0; i < numInStreamsTotal; i++) { if (folder->findBindPairForInStream(i) < 0) { folder->packedStreams.append(i); break; } } if (folder->packedStreams.size() != 1) { delete folder; return nullptr; } } } return folder; } bool K7Zip::K7ZipPrivate::readUInt64DefVector(int numFiles, QVector &values, QVector &defined) { if (!buffer) { return false; } readBoolVector2(numFiles, defined); int external = readByte(); if (external != 0) { int dataIndex = readNumber(); if (dataIndex < 0 /*|| dataIndex >= dataVector->Size()*/) { qCDebug(KArchiveLog) << "wrong data index"; return false; } // TODO : go to the new index } for (int i = 0; i < numFiles; i++) { quint64 t = 0; if (defined[i]) { t = readUInt64(); } values.append(t); } return true; } bool K7Zip::K7ZipPrivate::readPackInfo() { if (!buffer) { return false; } packPos = readNumber(); numPackStreams = readNumber(); packSizes.clear(); packCRCsDefined.clear(); packCRCs.clear(); if (!findAttribute(kSize)) { qCDebug(KArchiveLog) << "kSize not found"; return false; } for (quint64 i = 0; i < numPackStreams; ++i) { packSizes.append(readNumber()); } for (;;) { int type = readByte(); if (type == kEnd) { break; } if (type == kCRC) { readHashDigests(numPackStreams, packCRCsDefined, packCRCs); continue; } skipData(readNumber()); } if (packCRCs.isEmpty()) { for (quint64 i = 0; i < numPackStreams; ++i) { packCRCsDefined.append(false); packCRCs.append(0); } } return true; } bool K7Zip::K7ZipPrivate::readUnpackInfo() { if (!buffer) { return false; } if (!findAttribute(kFolder)) { qCDebug(KArchiveLog) << "kFolder not found"; return false; } int numFolders = readNumber(); qDeleteAll(folders); folders.clear(); int external = readByte(); switch (external) { case 0: { for (int i = 0; i < numFolders; ++i) { folders.append(folderItem()); } break; } case 1: { int dataIndex = readNumber(); if (dataIndex < 0 /*|| dataIndex >= dataVector->Size()*/) { qCDebug(KArchiveLog) << "wrong data index"; } // TODO : go to the new index break; } default: qCDebug(KArchiveLog) << "external error"; return false; } if (!findAttribute(kCodersUnpackSize)) { qCDebug(KArchiveLog) << "kCodersUnpackSize not found"; return false; } for (int i = 0; i < numFolders; ++i) { Folder *folder = folders.at(i); int numOutStreams = folder->getNumOutStreams(); for (int j = 0; j < numOutStreams; ++j) { folder->unpackSizes.append(readNumber()); } } for (;;) { int type = readByte(); if (type == kEnd) { break; } if (type == kCRC) { QVector crcsDefined; QVector crcs; readHashDigests(numFolders, crcsDefined, crcs); for (int i = 0; i < numFolders; i++) { Folder *folder = folders.at(i); folder->unpackCRCDefined = crcsDefined[i]; folder->unpackCRC = crcs[i]; } continue; } skipData(readNumber()); } return true; } bool K7Zip::K7ZipPrivate::readSubStreamsInfo() { if (!buffer) { return false; } numUnpackStreamsInFolders.clear(); int type; for (;;) { type = readByte(); if (type == kNumUnpackStream) { for (int i = 0; i < folders.size(); i++) { numUnpackStreamsInFolders.append(readNumber()); } continue; } if (type == kCRC || type == kSize) { break; } if (type == kEnd) { break; } skipData(readNumber()); } if (numUnpackStreamsInFolders.isEmpty()) { for (int i = 0; i < folders.size(); i++) { numUnpackStreamsInFolders.append(1); } } for (int i = 0; i < numUnpackStreamsInFolders.size(); i++) { quint64 numSubstreams = numUnpackStreamsInFolders.at(i); if (numSubstreams == 0) { continue; } quint64 sum = 0; for (quint64 j = 1; j < numSubstreams; j++) { if (type == kSize) { int size = readNumber(); unpackSizes.append(size); sum += size; } } unpackSizes.append(folders.at(i)->getUnpackSize() - sum); } if (type == kSize) { type = readByte(); } int numDigests = 0; int numDigestsTotal = 0; for (int i = 0; i < folders.size(); i++) { quint64 numSubstreams = numUnpackStreamsInFolders.at(i); if (numSubstreams != 1 || !folders.at(i)->unpackCRCDefined) { numDigests += numSubstreams; } numDigestsTotal += numSubstreams; } for (;;) { if (type == kCRC) { QVector digestsDefined2; QVector digests2; readHashDigests(numDigests, digestsDefined2, digests2); int digestIndex = 0; for (int i = 0; i < folders.size(); i++) { quint64 numSubstreams = numUnpackStreamsInFolders.at(i); const Folder *folder = folders.at(i); if (numSubstreams == 1 && folder->unpackCRCDefined) { digestsDefined.append(true); digests.append(folder->unpackCRC); } else { for (quint64 j = 0; j < numSubstreams; j++, digestIndex++) { digestsDefined.append(digestsDefined2[digestIndex]); digests.append(digests2[digestIndex]); } } } } else if (type == kEnd) { if (digestsDefined.isEmpty()) { for (int i = 0; i < numDigestsTotal; i++) { digestsDefined.append(false); digests.append(0); } } break; } else { skipData(readNumber()); } type = readByte(); } return true; } #define TICKSPERSEC 10000000 #define TICKSPERMSEC 10000 #define SECSPERDAY 86400 #define SECSPERHOUR 3600 #define SECSPERMIN 60 #define EPOCHWEEKDAY 1 /* Jan 1, 1601 was Monday */ #define DAYSPERWEEK 7 #define DAYSPERQUADRICENTENNIUM (365 * 400 + 97) #define DAYSPERNORMALQUADRENNIUM (365 * 4 + 1) #define TICKS_1601_TO_1970 (SECS_1601_TO_1970 * TICKSPERSEC) #define SECS_1601_TO_1970 ((369 * 365 + 89) * (unsigned long long)SECSPERDAY) static uint toTimeT(const long long liTime) { long long time = liTime / TICKSPERSEC; /* The native version of RtlTimeToTimeFields does not take leap seconds * into account */ /* Split the time into days and seconds within the day */ long int days = time / SECSPERDAY; int secondsInDay = time % SECSPERDAY; /* compute time of day */ short hour = (short)(secondsInDay / SECSPERHOUR); secondsInDay = secondsInDay % SECSPERHOUR; short minute = (short)(secondsInDay / SECSPERMIN); short second = (short)(secondsInDay % SECSPERMIN); /* compute year, month and day of month. */ long int cleaps = (3 * ((4 * days + 1227) / DAYSPERQUADRICENTENNIUM) + 3) / 4; days += 28188 + cleaps; long int years = (20 * days - 2442) / (5 * DAYSPERNORMALQUADRENNIUM); long int yearday = days - (years * DAYSPERNORMALQUADRENNIUM) / 4; long int months = (64 * yearday) / 1959; /* the result is based on a year starting on March. * To convert take 12 from Januari and Februari and * increase the year by one. */ short month, year; if (months < 14) { month = (short)(months - 1); year = (short)(years + 1524); } else { month = (short)(months - 13); year = (short)(years + 1525); } /* calculation of day of month is based on the wonderful * sequence of INT( n * 30.6): it reproduces the· * 31-30-31-30-31-31 month lengths exactly for small n's */ short day = (short)(yearday - (1959 * months) / 64); QDateTime t(QDate(year, month, day), QTime(hour, minute, second)); t.setTimeSpec(Qt::UTC); return t.toTime_t(); } long long rtlSecondsSince1970ToSpecTime(quint32 seconds) { long long secs = seconds * (long long)TICKSPERSEC + TICKS_1601_TO_1970; return secs; } bool K7Zip::K7ZipPrivate::readMainStreamsInfo() { if (!buffer) { return false; } quint32 type; for (;;) { type = readByte(); if (type > ((quint32)1 << 30)) { qCDebug(KArchiveLog) << "type error"; return false; } switch (type) { case kEnd: return true; case kPackInfo: { if (!readPackInfo()) { qCDebug(KArchiveLog) << "error during read pack information"; return false; } break; } case kUnpackInfo: { if (!readUnpackInfo()) { qCDebug(KArchiveLog) << "error during read pack information"; return false; } break; } case kSubStreamsInfo: { if (!readSubStreamsInfo()) { qCDebug(KArchiveLog) << "error during read substreams information"; return false; } break; } default: qCDebug(KArchiveLog) << "Wrong type"; return false; } } qCDebug(KArchiveLog) << "should not reach"; return false; } static bool getInStream(const Folder *folder, quint32 streamIndex, int &seqInStream, quint32 &coderIndex) { for (int i = 0; i < folder->packedStreams.size(); i++) { if (folder->packedStreams[i] == streamIndex) { seqInStream = i; return true; } } int binderIndex = folder->findBindPairForInStream(streamIndex); if (binderIndex < 0) { return false; } quint32 coderStreamIndex; folder->findOutStream(folder->outIndexes[binderIndex], coderIndex, coderStreamIndex); quint32 startIndex = folder->getCoderInStreamIndex(coderIndex); if (folder->folderInfos[coderIndex]->numInStreams > 1) { return false; } for (int i = 0; i < (int)folder->folderInfos[coderIndex]->numInStreams; i++) { getInStream(folder, startIndex + i, seqInStream, coderIndex); } return true; } static bool getOutStream(const Folder *folder, quint32 streamIndex, int &seqOutStream) { QVector outStreams; quint32 outStreamIndex = 0; for (int i = 0; i < folder->folderInfos.size(); i++) { const Folder::FolderInfo *coderInfo = folder->folderInfos.at(i); for (int j = 0; j < coderInfo->numOutStreams; j++, outStreamIndex++) { if (folder->findBindPairForOutStream(outStreamIndex) < 0) { outStreams.append(outStreamIndex); } } } for (int i = 0; i < outStreams.size(); i++) { if (outStreams[i] == streamIndex) { seqOutStream = i; return true; } } int binderIndex = folder->findBindPairForOutStream(streamIndex); if (binderIndex < 0) { return false; } quint32 coderIndex, coderStreamIndex; folder->findInStream(folder->inIndexes[binderIndex], coderIndex, coderStreamIndex); quint32 startIndex = folder->getCoderOutStreamIndex(coderIndex); if (folder->folderInfos[coderIndex]->numOutStreams > 1) { return false; } for (int i = 0; i < (int)folder->folderInfos[coderIndex]->numOutStreams; i++) { getOutStream(folder, startIndex + i, seqOutStream); } return true; } const int kNumTopBits = 24; const quint32 kTopValue = (1 << kNumTopBits); class RangeDecoder { int pos; public: QByteArray stream; quint32 range; quint32 code; RangeDecoder() : pos(0) { } unsigned char readByte() { return stream[pos++]; } void normalize() { while (range < kTopValue) { code = (code << 8) | readByte(); range <<= 8; } } void setStream(const QByteArray &s) { stream = s; } void init() { code = 0; range = 0xFFFFFFFF; for (int i = 0; i < 5; i++) { code = (code << 8) | readByte(); } } quint32 getThreshold(quint32 total) { return (code) / (range /= total); } void decode(quint32 start, quint32 size) { code -= start * range; range *= size; normalize(); } quint32 decodeDirectBits(int numTotalBits) { quint32 r = range; quint32 c = code; quint32 result = 0; for (int i = numTotalBits; i != 0; i--) { r >>= 1; quint32 t = (c - r) >> 31; c -= r & (t - 1); result = (result << 1) | (1 - t); if (r < kTopValue) { c = (c << 8) | readByte(); r <<= 8; } } range = r; code = c; return result; } quint32 DecodeBit(quint32 size0, quint32 numTotalBits) { quint32 newBound = (range >> numTotalBits) * size0; quint32 symbol; if (code < newBound) { symbol = 0; range = newBound; } else { symbol = 1; code -= newBound; range -= newBound; } normalize(); return symbol; } }; const int kNumBitModelTotalBits = 11; const quint32 kBitModelTotal = (1 << kNumBitModelTotalBits); template class CBitModel { public: quint32 prob; void updateModel(quint32 symbol) { if (symbol == 0) { prob += (kBitModelTotal - prob) >> numMoveBits; } else { prob -= (prob) >> numMoveBits; } } void init() { prob = kBitModelTotal / 2; } }; template class CBitDecoder : public CBitModel { public: quint32 decode(RangeDecoder *decoder) { quint32 newBound = (decoder->range >> kNumBitModelTotalBits) * this->prob; if (decoder->code < newBound) { decoder->range = newBound; this->prob += (kBitModelTotal - this->prob) >> numMoveBits; if (decoder->range < kTopValue) { decoder->code = (decoder->code << 8) | decoder->readByte(); decoder->range <<= 8; } return 0; } else { decoder->range -= newBound; decoder->code -= newBound; this->prob -= (this->prob) >> numMoveBits; if (decoder->range < kTopValue) { decoder->code = (decoder->code << 8) | decoder->readByte(); decoder->range <<= 8; } return 1; } } }; inline bool isJcc(unsigned char b0, unsigned char b1) { return (b0 == 0x0F && (b1 & 0xF0) == 0x80); } inline bool isJ(unsigned char b0, unsigned char b1) { return ((b1 & 0xFE) == 0xE8 || isJcc(b0, b1)); } inline unsigned getIndex(unsigned char b0, unsigned char b1) { return ((b1 == 0xE8) ? b0 : ((b1 == 0xE9) ? 256 : 257)); } const int kNumMoveBits = 5; static QByteArray decodeBCJ2(const QByteArray &mainStream, const QByteArray &callStream, const QByteArray &jumpStream, const QByteArray &rangeBuffer) { unsigned char prevByte = 0; QByteArray outStream; int mainStreamPos = 0; int callStreamPos = 0; int jumpStreamPos = 0; RangeDecoder rangeDecoder; rangeDecoder.setStream(rangeBuffer); rangeDecoder.init(); QVector > statusDecoder(256 + 2); for (int i = 0; i < 256 + 2; i++) { statusDecoder[i].init(); } for (;;) { quint32 i; unsigned char b = 0; const quint32 kBurstSize = (1 << 18); for (i = 0; i < kBurstSize; i++) { if (mainStreamPos == mainStream.size()) { return outStream; } b = mainStream[mainStreamPos++]; outStream.append(b); if (isJ(prevByte, b)) { break; } prevByte = b; } if (i == kBurstSize) { continue; } unsigned index = getIndex(prevByte, b); if (statusDecoder[index].decode(&rangeDecoder) == 1) { quint32 src = 0; for (int i = 0; i < 4; i++) { unsigned char b0; if (b == 0xE8) { b0 = callStream[callStreamPos++]; } else { b0 = jumpStream[jumpStreamPos++]; } src <<= 8; src |= ((quint32)b0); } quint32 dest = src - (quint32(outStream.size()) + 4); outStream.append((unsigned char)(dest)); outStream.append((unsigned char)(dest >> 8)); outStream.append((unsigned char)(dest >> 16)); outStream.append((unsigned char)(dest >> 24)); prevByte = (unsigned char)(dest >> 24); } else { prevByte = b; } } } QByteArray K7Zip::K7ZipPrivate::readAndDecodePackedStreams(bool readMainStreamInfo) { if (!buffer) { return QByteArray(); } if (readMainStreamInfo) { readMainStreamsInfo(); } QByteArray inflatedData; quint64 startPos = 32 + packPos; for (int i = 0; i < folders.size(); i++) { const Folder *folder = folders.at(i); quint64 unpackSize64 = folder->getUnpackSize();; size_t unpackSize = (size_t)unpackSize64; if (unpackSize != unpackSize64) { qCDebug(KArchiveLog) << "unsupported"; return inflatedData; } // Find main coder quint32 mainCoderIndex = 0; QVector outStreamIndexed; int outStreamIndex = 0; for (int j = 0; j < folder->folderInfos.size(); j++) { const Folder::FolderInfo *info = folder->folderInfos[j]; for (int k = 0; k < info->numOutStreams; k++, outStreamIndex++) { if (folder->findBindPairForOutStream(outStreamIndex) < 0) { outStreamIndexed.append(outStreamIndex); break; } } } quint32 temp = 0; if (!outStreamIndexed.isEmpty()) { folder->findOutStream(outStreamIndexed[0], mainCoderIndex, temp); } quint32 startInIndex = folder->getCoderInStreamIndex(mainCoderIndex); quint32 startOutIndex = folder->getCoderOutStreamIndex(mainCoderIndex); Folder::FolderInfo *mainCoder = folder->folderInfos[mainCoderIndex]; QVector seqInStreams; QVector coderIndexes; seqInStreams.reserve(mainCoder->numInStreams); coderIndexes.reserve(mainCoder->numInStreams); for (int j = 0; j < (int)mainCoder->numInStreams; j++) { int seqInStream; quint32 coderIndex; getInStream(folder, startInIndex + j, seqInStream, coderIndex); seqInStreams.append(seqInStream); coderIndexes.append(coderIndex); } QVector seqOutStreams; seqOutStreams.reserve(mainCoder->numOutStreams); for (int j = 0; j < (int)mainCoder->numOutStreams; j++) { int seqOutStream; getOutStream(folder, startOutIndex + j, seqOutStream); seqOutStreams.append(seqOutStream); } QVector datas; for (int j = 0; j < (int)mainCoder->numInStreams; j++) { int size = packSizes[j + i]; std::unique_ptr encodedBuffer(new char[size]); QIODevice *dev = q->device(); dev->seek(startPos); quint64 n = dev->read(encodedBuffer.get(), size); if (n != (quint64)size) { qCDebug(KArchiveLog) << "Failed read next size, should read " << size << ", read " << n; return inflatedData; } QByteArray deflatedData(encodedBuffer.get(), size); datas.append(deflatedData); startPos += size; pos += size; headerSize += size; } QVector inflatedDatas; QByteArray deflatedData; for (int j = 0; j < seqInStreams.size(); ++j) { Folder::FolderInfo *coder = nullptr; if ((quint32)j != mainCoderIndex) { coder = folder->folderInfos[coderIndexes[j]]; } else { coder = folder->folderInfos[mainCoderIndex]; } deflatedData = datas[seqInStreams[j]]; KFilterBase *filter = nullptr; switch (coder->methodID) { case k_LZMA: filter = KCompressionDevice::filterForCompressionType(KCompressionDevice::Xz); if (!filter) { qCDebug(KArchiveLog) << "filter not found"; return QByteArray(); } static_cast(filter)->init(QIODevice::ReadOnly, KXzFilter::LZMA, coder->properties); break; case k_LZMA2: filter = KCompressionDevice::filterForCompressionType(KCompressionDevice::Xz); if (!filter) { qCDebug(KArchiveLog) << "filter not found"; return QByteArray(); } static_cast(filter)->init(QIODevice::ReadOnly, KXzFilter::LZMA2, coder->properties); break; case k_PPMD: { /*if (coder->properties.size() == 5) { //Byte order = *(const Byte *)coder.Props; qint32 dicSize = ((unsigned char)coder->properties[1] | (((unsigned char)coder->properties[2]) << 8) | (((unsigned char)coder->properties[3]) << 16) | (((unsigned char)coder->properties[4]) << 24)); }*/ break; } case k_AES: if (coder->properties.size() >= 1) { //const Byte *data = (const Byte *)coder.Props; //Byte firstByte = *data++; //UInt32 numCyclesPower = firstByte & 0x3F; } break; case k_BCJ: filter = KCompressionDevice::filterForCompressionType(KCompressionDevice::Xz); if (!filter) { qCDebug(KArchiveLog) << "filter not found"; return QByteArray(); } static_cast(filter)->init(QIODevice::ReadOnly, KXzFilter::BCJ, coder->properties); break; case k_BCJ2: { QByteArray bcj2 = decodeBCJ2(inflatedDatas[0], inflatedDatas[1], inflatedDatas[2], deflatedData); inflatedDatas.clear(); inflatedDatas.append(bcj2); break; } case k_BZip2: filter = KCompressionDevice::filterForCompressionType(KCompressionDevice::BZip2); if (!filter) { qCDebug(KArchiveLog) << "filter not found"; return QByteArray(); } filter->init(QIODevice::ReadOnly); break; } if (coder->methodID == k_BCJ2) { continue; } if (!filter) { return QByteArray(); } filter->setInBuffer(deflatedData.data(), deflatedData.size()); QByteArray outBuffer; // reserve memory outBuffer.resize(unpackSize); KFilterBase::Result result = KFilterBase::Ok; QByteArray inflatedDataTmp; while (result != KFilterBase::End && result != KFilterBase::Error && !filter->inBufferEmpty()) { filter->setOutBuffer(outBuffer.data(), outBuffer.size()); result = filter->uncompress(); if (result == KFilterBase::Error) { qCDebug(KArchiveLog) << " decode error"; filter->terminate(); delete filter; return QByteArray(); } int uncompressedBytes = outBuffer.size() - filter->outBufferAvailable(); // append the uncompressed data to inflate buffer inflatedDataTmp.append(outBuffer.data(), uncompressedBytes); if (result == KFilterBase::End) { //qCDebug(KArchiveLog) << "Finished unpacking"; break; // Finished. } } if (result != KFilterBase::End && !filter->inBufferEmpty()) { qCDebug(KArchiveLog) << "decode failed result" << result; filter->terminate(); delete filter; return QByteArray(); } filter->terminate(); delete filter; inflatedDatas.append(inflatedDataTmp); } QByteArray inflated; - Q_FOREACH (const QByteArray& data, inflatedDatas) { + for (const QByteArray& data : qAsConst(inflatedDatas)) { inflated.append(data); } inflatedDatas.clear(); if (folder->unpackCRCDefined) { quint32 crc = crc32(0, (Bytef *)(inflated.data()), unpackSize); if (crc != folder->unpackCRC) { qCDebug(KArchiveLog) << "wrong crc"; return QByteArray(); } } inflatedData.append(inflated); } return inflatedData; } ///////////////// Write //////////////////// void K7Zip::K7ZipPrivate::createItemsFromEntities(const KArchiveDirectory *dir, const QString &path, QByteArray &data) { const QStringList l = dir->entries(); QStringList::ConstIterator it = l.begin(); for (; it != l.end(); ++it) { const KArchiveEntry *entry = dir->entry((*it)); FileInfo *fileInfo = new FileInfo; fileInfo->attribDefined = true; fileInfo->path = path + entry->name(); mTimesDefined.append(true); mTimes.append(rtlSecondsSince1970ToSpecTime(entry->date().toTime_t())); if (entry->isFile()) { const K7ZipFileEntry *fileEntry = static_cast(entry); fileInfo->attributes = FILE_ATTRIBUTE_ARCHIVE; fileInfo->attributes |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((entry->permissions() & 0xFFFF) << 16); fileInfo->size = fileEntry->size(); QString symLink = fileEntry->symLinkTarget(); if (fileInfo->size > 0) { fileInfo->hasStream = true; data.append(outData.mid(fileEntry->position(), fileEntry->size())); unpackSizes.append(fileInfo->size); } else if (!symLink.isEmpty()) { fileInfo->hasStream = true; data.append(symLink.toUtf8()); unpackSizes.append(symLink.size()); } fileInfos.append(fileInfo); } else if (entry->isDirectory()) { fileInfo->attributes = FILE_ATTRIBUTE_DIRECTORY; fileInfo->attributes |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((entry->permissions() & 0xFFFF) << 16); fileInfo->isDir = true; fileInfos.append(fileInfo); createItemsFromEntities((KArchiveDirectory *)entry, path + (*it) + QLatin1Char('/'), data); } } } void K7Zip::K7ZipPrivate::writeByte(unsigned char b) { header.append(b); countSize++; } void K7Zip::K7ZipPrivate::writeNumber(quint64 value) { int firstByte = 0; short mask = 0x80; int i; for (i = 0; i < 8; i++) { if (value < ((quint64(1) << (7 * (i + 1))))) { firstByte |= (int)(value >> (8 * i)); break; } firstByte |= mask; mask >>= 1; } writeByte(firstByte); for (; i > 0; i--) { writeByte((int)value); value >>= 8; } } void K7Zip::K7ZipPrivate::writeBoolVector(const QVector &boolVector) { int b = 0; short mask = 0x80; for (int i = 0; i < boolVector.size(); i++) { if (boolVector[i]) { b |= mask; } mask >>= 1; if (mask == 0) { writeByte(b); mask = 0x80; b = 0; } } if (mask != 0x80) { writeByte(b); } } void K7Zip::K7ZipPrivate::writeUInt32(quint32 value) { for (int i = 0; i < 4; i++) { writeByte((unsigned char)value); value >>= 8; } } void K7Zip::K7ZipPrivate::writeUInt64(quint64 value) { for (int i = 0; i < 8; i++) { writeByte((unsigned char)value); value >>= 8; } } void K7Zip::K7ZipPrivate::writeAlignedBoolHeader(const QVector &v, int numDefined, int type, unsigned itemSize) { const unsigned bvSize = (numDefined == v.size()) ? 0 : ((unsigned)v.size() + 7) / 8; const quint64 dataSize = (quint64)numDefined * itemSize + bvSize + 2; //SkipAlign(3 + (unsigned)bvSize + (unsigned)GetBigNumberSize(dataSize), itemSize); writeByte(type); writeNumber(dataSize); if (numDefined == v.size()) { writeByte(1); } else { writeByte(0); writeBoolVector(v); } writeByte(0); } void K7Zip::K7ZipPrivate::writeUInt64DefVector(const QVector &v, const QVector &defined, int type) { int numDefined = 0; for (int i = 0; i < defined.size(); i++) { if (defined[i]) { numDefined++; } } if (numDefined == 0) { return; } writeAlignedBoolHeader(defined, numDefined, type, 8); for (int i = 0; i < defined.size(); i++) { if (defined[i]) { writeUInt64(v[i]); } } } void K7Zip::K7ZipPrivate::writeHashDigests( const QVector &digestsDefined, const QVector &digests) { int numDefined = 0; int i; for (i = 0; i < digestsDefined.size(); i++) { if (digestsDefined[i]) { numDefined++; } } if (numDefined == 0) { return; } writeByte(kCRC); if (numDefined == digestsDefined.size()) { writeByte(1); } else { writeByte(0); writeBoolVector(digestsDefined); } for (i = 0; i < digests.size(); i++) { if (digestsDefined[i]) { writeUInt32(digests[i]); } } } void K7Zip::K7ZipPrivate::writePackInfo(quint64 dataOffset, QVector &packedSizes, QVector &packedCRCsDefined, QVector &packedCRCs) { if (packedSizes.isEmpty()) { return; } writeByte(kPackInfo); writeNumber(dataOffset); writeNumber(packedSizes.size()); writeByte(kSize); for (int i = 0; i < packedSizes.size(); i++) { writeNumber(packedSizes[i]); } writeHashDigests(packedCRCsDefined, packedCRCs); writeByte(kEnd); } void K7Zip::K7ZipPrivate::writeFolder(const Folder *folder) { writeNumber(folder->folderInfos.size()); for (int i = 0; i < folder->folderInfos.size(); i++) { const Folder::FolderInfo *info = folder->folderInfos.at(i); { size_t propsSize = info->properties.size(); quint64 id = info->methodID; size_t idSize; for (idSize = 1; idSize < sizeof (id); idSize++) { if ((id >> (8 * idSize)) == 0) { break; } } int longID[15]; for (int t = idSize - 1; t >= 0; t--, id >>= 8) { longID[t] = (int)(id & 0xFF); } int b; b = (int)(idSize & 0xF); bool isComplex = !info->isSimpleCoder(); b |= (isComplex ? 0x10 : 0); b |= ((propsSize != 0) ? 0x20 : 0); writeByte(b); for (size_t j = 0; j < idSize; ++j) { writeByte(longID[j]); } if (isComplex) { writeNumber(info->numInStreams); writeNumber(info->numOutStreams); } if (propsSize == 0) { continue; } writeNumber(propsSize); for (size_t j = 0; j < propsSize; ++j) { writeByte(info->properties[j]); } } } for (int i = 0; i < folder->inIndexes.size(); i++) { writeNumber(folder->inIndexes[i]); writeNumber(folder->outIndexes[i]); } if (folder->packedStreams.size() > 1) { for (int i = 0; i < folder->packedStreams.size(); i++) { writeNumber(folder->packedStreams[i]); } } } void K7Zip::K7ZipPrivate::writeUnpackInfo(const QVector &folderItems) { if (folderItems.isEmpty()) { return; } writeByte(kUnpackInfo); writeByte(kFolder); writeNumber(folderItems.size()); { writeByte(0); for (int i = 0; i < folderItems.size(); i++) { writeFolder(folderItems[i]); } } writeByte(kCodersUnpackSize); int i; for (i = 0; i < folderItems.size(); i++) { const Folder *folder = folderItems[i]; for (int j = 0; j < folder->unpackSizes.size(); j++) { writeNumber(folder->unpackSizes.at(j)); } } QVector unpackCRCsDefined; QVector unpackCRCs; unpackCRCsDefined.reserve(folderItems.size()); unpackCRCs.reserve(folderItems.size()); for (i = 0; i < folderItems.size(); i++) { const Folder *folder = folderItems[i]; unpackCRCsDefined.append(folder->unpackCRCDefined); unpackCRCs.append(folder->unpackCRC); } writeHashDigests(unpackCRCsDefined, unpackCRCs); writeByte(kEnd); } void K7Zip::K7ZipPrivate::writeSubStreamsInfo( const QVector &unpackSizes, const QVector &digestsDefined, const QVector &digests) { writeByte(kSubStreamsInfo); for (int i = 0; i < numUnpackStreamsInFolders.size(); i++) { if (numUnpackStreamsInFolders.at(i) != 1) { writeByte(kNumUnpackStream); for (int j = 0; j < numUnpackStreamsInFolders.size(); j++) { writeNumber(numUnpackStreamsInFolders.at(j)); } break; } } bool needFlag = true; int index = 0; for (int i = 0; i < numUnpackStreamsInFolders.size(); i++) { for (quint32 j = 0; j < numUnpackStreamsInFolders.at(i); j++) { if (j + 1 != numUnpackStreamsInFolders.at(i)) { if (needFlag) { writeByte(kSize); } needFlag = false; writeNumber(unpackSizes[index]); } index++; } } QVector digestsDefined2; QVector digests2; int digestIndex = 0; for (int i = 0; i < folders.size(); i++) { int numSubStreams = (int)numUnpackStreamsInFolders.at(i); if (numSubStreams == 1 && folders.at(i)->unpackCRCDefined) { digestIndex++; } else { for (int j = 0; j < numSubStreams; j++, digestIndex++) { digestsDefined2.append(digestsDefined[digestIndex]); digests2.append(digests[digestIndex]); } } } writeHashDigests(digestsDefined2, digests2); writeByte(kEnd); } QByteArray K7Zip::K7ZipPrivate::encodeStream(QVector &packSizes, QVector &folds) { Folder *folder = new Folder; folder->unpackCRCDefined = true; folder->unpackCRC = crc32(0, (Bytef *)(header.data()), header.size()); folder->unpackSizes.append(header.size()); Folder::FolderInfo *info = new Folder::FolderInfo(); info->numInStreams = 1; info->numOutStreams = 1; info->methodID = k_LZMA2; quint32 dictSize = header.size(); const quint32 kMinReduceSize = (1 << 16); if (dictSize < kMinReduceSize) { dictSize = kMinReduceSize; } int dict; for (dict = 0; dict < 40; dict++) { if (dictSize <= LZMA2_DIC_SIZE_FROM_PROP(dict)) { break; } } info->properties.append(dict); folder->folderInfos.append(info); folds.append(folder); //compress data QByteArray encodedData; if (!header.isEmpty()) { QByteArray enc; QBuffer inBuffer(&enc); KCompressionDevice flt(&inBuffer, false, KCompressionDevice::Xz); flt.open(QIODevice::WriteOnly); KFilterBase *filter = flt.filterBase(); static_cast(filter)->init(QIODevice::WriteOnly, KXzFilter::LZMA2, info->properties); const int ret = flt.write(header); if (ret != header.size()) { qCDebug(KArchiveLog) << "write error write " << ret << "expected" << header.size(); return encodedData; } flt.close(); encodedData = inBuffer.data(); } packSizes.append(encodedData.size()); return encodedData; } void K7Zip::K7ZipPrivate::writeHeader(quint64 &headerOffset) { quint64 packedSize = 0; for (int i = 0; i < packSizes.size(); ++i) { packedSize += packSizes[i]; } headerOffset = packedSize; writeByte(kHeader); // Archive Properties if (!folders.isEmpty()) { writeByte(kMainStreamsInfo); writePackInfo(0, packSizes, packCRCsDefined, packCRCs); writeUnpackInfo(folders); QVector unpackFileSizes; QVector digestsDefined; QVector digests; for (int i = 0; i < fileInfos.size(); i++) { const FileInfo *file = fileInfos.at(i); if (!file->hasStream) { continue; } unpackFileSizes.append(file->size); digestsDefined.append(file->crcDefined); digests.append(file->crc); } writeSubStreamsInfo(unpackSizes, digestsDefined, digests); writeByte(kEnd); } if (fileInfos.isEmpty()) { writeByte(kEnd); return; } writeByte(kFilesInfo); writeNumber(fileInfos.size()); { /* ---------- Empty Streams ---------- */ QVector emptyStreamVector; int numEmptyStreams = 0; for (int i = 0; i < fileInfos.size(); i++) { if (fileInfos.at(i)->hasStream) { emptyStreamVector.append(false); } else { emptyStreamVector.append(true); numEmptyStreams++; } } if (numEmptyStreams > 0) { writeByte(kEmptyStream); writeNumber(((unsigned)emptyStreamVector.size() + 7) / 8); writeBoolVector(emptyStreamVector); QVector emptyFileVector, antiVector; int numEmptyFiles = 0, numAntiItems = 0; for (int i = 0; i < fileInfos.size(); i++) { const FileInfo *file = fileInfos.at(i); if (!file->hasStream) { emptyFileVector.append(!file->isDir); if (!file->isDir) { numEmptyFiles++; bool isAnti = (i < this->isAnti.size() && this->isAnti[i]); antiVector.append(isAnti); if (isAnti) { numAntiItems++; } } } } if (numEmptyFiles > 0) { writeByte(kEmptyFile); writeNumber(((unsigned)emptyFileVector.size() + 7) / 8); writeBoolVector(emptyFileVector); } if (numAntiItems > 0) { writeByte(kAnti); writeNumber(((unsigned)antiVector.size() + 7) / 8); writeBoolVector(antiVector); } } } { /* ---------- Names ---------- */ int numDefined = 0; size_t namesDataSize = 0; for (int i = 0; i < fileInfos.size(); i++) { const QString &name = fileInfos.at(i)->path; if (!name.isEmpty()) { numDefined++; namesDataSize += (name.length() + 1) * 2; } } if (numDefined > 0) { namesDataSize++; //SkipAlign(2 + GetBigNumberSize(namesDataSize), 2); writeByte(kName); writeNumber(namesDataSize); writeByte(0); for (int i = 0; i < fileInfos.size(); i++) { const QString &name = fileInfos.at(i)->path; for (int t = 0; t < name.length(); t++) { wchar_t c = name[t].toLatin1(); writeByte((unsigned char)c); writeByte((unsigned char)(c >> 8)); } // End of string writeByte(0); writeByte(0); } } } writeUInt64DefVector(mTimes, mTimesDefined, kMTime); writeUInt64DefVector(startPositions, startPositionsDefined, kStartPos); { /* ---------- Write Attrib ---------- */ QVector boolVector; int numDefined = 0; boolVector.reserve(fileInfos.size()); for (int i = 0; i < fileInfos.size(); i++) { bool defined = fileInfos.at(i)->attribDefined; boolVector.append(defined); if (defined) { numDefined++; } } if (numDefined > 0) { writeAlignedBoolHeader(boolVector, numDefined, kAttributes, 4); for (int i = 0; i < fileInfos.size(); i++) { const FileInfo *file = fileInfos.at(i); if (file->attribDefined) { writeUInt32(file->attributes); } } } } writeByte(kEnd); // for files writeByte(kEnd); // for headers*/ } static void setUInt32(unsigned char *p, quint32 d) { for (int i = 0; i < 4; i++, d >>= 8) { p[i] = (unsigned)d; } } static void setUInt64(unsigned char *p, quint64 d) { for (int i = 0; i < 8; i++, d >>= 8) { p[i] = (unsigned char)d; } } void K7Zip::K7ZipPrivate::writeStartHeader(const quint64 nextHeaderSize, const quint32 nextHeaderCRC, const quint64 nextHeaderOffset) { unsigned char buf[24]; setUInt64(buf + 4, nextHeaderOffset); setUInt64(buf + 12, nextHeaderSize); setUInt32(buf + 20, nextHeaderCRC); setUInt32(buf, crc32(0, (Bytef *)(buf + 4), 20)); q->device()->write((char *)buf, 24); } void K7Zip::K7ZipPrivate::writeSignature() { unsigned char buf[8]; memcpy(buf, k7zip_signature, 6); buf[6] = 0/*kMajorVersion*/; buf[7] = 3; q->device()->write((char *)buf, 8); } bool K7Zip::openArchive(QIODevice::OpenMode mode) { if (!(mode & QIODevice::ReadOnly)) { return true; } QIODevice *dev = device(); if (!dev) { setErrorString(tr("Could not get underlying device")); return false; } char header[32]; // check signature qint64 n = dev->read(header, 32); if (n != 32) { setErrorString(tr("Read header failed")); return false; } for (int i = 0; i < 6; ++i) { if ((unsigned char)header[i] != k7zip_signature[i]) { setErrorString(tr("Check signature failed")); return false; } } // get Archive Version int major = header[6]; int minor = header[7]; /*if (major > 0 || minor > 2) { qCDebug(KArchiveLog) << "wrong archive version"; return false; }*/ // get Start Header CRC quint32 startHeaderCRC = GetUi32(header, 8); quint64 nextHeaderOffset = GetUi64(header, 12); quint64 nextHeaderSize = GetUi64(header, 20); quint32 nextHeaderCRC = GetUi32(header, 28); quint32 crc = crc32(0, (Bytef *)(header + 0xC), 20); if (crc != startHeaderCRC) { setErrorString(tr("Bad CRC")); return false; } if (nextHeaderSize == 0) { return true; } if (nextHeaderSize > (quint64)0xFFFFFFFF) { setErrorString(tr("Next header size is too big")); return false; } if ((qint64)nextHeaderOffset < 0) { setErrorString(tr("Next header size is less than zero")); return false; } dev->seek(nextHeaderOffset + 32); QByteArray inBuffer; inBuffer.resize(nextHeaderSize); n = dev->read(inBuffer.data(), inBuffer.size()); if (n != (qint64)nextHeaderSize) { setErrorString( tr("Failed read next header size; should read %1, read %2") .arg(nextHeaderSize).arg(n)); return false; } d->buffer = inBuffer.data(); d->end = nextHeaderSize; d->headerSize = 32 + nextHeaderSize; //int physSize = 32 + nextHeaderSize + nextHeaderOffset; crc = crc32(0, (Bytef *)(d->buffer), (quint32)nextHeaderSize); if (crc != nextHeaderCRC) { setErrorString(tr("Bad next header CRC")); return false; } int type = d->readByte(); QByteArray decodedData; if (type != kHeader) { if (type != kEncodedHeader) { setErrorString(tr("Error in header")); return false; } decodedData = d->readAndDecodePackedStreams(); int external = d->readByte(); if (external != 0) { int dataIndex = (int)d->readNumber(); if (dataIndex < 0) { //qCDebug(KArchiveLog) << "dataIndex error"; } d->buffer = decodedData.constData(); d->pos = 0; d->end = decodedData.size(); } type = d->readByte(); if (type != kHeader) { setErrorString(tr("Wrong header type")); return false; } } // read header type = d->readByte(); if (type == kArchiveProperties) { // TODO : implement this part setErrorString(tr("Not implemented")); return false; } if (type == kAdditionalStreamsInfo) { // TODO : implement this part setErrorString(tr("Not implemented")); return false; } if (type == kMainStreamsInfo) { if (!d->readMainStreamsInfo()) { setErrorString(tr("Error while reading main streams information")); return false; } type = d->readByte(); } else { for (int i = 0; i < d->folders.size(); ++i) { Folder *folder = d->folders.at(i); d->unpackSizes.append(folder->getUnpackSize()); d->digestsDefined.append(folder->unpackCRCDefined); d->digests.append(folder->unpackCRC); } } if (type == kEnd) { return true; } if (type != kFilesInfo) { setErrorString(tr("Error while reading header")); return false; } //read files info int numFiles = d->readNumber(); for (int i = 0; i < numFiles; ++i) { d->fileInfos.append(new FileInfo); } QVector emptyStreamVector; QVector emptyFileVector; QVector antiFileVector; int numEmptyStreams = 0; for (;;) { quint64 type = d->readByte(); if (type == kEnd) { break; } quint64 size = d->readNumber(); size_t ppp = d->pos; bool addPropIdToList = true; bool isKnownType = true; if (type > ((quint32)1 << 30)) { isKnownType = false; } else { switch (type) { case kEmptyStream: { d->readBoolVector(numFiles, emptyStreamVector); for (int i = 0; i < emptyStreamVector.size(); ++i) { if (emptyStreamVector[i]) { numEmptyStreams++; } } break; } case kEmptyFile: d->readBoolVector(numEmptyStreams, emptyFileVector); break; case kAnti: d->readBoolVector(numEmptyStreams, antiFileVector); break; case kCTime: if (!d->readUInt64DefVector(numFiles, d->cTimes, d->cTimesDefined)) { return false; } break; case kATime: if (!d->readUInt64DefVector(numFiles, d->aTimes, d->aTimesDefined)) { return false; } break; case kMTime: if (!d->readUInt64DefVector(numFiles, d->mTimes, d->mTimesDefined)) { setErrorString(tr("Error reading modification time")); return false; } break; case kName: { int external = d->readByte(); if (external != 0) { int dataIndex = d->readNumber(); if (dataIndex < 0 /*|| dataIndex >= dataVector->Size()*/) { qCDebug(KArchiveLog) << "wrong data index"; } // TODO : go to the new index } QString name; for (int i = 0; i < numFiles; i++) { name = d->readString(); d->fileInfos.at(i)->path = name; } break; } case kAttributes: { QVector attributesAreDefined; d->readBoolVector2(numFiles, attributesAreDefined); int external = d->readByte(); if (external != 0) { int dataIndex = d->readNumber(); if (dataIndex < 0) { qCDebug(KArchiveLog) << "wrong data index"; } // TODO : go to the new index } for (int i = 0; i < numFiles; i++) { FileInfo *fileInfo = d->fileInfos.at(i); fileInfo->attribDefined = attributesAreDefined[i]; if (fileInfo->attribDefined) { fileInfo->attributes = d->readUInt32(); } } break; } case kStartPos: if (!d->readUInt64DefVector(numFiles, d->startPositions, d->startPositionsDefined)) { setErrorString(tr("Error reading MTime")); return false; } break; case kDummy: { for (quint64 i = 0; i < size; i++) { if (d->readByte() != 0) { setErrorString(tr("Invalid")); return false; } } addPropIdToList = false; break; } default: addPropIdToList = isKnownType = false; } } if (isKnownType) { if (addPropIdToList) { d->fileInfoPopIDs.append(type); } } else { d->skipData(d->readNumber()); } bool checkRecordsSize = (major > 0 || minor > 2); if (checkRecordsSize && d->pos - ppp != size) { setErrorString( tr( "Read size failed " "(checkRecordsSize: %1, d->pos - ppp: %2, size: %3)") .arg(checkRecordsSize).arg(d->pos - ppp).arg(size)); return false; } } int emptyFileIndex = 0; int sizeIndex = 0; int numAntiItems = 0; if (emptyStreamVector.isEmpty()) { emptyStreamVector.fill(false, numFiles); } if (antiFileVector.isEmpty()) { antiFileVector.fill(false, numEmptyStreams); } if (emptyFileVector.isEmpty()) { emptyFileVector.fill(false, numEmptyStreams); } for (int i = 0; i < numEmptyStreams; i++) { if (antiFileVector[i]) { numAntiItems++; } } d->outData = d->readAndDecodePackedStreams(false); int oldPos = 0; for (int i = 0; i < numFiles; i++) { FileInfo *fileInfo = d->fileInfos.at(i); bool isAnti; fileInfo->hasStream = !emptyStreamVector[i]; if (fileInfo->hasStream) { fileInfo->isDir = false; isAnti = false; fileInfo->size = d->unpackSizes[sizeIndex]; fileInfo->crc = d->digests[sizeIndex]; fileInfo->crcDefined = d->digestsDefined[sizeIndex]; sizeIndex++; } else { fileInfo->isDir = !emptyFileVector[emptyFileIndex]; isAnti = antiFileVector[emptyFileIndex]; emptyFileIndex++; fileInfo->size = 0; fileInfo->crcDefined = false; } if (numAntiItems != 0) { d->isAnti.append(isAnti); } int access; bool symlink = false; if (fileInfo->attributes & FILE_ATTRIBUTE_UNIX_EXTENSION) { access = fileInfo->attributes >> 16; if ((access & QT_STAT_MASK) == QT_STAT_LNK) { symlink = true; } } else { if (fileInfo->isDir) { access = S_IFDIR | 0755; } else { access = 0100644; } } qint64 pos = 0; if (!fileInfo->isDir) { pos = oldPos; oldPos += fileInfo->size; } KArchiveEntry *e; QString entryName; int index = fileInfo->path.lastIndexOf(QLatin1Char('/')); if (index == -1) { entryName = fileInfo->path; } else { entryName = fileInfo->path.mid(index + 1); } Q_ASSERT(!entryName.isEmpty()); QDateTime mTime; if (d->mTimesDefined[i]) { mTime = KArchivePrivate::time_tToDateTime(toTimeT(d->mTimes[i])); } else { mTime = KArchivePrivate::time_tToDateTime(time(nullptr)); } if (fileInfo->isDir) { QString path = QDir::cleanPath(fileInfo->path); const KArchiveEntry *ent = rootDir()->entry(path); if (ent && ent->isDirectory()) { e = nullptr; } else { e = new KArchiveDirectory(this, entryName, access, mTime, rootDir()->user(), rootDir()->group(), QString()/*symlink*/); } } else { if (!symlink) { e = new K7ZipFileEntry(this, entryName, access, mTime, rootDir()->user(), rootDir()->group(), QString()/*symlink*/, pos, fileInfo->size, d->outData); } else { QString target = QFile::decodeName(d->outData.mid(pos, fileInfo->size)); e = new K7ZipFileEntry(this, entryName, access, mTime, rootDir()->user(), rootDir()->group(), target, 0, 0, nullptr); } } if (e) { if (index == -1) { rootDir()->addEntry(e); } else { QString path = QDir::cleanPath(fileInfo->path.left(index)); KArchiveDirectory *d = findOrCreate(path); d->addEntry(e); } } } return true; } bool K7Zip::closeArchive() { // Unnecessary check (already checked by KArchive::close()) if (!isOpen()) { //qCWarning(KArchiveLog) << "You must open the file before close it\n"; return false; } if ((mode() == QIODevice::ReadOnly)) { return true; } d->clear(); Folder *folder = new Folder(); folder->unpackSizes.clear(); folder->unpackSizes.append(d->outData.size()); Folder::FolderInfo *info = new Folder::FolderInfo(); info->numInStreams = 1; info->numOutStreams = 1; info->methodID = k_LZMA2; quint32 dictSize = d->outData.size(); const quint32 kMinReduceSize = (1 << 16); if (dictSize < kMinReduceSize) { dictSize = kMinReduceSize; } // k_LZMA2 mehtod int dict; for (dict = 0; dict < 40; dict++) { if (dictSize <= LZMA2_DIC_SIZE_FROM_PROP(dict)) { break; } } info->properties.append(dict); folder->folderInfos.append(info); d->folders.append(folder); const KArchiveDirectory *dir = directory(); QByteArray data; d->createItemsFromEntities(dir, QString(), data); d->outData = data; folder->unpackCRCDefined = true; folder->unpackCRC = crc32(0, (Bytef *)(d->outData.data()), d->outData.size()); //compress data QByteArray encodedData; if (!d->outData.isEmpty()) { QByteArray enc; QBuffer inBuffer(&enc); KCompressionDevice flt(&inBuffer, false, KCompressionDevice::Xz); flt.open(QIODevice::WriteOnly); KFilterBase *filter = flt.filterBase(); static_cast(filter)->init(QIODevice::WriteOnly, KXzFilter::LZMA2, info->properties); const int ret = flt.write(d->outData); if (ret != d->outData.size()) { setErrorString(tr("Write error")); return false; } flt.close(); encodedData = inBuffer.data(); } d->packSizes.append(encodedData.size()); int numUnpackStream = 0; for (int i = 0; i < d->fileInfos.size(); ++i) { if (d->fileInfos.at(i)->hasStream) { numUnpackStream++; } } d->numUnpackStreamsInFolders.append(numUnpackStream); quint64 headerOffset; d->writeHeader(headerOffset); // Encode Header QByteArray encodedStream; { QVector packSizes; QVector folders; encodedStream = d->encodeStream(packSizes, folders); if (folders.isEmpty()) { // FIXME Not sure why this is an error. Come up with a better message setErrorString(tr("Failed while encoding header")); return false; } d->header.clear(); d->writeByte(kEncodedHeader); QVector emptyDefined; QVector emptyCrcs; d->writePackInfo(headerOffset, packSizes, emptyDefined, emptyCrcs); d->writeUnpackInfo(folders); d->writeByte(kEnd); for (int i = 0; i < packSizes.size(); i++) { headerOffset += packSizes.at(i); } qDeleteAll(folders); } // end encode header quint64 nextHeaderSize = d->header.size(); quint32 nextHeaderCRC = crc32(0, (Bytef *)(d->header.data()), d->header.size()); quint64 nextHeaderOffset = headerOffset; device()->seek(0); d->writeSignature(); d->writeStartHeader(nextHeaderSize, nextHeaderCRC, nextHeaderOffset); device()->write(encodedData.data(), encodedData.size()); device()->write(encodedStream.data(), encodedStream.size()); device()->write(d->header.data(), d->header.size()); return true; } bool K7Zip::doFinishWriting(qint64 size) { d->m_currentFile->setSize(size); d->m_currentFile = nullptr; return true; } bool K7Zip::writeData(const char *data, qint64 size) { if (!d->m_currentFile) { setErrorString(tr("No file currently selected")); return false; } if (d->m_currentFile->position() == d->outData.size()) { d->outData.append(data, size); } else { d->outData.remove(d->m_currentFile->position(), d->m_currentFile->size()); d->outData.insert(d->m_currentFile->position(), data, size); } return true; } bool K7Zip::doPrepareWriting(const QString &name, const QString &user, const QString &group, qint64 /*size*/, mode_t perm, const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/) { if (!isOpen()) { setErrorString(tr("Application error: 7-Zip file must be open before being written into")); qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()"; return false; } if (!(mode() & QIODevice::WriteOnly)) { setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file")); qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)"; return false; } // Find or create parent dir KArchiveDirectory *parentDir = rootDir(); //QString fileName( name ); // In some files we can find dir/./file => call cleanPath QString fileName(QDir::cleanPath(name)); int i = name.lastIndexOf(QLatin1Char('/')); if (i != -1) { QString dir = name.left(i); fileName = name.mid(i + 1); parentDir = findOrCreate(dir); } // test if the entry already exist const KArchiveEntry *entry = parentDir->entry(fileName); if (!entry) { K7ZipFileEntry *e = new K7ZipFileEntry(this, fileName, perm, mtime, user, group, QString()/*symlink*/, d->outData.size(), 0 /*unknown yet*/, d->outData); parentDir->addEntry(e); d->m_entryList << e; d->m_currentFile = e; } else { // TODO : find and replace in m_entryList //d->m_currentFile = static_cast(entry); } return true; } bool K7Zip::doWriteDir(const QString &name, const QString &user, const QString &group, mode_t perm, const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/) { if (!isOpen()) { setErrorString(tr("Application error: 7-Zip file must be open before being written into")); qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()"; return false; } if (!(mode() & QIODevice::WriteOnly)) { //qCWarning(KArchiveLog) << "You must open the tar file for writing\n"; return false; } // In some tar files we can find dir/./ => call cleanPath QString dirName(QDir::cleanPath(name)); // Remove trailing '/' if (dirName.endsWith(QLatin1Char('/'))) { dirName.remove(dirName.size() - 1, 1); } KArchiveDirectory *parentDir = rootDir(); int i = dirName.lastIndexOf(QLatin1Char('/')); if (i != -1) { QString dir = name.left(i); dirName = name.mid(i + 1); parentDir = findOrCreate(dir); } KArchiveDirectory *e = new KArchiveDirectory(this, dirName, perm, mtime, user, group, QString()/*symlink*/); parentDir->addEntry(e); return true; } bool K7Zip::doWriteSymLink(const QString &name, const QString &target, const QString &user, const QString &group, mode_t perm, const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/) { if (!isOpen()) { setErrorString(tr("Application error: 7-Zip file must be open before being written into")); qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()"; return false; } if (!(mode() & QIODevice::WriteOnly)) { setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file")); qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)"; return false; } // Find or create parent dir KArchiveDirectory *parentDir = rootDir(); // In some files we can find dir/./file => call cleanPath QString fileName(QDir::cleanPath(name)); int i = name.lastIndexOf(QLatin1Char('/')); if (i != -1) { QString dir = name.left(i); fileName = name.mid(i + 1); parentDir = findOrCreate(dir); } QByteArray encodedTarget = QFile::encodeName(target); K7ZipFileEntry *e = new K7ZipFileEntry(this, fileName, perm, mtime, user, group, target, 0, 0, nullptr); d->outData.append(encodedTarget); parentDir->addEntry(e); d->m_entryList << e; return true; } void K7Zip::virtual_hook(int id, void *data) { KArchive::virtual_hook(id, data); } diff --git a/src/krcc.cpp b/src/krcc.cpp index c14ad03..99366a5 100644 --- a/src/krcc.cpp +++ b/src/krcc.cpp @@ -1,174 +1,174 @@ /* This file is part of the KDE libraries Copyright (C) 2014 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "krcc.h" #include "karchive_p.h" #include "loggingcategory.h" #include #include #include #include #include #include #include class Q_DECL_HIDDEN KRcc::KRccPrivate { public: KRccPrivate() { } void createEntries(const QDir &dir, KArchiveDirectory *parentDir, KRcc *q); QString m_prefix; // '/' + uuid }; /** * A KRccFileEntry represents a file in a rcc archive. */ class KARCHIVE_EXPORT KRccFileEntry : public KArchiveFile { public: KRccFileEntry(KArchive *archive, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, qint64 size, const QString &resourcePath) : KArchiveFile(archive, name, access, date, user, group, QString(), 0, size) , m_resourcePath(resourcePath) { } QByteArray data() const override { QFile f(m_resourcePath); if (f.open(QIODevice::ReadOnly)) { return f.readAll(); } qCWarning(KArchiveLog) << "Couldn't open" << m_resourcePath; return QByteArray(); } QIODevice *createDevice() const override { return new QFile(m_resourcePath); } private: QString m_resourcePath; }; KRcc::KRcc(const QString &filename) : KArchive(filename) , d(new KRccPrivate) { } KRcc::~KRcc() { if (isOpen()) { close(); } delete d; } bool KRcc::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &) { setErrorString(tr("Cannot write to RCC file")); qCWarning(KArchiveLog) << "doPrepareWriting not implemented for KRcc"; return false; } bool KRcc::doFinishWriting(qint64) { setErrorString(tr("Cannot write to RCC file")); qCWarning(KArchiveLog) << "doFinishWriting not implemented for KRcc"; return false; } bool KRcc::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &) { setErrorString(tr("Cannot write to RCC file")); qCWarning(KArchiveLog) << "doWriteDir not implemented for KRcc"; return false; } bool KRcc::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &) { setErrorString(tr("Cannot write to RCC file")); qCWarning(KArchiveLog) << "doWriteSymLink not implemented for KRcc"; return false; } bool KRcc::openArchive(QIODevice::OpenMode mode) { // Open archive if (mode == QIODevice::WriteOnly) { return true; } if (mode != QIODevice::ReadOnly && mode != QIODevice::ReadWrite) { setErrorString(tr("Unsupported mode %1").arg(mode)); return false; } QUuid uuid = QUuid::createUuid(); d->m_prefix = QLatin1Char('/') + uuid.toString(); if (!QResource::registerResource(fileName(), d->m_prefix)) { setErrorString( tr("Failed to register resource %1 under prefix %2") .arg(fileName(), d->m_prefix)); return false; } QDir dir(QLatin1Char(':') + d->m_prefix); d->createEntries(dir, rootDir(), this); return true; } void KRcc::KRccPrivate::createEntries(const QDir &dir, KArchiveDirectory *parentDir, KRcc *q) { - Q_FOREACH (const QString &fileName, dir.entryList()) { + for (const QString &fileName : dir.entryList()) { const QString entryPath = dir.path() + QLatin1Char('/') + fileName; const QFileInfo info(entryPath); if (info.isFile()) { KArchiveEntry *entry = new KRccFileEntry(q, fileName, 0444, info.lastModified(), parentDir->user(), parentDir->group(), info.size(), entryPath); parentDir->addEntry(entry); } else { KArchiveDirectory *entry = new KArchiveDirectory(q, fileName, 0555, info.lastModified(), parentDir->user(), parentDir->group(), /*symlink*/ QString()); parentDir->addEntry(entry); createEntries(QDir(entryPath), entry, q); } } } bool KRcc::closeArchive() { // Close the archive QResource::unregisterResource(fileName(), d->m_prefix); // ignore errors return true; } void KRcc::virtual_hook(int id, void *data) { KArchive::virtual_hook(id, data); } diff --git a/tests/kziptest.cpp b/tests/kziptest.cpp index 3401a3e..cb3ff8b 100644 --- a/tests/kziptest.cpp +++ b/tests/kziptest.cpp @@ -1,307 +1,310 @@ /* * Copyright (C) 2002-2013 David Faure * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kzip.h" #include "kcompressiondevice.h" #include #include #include #include void recursive_print(const KArchiveDirectory *dir, const QString &path) { - foreach (const QString &it, dir->entries()) { + const QStringList lst = dir->entries(); + for (const QString &it : lst) { const KArchiveEntry *entry = dir->entry(it); printf("mode=%07o %s %s \"%s%s\" size: %lld pos: %lld isdir=%d%s", entry->permissions(), entry->user().toLatin1().constData(), entry->group().toLatin1().constData(), path.toLatin1().constData(), it.toLatin1().constData(), entry->isDirectory() ? 0 : (static_cast(entry))->size(), entry->isDirectory() ? 0 : (static_cast(entry))->position(), entry->isDirectory(), entry->symLinkTarget().isEmpty() ? "" : QStringLiteral(" symlink: %1").arg(entry->symLinkTarget()).toLatin1().constData()); // if (!entry->isDirectory()) printf("%d", (static_cast(entry))->size()); printf("\n"); if (entry->isDirectory()) { recursive_print(static_cast(entry), path + it + '/'); } } } void recursive_transfer(const KArchiveDirectory *dir, const QString &path, KZip *zip) { - foreach (const QString &it, dir->entries()) { + const QStringList lst = dir->entries(); + for (const QString &it : lst) { const KArchiveEntry *e = dir->entry(it); qDebug() << "actual file: " << e->name(); if (e->isFile()) { Q_ASSERT(e && e->isFile()); const KArchiveFile *f = static_cast(e); printf("FILE=%s\n", qPrintable(e->name())); QByteArray arr(f->data()); printf("SIZE=%i\n", arr.size()); QString str(arr); printf("DATA=%s\n", qPrintable(str)); if (e->symLinkTarget().isEmpty()) { zip->writeFile(path + e->name(), arr); } else { zip->writeSymLink(path + e->name(), e->symLinkTarget()); } } else if (e->isDirectory()) { recursive_transfer(static_cast(e), path + e->name() + '/', zip); } } } static int doList(const QString &fileName) { KZip zip(fileName); if (!zip.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid."; return 1; } const KArchiveDirectory *dir = zip.directory(); recursive_print(dir, QString()); zip.close(); return 0; } static int doPrintAll(const QString &fileName) { KZip zip(fileName); qDebug() << "Opening zip file"; if (!zip.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid."; return 1; } const KArchiveDirectory *dir = zip.directory(); qDebug() << "Listing toplevel of zip file"; - foreach (const QString &it, dir->entries()) { + const QStringList lst = dir->entries(); + for (const QString &it : lst) { const KArchiveEntry *e = dir->entry(it); qDebug() << "Printing" << it; if (e->isFile()) { Q_ASSERT(e && e->isFile()); const KArchiveFile *f = static_cast(e); const QByteArray data(f->data()); printf("SIZE=%i\n", data.size()); QString str = QString::fromUtf8(data); printf("DATA=%s\n", qPrintable(str)); } } zip.close(); return 0; } static int doSave(const QString &fileName) { KZip zip(fileName); if (!zip.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fileName << "for writing"; return 1; } const QByteArray data = "This is the data for the main file"; bool writeOk = zip.writeFile(QStringLiteral("maindoc.txt"), data); if (!writeOk) { qWarning() << "Write error (main file)"; return 1; } const QByteArray data2 = "This is the data for the other file"; writeOk = zip.writeFile(QStringLiteral("subdir/other.txt"), data2); if (!writeOk) { qWarning() << "Write error (other file)"; return 1; } //writeOk = zip.addLocalFile("David.jpg", "picture.jpg"); //if (!writeOk) { // qWarning() << "Write error (picture)"; // return 1; //} return 0; } static int doLoad(const QString &fileName) { KZip zip(fileName); if (!zip.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid."; return 1; } const KArchiveDirectory *dir = zip.directory(); const KArchiveEntry *mainEntry = dir->entry(QStringLiteral("maindoc.txt")); Q_ASSERT(mainEntry && mainEntry->isFile()); const KArchiveFile *mainFile = static_cast(mainEntry); qDebug() << "maindoc.txt:" << mainFile->data(); return 0; } static int doPrint(const QString &fileName, const QString &entryName) { KZip zip(fileName); if (!zip.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid."; return 1; } const KArchiveDirectory *dir = zip.directory(); const KArchiveEntry *e = dir->entry(entryName); Q_ASSERT(e && e->isFile()); const KArchiveFile *f = static_cast(e); const QByteArray arr(f->data()); printf("SIZE=%i\n", arr.size()); QString str = QString::fromUtf8(arr); printf("%s", qPrintable(str)); return zip.close() ? 0 : 1 /*error*/; } static int doUpdate(const QString &archiveName, const QString &fileName) { KZip zip(archiveName); if (!zip.open(QIODevice::ReadWrite)) { qWarning() << "Could not open" << archiveName << "for read/write"; return 1; } QFile f(fileName); if (!f.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << fileName << "for reading."; return 1; } zip.writeFile(fileName, f.readAll()); return zip.close() ? 0 : 1 /*error*/; } static int doTransfer(const QString &sourceFile, const QString &destFile) { KZip zip1(sourceFile); KZip zip2(destFile); if (!zip1.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << sourceFile << "for reading. ZIP file doesn't exist or is invalid."; return 1; } if (!zip2.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << destFile << "for writing"; return 1; } const KArchiveDirectory *dir1 = zip1.directory(); recursive_transfer(dir1, QLatin1String(""), &zip2); zip1.close(); zip2.close(); return 0; } static bool save(QIODevice *device) { const QByteArray data = "This is some text that will be compressed.\n"; const int written = device->write(data); if (written != data.size()) { qWarning() << "Error writing data"; return 1; } // success return 0; } static int doCompress(const QString &fileName) { KCompressionDevice device(fileName, KCompressionDevice::BZip2); if (!device.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fileName << "for writing"; return 1; } return save(&device); } static bool load(QIODevice *device) { const QByteArray data = device->readAll(); printf("%s", data.constData()); return true; } static int doUncompress(const QString &fileName) { KCompressionDevice device(fileName, KCompressionDevice::BZip2); if (!device.open(QIODevice::ReadOnly)) { qWarning() << "Could not open" << fileName << "for reading"; return 1; } return load(&device); } int main(int argc, char **argv) { if (argc < 3) { // ###### Note: please consider adding new tests to karchivetest (so that they can be automated) // rather than here (interactive) printf("\n" " Usage :\n" " ./kziptest list /path/to/existing_file.zip tests listing an existing zip\n" " ./kziptest print-all file.zip prints contents of all files.\n" " ./kziptest print file.zip filename prints contents of one file.\n" " ./kziptest update file.zip filename update filename in file.zip.\n" " ./kziptest save file.zip save file.\n" " ./kziptest load file.zip load file.\n" " ./kziptest write file.bz2 write compressed file.\n" " ./kziptest read file.bz2 read uncompressed file.\n" ); return 1; } QCoreApplication app(argc, argv); QString command = argv[1]; if (command == QLatin1String("list")) { return doList(QFile::decodeName(argv[2])); } else if (command == QLatin1String("print-all")) { return doPrintAll(QFile::decodeName(argv[2])); } else if (command == QLatin1String("print")) { if (argc != 4) { printf("usage: kziptest print archivename filename"); return 1; } return doPrint(QFile::decodeName(argv[2]), argv[3]); } else if (command == QLatin1String("save")) { return doSave(QFile::decodeName(argv[2])); } else if (command == QLatin1String("load")) { return doLoad(QFile::decodeName(argv[2])); } else if (command == QLatin1String("write")) { return doCompress(QFile::decodeName(argv[2])); } else if (command == QLatin1String("read")) { return doUncompress(QFile::decodeName(argv[2])); } else if (command == QLatin1String("update")) { if (argc != 4) { printf("usage: kziptest update archivename filename"); return 1; } return doUpdate(QFile::decodeName(argv[2]), QFile::decodeName(argv[3])); } else if (command == QLatin1String("transfer")) { if (argc != 4) { printf("usage: kziptest transfer sourcefile destfile"); return 1; } return doTransfer(QFile::decodeName(argv[2]), QFile::decodeName(argv[3])); } else { printf("Unknown command\n"); } }