File indexing completed on 2024-04-28 15:18:38

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2006, 2010 David Faure <faure@kde.org>
0003    SPDX-FileCopyrightText: 2012 Mario Bensi <mbensi@ipsquad.net>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "karchivetest.h"
0009 #include <k7zip.h>
0010 #include <kar.h>
0011 #include <krcc.h>
0012 #include <ktar.h>
0013 #include <kzip.h>
0014 
0015 #include <QDebug>
0016 #include <QTest>
0017 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0018 #include <QTextCodec>
0019 #endif
0020 #include <QFileInfo>
0021 #include <QRegularExpression>
0022 #include <QSaveFile>
0023 #include <QStandardPaths>
0024 #include <QTemporaryDir>
0025 #include <kcompressiondevice.h>
0026 
0027 #ifndef Q_OS_WIN
0028 #include <cerrno>
0029 #include <unistd.h> // symlink
0030 #endif
0031 
0032 #ifdef Q_OS_WIN
0033 #include <QScopedValueRollback>
0034 #include <Windows.h>
0035 #else
0036 #include <grp.h>
0037 #include <pwd.h>
0038 #endif
0039 
0040 QTEST_MAIN(KArchiveTest)
0041 
0042 void initLocale()
0043 {
0044 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0045     // KArchive uses QFile::decodeName, and our tests use utf8 encoding for filenames
0046     QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));
0047 #endif
0048 }
0049 Q_CONSTRUCTOR_FUNCTION(initLocale)
0050 
0051 static const int SIZE1 = 100;
0052 
0053 /**
0054  * Writes test fileset specified archive
0055  * @param archive archive
0056  */
0057 static void writeTestFilesToArchive(KArchive *archive)
0058 {
0059     QVERIFY(archive->writeFile("empty", "", 0100644, "weis", "users"));
0060     QVERIFY(archive->writeFile("test1", QByteArray("Hallo"), 0100440, QString("weis"), QString("users")));
0061     // Now let's try with the prepareWriting/writeData/finishWriting API
0062     QVERIFY(archive->prepareWriting("test2", "weis", "users", 8));
0063     QVERIFY(archive->writeData("Hallo ", 6));
0064     QVERIFY(archive->writeData("Du", 2));
0065     QVERIFY(archive->finishWriting(8));
0066     // Add local file
0067     QFile localFile(QStringLiteral("test3"));
0068     QVERIFY(localFile.open(QIODevice::WriteOnly));
0069     QVERIFY(localFile.write("Noch so einer", 13) == 13);
0070     localFile.close();
0071 
0072     QVERIFY(archive->addLocalFile("test3", "z/test3"));
0073 
0074     // writeFile API
0075     QVERIFY(archive->writeFile("my/dir/test3", "I do not speak German\nDavid.", 0100644, "dfaure", "hackers"));
0076 
0077     // Now a medium file : 100 null bytes
0078     char medium[SIZE1] = {0};
0079     QVERIFY(archive->writeFile("mediumfile", QByteArray(medium, SIZE1)));
0080     // Another one, with an absolute path
0081     QVERIFY(archive->writeFile("/dir/subdir/mediumfile2", QByteArray(medium, SIZE1)));
0082 
0083     // Now a huge file : 20000 null bytes
0084     int n = 20000;
0085     char *huge = new char[n];
0086     memset(huge, 0, n);
0087     QVERIFY(archive->writeFile("hugefile", QByteArray(huge, n)));
0088     delete[] huge;
0089 
0090     // Now an empty directory
0091     QVERIFY(archive->writeDir("aaaemptydir"));
0092 
0093 #ifndef Q_OS_WIN
0094     // Add local symlink
0095     QVERIFY(archive->addLocalFile("test3_symlink", "z/test3_symlink"));
0096 #endif
0097 
0098     // Add executable
0099     QVERIFY(archive->writeFile("executableAll", "#!/bin/sh\necho hi", 0100755));
0100 }
0101 
0102 static QString getCurrentUserName()
0103 {
0104 #if defined(Q_OS_UNIX)
0105     struct passwd *pw = getpwuid(getuid());
0106     return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid());
0107 #elif defined(Q_OS_WIN)
0108     wchar_t buffer[255];
0109     DWORD size = 255;
0110     bool ok = GetUserNameW(buffer, &size);
0111     if (!ok) {
0112         return QString();
0113     }
0114     return QString::fromWCharArray(buffer);
0115 #else
0116     return QString();
0117 #endif
0118 }
0119 
0120 static QString getCurrentGroupName()
0121 {
0122 #if defined(Q_OS_UNIX)
0123     struct group *grp = getgrgid(getgid());
0124     return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid());
0125 #elif defined(Q_OS_WIN)
0126     return QString();
0127 #else
0128     return QString();
0129 #endif
0130 }
0131 
0132 enum ListingFlags {
0133     WithUserGroup = 1,
0134     WithTime = 0x02,
0135 }; // ListingFlags
0136 
0137 static QStringList recursiveListEntries(const KArchiveDirectory *dir, const QString &path, int listingFlags)
0138 {
0139     QStringList ret;
0140     QStringList l = dir->entries();
0141     l.sort();
0142     for (const QString &it : std::as_const(l)) {
0143         const KArchiveEntry *entry = dir->entry(it);
0144 
0145         QString descr;
0146         descr += QStringLiteral("mode=") + QString::number(entry->permissions(), 8) + ' ';
0147         if (listingFlags & WithUserGroup) {
0148             descr += QStringLiteral("user=") + entry->user() + ' ';
0149             descr += QStringLiteral("group=") + entry->group() + ' ';
0150         }
0151         descr += QStringLiteral("path=") + path + (it) + ' ';
0152         descr += QStringLiteral("type=") + (entry->isDirectory() ? "dir" : "file");
0153         if (entry->isFile()) {
0154             descr += QStringLiteral(" size=") + QString::number(static_cast<const KArchiveFile *>(entry)->size());
0155         }
0156         if (!entry->symLinkTarget().isEmpty()) {
0157             descr += QStringLiteral(" symlink=") + entry->symLinkTarget();
0158         }
0159 
0160         if (listingFlags & WithTime) {
0161             descr += QStringLiteral(" time=") + entry->date().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"));
0162         }
0163 
0164         // qDebug() << descr;
0165         ret.append(descr);
0166 
0167         if (entry->isDirectory()) {
0168             ret += recursiveListEntries((KArchiveDirectory *)entry, path + it + '/', listingFlags);
0169         }
0170     }
0171     return ret;
0172 }
0173 
0174 /**
0175  * Verifies contents of specified archive against test fileset
0176  * @param archive archive
0177  */
0178 static void testFileData(KArchive *archive)
0179 {
0180     const KArchiveDirectory *dir = archive->directory();
0181 
0182     const KArchiveFile *f = dir->file(QStringLiteral("z/test3"));
0183     QVERIFY(f);
0184 
0185     QByteArray arr(f->data());
0186     QCOMPARE(arr.size(), 13);
0187     QCOMPARE(arr, QByteArray("Noch so einer"));
0188 
0189     // Now test using createDevice()
0190     QIODevice *dev = f->createDevice();
0191     QByteArray contents = dev->readAll();
0192     QCOMPARE(contents, arr);
0193     delete dev;
0194 
0195     dev = f->createDevice();
0196     contents = dev->read(5); // test reading in two chunks
0197     QCOMPARE(contents.size(), 5);
0198     contents += dev->read(50);
0199     QCOMPARE(contents.size(), 13);
0200     QCOMPARE(QString::fromLatin1(contents.constData()), QString::fromLatin1(arr.constData()));
0201     delete dev;
0202 
0203     // test read/seek/peek work fine
0204     f = dir->file(QStringLiteral("test1"));
0205     dev = f->createDevice();
0206     contents = dev->peek(4);
0207     QCOMPARE(contents, QByteArray("Hall"));
0208     contents = dev->peek(2);
0209     QCOMPARE(contents, QByteArray("Ha"));
0210     dev->seek(2);
0211     contents = dev->peek(2);
0212     QCOMPARE(contents, QByteArray("ll"));
0213     dev->seek(0);
0214     contents = dev->read(2);
0215     QCOMPARE(contents, QByteArray("Ha"));
0216     contents = dev->peek(2);
0217     QCOMPARE(contents, QByteArray("ll"));
0218     dev->seek(1);
0219     contents = dev->read(1);
0220     QCOMPARE(contents, QByteArray("a"));
0221     dev->seek(4);
0222     contents = dev->read(1);
0223     QCOMPARE(contents, QByteArray("o"));
0224     delete dev;
0225 
0226     const KArchiveEntry *e = dir->entry(QStringLiteral("mediumfile"));
0227     QVERIFY(e && e->isFile());
0228     f = (KArchiveFile *)e;
0229     QCOMPARE(f->data().size(), SIZE1);
0230 
0231     f = dir->file(QStringLiteral("hugefile"));
0232     QCOMPARE(f->data().size(), 20000);
0233 
0234     e = dir->entry(QStringLiteral("aaaemptydir"));
0235     QVERIFY(e && e->isDirectory());
0236     QVERIFY(!dir->file("aaaemptydir"));
0237 
0238     e = dir->entry(QStringLiteral("my/dir/test3"));
0239     QVERIFY(e && e->isFile());
0240     f = (KArchiveFile *)e;
0241     dev = f->createDevice();
0242     QByteArray firstLine = dev->readLine();
0243     QCOMPARE(QString::fromLatin1(firstLine.constData()), QString::fromLatin1("I do not speak German\n"));
0244     QByteArray secondLine = dev->read(100);
0245     QCOMPARE(QString::fromLatin1(secondLine.constData()), QString::fromLatin1("David."));
0246     delete dev;
0247 #ifndef Q_OS_WIN
0248     e = dir->entry(QStringLiteral("z/test3_symlink"));
0249     QVERIFY(e);
0250     QVERIFY(e->isFile());
0251     QCOMPARE(e->symLinkTarget(), QString("test3"));
0252 #endif
0253 
0254     // Test "./" prefix for KOffice (xlink:href="./ObjectReplacements/Object 1")
0255     e = dir->entry(QStringLiteral("./hugefile"));
0256     QVERIFY(e && e->isFile());
0257     e = dir->entry(QStringLiteral("./my/dir/test3"));
0258     QVERIFY(e && e->isFile());
0259 
0260     // Test directory entries
0261     e = dir->entry(QStringLiteral("my"));
0262     QVERIFY(e && e->isDirectory());
0263     e = dir->entry(QStringLiteral("my/"));
0264     QVERIFY(e && e->isDirectory());
0265     e = dir->entry(QStringLiteral("./my/"));
0266     QVERIFY(e && e->isDirectory());
0267 }
0268 
0269 static void testReadWrite(KArchive *archive)
0270 {
0271     QVERIFY(archive->writeFile("newfile", "New File", 0100440, "dfaure", "users"));
0272 }
0273 
0274 #ifdef Q_OS_WIN
0275 extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
0276 #endif
0277 
0278 static void testCopyTo(KArchive *archive)
0279 {
0280     const KArchiveDirectory *dir = archive->directory();
0281     QTemporaryDir tmpDir;
0282     const QString dirName = tmpDir.path() + '/';
0283 
0284     QVERIFY(dir->copyTo(dirName));
0285 
0286     QVERIFY(QFile::exists(dirName + "dir"));
0287     QVERIFY(QFileInfo(dirName + "dir").isDir());
0288 
0289     QFileInfo fileInfo1(dirName + "dir/subdir/mediumfile2");
0290     QVERIFY(fileInfo1.exists());
0291     QVERIFY(fileInfo1.isFile());
0292     QCOMPARE(fileInfo1.size(), Q_INT64_C(100));
0293 
0294     QFileInfo fileInfo2(dirName + "hugefile");
0295     QVERIFY(fileInfo2.exists());
0296     QVERIFY(fileInfo2.isFile());
0297     QCOMPARE(fileInfo2.size(), Q_INT64_C(20000));
0298 
0299     QFileInfo fileInfo3(dirName + "mediumfile");
0300     QVERIFY(fileInfo3.exists());
0301     QVERIFY(fileInfo3.isFile());
0302     QCOMPARE(fileInfo3.size(), Q_INT64_C(100));
0303 
0304     QFileInfo fileInfo4(dirName + "my/dir/test3");
0305     QVERIFY(fileInfo4.exists());
0306     QVERIFY(fileInfo4.isFile());
0307     QCOMPARE(fileInfo4.size(), Q_INT64_C(28));
0308 
0309 #ifndef Q_OS_WIN
0310     const QString fileName = dirName + "z/test3_symlink";
0311     const QFileInfo fileInfo5(fileName);
0312     QVERIFY(fileInfo5.exists());
0313     QVERIFY(fileInfo5.isFile());
0314     // Do not use fileInfo.symLinkTarget() for unix symlinks
0315     // It returns the -full- path to the target, while we want the target string "as is".
0316     QString symLinkTarget;
0317     const QByteArray encodedFileName = QFile::encodeName(fileName);
0318     QByteArray s;
0319 #if defined(PATH_MAX)
0320     s.resize(PATH_MAX + 1);
0321 #else
0322     int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX);
0323     if (path_max <= 0) {
0324         path_max = 4096;
0325     }
0326     s.resize(path_max);
0327 #endif
0328     int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
0329     if (len >= 0) {
0330         s[len] = '\0';
0331         symLinkTarget = QFile::decodeName(s.constData());
0332     }
0333     QCOMPARE(symLinkTarget, QString("test3"));
0334 #endif
0335 
0336 #ifdef Q_OS_WIN
0337     QScopedValueRollback<int> ntfsMode(qt_ntfs_permission_lookup);
0338     qt_ntfs_permission_lookup++;
0339 #endif
0340     QVERIFY(QFileInfo(dirName + "executableAll").permissions() & (QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther));
0341 }
0342 
0343 /**
0344  * Prepares dataset for archive filter tests
0345  */
0346 void KArchiveTest::setupData()
0347 {
0348     QTest::addColumn<QString>("fileName");
0349     QTest::addColumn<QString>("mimeType");
0350 
0351     QTest::newRow(".tar.gz") << "karchivetest.tar.gz"
0352                              << "application/gzip";
0353 #if HAVE_BZIP2_SUPPORT
0354     QTest::newRow(".tar.bz2") << "karchivetest.tar.bz2"
0355                               << "application/x-bzip";
0356 #endif
0357 #if HAVE_XZ_SUPPORT
0358     QTest::newRow(".tar.lzma") << "karchivetest.tar.lzma"
0359                                << "application/x-lzma";
0360     QTest::newRow(".tar.xz") << "karchivetest.tar.xz"
0361                              << "application/x-xz";
0362 #endif
0363 #if HAVE_ZSTD_SUPPORT
0364     QTest::newRow(".tar.zst") << "karchivetest.tar.zst"
0365                               << "application/zstd";
0366 #endif
0367 }
0368 
0369 /**
0370  * @see QTest::initTestCase()
0371  */
0372 void KArchiveTest::initTestCase()
0373 {
0374 #ifndef Q_OS_WIN
0375     // Prepare local symlink
0376     QFile::remove(QStringLiteral("test3_symlink"));
0377     if (::symlink("test3", "test3_symlink") != 0) {
0378         qDebug() << errno;
0379         QVERIFY(false);
0380     }
0381 #endif
0382     // avoid interference from kdebugsettings on qCWarning
0383     QStandardPaths::setTestModeEnabled(true);
0384 }
0385 
0386 void KArchiveTest::testEmptyFilename()
0387 {
0388     QTest::ignoreMessage(QtWarningMsg, "KArchive: No file name specified");
0389     KTar tar(QLatin1String(""));
0390     QVERIFY(!tar.open(QIODevice::ReadOnly));
0391     QCOMPARE(tar.errorString(), tr("No filename or device was specified"));
0392 }
0393 
0394 void KArchiveTest::testNullDevice()
0395 {
0396     QIODevice *nil = nullptr;
0397     QTest::ignoreMessage(QtWarningMsg, "KArchive: Null device specified");
0398     KTar tar(nil);
0399     QVERIFY(!tar.open(QIODevice::ReadOnly));
0400     QCOMPARE(tar.errorString(), tr("No filename or device was specified"));
0401 }
0402 
0403 void KArchiveTest::testNonExistentFile()
0404 {
0405     KTar tar(QStringLiteral("nonexistent.tar.gz"));
0406     QVERIFY(!tar.open(QIODevice::ReadOnly));
0407     QCOMPARE(tar.errorString(), tr("File %1 does not exist").arg("nonexistent.tar.gz"));
0408 }
0409 
0410 void KArchiveTest::testCreateTar_data()
0411 {
0412     QTest::addColumn<QString>("fileName");
0413     QTest::newRow(".tar") << "karchivetest.tar";
0414 }
0415 
0416 /**
0417  * @dataProvider testCreateTar_data
0418  */
0419 void KArchiveTest::testCreateTar()
0420 {
0421     QFETCH(QString, fileName);
0422 
0423     // With    tempfile: 0.7-0.8 ms, 994236 instr. loads
0424     // Without tempfile:    0.81 ms, 992541 instr. loads
0425     // Note: use ./karchivetest 2>&1 | grep ms
0426     //       to avoid being slowed down by the kDebugs.
0427     QBENCHMARK {
0428         KTar tar(fileName);
0429         QVERIFY(tar.open(QIODevice::WriteOnly));
0430 
0431         writeTestFilesToArchive(&tar);
0432 
0433         QVERIFY(tar.close());
0434 
0435         QFileInfo fileInfo(QFile::encodeName(fileName));
0436         QVERIFY(fileInfo.exists());
0437         // We can't check for an exact size because of the addLocalFile, whose data is system-dependent
0438         QVERIFY(fileInfo.size() > 450);
0439     }
0440 
0441     // NOTE The only .tar test, cleanup here
0442     // QFile::remove(fileName);
0443 }
0444 
0445 /**
0446  * @dataProvider setupData
0447  */
0448 void KArchiveTest::testCreateTarXXX()
0449 {
0450     QFETCH(QString, fileName);
0451 
0452     // With    tempfile: 1.3-1.7 ms, 2555089 instr. loads
0453     // Without tempfile:    0.87 ms,  987915 instr. loads
0454     QBENCHMARK {
0455         KTar tar(fileName);
0456         QVERIFY(tar.open(QIODevice::WriteOnly));
0457 
0458         writeTestFilesToArchive(&tar);
0459 
0460         QVERIFY(tar.close());
0461 
0462         QFileInfo fileInfo(QFile::encodeName(fileName));
0463         QVERIFY(fileInfo.exists());
0464         // We can't check for an exact size because of the addLocalFile, whose data is system-dependent
0465         QVERIFY(fileInfo.size() > 350);
0466     }
0467 }
0468 
0469 // static void compareEntryWithTimestamp(const QString &dateString, const QString &expectedDateString, const QDateTime &expectedDateTime)
0470 // Made it a macro so that line numbers are meaningful on failure
0471 
0472 #define compareEntryWithTimestamp(dateString, expectedDateString, expectedDateTime)                                                                            \
0473     {                                                                                                                                                          \
0474         /* Take the time from the listing and chop it off */                                                                                                   \
0475         const QDateTime dt = QDateTime::fromString(dateString.right(19), "dd.MM.yyyy hh:mm:ss");                                                               \
0476         QString _str(dateString);                                                                                                                              \
0477         _str.chop(25);                                                                                                                                         \
0478         QCOMPARE(_str, expectedDateString);                                                                                                                    \
0479                                                                                                                                                                \
0480         /* Compare the times separately with allowed 2 sec diversion */                                                                                        \
0481         if (dt.secsTo(expectedDateTime) > 2) {                                                                                                                 \
0482             qWarning() << dt << "is too different from" << expectedDateTime;                                                                                   \
0483         }                                                                                                                                                      \
0484         QVERIFY(dt.secsTo(expectedDateTime) <= 2);                                                                                                             \
0485     }
0486 
0487 /**
0488  * @dataProvider setupData
0489  */
0490 void KArchiveTest::testReadTar() // testCreateTarGz must have been run first.
0491 {
0492     QFETCH(QString, fileName);
0493 
0494     QFileInfo localFileData(QStringLiteral("test3"));
0495 
0496     const QString systemUserName = getCurrentUserName();
0497     const QString systemGroupName = getCurrentGroupName();
0498     const QString owner = localFileData.owner();
0499     const QString group = localFileData.group();
0500     const QString emptyTime = QDateTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"));
0501     const QDateTime creationTime = QFileInfo(fileName).birthTime();
0502 
0503     // 1.6-1.7 ms per interaction, 2908428 instruction loads
0504     // After the "no tempfile when writing fix" this went down
0505     // to 0.9-1.0 ms, 1689059 instruction loads.
0506     // I guess it finds the data in the kernel cache now that no KTempFile is
0507     // used when writing.
0508     QBENCHMARK {
0509         KTar tar(fileName);
0510 
0511         QVERIFY(tar.open(QIODevice::ReadOnly));
0512 
0513         const KArchiveDirectory *dir = tar.directory();
0514         QVERIFY(dir != nullptr);
0515         const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup | WithTime);
0516 
0517 #ifndef Q_OS_WIN
0518         const int expectedCount = 16;
0519 #else
0520         const int expectedCount = 15;
0521 #endif
0522         if (listing.count() != expectedCount) {
0523             qWarning() << listing;
0524         }
0525         QCOMPARE(listing.count(), expectedCount);
0526         compareEntryWithTimestamp(listing[0], QString("mode=40755 user= group= path=aaaemptydir type=dir"), creationTime);
0527 
0528         QCOMPARE(listing[1], QString("mode=40777 user=%1 group=%2 path=dir type=dir time=%3").arg(systemUserName).arg(systemGroupName).arg(emptyTime));
0529         QCOMPARE(listing[2], QString("mode=40777 user=%1 group=%2 path=dir/subdir type=dir time=%3").arg(systemUserName).arg(systemGroupName).arg(emptyTime));
0530         compareEntryWithTimestamp(listing[3], QString("mode=100644 user= group= path=dir/subdir/mediumfile2 type=file size=100"), creationTime);
0531         compareEntryWithTimestamp(listing[4], QString("mode=100644 user=weis group=users path=empty type=file size=0"), creationTime);
0532         compareEntryWithTimestamp(listing[5], QString("mode=100755 user= group= path=executableAll type=file size=17"), creationTime);
0533         compareEntryWithTimestamp(listing[6], QString("mode=100644 user= group= path=hugefile type=file size=20000"), creationTime);
0534         compareEntryWithTimestamp(listing[7], QString("mode=100644 user= group= path=mediumfile type=file size=100"), creationTime);
0535         QCOMPARE(listing[8], QString("mode=40777 user=%1 group=%2 path=my type=dir time=").arg(systemUserName).arg(systemGroupName));
0536         QCOMPARE(listing[9], QString("mode=40777 user=%1 group=%2 path=my/dir type=dir time=").arg(systemUserName).arg(systemGroupName));
0537         compareEntryWithTimestamp(listing[10], QString("mode=100644 user=dfaure group=hackers path=my/dir/test3 type=file size=28"), creationTime);
0538         compareEntryWithTimestamp(listing[11], QString("mode=100440 user=weis group=users path=test1 type=file size=5"), creationTime);
0539         compareEntryWithTimestamp(listing[12], QString("mode=100644 user=weis group=users path=test2 type=file size=8"), creationTime);
0540         QCOMPARE(listing[13], QString("mode=40777 user=%1 group=%2 path=z type=dir time=").arg(systemUserName).arg(systemGroupName));
0541 
0542         // This one was added with addLocalFile, so ignore mode.
0543         QString str = listing[14];
0544         str.replace(QRegularExpression(QStringLiteral("mode.*user=")), QStringLiteral("user="));
0545 
0546         compareEntryWithTimestamp(str, QString("user=%1 group=%2 path=z/test3 type=file size=13").arg(owner).arg(group), creationTime);
0547 
0548 #ifndef Q_OS_WIN
0549         str = listing[15];
0550         str.replace(QRegularExpression(QStringLiteral("mode.*path=")), QStringLiteral("path="));
0551 
0552         compareEntryWithTimestamp(str, QString("path=z/test3_symlink type=file size=0 symlink=test3"), creationTime);
0553 #endif
0554 
0555         QVERIFY(tar.close());
0556     }
0557 }
0558 
0559 /**
0560  * This tests the decompression using kfilterdev, basically.
0561  * To debug KTarPrivate::fillTempFile().
0562  *
0563  * @dataProvider setupData
0564  */
0565 void KArchiveTest::testUncompress()
0566 {
0567     QFETCH(QString, fileName);
0568     QFETCH(QString, mimeType);
0569 
0570     // testCreateTar must have been run first.
0571     QVERIFY(QFile::exists(fileName));
0572     KCompressionDevice filterDev(fileName);
0573     QByteArray buffer;
0574     buffer.resize(8 * 1024);
0575     // qDebug() << "buffer.size()=" << buffer.size();
0576     QVERIFY(filterDev.open(QIODevice::ReadOnly));
0577 
0578     qint64 totalSize = 0;
0579     qint64 len = -1;
0580     while (!filterDev.atEnd() && len != 0) {
0581         len = filterDev.read(buffer.data(), buffer.size());
0582         QVERIFY(len >= 0);
0583         totalSize += len;
0584         // qDebug() << "read len=" << len << " totalSize=" << totalSize;
0585     }
0586     filterDev.close();
0587     // qDebug() << "totalSize=" << totalSize;
0588     QVERIFY(totalSize > 26000); // 27648 here when using gunzip
0589 }
0590 
0591 /**
0592  * @dataProvider setupData
0593  */
0594 void KArchiveTest::testTarFileData()
0595 {
0596     QFETCH(QString, fileName);
0597 
0598     // testCreateTar must have been run first.
0599     KTar tar(fileName);
0600     QVERIFY(tar.open(QIODevice::ReadOnly));
0601 
0602     testFileData(&tar);
0603 
0604     QVERIFY(tar.close());
0605 }
0606 
0607 /**
0608  * @dataProvider setupData
0609  */
0610 void KArchiveTest::testTarCopyTo()
0611 {
0612     QFETCH(QString, fileName);
0613 
0614     // testCreateTar must have been run first.
0615     KTar tar(fileName);
0616     QVERIFY(tar.open(QIODevice::ReadOnly));
0617 
0618     testCopyTo(&tar);
0619 
0620     QVERIFY(tar.close());
0621 }
0622 
0623 /**
0624  * @dataProvider setupData
0625  */
0626 void KArchiveTest::testTarReadWrite()
0627 {
0628     QFETCH(QString, fileName);
0629 
0630     // testCreateTar must have been run first.
0631     KTar tar(fileName);
0632     QVERIFY(tar.open(QIODevice::ReadWrite));
0633 
0634     testReadWrite(&tar);
0635     testFileData(&tar);
0636 
0637     QVERIFY(tar.close());
0638 
0639     // Reopen it and check it
0640     {
0641         KTar tar(fileName);
0642         QVERIFY(tar.open(QIODevice::ReadOnly));
0643         testFileData(&tar);
0644         const KArchiveDirectory *dir = tar.directory();
0645         const KArchiveEntry *e = dir->entry(QStringLiteral("newfile"));
0646         QVERIFY(e && e->isFile());
0647         const KArchiveFile *f = (KArchiveFile *)e;
0648         QCOMPARE(f->data().size(), 8);
0649     }
0650 
0651     // NOTE This is the last test for this dataset. so cleanup here
0652     QFile::remove(fileName);
0653 }
0654 
0655 void KArchiveTest::testTarMaxLength_data()
0656 {
0657     QTest::addColumn<QString>("fileName");
0658     QTest::newRow("maxlength.tar.gz") << "karchivetest-maxlength.tar.gz";
0659 }
0660 
0661 /**
0662  * @dataProvider testTarMaxLength_data
0663  */
0664 void KArchiveTest::testTarMaxLength()
0665 {
0666     QFETCH(QString, fileName);
0667 
0668     KTar tar(fileName);
0669 
0670     QVERIFY(tar.open(QIODevice::WriteOnly));
0671 
0672     // Generate long filenames of each possible length bigger than 98...
0673     // Also exceed 512 byte block size limit to see how well the ././@LongLink
0674     // implementation fares
0675     for (int i = 98; i < 514; i++) {
0676         QString str;
0677         QString num;
0678         str.fill('a', i - 10);
0679         num.setNum(i);
0680         num = num.rightJustified(10, '0');
0681         tar.writeFile(str + num, "hum");
0682     }
0683     // Result of this test : works perfectly now (failed at 482 formerly and
0684     // before that at 154).
0685     QVERIFY(tar.close());
0686 
0687     QVERIFY(tar.open(QIODevice::ReadOnly));
0688 
0689     const KArchiveDirectory *dir = tar.directory();
0690     QVERIFY(dir != nullptr);
0691     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup);
0692 
0693     QCOMPARE(listing[0],
0694              QString("mode=100644 user= group= path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 "
0695                      "type=file size=3"));
0696     QCOMPARE(listing[3],
0697              QString("mode=100644 user= group= path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 "
0698                      "type=file size=3"));
0699     QCOMPARE(listing[4],
0700              QString("mode=100644 user= group= path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 "
0701                      "type=file size=3"));
0702 
0703     QCOMPARE(listing.count(), 416);
0704 
0705     QVERIFY(tar.close());
0706 
0707     // NOTE Cleanup here
0708     QFile::remove(fileName);
0709 }
0710 
0711 void KArchiveTest::testTarGlobalHeader()
0712 {
0713     KTar tar(QFINDTESTDATA(QLatin1String("global_header_test.tar.gz")));
0714     QVERIFY2(tar.open(QIODevice::ReadOnly), "global_header_test.tar.gz");
0715 
0716     const KArchiveDirectory *dir = tar.directory();
0717     QVERIFY(dir != nullptr);
0718 
0719     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup);
0720 
0721     QCOMPARE(listing.count(), 2);
0722 
0723     QCOMPARE(listing[0], QString("mode=40775 user=root group=root path=Test type=dir"));
0724     QCOMPARE(listing[1], QString("mode=664 user=root group=root path=Test/test.txt type=file size=0"));
0725 
0726     QVERIFY(tar.close());
0727 }
0728 
0729 void KArchiveTest::testTarPrefix()
0730 {
0731     KTar tar(QFINDTESTDATA(QLatin1String("tar_prefix_test.tar.gz")));
0732     QVERIFY2(tar.open(QIODevice::ReadOnly), "tar_prefix_test.tar.gz");
0733 
0734     const KArchiveDirectory *dir = tar.directory();
0735     QVERIFY(dir != nullptr);
0736 
0737     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup);
0738 
0739     QCOMPARE(listing[0], QString("mode=40775 user=root group=root path=Test type=dir"));
0740     QCOMPARE(listing[1], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7 type=dir"));
0741     QCOMPARE(listing[2], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples type=dir"));
0742     QCOMPARE(listing[3], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator type=dir"));
0743     QCOMPARE(listing[4], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original type=dir"));
0744     QCOMPARE(listing[5], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java type=dir"));
0745     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"));
0746     QCOMPARE(listing[7],
0747              QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech type=dir"));
0748     QCOMPARE(
0749         listing[8],
0750         QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples type=dir"));
0751     QCOMPARE(
0752         listing[9],
0753         QString("mode=664 user=root group=root "
0754                 "path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples/GeneratorExample.html type=file size=43086"));
0755 
0756     QCOMPARE(listing.count(), 10);
0757 
0758     QVERIFY(tar.close());
0759 }
0760 
0761 void KArchiveTest::testTarDirectoryForgotten()
0762 {
0763     KTar tar(QFINDTESTDATA(QLatin1String("tar_directory_forgotten.tar.gz")));
0764     QVERIFY2(tar.open(QIODevice::ReadOnly), "tar_directory_forgotten.tar.gz");
0765 
0766     const KArchiveDirectory *dir = tar.directory();
0767     QVERIFY(dir != nullptr);
0768 
0769     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup);
0770 
0771     QVERIFY(listing[9].contains("trolltech/examples/generator"));
0772     QVERIFY(listing[10].contains("trolltech/examples/generator/GeneratorExample.html"));
0773 
0774     QCOMPARE(listing.count(), 11);
0775 
0776     QVERIFY(tar.close());
0777 }
0778 
0779 void KArchiveTest::testTarEmptyFileMissingDir()
0780 {
0781     KTar tar(QFINDTESTDATA(QLatin1String("tar_emptyfile_missingdir.tar.gz")));
0782     QVERIFY(tar.open(QIODevice::ReadOnly));
0783 
0784     const KArchiveDirectory *dir = tar.directory();
0785     QVERIFY(dir != nullptr);
0786 
0787     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
0788 
0789     QCOMPARE(listing[0], QString("mode=40777 path=dir type=dir"));
0790     QCOMPARE(listing[1], QString("mode=40777 path=dir/foo type=dir"));
0791     QCOMPARE(listing[2], QString("mode=644 path=dir/foo/file type=file size=0"));
0792     QCOMPARE(listing.count(), 3);
0793 
0794     QVERIFY(tar.close());
0795 }
0796 
0797 void KArchiveTest::testTarRootDir() // bug 309463
0798 {
0799     KTar tar(QFINDTESTDATA(QLatin1String("tar_rootdir.tar.gz")));
0800     QVERIFY2(tar.open(QIODevice::ReadOnly), qPrintable(tar.fileName()));
0801 
0802     const KArchiveDirectory *dir = tar.directory();
0803     QVERIFY(dir != nullptr);
0804 
0805     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup);
0806     // qDebug() << listing.join("\n");
0807 
0808     QVERIFY(listing[0].contains("%{APPNAME}.cpp"));
0809     QVERIFY(listing[1].contains("%{APPNAME}.h"));
0810     QVERIFY(listing[5].contains("main.cpp"));
0811 
0812     QCOMPARE(listing.count(), 10);
0813 }
0814 
0815 void KArchiveTest::testTarLongNonASCIINames() // bug 266141
0816 {
0817     const QString tarName = QString("karchive-long-non-ascii-names.tar");
0818     const QString longName = QString::fromUtf8("раз-два-три-четыре-пять-вышел-зайчик-погулять-вдруг-охотник-выбегает-прямо-в-зайчика.txt");
0819 
0820     {
0821         KTar tar(tarName);
0822         QVERIFY(tar.open(QIODevice::WriteOnly));
0823         QVERIFY(tar.writeFile(longName, "", 0644, "user", "users"));
0824         QVERIFY(tar.close());
0825     }
0826 
0827     {
0828         KTar tar(tarName);
0829 
0830         QVERIFY(tar.open(QIODevice::ReadOnly));
0831         const KArchiveDirectory *dir = tar.directory();
0832         QVERIFY(dir != nullptr);
0833 
0834         const QStringList listing = recursiveListEntries(dir, QString(""), 0);
0835 
0836         const QString expectedListingEntry = QString("mode=644 path=") + longName + QString(" type=file size=0");
0837 
0838         QCOMPARE(listing.count(), 1);
0839 
0840         QCOMPARE(listing[0], expectedListingEntry);
0841         QVERIFY(tar.close());
0842     }
0843 }
0844 
0845 void KArchiveTest::testTarShortNonASCIINames() // bug 266141
0846 {
0847     KTar tar(QFINDTESTDATA(QString("tar_non_ascii_file_name.tar.gz")));
0848 
0849     QVERIFY(tar.open(QIODevice::ReadOnly));
0850     const KArchiveDirectory *dir = tar.directory();
0851     QVERIFY(dir != nullptr);
0852 
0853     const QStringList listing = recursiveListEntries(dir, QString(""), 0);
0854 
0855     QCOMPARE(listing.count(), 1);
0856     QCOMPARE(listing[0],
0857              QString("mode=644 path=абвгдеёжзийклмнопрстуфхцчшщъыьэюя.txt"
0858                      " type=file size=0"));
0859     QVERIFY(tar.close());
0860 }
0861 
0862 void KArchiveTest::testTarDirectoryTwice() // bug 206994
0863 {
0864     KTar tar(QFINDTESTDATA(QLatin1String("tar_directory_twice.tar.gz")));
0865     QVERIFY(tar.open(QIODevice::ReadOnly));
0866 
0867     const KArchiveDirectory *dir = tar.directory();
0868     QVERIFY(dir != nullptr);
0869 
0870     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), WithUserGroup);
0871     // qDebug() << listing.join("\n");
0872 
0873     QVERIFY(listing[0].contains("path=d"));
0874     QVERIFY(listing[1].contains("path=d/f1.txt"));
0875     QVERIFY(listing[2].contains("path=d/f2.txt"));
0876 
0877     QCOMPARE(listing.count(), 3);
0878 }
0879 
0880 void KArchiveTest::testTarIgnoreRelativePathOutsideArchive()
0881 {
0882 #if HAVE_BZIP2_SUPPORT
0883     // This test extracts a Tar archive that contains a relative path "../foo" pointing
0884     // outside of the archive directory. For security reasons extractions should only
0885     // be allowed within the extracted directory as long as not specifically asked.
0886 
0887     KTar tar(QFINDTESTDATA(QLatin1String("tar_relative_path_outside_archive.tar.bz2")));
0888     QVERIFY(tar.open(QIODevice::ReadOnly));
0889 
0890     const KArchiveDirectory *dir = tar.directory();
0891     QTemporaryDir tmpDir;
0892     const QString dirName = tmpDir.path() + "/subdir"; // use a subdir so /tmp/foo doesn't break the test :)
0893     QDir().mkdir(dirName);
0894 
0895     QVERIFY(dir->copyTo(dirName));
0896     QVERIFY(!QFile::exists(dirName + "../foo"));
0897     QVERIFY(QFile::exists(dirName + "/foo"));
0898 #else
0899     QSKIP("Test data is in bz2 format and karchive is built without bzip2 format");
0900 #endif
0901 }
0902 ///
0903 
0904 static const char s_zipFileName[] = "karchivetest.zip";
0905 static const char s_zipMaxLengthFileName[] = "karchivetest-maxlength.zip";
0906 static const char s_zipLocaleFileName[] = "karchivetest-locale.zip";
0907 static const char s_zipMimeType[] = "application/vnd.oasis.opendocument.text";
0908 
0909 void KArchiveTest::testCreateZip()
0910 {
0911     KZip zip(s_zipFileName);
0912 
0913     QVERIFY(zip.open(QIODevice::WriteOnly));
0914 
0915     zip.setExtraField(KZip::NoExtraField);
0916 
0917     zip.setCompression(KZip::NoCompression);
0918     QByteArray zipMimeType(s_zipMimeType);
0919     zip.writeFile(QStringLiteral("mimetype"), zipMimeType);
0920     zip.setCompression(KZip::DeflateCompression);
0921 
0922     writeTestFilesToArchive(&zip);
0923 
0924     QVERIFY(zip.close());
0925 
0926     QFile zipFile(QFile::encodeName(s_zipFileName));
0927     QFileInfo fileInfo(zipFile);
0928     QVERIFY(fileInfo.exists());
0929     QVERIFY(fileInfo.size() > 300);
0930 
0931     // Check that the header with no-compression and no-extrafield worked.
0932     // (This is for the "magic" for koffice documents)
0933     QVERIFY(zipFile.open(QIODevice::ReadOnly));
0934     QByteArray arr = zipFile.read(4);
0935     QCOMPARE(arr, QByteArray("PK\003\004"));
0936     QVERIFY(zipFile.seek(30));
0937     arr = zipFile.read(8);
0938     QCOMPARE(arr, QByteArray("mimetype"));
0939     arr = zipFile.read(zipMimeType.size());
0940     QCOMPARE(arr, zipMimeType);
0941 }
0942 
0943 void KArchiveTest::testCreateZipError()
0944 {
0945     // Giving a directory name to kzip must give an error case in close(), see #136630.
0946     // Otherwise we just lose data.
0947     KZip zip(QDir::currentPath());
0948 
0949     QVERIFY(!zip.open(QIODevice::WriteOnly));
0950     QCOMPARE(zip.errorString(), tr("QSaveFile creation for %1 failed: Filename refers to a directory").arg(QDir::currentPath()));
0951 }
0952 
0953 void KArchiveTest::testReadZipError()
0954 {
0955     QFile brokenZip(QStringLiteral("broken.zip"));
0956     QVERIFY(brokenZip.open(QIODevice::WriteOnly));
0957 
0958     // incomplete magic
0959     brokenZip.write(QByteArray("PK\003"));
0960 
0961     brokenZip.close();
0962     {
0963         KZip zip(QStringLiteral("broken.zip"));
0964 
0965         QVERIFY(!zip.open(QIODevice::ReadOnly));
0966         QCOMPARE(zip.errorString(), tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(1));
0967 
0968         QVERIFY(brokenZip.open(QIODevice::WriteOnly | QIODevice::Append));
0969 
0970         // add rest of magic, but still incomplete header
0971         brokenZip.write(QByteArray("\004\000\000\000\000"));
0972 
0973         brokenZip.close();
0974 
0975         QVERIFY(!zip.open(QIODevice::ReadOnly));
0976         QCOMPARE(zip.errorString(), tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(4));
0977     }
0978 
0979     QVERIFY(brokenZip.remove());
0980 }
0981 
0982 void KArchiveTest::testReadZip()
0983 {
0984     // testCreateZip must have been run first.
0985     KZip zip(s_zipFileName);
0986 
0987     QVERIFY(zip.open(QIODevice::ReadOnly));
0988 
0989     const KArchiveDirectory *dir = zip.directory();
0990     QVERIFY(dir != nullptr);
0991 
0992     // ZIP has no support for per-file user/group, so omit them from the listing
0993     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
0994 
0995 #ifndef Q_OS_WIN
0996     QCOMPARE(listing.count(), 17);
0997 #else
0998     QCOMPARE(listing.count(), 16);
0999 #endif
1000     QCOMPARE(listing[0], QString("mode=40755 path=aaaemptydir type=dir"));
1001     QCOMPARE(listing[1], QString("mode=40777 path=dir type=dir"));
1002     QCOMPARE(listing[2], QString("mode=40777 path=dir/subdir type=dir"));
1003     QCOMPARE(listing[3], QString("mode=100644 path=dir/subdir/mediumfile2 type=file size=100"));
1004     QCOMPARE(listing[4], QString("mode=100644 path=empty type=file size=0"));
1005     QCOMPARE(listing[5], QString("mode=100755 path=executableAll type=file size=17"));
1006     QCOMPARE(listing[6], QString("mode=100644 path=hugefile type=file size=20000"));
1007     QCOMPARE(listing[7], QString("mode=100644 path=mediumfile type=file size=100"));
1008     QCOMPARE(listing[8], QString("mode=100644 path=mimetype type=file size=%1").arg(strlen(s_zipMimeType)));
1009     QCOMPARE(listing[9], QString("mode=40777 path=my type=dir"));
1010     QCOMPARE(listing[10], QString("mode=40777 path=my/dir type=dir"));
1011     QCOMPARE(listing[11], QString("mode=100644 path=my/dir/test3 type=file size=28"));
1012     QCOMPARE(listing[12], QString("mode=100440 path=test1 type=file size=5"));
1013     QCOMPARE(listing[13], QString("mode=100644 path=test2 type=file size=8"));
1014     QCOMPARE(listing[14], QString("mode=40777 path=z type=dir"));
1015     // This one was added with addLocalFile, so ignore mode
1016     QString str = listing[15];
1017     str.replace(QRegularExpression(QStringLiteral("mode.*path=")), QStringLiteral("path="));
1018     QCOMPARE(str, QString("path=z/test3 type=file size=13"));
1019 #ifndef Q_OS_WIN
1020     str = listing[16];
1021     str.replace(QRegularExpression(QStringLiteral("mode.*path=")), QStringLiteral("path="));
1022     QCOMPARE(str, QString("path=z/test3_symlink type=file size=5 symlink=test3"));
1023 #endif
1024 
1025     QVERIFY(zip.close());
1026 }
1027 
1028 void KArchiveTest::testZipFileData()
1029 {
1030     // testCreateZip must have been run first.
1031     KZip zip(s_zipFileName);
1032     QVERIFY(zip.open(QIODevice::ReadOnly));
1033 
1034     testFileData(&zip);
1035 
1036     QVERIFY(zip.close());
1037 }
1038 
1039 void KArchiveTest::testZipCopyTo()
1040 {
1041     // testCreateZip must have been run first.
1042     KZip zip(s_zipFileName);
1043     QVERIFY(zip.open(QIODevice::ReadOnly));
1044 
1045     testCopyTo(&zip);
1046 
1047     QVERIFY(zip.close());
1048 }
1049 
1050 void KArchiveTest::testZipMaxLength()
1051 {
1052     KZip zip(s_zipMaxLengthFileName);
1053 
1054     QVERIFY(zip.open(QIODevice::WriteOnly));
1055 
1056     // Similar to testTarMaxLength just to make sure, but of course zip doesn't have
1057     // those limitations in the first place.
1058     for (int i = 98; i < 514; i++) {
1059         QString str;
1060         QString num;
1061         str.fill('a', i - 10);
1062         num.setNum(i);
1063         num = num.rightJustified(10, '0');
1064         zip.writeFile(str + num, "hum");
1065     }
1066     QVERIFY(zip.close());
1067 
1068     QVERIFY(zip.open(QIODevice::ReadOnly));
1069 
1070     const KArchiveDirectory *dir = zip.directory();
1071     QVERIFY(dir != nullptr);
1072     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1073 
1074     QCOMPARE(listing[0],
1075              QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3"));
1076     QCOMPARE(
1077         listing[3],
1078         QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3"));
1079     QCOMPARE(
1080         listing[4],
1081         QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3"));
1082 
1083     QCOMPARE(listing.count(), 514 - 98);
1084 
1085     QVERIFY(zip.close());
1086 }
1087 
1088 void KArchiveTest::testZipWithNonLatinFileNames()
1089 {
1090     KZip zip(s_zipLocaleFileName);
1091 
1092     QVERIFY(zip.open(QIODevice::WriteOnly));
1093 
1094     const QByteArray fileData("Test of data with a russian file name");
1095     const QString fileName = QString::fromUtf8("Архитектура.okular");
1096     const QString recodedFileName = QFile::decodeName(QFile::encodeName(fileName));
1097     QVERIFY(zip.writeFile(fileName, fileData));
1098 
1099     QVERIFY(zip.close());
1100 
1101     QVERIFY(zip.open(QIODevice::ReadOnly));
1102 
1103     const KArchiveDirectory *dir = zip.directory();
1104     QVERIFY(dir != nullptr);
1105     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1106 
1107     QCOMPARE(listing.count(), 1);
1108     QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(recodedFileName).arg(fileData.size()));
1109 
1110     const KArchiveFile *fileEntry = static_cast<const KArchiveFile *>(dir->entry(dir->entries()[0]));
1111     QCOMPARE(fileEntry->data(), fileData);
1112 }
1113 
1114 void KArchiveTest::testZipWithOverwrittenFileName()
1115 {
1116     KZip zip(s_zipFileName);
1117 
1118     QVERIFY(zip.open(QIODevice::WriteOnly));
1119 
1120     const QByteArray fileData1("There could be a fire, if there is smoke.");
1121     const QString fileName = QStringLiteral("wisdom");
1122     QVERIFY(zip.writeFile(fileName, fileData1, 0100644, "konqi", "dragons"));
1123 
1124     // now overwrite it
1125     const QByteArray fileData2("If there is smoke, there could be a fire.");
1126     QVERIFY(zip.writeFile(fileName, fileData2, 0100644, "konqi", "dragons"));
1127 
1128     QVERIFY(zip.close());
1129 
1130     QVERIFY(zip.open(QIODevice::ReadOnly));
1131 
1132     const KArchiveDirectory *dir = zip.directory();
1133     QVERIFY(dir != nullptr);
1134     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1135 
1136     QCOMPARE(listing.count(), 1);
1137     QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(fileName).arg(fileData2.size()));
1138 
1139     const KArchiveFile *fileEntry = static_cast<const KArchiveFile *>(dir->entry(dir->entries()[0]));
1140     QCOMPARE(fileEntry->data(), fileData2);
1141 }
1142 
1143 static bool writeFile(const QString &dirName, const QString &fileName, const QByteArray &data)
1144 {
1145     Q_ASSERT(dirName.endsWith('/'));
1146     QFile file(dirName + fileName);
1147     if (!file.open(QIODevice::WriteOnly)) {
1148         return false;
1149     }
1150     file.write(data);
1151     return true;
1152 }
1153 
1154 void KArchiveTest::testZipAddLocalDirectory()
1155 {
1156     // Prepare local dir
1157     QTemporaryDir tmpDir;
1158     const QString dirName = tmpDir.path() + '/';
1159 
1160     const QByteArray file1Data = "Hello Shantanu";
1161     const QString file1 = QStringLiteral("file1");
1162     QVERIFY(writeFile(dirName, file1, file1Data));
1163 
1164     const QString emptyDir = QStringLiteral("emptyDir");
1165     QDir(dirName).mkdir(emptyDir);
1166 
1167     {
1168         KZip zip(s_zipFileName);
1169 
1170         QVERIFY(zip.open(QIODevice::WriteOnly));
1171         QVERIFY(zip.addLocalDirectory(dirName, "."));
1172         QVERIFY(zip.close());
1173     }
1174     {
1175         KZip zip(s_zipFileName);
1176 
1177         QVERIFY(zip.open(QIODevice::ReadOnly));
1178 
1179         const KArchiveDirectory *dir = zip.directory();
1180         QVERIFY(dir != nullptr);
1181 
1182         const KArchiveEntry *e = dir->entry(file1);
1183         QVERIFY(e && e->isFile());
1184         const KArchiveFile *f = static_cast<const KArchiveFile *>(e);
1185         QCOMPARE(f->data(), file1Data);
1186 
1187         const KArchiveEntry *empty = dir->entry(emptyDir);
1188         QVERIFY(empty && empty->isDirectory());
1189     }
1190 }
1191 
1192 void KArchiveTest::testZipReadRedundantDataDescriptor_data()
1193 {
1194     QTest::addColumn<QString>("fileName");
1195     QTest::newRow("noSignature") << "data/redundantDataDescriptorsNoSignature.zip";
1196     QTest::newRow("withSignature") << "data/redundantDataDescriptorsWithSignature.zip";
1197 }
1198 
1199 /**
1200  * @dataProvider testZipReadRedundantDataDescriptor_data
1201  */
1202 void KArchiveTest::testZipReadRedundantDataDescriptor()
1203 {
1204     QFETCH(QString, fileName);
1205 
1206     const QString redundantDataDescriptorZipFileName = QFINDTESTDATA(fileName);
1207     QVERIFY(!redundantDataDescriptorZipFileName.isEmpty());
1208 
1209     KZip zip(redundantDataDescriptorZipFileName);
1210 
1211     QVERIFY(zip.open(QIODevice::ReadOnly));
1212 
1213     const KArchiveDirectory *dir = zip.directory();
1214     QVERIFY(dir != nullptr);
1215 
1216     const QByteArray fileData("aaaaaaaaaaaaaaa");
1217 
1218     // ZIP has no support for per-file user/group, so omit them from the listing
1219     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1220 
1221     QCOMPARE(listing.count(), 2);
1222     QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=compressed type=file size=%2").arg(fileData.size()));
1223     QCOMPARE(listing[1], QString::fromUtf8("mode=100644 path=uncompressed type=file size=%2").arg(fileData.size()));
1224 
1225     const KArchiveFile *fileEntry = static_cast<const KArchiveFile *>(dir->entry(dir->entries()[0]));
1226     QCOMPARE(fileEntry->data(), fileData);
1227     fileEntry = static_cast<const KArchiveFile *>(dir->entry(dir->entries()[1]));
1228     QCOMPARE(fileEntry->data(), fileData);
1229 }
1230 
1231 void KArchiveTest::testZipDirectoryPermissions()
1232 {
1233     QString fileName = QFINDTESTDATA("data/dirpermissions.zip");
1234     QVERIFY(!fileName.isEmpty());
1235 
1236     KZip zip(fileName);
1237 
1238     QVERIFY(zip.open(QIODevice::ReadOnly));
1239     const KArchiveDirectory *dir = zip.directory();
1240     const QStringList listing = recursiveListEntries(dir, QString(), 0);
1241     QCOMPARE(listing.join(' '), QString::fromUtf8("mode=40700 path=700 type=dir mode=40750 path=750 type=dir mode=40755 path=755 type=dir"));
1242 }
1243 
1244 void KArchiveTest::testZipWithinZip()
1245 {
1246     QString fileName = QFINDTESTDATA("data/zip_within_zip.zip");
1247     QVERIFY(!fileName.isEmpty());
1248 
1249     KZip zip(fileName);
1250 
1251     QVERIFY(zip.open(QIODevice::ReadOnly));
1252     const KArchiveDirectory *dir = zip.directory();
1253     const QStringList listing = recursiveListEntries(dir, QString(), 0);
1254     QCOMPARE(listing.count(), 2);
1255     QCOMPARE(listing[0], QString::fromUtf8("mode=100644 path=foo type=file size=7"));
1256     QCOMPARE(listing[1], QString::fromUtf8("mode=100644 path=test.epub type=file size=525154"));
1257 }
1258 
1259 void KArchiveTest::testZipUnusualButValid()
1260 {
1261     QString fileName = QFINDTESTDATA("data/unusual_but_valid_364071.zip");
1262     QVERIFY(!fileName.isEmpty());
1263 
1264     KZip zip(fileName);
1265 
1266     QVERIFY(zip.open(QIODevice::ReadOnly));
1267     const KArchiveDirectory *dir = zip.directory();
1268     const QStringList listing = recursiveListEntries(dir, QString(), 0);
1269     QCOMPARE(listing.join(' '), QLatin1String("mode=40744 path=test type=dir mode=744 path=test/os-release type=file size=199"));
1270 }
1271 
1272 void KArchiveTest::testZipDuplicateNames()
1273 {
1274     QString fileName = QFINDTESTDATA("data/out.epub");
1275     QVERIFY(!fileName.isEmpty());
1276 
1277     KZip zip(fileName);
1278 
1279     QVERIFY(zip.open(QIODevice::ReadOnly));
1280 
1281     int metaInfCount = 0;
1282     for (const QString &entryName : zip.directory()->entries()) {
1283         if (entryName.startsWith("META-INF")) {
1284             metaInfCount++;
1285         }
1286     }
1287 
1288     QVERIFY2(metaInfCount == 1, "Archive root directory contains duplicates");
1289 }
1290 
1291 void KArchiveTest::testZip64()
1292 {
1293     QString fileName = QFINDTESTDATA("data/zip64.pkpass");
1294     QVERIFY(!fileName.isEmpty());
1295 
1296     KZip zip(fileName);
1297     QVERIFY(zip.open(QIODevice::ReadOnly));
1298 
1299     // NOTE the actual content of this file has been wiped, we can only verify the file meta data here
1300     QCOMPARE(zip.directory()->entries().size(), 7);
1301     auto entry = static_cast<const KZipFileEntry *>(zip.directory()->entry(QStringLiteral("manifest.json")));
1302     QVERIFY(entry);
1303     QCOMPARE(entry->size(), 305);
1304     QCOMPARE(entry->compressedSize(), 194);
1305     QCOMPARE(entry->position(), 21417);
1306     entry = static_cast<const KZipFileEntry *>(zip.directory()->entry(QStringLiteral("logo.png")));
1307     QVERIFY(entry);
1308     QCOMPARE(entry->size(), 3589);
1309     QCOMPARE(entry->compressedSize(), 3594);
1310     QCOMPARE(entry->position(), 8943);
1311 }
1312 
1313 void KArchiveTest::testRcc()
1314 {
1315     const QString rccFile = QFINDTESTDATA("runtime_resource.rcc"); // was copied from qtbase/tests/auto/corelib/io/qresourceengine
1316     QVERIFY(!rccFile.isEmpty());
1317     KRcc rcc(rccFile);
1318     QVERIFY(rcc.open(QIODevice::ReadOnly));
1319     const KArchiveDirectory *rootDir = rcc.directory();
1320     QVERIFY(rootDir != nullptr);
1321     const KArchiveEntry *rrEntry = rootDir->entry(QStringLiteral("runtime_resource"));
1322     QVERIFY(rrEntry && rrEntry->isDirectory());
1323     const KArchiveDirectory *rrDir = static_cast<const KArchiveDirectory *>(rrEntry);
1324     const KArchiveEntry *fileEntry = rrDir->entry(QStringLiteral("search_file.txt"));
1325     QVERIFY(fileEntry && fileEntry->isFile());
1326     const KArchiveFile *searchFile = static_cast<const KArchiveFile *>(fileEntry);
1327     const QByteArray fileData = searchFile->data();
1328     QCOMPARE(QString::fromLatin1(fileData), QString::fromLatin1("root\n"));
1329 }
1330 
1331 void KArchiveTest::testAr()
1332 {
1333     const QString artestFile = QFINDTESTDATA("data/artest.a");
1334     KAr ar(artestFile);
1335 
1336     QVERIFY(ar.open(QIODevice::ReadOnly));
1337 
1338     const KArchiveDirectory *dir = ar.directory();
1339     QVERIFY(dir != nullptr);
1340 
1341     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1342 
1343     const QStringList expected = {"mode=100644 path=at_quick_exit.oS type=file size=3456",
1344                                   "mode=100644 path=atexit.oS type=file size=3436",
1345                                   "mode=100644 path=elf-init.oS type=file size=3288",
1346                                   "mode=100644 path=fstat.oS type=file size=3296",
1347                                   "mode=100644 path=fstat64.oS type=file size=3304",
1348                                   "mode=100644 path=fstatat.oS type=file size=3352",
1349                                   "mode=100644 path=fstatat64.oS type=file size=3380",
1350                                   "mode=100644 path=lstat.oS type=file size=3320",
1351                                   "mode=100644 path=lstat64.oS type=file size=3332",
1352                                   "mode=100644 path=mknod.oS type=file size=2840",
1353                                   "mode=100644 path=mknodat.oS type=file size=2848",
1354                                   "mode=100644 path=stack_chk_fail_local.oS type=file size=2288",
1355                                   "mode=100644 path=stat.oS type=file size=3316",
1356                                   "mode=100644 path=stat64.oS type=file size=3312",
1357                                   "mode=100644 path=warning-nop.oS type=file size=1976"};
1358 
1359     QCOMPARE(listing.count(), expected.count());
1360     QCOMPARE(listing, expected);
1361 
1362     QVERIFY(ar.close());
1363 }
1364 
1365 /**
1366  * @see QTest::cleanupTestCase()
1367  */
1368 void KArchiveTest::cleanupTestCase()
1369 {
1370     QFile::remove(s_zipMaxLengthFileName);
1371     QFile::remove(s_zipFileName);
1372     QFile::remove(s_zipLocaleFileName);
1373 #ifndef Q_OS_WIN
1374     QFile::remove(QStringLiteral("test3_symlink"));
1375 #endif
1376 }
1377 
1378 ///
1379 
1380 #if HAVE_XZ_SUPPORT
1381 
1382 /**
1383  * Prepares dataset for archive filter tests
1384  */
1385 void KArchiveTest::setup7ZipData()
1386 {
1387     QTest::addColumn<QString>("fileName");
1388     QTest::newRow(".7z") << "karchivetest.7z";
1389 }
1390 
1391 /**
1392  * @dataProvider testCreate7Zip_data
1393  */
1394 void KArchiveTest::testCreate7Zip()
1395 {
1396     QFETCH(QString, fileName);
1397 
1398     QBENCHMARK {
1399         K7Zip k7zip(fileName);
1400         QVERIFY(k7zip.open(QIODevice::WriteOnly));
1401 
1402         writeTestFilesToArchive(&k7zip);
1403 
1404         QVERIFY(k7zip.close());
1405 
1406         QFileInfo fileInfo(QFile::encodeName(fileName));
1407         QVERIFY(fileInfo.exists());
1408         // qDebug() << "fileInfo.size()" << fileInfo.size();
1409         // We can't check for an exact size because of the addLocalFile, whose data is system-dependent
1410         QVERIFY(fileInfo.size() > 390);
1411     }
1412 }
1413 
1414 /**
1415  * @dataProvider setupData
1416  */
1417 void KArchiveTest::testRead7Zip() // testCreate7Zip must have been run first.
1418 {
1419     QFETCH(QString, fileName);
1420 
1421     QBENCHMARK {
1422         K7Zip k7zip(fileName);
1423 
1424         QVERIFY(k7zip.open(QIODevice::ReadOnly));
1425 
1426         const KArchiveDirectory *dir = k7zip.directory();
1427         QVERIFY(dir != nullptr);
1428         const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1429 
1430 #ifndef Q_OS_WIN
1431         QCOMPARE(listing.count(), 16);
1432 #else
1433         QCOMPARE(listing.count(), 15);
1434 #endif
1435         QCOMPARE(listing[0], QString("mode=40755 path=aaaemptydir type=dir"));
1436         QCOMPARE(listing[1], QString("mode=40777 path=dir type=dir"));
1437         QCOMPARE(listing[2], QString("mode=40777 path=dir/subdir type=dir"));
1438         QCOMPARE(listing[3], QString("mode=100644 path=dir/subdir/mediumfile2 type=file size=100"));
1439         QCOMPARE(listing[4], QString("mode=100644 path=empty type=file size=0"));
1440         QCOMPARE(listing[5], QString("mode=100755 path=executableAll type=file size=17"));
1441         QCOMPARE(listing[6], QString("mode=100644 path=hugefile type=file size=20000"));
1442         QCOMPARE(listing[7], QString("mode=100644 path=mediumfile type=file size=100"));
1443         QCOMPARE(listing[8], QString("mode=40777 path=my type=dir"));
1444         QCOMPARE(listing[9], QString("mode=40777 path=my/dir type=dir"));
1445         QCOMPARE(listing[10], QString("mode=100644 path=my/dir/test3 type=file size=28"));
1446         QCOMPARE(listing[11], QString("mode=100440 path=test1 type=file size=5"));
1447         QCOMPARE(listing[12], QString("mode=100644 path=test2 type=file size=8"));
1448         QCOMPARE(listing[13], QString("mode=40777 path=z type=dir"));
1449         // This one was added with addLocalFile, so ignore mode/user/group.
1450         QString str = listing[14];
1451         str.replace(QRegularExpression(QStringLiteral("mode.*path=")), QStringLiteral("path="));
1452         QCOMPARE(str, QString("path=z/test3 type=file size=13"));
1453 #ifndef Q_OS_WIN
1454         str = listing[15];
1455         str.replace(QRegularExpression(QStringLiteral("mode.*path=")), QStringLiteral("path="));
1456         QCOMPARE(str, QString("path=z/test3_symlink type=file size=0 symlink=test3"));
1457 #endif
1458 
1459         QVERIFY(k7zip.close());
1460     }
1461 }
1462 
1463 /**
1464  * @dataProvider setupData
1465  */
1466 void KArchiveTest::test7ZipFileData()
1467 {
1468     QFETCH(QString, fileName);
1469 
1470     // testCreate7Zip must have been run first.
1471     K7Zip k7zip(fileName);
1472     QVERIFY(k7zip.open(QIODevice::ReadOnly));
1473 
1474     testFileData(&k7zip);
1475 
1476     QVERIFY(k7zip.close());
1477 }
1478 
1479 /**
1480  * @dataProvider setupData
1481  */
1482 void KArchiveTest::test7ZipCopyTo()
1483 {
1484     QFETCH(QString, fileName);
1485 
1486     // testCreateTar must have been run first.
1487     K7Zip k7zip(fileName);
1488     QVERIFY(k7zip.open(QIODevice::ReadOnly));
1489 
1490     testCopyTo(&k7zip);
1491 
1492     QVERIFY(k7zip.close());
1493 }
1494 
1495 /**
1496  * @dataProvider setupData
1497  */
1498 void KArchiveTest::test7ZipReadWrite()
1499 {
1500     QFETCH(QString, fileName);
1501 
1502     // testCreate7zip must have been run first.
1503     K7Zip k7zip(fileName);
1504     QVERIFY(k7zip.open(QIODevice::ReadWrite));
1505 
1506     testReadWrite(&k7zip);
1507     testFileData(&k7zip);
1508 
1509     QVERIFY(k7zip.close());
1510 
1511     // Reopen it and check it
1512     {
1513         K7Zip k7zip(fileName);
1514         QVERIFY(k7zip.open(QIODevice::ReadOnly));
1515         testFileData(&k7zip);
1516         const KArchiveDirectory *dir = k7zip.directory();
1517         const KArchiveEntry *e = dir->entry(QStringLiteral("newfile"));
1518         QVERIFY(e && e->isFile());
1519         const KArchiveFile *f = (KArchiveFile *)e;
1520         QCOMPARE(f->data().size(), 8);
1521     }
1522 
1523     // NOTE This is the last test for this dataset. so cleanup here
1524     QFile::remove(fileName);
1525 }
1526 
1527 /**
1528  * @dataProvider test7ZipMaxLength_data
1529  */
1530 void KArchiveTest::test7ZipMaxLength()
1531 {
1532     QFETCH(QString, fileName);
1533 
1534     K7Zip k7zip(fileName);
1535 
1536     QVERIFY(k7zip.open(QIODevice::WriteOnly));
1537 
1538     // Generate long filenames of each possible length bigger than 98...
1539     for (int i = 98; i < 514; i++) {
1540         QString str;
1541         QString num;
1542         str.fill('a', i - 10);
1543         num.setNum(i);
1544         num = num.rightJustified(10, '0');
1545         k7zip.writeFile(str + num, "hum");
1546     }
1547     QVERIFY(k7zip.close());
1548 
1549     QVERIFY(k7zip.open(QIODevice::ReadOnly));
1550 
1551     const KArchiveDirectory *dir = k7zip.directory();
1552     QVERIFY(dir != nullptr);
1553     const QStringList listing = recursiveListEntries(dir, QLatin1String(""), 0);
1554 
1555     QCOMPARE(listing[0],
1556              QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3"));
1557     QCOMPARE(
1558         listing[3],
1559         QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3"));
1560     QCOMPARE(
1561         listing[4],
1562         QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3"));
1563 
1564     QCOMPARE(listing.count(), 416);
1565 
1566     QVERIFY(k7zip.close());
1567 
1568     // NOTE Cleanup here
1569     QFile::remove(fileName);
1570 }
1571 #endif
1572 
1573 #include "moc_karchivetest.cpp"