Marble

MarbleZip.cpp
1 // SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LGPL-3.0-only WITH Qt-LGPL-exception-1.1 OR GPL-3.0-only OR LicenseRef-Qt-Commercial
2 //
3 // SPDX-FileCopyrightText: 2015 The Qt Company Ltd. <https://www.qt.io/licensing/>
4 // This file is based on qzip.cpp from Qt with the original license
5 // below, taken from
6 // https://code.qt.io/cgit/qt/qt.git/plain/src/gui/text/qzip.cpp
7 
8 #include <qglobal.h>
9 
10 #ifndef QT_NO_TEXTODFWRITER
11 
12 #include "MarbleZipReader.h"
13 #include "MarbleZipWriter.h"
14 #include <QDateTime>
15 #include <qplatformdefs.h>
16 #include <qendian.h>
17 #include <QDebug>
18 #include <QDir>
19 
20 #include <zlib.h>
21 
22 #if defined(Q_OS_WIN)
23 # undef S_IFREG
24 # define S_IFREG 0100000
25 # ifndef S_IFDIR
26 # define S_IFDIR 0040000
27 # endif
28 # ifndef S_ISDIR
29 # define S_ISDIR(x) ((x) & S_IFDIR) > 0
30 # endif
31 # ifndef S_ISREG
32 # define S_ISREG(x) ((x) & 0170000) == S_IFREG
33 # endif
34 # define S_IFLNK 020000
35 # define S_ISLNK(x) ((x) & S_IFLNK) > 0
36 # ifndef S_IRUSR
37 # define S_IRUSR 0400
38 # endif
39 # ifndef S_IWUSR
40 # define S_IWUSR 0200
41 # endif
42 # ifndef S_IXUSR
43 # define S_IXUSR 0100
44 # endif
45 # define S_IRGRP 0040
46 # define S_IWGRP 0020
47 # define S_IXGRP 0010
48 # define S_IROTH 0004
49 # define S_IWOTH 0002
50 # define S_IXOTH 0001
51 #endif
52 
53 #if 0
54 #define ZDEBUG qDebug
55 #else
56 #define ZDEBUG if (0) qDebug
57 #endif
58 
59 namespace Marble {
60 
61 static inline uint readUInt(const uchar *data)
62 {
63  return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
64 }
65 
66 static inline ushort readUShort(const uchar *data)
67 {
68  return (data[0]) + (data[1]<<8);
69 }
70 
71 static inline void writeUInt(uchar *data, uint i)
72 {
73  data[0] = i & 0xff;
74  data[1] = (i>>8) & 0xff;
75  data[2] = (i>>16) & 0xff;
76  data[3] = (i>>24) & 0xff;
77 }
78 
79 static inline void writeUShort(uchar *data, ushort i)
80 {
81  data[0] = i & 0xff;
82  data[1] = (i>>8) & 0xff;
83 }
84 
85 static inline void copyUInt(uchar *dest, const uchar *src)
86 {
87  dest[0] = src[0];
88  dest[1] = src[1];
89  dest[2] = src[2];
90  dest[3] = src[3];
91 }
92 
93 static inline void copyUShort(uchar *dest, const uchar *src)
94 {
95  dest[0] = src[0];
96  dest[1] = src[1];
97 }
98 
99 static void writeMSDosDate(uchar *dest, const QDateTime& dt)
100 {
101  if (dt.isValid()) {
102  quint16 time =
103  (dt.time().hour() << 11) // 5 bit hour
104  | (dt.time().minute() << 5) // 6 bit minute
105  | (dt.time().second() >> 1); // 5 bit double seconds
106 
107  dest[0] = time & 0xff;
108  dest[1] = time >> 8;
109 
110  quint16 date =
111  ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
112  | (dt.date().month() << 5) // 4 bit month
113  | (dt.date().day()); // 5 bit day
114 
115  dest[2] = char(date);
116  dest[3] = char(date >> 8);
117  } else {
118  dest[0] = 0;
119  dest[1] = 0;
120  dest[2] = 0;
121  dest[3] = 0;
122  }
123 }
124 
125 static quint32 permissionsToMode(QFile::Permissions perms)
126 {
127  quint32 mode = 0;
128  if (perms & QFile::ReadOwner)
129  mode |= S_IRUSR;
130  if (perms & QFile::WriteOwner)
131  mode |= S_IWUSR;
132  if (perms & QFile::ExeOwner)
133  mode |= S_IXUSR;
134  if (perms & QFile::ReadUser)
135  mode |= S_IRUSR;
136  if (perms & QFile::WriteUser)
137  mode |= S_IWUSR;
138  if (perms & QFile::ExeUser)
139  mode |= S_IXUSR;
140  if (perms & QFile::ReadGroup)
141  mode |= S_IRGRP;
142  if (perms & QFile::WriteGroup)
143  mode |= S_IWGRP;
144  if (perms & QFile::ExeGroup)
145  mode |= S_IXGRP;
146  if (perms & QFile::ReadOther)
147  mode |= S_IROTH;
148  if (perms & QFile::WriteOther)
149  mode |= S_IWOTH;
150  if (perms & QFile::ExeOther)
151  mode |= S_IXOTH;
152  return mode;
153 }
154 
155 static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
156 {
157  z_stream stream;
158  int err;
159 
160  stream.next_in = (Bytef*)source;
161  stream.avail_in = (uInt)sourceLen;
162  if ((uLong)stream.avail_in != sourceLen)
163  return Z_BUF_ERROR;
164 
165  stream.next_out = dest;
166  stream.avail_out = (uInt)*destLen;
167  if ((uLong)stream.avail_out != *destLen)
168  return Z_BUF_ERROR;
169 
170  stream.zalloc = (alloc_func)nullptr;
171  stream.zfree = (free_func)nullptr;
172 
173  err = inflateInit2(&stream, -MAX_WBITS);
174  if (err != Z_OK)
175  return err;
176 
177  err = inflate(&stream, Z_FINISH);
178  if (err != Z_STREAM_END) {
179  inflateEnd(&stream);
180  if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
181  return Z_DATA_ERROR;
182  return err;
183  }
184  *destLen = stream.total_out;
185 
186  err = inflateEnd(&stream);
187  return err;
188 }
189 
190 static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
191 {
192  z_stream stream;
193  int err;
194 
195  stream.next_in = (Bytef*)source;
196  stream.avail_in = (uInt)sourceLen;
197  stream.next_out = dest;
198  stream.avail_out = (uInt)*destLen;
199  if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
200 
201  stream.zalloc = (alloc_func)nullptr;
202  stream.zfree = (free_func)nullptr;
203  stream.opaque = (voidpf)nullptr;
204 
205  err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
206  if (err != Z_OK) return err;
207 
208  err = deflate(&stream, Z_FINISH);
209  if (err != Z_STREAM_END) {
210  deflateEnd(&stream);
211  return err == Z_OK ? Z_BUF_ERROR : err;
212  }
213  *destLen = stream.total_out;
214 
215  err = deflateEnd(&stream);
216  return err;
217 }
218 
219 static QFile::Permissions modeToPermissions(quint32 mode)
220 {
221  QFile::Permissions ret;
222  if (mode & S_IRUSR)
223  ret |= QFile::ReadOwner;
224  if (mode & S_IWUSR)
225  ret |= QFile::WriteOwner;
226  if (mode & S_IXUSR)
227  ret |= QFile::ExeOwner;
228  if (mode & S_IRUSR)
229  ret |= QFile::ReadUser;
230  if (mode & S_IWUSR)
231  ret |= QFile::WriteUser;
232  if (mode & S_IXUSR)
233  ret |= QFile::ExeUser;
234  if (mode & S_IRGRP)
235  ret |= QFile::ReadGroup;
236  if (mode & S_IWGRP)
237  ret |= QFile::WriteGroup;
238  if (mode & S_IXGRP)
239  ret |= QFile::ExeGroup;
240  if (mode & S_IROTH)
241  ret |= QFile::ReadOther;
242  if (mode & S_IWOTH)
243  ret |= QFile::WriteOther;
244  if (mode & S_IXOTH)
245  ret |= QFile::ExeOther;
246  return ret;
247 }
248 
249 static QDateTime readMSDosDate(const uchar *src)
250 {
251  uint dosDate = readUInt(src);
252  quint64 uDate;
253  uDate = (quint64)(dosDate >> 16);
254  uint tm_mday = (uDate & 0x1f);
255  uint tm_mon = ((uDate & 0x1E0) >> 5);
256  uint tm_year = (((uDate & 0x0FE00) >> 9) + 1980);
257  uint tm_hour = ((dosDate & 0xF800) >> 11);
258  uint tm_min = ((dosDate & 0x7E0) >> 5);
259  uint tm_sec = ((dosDate & 0x1f) << 1);
260 
261  return QDateTime(QDate(tm_year, tm_mon, tm_mday), QTime(tm_hour, tm_min, tm_sec));
262 }
263 
264 struct LocalFileHeader
265 {
266  uchar signature[4]; // 0x04034b50
267  uchar version_needed[2];
268  uchar general_purpose_bits[2];
269  uchar compression_method[2];
270  uchar last_mod_file[4];
271  uchar crc_32[4];
272  uchar compressed_size[4];
273  uchar uncompressed_size[4];
274  uchar file_name_length[2];
275  uchar extra_field_length[2];
276 };
277 
278 struct DataDescriptor
279 {
280  uchar crc_32[4];
281  uchar compressed_size[4];
282  uchar uncompressed_size[4];
283 };
284 
285 struct CentralFileHeader
286 {
287  uchar signature[4]; // 0x02014b50
288  uchar version_made[2];
289  uchar version_needed[2];
290  uchar general_purpose_bits[2];
291  uchar compression_method[2];
292  uchar last_mod_file[4];
293  uchar crc_32[4];
294  uchar compressed_size[4];
295  uchar uncompressed_size[4];
296  uchar file_name_length[2];
297  uchar extra_field_length[2];
298  uchar file_comment_length[2];
299  uchar disk_start[2];
300  uchar internal_file_attributes[2];
301  uchar external_file_attributes[4];
302  uchar offset_local_header[4];
303  LocalFileHeader toLocalHeader() const;
304 };
305 
306 struct EndOfDirectory
307 {
308  uchar signature[4]; // 0x06054b50
309  uchar this_disk[2];
310  uchar start_of_directory_disk[2];
311  uchar num_dir_entries_this_disk[2];
312  uchar num_dir_entries[2];
313  uchar directory_size[4];
314  uchar dir_start_offset[4];
315  uchar comment_length[2];
316 };
317 
318 struct FileHeader
319 {
320  CentralFileHeader h;
321  QByteArray file_name;
322  QByteArray extra_field;
323  QByteArray file_comment;
324 };
325 
326 MarbleZipReader::FileInfo::FileInfo()
327  : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0)
328 {
329 }
330 
331 MarbleZipReader::FileInfo::~FileInfo()
332 {
333 }
334 
335 MarbleZipReader::FileInfo::FileInfo(const FileInfo &other)
336 {
337  operator=(other);
338 }
339 
340 MarbleZipReader::FileInfo& MarbleZipReader::FileInfo::operator=(const FileInfo &other)
341 {
342  filePath = other.filePath;
343  isDir = other.isDir;
344  isFile = other.isFile;
345  isSymLink = other.isSymLink;
346  permissions = other.permissions;
347  crc32 = other.crc32;
348  size = other.size;
349  lastModified = other.lastModified;
350  return *this;
351 }
352 
353 bool MarbleZipReader::FileInfo::isValid() const
354 {
355  return isDir || isFile || isSymLink;
356 }
357 
358 class QZipPrivate
359 {
360 public:
361  QZipPrivate(QIODevice *device, bool ownDev)
362  : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0)
363  {
364  }
365 
366  ~QZipPrivate()
367  {
368  if (ownDevice)
369  delete device;
370  }
371 
372  void fillFileInfo(int index, MarbleZipReader::FileInfo &fileInfo) const;
373 
374  QIODevice *device;
375  bool ownDevice;
376  bool dirtyFileTree;
377  QList<FileHeader> fileHeaders;
378  QByteArray comment;
379  uint start_of_directory;
380 };
381 
382 void QZipPrivate::fillFileInfo(int index, MarbleZipReader::FileInfo &fileInfo) const
383 {
384  FileHeader header = fileHeaders.at(index);
385  fileInfo.filePath = QString::fromLocal8Bit(header.file_name);
386  const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
387  if (mode == 0) {
388  fileInfo.isDir = false;
389  fileInfo.isFile = true;
390  fileInfo.isSymLink = false;
391  fileInfo.permissions = QFile::ReadOwner;
392  } else {
393  fileInfo.isDir = S_ISDIR(mode);
394  fileInfo.isFile = S_ISREG(mode);
395  fileInfo.isSymLink = S_ISLNK(mode);
396  fileInfo.permissions = modeToPermissions(mode);
397  }
398  fileInfo.crc32 = readUInt(header.h.crc_32);
399  fileInfo.size = readUInt(header.h.uncompressed_size);
400  fileInfo.lastModified = readMSDosDate(header.h.last_mod_file);
401 }
402 
403 class MarbleZipReaderPrivate : public QZipPrivate
404 {
405 public:
406  MarbleZipReaderPrivate(QIODevice *device, bool ownDev)
407  : QZipPrivate(device, ownDev), status(MarbleZipReader::NoError)
408  {
409  }
410 
411  void scanFiles();
412 
413  MarbleZipReader::Status status;
414 };
415 
416 class MarbleZipWriterPrivate : public QZipPrivate
417 {
418 public:
419  MarbleZipWriterPrivate(QIODevice *device, bool ownDev)
420  : QZipPrivate(device, ownDev),
421  status(MarbleZipWriter::NoError),
422  permissions(QFile::ReadOwner | QFile::WriteOwner),
423  compressionPolicy(MarbleZipWriter::AlwaysCompress)
424  {
425  }
426 
427  MarbleZipWriter::Status status;
428  QFile::Permissions permissions;
429  MarbleZipWriter::CompressionPolicy compressionPolicy;
430 
431  enum EntryType { Directory, File, Symlink };
432 
433  void addEntry(EntryType type, const QString &fileName, const QByteArray &contents);
434 };
435 
436 LocalFileHeader CentralFileHeader::toLocalHeader() const
437 {
438  LocalFileHeader h;
439  writeUInt(h.signature, 0x04034b50);
440  copyUShort(h.version_needed, version_needed);
441  copyUShort(h.general_purpose_bits, general_purpose_bits);
442  copyUShort(h.compression_method, compression_method);
443  copyUInt(h.last_mod_file, last_mod_file);
444  copyUInt(h.crc_32, crc_32);
445  copyUInt(h.compressed_size, compressed_size);
446  copyUInt(h.uncompressed_size, uncompressed_size);
447  copyUShort(h.file_name_length, file_name_length);
448  copyUShort(h.extra_field_length, extra_field_length);
449  return h;
450 }
451 
452 void MarbleZipReaderPrivate::scanFiles()
453 {
454  if (!dirtyFileTree)
455  return;
456 
457  if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) {
458  status = MarbleZipReader::FileOpenError;
459  return;
460  }
461 
462  if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files.
463  status = MarbleZipReader::FileReadError;
464  return;
465  }
466 
467  dirtyFileTree = false;
468  uchar tmp[4];
469  device->read((char *)tmp, 4);
470  if (readUInt(tmp) != 0x04034b50) {
471  qWarning() << "QZip: not a zip file!";
472  return;
473  }
474 
475  // find EndOfDirectory header
476  int i = 0;
477  int start_of_directory = -1;
478  int num_dir_entries = 0;
479  EndOfDirectory eod;
480  while (start_of_directory == -1) {
481  int pos = device->size() - sizeof(EndOfDirectory) - i;
482  if (pos < 0 || i > 65535) {
483  qWarning() << "QZip: EndOfDirectory not found";
484  return;
485  }
486 
487  device->seek(pos);
488  device->read((char *)&eod, sizeof(EndOfDirectory));
489  if (readUInt(eod.signature) == 0x06054b50)
490  break;
491  ++i;
492  }
493 
494  // have the eod
495  start_of_directory = readUInt(eod.dir_start_offset);
496  num_dir_entries = readUShort(eod.num_dir_entries);
497  ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
498  int comment_length = readUShort(eod.comment_length);
499  if (comment_length != i)
500  qWarning() << "QZip: failed to parse zip file.";
501  comment = device->read(qMin(comment_length, i));
502 
503 
504  device->seek(start_of_directory);
505  for (i = 0; i < num_dir_entries; ++i) {
506  FileHeader header;
507  int read = device->read((char *) &header.h, sizeof(CentralFileHeader));
508  if (read < (int)sizeof(CentralFileHeader)) {
509  qWarning() << "QZip: Failed to read complete header, index may be incomplete";
510  break;
511  }
512  if (readUInt(header.h.signature) != 0x02014b50) {
513  qWarning() << "QZip: invalid header signature, index may be incomplete";
514  break;
515  }
516 
517  int l = readUShort(header.h.file_name_length);
518  header.file_name = device->read(l);
519  if (header.file_name.length() != l) {
520  qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete";
521  break;
522  }
523  l = readUShort(header.h.extra_field_length);
524  header.extra_field = device->read(l);
525  if (header.extra_field.length() != l) {
526  qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
527  break;
528  }
529  l = readUShort(header.h.file_comment_length);
530  header.file_comment = device->read(l);
531  if (header.file_comment.length() != l) {
532  qWarning() << "QZip: Failed to read file comment, index may be incomplete";
533  break;
534  }
535 
536  ZDEBUG("found file '%s'", header.file_name.data());
537  fileHeaders.append(header);
538  }
539 }
540 
541 void MarbleZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/)
542 {
543 #ifndef NDEBUG
544  static const char *entryTypes[] = {
545  "directory",
546  "file ",
547  "symlink " };
548  ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? QByteArray(" -> " + contents).constData() : "");
549 #endif
550 
551  if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) {
552  status = MarbleZipWriter::FileOpenError;
553  return;
554  }
555  device->seek(start_of_directory);
556 
557  // don't compress small files
558  MarbleZipWriter::CompressionPolicy compression = compressionPolicy;
559  if (compressionPolicy == MarbleZipWriter::AutoCompress) {
560  if (contents.length() < 64)
561  compression = MarbleZipWriter::NeverCompress;
562  else
563  compression = MarbleZipWriter::AlwaysCompress;
564  }
565 
566  FileHeader header;
567  memset(&header.h, 0, sizeof(CentralFileHeader));
568  writeUInt(header.h.signature, 0x02014b50);
569 
570  writeUShort(header.h.version_needed, 0x14);
571  writeUInt(header.h.uncompressed_size, contents.length());
572  writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime());
573  QByteArray data = contents;
574  if (compression == MarbleZipWriter::AlwaysCompress) {
575  writeUShort(header.h.compression_method, 8);
576 
577  ulong len = contents.length();
578  // shamelessly copied form zlib
579  len += (len >> 12) + (len >> 14) + 11;
580  int res;
581  do {
582  data.resize(len);
583  res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length());
584 
585  switch (res) {
586  case Z_OK:
587  data.resize(len);
588  break;
589  case Z_MEM_ERROR:
590  qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
591  data.resize(0);
592  break;
593  case Z_BUF_ERROR:
594  len *= 2;
595  break;
596  }
597  } while (res == Z_BUF_ERROR);
598  }
599 // TODO add a check if data.length() > contents.length(). Then try to store the original and revert the compression method to be uncompressed
600  writeUInt(header.h.compressed_size, data.length());
601  uint crc_32 = ::crc32(0, nullptr, 0);
602  crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length());
603  writeUInt(header.h.crc_32, crc_32);
604 
605  header.file_name = fileName.toLocal8Bit();
606  if (header.file_name.size() > 0xffff) {
607  qWarning("QZip: Filename too long, chopping it to 65535 characters");
608  header.file_name = header.file_name.left(0xffff);
609  }
610  writeUShort(header.h.file_name_length, header.file_name.length());
611  //h.extra_field_length[2];
612 
613  writeUShort(header.h.version_made, 3 << 8);
614  //uchar internal_file_attributes[2];
615  //uchar external_file_attributes[4];
616  quint32 mode = permissionsToMode(permissions);
617  switch (type) {
618  case File: mode |= S_IFREG; break;
619  case Directory: mode |= S_IFDIR; break;
620  case Symlink: mode |= S_IFLNK; break;
621  }
622  writeUInt(header.h.external_file_attributes, mode << 16);
623  writeUInt(header.h.offset_local_header, start_of_directory);
624 
625 
626  fileHeaders.append(header);
627 
628  LocalFileHeader h = header.h.toLocalHeader();
629  device->write((const char *)&h, sizeof(LocalFileHeader));
630  device->write(header.file_name);
631  device->write(data);
632  start_of_directory = device->pos();
633  dirtyFileTree = true;
634 }
635 
636 ////////////////////////////// Reader
637 
638 /*!
639  \class QZipReader::FileInfo
640  \internal
641  Represents one entry in the zip table of contents.
642 */
643 
644 /*!
645  \variable FileInfo::filePath
646  The full filepath inside the archive.
647 */
648 
649 /*!
650  \variable FileInfo::isDir
651  A boolean type indicating if the entry is a directory.
652 */
653 
654 /*!
655  \variable FileInfo::isFile
656  A boolean type, if it is one this entry is a file.
657 */
658 
659 /*!
660  \variable FileInfo::isSymLink
661  A boolean type, if it is one this entry is symbolic link.
662 */
663 
664 /*!
665  \variable FileInfo::permissions
666  A list of flags for the permissions of this entry.
667 */
668 
669 /*!
670  \variable FileInfo::crc32
671  The calculated checksum as a crc32 type.
672 */
673 
674 /*!
675  \variable FileInfo::size
676  The total size of the unpacked content.
677 */
678 
679 /*!
680  \variable FileInfo::d
681  \internal
682  private pointer.
683 */
684 
685 /*!
686  \class QZipReader
687  \internal
688  \since 4.5
689 
690  \brief the QZipReader class provides a way to inspect the contents of a zip
691  archive and extract individual files from it.
692 
693  QZipReader can be used to read a zip archive either from a file or from any
694  device. An in-memory QBuffer for instance. The reader can be used to read
695  which files are in the archive using fileInfoList() and entryInfoAt() but
696  also to extract individual files using fileData() or even to extract all
697  files in the archive using extractAll()
698 */
699 
700 /*!
701  Create a new zip archive that operates on the \a fileName. The file will be
702  opened with the \a mode.
703 */
704 MarbleZipReader::MarbleZipReader(const QString &archive, QIODevice::OpenMode mode)
705 {
706  QScopedPointer<QFile> f(new QFile(archive));
707  f->open(mode);
708  MarbleZipReader::Status status;
709  if (f->error() == QFile::NoError)
710  status = NoError;
711  else {
712  if (f->error() == QFile::ReadError)
713  status = FileReadError;
714  else if (f->error() == QFile::OpenError)
715  status = FileOpenError;
716  else if (f->error() == QFile::PermissionsError)
717  status = FilePermissionsError;
718  else
719  status = FileError;
720  }
721 
722  d = new MarbleZipReaderPrivate(f.data(), /*ownDevice=*/true);
723  f.take();
724  d->status = status;
725 }
726 
727 /*!
728  Create a new zip archive that operates on the archive found in \a device.
729  You have to open the device previous to calling the constructor and only a
730  device that is readable will be scanned for zip filecontent.
731  */
732 MarbleZipReader::MarbleZipReader(QIODevice *device)
733  : d(new MarbleZipReaderPrivate(device, /*ownDevice=*/false))
734 {
735  Q_ASSERT(device);
736 }
737 
738 /*!
739  Desctructor
740 */
741 MarbleZipReader::~MarbleZipReader()
742 {
743  close();
744  delete d;
745 }
746 
747 /*!
748  Returns device used for reading zip archive.
749 */
750 QIODevice* MarbleZipReader::device() const
751 {
752  return d->device;
753 }
754 
755 /*!
756  Returns true if the user can read the file; otherwise returns false.
757 */
758 bool MarbleZipReader::isReadable() const
759 {
760  return d->device->isReadable();
761 }
762 
763 /*!
764  Returns true if the file exists; otherwise returns false.
765 */
766 bool MarbleZipReader::exists() const
767 {
768  QFile *f = qobject_cast<QFile*> (d->device);
769  if (f == nullptr)
770  return true;
771  return f->exists();
772 }
773 
774 /*!
775  Returns the list of files the archive contains.
776 */
777 QList<MarbleZipReader::FileInfo> MarbleZipReader::fileInfoList() const
778 {
779  d->scanFiles();
781  for (int i = 0; i < d->fileHeaders.size(); ++i) {
782  MarbleZipReader::FileInfo fi;
783  d->fillFileInfo(i, fi);
784  files.append(fi);
785  }
786  return files;
787 
788 }
789 
790 /*!
791  Return the number of items in the zip archive.
792 */
793 int MarbleZipReader::count() const
794 {
795  d->scanFiles();
796  return d->fileHeaders.count();
797 }
798 
799 /*!
800  Returns a FileInfo of an entry in the zipfile.
801  The \a index is the index into the directory listing of the zipfile.
802  Returns an invalid FileInfo if \a index is out of boundaries.
803 
804  \sa fileInfoList()
805 */
806 MarbleZipReader::FileInfo MarbleZipReader::entryInfoAt(int index) const
807 {
808  d->scanFiles();
809  MarbleZipReader::FileInfo fi;
810  if (index >= 0 && index < d->fileHeaders.count())
811  d->fillFileInfo(index, fi);
812  return fi;
813 }
814 
815 /*!
816  Fetch the file contents from the zip archive and return the uncompressed bytes.
817 */
818 QByteArray MarbleZipReader::fileData(const QString &fileName) const
819 {
820  d->scanFiles();
821  int i;
822  for (i = 0; i < d->fileHeaders.size(); ++i) {
823  if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName)
824  break;
825  }
826  if (i == d->fileHeaders.size())
827  return QByteArray();
828 
829  FileHeader header = d->fileHeaders.at(i);
830 
831  int compressed_size = readUInt(header.h.compressed_size);
832  int uncompressed_size = readUInt(header.h.uncompressed_size);
833  int start = readUInt(header.h.offset_local_header);
834  //qDebug("uncompressing file %d: local header at %d", i, start);
835 
836  d->device->seek(start);
837  LocalFileHeader lh;
838  d->device->read((char *)&lh, sizeof(LocalFileHeader));
839  uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
840  d->device->seek(d->device->pos() + skip);
841 
842  int compression_method = readUShort(lh.compression_method);
843  //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
844 
845  //qDebug("file at %lld", d->device->pos());
846  QByteArray compressed = d->device->read(compressed_size);
847  if (compression_method == 0) {
848  // no compression
849  compressed.truncate(uncompressed_size);
850  return compressed;
851  } else if (compression_method == 8) {
852  // Deflate
853  //qDebug("compressed=%d", compressed.size());
854  compressed.truncate(compressed_size);
855  QByteArray baunzip;
856  ulong len = qMax(uncompressed_size, 1);
857  int res;
858  do {
859  baunzip.resize(len);
860  res = inflate((uchar*)baunzip.data(), &len,
861  (uchar*)compressed.constData(), compressed_size);
862 
863  switch (res) {
864  case Z_OK:
865  if ((int)len != baunzip.size())
866  baunzip.resize(len);
867  break;
868  case Z_MEM_ERROR:
869  qWarning("QZip: Z_MEM_ERROR: Not enough memory");
870  break;
871  case Z_BUF_ERROR:
872  len *= 2;
873  break;
874  case Z_DATA_ERROR:
875  qWarning("QZip: Z_DATA_ERROR: Input data is corrupted");
876  break;
877  }
878  } while (res == Z_BUF_ERROR);
879  return baunzip;
880  }
881  qWarning() << "QZip: Unknown compression method";
882  return QByteArray();
883 }
884 
885 /*!
886  Extracts the full contents of the zip file into \a destinationDir on
887  the local filesystem.
888  In case writing or linking a file fails, the extraction will be aborted.
889 */
890 bool MarbleZipReader::extractAll(const QString &destinationDir) const
891 {
892  QDir baseDir(destinationDir);
893 
894  // create directories first
895  QList<FileInfo> allFiles = fileInfoList();
896  for (const FileInfo& fi: allFiles) {
897  const QString absPath = destinationDir + QDir::separator() + fi.filePath;
898  if (fi.isDir) {
899  if (!baseDir.mkpath(fi.filePath))
900  return false;
901  if (!QFile::setPermissions(absPath, fi.permissions))
902  return false;
903  }
904  }
905 
906  // set up symlinks
907  for (const FileInfo& fi: allFiles) {
908  const QString absPath = destinationDir + QDir::separator() + fi.filePath;
909  if (fi.isSymLink) {
910  QString destination = QFile::decodeName(fileData(fi.filePath));
911  if (destination.isEmpty())
912  return false;
913  QFileInfo linkFi(absPath);
914  if (!QFile::exists(linkFi.absolutePath()))
915  QDir::root().mkpath(linkFi.absolutePath());
916  if (!QFile::link(destination, absPath))
917  return false;
918  /* cannot change permission of links
919  if (!QFile::setPermissions(absPath, fi.permissions))
920  return false;
921  */
922  }
923  }
924 
925  for (const FileInfo& fi: allFiles) {
926  const QString absPath = destinationDir + QDir::separator() + fi.filePath;
927  if (fi.isFile) {
928  QDir::root().mkpath(QFileInfo(absPath).dir().absolutePath());
929  QFile f(absPath);
930  if (!f.open(QIODevice::WriteOnly))
931  return false;
932  f.write(fileData(fi.filePath));
933  f.setPermissions(fi.permissions);
934  f.close();
935  }
936  }
937 
938  return true;
939 }
940 
941 /*!
942  \enum QZipReader::Status
943 
944  The following status values are possible:
945 
946  \value NoError No error occurred.
947  \value FileReadError An error occurred when reading from the file.
948  \value FileOpenError The file could not be opened.
949  \value FilePermissionsError The file could not be accessed.
950  \value FileError Another file error occurred.
951 */
952 
953 /*!
954  Returns a status code indicating the first error that was met by QZipReader,
955  or QZipReader::NoError if no error occurred.
956 */
957 MarbleZipReader::Status MarbleZipReader::status() const
958 {
959  return d->status;
960 }
961 
962 /*!
963  Close the zip file.
964 */
965 void MarbleZipReader::close()
966 {
967  d->device->close();
968 }
969 
970 ////////////////////////////// Writer
971 
972 /*!
973  \class QZipWriter
974  \internal
975  \since 4.5
976 
977  \brief the QZipWriter class provides a way to create a new zip archive.
978 
979  QZipWriter can be used to create a zip archive containing any number of files
980  and directories. The files in the archive will be compressed in a way that is
981  compatible with common zip reader applications.
982 */
983 
984 
985 /*!
986  Create a new zip archive that operates on the \a archive filename. The file will
987  be opened with the \a mode.
988  \sa isValid()
989 */
990 MarbleZipWriter::MarbleZipWriter(const QString &fileName, QIODevice::OpenMode mode)
991 {
992  QScopedPointer<QFile> f(new QFile(fileName));
993  f->open(mode);
994  MarbleZipWriter::Status status;
995  if (f->error() == QFile::NoError)
996  status = MarbleZipWriter::NoError;
997  else {
998  if (f->error() == QFile::WriteError)
999  status = MarbleZipWriter::FileWriteError;
1000  else if (f->error() == QFile::OpenError)
1001  status = MarbleZipWriter::FileOpenError;
1002  else if (f->error() == QFile::PermissionsError)
1003  status = MarbleZipWriter::FilePermissionsError;
1004  else
1005  status = MarbleZipWriter::FileError;
1006  }
1007 
1008  d = new MarbleZipWriterPrivate(f.data(), /*ownDevice=*/true);
1009  f.take();
1010  d->status = status;
1011 }
1012 
1013 /*!
1014  Create a new zip archive that operates on the archive found in \a device.
1015  You have to open the device previous to calling the constructor and
1016  only a device that is readable will be scanned for zip filecontent.
1017  */
1018 MarbleZipWriter::MarbleZipWriter(QIODevice *device)
1019  : d(new MarbleZipWriterPrivate(device, /*ownDevice=*/false))
1020 {
1021  Q_ASSERT(device);
1022 }
1023 
1024 MarbleZipWriter::~MarbleZipWriter()
1025 {
1026  close();
1027  delete d;
1028 }
1029 
1030 /*!
1031  Returns device used for writing zip archive.
1032 */
1033 QIODevice* MarbleZipWriter::device() const
1034 {
1035  return d->device;
1036 }
1037 
1038 /*!
1039  Returns true if the user can write to the archive; otherwise returns false.
1040 */
1041 bool MarbleZipWriter::isWritable() const
1042 {
1043  return d->device->isWritable();
1044 }
1045 
1046 /*!
1047  Returns true if the file exists; otherwise returns false.
1048 */
1049 bool MarbleZipWriter::exists() const
1050 {
1051  QFile *f = qobject_cast<QFile*> (d->device);
1052  if (f == nullptr)
1053  return true;
1054  return f->exists();
1055 }
1056 
1057 /*!
1058  \enum QZipWriter::Status
1059 
1060  The following status values are possible:
1061 
1062  \var NoError
1063  No error occurred.
1064  \var FileWriteError
1065  An error occurred when writing to the device.
1066  \var FileOpenError
1067  The file could not be opened.
1068  \var FilePermissionsError
1069  The file could not be accessed.
1070  \var FileError
1071  Another file error occurred.
1072 */
1073 
1074 /*!
1075  Returns a status code indicating the first error that was met by QZipWriter,
1076  or QZipWriter::NoError if no error occurred.
1077 */
1078 MarbleZipWriter::Status MarbleZipWriter::status() const
1079 {
1080  return d->status;
1081 }
1082 
1083 /*!
1084  \enum CompressionPolicy
1085 
1086  \var AlwaysCompress
1087  A file that is added is compressed.
1088  \var NeverCompress
1089  A file that is added will be stored without changes.
1090  \var AutoCompress
1091  A file that is added will be compressed only if that will give a smaller file.
1092 */
1093 
1094 /*!
1095  Sets the policy for compressing newly added files to the new \a policy.
1096 
1097  \note the default policy is AlwaysCompress
1098 
1099  \sa compressionPolicy()
1100  \sa addFile()
1101 */
1102 void MarbleZipWriter::setCompressionPolicy(CompressionPolicy policy)
1103 {
1104  d->compressionPolicy = policy;
1105 }
1106 
1107 /*!
1108  Returns the currently set compression policy.
1109  \sa setCompressionPolicy()
1110  \sa addFile()
1111 */
1112 MarbleZipWriter::CompressionPolicy MarbleZipWriter::compressionPolicy() const
1113 {
1114  return d->compressionPolicy;
1115 }
1116 
1117 /*!
1118  Sets the permissions that will be used for newly added files.
1119 
1120  \note the default permissions are QFile::ReadOwner | QFile::WriteOwner.
1121 
1122  \sa creationPermissions()
1123  \sa addFile()
1124 */
1125 void MarbleZipWriter::setCreationPermissions(QFile::Permissions permissions)
1126 {
1127  d->permissions = permissions;
1128 }
1129 
1130 /*!
1131  Returns the currently set creation permissions.
1132 
1133  \sa setCreationPermissions()
1134  \sa addFile()
1135 */
1136 QFile::Permissions MarbleZipWriter::creationPermissions() const
1137 {
1138  return d->permissions;
1139 }
1140 
1141 /*!
1142  Add a file to the archive with \a data as the file contents.
1143  The file will be stored in the archive using the \a fileName which
1144  includes the full path in the archive.
1145 
1146  The new file will get the file permissions based on the current
1147  creationPermissions and it will be compressed using the zip compression
1148  based on the current compression policy.
1149 
1150  \sa setCreationPermissions()
1151  \sa setCompressionPolicy()
1152 */
1153 void MarbleZipWriter::addFile(const QString &fileName, const QByteArray &data)
1154 {
1155  d->addEntry(MarbleZipWriterPrivate::File, QDir::fromNativeSeparators(fileName), data);
1156 }
1157 
1158 /*!
1159  Add a file to the archive with \a device as the source of the contents.
1160  The contents returned from QIODevice::readAll() will be used as the
1161  filedata.
1162  The file will be stored in the archive using the \a fileName which
1163  includes the full path in the archive.
1164 */
1165 void MarbleZipWriter::addFile(const QString &fileName, QIODevice *device)
1166 {
1167  Q_ASSERT(device);
1168  QIODevice::OpenMode mode = device->openMode();
1169  bool opened = false;
1170  if ((mode & QIODevice::ReadOnly) == 0) {
1171  opened = true;
1172  if (! device->open(QIODevice::ReadOnly)) {
1173  d->status = FileOpenError;
1174  return;
1175  }
1176  }
1177  d->addEntry(MarbleZipWriterPrivate::File, QDir::fromNativeSeparators(fileName), device->readAll());
1178  if (opened)
1179  device->close();
1180 }
1181 
1182 /*!
1183  Create a new directory in the archive with the specified \a dirName and
1184  the \a permissions;
1185 */
1186 void MarbleZipWriter::addDirectory(const QString &dirName)
1187 {
1189  // separator is mandatory
1190  if (!name.endsWith(QLatin1Char('/')))
1191  name.append(QLatin1Char('/'));
1192  d->addEntry(MarbleZipWriterPrivate::Directory, name, QByteArray());
1193 }
1194 
1195 /*!
1196  Create a new symbolic link in the archive with the specified \a dirName
1197  and the \a permissions;
1198  A symbolic link contains the destination (relative) path and name.
1199 */
1200 void MarbleZipWriter::addSymLink(const QString &fileName, const QString &destination)
1201 {
1202  d->addEntry(MarbleZipWriterPrivate::Symlink, QDir::fromNativeSeparators(fileName), QFile::encodeName(destination));
1203 }
1204 
1205 /*!
1206  Closes the zip file.
1207 */
1208 void MarbleZipWriter::close()
1209 {
1210  if (!(d->device->openMode() & QIODevice::WriteOnly)) {
1211  d->device->close();
1212  return;
1213  }
1214 
1215  //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size());
1216  d->device->seek(d->start_of_directory);
1217  // write new directory
1218  for (int i = 0; i < d->fileHeaders.size(); ++i) {
1219  const FileHeader &header = d->fileHeaders.at(i);
1220  d->device->write((const char *)&header.h, sizeof(CentralFileHeader));
1221  d->device->write(header.file_name);
1222  d->device->write(header.extra_field);
1223  d->device->write(header.file_comment);
1224  }
1225  int dir_size = d->device->pos() - d->start_of_directory;
1226  // write end of directory
1227  EndOfDirectory eod;
1228  memset(&eod, 0, sizeof(EndOfDirectory));
1229  writeUInt(eod.signature, 0x06054b50);
1230  //uchar this_disk[2];
1231  //uchar start_of_directory_disk[2];
1232  writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size());
1233  writeUShort(eod.num_dir_entries, d->fileHeaders.size());
1234  writeUInt(eod.directory_size, dir_size);
1235  writeUInt(eod.dir_start_offset, d->start_of_directory);
1236  writeUShort(eod.comment_length, d->comment.length());
1237 
1238  d->device->write((const char *)&eod, sizeof(EndOfDirectory));
1239  d->device->write(d->comment);
1240  d->device->close();
1241 }
1242 
1243 }
1244 
1245 #endif // QT_NO_TEXTODFWRITER
void append(const T &value)
int month() const const
bool isWritable() const const
Type type(const QSqlDatabase &db)
virtual bool open(QIODevice::OpenMode mode) override
QDateTime currentDateTime()
int count(const T &value) const const
QChar separator()
QByteArray encodeName(const QString &fileName)
QTime time() const const
int year() const const
QDir root()
virtual bool setPermissions(QFileDevice::Permissions permissions) override
virtual bool open(QIODevice::OpenMode mode)
bool exists() const const
bool isReadable() const const
char at(int i) const const
Q_SCRIPTABLE Q_NOREPLY void start()
QString fromLocal8Bit(const char *str, int size)
bool isEmpty() const const
QByteArray toUtf8() const const
bool mkpath(const QString &dirPath) const const
Q_SCRIPTABLE CaptureState status()
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
QString fromNativeSeparators(const QString &pathName)
typedef Permissions
Binds a QML item to a specific geodetic location in screen coordinates.
virtual void close() override
QIODevice::OpenMode openMode() const const
int hour() const const
void resize(int size)
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
const char * constData() const const
int second() const const
QFileDevice::FileError error() const const
const char * name(StandardAction id)
QDate date() const const
bool isValid() const const
QVariant read(const QByteArray &data, int versionOverride=0)
int size() const const
QByteArray readAll()
int length() const const
QByteArray toLocal8Bit() const const
virtual void close()
void truncate(int pos)
int minute() const const
char * data()
qint64 write(const char *data, qint64 maxSize)
QString decodeName(const QByteArray &localFileName)
int day() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Sep 21 2023 04:12:27 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.