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

KDE's Doxygen guidelines are available online.