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
59namespace Marble {
60
61static inline uint readUInt(const uchar *data)
62{
63 return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
64}
65
66static inline ushort readUShort(const uchar *data)
67{
68 return (data[0]) + (data[1]<<8);
69}
70
71static 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
79static inline void writeUShort(uchar *data, ushort i)
80{
81 data[0] = i & 0xff;
82 data[1] = (i>>8) & 0xff;
83}
84
85static 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
93static inline void copyUShort(uchar *dest, const uchar *src)
94{
95 dest[0] = src[0];
96 dest[1] = src[1];
97}
98
99static 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
125static 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
155static 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
190static 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
219static QFile::Permissions modeToPermissions(quint32 mode)
220{
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
249static 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
264struct 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
278struct DataDescriptor
279{
280 uchar crc_32[4];
281 uchar compressed_size[4];
282 uchar uncompressed_size[4];
283};
284
285struct 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
306struct 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
318struct FileHeader
319{
320 CentralFileHeader h;
321 QByteArray file_name;
322 QByteArray extra_field;
323 QByteArray file_comment;
324};
325
326MarbleZipReader::FileInfo::FileInfo()
327 : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0)
328{
329}
330
331MarbleZipReader::FileInfo::~FileInfo()
332{
333}
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), 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
382void 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
403class MarbleZipReaderPrivate : public QZipPrivate
404{
405public:
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
416class MarbleZipWriterPrivate : public QZipPrivate
417{
418public:
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
436LocalFileHeader 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
452void 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
541void 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*/
704MarbleZipReader::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 */
732MarbleZipReader::MarbleZipReader(QIODevice *device)
733 : d(new MarbleZipReaderPrivate(device, /*ownDevice=*/false))
734{
735 Q_ASSERT(device);
736}
737
738/*!
739 Desctructor
740*/
741MarbleZipReader::~MarbleZipReader()
742{
743 close();
744 delete d;
745}
746
747/*!
748 Returns device used for reading zip archive.
749*/
750QIODevice* 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*/
758bool MarbleZipReader::isReadable() const
759{
760 return d->device->isReadable();
761}
762
763/*!
764 Returns true if the file exists; otherwise returns false.
765*/
766bool 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*/
777QList<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*/
793int 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*/
806MarbleZipReader::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*/
818QByteArray 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*/
890bool 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);
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*/
957MarbleZipReader::Status MarbleZipReader::status() const
958{
959 return d->status;
960}
961
962/*!
963 Close the zip file.
964*/
965void 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*/
990MarbleZipWriter::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 */
1018MarbleZipWriter::MarbleZipWriter(QIODevice *device)
1019 : d(new MarbleZipWriterPrivate(device, /*ownDevice=*/false))
1020{
1021 Q_ASSERT(device);
1022}
1023
1024MarbleZipWriter::~MarbleZipWriter()
1025{
1026 close();
1027 delete d;
1028}
1029
1030/*!
1031 Returns device used for writing zip archive.
1032*/
1033QIODevice* 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*/
1041bool MarbleZipWriter::isWritable() const
1042{
1043 return d->device->isWritable();
1044}
1045
1046/*!
1047 Returns true if the file exists; otherwise returns false.
1048*/
1049bool 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*/
1078MarbleZipWriter::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*/
1102void 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*/
1112MarbleZipWriter::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*/
1125void 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*/
1136QFile::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*/
1153void 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*/
1165void 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*/
1186void 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*/
1200void 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*/
1208void 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
Q_SCRIPTABLE Q_NOREPLY void start()
Q_SCRIPTABLE CaptureState status()
QVariant read(const QByteArray &data, int versionOverride=0)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
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)
int day() const const
int month() const const
int year() const const
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 QString &fileName)
bool exists() const const
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual bool setPermissions(Permissions permissions) override
typedef Permissions
virtual void close() override
FileError error() const const
virtual void close()
bool isReadable() const const
bool isWritable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QIODeviceBase::OpenMode openMode() const const
QByteArray readAll()
qint64 write(const QByteArray &data)
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
int hour() const const
int minute() const const
int second() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:54:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.