Coverage Report

Created: 2025-03-15 06:58

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