1 /* This file is part of the KDE libraries
2    SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
3    SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
4 
5    SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "ktar.h"
9 #include "karchive_p.h"
10 #include "loggingcategory.h"
11 
12 #include <stdlib.h> // strtol
13 #include <assert.h>
14 
15 #include <QDebug>
16 #include <QDir>
17 #include <QFile>
18 #include <QMimeDatabase>
19 #include <QTemporaryFile>
20 
21 #include <kfilterdev.h>
22 #include <kfilterbase.h>
23 
24 ////////////////////////////////////////////////////////////////////////
25 /////////////////////////// KTar ///////////////////////////////////
26 ////////////////////////////////////////////////////////////////////////
27 
28 // Mime types of known filters
29 static const char application_gzip[] = "application/x-gzip";
30 static const char application_bzip[] = "application/x-bzip";
31 static const char application_lzma[] = "application/x-lzma";
32 static const char application_xz[] = "application/x-xz";
33 
34 class Q_DECL_HIDDEN KTar::KTarPrivate
35 {
36 public:
KTarPrivate(KTar * parent)37     KTarPrivate(KTar *parent)
38         : q(parent)
39         , tarEnd(0)
40         , tmpFile(nullptr)
41         , compressionDevice(nullptr)
42     {
43     }
44 
45     KTar *q;
46     QStringList dirList;
47     qint64 tarEnd;
48     QTemporaryFile *tmpFile;
49     QString mimetype;
50     QByteArray origFileName;
51     KCompressionDevice *compressionDevice;
52 
53     bool fillTempFile(const QString &fileName);
54     bool writeBackTempFile(const QString &fileName);
55     void fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime,
56                     char typeflag, const char *uname, const char *gname);
57     void writeLonglink(char *buffer, const QByteArray &name, char typeflag,
58                        const char *uname, const char *gname);
59     qint64 readRawHeader(char *buffer);
60     bool readLonglink(char *buffer, QByteArray &longlink);
61     qint64 readHeader(char *buffer, QString &name, QString &symlink);
62 };
63 
KTar(const QString & fileName,const QString & _mimetype)64 KTar::KTar(const QString &fileName, const QString &_mimetype)
65     : KArchive(fileName)
66     , d(new KTarPrivate(this))
67 {
68     d->mimetype = _mimetype;
69 }
70 
KTar(QIODevice * dev)71 KTar::KTar(QIODevice *dev)
72     : KArchive(dev)
73     , d(new KTarPrivate(this))
74 {
75 }
76 
77 // Only called when a filename was given
createDevice(QIODevice::OpenMode mode)78 bool KTar::createDevice(QIODevice::OpenMode mode)
79 {
80     if (d->mimetype.isEmpty()) {
81         // Find out mimetype manually
82 
83         QMimeDatabase db;
84         QMimeType mime;
85         if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
86             // Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
87             // we can still do the right thing here.
88             QFile f(fileName());
89             if (f.open(QIODevice::ReadOnly)) {
90                 mime = db.mimeTypeForData(&f);
91             }
92             if (!mime.isValid()) {
93                 // Unable to determine mimetype from contents, get it from file name
94                 mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
95             }
96         } else {
97             mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
98         }
99 
100         //qCDebug(KArchiveLog) << mode << mime->name();
101 
102         if (mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(QString::fromLatin1(application_gzip))) {
103             // gzipped tar file (with possibly invalid file name), ask for gzip filter
104             d->mimetype = QString::fromLatin1(application_gzip);
105         } else if (mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QString::fromLatin1(application_bzip))) {
106             // bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
107             d->mimetype = QString::fromLatin1(application_bzip);
108         } else if (mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QString::fromLatin1(application_lzma))) {
109             // lzma compressed tar file (with possibly invalid file name), ask for xz filter
110             d->mimetype = QString::fromLatin1(application_lzma);
111         } else if (mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QString::fromLatin1(application_xz))) {
112             // xz compressed tar file (with possibly invalid name), ask for xz filter
113             d->mimetype = QString::fromLatin1(application_xz);
114         }
115     }
116 
117     if (d->mimetype == QLatin1String("application/x-tar")) {
118         return KArchive::createDevice(mode);
119     } else if (mode == QIODevice::WriteOnly) {
120         if (!KArchive::createDevice(mode)) {
121             return false;
122         }
123         if (!d->mimetype.isEmpty()) {
124             // Create a compression filter on top of the QSaveFile device that KArchive created.
125             //qCDebug(KArchiveLog) << "creating KFilterDev for" << d->mimetype;
126             KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(d->mimetype);
127             d->compressionDevice = new KCompressionDevice(device(), false, type);
128             setDevice(d->compressionDevice);
129         }
130         return true;
131     } else {
132         // The compression filters are very slow with random access.
133         // So instead of applying the filter to the device,
134         // the file is completely extracted instead,
135         // and we work on the extracted tar file.
136         // This improves the extraction speed by the tar ioslave dramatically,
137         // if the archive file contains many files.
138         // This is because the tar ioslave extracts one file after the other and normally
139         // has to walk through the decompression filter each time.
140         // Which is in fact nearly as slow as a complete decompression for each file.
141 
142         Q_ASSERT(!d->tmpFile);
143         d->tmpFile = new QTemporaryFile();
144         d->tmpFile->setFileTemplate(QDir::tempPath() + QStringLiteral("/") + QLatin1String("ktar-XXXXXX.tar"));
145         d->tmpFile->open();
146         //qCDebug(KArchiveLog) << "creating tempfile:" << d->tmpFile->fileName();
147 
148         setDevice(d->tmpFile);
149         return true;
150     }
151 }
152 
~KTar()153 KTar::~KTar()
154 {
155     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
156     if (isOpen()) {
157         close();
158     }
159 
160     delete d->tmpFile;
161     delete d->compressionDevice;
162     delete d;
163 }
164 
setOrigFileName(const QByteArray & fileName)165 void KTar::setOrigFileName(const QByteArray &fileName)
166 {
167     if (!isOpen() || !(mode() & QIODevice::WriteOnly)) {
168         //qCWarning(KArchiveLog) << "KTar::setOrigFileName: File must be opened for writing first.\n";
169         return;
170     }
171     d->origFileName = fileName;
172 }
173 
readRawHeader(char * buffer)174 qint64 KTar::KTarPrivate::readRawHeader(char *buffer)
175 {
176     // Read header
177     qint64 n = q->device()->read(buffer, 0x200);
178     // we need to test if there is a prefix value because the file name can be null
179     // and the prefix can have a value and in this case we don't reset n.
180     if (n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0)) {
181         // Make sure this is actually a tar header
182         if (strncmp(buffer + 257, "ustar", 5)) {
183             // The magic isn't there (broken/old tars), but maybe a correct checksum?
184 
185             int check = 0;
186             for (uint j = 0; j < 0x200; ++j) {
187                 check += static_cast<unsigned char>(buffer[j]);
188             }
189 
190             // adjust checksum to count the checksum fields as blanks
191             for (uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++) {
192                 check -= static_cast<unsigned char>(buffer[148 + j]);
193             }
194             check += 8 * ' ';
195 
196             QByteArray s = QByteArray::number(check, 8);   // octal
197 
198             // only compare those of the 6 checksum digits that mean something,
199             // because the other digits are filled with all sorts of different chars by different tars ...
200             // Some tars right-justify the checksum so it could start in one of three places - we have to check each.
201             if (strncmp(buffer + 148 + 6 - s.length(), s.data(), s.length())
202                 && strncmp(buffer + 148 + 7 - s.length(), s.data(), s.length())
203                 && strncmp(buffer + 148 + 8 - s.length(), s.data(), s.length())) {
204                 /*qCWarning(KArchiveLog) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
205                                << "instead of ustar. Reading from wrong pos in file?"
206                                << "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );*/
207                 return -1;
208             }
209         }/*end if*/
210     } else {
211         // reset to 0 if 0x200 because logical end of archive has been reached
212         if (n == 0x200) {
213             n = 0;
214         }
215     }/*end if*/
216     return n;
217 }
218 
readLonglink(char * buffer,QByteArray & longlink)219 bool KTar::KTarPrivate::readLonglink(char *buffer, QByteArray &longlink)
220 {
221     qint64 n = 0;
222     //qCDebug(KArchiveLog) << "reading longlink from pos " << q->device()->pos();
223     QIODevice *dev = q->device();
224     // read size of longlink from size field in header
225     // size is in bytes including the trailing null (which we ignore)
226     qint64 size = QByteArray(buffer + 0x7c, 12).trimmed().toLongLong(nullptr, 8 /*octal*/);
227 
228     size--;    // ignore trailing null
229     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 depending the platform so just be safe
230         qCWarning(KArchiveLog) << "Failed to allocate memory for longlink of size" << size;
231         return false;
232     }
233     if (size < 0) {
234         qCWarning(KArchiveLog) << "Invalid longlink size" << size;
235         return false;
236     }
237     longlink.resize(size);
238     qint64 offset = 0;
239     while (size > 0) {
240         int chunksize = qMin(size, 0x200LL);
241         n = dev->read(longlink.data() + offset, chunksize);
242         if (n == -1) {
243             return false;
244         }
245         size -= chunksize;
246         offset += 0x200;
247     }/*wend*/
248     // jump over the rest
249     const int skip = 0x200 - (n % 0x200);
250     if (skip <= 0x200) {
251         if (dev->read(buffer, skip) != skip) {
252             return false;
253         }
254     }
255     longlink.truncate(qstrlen(longlink.constData()));
256     return true;
257 }
258 
readHeader(char * buffer,QString & name,QString & symlink)259 qint64 KTar::KTarPrivate::readHeader(char *buffer, QString &name, QString &symlink)
260 {
261     name.truncate(0);
262     symlink.truncate(0);
263     while (true) {
264         qint64 n = readRawHeader(buffer);
265         if (n != 0x200) {
266             return n;
267         }
268 
269         // is it a longlink?
270         if (strcmp(buffer, "././@LongLink") == 0) {
271             char typeflag = buffer[0x9c];
272             QByteArray longlink;
273             if (readLonglink(buffer, longlink)) {
274                 switch (typeflag) {
275                 case 'L':
276                     name = QFile::decodeName(longlink.constData());
277                     break;
278                 case 'K':
279                     symlink = QFile::decodeName(longlink.constData());
280                     break;
281                 }/*end switch*/
282             }
283         } else {
284             break;
285         }/*end if*/
286     }/*wend*/
287 
288     // if not result of longlink, read names directly from the header
289     if (name.isEmpty())
290         // there are names that are exactly 100 bytes long
291         // and neither longlink nor \0 terminated (bug:101472)
292     {
293         name = QFile::decodeName(QByteArray(buffer, qstrnlen(buffer, 100)));
294     }
295     if (symlink.isEmpty()) {
296         char *symlinkBuffer = buffer + 0x9d /*?*/;
297         symlink = QFile::decodeName(QByteArray(symlinkBuffer, qstrnlen(symlinkBuffer, 100)));
298     }
299 
300     return 0x200;
301 }
302 
303 /*
304  * If we have created a temporary file, we have
305  * to decompress the original file now and write
306  * the contents to the temporary file.
307  */
fillTempFile(const QString & fileName)308 bool KTar::KTarPrivate::fillTempFile(const QString &fileName)
309 {
310     if (! tmpFile) {
311         return true;
312     }
313 
314     //qCDebug(KArchiveLog) << "filling tmpFile of mimetype" << mimetype;
315 
316     KCompressionDevice::CompressionType compressionType = KFilterDev::compressionTypeForMimeType(mimetype);
317     KCompressionDevice filterDev(fileName, compressionType);
318 
319     QFile *file = tmpFile;
320     Q_ASSERT(file->isOpen());
321     Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
322     file->seek(0);
323     QByteArray buffer;
324     buffer.resize(8 * 1024);
325     if (! filterDev.open(QIODevice::ReadOnly)) {
326         q->setErrorString(
327             tr("File %1 does not exist")
328             .arg(fileName));
329         return false;
330     }
331     qint64 len = -1;
332     while (!filterDev.atEnd() && len != 0) {
333         len = filterDev.read(buffer.data(), buffer.size());
334         if (len < 0) {   // corrupted archive
335             q->setErrorString(tr("Archive %1 is corrupt").arg(fileName));
336             return false;
337         }
338         if (file->write(buffer.data(), len) != len) {   // disk full
339             q->setErrorString(tr("Disk full"));
340             return false;
341         }
342     }
343     filterDev.close();
344 
345     file->flush();
346     file->seek(0);
347     Q_ASSERT(file->isOpen());
348     Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
349 
350     //qCDebug(KArchiveLog) << "filling tmpFile finished.";
351     return true;
352 }
353 
openArchive(QIODevice::OpenMode mode)354 bool KTar::openArchive(QIODevice::OpenMode mode)
355 {
356 
357     if (!(mode & QIODevice::ReadOnly)) {
358         return true;
359     }
360 
361     if (!d->fillTempFile(fileName())) {
362         return false;
363     }
364 
365     // We'll use the permission and user/group of d->rootDir
366     // for any directory we emulate (see findOrCreate)
367     //struct stat buf;
368     //stat( fileName(), &buf );
369 
370     d->dirList.clear();
371     QIODevice *dev = device();
372 
373     if (!dev) {
374         setErrorString(tr("Could not get underlying device"));
375         qCWarning(KArchiveLog) << "Could not get underlying device";
376         return false;
377     }
378 
379     // read dir information
380     char buffer[0x200];
381     bool ende = false;
382     do {
383         QString name;
384         QString symlink;
385 
386         // Read header
387         qint64 n = d->readHeader(buffer, name, symlink);
388         if (n < 0) {
389             setErrorString(tr("Could not read tar header"));
390             return false;
391         }
392         if (n == 0x200) {
393             bool isdir = false;
394 
395             if (name.isEmpty()) {
396                 continue;
397             }
398             if (name.endsWith(QLatin1Char('/'))) {
399                 isdir = true;
400                 name.truncate(name.length() - 1);
401             }
402 
403             QByteArray prefix = QByteArray(buffer + 0x159, 155);
404             if (prefix[0] != '\0') {
405                 name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') +  name);
406             }
407 
408             int pos = name.lastIndexOf(QLatin1Char('/'));
409             QString nm = (pos == -1) ? name : name.mid(pos + 1);
410 
411             // read access
412             buffer[0x6b] = 0;
413             char *dummy;
414             const char *p = buffer + 0x64;
415             while (*p == ' ') {
416                 ++p;
417             }
418             int access = strtol(p, &dummy, 8);
419 
420             // read user and group
421             const int maxUserGroupLength = 32;
422             const char *userStart = buffer + 0x109;
423             const int userLen = qstrnlen(userStart, maxUserGroupLength);
424             const QString user = QString::fromLocal8Bit(userStart, userLen);
425             const char *groupStart = buffer + 0x129;
426             const int groupLen = qstrnlen(groupStart, maxUserGroupLength);
427             const QString group = QString::fromLocal8Bit(groupStart, groupLen);
428 
429             // read time
430             buffer[0x93] = 0;
431             p = buffer + 0x88;
432             while (*p == ' ') {
433                 ++p;
434             }
435             uint time = strtol(p, &dummy, 8);
436 
437             // read type flag
438             char typeflag = buffer[0x9c];
439             // '0' for files, '1' hard link, '2' symlink, '5' for directory
440             // (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
441             // 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
442             // to the next file in the archive and 'g' for Global extended header
443 
444             if (typeflag == '5') {
445                 isdir = true;
446             }
447 
448             bool isDumpDir = false;
449             if (typeflag == 'D') {
450                 isdir = false;
451                 isDumpDir = true;
452             }
453             //qCDebug(KArchiveLog) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' );
454 
455             if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header
456                 // Skip it for now. TODO: implement reading of extended header, as per http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html
457                 (void)dev->read(buffer, 0x200);
458                 continue;
459             }
460 
461             if (isdir) {
462                 access |= S_IFDIR;    // f*cking broken tar files
463             }
464 
465             KArchiveEntry *e;
466             if (isdir) {
467                 //qCDebug(KArchiveLog) << "directory" << nm;
468                 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
469             } else {
470                 // read size
471                 QByteArray sizeBuffer(buffer + 0x7c, 12);
472                 qint64 size = sizeBuffer.trimmed().toLongLong(nullptr, 8 /*octal*/);
473                 //qCDebug(KArchiveLog) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
474 
475                 // for isDumpDir we will skip the additional info about that dirs contents
476                 if (isDumpDir) {
477                     //qCDebug(KArchiveLog) << nm << "isDumpDir";
478                     e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
479                 } else {
480 
481                     // Let's hack around hard links. Our classes don't support that, so make them symlinks
482                     if (typeflag == '1') {
483                         //qCDebug(KArchiveLog) << "Hard link, setting size to 0 instead of" << size;
484                         size = 0; // no contents
485                     }
486 
487                     //qCDebug(KArchiveLog) << "file" << nm << "size=" << size;
488                     e = new KArchiveFile(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink,
489                                          dev->pos(), size);
490                 }
491 
492                 // Skip contents + align bytes
493                 qint64 rest = size % 0x200;
494                 qint64 skip = size + (rest ? 0x200 - rest : 0);
495                 //qCDebug(KArchiveLog) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
496                 if (! dev->seek(dev->pos() + skip)) {
497                     //qCWarning(KArchiveLog) << "skipping" << skip << "failed";
498                 }
499             }
500 
501             if (pos == -1) {
502                 if (nm == QLatin1String(".")) { // special case
503                     if (isdir) {
504                         if (KArchivePrivate::hasRootDir(this)) {
505                             qWarning() << "Broken tar file has two root dir entries";
506                             delete e;
507                         } else {
508                             setRootDir(static_cast<KArchiveDirectory *>(e));
509                         }
510                     } else {
511                         delete e;
512                     }
513                 } else {
514                     rootDir()->addEntry(e);
515                 }
516             } else {
517                 // In some tar files we can find dir/./file => call cleanPath
518                 QString path = QDir::cleanPath(name.left(pos));
519                 // Ensure container directory exists, create otherwise
520                 KArchiveDirectory *d = findOrCreate(path);
521                 if (d) {
522                     d->addEntry(e);
523                 } else {
524                     delete e;
525                     return false;
526                 }
527             }
528         } else {
529             //qCDebug(KArchiveLog) << "Terminating. Read " << n << " bytes, first one is " << buffer[0];
530             d->tarEnd = dev->pos() - n; // Remember end of archive
531             ende = true;
532         }
533     } while (!ende);
534     return true;
535 }
536 
537 /*
538  * Writes back the changes of the temporary file
539  * to the original file.
540  * Must only be called if in write mode, not in read mode
541  */
writeBackTempFile(const QString & fileName)542 bool KTar::KTarPrivate::writeBackTempFile(const QString &fileName)
543 {
544     if (!tmpFile) {
545         return true;
546     }
547 
548     //qCDebug(KArchiveLog) << "Write temporary file to compressed file" << fileName << mimetype;
549 
550     bool forced = false;
551     if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype ||
552         QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype) {
553         forced = true;
554     }
555 
556     // #### TODO this should use QSaveFile to avoid problems on disk full
557     // (KArchive uses QSaveFile by default, but the temp-uncompressed-file trick
558     // circumvents that).
559 
560     KFilterDev dev(fileName);
561     QFile *file = tmpFile;
562     if (!dev.open(QIODevice::WriteOnly)) {
563         file->close();
564         q->setErrorString(tr("Failed to write back temp file: %1").arg(dev.errorString()));
565         return false;
566     }
567     if (forced) {
568         dev.setOrigFileName(origFileName);
569     }
570     file->seek(0);
571     QByteArray buffer;
572     buffer.resize(8 * 1024);
573     qint64 len;
574     while (!file->atEnd()) {
575         len = file->read(buffer.data(), buffer.size());
576         dev.write(buffer.data(), len); // TODO error checking
577     }
578     file->close();
579     dev.close();
580 
581     //qCDebug(KArchiveLog) << "Write temporary file to compressed file done.";
582     return true;
583 }
584 
closeArchive()585 bool KTar::closeArchive()
586 {
587     d->dirList.clear();
588 
589     bool ok = true;
590 
591     // If we are in readwrite mode and had created
592     // a temporary tar file, we have to write
593     // back the changes to the original file
594     if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
595         ok = d->writeBackTempFile(fileName());
596         delete d->tmpFile;
597         d->tmpFile = nullptr;
598         setDevice(nullptr);
599     }
600 
601     return ok;
602 }
603 
doFinishWriting(qint64 size)604 bool KTar::doFinishWriting(qint64 size)
605 {
606     // Write alignment
607     int rest = size % 0x200;
608     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
609         d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0);    // Record our new end of archive
610     }
611     if (rest) {
612         char buffer[0x201];
613         for (uint i = 0; i < 0x200; ++i) {
614             buffer[i] = 0;
615         }
616         qint64 nwritten = device()->write(buffer, 0x200 - rest);
617         const bool ok = nwritten == 0x200 - rest;
618 
619         if (!ok) {
620             setErrorString(
621                 tr("Couldn't write alignment: %1")
622                 .arg(device()->errorString()));
623         }
624 
625         return ok;
626     }
627     return true;
628 }
629 
630 /*** Some help from the tar sources
631 struct posix_header
632 {                               byte offset
633   char name[100];               *   0 *     0x0
634   char mode[8];                 * 100 *     0x64
635   char uid[8];                  * 108 *     0x6c
636   char gid[8];                  * 116 *     0x74
637   char size[12];                * 124 *     0x7c
638   char mtime[12];               * 136 *     0x88
639   char chksum[8];               * 148 *     0x94
640   char typeflag;                * 156 *     0x9c
641   char linkname[100];           * 157 *     0x9d
642   char magic[6];                * 257 *     0x101
643   char version[2];              * 263 *     0x107
644   char uname[32];               * 265 *     0x109
645   char gname[32];               * 297 *     0x129
646   char devmajor[8];             * 329 *     0x149
647   char devminor[8];             * 337 *     ...
648   char prefix[155];             * 345 *
649                                 * 500 *
650 };
651 */
652 
fillBuffer(char * buffer,const char * mode,qint64 size,const QDateTime & mtime,char typeflag,const char * uname,const char * gname)653 void KTar::KTarPrivate::fillBuffer(char *buffer,
654                                    const char *mode, qint64 size, const QDateTime &mtime, char typeflag,
655                                    const char *uname, const char *gname)
656 {
657     // mode (as in stpos())
658     assert(strlen(mode) == 6);
659     memcpy(buffer + 0x64, mode, 6);
660     buffer[0x6a] = ' ';
661     buffer[0x6b] = '\0';
662 
663     // dummy uid
664     strcpy(buffer + 0x6c, "   765 ");  // 501 in decimal
665     // dummy gid
666     strcpy(buffer + 0x74, "   144 ");  // 100 in decimal
667 
668     // size
669     QByteArray s = QByteArray::number(size, 8);   // octal
670     s = s.rightJustified(11, '0');
671     memcpy(buffer + 0x7c, s.data(), 11);
672     buffer[0x87] = ' '; // space-terminate (no null after)
673 
674     // modification time
675     const QDateTime modificationTime = mtime.isValid() ? mtime : QDateTime::currentDateTime();
676     s = QByteArray::number(static_cast<qulonglong>(modificationTime.toMSecsSinceEpoch() / 1000), 8);   // octal
677     s = s.rightJustified(11, '0');
678     memcpy(buffer + 0x88, s.data(), 11);
679     buffer[0x93] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
680 
681     // spaces, replaced by the check sum later
682     buffer[0x94] = 0x20;
683     buffer[0x95] = 0x20;
684     buffer[0x96] = 0x20;
685     buffer[0x97] = 0x20;
686     buffer[0x98] = 0x20;
687     buffer[0x99] = 0x20;
688 
689     /* From the tar sources :
690        Fill in the checksum field.  It's formatted differently from the
691        other fields: it has [6] digits, a null, then a space -- rather than
692        digits, a space, then a null. */
693 
694     buffer[0x9a] = '\0';
695     buffer[0x9b] = ' ';
696 
697     // type flag (dir, file, link)
698     buffer[0x9c] = typeflag;
699 
700     // magic + version
701     strcpy(buffer + 0x101, "ustar");
702     strcpy(buffer + 0x107, "00");
703 
704     // user
705     strcpy(buffer + 0x109, uname);
706     // group
707     strcpy(buffer + 0x129, gname);
708 
709     // Header check sum
710     int check = 32;
711     for (uint j = 0; j < 0x200; ++j) {
712         check += static_cast<unsigned char>(buffer[j]);
713     }
714     s = QByteArray::number(check, 8);   // octal
715     s = s.rightJustified(6, '0');
716     memcpy(buffer + 0x94, s.constData(), 6);
717 }
718 
writeLonglink(char * buffer,const QByteArray & name,char typeflag,const char * uname,const char * gname)719 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag,
720                                       const char *uname, const char *gname)
721 {
722     strcpy(buffer, "././@LongLink");
723     qint64 namelen = name.length() + 1;
724     fillBuffer(buffer, "     0", namelen, QDateTime(), typeflag, uname, gname);
725     q->device()->write(buffer, 0x200);   // TODO error checking
726     qint64 offset = 0;
727     while (namelen > 0) {
728         int chunksize = qMin(namelen, 0x200LL);
729         memcpy(buffer, name.data() + offset, chunksize);
730         // write long name
731         q->device()->write(buffer, 0x200);   // TODO error checking
732         // not even needed to reclear the buffer, tar doesn't do it
733         namelen -= chunksize;
734         offset += 0x200;
735     }/*wend*/
736 }
737 
doPrepareWriting(const QString & name,const QString & user,const QString & group,qint64 size,mode_t perm,const QDateTime &,const QDateTime & mtime,const QDateTime &)738 bool KTar::doPrepareWriting(const QString &name, const QString &user,
739                             const QString &group, qint64 size, mode_t perm,
740                             const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/)
741 {
742     if (!isOpen()) {
743         setErrorString(tr("Application error: TAR file must be open before being written into"));
744         qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
745         return false;
746     }
747 
748     if (!(mode() & QIODevice::WriteOnly)) {
749         setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file"));
750         qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
751         return false;
752     }
753 
754     // In some tar files we can find dir/./file => call cleanPath
755     QString fileName(QDir::cleanPath(name));
756 
757     /*
758       // Create toplevel dirs
759       // Commented out by David since it's not necessary, and if anybody thinks it is,
760       // he needs to implement a findOrCreate equivalent in writeDir.
761       // But as KTar and the "tar" program both handle tar files without
762       // dir entries, there's really no need for that
763       QString tmp ( fileName );
764       int i = tmp.lastIndexOf( '/' );
765       if ( i != -1 )
766       {
767       QString d = tmp.left( i + 1 ); // contains trailing slash
768       if ( !m_dirList.contains( d ) )
769       {
770       tmp = tmp.mid( i + 1 );
771       writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
772       }
773       }
774     */
775 
776     char buffer[0x201];
777     memset(buffer, 0, 0x200);
778     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
779         device()->seek(d->tarEnd);    // Go to end of archive as might have moved with a read
780     }
781 
782     // provide converted stuff we need later on
783     const QByteArray encodedFileName = QFile::encodeName(fileName);
784     const QByteArray uname = user.toLocal8Bit();
785     const QByteArray gname = group.toLocal8Bit();
786 
787     // If more than 100 bytes, we need to use the LongLink trick
788     if (encodedFileName.length() > 99) {
789         d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
790     }
791 
792     // Write (potentially truncated) name
793     strncpy(buffer, encodedFileName.constData(), 99);
794     buffer[99] = 0;
795     // zero out the rest (except for what gets filled anyways)
796     memset(buffer + 0x9d, 0, 0x200 - 0x9d);
797 
798     QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
799     permstr = permstr.rightJustified(6, '0');
800     d->fillBuffer(buffer, permstr.constData(), size, mtime, 0x30, uname.constData(), gname.constData());
801 
802     // Write header
803     if (device()->write(buffer, 0x200) != 0x200) {
804         setErrorString(
805             tr("Failed to write header: %1")
806             .arg(device()->errorString()));
807         return false;
808     } else {
809         return true;
810     }
811 }
812 
doWriteDir(const QString & name,const QString & user,const QString & group,mode_t perm,const QDateTime &,const QDateTime & mtime,const QDateTime &)813 bool KTar::doWriteDir(const QString &name, const QString &user,
814                       const QString &group, mode_t perm,
815                       const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/)
816 {
817     if (!isOpen()) {
818         setErrorString(tr("Application error: TAR file must be open before being written into"));
819         qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()";
820         return false;
821     }
822 
823     if (!(mode() & QIODevice::WriteOnly)) {
824         setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
825         qCWarning(KArchiveLog) << "doWriteDir failed: !(mode() & QIODevice::WriteOnly)";
826         return false;
827     }
828 
829     // In some tar files we can find dir/./ => call cleanPath
830     QString dirName(QDir::cleanPath(name));
831 
832     // Need trailing '/'
833     if (!dirName.endsWith(QLatin1Char('/'))) {
834         dirName += QLatin1Char('/');
835     }
836 
837     if (d->dirList.contains(dirName)) {
838         return true;    // already there
839     }
840 
841     char buffer[0x201];
842     memset(buffer, 0, 0x200);
843     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
844         device()->seek(d->tarEnd);    // Go to end of archive as might have moved with a read
845     }
846 
847     // provide converted stuff we need lateron
848     QByteArray encodedDirname = QFile::encodeName(dirName);
849     QByteArray uname = user.toLocal8Bit();
850     QByteArray gname = group.toLocal8Bit();
851 
852     // If more than 100 bytes, we need to use the LongLink trick
853     if (encodedDirname.length() > 99) {
854         d->writeLonglink(buffer, encodedDirname, 'L', uname.constData(), gname.constData());
855     }
856 
857     // Write (potentially truncated) name
858     strncpy(buffer, encodedDirname.constData(), 99);
859     buffer[99] = 0;
860     // zero out the rest (except for what gets filled anyways)
861     memset(buffer + 0x9d, 0, 0x200 - 0x9d);
862 
863     QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
864     permstr = permstr.rightJustified(6, ' ');
865     d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x35, uname.constData(), gname.constData());
866 
867     // Write header
868     device()->write(buffer, 0x200);
869     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
870         d->tarEnd = device()->pos();
871     }
872 
873     d->dirList.append(dirName);   // contains trailing slash
874     return true; // TODO if wanted, better error control
875 }
876 
doWriteSymLink(const QString & name,const QString & target,const QString & user,const QString & group,mode_t perm,const QDateTime &,const QDateTime & mtime,const QDateTime &)877 bool KTar::doWriteSymLink(const QString &name, const QString &target,
878                           const QString &user, const QString &group,
879                           mode_t perm, const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/)
880 {
881     if (!isOpen()) {
882         setErrorString(tr("Application error: TAR file must be open before being written into"));
883         qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()";
884         return false;
885     }
886 
887     if (!(mode() & QIODevice::WriteOnly)) {
888         setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
889         qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)";
890         return false;
891     }
892 
893     // In some tar files we can find dir/./file => call cleanPath
894     QString fileName(QDir::cleanPath(name));
895 
896     char buffer[0x201];
897     memset(buffer, 0, 0x200);
898     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
899         device()->seek(d->tarEnd);    // Go to end of archive as might have moved with a read
900     }
901 
902     // provide converted stuff we need lateron
903     QByteArray encodedFileName = QFile::encodeName(fileName);
904     QByteArray encodedTarget = QFile::encodeName(target);
905     QByteArray uname = user.toLocal8Bit();
906     QByteArray gname = group.toLocal8Bit();
907 
908     // If more than 100 bytes, we need to use the LongLink trick
909     if (encodedTarget.length() > 99) {
910         d->writeLonglink(buffer, encodedTarget, 'K', uname.constData(), gname.constData());
911     }
912     if (encodedFileName.length() > 99) {
913         d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
914     }
915 
916     // Write (potentially truncated) name
917     strncpy(buffer, encodedFileName.constData(), 99);
918     buffer[99] = 0;
919     // Write (potentially truncated) symlink target
920     strncpy(buffer + 0x9d, encodedTarget.constData(), 99);
921     buffer[0x9d + 99] = 0;
922     // zero out the rest
923     memset(buffer + 0x9d + 100, 0, 0x200 - 100 - 0x9d);
924 
925     QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
926     permstr = permstr.rightJustified(6, ' ');
927     d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x32, uname.constData(), gname.constData());
928 
929     // Write header
930     bool retval = device()->write(buffer, 0x200) == 0x200;
931     if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
932         d->tarEnd = device()->pos();
933     }
934     return retval;
935 }
936 
virtual_hook(int id,void * data)937 void KTar::virtual_hook(int id, void *data)
938 {
939     KArchive::virtual_hook(id, data);
940 }
941