File indexing completed on 2024-03-24 03:55:18
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 }