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

source code of qtbase/src/corelib/io/qzip.cpp