1 /* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
3 SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
4
5 Moved from ktar.cpp by Roberto Teixeira <maragato@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only
8 */
9
10 #include "karchive.h"
11 #include "karchive_p.h"
12 #include "klimitediodevice_p.h"
13 #include "loggingcategory.h"
14
15 #include <qplatformdefs.h> // QT_STATBUF, QT_LSTAT
16
17 #include <QStack>
18 #include <QMap>
19 #include <QDebug>
20 #include <QDir>
21 #include <QFile>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <errno.h>
26
27 #include <assert.h>
28
29 #ifdef Q_OS_UNIX
30 #include <grp.h>
31 #include <pwd.h>
32 #include <limits.h> // PATH_MAX
33 #include <unistd.h>
34 #endif
35 #ifdef Q_OS_WIN
36 #include <windows.h> // DWORD, GetUserNameW
37 #endif // Q_OS_WIN
38
39 ////////////////////////////////////////////////////////////////////////
40 /////////////////// KArchiveDirectoryPrivate ///////////////////////////
41 ////////////////////////////////////////////////////////////////////////
42
43 class KArchiveDirectoryPrivate
44 {
45 public:
KArchiveDirectoryPrivate(KArchiveDirectory * parent)46 KArchiveDirectoryPrivate(KArchiveDirectory *parent) : q(parent)
47 {
48 }
49
~KArchiveDirectoryPrivate()50 ~KArchiveDirectoryPrivate()
51 {
52 qDeleteAll(entries);
53 }
54
55 KArchiveDirectoryPrivate(const KArchiveDirectoryPrivate &) = delete;
56 KArchiveDirectoryPrivate &operator=(const KArchiveDirectoryPrivate &) = delete;
57
get(KArchiveDirectory * directory)58 static KArchiveDirectoryPrivate *get(KArchiveDirectory *directory)
59 {
60 return directory->d;
61 }
62
63 // Returns in containingDirectory the directory that actually contains the returned entry
entry(const QString & _name,KArchiveDirectory ** containingDirectory) const64 const KArchiveEntry *entry(const QString &_name, KArchiveDirectory **containingDirectory) const
65 {
66 *containingDirectory = q;
67
68 QString name = QDir::cleanPath(_name);
69 int pos = name.indexOf(QLatin1Char('/'));
70 if (pos == 0) { // ouch absolute path (see also KArchive::findOrCreate)
71 if (name.length() > 1) {
72 name = name.mid(1); // remove leading slash
73 pos = name.indexOf(QLatin1Char('/')); // look again
74 } else { // "/"
75 return q;
76 }
77 }
78 // trailing slash ? -> remove
79 if (pos != -1 && pos == name.length() - 1) {
80 name = name.left(pos);
81 pos = name.indexOf(QLatin1Char('/')); // look again
82 }
83 if (pos != -1) {
84 const QString left = name.left(pos);
85 const QString right = name.mid(pos + 1);
86
87 //qCDebug(KArchiveLog) << "left=" << left << "right=" << right;
88
89 KArchiveEntry *e = entries.value(left);
90 if (!e || !e->isDirectory()) {
91 return nullptr;
92 }
93 *containingDirectory = static_cast<KArchiveDirectory *>(e);
94 return (*containingDirectory)->d->entry(right, containingDirectory);
95 }
96
97 return entries.value(name);
98 }
99
100 KArchiveDirectory *q;
101 QHash<QString, KArchiveEntry *> entries;
102 };
103
104 ////////////////////////////////////////////////////////////////////////
105 /////////////////////////// KArchive ///////////////////////////////////
106 ////////////////////////////////////////////////////////////////////////
107
KArchive(const QString & fileName)108 KArchive::KArchive(const QString &fileName)
109 : d(new KArchivePrivate(this))
110 {
111 if (fileName.isEmpty()) {
112 qCWarning(KArchiveLog) << "KArchive: No file name specified";
113 }
114 d->fileName = fileName;
115 // This constructor leaves the device set to 0.
116 // This is for the use of QSaveFile, see open().
117 }
118
KArchive(QIODevice * dev)119 KArchive::KArchive(QIODevice *dev)
120 : d(new KArchivePrivate(this))
121 {
122 if (!dev) {
123 qCWarning(KArchiveLog) << "KArchive: Null device specified";
124 }
125 d->dev = dev;
126 }
127
~KArchive()128 KArchive::~KArchive()
129 {
130 Q_ASSERT(!isOpen()); // the derived class destructor must have closed already
131 delete d;
132 }
133
open(QIODevice::OpenMode mode)134 bool KArchive::open(QIODevice::OpenMode mode)
135 {
136 Q_ASSERT(mode != QIODevice::NotOpen);
137
138 if (isOpen()) {
139 close();
140 }
141
142 if (!d->fileName.isEmpty()) {
143 Q_ASSERT(!d->dev);
144 if (!createDevice(mode)) {
145 return false;
146 }
147 }
148
149 if (!d->dev) {
150 setErrorString(tr("No filename or device was specified"));
151 return false;
152 }
153
154 if (!d->dev->isOpen() && !d->dev->open(mode)) {
155 setErrorString(tr("Could not set device mode to %1").arg(mode));
156 return false;
157 }
158
159 d->mode = mode;
160
161 Q_ASSERT(!d->rootDir);
162 d->rootDir = nullptr;
163
164 return openArchive(mode);
165 }
166
createDevice(QIODevice::OpenMode mode)167 bool KArchive::createDevice(QIODevice::OpenMode mode)
168 {
169 switch (mode) {
170 case QIODevice::WriteOnly:
171 if (!d->fileName.isEmpty()) {
172 // The use of QSaveFile can't be done in the ctor (no mode known yet)
173 //qCDebug(KArchiveLog) << "Writing to a file using QSaveFile";
174 d->saveFile = new QSaveFile(d->fileName);
175 #ifdef Q_OS_ANDROID
176 // we cannot rename on to Android content: URLs
177 if (d->fileName.startsWith(QLatin1String("content://"))) {
178 d->saveFile->setDirectWriteFallback(true);
179 }
180 #endif
181 if (!d->saveFile->open(QIODevice::WriteOnly)) {
182 setErrorString(
183 tr("QSaveFile creation for %1 failed: %2")
184 .arg(d->fileName, d->saveFile->errorString()));
185
186 delete d->saveFile;
187 d->saveFile = nullptr;
188 return false;
189 }
190 d->dev = d->saveFile;
191 Q_ASSERT(d->dev);
192 }
193 break;
194 case QIODevice::ReadOnly:
195 case QIODevice::ReadWrite:
196 // ReadWrite mode still uses QFile for now; we'd need to copy to the tempfile, in fact.
197 if (!d->fileName.isEmpty()) {
198 d->dev = new QFile(d->fileName);
199 d->deviceOwned = true;
200 }
201 break; // continued below
202 default:
203 setErrorString(tr("Unsupported mode %1").arg(d->mode));
204 return false;
205 }
206 return true;
207 }
208
close()209 bool KArchive::close()
210 {
211 if (!isOpen()) {
212 setErrorString(tr("Archive already closed"));
213 return false; // already closed (return false or true? arguable...)
214 }
215
216 // moved by holger to allow kzip to write the zip central dir
217 // to the file in closeArchive()
218 // DF: added d->dev so that we skip closeArchive if saving aborted.
219 bool closeSucceeded = true;
220 if (d->dev) {
221 closeSucceeded = closeArchive();
222 if (d->mode == QIODevice::WriteOnly && !closeSucceeded) {
223 d->abortWriting();
224 }
225 }
226
227 if (d->dev && d->dev != d->saveFile) {
228 d->dev->close();
229 }
230
231 // if d->saveFile is not null then it is equal to d->dev.
232 if (d->saveFile) {
233 closeSucceeded = d->saveFile->commit();
234 delete d->saveFile;
235 d->saveFile = nullptr;
236 }
237 if (d->deviceOwned) {
238 delete d->dev; // we created it ourselves in open()
239 }
240
241 delete d->rootDir;
242 d->rootDir = nullptr;
243 d->mode = QIODevice::NotOpen;
244 d->dev = nullptr;
245 return closeSucceeded;
246 }
247
errorString() const248 QString KArchive::errorString() const
249 {
250 return d->errorStr;
251 }
252
directory() const253 const KArchiveDirectory *KArchive::directory() const
254 {
255 // rootDir isn't const so that parsing-on-demand is possible
256 return const_cast<KArchive *>(this)->rootDir();
257 }
258
addLocalFile(const QString & fileName,const QString & destName)259 bool KArchive::addLocalFile(const QString &fileName, const QString &destName)
260 {
261 QFileInfo fileInfo(fileName);
262 if (!fileInfo.isFile() && !fileInfo.isSymLink()) {
263 setErrorString(
264 tr("%1 doesn't exist or is not a regular file.")
265 .arg(fileName));
266 return false;
267 }
268
269 #if defined(Q_OS_UNIX)
270 #define STAT_METHOD QT_LSTAT
271 #else
272 #define STAT_METHOD QT_STAT
273 #endif
274 QT_STATBUF fi;
275 if (STAT_METHOD(QFile::encodeName(fileName).constData(), &fi) == -1) {
276 setErrorString(
277 tr("Failed accessing the file %1 for adding to the archive. The error was: %2")
278 .arg(fileName)
279 .arg(QLatin1String{strerror(errno)}));
280 return false;
281 }
282
283 if (fileInfo.isSymLink()) {
284 QString symLinkTarget;
285 // Do NOT use fileInfo.symLinkTarget() for unix symlinks!
286 // It returns the -full- path to the target, while we want the target string "as is".
287 #if defined(Q_OS_UNIX) && !defined(Q_OS_OS2EMX)
288 const QByteArray encodedFileName = QFile::encodeName(fileName);
289 QByteArray s;
290 #if defined(PATH_MAX)
291 s.resize(PATH_MAX + 1);
292 #else
293 int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX);
294 if (path_max <= 0) {
295 path_max = 4096;
296 }
297 s.resize(path_max);
298 #endif
299 int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
300 if (len >= 0) {
301 s[len] = '\0';
302 symLinkTarget = QFile::decodeName(s.constData());
303 }
304 #endif
305 if (symLinkTarget.isEmpty()) { // Mac or Windows
306 symLinkTarget = fileInfo.symLinkTarget();
307 }
308 return writeSymLink(destName, symLinkTarget, fileInfo.owner(),
309 fileInfo.group(), fi.st_mode, fileInfo.lastRead(), fileInfo.lastModified(),
310 fileInfo.birthTime());
311 }/*end if*/
312
313 qint64 size = fileInfo.size();
314
315 // the file must be opened before prepareWriting is called, otherwise
316 // if the opening fails, no content will follow the already written
317 // header and the tar file is effectively f*cked up
318 QFile file(fileName);
319 if (!file.open(QIODevice::ReadOnly)) {
320 setErrorString(
321 tr("Couldn't open file %1: %2")
322 .arg(fileName, file.errorString()));
323 return false;
324 }
325
326 if (!prepareWriting(destName, fileInfo.owner(), fileInfo.group(), size,
327 fi.st_mode, fileInfo.lastRead(), fileInfo.lastModified(), fileInfo.birthTime())) {
328 //qCWarning(KArchiveLog) << " prepareWriting" << destName << "failed";
329 return false;
330 }
331
332 // Read and write data in chunks to minimize memory usage
333 QByteArray array;
334 array.resize(int(qMin(qint64(1024 * 1024), size)));
335 qint64 n;
336 qint64 total = 0;
337 while ((n = file.read(array.data(), array.size())) > 0) {
338 if (!writeData(array.data(), n)) {
339 //qCWarning(KArchiveLog) << "writeData failed";
340 return false;
341 }
342 total += n;
343 }
344 Q_ASSERT(total == size);
345
346 if (!finishWriting(size)) {
347 //qCWarning(KArchiveLog) << "finishWriting failed";
348 return false;
349 }
350 return true;
351 }
352
addLocalDirectory(const QString & path,const QString & destName)353 bool KArchive::addLocalDirectory(const QString &path, const QString &destName)
354 {
355 QDir dir(path);
356 if (!dir.exists()) {
357 setErrorString(
358 tr("Directory %1 does not exist")
359 .arg(path));
360 return false;
361 }
362 dir.setFilter(dir.filter() | QDir::Hidden);
363 const QStringList files = dir.entryList();
364 for (QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
365 if (*it != QLatin1String(".") && *it != QLatin1String("..")) {
366 QString fileName = path + QLatin1Char('/') + *it;
367 // qCDebug(KArchiveLog) << "storing " << fileName;
368 QString dest = destName.isEmpty() ? *it : (destName + QLatin1Char('/') + *it);
369 QFileInfo fileInfo(fileName);
370
371 if (fileInfo.isFile() || fileInfo.isSymLink()) {
372 addLocalFile(fileName, dest);
373 } else if (fileInfo.isDir()) {
374 addLocalDirectory(fileName, dest);
375 }
376 // We omit sockets
377 }
378 }
379 return true;
380 }
381
writeFile(const QString & name,const QByteArray & data,mode_t perm,const QString & user,const QString & group,const QDateTime & atime,const QDateTime & mtime,const QDateTime & ctime)382 bool KArchive::writeFile(const QString &name, const QByteArray &data,
383 mode_t perm,
384 const QString &user, const QString &group, const QDateTime &atime,
385 const QDateTime &mtime, const QDateTime &ctime)
386 {
387 const qint64 size = data.size();
388 if (!prepareWriting(name, user, group, size, perm, atime, mtime, ctime)) {
389 //qCWarning(KArchiveLog) << "prepareWriting failed";
390 return false;
391 }
392
393 // Write data
394 // Note: if data is null, don't call write, it would terminate the KCompressionDevice
395 if (data.constData() && size && !writeData(data.constData(), size)) {
396 //qCWarning(KArchiveLog) << "writeData failed";
397 return false;
398 }
399
400 if (!finishWriting(size)) {
401 //qCWarning(KArchiveLog) << "finishWriting failed";
402 return false;
403 }
404 return true;
405 }
406
writeData(const char * data,qint64 size)407 bool KArchive::writeData(const char *data, qint64 size)
408 {
409 bool ok = device()->write(data, size) == size;
410 if (!ok) {
411 setErrorString(
412 tr("Writing failed: %1")
413 .arg(device()->errorString()));
414 d->abortWriting();
415 }
416 return ok;
417 }
418
419 // The writeDir -> doWriteDir pattern allows to avoid propagating the default
420 // values into all virtual methods of subclasses, and it allows more extensibility:
421 // if a new argument is needed, we can add a writeDir overload which stores the
422 // additional argument in the d pointer, and doWriteDir reimplementations can fetch
423 // it from there.
424
writeDir(const QString & name,const QString & user,const QString & group,mode_t perm,const QDateTime & atime,const QDateTime & mtime,const QDateTime & ctime)425 bool KArchive::writeDir(const QString &name, const QString &user, const QString &group,
426 mode_t perm, const QDateTime &atime,
427 const QDateTime &mtime, const QDateTime &ctime)
428 {
429 return doWriteDir(name, user, group, perm | 040000, atime, mtime, ctime);
430 }
431
writeSymLink(const QString & name,const QString & target,const QString & user,const QString & group,mode_t perm,const QDateTime & atime,const QDateTime & mtime,const QDateTime & ctime)432 bool KArchive::writeSymLink(const QString &name, const QString &target,
433 const QString &user, const QString &group,
434 mode_t perm, const QDateTime &atime,
435 const QDateTime &mtime, const QDateTime &ctime)
436 {
437 return doWriteSymLink(name, target, user, group, perm, atime, mtime, ctime);
438 }
439
prepareWriting(const QString & name,const QString & user,const QString & group,qint64 size,mode_t perm,const QDateTime & atime,const QDateTime & mtime,const QDateTime & ctime)440 bool KArchive::prepareWriting(const QString &name, const QString &user,
441 const QString &group, qint64 size,
442 mode_t perm, const QDateTime &atime,
443 const QDateTime &mtime, const QDateTime &ctime)
444 {
445 bool ok = doPrepareWriting(name, user, group, size, perm, atime, mtime, ctime);
446 if (!ok) {
447 d->abortWriting();
448 }
449 return ok;
450 }
451
finishWriting(qint64 size)452 bool KArchive::finishWriting(qint64 size)
453 {
454 return doFinishWriting(size);
455 }
456
setErrorString(const QString & errorStr)457 void KArchive::setErrorString(const QString &errorStr)
458 {
459 d->errorStr = errorStr;
460 }
461
getCurrentUserName()462 static QString getCurrentUserName()
463 {
464 #if defined(Q_OS_UNIX)
465 struct passwd *pw = getpwuid(getuid());
466 return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid());
467 #elif defined(Q_OS_WIN)
468 wchar_t buffer[255];
469 DWORD size = 255;
470 bool ok = GetUserNameW(buffer, &size);
471 if (!ok) {
472 return QString();
473 }
474 return QString::fromWCharArray(buffer);
475 #else
476 return QString();
477 #endif
478 }
479
getCurrentGroupName()480 static QString getCurrentGroupName()
481 {
482 #if defined(Q_OS_UNIX)
483 struct group *grp = getgrgid(getgid());
484 return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid());
485 #elif defined(Q_OS_WIN)
486 return QString();
487 #else
488 return QString();
489 #endif
490 }
491
rootDir()492 KArchiveDirectory *KArchive::rootDir()
493 {
494 if (!d->rootDir) {
495 //qCDebug(KArchiveLog) << "Making root dir ";
496 QString username = ::getCurrentUserName();
497 QString groupname = ::getCurrentGroupName();
498
499 d->rootDir = new KArchiveDirectory(this, QStringLiteral("/"), int(0777 + S_IFDIR), QDateTime(), username, groupname, QString());
500 }
501 return d->rootDir;
502 }
503
findOrCreate(const QString & path)504 KArchiveDirectory *KArchive::findOrCreate(const QString &path)
505 {
506 return d->findOrCreate(path, 0 /*recursionCounter*/);
507 }
508
findOrCreate(const QString & path,int recursionCounter)509 KArchiveDirectory *KArchivePrivate::findOrCreate(const QString &path, int recursionCounter)
510 {
511 // Check we're not in a path that is ultra deep, this is most probably fine since PATH_MAX on Linux
512 // is defined as 4096, so even on /a/a/a/a/a/a 2500 recursions puts us over that limit
513 // an ultra deep recursion will makes us crash due to not enough stack. Tests show that 1MB stack
514 // (default on Linux seems to be 8MB) gives us up to around 4000 recursions
515 if (recursionCounter > 2500) {
516 qCWarning(KArchiveLog) << "path recursion limit exceeded, bailing out";
517 return nullptr;
518 }
519 //qCDebug(KArchiveLog) << path;
520 if (path.isEmpty() || path == QLatin1String("/") || path == QLatin1String(".")) { // root dir => found
521 //qCDebug(KArchiveLog) << "returning rootdir";
522 return q->rootDir();
523 }
524 // Important note : for tar files containing absolute paths
525 // (i.e. beginning with "/"), this means the leading "/" will
526 // be removed (no KDirectory for it), which is exactly the way
527 // the "tar" program works (though it displays a warning about it)
528 // See also KArchiveDirectory::entry().
529
530 // Already created ? => found
531 KArchiveDirectory *existingEntryParentDirectory;
532 const KArchiveEntry *existingEntry = KArchiveDirectoryPrivate::get(q->rootDir())->entry(path, &existingEntryParentDirectory);
533 if (existingEntry) {
534 if (existingEntry->isDirectory())
535 //qCDebug(KArchiveLog) << "found it";
536 {
537 const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(existingEntry);
538 return const_cast<KArchiveDirectory *>(dir);
539 } else {
540 const KArchiveFile *file = static_cast<const KArchiveFile *>(existingEntry);
541 if (file->size() > 0) {
542 qCWarning(KArchiveLog) << path << "is normal file, but there are file paths in the archive assuming it is a directory, bailing out";
543 return nullptr;
544 }
545
546 qCDebug(KArchiveLog) << path << " is an empty file, assuming it is actually a directory and replacing";
547 KArchiveEntry *myEntry = const_cast<KArchiveEntry*>(existingEntry);
548 existingEntryParentDirectory->removeEntry(myEntry);
549 delete myEntry;
550 }
551 }
552
553 // Otherwise go up and try again
554 int pos = path.lastIndexOf(QLatin1Char('/'));
555 KArchiveDirectory *parent;
556 QString dirname;
557 if (pos == -1) { // no more slash => create in root dir
558 parent = q->rootDir();
559 dirname = path;
560 } else {
561 QString left = path.left(pos);
562 dirname = path.mid(pos + 1);
563 parent = findOrCreate(left, recursionCounter + 1); // recursive call... until we find an existing dir.
564 }
565
566 if (!parent) {
567 return nullptr;
568 }
569
570 //qCDebug(KArchiveLog) << "found parent " << parent->name() << " adding " << dirname << " to ensure " << path;
571 // Found -> add the missing piece
572 KArchiveDirectory *e = new KArchiveDirectory(q, dirname, rootDir->permissions(),
573 rootDir->date(), rootDir->user(),
574 rootDir->group(), QString());
575 if (parent->addEntryV2(e)) {
576 return e; // now a directory to <path> exists
577 } else {
578 return nullptr;
579 }
580 }
581
setDevice(QIODevice * dev)582 void KArchive::setDevice(QIODevice *dev)
583 {
584 if (d->deviceOwned) {
585 delete d->dev;
586 }
587 d->dev = dev;
588 d->deviceOwned = false;
589 }
590
setRootDir(KArchiveDirectory * rootDir)591 void KArchive::setRootDir(KArchiveDirectory *rootDir)
592 {
593 Q_ASSERT(!d->rootDir); // Call setRootDir only once during parsing please ;)
594 delete d->rootDir; // but if it happens, don't leak
595 d->rootDir = rootDir;
596 }
597
mode() const598 QIODevice::OpenMode KArchive::mode() const
599 {
600 return d->mode;
601 }
602
device() const603 QIODevice *KArchive::device() const
604 {
605 return d->dev;
606 }
607
isOpen() const608 bool KArchive::isOpen() const
609 {
610 return d->mode != QIODevice::NotOpen;
611 }
612
fileName() const613 QString KArchive::fileName() const
614 {
615 return d->fileName;
616 }
617
abortWriting()618 void KArchivePrivate::abortWriting()
619 {
620 if (saveFile) {
621 saveFile->cancelWriting();
622 delete saveFile;
623 saveFile = nullptr;
624 dev = nullptr;
625 }
626 }
627
628 // this is a hacky wrapper to check if time_t value is invalid
time_tToDateTime(uint time_t)629 QDateTime KArchivePrivate::time_tToDateTime(uint time_t)
630 {
631 if (time_t == uint(-1)) {
632 return QDateTime();
633 }
634 return QDateTime::fromSecsSinceEpoch(time_t);
635 }
636
637 ////////////////////////////////////////////////////////////////////////
638 /////////////////////// KArchiveEntry //////////////////////////////////
639 ////////////////////////////////////////////////////////////////////////
640
641 class KArchiveEntryPrivate
642 {
643 public:
KArchiveEntryPrivate(KArchive * _archive,const QString & _name,int _access,const QDateTime & _date,const QString & _user,const QString & _group,const QString & _symlink)644 KArchiveEntryPrivate(KArchive *_archive, const QString &_name, int _access,
645 const QDateTime &_date, const QString &_user, const QString &_group,
646 const QString &_symlink)
647 : name(_name)
648 , date(_date)
649 , access(_access)
650 , user(_user)
651 , group(_group)
652 , symlink(_symlink)
653 , archive(_archive)
654 {
655 }
656 QString name;
657 QDateTime date;
658 mode_t access;
659 QString user;
660 QString group;
661 QString symlink;
662 KArchive *archive;
663 };
664
KArchiveEntry(KArchive * t,const QString & name,int access,const QDateTime & date,const QString & user,const QString & group,const QString & symlink)665 KArchiveEntry::KArchiveEntry(KArchive *t, const QString &name, int access, const QDateTime &date,
666 const QString &user, const QString &group, const
667 QString &symlink)
668 : d(new KArchiveEntryPrivate(t, name, access, date, user, group, symlink))
669 {
670 }
671
~KArchiveEntry()672 KArchiveEntry::~KArchiveEntry()
673 {
674 delete d;
675 }
676
date() const677 QDateTime KArchiveEntry::date() const
678 {
679 return d->date;
680 }
681
name() const682 QString KArchiveEntry::name() const
683 {
684 return d->name;
685 }
686
permissions() const687 mode_t KArchiveEntry::permissions() const
688 {
689 return d->access;
690 }
691
user() const692 QString KArchiveEntry::user() const
693 {
694 return d->user;
695 }
696
group() const697 QString KArchiveEntry::group() const
698 {
699 return d->group;
700 }
701
symLinkTarget() const702 QString KArchiveEntry::symLinkTarget() const
703 {
704 return d->symlink;
705 }
706
isFile() const707 bool KArchiveEntry::isFile() const
708 {
709 return false;
710 }
711
isDirectory() const712 bool KArchiveEntry::isDirectory() const
713 {
714 return false;
715 }
716
archive() const717 KArchive *KArchiveEntry::archive() const
718 {
719 return d->archive;
720 }
721
722 ////////////////////////////////////////////////////////////////////////
723 /////////////////////// KArchiveFile ///////////////////////////////////
724 ////////////////////////////////////////////////////////////////////////
725
726 class KArchiveFilePrivate
727 {
728 public:
KArchiveFilePrivate(qint64 _pos,qint64 _size)729 KArchiveFilePrivate(qint64 _pos, qint64 _size)
730 : pos(_pos)
731 , size(_size)
732 {
733 }
734 qint64 pos;
735 qint64 size;
736 };
737
KArchiveFile(KArchive * t,const QString & name,int access,const QDateTime & date,const QString & user,const QString & group,const QString & symlink,qint64 pos,qint64 size)738 KArchiveFile::KArchiveFile(KArchive *t, const QString &name, int access, const QDateTime &date,
739 const QString &user, const QString &group,
740 const QString &symlink,
741 qint64 pos, qint64 size)
742 : KArchiveEntry(t, name, access, date, user, group, symlink)
743 , d(new KArchiveFilePrivate(pos, size))
744 {
745 }
746
~KArchiveFile()747 KArchiveFile::~KArchiveFile()
748 {
749 delete d;
750 }
751
position() const752 qint64 KArchiveFile::position() const
753 {
754 return d->pos;
755 }
756
size() const757 qint64 KArchiveFile::size() const
758 {
759 return d->size;
760 }
761
setSize(qint64 s)762 void KArchiveFile::setSize(qint64 s)
763 {
764 d->size = s;
765 }
766
data() const767 QByteArray KArchiveFile::data() const
768 {
769 bool ok = archive()->device()->seek(d->pos);
770 if (!ok) {
771 //qCWarning(KArchiveLog) << "Failed to sync to" << d->pos << "to read" << name();
772 }
773
774 // Read content
775 QByteArray arr;
776 if (d->size) {
777 arr = archive()->device()->read(d->size);
778 Q_ASSERT(arr.size() == d->size);
779 }
780 return arr;
781 }
782
createDevice() const783 QIODevice *KArchiveFile::createDevice() const
784 {
785 return new KLimitedIODevice(archive()->device(), d->pos, d->size);
786 }
787
isFile() const788 bool KArchiveFile::isFile() const
789 {
790 return true;
791 }
792
withExecutablePerms(QFileDevice::Permissions filePerms,mode_t perms)793 static QFileDevice::Permissions withExecutablePerms(
794 QFileDevice::Permissions filePerms,
795 mode_t perms)
796 {
797 if (perms & 01)
798 filePerms |= QFileDevice::ExeOther;
799
800 if (perms & 010)
801 filePerms |= QFileDevice::ExeGroup;
802
803 if (perms & 0100)
804 filePerms |= QFileDevice::ExeOwner;
805
806 return filePerms;
807 }
808
copyTo(const QString & dest) const809 bool KArchiveFile::copyTo(const QString &dest) const
810 {
811 QFile f(dest + QLatin1Char('/') + name());
812 if (f.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
813 QIODevice *inputDev = createDevice();
814 if (!inputDev) {
815 f.remove();
816 return false;
817 }
818
819 // Read and write data in chunks to minimize memory usage
820 const qint64 chunkSize = 1024 * 1024;
821 qint64 remainingSize = d->size;
822 QByteArray array;
823 array.resize(int(qMin(chunkSize, remainingSize)));
824
825 while (remainingSize > 0) {
826 const qint64 currentChunkSize = qMin(chunkSize, remainingSize);
827 const qint64 n = inputDev->read(array.data(), currentChunkSize);
828 Q_UNUSED(n) // except in Q_ASSERT
829 Q_ASSERT(n == currentChunkSize);
830 f.write(array.data(), currentChunkSize);
831 remainingSize -= currentChunkSize;
832 }
833 f.setPermissions(withExecutablePerms(f.permissions(), permissions()));
834 f.close();
835
836 delete inputDev;
837 return true;
838 }
839 return false;
840 }
841
842 ////////////////////////////////////////////////////////////////////////
843 //////////////////////// KArchiveDirectory /////////////////////////////////
844 ////////////////////////////////////////////////////////////////////////
845
KArchiveDirectory(KArchive * t,const QString & name,int access,const QDateTime & date,const QString & user,const QString & group,const QString & symlink)846 KArchiveDirectory::KArchiveDirectory(KArchive *t, const QString &name, int access,
847 const QDateTime &date,
848 const QString &user, const QString &group,
849 const QString &symlink)
850 : KArchiveEntry(t, name, access, date, user, group, symlink)
851 , d(new KArchiveDirectoryPrivate(this))
852 {
853 }
854
~KArchiveDirectory()855 KArchiveDirectory::~KArchiveDirectory()
856 {
857 delete d;
858 }
859
entries() const860 QStringList KArchiveDirectory::entries() const
861 {
862 return d->entries.keys();
863 }
864
entry(const QString & _name) const865 const KArchiveEntry *KArchiveDirectory::entry(const QString &_name) const
866 {
867 KArchiveDirectory *dummy;
868 return d->entry(_name, &dummy);
869 }
870
file(const QString & name) const871 const KArchiveFile *KArchiveDirectory::file(const QString &name) const
872 {
873 const KArchiveEntry *e = entry(name);
874 if (e && e->isFile()) {
875 return static_cast<const KArchiveFile *>(e);
876 }
877 return nullptr;
878 }
879
addEntry(KArchiveEntry * entry)880 void KArchiveDirectory::addEntry(KArchiveEntry *entry)
881 {
882 addEntryV2(entry);
883 }
884
addEntryV2(KArchiveEntry * entry)885 bool KArchiveDirectory::addEntryV2(KArchiveEntry *entry)
886 {
887 if (d->entries.value(entry->name())) {
888 qCWarning(KArchiveLog) << "directory " << name()
889 << "has entry" << entry->name() << "already";
890 delete entry;
891 return false;
892 }
893 d->entries.insert(entry->name(), entry);
894 return true;
895 }
896
removeEntry(KArchiveEntry * entry)897 void KArchiveDirectory::removeEntry(KArchiveEntry *entry)
898 {
899 if (!entry) {
900 return;
901 }
902
903 QHash<QString, KArchiveEntry *>::Iterator it = d->entries.find(entry->name());
904 // nothing removed?
905 if (it == d->entries.end()) {
906 qCWarning(KArchiveLog) << "directory " << name()
907 << "has no entry with name " << entry->name();
908 return;
909 }
910 if (it.value() != entry) {
911 qCWarning(KArchiveLog) << "directory " << name()
912 << "has another entry for name " << entry->name();
913 return;
914 }
915 d->entries.erase(it);
916 }
917
isDirectory() const918 bool KArchiveDirectory::isDirectory() const
919 {
920 return true;
921 }
922
sortByPosition(const KArchiveFile * file1,const KArchiveFile * file2)923 static bool sortByPosition(const KArchiveFile *file1, const KArchiveFile *file2)
924 {
925 return file1->position() < file2->position();
926 }
927
copyTo(const QString & dest,bool recursiveCopy) const928 bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
929 {
930 QDir root;
931 const QString destDir(QDir(dest).absolutePath()); // get directory path without any "." or ".."
932
933 QList<const KArchiveFile *> fileList;
934 QMap<qint64, QString> fileToDir;
935
936 // placeholders for iterated items
937 QStack<const KArchiveDirectory *> dirStack;
938 QStack<QString> dirNameStack;
939
940 dirStack.push(this); // init stack at current directory
941 dirNameStack.push(destDir); // ... with given path
942 do {
943 const KArchiveDirectory *curDir = dirStack.pop();
944
945 // extract only to specified folder if it is located within archive's extraction folder
946 // otherwise put file under root position in extraction folder
947 QString curDirName = dirNameStack.pop();
948 if (!QDir(curDirName).absolutePath().startsWith(destDir)) {
949 qCWarning(KArchiveLog) << "Attempted export into folder" << curDirName
950 << "which is outside of the extraction root folder" << destDir << "."
951 << "Changing export of contained files to extraction root folder.";
952 curDirName = destDir;
953 }
954
955 if (!root.mkpath(curDirName)) {
956 return false;
957 }
958
959 const QStringList dirEntries = curDir->entries();
960 for (QStringList::const_iterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
961 const KArchiveEntry *curEntry = curDir->entry(*it);
962 if (!curEntry->symLinkTarget().isEmpty()) {
963 QString linkName = curDirName + QLatin1Char('/') + curEntry->name();
964 // To create a valid link on Windows, linkName must have a .lnk file extension.
965 #ifdef Q_OS_WIN
966 if (!linkName.endsWith(QLatin1String(".lnk"))) {
967 linkName += QLatin1String(".lnk");
968 }
969 #endif
970 QFile symLinkTarget(curEntry->symLinkTarget());
971 if (!symLinkTarget.link(linkName)) {
972 //qCDebug(KArchiveLog) << "symlink(" << curEntry->symLinkTarget() << ',' << linkName << ") failed:" << strerror(errno);
973 }
974 } else {
975 if (curEntry->isFile()) {
976 const KArchiveFile *curFile = dynamic_cast<const KArchiveFile *>(curEntry);
977 if (curFile) {
978 fileList.append(curFile);
979 fileToDir.insert(curFile->position(), curDirName);
980 }
981 }
982
983 if (curEntry->isDirectory() && recursiveCopy) {
984 const KArchiveDirectory *ad = dynamic_cast<const KArchiveDirectory *>(curEntry);
985 if (ad) {
986 dirStack.push(ad);
987 dirNameStack.push(curDirName + QLatin1Char('/') + curEntry->name());
988 }
989 }
990 }
991 }
992 } while (!dirStack.isEmpty());
993
994 std::sort(fileList.begin(), fileList.end(), sortByPosition); // sort on d->pos, so we have a linear access
995
996 for (QList<const KArchiveFile *>::const_iterator it = fileList.constBegin(), end = fileList.constEnd();
997 it != end; ++it) {
998 const KArchiveFile *f = *it;
999 qint64 pos = f->position();
1000 if (!f->copyTo(fileToDir[pos])) {
1001 return false;
1002 }
1003 }
1004 return true;
1005 }
1006
virtual_hook(int,void *)1007 void KArchive::virtual_hook(int, void *)
1008 {
1009 /*BASE::virtual_hook( id, data )*/;
1010 }
1011
virtual_hook(int,void *)1012 void KArchiveEntry::virtual_hook(int, void *)
1013 {
1014 /*BASE::virtual_hook( id, data );*/
1015 }
1016
virtual_hook(int id,void * data)1017 void KArchiveFile::virtual_hook(int id, void *data)
1018 {
1019 KArchiveEntry::virtual_hook(id, data);
1020 }
1021
virtual_hook(int id,void * data)1022 void KArchiveDirectory::virtual_hook(int id, void *data)
1023 {
1024 KArchiveEntry::virtual_hook(id, data);
1025 }
1026