KArchive

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

KDE's Doxygen guidelines are available online.