File indexing completed on 2024-04-21 03:52:28

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