File indexing completed on 2024-04-21 07:39:12

0001 /* This file is part of the KDE libraries
0002    SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
0003    SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
0004 
0005    Moved from ktar.cpp by Roberto Teixeira <maragato@kde.org>
0006 
0007    SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "karchive.h"
0011 #include "karchive_p.h"
0012 #include "klimitediodevice_p.h"
0013 #include "loggingcategory.h"
0014 
0015 #include <qplatformdefs.h> // QT_STATBUF, QT_LSTAT
0016 
0017 #include <QDebug>
0018 #include <QDir>
0019 #include <QFile>
0020 #include <QMap>
0021 #include <QStack>
0022 
0023 #include <cerrno>
0024 #include <stdio.h>
0025 #include <stdlib.h>
0026 
0027 #include <assert.h>
0028 
0029 #ifdef Q_OS_UNIX
0030 #include <grp.h>
0031 #include <limits.h> // PATH_MAX
0032 #include <pwd.h>
0033 #include <unistd.h>
0034 #endif
0035 #ifdef Q_OS_WIN
0036 #include <windows.h> // DWORD, GetUserNameW
0037 #endif // Q_OS_WIN
0038 
0039 #if defined(Q_OS_UNIX)
0040 #define STAT_METHOD QT_LSTAT
0041 #else
0042 #define STAT_METHOD QT_STAT
0043 #endif
0044 
0045 ////////////////////////////////////////////////////////////////////////
0046 /////////////////// KArchiveDirectoryPrivate ///////////////////////////
0047 ////////////////////////////////////////////////////////////////////////
0048 
0049 class KArchiveDirectoryPrivate
0050 {
0051 public:
0052     KArchiveDirectoryPrivate(KArchiveDirectory *parent)
0053         : q(parent)
0054     {
0055     }
0056 
0057     ~KArchiveDirectoryPrivate()
0058     {
0059         qDeleteAll(entries);
0060     }
0061 
0062     KArchiveDirectoryPrivate(const KArchiveDirectoryPrivate &) = delete;
0063     KArchiveDirectoryPrivate &operator=(const KArchiveDirectoryPrivate &) = delete;
0064 
0065     static KArchiveDirectoryPrivate *get(KArchiveDirectory *directory)
0066     {
0067         return directory->d;
0068     }
0069 
0070     // Returns in containingDirectory the directory that actually contains the returned entry
0071     const KArchiveEntry *entry(const QString &_name, KArchiveDirectory **containingDirectory) const
0072     {
0073         *containingDirectory = q;
0074 
0075         QString name = QDir::cleanPath(_name);
0076         int pos = name.indexOf(QLatin1Char('/'));
0077         if (pos == 0) { // absolute path (see also KArchive::findOrCreate)
0078             if (name.length() > 1) {
0079                 name = name.mid(1); // remove leading slash
0080                 pos = name.indexOf(QLatin1Char('/')); // look again
0081             } else { // "/"
0082                 return q;
0083             }
0084         }
0085         // trailing slash ? -> remove
0086         if (pos != -1 && pos == name.length() - 1) {
0087             name = name.left(pos);
0088             pos = name.indexOf(QLatin1Char('/')); // look again
0089         }
0090         if (pos != -1) {
0091             const QString left = name.left(pos);
0092             const QString right = name.mid(pos + 1);
0093 
0094             // qCDebug(KArchiveLog) << "left=" << left << "right=" << right;
0095 
0096             KArchiveEntry *e = entries.value(left);
0097             if (!e || !e->isDirectory()) {
0098                 return nullptr;
0099             }
0100             *containingDirectory = static_cast<KArchiveDirectory *>(e);
0101             return (*containingDirectory)->d->entry(right, containingDirectory);
0102         }
0103 
0104         return entries.value(name);
0105     }
0106 
0107     KArchiveDirectory *q;
0108     QHash<QString, KArchiveEntry *> entries;
0109 };
0110 
0111 ////////////////////////////////////////////////////////////////////////
0112 /////////////////////////// KArchive ///////////////////////////////////
0113 ////////////////////////////////////////////////////////////////////////
0114 
0115 KArchive::KArchive(const QString &fileName)
0116     : d(new KArchivePrivate(this))
0117 {
0118     if (fileName.isEmpty()) {
0119         qCWarning(KArchiveLog) << "KArchive: No file name specified";
0120     }
0121     d->fileName = fileName;
0122     // This constructor leaves the device set to 0.
0123     // This is for the use of QSaveFile, see open().
0124 }
0125 
0126 KArchive::KArchive(QIODevice *dev)
0127     : d(new KArchivePrivate(this))
0128 {
0129     if (!dev) {
0130         qCWarning(KArchiveLog) << "KArchive: Null device specified";
0131     }
0132     d->dev = dev;
0133 }
0134 
0135 KArchive::~KArchive()
0136 {
0137     Q_ASSERT(!isOpen()); // the derived class destructor must have closed already
0138     delete d;
0139 }
0140 
0141 bool KArchive::open(QIODevice::OpenMode mode)
0142 {
0143     Q_ASSERT(mode != QIODevice::NotOpen);
0144 
0145     if (isOpen()) {
0146         close();
0147     }
0148 
0149     if (!d->fileName.isEmpty()) {
0150         Q_ASSERT(!d->dev);
0151         if (!createDevice(mode)) {
0152             return false;
0153         }
0154     }
0155 
0156     if (!d->dev) {
0157         setErrorString(tr("No filename or device was specified"));
0158         return false;
0159     }
0160 
0161     if (!d->dev->isOpen() && !d->dev->open(mode)) {
0162         setErrorString(tr("Could not open device in mode %1").arg(mode));
0163         return false;
0164     }
0165 
0166     d->mode = mode;
0167 
0168     Q_ASSERT(!d->rootDir);
0169     d->rootDir = nullptr;
0170 
0171     return openArchive(mode);
0172 }
0173 
0174 bool KArchive::createDevice(QIODevice::OpenMode mode)
0175 {
0176     switch (mode) {
0177     case QIODevice::WriteOnly:
0178         if (!d->fileName.isEmpty()) {
0179             // The use of QSaveFile can't be done in the ctor (no mode known yet)
0180             // qCDebug(KArchiveLog) << "Writing to a file using QSaveFile";
0181             d->saveFile = new QSaveFile(d->fileName);
0182 #ifdef Q_OS_ANDROID
0183             // we cannot rename on to Android content: URLs
0184             if (d->fileName.startsWith(QLatin1String("content://"))) {
0185                 d->saveFile->setDirectWriteFallback(true);
0186             }
0187 #endif
0188             if (!d->saveFile->open(QIODevice::WriteOnly)) {
0189                 setErrorString(tr("QSaveFile creation for %1 failed: %2").arg(d->fileName, d->saveFile->errorString()));
0190 
0191                 delete d->saveFile;
0192                 d->saveFile = nullptr;
0193                 return false;
0194             }
0195             d->dev = d->saveFile;
0196             Q_ASSERT(d->dev);
0197         }
0198         break;
0199     case QIODevice::ReadOnly:
0200     case QIODevice::ReadWrite:
0201         // ReadWrite mode still uses QFile for now; we'd need to copy to the tempfile, in fact.
0202         if (!d->fileName.isEmpty()) {
0203             d->dev = new QFile(d->fileName);
0204             d->deviceOwned = true;
0205         }
0206         break; // continued below
0207     default:
0208         setErrorString(tr("Unsupported mode %1").arg(d->mode));
0209         return false;
0210     }
0211     return true;
0212 }
0213 
0214 bool KArchive::close()
0215 {
0216     if (!isOpen()) {
0217         setErrorString(tr("Archive already closed"));
0218         return false; // already closed (return false or true? arguable...)
0219     }
0220 
0221     // moved by holger to allow kzip to write the zip central dir
0222     // to the file in closeArchive()
0223     // DF: added d->dev so that we skip closeArchive if saving aborted.
0224     bool closeSucceeded = true;
0225     if (d->dev) {
0226         closeSucceeded = closeArchive();
0227         if (d->mode == QIODevice::WriteOnly && !closeSucceeded) {
0228             d->abortWriting();
0229         }
0230     }
0231 
0232     if (d->dev && d->dev != d->saveFile) {
0233         d->dev->close();
0234     }
0235 
0236     // if d->saveFile is not null then it is equal to d->dev.
0237     if (d->saveFile) {
0238         closeSucceeded = d->saveFile->commit();
0239         delete d->saveFile;
0240         d->saveFile = nullptr;
0241     }
0242     if (d->deviceOwned) {
0243         delete d->dev; // we created it ourselves in open()
0244     }
0245 
0246     delete d->rootDir;
0247     d->rootDir = nullptr;
0248     d->mode = QIODevice::NotOpen;
0249     d->dev = nullptr;
0250     return closeSucceeded;
0251 }
0252 
0253 QString KArchive::errorString() const
0254 {
0255     return d->errorStr;
0256 }
0257 
0258 const KArchiveDirectory *KArchive::directory() const
0259 {
0260     // rootDir isn't const so that parsing-on-demand is possible
0261     return const_cast<KArchive *>(this)->rootDir();
0262 }
0263 
0264 bool KArchive::addLocalFile(const QString &fileName, const QString &destName)
0265 {
0266     QFileInfo fileInfo(fileName);
0267     if (!fileInfo.isFile() && !fileInfo.isSymLink()) {
0268         setErrorString(tr("%1 doesn't exist or is not a regular file.").arg(fileName));
0269         return false;
0270     }
0271 
0272     QT_STATBUF fi;
0273     if (STAT_METHOD(QFile::encodeName(fileName).constData(), &fi) == -1) {
0274         setErrorString(tr("Failed accessing the file %1 for adding to the archive. The error was: %2").arg(fileName).arg(QLatin1String{strerror(errno)}));
0275         return false;
0276     }
0277 
0278     if (fileInfo.isSymLink()) {
0279         QString symLinkTarget;
0280         // Do NOT use fileInfo.symLinkTarget() for unix symlinks!
0281         // It returns the -full- path to the target, while we want the target string "as is".
0282 #if defined(Q_OS_UNIX) && !defined(Q_OS_OS2EMX)
0283         const QByteArray encodedFileName = QFile::encodeName(fileName);
0284         QByteArray s;
0285 #if defined(PATH_MAX)
0286         s.resize(PATH_MAX + 1);
0287 #else
0288         int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX);
0289         if (path_max <= 0) {
0290             path_max = 4096;
0291         }
0292         s.resize(path_max);
0293 #endif
0294         int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
0295         if (len >= 0) {
0296             s[len] = '\0';
0297             symLinkTarget = QFile::decodeName(s.constData());
0298         }
0299 #endif
0300         if (symLinkTarget.isEmpty()) { // Mac or Windows
0301             symLinkTarget = fileInfo.symLinkTarget();
0302         }
0303         return writeSymLink(destName,
0304                             symLinkTarget,
0305                             fileInfo.owner(),
0306                             fileInfo.group(),
0307                             fi.st_mode,
0308                             fileInfo.lastRead(),
0309                             fileInfo.lastModified(),
0310                             fileInfo.birthTime());
0311     } /*end if*/
0312 
0313     qint64 size = fileInfo.size();
0314 
0315     // the file must be opened before prepareWriting is called, otherwise
0316     // if the opening fails, no content will follow the already written
0317     // header and the tar file is incorrect
0318     QFile file(fileName);
0319     if (!file.open(QIODevice::ReadOnly)) {
0320         setErrorString(tr("Couldn't open file %1: %2").arg(fileName, file.errorString()));
0321         return false;
0322     }
0323 
0324     if (!prepareWriting(destName, fileInfo.owner(), fileInfo.group(), size, fi.st_mode, fileInfo.lastRead(), fileInfo.lastModified(), fileInfo.birthTime())) {
0325         // qCWarning(KArchiveLog) << " prepareWriting" << destName << "failed";
0326         return false;
0327     }
0328 
0329     // Read and write data in chunks to minimize memory usage
0330     QByteArray array;
0331     array.resize(int(qMin(qint64(1024 * 1024), size)));
0332     qint64 n;
0333     qint64 total = 0;
0334     while ((n = file.read(array.data(), array.size())) > 0) {
0335         if (!writeData(array.data(), n)) {
0336             // qCWarning(KArchiveLog) << "writeData failed";
0337             return false;
0338         }
0339         total += n;
0340     }
0341     Q_ASSERT(total == size);
0342 
0343     if (!finishWriting(size)) {
0344         // qCWarning(KArchiveLog) << "finishWriting failed";
0345         return false;
0346     }
0347     return true;
0348 }
0349 
0350 bool KArchive::addLocalDirectory(const QString &path, const QString &destName)
0351 {
0352     QDir dir(path);
0353     if (!dir.exists()) {
0354         setErrorString(tr("Directory %1 does not exist").arg(path));
0355         return false;
0356     }
0357     dir.setFilter(dir.filter() | QDir::Hidden);
0358     const QStringList files = dir.entryList();
0359     for (const QString &file : files) {
0360         if (file != QLatin1String(".") && file != QLatin1String("..")) {
0361             const QString fileName = path + QLatin1Char('/') + file;
0362             //            qCDebug(KArchiveLog) << "storing " << fileName;
0363             const QString dest = destName.isEmpty() ? file : (destName + QLatin1Char('/') + file);
0364             QFileInfo fileInfo(fileName);
0365 
0366             if (fileInfo.isFile() || fileInfo.isSymLink()) {
0367                 addLocalFile(fileName, dest);
0368             } else if (fileInfo.isDir()) {
0369                 // Write directory, so that empty dirs are preserved (and permissions written out, etc.)
0370                 int perms = 0;
0371                 QT_STATBUF fi;
0372                 if (STAT_METHOD(QFile::encodeName(fileName).constData(), &fi) != -1) {
0373                     perms = fi.st_mode;
0374                 }
0375                 writeDir(dest, fileInfo.owner(), fileInfo.group(), perms, fileInfo.lastRead(), fileInfo.lastModified(), fileInfo.birthTime());
0376                 // Recurse
0377                 addLocalDirectory(fileName, dest);
0378             }
0379             // We omit sockets
0380         }
0381     }
0382     return true;
0383 }
0384 
0385 bool KArchive::writeFile(const QString &name,
0386                          QByteArrayView data,
0387                          mode_t perm,
0388                          const QString &user,
0389                          const QString &group,
0390                          const QDateTime &atime,
0391                          const QDateTime &mtime,
0392                          const QDateTime &ctime)
0393 {
0394     const qint64 size = data.size();
0395     if (!prepareWriting(name, user, group, size, perm, atime, mtime, ctime)) {
0396         // qCWarning(KArchiveLog) << "prepareWriting failed";
0397         return false;
0398     }
0399 
0400     // Write data
0401     // Note: if data is null, don't call write, it would terminate the KCompressionDevice
0402     if (data.constData() && size && !writeData(data.constData(), size)) {
0403         // qCWarning(KArchiveLog) << "writeData failed";
0404         return false;
0405     }
0406 
0407     if (!finishWriting(size)) {
0408         // qCWarning(KArchiveLog) << "finishWriting failed";
0409         return false;
0410     }
0411     return true;
0412 }
0413 
0414 bool KArchive::writeData(const char *data, qint64 size)
0415 {
0416     return doWriteData(data, size);
0417 }
0418 
0419 bool KArchive::writeData(QByteArrayView data)
0420 {
0421     return doWriteData(data.constData(), data.size());
0422 }
0423 
0424 bool KArchive::doWriteData(const char *data, qint64 size)
0425 {
0426     bool ok = device()->write(data, size) == size;
0427     if (!ok) {
0428         setErrorString(tr("Writing failed: %1").arg(device()->errorString()));
0429         d->abortWriting();
0430     }
0431     return ok;
0432 }
0433 
0434 // The writeDir -> doWriteDir pattern allows to avoid propagating the default
0435 // values into all virtual methods of subclasses, and it allows more extensibility:
0436 // if a new argument is needed, we can add a writeDir overload which stores the
0437 // additional argument in the d pointer, and doWriteDir reimplementations can fetch
0438 // it from there.
0439 
0440 bool KArchive::writeDir(const QString &name,
0441                         const QString &user,
0442                         const QString &group,
0443                         mode_t perm,
0444                         const QDateTime &atime,
0445                         const QDateTime &mtime,
0446                         const QDateTime &ctime)
0447 {
0448     return doWriteDir(name, user, group, perm | 040000, atime, mtime, ctime);
0449 }
0450 
0451 bool KArchive::writeSymLink(const QString &name,
0452                             const QString &target,
0453                             const QString &user,
0454                             const QString &group,
0455                             mode_t perm,
0456                             const QDateTime &atime,
0457                             const QDateTime &mtime,
0458                             const QDateTime &ctime)
0459 {
0460     return doWriteSymLink(name, target, user, group, perm, atime, mtime, ctime);
0461 }
0462 
0463 bool KArchive::prepareWriting(const QString &name,
0464                               const QString &user,
0465                               const QString &group,
0466                               qint64 size,
0467                               mode_t perm,
0468                               const QDateTime &atime,
0469                               const QDateTime &mtime,
0470                               const QDateTime &ctime)
0471 {
0472     bool ok = doPrepareWriting(name, user, group, size, perm, atime, mtime, ctime);
0473     if (!ok) {
0474         d->abortWriting();
0475     }
0476     return ok;
0477 }
0478 
0479 bool KArchive::finishWriting(qint64 size)
0480 {
0481     return doFinishWriting(size);
0482 }
0483 
0484 void KArchive::setErrorString(const QString &errorStr)
0485 {
0486     d->errorStr = errorStr;
0487 }
0488 
0489 static QString getCurrentUserName()
0490 {
0491 #if defined(Q_OS_UNIX)
0492     struct passwd *pw = getpwuid(getuid());
0493     return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid());
0494 #elif defined(Q_OS_WIN)
0495     wchar_t buffer[255];
0496     DWORD size = 255;
0497     bool ok = GetUserNameW(buffer, &size);
0498     if (!ok) {
0499         return QString();
0500     }
0501     return QString::fromWCharArray(buffer);
0502 #else
0503     return QString();
0504 #endif
0505 }
0506 
0507 static QString getCurrentGroupName()
0508 {
0509 #if defined(Q_OS_UNIX)
0510     struct group *grp = getgrgid(getgid());
0511     return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid());
0512 #elif defined(Q_OS_WIN)
0513     return QString();
0514 #else
0515     return QString();
0516 #endif
0517 }
0518 
0519 KArchiveDirectory *KArchive::rootDir()
0520 {
0521     if (!d->rootDir) {
0522         // qCDebug(KArchiveLog) << "Making root dir ";
0523         QString username = ::getCurrentUserName();
0524         QString groupname = ::getCurrentGroupName();
0525 
0526         d->rootDir = new KArchiveDirectory(this, QStringLiteral("/"), int(0777 + S_IFDIR), QDateTime(), username, groupname, QString());
0527     }
0528     return d->rootDir;
0529 }
0530 
0531 KArchiveDirectory *KArchive::findOrCreate(const QString &path)
0532 {
0533     return d->findOrCreate(path, 0 /*recursionCounter*/);
0534 }
0535 
0536 KArchiveDirectory *KArchivePrivate::findOrCreate(const QString &path, int recursionCounter)
0537 {
0538     // Check we're not in a path that is ultra deep, this is most probably fine since PATH_MAX on Linux
0539     // is defined as 4096, so even on /a/a/a/a/a/a 2500 recursions puts us over that limit
0540     // an ultra deep recursion will make us crash due to not enough stack. Tests show that 1MB stack
0541     // (default on Linux seems to be 8MB) gives us up to around 4000 recursions
0542     if (recursionCounter > 2500) {
0543         qCWarning(KArchiveLog) << "path recursion limit exceeded, bailing out";
0544         return nullptr;
0545     }
0546     // qCDebug(KArchiveLog) << path;
0547     if (path.isEmpty() || path == QLatin1String("/") || path == QLatin1String(".")) { // root dir => found
0548         // qCDebug(KArchiveLog) << "returning rootdir";
0549         return q->rootDir();
0550     }
0551     // Important note : for tar files containing absolute paths
0552     // (i.e. beginning with "/"), this means the leading "/" will
0553     // be removed (no KDirectory for it), which is exactly the way
0554     // the "tar" program works (though it displays a warning about it)
0555     // See also KArchiveDirectory::entry().
0556 
0557     // Already created ? => found
0558     KArchiveDirectory *existingEntryParentDirectory;
0559     const KArchiveEntry *existingEntry = KArchiveDirectoryPrivate::get(q->rootDir())->entry(path, &existingEntryParentDirectory);
0560     if (existingEntry) {
0561         if (existingEntry->isDirectory())
0562         // qCDebug(KArchiveLog) << "found it";
0563         {
0564             const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(existingEntry);
0565             return const_cast<KArchiveDirectory *>(dir);
0566         } else {
0567             const KArchiveFile *file = static_cast<const KArchiveFile *>(existingEntry);
0568             if (file->size() > 0) {
0569                 qCWarning(KArchiveLog) << path << "is normal file, but there are file paths in the archive assuming it is a directory, bailing out";
0570                 return nullptr;
0571             }
0572 
0573             qCDebug(KArchiveLog) << path << " is an empty file, assuming it is actually a directory and replacing";
0574             KArchiveEntry *myEntry = const_cast<KArchiveEntry *>(existingEntry);
0575             existingEntryParentDirectory->removeEntry(myEntry);
0576             delete myEntry;
0577         }
0578     }
0579 
0580     // Otherwise go up and try again
0581     int pos = path.lastIndexOf(QLatin1Char('/'));
0582     KArchiveDirectory *parent;
0583     QString dirname;
0584     if (pos == -1) { // no more slash => create in root dir
0585         parent = q->rootDir();
0586         dirname = path;
0587     } else {
0588         QString left = path.left(pos);
0589         dirname = path.mid(pos + 1);
0590         parent = findOrCreate(left, recursionCounter + 1); // recursive call... until we find an existing dir.
0591     }
0592 
0593     if (!parent) {
0594         return nullptr;
0595     }
0596 
0597     // qCDebug(KArchiveLog) << "found parent " << parent->name() << " adding " << dirname << " to ensure " << path;
0598     // Found -> add the missing piece
0599     KArchiveDirectory *e = new KArchiveDirectory(q, dirname, rootDir->permissions(), rootDir->date(), rootDir->user(), rootDir->group(), QString());
0600     if (parent->addEntryV2(e)) {
0601         return e; // now a directory to <path> exists
0602     } else {
0603         return nullptr;
0604     }
0605 }
0606 
0607 void KArchive::setDevice(QIODevice *dev)
0608 {
0609     if (d->deviceOwned) {
0610         delete d->dev;
0611     }
0612     d->dev = dev;
0613     d->deviceOwned = false;
0614 }
0615 
0616 void KArchive::setRootDir(KArchiveDirectory *rootDir)
0617 {
0618     Q_ASSERT(!d->rootDir); // Call setRootDir only once during parsing please ;)
0619     delete d->rootDir; // but if it happens, don't leak
0620     d->rootDir = rootDir;
0621 }
0622 
0623 QIODevice::OpenMode KArchive::mode() const
0624 {
0625     return d->mode;
0626 }
0627 
0628 QIODevice *KArchive::device() const
0629 {
0630     return d->dev;
0631 }
0632 
0633 bool KArchive::isOpen() const
0634 {
0635     return d->mode != QIODevice::NotOpen;
0636 }
0637 
0638 QString KArchive::fileName() const
0639 {
0640     return d->fileName;
0641 }
0642 
0643 void KArchivePrivate::abortWriting()
0644 {
0645     if (saveFile) {
0646         saveFile->cancelWriting();
0647         delete saveFile;
0648         saveFile = nullptr;
0649         dev = nullptr;
0650     }
0651 }
0652 
0653 // this is a hacky wrapper to check if time_t value is invalid
0654 QDateTime KArchivePrivate::time_tToDateTime(uint time_t)
0655 {
0656     if (time_t == uint(-1)) {
0657         return QDateTime();
0658     }
0659     return QDateTime::fromSecsSinceEpoch(time_t);
0660 }
0661 
0662 ////////////////////////////////////////////////////////////////////////
0663 /////////////////////// KArchiveEntry //////////////////////////////////
0664 ////////////////////////////////////////////////////////////////////////
0665 
0666 class KArchiveEntryPrivate
0667 {
0668 public:
0669     KArchiveEntryPrivate(KArchive *_archive,
0670                          const QString &_name,
0671                          int _access,
0672                          const QDateTime &_date,
0673                          const QString &_user,
0674                          const QString &_group,
0675                          const QString &_symlink)
0676         : name(_name)
0677         , date(_date)
0678         , access(_access)
0679         , user(_user)
0680         , group(_group)
0681         , symlink(_symlink)
0682         , archive(_archive)
0683     {
0684     }
0685     QString name;
0686     QDateTime date;
0687     mode_t access;
0688     QString user;
0689     QString group;
0690     QString symlink;
0691     KArchive *archive;
0692 };
0693 
0694 KArchiveEntry::KArchiveEntry(KArchive *t,
0695                              const QString &name,
0696                              int access,
0697                              const QDateTime &date,
0698                              const QString &user,
0699                              const QString &group,
0700                              const QString &symlink)
0701     : d(new KArchiveEntryPrivate(t, name, access, date, user, group, symlink))
0702 {
0703 }
0704 
0705 KArchiveEntry::~KArchiveEntry()
0706 {
0707     delete d;
0708 }
0709 
0710 QDateTime KArchiveEntry::date() const
0711 {
0712     return d->date;
0713 }
0714 
0715 QString KArchiveEntry::name() const
0716 {
0717     return d->name;
0718 }
0719 
0720 mode_t KArchiveEntry::permissions() const
0721 {
0722     return d->access;
0723 }
0724 
0725 QString KArchiveEntry::user() const
0726 {
0727     return d->user;
0728 }
0729 
0730 QString KArchiveEntry::group() const
0731 {
0732     return d->group;
0733 }
0734 
0735 QString KArchiveEntry::symLinkTarget() const
0736 {
0737     return d->symlink;
0738 }
0739 
0740 bool KArchiveEntry::isFile() const
0741 {
0742     return false;
0743 }
0744 
0745 bool KArchiveEntry::isDirectory() const
0746 {
0747     return false;
0748 }
0749 
0750 KArchive *KArchiveEntry::archive() const
0751 {
0752     return d->archive;
0753 }
0754 
0755 ////////////////////////////////////////////////////////////////////////
0756 /////////////////////// KArchiveFile ///////////////////////////////////
0757 ////////////////////////////////////////////////////////////////////////
0758 
0759 class KArchiveFilePrivate
0760 {
0761 public:
0762     KArchiveFilePrivate(qint64 _pos, qint64 _size)
0763         : pos(_pos)
0764         , size(_size)
0765     {
0766     }
0767     qint64 pos;
0768     qint64 size;
0769 };
0770 
0771 KArchiveFile::KArchiveFile(KArchive *t,
0772                            const QString &name,
0773                            int access,
0774                            const QDateTime &date,
0775                            const QString &user,
0776                            const QString &group,
0777                            const QString &symlink,
0778                            qint64 pos,
0779                            qint64 size)
0780     : KArchiveEntry(t, name, access, date, user, group, symlink)
0781     , d(new KArchiveFilePrivate(pos, size))
0782 {
0783 }
0784 
0785 KArchiveFile::~KArchiveFile()
0786 {
0787     delete d;
0788 }
0789 
0790 qint64 KArchiveFile::position() const
0791 {
0792     return d->pos;
0793 }
0794 
0795 qint64 KArchiveFile::size() const
0796 {
0797     return d->size;
0798 }
0799 
0800 void KArchiveFile::setSize(qint64 s)
0801 {
0802     d->size = s;
0803 }
0804 
0805 QByteArray KArchiveFile::data() const
0806 {
0807     bool ok = archive()->device()->seek(d->pos);
0808     if (!ok) {
0809         // qCWarning(KArchiveLog) << "Failed to sync to" << d->pos << "to read" << name();
0810     }
0811 
0812     // Read content
0813     QByteArray arr;
0814     if (d->size) {
0815         arr = archive()->device()->read(d->size);
0816         Q_ASSERT(arr.size() == d->size);
0817     }
0818     return arr;
0819 }
0820 
0821 QIODevice *KArchiveFile::createDevice() const
0822 {
0823     return new KLimitedIODevice(archive()->device(), d->pos, d->size);
0824 }
0825 
0826 bool KArchiveFile::isFile() const
0827 {
0828     return true;
0829 }
0830 
0831 static QFileDevice::Permissions withExecutablePerms(QFileDevice::Permissions filePerms, mode_t perms)
0832 {
0833     if (perms & 01) {
0834         filePerms |= QFileDevice::ExeOther;
0835     }
0836 
0837     if (perms & 010) {
0838         filePerms |= QFileDevice::ExeGroup;
0839     }
0840 
0841     if (perms & 0100) {
0842         filePerms |= QFileDevice::ExeOwner;
0843     }
0844 
0845     return filePerms;
0846 }
0847 
0848 bool KArchiveFile::copyTo(const QString &dest) const
0849 {
0850     QFile f(dest + QLatin1Char('/') + name());
0851     if (f.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
0852         QIODevice *inputDev = createDevice();
0853         if (!inputDev) {
0854             f.remove();
0855             return false;
0856         }
0857 
0858         // Read and write data in chunks to minimize memory usage
0859         const qint64 chunkSize = 1024 * 1024;
0860         qint64 remainingSize = d->size;
0861         QByteArray array;
0862         array.resize(int(qMin(chunkSize, remainingSize)));
0863 
0864         while (remainingSize > 0) {
0865             const qint64 currentChunkSize = qMin(chunkSize, remainingSize);
0866             const qint64 n = inputDev->read(array.data(), currentChunkSize);
0867             Q_UNUSED(n) // except in Q_ASSERT
0868             Q_ASSERT(n == currentChunkSize);
0869             f.write(array.data(), currentChunkSize);
0870             remainingSize -= currentChunkSize;
0871         }
0872         f.setPermissions(withExecutablePerms(f.permissions(), permissions()));
0873         f.close();
0874 
0875         delete inputDev;
0876         return true;
0877     }
0878     return false;
0879 }
0880 
0881 ////////////////////////////////////////////////////////////////////////
0882 //////////////////////// KArchiveDirectory /////////////////////////////////
0883 ////////////////////////////////////////////////////////////////////////
0884 
0885 KArchiveDirectory::KArchiveDirectory(KArchive *t,
0886                                      const QString &name,
0887                                      int access,
0888                                      const QDateTime &date,
0889                                      const QString &user,
0890                                      const QString &group,
0891                                      const QString &symlink)
0892     : KArchiveEntry(t, name, access, date, user, group, symlink)
0893     , d(new KArchiveDirectoryPrivate(this))
0894 {
0895 }
0896 
0897 KArchiveDirectory::~KArchiveDirectory()
0898 {
0899     delete d;
0900 }
0901 
0902 QStringList KArchiveDirectory::entries() const
0903 {
0904     return d->entries.keys();
0905 }
0906 
0907 const KArchiveEntry *KArchiveDirectory::entry(const QString &_name) const
0908 {
0909     KArchiveDirectory *dummy;
0910     return d->entry(_name, &dummy);
0911 }
0912 
0913 const KArchiveFile *KArchiveDirectory::file(const QString &name) const
0914 {
0915     const KArchiveEntry *e = entry(name);
0916     if (e && e->isFile()) {
0917         return static_cast<const KArchiveFile *>(e);
0918     }
0919     return nullptr;
0920 }
0921 
0922 void KArchiveDirectory::addEntry(KArchiveEntry *entry)
0923 {
0924     addEntryV2(entry);
0925 }
0926 
0927 bool KArchiveDirectory::addEntryV2(KArchiveEntry *entry)
0928 {
0929     if (d->entries.value(entry->name())) {
0930         qCWarning(KArchiveLog) << "directory " << name() << "has entry" << entry->name() << "already";
0931         delete entry;
0932         return false;
0933     }
0934     d->entries.insert(entry->name(), entry);
0935     return true;
0936 }
0937 
0938 void KArchiveDirectory::removeEntry(KArchiveEntry *entry)
0939 {
0940     if (!entry) {
0941         return;
0942     }
0943 
0944     QHash<QString, KArchiveEntry *>::Iterator it = d->entries.find(entry->name());
0945     // nothing removed?
0946     if (it == d->entries.end()) {
0947         qCWarning(KArchiveLog) << "directory " << name() << "has no entry with name " << entry->name();
0948         return;
0949     }
0950     if (it.value() != entry) {
0951         qCWarning(KArchiveLog) << "directory " << name() << "has another entry for name " << entry->name();
0952         return;
0953     }
0954     d->entries.erase(it);
0955 }
0956 
0957 bool KArchiveDirectory::isDirectory() const
0958 {
0959     return true;
0960 }
0961 
0962 static bool sortByPosition(const KArchiveFile *file1, const KArchiveFile *file2)
0963 {
0964     return file1->position() < file2->position();
0965 }
0966 
0967 bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
0968 {
0969     QDir root;
0970     const QString destDir(QDir(dest).absolutePath()); // get directory path without any "." or ".."
0971 
0972     QList<const KArchiveFile *> fileList;
0973     QMap<qint64, QString> fileToDir;
0974 
0975     // placeholders for iterated items
0976     QStack<const KArchiveDirectory *> dirStack;
0977     QStack<QString> dirNameStack;
0978 
0979     dirStack.push(this); // init stack at current directory
0980     dirNameStack.push(destDir); // ... with given path
0981     do {
0982         const KArchiveDirectory *curDir = dirStack.pop();
0983 
0984         // extract only to specified folder if it is located within archive's extraction folder
0985         // otherwise put file under root position in extraction folder
0986         QString curDirName = dirNameStack.pop();
0987         if (!QDir(curDirName).absolutePath().startsWith(destDir)) {
0988             qCWarning(KArchiveLog) << "Attempted export into folder" << curDirName << "which is outside of the extraction root folder" << destDir << "."
0989                                    << "Changing export of contained files to extraction root folder.";
0990             curDirName = destDir;
0991         }
0992 
0993         if (!root.mkpath(curDirName)) {
0994             return false;
0995         }
0996 
0997         const QStringList dirEntries = curDir->entries();
0998         for (QStringList::const_iterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
0999             const KArchiveEntry *curEntry = curDir->entry(*it);
1000             if (!curEntry->symLinkTarget().isEmpty()) {
1001                 QString linkName = curDirName + QLatin1Char('/') + curEntry->name();
1002                 // To create a valid link on Windows, linkName must have a .lnk file extension.
1003 #ifdef Q_OS_WIN
1004                 if (!linkName.endsWith(QLatin1String(".lnk"))) {
1005                     linkName += QLatin1String(".lnk");
1006                 }
1007 #endif
1008                 QFile symLinkTarget(curEntry->symLinkTarget());
1009                 if (!symLinkTarget.link(linkName)) {
1010                     // qCDebug(KArchiveLog) << "symlink(" << curEntry->symLinkTarget() << ',' << linkName << ") failed:" << strerror(errno);
1011                 }
1012             } else {
1013                 if (curEntry->isFile()) {
1014                     const KArchiveFile *curFile = dynamic_cast<const KArchiveFile *>(curEntry);
1015                     if (curFile) {
1016                         fileList.append(curFile);
1017                         fileToDir.insert(curFile->position(), curDirName);
1018                     }
1019                 }
1020 
1021                 if (curEntry->isDirectory() && recursiveCopy) {
1022                     const KArchiveDirectory *ad = dynamic_cast<const KArchiveDirectory *>(curEntry);
1023                     if (ad) {
1024                         dirStack.push(ad);
1025                         dirNameStack.push(curDirName + QLatin1Char('/') + curEntry->name());
1026                     }
1027                 }
1028             }
1029         }
1030     } while (!dirStack.isEmpty());
1031 
1032     std::sort(fileList.begin(), fileList.end(), sortByPosition); // sort on d->pos, so we have a linear access
1033 
1034     for (QList<const KArchiveFile *>::const_iterator it = fileList.constBegin(), end = fileList.constEnd(); it != end; ++it) {
1035         const KArchiveFile *f = *it;
1036         qint64 pos = f->position();
1037         if (!f->copyTo(fileToDir[pos])) {
1038             return false;
1039         }
1040     }
1041     return true;
1042 }
1043 
1044 void KArchive::virtual_hook(int, void *)
1045 {
1046     /*BASE::virtual_hook( id, data )*/;
1047 }
1048 
1049 void KArchiveEntry::virtual_hook(int, void *)
1050 {
1051     /*BASE::virtual_hook( id, data );*/
1052 }
1053 
1054 void KArchiveFile::virtual_hook(int id, void *data)
1055 {
1056     KArchiveEntry::virtual_hook(id, data);
1057 }
1058 
1059 void KArchiveDirectory::virtual_hook(int id, void *data)
1060 {
1061     KArchiveEntry::virtual_hook(id, data);
1062 }