File indexing completed on 2024-04-21 03:52:33

0001 /* This file is part of the KDE libraries
0002    SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0003    SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "ktar.h"
0009 #include "karchive_p.h"
0010 #include "kcompressiondevice.h"
0011 #include "kfilterbase.h"
0012 #include "loggingcategory.h"
0013 
0014 #include <QDebug>
0015 #include <QDir>
0016 #include <QFile>
0017 #include <QMimeDatabase>
0018 #include <QTemporaryFile>
0019 
0020 #include <assert.h>
0021 #include <stdlib.h> // strtol
0022 
0023 ////////////////////////////////////////////////////////////////////////
0024 /////////////////////////// KTar ///////////////////////////////////
0025 ////////////////////////////////////////////////////////////////////////
0026 
0027 // Mime types of known filters
0028 static const char application_bzip[] = "application/x-bzip";
0029 static const char application_lzma[] = "application/x-lzma";
0030 static const char application_xz[] = "application/x-xz";
0031 static const char application_zstd[] = "application/zstd";
0032 
0033 /* clang-format off */
0034 namespace MimeType
0035 {
0036 QString application_gzip()     { return QStringLiteral("application/gzip"); }
0037 QString application_gzip_old() { return QStringLiteral("application/x-gzip"); }
0038 }
0039 /* clang-format on */
0040 
0041 class Q_DECL_HIDDEN KTar::KTarPrivate
0042 {
0043 public:
0044     KTarPrivate(KTar *parent)
0045         : q(parent)
0046         , tarEnd(0)
0047         , tmpFile(nullptr)
0048         , compressionDevice(nullptr)
0049     {
0050     }
0051 
0052     KTar *q;
0053     QStringList dirList;
0054     qint64 tarEnd;
0055     QTemporaryFile *tmpFile;
0056     QString mimetype;
0057     QByteArray origFileName;
0058     KCompressionDevice *compressionDevice;
0059 
0060     bool fillTempFile(const QString &fileName);
0061     bool writeBackTempFile(const QString &fileName);
0062     void fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname);
0063     void writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname);
0064     qint64 readRawHeader(char *buffer);
0065     bool readLonglink(char *buffer, QByteArray &longlink);
0066     qint64 readHeader(char *buffer, QString &name, QString &symlink);
0067 };
0068 
0069 KTar::KTar(const QString &fileName, const QString &_mimetype)
0070     : KArchive(fileName)
0071     , d(new KTarPrivate(this))
0072 {
0073     // shared-mime-info < 1.1 does not know about application/gzip.
0074     // While Qt has optionally a copy of shared-mime-info (1.10 for 5.15.0),
0075     // it uses the system one if it exists.
0076     // Once shared-mime-info 1.1 is required or can be assumed on all targeted
0077     // platforms (right now RHEL/CentOS 6 as target of appimage-based apps
0078     // bundling also karchive does not meet this requirement)
0079     // switch to use the new application/gzip id instead when interacting with QMimeDatabase
0080     // For now: map new name to legacy name and use that
0081     d->mimetype = (_mimetype == MimeType::application_gzip()) ? MimeType::application_gzip_old() : _mimetype;
0082 }
0083 
0084 KTar::KTar(QIODevice *dev)
0085     : KArchive(dev)
0086     , d(new KTarPrivate(this))
0087 {
0088 }
0089 
0090 // Only called when a filename was given
0091 bool KTar::createDevice(QIODevice::OpenMode mode)
0092 {
0093     if (d->mimetype.isEmpty()) {
0094         // Find out mimetype manually
0095 
0096         QMimeDatabase db;
0097         QMimeType mime;
0098         if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
0099             // Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
0100             // we can still do the right thing here.
0101             QFile f(fileName());
0102             if (f.open(QIODevice::ReadOnly)) {
0103                 mime = db.mimeTypeForData(&f);
0104             }
0105             if (!mime.isValid()) {
0106                 // Unable to determine mimetype from contents, get it from file name
0107                 mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
0108             }
0109         } else {
0110             mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
0111         }
0112 
0113         // qCDebug(KArchiveLog) << mode << mime->name();
0114 
0115         if (mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(MimeType::application_gzip_old())) {
0116             // gzipped tar file (with possibly invalid file name), ask for gzip filter
0117             d->mimetype = MimeType::application_gzip_old();
0118         } else if (mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QStringLiteral("application/x-bzip2-compressed-tar"))
0119                    || mime.inherits(QStringLiteral("application/x-bzip2")) || mime.inherits(QString::fromLatin1(application_bzip))) {
0120             // bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
0121             d->mimetype = QString::fromLatin1(application_bzip);
0122         } else if (mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QString::fromLatin1(application_lzma))) {
0123             // lzma compressed tar file (with possibly invalid file name), ask for xz filter
0124             d->mimetype = QString::fromLatin1(application_lzma);
0125         } else if (mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QString::fromLatin1(application_xz))) {
0126             // xz compressed tar file (with possibly invalid name), ask for xz filter
0127             d->mimetype = QString::fromLatin1(application_xz);
0128         } else if (mime.inherits(QStringLiteral("application/x-zstd-compressed-tar")) || mime.inherits(QString::fromLatin1(application_zstd))) {
0129             // zstd compressed tar file (with possibly invalid name), ask for zstd filter
0130             d->mimetype = QString::fromLatin1(application_zstd);
0131         }
0132     }
0133 
0134     if (d->mimetype == QLatin1String("application/x-tar")) {
0135         return KArchive::createDevice(mode);
0136     } else if (mode == QIODevice::WriteOnly) {
0137         if (!KArchive::createDevice(mode)) {
0138             return false;
0139         }
0140         if (!d->mimetype.isEmpty()) {
0141             // Create a compression filter on top of the QSaveFile device that KArchive created.
0142             // qCDebug(KArchiveLog) << "creating KCompressionDevice for" << d->mimetype;
0143             KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(d->mimetype);
0144             d->compressionDevice = new KCompressionDevice(device(), false, type);
0145             setDevice(d->compressionDevice);
0146         }
0147         return true;
0148     } else {
0149         // The compression filters are very slow with random access.
0150         // So instead of applying the filter to the device,
0151         // the file is completely extracted instead,
0152         // and we work on the extracted tar file.
0153         // This improves the extraction speed by the archive KIO worker supporting the tar protocol dramatically,
0154         // if the archive file contains many files.
0155         // This is because the archive KIO worker extracts one file after the other and normally
0156         // has to walk through the decompression filter each time.
0157         // Which is in fact nearly as slow as a complete decompression for each file.
0158 
0159         Q_ASSERT(!d->tmpFile);
0160         d->tmpFile = new QTemporaryFile();
0161         d->tmpFile->setFileTemplate(QDir::tempPath() + QLatin1Char('/') + QLatin1String("ktar-XXXXXX.tar"));
0162         d->tmpFile->open();
0163         // qCDebug(KArchiveLog) << "creating tempfile:" << d->tmpFile->fileName();
0164 
0165         setDevice(d->tmpFile);
0166         return true;
0167     }
0168 }
0169 
0170 KTar::~KTar()
0171 {
0172     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
0173     if (isOpen()) {
0174         close();
0175     }
0176 
0177     delete d->tmpFile;
0178     delete d->compressionDevice;
0179     delete d;
0180 }
0181 
0182 void KTar::setOrigFileName(const QByteArray &fileName)
0183 {
0184     if (!isOpen() || !(mode() & QIODevice::WriteOnly)) {
0185         // qCWarning(KArchiveLog) << "KTar::setOrigFileName: File must be opened for writing first.\n";
0186         return;
0187     }
0188     d->origFileName = fileName;
0189 }
0190 
0191 qint64 KTar::KTarPrivate::readRawHeader(char *buffer)
0192 {
0193     // Read header
0194     qint64 n = q->device()->read(buffer, 0x200);
0195     // we need to test if there is a prefix value because the file name can be null
0196     // and the prefix can have a value and in this case we don't reset n.
0197     if (n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0)) {
0198         // Make sure this is actually a tar header
0199         if (strncmp(buffer + 257, "ustar", 5)) {
0200             // The magic isn't there (broken/old tars), but maybe a correct checksum?
0201 
0202             int check = 0;
0203             for (uint j = 0; j < 0x200; ++j) {
0204                 check += static_cast<unsigned char>(buffer[j]);
0205             }
0206 
0207             // adjust checksum to count the checksum fields as blanks
0208             for (uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++) {
0209                 check -= static_cast<unsigned char>(buffer[148 + j]);
0210             }
0211             check += 8 * ' ';
0212 
0213             QByteArray s = QByteArray::number(check, 8); // octal
0214 
0215             // only compare those of the 6 checksum digits that mean something,
0216             // because the other digits are filled with all sorts of different chars by different tars ...
0217             // Some tars right-justify the checksum so it could start in one of three places - we have to check each.
0218             if (strncmp(buffer + 148 + 6 - s.length(), s.data(), s.length()) //
0219                 && strncmp(buffer + 148 + 7 - s.length(), s.data(), s.length()) //
0220                 && strncmp(buffer + 148 + 8 - s.length(), s.data(), s.length())) {
0221                 /*qCWarning(KArchiveLog) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
0222                                << "instead of ustar. Reading from wrong pos in file?"
0223                                << "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );*/
0224                 return -1;
0225             }
0226         } /*end if*/
0227     } else {
0228         // reset to 0 if 0x200 because logical end of archive has been reached
0229         if (n == 0x200) {
0230             n = 0;
0231         }
0232     } /*end if*/
0233     return n;
0234 }
0235 
0236 bool KTar::KTarPrivate::readLonglink(char *buffer, QByteArray &longlink)
0237 {
0238     qint64 n = 0;
0239     // qCDebug(KArchiveLog) << "reading longlink from pos " << q->device()->pos();
0240     QIODevice *dev = q->device();
0241     // read size of longlink from size field in header
0242     // size is in bytes including the trailing null (which we ignore)
0243     qint64 size = QByteArray(buffer + 0x7c, 12).trimmed().toLongLong(nullptr, 8 /*octal*/);
0244 
0245     size--; // ignore trailing null
0246     if (size > std::numeric_limits<int>::max() - 32) { // QByteArray can't really be INT_MAX big, it's max size is something between INT_MAX - 32 and INT_MAX
0247                                                        // depending the platform so just be safe
0248         qCWarning(KArchiveLog) << "Failed to allocate memory for longlink of size" << size;
0249         return false;
0250     }
0251     if (size < 0) {
0252         qCWarning(KArchiveLog) << "Invalid longlink size" << size;
0253         return false;
0254     }
0255     longlink.resize(size);
0256     qint64 offset = 0;
0257     while (size > 0) {
0258         int chunksize = qMin(size, 0x200LL);
0259         n = dev->read(longlink.data() + offset, chunksize);
0260         if (n == -1) {
0261             return false;
0262         }
0263         size -= chunksize;
0264         offset += 0x200;
0265     } /*wend*/
0266     // jump over the rest
0267     const int skip = 0x200 - (n % 0x200);
0268     if (skip <= 0x200) {
0269         if (dev->read(buffer, skip) != skip) {
0270             return false;
0271         }
0272     }
0273     longlink.truncate(qstrlen(longlink.constData()));
0274     return true;
0275 }
0276 
0277 qint64 KTar::KTarPrivate::readHeader(char *buffer, QString &name, QString &symlink)
0278 {
0279     name.truncate(0);
0280     symlink.truncate(0);
0281     while (true) {
0282         qint64 n = readRawHeader(buffer);
0283         if (n != 0x200) {
0284             return n;
0285         }
0286 
0287         // is it a longlink?
0288         if (strcmp(buffer, "././@LongLink") == 0) {
0289             char typeflag = buffer[0x9c];
0290             QByteArray longlink;
0291             if (readLonglink(buffer, longlink)) {
0292                 switch (typeflag) {
0293                 case 'L':
0294                     name = QFile::decodeName(longlink.constData());
0295                     break;
0296                 case 'K':
0297                     symlink = QFile::decodeName(longlink.constData());
0298                     break;
0299                 } /*end switch*/
0300             }
0301         } else {
0302             break;
0303         } /*end if*/
0304     } /*wend*/
0305 
0306     // if not result of longlink, read names directly from the header
0307     if (name.isEmpty())
0308     // there are names that are exactly 100 bytes long
0309     // and neither longlink nor \0 terminated (bug:101472)
0310     {
0311         name = QFile::decodeName(QByteArray(buffer, qstrnlen(buffer, 100)));
0312     }
0313     if (symlink.isEmpty()) {
0314         char *symlinkBuffer = buffer + 0x9d /*?*/;
0315         symlink = QFile::decodeName(QByteArray(symlinkBuffer, qstrnlen(symlinkBuffer, 100)));
0316     }
0317 
0318     return 0x200;
0319 }
0320 
0321 /*
0322  * If we have created a temporary file, we have
0323  * to decompress the original file now and write
0324  * the contents to the temporary file.
0325  */
0326 bool KTar::KTarPrivate::fillTempFile(const QString &fileName)
0327 {
0328     if (!tmpFile) {
0329         return true;
0330     }
0331 
0332     // qCDebug(KArchiveLog) << "filling tmpFile of mimetype" << mimetype;
0333 
0334     KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimetype);
0335     KCompressionDevice filterDev(fileName, compressionType);
0336 
0337     QFile *file = tmpFile;
0338     Q_ASSERT(file->isOpen());
0339     Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
0340     file->seek(0);
0341     QByteArray buffer;
0342     buffer.resize(8 * 1024);
0343     if (!filterDev.open(QIODevice::ReadOnly)) {
0344         q->setErrorString(tr("File %1 does not exist").arg(fileName));
0345         return false;
0346     }
0347     qint64 len = -1;
0348     while (!filterDev.atEnd() && len != 0) {
0349         len = filterDev.read(buffer.data(), buffer.size());
0350         if (len < 0) { // corrupted archive
0351             q->setErrorString(tr("Archive %1 is corrupt").arg(fileName));
0352             return false;
0353         }
0354         if (file->write(buffer.data(), len) != len) { // disk full
0355             q->setErrorString(tr("Disk full"));
0356             return false;
0357         }
0358     }
0359     filterDev.close();
0360 
0361     file->flush();
0362     file->seek(0);
0363     Q_ASSERT(file->isOpen());
0364     Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
0365 
0366     // qCDebug(KArchiveLog) << "filling tmpFile finished.";
0367     return true;
0368 }
0369 
0370 bool KTar::openArchive(QIODevice::OpenMode mode)
0371 {
0372     if (!(mode & QIODevice::ReadOnly)) {
0373         return true;
0374     }
0375 
0376     if (!d->fillTempFile(fileName())) {
0377         return false;
0378     }
0379 
0380     // We'll use the permission and user/group of d->rootDir
0381     // for any directory we emulate (see findOrCreate)
0382     // struct stat buf;
0383     // stat( fileName(), &buf );
0384 
0385     d->dirList.clear();
0386     QIODevice *dev = device();
0387 
0388     if (!dev) {
0389         setErrorString(tr("Could not get underlying device"));
0390         qCWarning(KArchiveLog) << "Could not get underlying device";
0391         return false;
0392     }
0393 
0394     // read dir information
0395     char buffer[0x200];
0396     bool ende = false;
0397     do {
0398         QString name;
0399         QString symlink;
0400 
0401         // Read header
0402         qint64 n = d->readHeader(buffer, name, symlink);
0403         if (n < 0) {
0404             setErrorString(tr("Could not read tar header"));
0405             return false;
0406         }
0407         if (n == 0x200) {
0408             bool isdir = false;
0409 
0410             if (name.isEmpty()) {
0411                 continue;
0412             }
0413             if (name.endsWith(QLatin1Char('/'))) {
0414                 isdir = true;
0415                 name.truncate(name.length() - 1);
0416             }
0417 
0418             QByteArray prefix = QByteArray(buffer + 0x159, 155);
0419             if (prefix[0] != '\0') {
0420                 name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name);
0421             }
0422 
0423             int pos = name.lastIndexOf(QLatin1Char('/'));
0424             QString nm = (pos == -1) ? name : name.mid(pos + 1);
0425 
0426             // read access
0427             buffer[0x6b] = 0;
0428             char *dummy;
0429             const char *p = buffer + 0x64;
0430             while (*p == ' ') {
0431                 ++p;
0432             }
0433             int access = strtol(p, &dummy, 8);
0434 
0435             // read user and group
0436             const int maxUserGroupLength = 32;
0437             const char *userStart = buffer + 0x109;
0438             const int userLen = qstrnlen(userStart, maxUserGroupLength);
0439             const QString user = QString::fromLocal8Bit(userStart, userLen);
0440             const char *groupStart = buffer + 0x129;
0441             const int groupLen = qstrnlen(groupStart, maxUserGroupLength);
0442             const QString group = QString::fromLocal8Bit(groupStart, groupLen);
0443 
0444             // read time
0445             buffer[0x93] = 0;
0446             p = buffer + 0x88;
0447             while (*p == ' ') {
0448                 ++p;
0449             }
0450             uint time = strtol(p, &dummy, 8);
0451 
0452             // read type flag
0453             char typeflag = buffer[0x9c];
0454             // '0' for files, '1' hard link, '2' symlink, '5' for directory
0455             // (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
0456             // 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
0457             // to the next file in the archive and 'g' for Global extended header
0458 
0459             if (typeflag == '5') {
0460                 isdir = true;
0461             }
0462 
0463             bool isDumpDir = false;
0464             if (typeflag == 'D') {
0465                 isdir = false;
0466                 isDumpDir = true;
0467             }
0468             // qCDebug(KArchiveLog) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag
0469             // == '2' );
0470 
0471             if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header
0472                 // Skip it for now. TODO: implement reading of extended header, as per https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
0473                 (void)dev->read(buffer, 0x200);
0474                 continue;
0475             }
0476 
0477             if (isdir) {
0478                 access |= S_IFDIR; // broken tar files...
0479             }
0480 
0481             KArchiveEntry *e;
0482             if (isdir) {
0483                 // qCDebug(KArchiveLog) << "directory" << nm;
0484                 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
0485             } else {
0486                 // read size
0487                 QByteArray sizeBuffer(buffer + 0x7c, 12);
0488                 qint64 size = sizeBuffer.trimmed().toLongLong(nullptr, 8 /*octal*/);
0489                 // qCDebug(KArchiveLog) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
0490 
0491                 // for isDumpDir we will skip the additional info about that dirs contents
0492                 if (isDumpDir) {
0493                     // qCDebug(KArchiveLog) << nm << "isDumpDir";
0494                     e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
0495                 } else {
0496                     // Let's hack around hard links. Our classes don't support that, so make them symlinks
0497                     if (typeflag == '1') {
0498                         // qCDebug(KArchiveLog) << "Hard link, setting size to 0 instead of" << size;
0499                         size = 0; // no contents
0500                     }
0501 
0502                     // qCDebug(KArchiveLog) << "file" << nm << "size=" << size;
0503                     e = new KArchiveFile(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink, dev->pos(), size);
0504                 }
0505 
0506                 // Skip contents + align bytes
0507                 qint64 rest = size % 0x200;
0508                 qint64 skip = size + (rest ? 0x200 - rest : 0);
0509                 // qCDebug(KArchiveLog) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
0510                 if (!dev->seek(dev->pos() + skip)) {
0511                     // qCWarning(KArchiveLog) << "skipping" << skip << "failed";
0512                 }
0513             }
0514 
0515             if (pos == -1) {
0516                 if (nm == QLatin1String(".")) { // special case
0517                     if (isdir) {
0518                         if (KArchivePrivate::hasRootDir(this)) {
0519                             qWarning() << "Broken tar file has two root dir entries";
0520                             delete e;
0521                         } else {
0522                             setRootDir(static_cast<KArchiveDirectory *>(e));
0523                         }
0524                     } else {
0525                         delete e;
0526                     }
0527                 } else {
0528                     rootDir()->addEntry(e);
0529                 }
0530             } else {
0531                 // In some tar files we can find dir/./file => call cleanPath
0532                 QString path = QDir::cleanPath(name.left(pos));
0533                 // Ensure container directory exists, create otherwise
0534                 KArchiveDirectory *d = findOrCreate(path);
0535                 if (d) {
0536                     d->addEntry(e);
0537                 } else {
0538                     delete e;
0539                     return false;
0540                 }
0541             }
0542         } else {
0543             // qCDebug(KArchiveLog) << "Terminating. Read " << n << " bytes, first one is " << buffer[0];
0544             d->tarEnd = dev->pos() - n; // Remember end of archive
0545             ende = true;
0546         }
0547     } while (!ende);
0548     return true;
0549 }
0550 
0551 /*
0552  * Writes back the changes of the temporary file
0553  * to the original file.
0554  * Must only be called if in write mode, not in read mode
0555  */
0556 bool KTar::KTarPrivate::writeBackTempFile(const QString &fileName)
0557 {
0558     if (!tmpFile) {
0559         return true;
0560     }
0561 
0562     // qCDebug(KArchiveLog) << "Write temporary file to compressed file" << fileName << mimetype;
0563 
0564     bool forced = false;
0565     /* clang-format off */
0566     if (MimeType::application_gzip_old() == mimetype ||
0567         QLatin1String(application_bzip) == mimetype ||
0568         QLatin1String(application_lzma) == mimetype ||
0569         QLatin1String(application_xz) == mimetype) {
0570         /* clang-format on */
0571         forced = true;
0572     }
0573 
0574     // #### TODO this should use QSaveFile to avoid problems on disk full
0575     // (KArchive uses QSaveFile by default, but the temp-uncompressed-file trick
0576     // circumvents that).
0577 
0578     KCompressionDevice dev(fileName);
0579     QFile *file = tmpFile;
0580     if (!dev.open(QIODevice::WriteOnly)) {
0581         file->close();
0582         q->setErrorString(tr("Failed to write back temp file: %1").arg(dev.errorString()));
0583         return false;
0584     }
0585     if (forced) {
0586         dev.setOrigFileName(origFileName);
0587     }
0588     file->seek(0);
0589     QByteArray buffer;
0590     buffer.resize(8 * 1024);
0591     qint64 len;
0592     while (!file->atEnd()) {
0593         len = file->read(buffer.data(), buffer.size());
0594         dev.write(buffer.data(), len); // TODO error checking
0595     }
0596     file->close();
0597     dev.close();
0598 
0599     // qCDebug(KArchiveLog) << "Write temporary file to compressed file done.";
0600     return true;
0601 }
0602 
0603 bool KTar::closeArchive()
0604 {
0605     d->dirList.clear();
0606 
0607     bool ok = true;
0608 
0609     // If we are in readwrite mode and had created
0610     // a temporary tar file, we have to write
0611     // back the changes to the original file
0612     if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
0613         ok = d->writeBackTempFile(fileName());
0614         delete d->tmpFile;
0615         d->tmpFile = nullptr;
0616         setDevice(nullptr);
0617     }
0618 
0619     return ok;
0620 }
0621 
0622 bool KTar::doFinishWriting(qint64 size)
0623 {
0624     // Write alignment
0625     int rest = size % 0x200;
0626     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
0627         d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
0628     }
0629     if (rest) {
0630         char buffer[0x201];
0631         for (uint i = 0; i < 0x200; ++i) {
0632             buffer[i] = 0;
0633         }
0634         qint64 nwritten = device()->write(buffer, 0x200 - rest);
0635         const bool ok = nwritten == 0x200 - rest;
0636 
0637         if (!ok) {
0638             setErrorString(tr("Couldn't write alignment: %1").arg(device()->errorString()));
0639         }
0640 
0641         return ok;
0642     }
0643     return true;
0644 }
0645 
0646 /*** Some help from the tar sources
0647 struct posix_header
0648 {                               byte offset
0649   char name[100];               *   0 *     0x0
0650   char mode[8];                 * 100 *     0x64
0651   char uid[8];                  * 108 *     0x6c
0652   char gid[8];                  * 116 *     0x74
0653   char size[12];                * 124 *     0x7c
0654   char mtime[12];               * 136 *     0x88
0655   char chksum[8];               * 148 *     0x94
0656   char typeflag;                * 156 *     0x9c
0657   char linkname[100];           * 157 *     0x9d
0658   char magic[6];                * 257 *     0x101
0659   char version[2];              * 263 *     0x107
0660   char uname[32];               * 265 *     0x109
0661   char gname[32];               * 297 *     0x129
0662   char devmajor[8];             * 329 *     0x149
0663   char devminor[8];             * 337 *     ...
0664   char prefix[155];             * 345 *
0665                                 * 500 *
0666 };
0667 */
0668 
0669 void KTar::KTarPrivate::fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname)
0670 {
0671     // mode (as in stpos())
0672     assert(strlen(mode) == 6);
0673     memcpy(buffer + 0x64, mode, 6);
0674     buffer[0x6a] = ' ';
0675     buffer[0x6b] = '\0';
0676 
0677     // dummy uid
0678     strcpy(buffer + 0x6c, "   765 "); // 501 in decimal
0679     // dummy gid
0680     strcpy(buffer + 0x74, "   144 "); // 100 in decimal
0681 
0682     // size
0683     QByteArray s = QByteArray::number(size, 8); // octal
0684     s = s.rightJustified(11, '0');
0685     memcpy(buffer + 0x7c, s.data(), 11);
0686     buffer[0x87] = ' '; // space-terminate (no null after)
0687 
0688     // modification time
0689     const QDateTime modificationTime = mtime.isValid() ? mtime : QDateTime::currentDateTime();
0690     s = QByteArray::number(static_cast<qulonglong>(modificationTime.toMSecsSinceEpoch() / 1000), 8); // octal
0691     s = s.rightJustified(11, '0');
0692     memcpy(buffer + 0x88, s.data(), 11);
0693     buffer[0x93] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
0694 
0695     // spaces, replaced by the check sum later
0696     buffer[0x94] = 0x20;
0697     buffer[0x95] = 0x20;
0698     buffer[0x96] = 0x20;
0699     buffer[0x97] = 0x20;
0700     buffer[0x98] = 0x20;
0701     buffer[0x99] = 0x20;
0702 
0703     /* From the tar sources :
0704        Fill in the checksum field.  It's formatted differently from the
0705        other fields: it has [6] digits, a null, then a space -- rather than
0706        digits, a space, then a null. */
0707 
0708     buffer[0x9a] = '\0';
0709     buffer[0x9b] = ' ';
0710 
0711     // type flag (dir, file, link)
0712     buffer[0x9c] = typeflag;
0713 
0714     // magic + version
0715     strcpy(buffer + 0x101, "ustar");
0716     strcpy(buffer + 0x107, "00");
0717 
0718     // user
0719     strcpy(buffer + 0x109, uname);
0720     // group
0721     strcpy(buffer + 0x129, gname);
0722 
0723     // Header check sum
0724     int check = 32;
0725     for (uint j = 0; j < 0x200; ++j) {
0726         check += static_cast<unsigned char>(buffer[j]);
0727     }
0728     s = QByteArray::number(check, 8); // octal
0729     s = s.rightJustified(6, '0');
0730     memcpy(buffer + 0x94, s.constData(), 6);
0731 }
0732 
0733 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname)
0734 {
0735     strcpy(buffer, "././@LongLink");
0736     qint64 namelen = name.length() + 1;
0737     fillBuffer(buffer, "     0", namelen, QDateTime(), typeflag, uname, gname);
0738     q->device()->write(buffer, 0x200); // TODO error checking
0739     qint64 offset = 0;
0740     while (namelen > 0) {
0741         int chunksize = qMin(namelen, 0x200LL);
0742         memcpy(buffer, name.data() + offset, chunksize);
0743         // write long name
0744         q->device()->write(buffer, 0x200); // TODO error checking
0745         // not even needed to reclear the buffer, tar doesn't do it
0746         namelen -= chunksize;
0747         offset += 0x200;
0748     } /*wend*/
0749 }
0750 
0751 bool KTar::doPrepareWriting(const QString &name,
0752                             const QString &user,
0753                             const QString &group,
0754                             qint64 size,
0755                             mode_t perm,
0756                             const QDateTime & /*atime*/,
0757                             const QDateTime &mtime,
0758                             const QDateTime & /*ctime*/)
0759 {
0760     if (!isOpen()) {
0761         setErrorString(tr("Application error: TAR file must be open before being written into"));
0762         qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
0763         return false;
0764     }
0765 
0766     if (!(mode() & QIODevice::WriteOnly)) {
0767         setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file"));
0768         qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
0769         return false;
0770     }
0771 
0772     const qint64 MAX_FILESIZE = 077777777777L; // the format we use only allows 11 octal digits for size
0773     if (size > MAX_FILESIZE) {
0774         setErrorString(tr("Application limitation: Can not add file larger than %1 bytes").arg(MAX_FILESIZE));
0775         return false;
0776     }
0777 
0778     // In some tar files we can find dir/./file => call cleanPath
0779     QString fileName(QDir::cleanPath(name));
0780 
0781     /*
0782       // Create toplevel dirs
0783       // Commented out by David since it's not necessary, and if anybody thinks it is,
0784       // he needs to implement a findOrCreate equivalent in writeDir.
0785       // But as KTar and the "tar" program both handle tar files without
0786       // dir entries, there's really no need for that
0787       QString tmp ( fileName );
0788       int i = tmp.lastIndexOf( '/' );
0789       if ( i != -1 )
0790       {
0791       QString d = tmp.left( i + 1 ); // contains trailing slash
0792       if ( !m_dirList.contains( d ) )
0793       {
0794       tmp = tmp.mid( i + 1 );
0795       writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
0796       }
0797       }
0798     */
0799 
0800     char buffer[0x201] = {0};
0801 
0802     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
0803         device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
0804     }
0805 
0806     // provide converted stuff we need later on
0807     const QByteArray encodedFileName = QFile::encodeName(fileName);
0808     const QByteArray uname = user.toLocal8Bit();
0809     const QByteArray gname = group.toLocal8Bit();
0810 
0811     // If more than 100 bytes, we need to use the LongLink trick
0812     if (encodedFileName.length() > 99) {
0813         d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
0814     }
0815 
0816     // Write (potentially truncated) name
0817     strncpy(buffer, encodedFileName.constData(), 99);
0818     buffer[99] = 0;
0819     // zero out the rest (except for what gets filled anyways)
0820     memset(buffer + 0x9d, 0, 0x200 - 0x9d);
0821 
0822     QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
0823     permstr = permstr.rightJustified(6, '0');
0824     d->fillBuffer(buffer, permstr.constData(), size, mtime, 0x30, uname.constData(), gname.constData());
0825 
0826     // Write header
0827     if (device()->write(buffer, 0x200) != 0x200) {
0828         setErrorString(tr("Failed to write header: %1").arg(device()->errorString()));
0829         return false;
0830     } else {
0831         return true;
0832     }
0833 }
0834 
0835 bool KTar::doWriteDir(const QString &name,
0836                       const QString &user,
0837                       const QString &group,
0838                       mode_t perm,
0839                       const QDateTime & /*atime*/,
0840                       const QDateTime &mtime,
0841                       const QDateTime & /*ctime*/)
0842 {
0843     if (!isOpen()) {
0844         setErrorString(tr("Application error: TAR file must be open before being written into"));
0845         qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()";
0846         return false;
0847     }
0848 
0849     if (!(mode() & QIODevice::WriteOnly)) {
0850         setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
0851         qCWarning(KArchiveLog) << "doWriteDir failed: !(mode() & QIODevice::WriteOnly)";
0852         return false;
0853     }
0854 
0855     // In some tar files we can find dir/./ => call cleanPath
0856     QString dirName(QDir::cleanPath(name));
0857 
0858     // Need trailing '/'
0859     if (!dirName.endsWith(QLatin1Char('/'))) {
0860         dirName += QLatin1Char('/');
0861     }
0862 
0863     if (d->dirList.contains(dirName)) {
0864         return true; // already there
0865     }
0866 
0867     char buffer[0x201] = {0};
0868 
0869     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
0870         device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
0871     }
0872 
0873     // provide converted stuff we need lateron
0874     QByteArray encodedDirname = QFile::encodeName(dirName);
0875     QByteArray uname = user.toLocal8Bit();
0876     QByteArray gname = group.toLocal8Bit();
0877 
0878     // If more than 100 bytes, we need to use the LongLink trick
0879     if (encodedDirname.length() > 99) {
0880         d->writeLonglink(buffer, encodedDirname, 'L', uname.constData(), gname.constData());
0881     }
0882 
0883     // Write (potentially truncated) name
0884     strncpy(buffer, encodedDirname.constData(), 99);
0885     buffer[99] = 0;
0886     // zero out the rest (except for what gets filled anyways)
0887     memset(buffer + 0x9d, 0, 0x200 - 0x9d);
0888 
0889     QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
0890     permstr = permstr.rightJustified(6, ' ');
0891     d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x35, uname.constData(), gname.constData());
0892 
0893     // Write header
0894     device()->write(buffer, 0x200);
0895     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
0896         d->tarEnd = device()->pos();
0897     }
0898 
0899     d->dirList.append(dirName); // contains trailing slash
0900     return true; // TODO if wanted, better error control
0901 }
0902 
0903 bool KTar::doWriteSymLink(const QString &name,
0904                           const QString &target,
0905                           const QString &user,
0906                           const QString &group,
0907                           mode_t perm,
0908                           const QDateTime & /*atime*/,
0909                           const QDateTime &mtime,
0910                           const QDateTime & /*ctime*/)
0911 {
0912     if (!isOpen()) {
0913         setErrorString(tr("Application error: TAR file must be open before being written into"));
0914         qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()";
0915         return false;
0916     }
0917 
0918     if (!(mode() & QIODevice::WriteOnly)) {
0919         setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
0920         qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)";
0921         return false;
0922     }
0923 
0924     // In some tar files we can find dir/./file => call cleanPath
0925     QString fileName(QDir::cleanPath(name));
0926 
0927     char buffer[0x201] = {0};
0928 
0929     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
0930         device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
0931     }
0932 
0933     // provide converted stuff we need lateron
0934     QByteArray encodedFileName = QFile::encodeName(fileName);
0935     QByteArray encodedTarget = QFile::encodeName(target);
0936     QByteArray uname = user.toLocal8Bit();
0937     QByteArray gname = group.toLocal8Bit();
0938 
0939     // If more than 100 bytes, we need to use the LongLink trick
0940     if (encodedTarget.length() > 99) {
0941         d->writeLonglink(buffer, encodedTarget, 'K', uname.constData(), gname.constData());
0942     }
0943     if (encodedFileName.length() > 99) {
0944         d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
0945     }
0946 
0947     // Write (potentially truncated) name
0948     strncpy(buffer, encodedFileName.constData(), 99);
0949     buffer[99] = 0;
0950     // Write (potentially truncated) symlink target
0951     strncpy(buffer + 0x9d, encodedTarget.constData(), 99);
0952     buffer[0x9d + 99] = 0;
0953     // zero out the rest
0954     memset(buffer + 0x9d + 100, 0, 0x200 - 100 - 0x9d);
0955 
0956     QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
0957     permstr = permstr.rightJustified(6, ' ');
0958     d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x32, uname.constData(), gname.constData());
0959 
0960     // Write header
0961     bool retval = device()->write(buffer, 0x200) == 0x200;
0962     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
0963         d->tarEnd = device()->pos();
0964     }
0965     return retval;
0966 }
0967 
0968 void KTar::virtual_hook(int id, void *data)
0969 {
0970     KArchive::virtual_hook(id, data);
0971 }