KArchive

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

KDE's Doxygen guidelines are available online.