File indexing completed on 2024-04-21 14:53:20

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) { // ouch 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 effectively f*cked up
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                          const QByteArray &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     bool ok = device()->write(data, size) == size;
0417     if (!ok) {
0418         setErrorString(tr("Writing failed: %1").arg(device()->errorString()));
0419         d->abortWriting();
0420     }
0421     return ok;
0422 }
0423 
0424 // The writeDir -> doWriteDir pattern allows to avoid propagating the default
0425 // values into all virtual methods of subclasses, and it allows more extensibility:
0426 // if a new argument is needed, we can add a writeDir overload which stores the
0427 // additional argument in the d pointer, and doWriteDir reimplementations can fetch
0428 // it from there.
0429 
0430 bool KArchive::writeDir(const QString &name,
0431                         const QString &user,
0432                         const QString &group,
0433                         mode_t perm,
0434                         const QDateTime &atime,
0435                         const QDateTime &mtime,
0436                         const QDateTime &ctime)
0437 {
0438     return doWriteDir(name, user, group, perm | 040000, atime, mtime, ctime);
0439 }
0440 
0441 bool KArchive::writeSymLink(const QString &name,
0442                             const QString &target,
0443                             const QString &user,
0444                             const QString &group,
0445                             mode_t perm,
0446                             const QDateTime &atime,
0447                             const QDateTime &mtime,
0448                             const QDateTime &ctime)
0449 {
0450     return doWriteSymLink(name, target, user, group, perm, atime, mtime, ctime);
0451 }
0452 
0453 bool KArchive::prepareWriting(const QString &name,
0454                               const QString &user,
0455                               const QString &group,
0456                               qint64 size,
0457                               mode_t perm,
0458                               const QDateTime &atime,
0459                               const QDateTime &mtime,
0460                               const QDateTime &ctime)
0461 {
0462     bool ok = doPrepareWriting(name, user, group, size, perm, atime, mtime, ctime);
0463     if (!ok) {
0464         d->abortWriting();
0465     }
0466     return ok;
0467 }
0468 
0469 bool KArchive::finishWriting(qint64 size)
0470 {
0471     return doFinishWriting(size);
0472 }
0473 
0474 void KArchive::setErrorString(const QString &errorStr)
0475 {
0476     d->errorStr = errorStr;
0477 }
0478 
0479 static QString getCurrentUserName()
0480 {
0481 #if defined(Q_OS_UNIX)
0482     struct passwd *pw = getpwuid(getuid());
0483     return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid());
0484 #elif defined(Q_OS_WIN)
0485     wchar_t buffer[255];
0486     DWORD size = 255;
0487     bool ok = GetUserNameW(buffer, &size);
0488     if (!ok) {
0489         return QString();
0490     }
0491     return QString::fromWCharArray(buffer);
0492 #else
0493     return QString();
0494 #endif
0495 }
0496 
0497 static QString getCurrentGroupName()
0498 {
0499 #if defined(Q_OS_UNIX)
0500     struct group *grp = getgrgid(getgid());
0501     return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid());
0502 #elif defined(Q_OS_WIN)
0503     return QString();
0504 #else
0505     return QString();
0506 #endif
0507 }
0508 
0509 KArchiveDirectory *KArchive::rootDir()
0510 {
0511     if (!d->rootDir) {
0512         // qCDebug(KArchiveLog) << "Making root dir ";
0513         QString username = ::getCurrentUserName();
0514         QString groupname = ::getCurrentGroupName();
0515 
0516         d->rootDir = new KArchiveDirectory(this, QStringLiteral("/"), int(0777 + S_IFDIR), QDateTime(), username, groupname, QString());
0517     }
0518     return d->rootDir;
0519 }
0520 
0521 KArchiveDirectory *KArchive::findOrCreate(const QString &path)
0522 {
0523     return d->findOrCreate(path, 0 /*recursionCounter*/);
0524 }
0525 
0526 KArchiveDirectory *KArchivePrivate::findOrCreate(const QString &path, int recursionCounter)
0527 {
0528     // Check we're not in a path that is ultra deep, this is most probably fine since PATH_MAX on Linux
0529     // is defined as 4096, so even on /a/a/a/a/a/a 2500 recursions puts us over that limit
0530     // an ultra deep recursion will makes us crash due to not enough stack. Tests show that 1MB stack
0531     // (default on Linux seems to be 8MB) gives us up to around 4000 recursions
0532     if (recursionCounter > 2500) {
0533         qCWarning(KArchiveLog) << "path recursion limit exceeded, bailing out";
0534         return nullptr;
0535     }
0536     // qCDebug(KArchiveLog) << path;
0537     if (path.isEmpty() || path == QLatin1String("/") || path == QLatin1String(".")) { // root dir => found
0538         // qCDebug(KArchiveLog) << "returning rootdir";
0539         return q->rootDir();
0540     }
0541     // Important note : for tar files containing absolute paths
0542     // (i.e. beginning with "/"), this means the leading "/" will
0543     // be removed (no KDirectory for it), which is exactly the way
0544     // the "tar" program works (though it displays a warning about it)
0545     // See also KArchiveDirectory::entry().
0546 
0547     // Already created ? => found
0548     KArchiveDirectory *existingEntryParentDirectory;
0549     const KArchiveEntry *existingEntry = KArchiveDirectoryPrivate::get(q->rootDir())->entry(path, &existingEntryParentDirectory);
0550     if (existingEntry) {
0551         if (existingEntry->isDirectory())
0552         // qCDebug(KArchiveLog) << "found it";
0553         {
0554             const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(existingEntry);
0555             return const_cast<KArchiveDirectory *>(dir);
0556         } else {
0557             const KArchiveFile *file = static_cast<const KArchiveFile *>(existingEntry);
0558             if (file->size() > 0) {
0559                 qCWarning(KArchiveLog) << path << "is normal file, but there are file paths in the archive assuming it is a directory, bailing out";
0560                 return nullptr;
0561             }
0562 
0563             qCDebug(KArchiveLog) << path << " is an empty file, assuming it is actually a directory and replacing";
0564             KArchiveEntry *myEntry = const_cast<KArchiveEntry *>(existingEntry);
0565             existingEntryParentDirectory->removeEntry(myEntry);
0566             delete myEntry;
0567         }
0568     }
0569 
0570     // Otherwise go up and try again
0571     int pos = path.lastIndexOf(QLatin1Char('/'));
0572     KArchiveDirectory *parent;
0573     QString dirname;
0574     if (pos == -1) { // no more slash => create in root dir
0575         parent = q->rootDir();
0576         dirname = path;
0577     } else {
0578         QString left = path.left(pos);
0579         dirname = path.mid(pos + 1);
0580         parent = findOrCreate(left, recursionCounter + 1); // recursive call... until we find an existing dir.
0581     }
0582 
0583     if (!parent) {
0584         return nullptr;
0585     }
0586 
0587     // qCDebug(KArchiveLog) << "found parent " << parent->name() << " adding " << dirname << " to ensure " << path;
0588     // Found -> add the missing piece
0589     KArchiveDirectory *e = new KArchiveDirectory(q, dirname, rootDir->permissions(), rootDir->date(), rootDir->user(), rootDir->group(), QString());
0590     if (parent->addEntryV2(e)) {
0591         return e; // now a directory to <path> exists
0592     } else {
0593         return nullptr;
0594     }
0595 }
0596 
0597 void KArchive::setDevice(QIODevice *dev)
0598 {
0599     if (d->deviceOwned) {
0600         delete d->dev;
0601     }
0602     d->dev = dev;
0603     d->deviceOwned = false;
0604 }
0605 
0606 void KArchive::setRootDir(KArchiveDirectory *rootDir)
0607 {
0608     Q_ASSERT(!d->rootDir); // Call setRootDir only once during parsing please ;)
0609     delete d->rootDir; // but if it happens, don't leak
0610     d->rootDir = rootDir;
0611 }
0612 
0613 QIODevice::OpenMode KArchive::mode() const
0614 {
0615     return d->mode;
0616 }
0617 
0618 QIODevice *KArchive::device() const
0619 {
0620     return d->dev;
0621 }
0622 
0623 bool KArchive::isOpen() const
0624 {
0625     return d->mode != QIODevice::NotOpen;
0626 }
0627 
0628 QString KArchive::fileName() const
0629 {
0630     return d->fileName;
0631 }
0632 
0633 void KArchivePrivate::abortWriting()
0634 {
0635     if (saveFile) {
0636         saveFile->cancelWriting();
0637         delete saveFile;
0638         saveFile = nullptr;
0639         dev = nullptr;
0640     }
0641 }
0642 
0643 // this is a hacky wrapper to check if time_t value is invalid
0644 QDateTime KArchivePrivate::time_tToDateTime(uint time_t)
0645 {
0646     if (time_t == uint(-1)) {
0647         return QDateTime();
0648     }
0649     return QDateTime::fromSecsSinceEpoch(time_t);
0650 }
0651 
0652 ////////////////////////////////////////////////////////////////////////
0653 /////////////////////// KArchiveEntry //////////////////////////////////
0654 ////////////////////////////////////////////////////////////////////////
0655 
0656 class KArchiveEntryPrivate
0657 {
0658 public:
0659     KArchiveEntryPrivate(KArchive *_archive,
0660                          const QString &_name,
0661                          int _access,
0662                          const QDateTime &_date,
0663                          const QString &_user,
0664                          const QString &_group,
0665                          const QString &_symlink)
0666         : name(_name)
0667         , date(_date)
0668         , access(_access)
0669         , user(_user)
0670         , group(_group)
0671         , symlink(_symlink)
0672         , archive(_archive)
0673     {
0674     }
0675     QString name;
0676     QDateTime date;
0677     mode_t access;
0678     QString user;
0679     QString group;
0680     QString symlink;
0681     KArchive *archive;
0682 };
0683 
0684 KArchiveEntry::KArchiveEntry(KArchive *t,
0685                              const QString &name,
0686                              int access,
0687                              const QDateTime &date,
0688                              const QString &user,
0689                              const QString &group,
0690                              const QString &symlink)
0691     : d(new KArchiveEntryPrivate(t, name, access, date, user, group, symlink))
0692 {
0693 }
0694 
0695 KArchiveEntry::~KArchiveEntry()
0696 {
0697     delete d;
0698 }
0699 
0700 QDateTime KArchiveEntry::date() const
0701 {
0702     return d->date;
0703 }
0704 
0705 QString KArchiveEntry::name() const
0706 {
0707     return d->name;
0708 }
0709 
0710 mode_t KArchiveEntry::permissions() const
0711 {
0712     return d->access;
0713 }
0714 
0715 QString KArchiveEntry::user() const
0716 {
0717     return d->user;
0718 }
0719 
0720 QString KArchiveEntry::group() const
0721 {
0722     return d->group;
0723 }
0724 
0725 QString KArchiveEntry::symLinkTarget() const
0726 {
0727     return d->symlink;
0728 }
0729 
0730 bool KArchiveEntry::isFile() const
0731 {
0732     return false;
0733 }
0734 
0735 bool KArchiveEntry::isDirectory() const
0736 {
0737     return false;
0738 }
0739 
0740 KArchive *KArchiveEntry::archive() const
0741 {
0742     return d->archive;
0743 }
0744 
0745 ////////////////////////////////////////////////////////////////////////
0746 /////////////////////// KArchiveFile ///////////////////////////////////
0747 ////////////////////////////////////////////////////////////////////////
0748 
0749 class KArchiveFilePrivate
0750 {
0751 public:
0752     KArchiveFilePrivate(qint64 _pos, qint64 _size)
0753         : pos(_pos)
0754         , size(_size)
0755     {
0756     }
0757     qint64 pos;
0758     qint64 size;
0759 };
0760 
0761 KArchiveFile::KArchiveFile(KArchive *t,
0762                            const QString &name,
0763                            int access,
0764                            const QDateTime &date,
0765                            const QString &user,
0766                            const QString &group,
0767                            const QString &symlink,
0768                            qint64 pos,
0769                            qint64 size)
0770     : KArchiveEntry(t, name, access, date, user, group, symlink)
0771     , d(new KArchiveFilePrivate(pos, size))
0772 {
0773 }
0774 
0775 KArchiveFile::~KArchiveFile()
0776 {
0777     delete d;
0778 }
0779 
0780 qint64 KArchiveFile::position() const
0781 {
0782     return d->pos;
0783 }
0784 
0785 qint64 KArchiveFile::size() const
0786 {
0787     return d->size;
0788 }
0789 
0790 void KArchiveFile::setSize(qint64 s)
0791 {
0792     d->size = s;
0793 }
0794 
0795 QByteArray KArchiveFile::data() const
0796 {
0797     bool ok = archive()->device()->seek(d->pos);
0798     if (!ok) {
0799         // qCWarning(KArchiveLog) << "Failed to sync to" << d->pos << "to read" << name();
0800     }
0801 
0802     // Read content
0803     QByteArray arr;
0804     if (d->size) {
0805         arr = archive()->device()->read(d->size);
0806         Q_ASSERT(arr.size() == d->size);
0807     }
0808     return arr;
0809 }
0810 
0811 QIODevice *KArchiveFile::createDevice() const
0812 {
0813     return new KLimitedIODevice(archive()->device(), d->pos, d->size);
0814 }
0815 
0816 bool KArchiveFile::isFile() const
0817 {
0818     return true;
0819 }
0820 
0821 static QFileDevice::Permissions withExecutablePerms(QFileDevice::Permissions filePerms, mode_t perms)
0822 {
0823     if (perms & 01) {
0824         filePerms |= QFileDevice::ExeOther;
0825     }
0826 
0827     if (perms & 010) {
0828         filePerms |= QFileDevice::ExeGroup;
0829     }
0830 
0831     if (perms & 0100) {
0832         filePerms |= QFileDevice::ExeOwner;
0833     }
0834 
0835     return filePerms;
0836 }
0837 
0838 bool KArchiveFile::copyTo(const QString &dest) const
0839 {
0840     QFile f(dest + QLatin1Char('/') + name());
0841     if (f.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
0842         QIODevice *inputDev = createDevice();
0843         if (!inputDev) {
0844             f.remove();
0845             return false;
0846         }
0847 
0848         // Read and write data in chunks to minimize memory usage
0849         const qint64 chunkSize = 1024 * 1024;
0850         qint64 remainingSize = d->size;
0851         QByteArray array;
0852         array.resize(int(qMin(chunkSize, remainingSize)));
0853 
0854         while (remainingSize > 0) {
0855             const qint64 currentChunkSize = qMin(chunkSize, remainingSize);
0856             const qint64 n = inputDev->read(array.data(), currentChunkSize);
0857             Q_UNUSED(n) // except in Q_ASSERT
0858             Q_ASSERT(n == currentChunkSize);
0859             f.write(array.data(), currentChunkSize);
0860             remainingSize -= currentChunkSize;
0861         }
0862         f.setPermissions(withExecutablePerms(f.permissions(), permissions()));
0863         f.close();
0864 
0865         delete inputDev;
0866         return true;
0867     }
0868     return false;
0869 }
0870 
0871 ////////////////////////////////////////////////////////////////////////
0872 //////////////////////// KArchiveDirectory /////////////////////////////////
0873 ////////////////////////////////////////////////////////////////////////
0874 
0875 KArchiveDirectory::KArchiveDirectory(KArchive *t,
0876                                      const QString &name,
0877                                      int access,
0878                                      const QDateTime &date,
0879                                      const QString &user,
0880                                      const QString &group,
0881                                      const QString &symlink)
0882     : KArchiveEntry(t, name, access, date, user, group, symlink)
0883     , d(new KArchiveDirectoryPrivate(this))
0884 {
0885 }
0886 
0887 KArchiveDirectory::~KArchiveDirectory()
0888 {
0889     delete d;
0890 }
0891 
0892 QStringList KArchiveDirectory::entries() const
0893 {
0894     return d->entries.keys();
0895 }
0896 
0897 const KArchiveEntry *KArchiveDirectory::entry(const QString &_name) const
0898 {
0899     KArchiveDirectory *dummy;
0900     return d->entry(_name, &dummy);
0901 }
0902 
0903 const KArchiveFile *KArchiveDirectory::file(const QString &name) const
0904 {
0905     const KArchiveEntry *e = entry(name);
0906     if (e && e->isFile()) {
0907         return static_cast<const KArchiveFile *>(e);
0908     }
0909     return nullptr;
0910 }
0911 
0912 void KArchiveDirectory::addEntry(KArchiveEntry *entry)
0913 {
0914     addEntryV2(entry);
0915 }
0916 
0917 bool KArchiveDirectory::addEntryV2(KArchiveEntry *entry)
0918 {
0919     if (d->entries.value(entry->name())) {
0920         qCWarning(KArchiveLog) << "directory " << name() << "has entry" << entry->name() << "already";
0921         delete entry;
0922         return false;
0923     }
0924     d->entries.insert(entry->name(), entry);
0925     return true;
0926 }
0927 
0928 void KArchiveDirectory::removeEntry(KArchiveEntry *entry)
0929 {
0930     if (!entry) {
0931         return;
0932     }
0933 
0934     QHash<QString, KArchiveEntry *>::Iterator it = d->entries.find(entry->name());
0935     // nothing removed?
0936     if (it == d->entries.end()) {
0937         qCWarning(KArchiveLog) << "directory " << name() << "has no entry with name " << entry->name();
0938         return;
0939     }
0940     if (it.value() != entry) {
0941         qCWarning(KArchiveLog) << "directory " << name() << "has another entry for name " << entry->name();
0942         return;
0943     }
0944     d->entries.erase(it);
0945 }
0946 
0947 bool KArchiveDirectory::isDirectory() const
0948 {
0949     return true;
0950 }
0951 
0952 static bool sortByPosition(const KArchiveFile *file1, const KArchiveFile *file2)
0953 {
0954     return file1->position() < file2->position();
0955 }
0956 
0957 bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
0958 {
0959     QDir root;
0960     const QString destDir(QDir(dest).absolutePath()); // get directory path without any "." or ".."
0961 
0962     QList<const KArchiveFile *> fileList;
0963     QMap<qint64, QString> fileToDir;
0964 
0965     // placeholders for iterated items
0966     QStack<const KArchiveDirectory *> dirStack;
0967     QStack<QString> dirNameStack;
0968 
0969     dirStack.push(this); // init stack at current directory
0970     dirNameStack.push(destDir); // ... with given path
0971     do {
0972         const KArchiveDirectory *curDir = dirStack.pop();
0973 
0974         // extract only to specified folder if it is located within archive's extraction folder
0975         // otherwise put file under root position in extraction folder
0976         QString curDirName = dirNameStack.pop();
0977         if (!QDir(curDirName).absolutePath().startsWith(destDir)) {
0978             qCWarning(KArchiveLog) << "Attempted export into folder" << curDirName << "which is outside of the extraction root folder" << destDir << "."
0979                                    << "Changing export of contained files to extraction root folder.";
0980             curDirName = destDir;
0981         }
0982 
0983         if (!root.mkpath(curDirName)) {
0984             return false;
0985         }
0986 
0987         const QStringList dirEntries = curDir->entries();
0988         for (QStringList::const_iterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
0989             const KArchiveEntry *curEntry = curDir->entry(*it);
0990             if (!curEntry->symLinkTarget().isEmpty()) {
0991                 QString linkName = curDirName + QLatin1Char('/') + curEntry->name();
0992                 // To create a valid link on Windows, linkName must have a .lnk file extension.
0993 #ifdef Q_OS_WIN
0994                 if (!linkName.endsWith(QLatin1String(".lnk"))) {
0995                     linkName += QLatin1String(".lnk");
0996                 }
0997 #endif
0998                 QFile symLinkTarget(curEntry->symLinkTarget());
0999                 if (!symLinkTarget.link(linkName)) {
1000                     // qCDebug(KArchiveLog) << "symlink(" << curEntry->symLinkTarget() << ',' << linkName << ") failed:" << strerror(errno);
1001                 }
1002             } else {
1003                 if (curEntry->isFile()) {
1004                     const KArchiveFile *curFile = dynamic_cast<const KArchiveFile *>(curEntry);
1005                     if (curFile) {
1006                         fileList.append(curFile);
1007                         fileToDir.insert(curFile->position(), curDirName);
1008                     }
1009                 }
1010 
1011                 if (curEntry->isDirectory() && recursiveCopy) {
1012                     const KArchiveDirectory *ad = dynamic_cast<const KArchiveDirectory *>(curEntry);
1013                     if (ad) {
1014                         dirStack.push(ad);
1015                         dirNameStack.push(curDirName + QLatin1Char('/') + curEntry->name());
1016                     }
1017                 }
1018             }
1019         }
1020     } while (!dirStack.isEmpty());
1021 
1022     std::sort(fileList.begin(), fileList.end(), sortByPosition); // sort on d->pos, so we have a linear access
1023 
1024     for (QList<const KArchiveFile *>::const_iterator it = fileList.constBegin(), end = fileList.constEnd(); it != end; ++it) {
1025         const KArchiveFile *f = *it;
1026         qint64 pos = f->position();
1027         if (!f->copyTo(fileToDir[pos])) {
1028             return false;
1029         }
1030     }
1031     return true;
1032 }
1033 
1034 void KArchive::virtual_hook(int, void *)
1035 {
1036     /*BASE::virtual_hook( id, data )*/;
1037 }
1038 
1039 void KArchiveEntry::virtual_hook(int, void *)
1040 {
1041     /*BASE::virtual_hook( id, data );*/
1042 }
1043 
1044 void KArchiveFile::virtual_hook(int id, void *data)
1045 {
1046     KArchiveEntry::virtual_hook(id, data);
1047 }
1048 
1049 void KArchiveDirectory::virtual_hook(int id, void *data)
1050 {
1051     KArchiveEntry::virtual_hook(id, data);
1052 }