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