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(strm: &stream, Z_FINISH);
152 if (err != Z_STREAM_END) {
153 inflateEnd(strm: &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(strm: &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(strm: &stream, Z_FINISH);
183 if (err != Z_STREAM_END) {
184 deflateEnd(strm: &stream);
185 return err == Z_OK ? Z_BUF_ERROR : err;
186 }
187 *destLen = stream.total_out;
188
189 err = deflateEnd(strm: &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(data: 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 QVector<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(i: index);
447 quint32 mode = readUInt(data: header.h.external_file_attributes);
448 const HostOS hostOS = HostOS(readUShort(data: 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(msg: "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(data: 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(str: header.file_name) : QString::fromLocal8Bit(str: header.file_name);
494 fileInfo.crc = readUInt(data: header.h.crc_32);
495 fileInfo.size = readUInt(data: header.h.uncompressed_size);
496 fileInfo.lastModified = readMSDosDate(src: 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(pathName: fileInfo.filePath);
500 QStringRef filePathRef(&fileInfo.filePath);
501 while (filePathRef.startsWith(c: QLatin1Char('.')) || filePathRef.startsWith(c: QLatin1Char('/')))
502 filePathRef = filePathRef.mid(pos: 1);
503 while (filePathRef.endsWith(c: QLatin1Char('/')))
504 filePathRef.chop(n: 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(data: h.signature, i: 0x04034b50);
547 copyUShort(dest: h.version_needed, src: ch.version_needed);
548 copyUShort(dest: h.general_purpose_bits, src: ch.general_purpose_bits);
549 copyUShort(dest: h.compression_method, src: ch.compression_method);
550 copyUInt(dest: h.last_mod_file, src: ch.last_mod_file);
551 copyUInt(dest: h.crc_32, src: ch.crc_32);
552 copyUInt(dest: h.compressed_size, src: ch.compressed_size);
553 copyUInt(dest: h.uncompressed_size, src: ch.uncompressed_size);
554 copyUShort(dest: h.file_name_length, src: ch.file_name_length);
555 copyUShort(dest: h.extra_field_length, src: 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(mode: 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(data: (char *)tmp, maxlen: 4);
577 if (readUInt(data: tmp) != 0x04034b50) {
578 qWarning(msg: "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(msg: "QZip: EndOfDirectory not found");
591 return;
592 }
593
594 device->seek(pos);
595 device->read(data: (char *)&eod, maxlen: sizeof(EndOfDirectory));
596 if (readUInt(data: eod.signature) == 0x06054b50)
597 break;
598 ++i;
599 }
600
601 // have the eod
602 start_of_directory = readUInt(data: eod.dir_start_offset);
603 num_dir_entries = readUShort(data: eod.num_dir_entries);
604 ZDEBUG(msg: "start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
605 int comment_length = readUShort(data: eod.comment_length);
606 if (comment_length != i)
607 qWarning(msg: "QZip: failed to parse zip file.");
608 comment = device->read(maxlen: qMin(a: comment_length, b: i));
609
610
611 device->seek(pos: start_of_directory);
612 for (i = 0; i < num_dir_entries; ++i) {
613 FileHeader header;
614 int read = device->read(data: (char *) &header.h, maxlen: sizeof(CentralFileHeader));
615 if (read < (int)sizeof(CentralFileHeader)) {
616 qWarning(msg: "QZip: Failed to read complete header, index may be incomplete");
617 break;
618 }
619 if (readUInt(data: header.h.signature) != 0x02014b50) {
620 qWarning(msg: "QZip: invalid header signature, index may be incomplete");
621 break;
622 }
623
624 int l = readUShort(data: header.h.file_name_length);
625 header.file_name = device->read(maxlen: l);
626 if (header.file_name.length() != l) {
627 qWarning(msg: "QZip: Failed to read filename from zip index, index may be incomplete");
628 break;
629 }
630 l = readUShort(data: header.h.extra_field_length);
631 header.extra_field = device->read(maxlen: l);
632 if (header.extra_field.length() != l) {
633 qWarning(msg: "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete");
634 break;
635 }
636 l = readUShort(data: header.h.file_comment_length);
637 header.file_comment = device->read(maxlen: l);
638 if (header.file_comment.length() != l) {
639 qWarning(msg: "QZip: Failed to read read file comment, index may be incomplete");
640 break;
641 }
642
643 ZDEBUG(msg: "found file '%s'", header.file_name.data());
644 fileHeaders.append(t: 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(mode: QIODevice::WriteOnly))) {
659 status = QZipWriter::FileOpenError;
660 return;
661 }
662 device->seek(pos: 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(s: &header.h, c: 0, n: sizeof(CentralFileHeader));
675 writeUInt(data: header.h.signature, i: 0x02014b50);
676
677 writeUShort(data: header.h.version_needed, ZIP_VERSION);
678 writeUInt(data: header.h.uncompressed_size, i: contents.length());
679 writeMSDosDate(dest: header.h.last_mod_file, dt: QDateTime::currentDateTime());
680 QByteArray data = contents;
681 if (compression == QZipWriter::AlwaysCompress) {
682 writeUShort(data: header.h.compression_method, i: 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(size: len);
690 res = deflate(dest: (uchar*)data.data(), destLen: &len, source: (const uchar*)contents.constData(), sourceLen: contents.length());
691
692 switch (res) {
693 case Z_OK:
694 data.resize(size: len);
695 break;
696 case Z_MEM_ERROR:
697 qWarning(msg: "QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
698 data.resize(size: 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(data: header.h.compressed_size, i: data.length());
708 uint crc_32 = ::crc32(crc: 0, buf: nullptr, len: 0);
709 crc_32 = ::crc32(crc: crc_32, buf: (const uchar *)contents.constData(), len: contents.length());
710 writeUInt(data: header.h.crc_32, i: 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(data: header.h.general_purpose_bits, i: 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(msg: "QZip: Filename is too long, chopping it to 65535 bytes");
720 header.file_name = header.file_name.left(len: 0xffff); // ### don't break the utf-8 sequence, if any
721 }
722 if (header.file_comment.size() + header.file_name.size() > 0xffff) {
723 qWarning(msg: "QZip: File comment is too long, chopping it to 65535 bytes");
724 header.file_comment.truncate(pos: 0xffff - header.file_name.size()); // ### don't break the utf-8 sequence, if any
725 }
726 writeUShort(data: header.h.file_name_length, i: header.file_name.length());
727 //h.extra_field_length[2];
728
729 writeUShort(data: header.h.version_made, i: HostUnix << 8);
730 //uchar internal_file_attributes[2];
731 //uchar external_file_attributes[4];
732 quint32 mode = permissionsToMode(perms: 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(data: header.h.external_file_attributes, i: mode << 16);
748 writeUInt(data: header.h.offset_local_header, i: start_of_directory);
749
750
751 fileHeaders.append(t: header);
752
753 LocalFileHeader h = toLocalHeader(ch: header.h);
754 device->write(data: (const char *)&h, len: sizeof(LocalFileHeader));
755 device->write(data: 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(flags: 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*> (object: 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*/
897QVector<QZipReader::FileInfo> QZipReader::fileInfoList() const
898{
899 d->scanFiles();
900 QVector<FileInfo> files;
901 const int numFileHeaders = d->fileHeaders.size();
902 files.reserve(asize: numFileHeaders);
903 for (int i = 0; i < numFileHeaders; ++i)
904 files.append(t: d->fillFileInfo(index: 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(str: 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(data: header.h.version_needed);
950 if (version_needed > ZIP_VERSION) {
951 qWarning(msg: "QZip: .ZIP specification version %d implementationis needed to extract the data.", version_needed);
952 return QByteArray();
953 }
954
955 ushort general_purpose_bits = readUShort(data: header.h.general_purpose_bits);
956 int compressed_size = readUInt(data: header.h.compressed_size);
957 int uncompressed_size = readUInt(data: header.h.uncompressed_size);
958 int start = readUInt(data: header.h.offset_local_header);
959 //qDebug("uncompressing file %d: local header at %d", i, start);
960
961 d->device->seek(pos: start);
962 LocalFileHeader lh;
963 d->device->read(data: (char *)&lh, maxlen: sizeof(LocalFileHeader));
964 uint skip = readUShort(data: lh.file_name_length) + readUShort(data: lh.extra_field_length);
965 d->device->seek(pos: d->device->pos() + skip);
966
967 int compression_method = readUShort(data: 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(msg: "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(maxlen: compressed_size);
977 if (compression_method == CompressionMethodStored) {
978 // no compression
979 compressed.truncate(pos: uncompressed_size);
980 return compressed;
981 } else if (compression_method == CompressionMethodDeflated) {
982 // Deflate
983 //qDebug("compressed=%d", compressed.size());
984 compressed.truncate(pos: compressed_size);
985 QByteArray baunzip;
986 ulong len = qMax(a: uncompressed_size, b: 1);
987 int res;
988 do {
989 baunzip.resize(size: len);
990 res = inflate(dest: (uchar*)baunzip.data(), destLen: &len,
991 source: (const uchar*)compressed.constData(), sourceLen: compressed_size);
992
993 switch (res) {
994 case Z_OK:
995 if ((int)len != baunzip.size())
996 baunzip.resize(size: len);
997 break;
998 case Z_MEM_ERROR:
999 qWarning(msg: "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(msg: "QZip: Z_DATA_ERROR: Input data is corrupted");
1006 break;
1007 }
1008 } while (res == Z_BUF_ERROR);
1009 return baunzip;
1010 }
1011
1012 qWarning(msg: "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 QVector<FileInfo> allFiles = fileInfoList();
1027 bool foundDirs = false;
1028 bool hasDirs = false;
1029 for (const FileInfo &fi : allFiles) {
1030 const QString absPath = destinationDir + QDir::separator() + fi.filePath;
1031 if (fi.isDir) {
1032 foundDirs = true;
1033 if (!baseDir.mkpath(dirPath: fi.filePath))
1034 return false;
1035 if (!QFile::setPermissions(filename: absPath, permissionSpec: fi.permissions))
1036 return false;
1037 } else if (!hasDirs && fi.filePath.contains(s: u"/")) {
1038 // filePath does not have leading or trailing '/', so if we find
1039 // one, than the file path contains directories.
1040 hasDirs = true;
1041 }
1042 }
1043
1044 // Some zip archives can be broken in the sense that they do not report
1045 // separate entries for directories, only for files. In this case we
1046 // need to recreate directory structure based on the file paths.
1047 if (hasDirs && !foundDirs) {
1048 for (const FileInfo &fi : allFiles) {
1049 const auto dirPath = fi.filePath.left(n: fi.filePath.lastIndexOf(s: u"/"));
1050 if (!baseDir.mkpath(dirPath))
1051 return false;
1052 // We will leave the directory permissions default in this case,
1053 // because setting dir permissions based on file is incorrect
1054 }
1055 }
1056
1057 // set up symlinks
1058 for (const FileInfo &fi : allFiles) {
1059 const QString absPath = destinationDir + QDir::separator() + fi.filePath;
1060 if (fi.isSymLink) {
1061 QString destination = QFile::decodeName(localFileName: fileData(fileName: fi.filePath));
1062 if (destination.isEmpty())
1063 return false;
1064 QFileInfo linkFi(absPath);
1065 if (!QFile::exists(fileName: linkFi.absolutePath()))
1066 QDir::root().mkpath(dirPath: linkFi.absolutePath());
1067 if (!QFile::link(oldname: destination, newName: absPath))
1068 return false;
1069 /* cannot change permission of links
1070 if (!QFile::setPermissions(absPath, fi.permissions))
1071 return false;
1072 */
1073 }
1074 }
1075
1076 for (const FileInfo &fi : allFiles) {
1077 const QString absPath = destinationDir + QDir::separator() + fi.filePath;
1078 if (fi.isFile) {
1079 QFile f(absPath);
1080 if (!f.open(flags: QIODevice::WriteOnly))
1081 return false;
1082 f.write(data: fileData(fileName: fi.filePath));
1083 f.setPermissions(fi.permissions);
1084 f.close();
1085 }
1086 }
1087
1088 return true;
1089}
1090
1091/*!
1092 \enum QZipReader::Status
1093
1094 The following status values are possible:
1095
1096 \value NoError No error occurred.
1097 \value FileReadError An error occurred when reading from the file.
1098 \value FileOpenError The file could not be opened.
1099 \value FilePermissionsError The file could not be accessed.
1100 \value FileError Another file error occurred.
1101*/
1102
1103/*!
1104 Returns a status code indicating the first error that was met by QZipReader,
1105 or QZipReader::NoError if no error occurred.
1106*/
1107QZipReader::Status QZipReader::status() const
1108{
1109 return d->status;
1110}
1111
1112/*!
1113 Close the zip file.
1114*/
1115void QZipReader::close()
1116{
1117 d->device->close();
1118}
1119
1120////////////////////////////// Writer
1121
1122/*!
1123 \class QZipWriter
1124 \internal
1125 \since 4.5
1126
1127 \brief the QZipWriter class provides a way to create a new zip archive.
1128
1129 QZipWriter can be used to create a zip archive containing any number of files
1130 and directories. The files in the archive will be compressed in a way that is
1131 compatible with common zip reader applications.
1132*/
1133
1134
1135/*!
1136 Create a new zip archive that operates on the \a archive filename. The file will
1137 be opened with the \a mode.
1138 \sa isValid()
1139*/
1140QZipWriter::QZipWriter(const QString &fileName, QIODevice::OpenMode mode)
1141{
1142 QScopedPointer<QFile> f(new QFile(fileName));
1143 QZipWriter::Status status;
1144 if (f->open(flags: mode) && f->error() == QFile::NoError)
1145 status = QZipWriter::NoError;
1146 else {
1147 if (f->error() == QFile::WriteError)
1148 status = QZipWriter::FileWriteError;
1149 else if (f->error() == QFile::OpenError)
1150 status = QZipWriter::FileOpenError;
1151 else if (f->error() == QFile::PermissionsError)
1152 status = QZipWriter::FilePermissionsError;
1153 else
1154 status = QZipWriter::FileError;
1155 }
1156
1157 d = new QZipWriterPrivate(f.data(), /*ownDevice=*/true);
1158 f.take();
1159 d->status = status;
1160}
1161
1162/*!
1163 Create a new zip archive that operates on the archive found in \a device.
1164 You have to open the device previous to calling the constructor and
1165 only a device that is readable will be scanned for zip filecontent.
1166 */
1167QZipWriter::QZipWriter(QIODevice *device)
1168 : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
1169{
1170 Q_ASSERT(device);
1171}
1172
1173QZipWriter::~QZipWriter()
1174{
1175 close();
1176 delete d;
1177}
1178
1179/*!
1180 Returns device used for writing zip archive.
1181*/
1182QIODevice* QZipWriter::device() const
1183{
1184 return d->device;
1185}
1186
1187/*!
1188 Returns \c true if the user can write to the archive; otherwise returns \c false.
1189*/
1190bool QZipWriter::isWritable() const
1191{
1192 return d->device->isWritable();
1193}
1194
1195/*!
1196 Returns \c true if the file exists; otherwise returns \c false.
1197*/
1198bool QZipWriter::exists() const
1199{
1200 QFile *f = qobject_cast<QFile*> (object: d->device);
1201 if (f == nullptr)
1202 return true;
1203 return f->exists();
1204}
1205
1206/*!
1207 \enum QZipWriter::Status
1208
1209 The following status values are possible:
1210
1211 \value NoError No error occurred.
1212 \value FileWriteError An error occurred when writing to the device.
1213 \value FileOpenError The file could not be opened.
1214 \value FilePermissionsError The file could not be accessed.
1215 \value FileError Another file error occurred.
1216*/
1217
1218/*!
1219 Returns a status code indicating the first error that was met by QZipWriter,
1220 or QZipWriter::NoError if no error occurred.
1221*/
1222QZipWriter::Status QZipWriter::status() const
1223{
1224 return d->status;
1225}
1226
1227/*!
1228 \enum QZipWriter::CompressionPolicy
1229
1230 \value AlwaysCompress A file that is added is compressed.
1231 \value NeverCompress A file that is added will be stored without changes.
1232 \value AutoCompress A file that is added will be compressed only if that will give a smaller file.
1233*/
1234
1235/*!
1236 Sets the policy for compressing newly added files to the new \a policy.
1237
1238 \note the default policy is AlwaysCompress
1239
1240 \sa compressionPolicy()
1241 \sa addFile()
1242*/
1243void QZipWriter::setCompressionPolicy(CompressionPolicy policy)
1244{
1245 d->compressionPolicy = policy;
1246}
1247
1248/*!
1249 Returns the currently set compression policy.
1250 \sa setCompressionPolicy()
1251 \sa addFile()
1252*/
1253QZipWriter::CompressionPolicy QZipWriter::compressionPolicy() const
1254{
1255 return d->compressionPolicy;
1256}
1257
1258/*!
1259 Sets the permissions that will be used for newly added files.
1260
1261 \note the default permissions are QFile::ReadOwner | QFile::WriteOwner.
1262
1263 \sa creationPermissions()
1264 \sa addFile()
1265*/
1266void QZipWriter::setCreationPermissions(QFile::Permissions permissions)
1267{
1268 d->permissions = permissions;
1269}
1270
1271/*!
1272 Returns the currently set creation permissions.
1273
1274 \sa setCreationPermissions()
1275 \sa addFile()
1276*/
1277QFile::Permissions QZipWriter::creationPermissions() const
1278{
1279 return d->permissions;
1280}
1281
1282/*!
1283 Add a file to the archive with \a data as the file contents.
1284 The file will be stored in the archive using the \a fileName which
1285 includes the full path in the archive.
1286
1287 The new file will get the file permissions based on the current
1288 creationPermissions and it will be compressed using the zip compression
1289 based on the current compression policy.
1290
1291 \sa setCreationPermissions()
1292 \sa setCompressionPolicy()
1293*/
1294void QZipWriter::addFile(const QString &fileName, const QByteArray &data)
1295{
1296 d->addEntry(type: QZipWriterPrivate::File, fileName: QDir::fromNativeSeparators(pathName: fileName), contents: data);
1297}
1298
1299/*!
1300 Add a file to the archive with \a device as the source of the contents.
1301 The contents returned from QIODevice::readAll() will be used as the
1302 filedata.
1303 The file will be stored in the archive using the \a fileName which
1304 includes the full path in the archive.
1305*/
1306void QZipWriter::addFile(const QString &fileName, QIODevice *device)
1307{
1308 Q_ASSERT(device);
1309 QIODevice::OpenMode mode = device->openMode();
1310 bool opened = false;
1311 if ((mode & QIODevice::ReadOnly) == 0) {
1312 opened = true;
1313 if (! device->open(mode: QIODevice::ReadOnly)) {
1314 d->status = FileOpenError;
1315 return;
1316 }
1317 }
1318 d->addEntry(type: QZipWriterPrivate::File, fileName: QDir::fromNativeSeparators(pathName: fileName), contents: device->readAll());
1319 if (opened)
1320 device->close();
1321}
1322
1323/*!
1324 Create a new directory in the archive with the specified \a dirName and
1325 the \a permissions;
1326*/
1327void QZipWriter::addDirectory(const QString &dirName)
1328{
1329 QString name(QDir::fromNativeSeparators(pathName: dirName));
1330 // separator is mandatory
1331 if (!name.endsWith(c: QLatin1Char('/')))
1332 name.append(c: QLatin1Char('/'));
1333 d->addEntry(type: QZipWriterPrivate::Directory, fileName: name, contents: QByteArray());
1334}
1335
1336/*!
1337 Create a new symbolic link in the archive with the specified \a dirName
1338 and the \a permissions;
1339 A symbolic link contains the destination (relative) path and name.
1340*/
1341void QZipWriter::addSymLink(const QString &fileName, const QString &destination)
1342{
1343 d->addEntry(type: QZipWriterPrivate::Symlink, fileName: QDir::fromNativeSeparators(pathName: fileName), contents: QFile::encodeName(fileName: destination));
1344}
1345
1346/*!
1347 Closes the zip file.
1348*/
1349void QZipWriter::close()
1350{
1351 if (!(d->device->openMode() & QIODevice::WriteOnly)) {
1352 d->device->close();
1353 return;
1354 }
1355
1356 //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size());
1357 d->device->seek(pos: d->start_of_directory);
1358 // write new directory
1359 for (int i = 0; i < d->fileHeaders.size(); ++i) {
1360 const FileHeader &header = d->fileHeaders.at(i);
1361 d->device->write(data: (const char *)&header.h, len: sizeof(CentralFileHeader));
1362 d->device->write(data: header.file_name);
1363 d->device->write(data: header.extra_field);
1364 d->device->write(data: header.file_comment);
1365 }
1366 int dir_size = d->device->pos() - d->start_of_directory;
1367 // write end of directory
1368 EndOfDirectory eod;
1369 memset(s: &eod, c: 0, n: sizeof(EndOfDirectory));
1370 writeUInt(data: eod.signature, i: 0x06054b50);
1371 //uchar this_disk[2];
1372 //uchar start_of_directory_disk[2];
1373 writeUShort(data: eod.num_dir_entries_this_disk, i: d->fileHeaders.size());
1374 writeUShort(data: eod.num_dir_entries, i: d->fileHeaders.size());
1375 writeUInt(data: eod.directory_size, i: dir_size);
1376 writeUInt(data: eod.dir_start_offset, i: d->start_of_directory);
1377 writeUShort(data: eod.comment_length, i: d->comment.length());
1378
1379 d->device->write(data: (const char *)&eod, len: sizeof(EndOfDirectory));
1380 d->device->write(data: d->comment);
1381 d->device->close();
1382}
1383
1384QT_END_NAMESPACE
1385
1386#endif // QT_NO_TEXTODFWRITER
1387

source code of qtbase/src/gui/text/qzip.cpp