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 // qCDebug(KArchiveLog) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
490
491 // for isDumpDir we will skip the additional info about that dirs contents
492 if (isDumpDir) {
493 // qCDebug(KArchiveLog) << nm << "isDumpDir";
494 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
495 } else {
496 // Let's hack around hard links. Our classes don't support that, so make them symlinks
497 if (typeflag == '1') {
498 // qCDebug(KArchiveLog) << "Hard link, setting size to 0 instead of" << size;
499 size = 0; // no contents
500 }
501
502 // qCDebug(KArchiveLog) << "file" << nm << "size=" << size;
503 e = new KArchiveFile(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink, dev->pos(), size);
504 }
505
506 // Skip contents + align bytes
507 qint64 rest = size % 0x200;
508 qint64 skip = size + (rest ? 0x200 - rest : 0);
509 // qCDebug(KArchiveLog) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
510 if (!dev->seek(dev->pos() + skip)) {
511 // qCWarning(KArchiveLog) << "skipping" << skip << "failed";
512 }
513 }
514
515 if (pos == -1) {
516 if (nm == QLatin1String(".")) { // special case
517 if (isdir) {
518 if (KArchivePrivate::hasRootDir(this)) {
519 qWarning() << "Broken tar file has two root dir entries";
520 delete e;
521 } else {
522 setRootDir(static_cast<KArchiveDirectory *>(e));
523 }
524 } else {
525 delete e;
526 }
527 } else {
528 rootDir()->addEntry(e);
529 }
530 } else {
531 // In some tar files we can find dir/./file => call cleanPath
532 QString path = QDir::cleanPath(name.left(pos));
533 // Ensure container directory exists, create otherwise
535 if (d) {
536 d->addEntry(e);
537 } else {
538 delete e;
539 return false;
540 }
541 }
542 } else {
543 // qCDebug(KArchiveLog) << "Terminating. Read " << n << " bytes, first one is " << buffer[0];
544 d->tarEnd = dev->pos() - n; // Remember end of archive
545 ende = true;
546 }
547 } while (!ende);
548 return true;
549}
550
551/*
552 * Writes back the changes of the temporary file
553 * to the original file.
554 * Must only be called if in write mode, not in read mode
555 */
556bool KTar::KTarPrivate::writeBackTempFile(const QString &fileName)
557{
558 if (!tmpFile) {
559 return true;
560 }
561
562 // qCDebug(KArchiveLog) << "Write temporary file to compressed file" << fileName << mimetype;
563
564 bool forced = false;
565 /* clang-format off */
566 if (MimeType::application_gzip_old() == mimetype ||
567 QLatin1String(application_bzip) == mimetype ||
568 QLatin1String(application_lzma) == mimetype ||
569 QLatin1String(application_xz) == mimetype) {
570 /* clang-format on */
571 forced = true;
572 }
573
574 // #### TODO this should use QSaveFile to avoid problems on disk full
575 // (KArchive uses QSaveFile by default, but the temp-uncompressed-file trick
576 // circumvents that).
577
579 QFile *file = tmpFile;
580 if (!dev.open(QIODevice::WriteOnly)) {
581 file->close();
582 q->setErrorString(tr("Failed to write back temp file: %1").arg(dev.errorString()));
583 return false;
584 }
585 if (forced) {
586 dev.setOrigFileName(origFileName);
587 }
588 file->seek(0);
589 QByteArray buffer;
590 buffer.resize(8 * 1024);
591 qint64 len;
592 while (!file->atEnd()) {
593 len = file->read(buffer.data(), buffer.size());
594 dev.write(buffer.data(), len); // TODO error checking
595 }
596 file->close();
597 dev.close();
598
599 // qCDebug(KArchiveLog) << "Write temporary file to compressed file done.";
600 return true;
601}
602
604{
605 d->dirList.clear();
606
607 bool ok = true;
608
609 // If we are in readwrite mode and had created
610 // a temporary tar file, we have to write
611 // back the changes to the original file
612 if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
613 ok = d->writeBackTempFile(fileName());
614 delete d->tmpFile;
615 d->tmpFile = nullptr;
616 setDevice(nullptr);
617 }
618
619 return ok;
620}
621
622bool KTar::doFinishWriting(qint64 size)
623{
624 // Write alignment
625 int rest = size % 0x200;
627 d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
628 }
629 if (rest) {
630 char buffer[0x201];
631 for (uint i = 0; i < 0x200; ++i) {
632 buffer[i] = 0;
633 }
634 qint64 nwritten = device()->write(buffer, 0x200 - rest);
635 const bool ok = nwritten == 0x200 - rest;
636
637 if (!ok) {
638 setErrorString(tr("Couldn't write alignment: %1").arg(device()->errorString()));
639 }
640
641 return ok;
642 }
643 return true;
644}
645
646/*** Some help from the tar sources
647struct posix_header
648{ byte offset
649 char name[100]; * 0 * 0x0
650 char mode[8]; * 100 * 0x64
651 char uid[8]; * 108 * 0x6c
652 char gid[8]; * 116 * 0x74
653 char size[12]; * 124 * 0x7c
654 char mtime[12]; * 136 * 0x88
655 char chksum[8]; * 148 * 0x94
656 char typeflag; * 156 * 0x9c
657 char linkname[100]; * 157 * 0x9d
658 char magic[6]; * 257 * 0x101
659 char version[2]; * 263 * 0x107
660 char uname[32]; * 265 * 0x109
661 char gname[32]; * 297 * 0x129
662 char devmajor[8]; * 329 * 0x149
663 char devminor[8]; * 337 * ...
664 char prefix[155]; * 345 *
665 * 500 *
666};
667*/
668
669void KTar::KTarPrivate::fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname)
670{
671 // mode (as in stpos())
672 assert(strlen(mode) == 6);
673 memcpy(buffer + 0x64, mode, 6);
674 buffer[0x6a] = ' ';
675 buffer[0x6b] = '\0';
676
677 // dummy uid
678 strcpy(buffer + 0x6c, " 765 "); // 501 in decimal
679 // dummy gid
680 strcpy(buffer + 0x74, " 144 "); // 100 in decimal
681
682 // size
683 QByteArray s = QByteArray::number(size, 8); // octal
684 s = s.rightJustified(11, '0');
685 memcpy(buffer + 0x7c, s.data(), 11);
686 buffer[0x87] = ' '; // space-terminate (no null after)
687
688 // modification time
689 const QDateTime modificationTime = mtime.isValid() ? mtime : QDateTime::currentDateTime();
690 s = QByteArray::number(static_cast<qulonglong>(modificationTime.toMSecsSinceEpoch() / 1000), 8); // octal
691 s = s.rightJustified(11, '0');
692 memcpy(buffer + 0x88, s.data(), 11);
693 buffer[0x93] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
694
695 // spaces, replaced by the check sum later
696 buffer[0x94] = 0x20;
697 buffer[0x95] = 0x20;
698 buffer[0x96] = 0x20;
699 buffer[0x97] = 0x20;
700 buffer[0x98] = 0x20;
701 buffer[0x99] = 0x20;
702
703 /* From the tar sources :
704 Fill in the checksum field. It's formatted differently from the
705 other fields: it has [6] digits, a null, then a space -- rather than
706 digits, a space, then a null. */
707
708 buffer[0x9a] = '\0';
709 buffer[0x9b] = ' ';
710
711 // type flag (dir, file, link)
712 buffer[0x9c] = typeflag;
713
714 // magic + version
715 strcpy(buffer + 0x101, "ustar");
716 strcpy(buffer + 0x107, "00");
717
718 // user
719 strcpy(buffer + 0x109, uname);
720 // group
721 strcpy(buffer + 0x129, gname);
722
723 // Header check sum
724 int check = 32;
725 for (uint j = 0; j < 0x200; ++j) {
726 check += static_cast<unsigned char>(buffer[j]);
727 }
728 s = QByteArray::number(check, 8); // octal
729 s = s.rightJustified(6, '0');
730 memcpy(buffer + 0x94, s.constData(), 6);
731}
732
733void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname)
734{
735 strcpy(buffer, "././@LongLink");
736 qint64 namelen = name.length() + 1;
737 fillBuffer(buffer, " 0", namelen, QDateTime(), typeflag, uname, gname);
738 q->device()->write(buffer, 0x200); // TODO error checking
739 qint64 offset = 0;
740 while (namelen > 0) {
741 int chunksize = qMin(namelen, 0x200LL);
742 memcpy(buffer, name.data() + offset, chunksize);
743 // write long name
744 q->device()->write(buffer, 0x200); // TODO error checking
745 // not even needed to reclear the buffer, tar doesn't do it
746 namelen -= chunksize;
747 offset += 0x200;
748 } /*wend*/
749}
750
752 const QString &user,
753 const QString &group,
754 qint64 size,
755 mode_t perm,
756 const QDateTime & /*atime*/,
757 const QDateTime &mtime,
758 const QDateTime & /*ctime*/)
759{
760 if (!isOpen()) {
761 setErrorString(tr("Application error: TAR file must be open before being written into"));
762 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
763 return false;
764 }
765
766 if (!(mode() & QIODevice::WriteOnly)) {
767 setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file"));
768 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
769 return false;
770 }
771
772 const qint64 MAX_FILESIZE = 077777777777L; // the format we use only allows 11 octal digits for size
773 if (size > MAX_FILESIZE) {
774 setErrorString(tr("Application limitation: Can not add file larger than %1 bytes").arg(MAX_FILESIZE));
775 return false;
776 }
777
778 // In some tar files we can find dir/./file => call cleanPath
780
781 /*
782 // Create toplevel dirs
783 // Commented out by David since it's not necessary, and if anybody thinks it is,
784 // he needs to implement a findOrCreate equivalent in writeDir.
785 // But as KTar and the "tar" program both handle tar files without
786 // dir entries, there's really no need for that
787 QString tmp ( fileName );
788 int i = tmp.lastIndexOf( '/' );
789 if ( i != -1 )
790 {
791 QString d = tmp.left( i + 1 ); // contains trailing slash
792 if ( !m_dirList.contains( d ) )
793 {
794 tmp = tmp.mid( i + 1 );
795 writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
796 }
797 }
798 */
799
800 char buffer[0x201] = {0};
801
803 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
804 }
805
806 // provide converted stuff we need later on
807 const QByteArray encodedFileName = QFile::encodeName(fileName);
808 const QByteArray uname = user.toLocal8Bit();
809 const QByteArray gname = group.toLocal8Bit();
810
811 // If more than 100 bytes, we need to use the LongLink trick
812 if (encodedFileName.length() > 99) {
813 d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
814 }
815
816 // Write (potentially truncated) name
817 strncpy(buffer, encodedFileName.constData(), 99);
818 buffer[99] = 0;
819 // zero out the rest (except for what gets filled anyways)
820 memset(buffer + 0x9d, 0, 0x200 - 0x9d);
821
822 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
823 permstr = permstr.rightJustified(6, '0');
824 d->fillBuffer(buffer, permstr.constData(), size, mtime, 0x30, uname.constData(), gname.constData());
825
826 // Write header
827 if (device()->write(buffer, 0x200) != 0x200) {
828 setErrorString(tr("Failed to write header: %1").arg(device()->errorString()));
829 return false;
830 } else {
831 return true;
832 }
833}
834
835bool KTar::doWriteDir(const QString &name,
836 const QString &user,
837 const QString &group,
838 mode_t perm,
839 const QDateTime & /*atime*/,
840 const QDateTime &mtime,
841 const QDateTime & /*ctime*/)
842{
843 if (!isOpen()) {
844 setErrorString(tr("Application error: TAR file must be open before being written into"));
845 qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()";
846 return false;
847 }
848
849 if (!(mode() & QIODevice::WriteOnly)) {
850 setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
851 qCWarning(KArchiveLog) << "doWriteDir failed: !(mode() & QIODevice::WriteOnly)";
852 return false;
853 }
854
855 // In some tar files we can find dir/./ => call cleanPath
856 QString dirName(QDir::cleanPath(name));
857
858 // Need trailing '/'
859 if (!dirName.endsWith(QLatin1Char('/'))) {
860 dirName += QLatin1Char('/');
861 }
862
863 if (d->dirList.contains(dirName)) {
864 return true; // already there
865 }
866
867 char buffer[0x201] = {0};
868
870 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
871 }
872
873 // provide converted stuff we need lateron
874 QByteArray encodedDirname = QFile::encodeName(dirName);
875 QByteArray uname = user.toLocal8Bit();
876 QByteArray gname = group.toLocal8Bit();
877
878 // If more than 100 bytes, we need to use the LongLink trick
879 if (encodedDirname.length() > 99) {
880 d->writeLonglink(buffer, encodedDirname, 'L', uname.constData(), gname.constData());
881 }
882
883 // Write (potentially truncated) name
884 strncpy(buffer, encodedDirname.constData(), 99);
885 buffer[99] = 0;
886 // zero out the rest (except for what gets filled anyways)
887 memset(buffer + 0x9d, 0, 0x200 - 0x9d);
888
889 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
890 permstr = permstr.rightJustified(6, ' ');
891 d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x35, uname.constData(), gname.constData());
892
893 // Write header
894 device()->write(buffer, 0x200);
896 d->tarEnd = device()->pos();
897 }
898
899 d->dirList.append(dirName); // contains trailing slash
900 return true; // TODO if wanted, better error control
901}
902
904 const QString &target,
905 const QString &user,
906 const QString &group,
907 mode_t perm,
908 const QDateTime & /*atime*/,
909 const QDateTime &mtime,
910 const QDateTime & /*ctime*/)
911{
912 if (!isOpen()) {
913 setErrorString(tr("Application error: TAR file must be open before being written into"));
914 qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()";
915 return false;
916 }
917
918 if (!(mode() & QIODevice::WriteOnly)) {
919 setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
920 qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)";
921 return false;
922 }
923
924 // In some tar files we can find dir/./file => call cleanPath
926
927 char buffer[0x201] = {0};
928
930 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
931 }
932
933 // provide converted stuff we need lateron
934 QByteArray encodedFileName = QFile::encodeName(fileName);
935 QByteArray encodedTarget = QFile::encodeName(target);
936 QByteArray uname = user.toLocal8Bit();
937 QByteArray gname = group.toLocal8Bit();
938
939 // If more than 100 bytes, we need to use the LongLink trick
940 if (encodedTarget.length() > 99) {
941 d->writeLonglink(buffer, encodedTarget, 'K', uname.constData(), gname.constData());
942 }
943 if (encodedFileName.length() > 99) {
944 d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
945 }
946
947 // Write (potentially truncated) name
948 strncpy(buffer, encodedFileName.constData(), 99);
949 buffer[99] = 0;
950 // Write (potentially truncated) symlink target
951 strncpy(buffer + 0x9d, encodedTarget.constData(), 99);
952 buffer[0x9d + 99] = 0;
953 // zero out the rest
954 memset(buffer + 0x9d + 100, 0, 0x200 - 100 - 0x9d);
955
956 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
957 permstr = permstr.rightJustified(6, ' ');
958 d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x32, uname.constData(), gname.constData());
959
960 // Write header
961 bool retval = device()->write(buffer, 0x200) == 0x200;
963 d->tarEnd = device()->pos();
964 }
965 return retval;
966}
967
968void KTar::virtual_hook(int id, void *data)
969{
970 KArchive::virtual_hook(id, data);
971}
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:622
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:903
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:603
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:835
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:751
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(StandardShortcut 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)
typedef OpenMode
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 Tue Mar 26 2024 11:15:57 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.