File indexing completed on 2025-02-16 13:00:36
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 }