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 
63 QT_BEGIN_NAMESPACE
64 
readUInt(const uchar * data)65 static inline uint readUInt(const uchar *data)
66 {
67     return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
68 }
69 
readUShort(const uchar * data)70 static inline ushort readUShort(const uchar *data)
71 {
72     return (data[0]) + (data[1]<<8);
73 }
74 
writeUInt(uchar * data,uint i)75 static 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 
writeUShort(uchar * data,ushort i)83 static inline void writeUShort(uchar *data, ushort i)
84 {
85     data[0] = i & 0xff;
86     data[1] = (i>>8) & 0xff;
87 }
88 
copyUInt(uchar * dest,const uchar * src)89 static 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 
copyUShort(uchar * dest,const uchar * src)97 static inline void copyUShort(uchar *dest, const uchar *src)
98 {
99     dest[0] = src[0];
100     dest[1] = src[1];
101 }
102 
writeMSDosDate(uchar * dest,const QDateTime & dt)103 static 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 
inflate(Bytef * dest,ulong * destLen,const Bytef * source,ulong sourceLen)129 static 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 
deflate(Bytef * dest,ulong * destLen,const Bytef * source,ulong sourceLen)164 static 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 
194 namespace WindowsFileAttributes {
195 enum {
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 
205 namespace UnixFileAttributes {
206 enum {
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 
modeToPermissions(quint32 mode)225 static 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 
permissionsToMode(QFile::Permissions perms)249 static 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 
readMSDosDate(const uchar * src)273 static 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 
290 enum 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 };
312 Q_DECLARE_TYPEINFO(HostOS, Q_PRIMITIVE_TYPE);
313 
314 enum 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 };
324 Q_DECLARE_TYPEINFO(GeneralPurposeFlag, Q_PRIMITIVE_TYPE);
325 
326 enum 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 };
351 Q_DECLARE_TYPEINFO(CompressionMethod, Q_PRIMITIVE_TYPE);
352 
353 struct 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 };
366 Q_DECLARE_TYPEINFO(LocalFileHeader, Q_PRIMITIVE_TYPE);
367 
368 struct DataDescriptor
369 {
370     uchar crc_32[4];
371     uchar compressed_size[4];
372     uchar uncompressed_size[4];
373 };
374 Q_DECLARE_TYPEINFO(DataDescriptor, Q_PRIMITIVE_TYPE);
375 
376 struct 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 };
395 Q_DECLARE_TYPEINFO(CentralFileHeader, Q_PRIMITIVE_TYPE);
396 
397 struct 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 };
408 Q_DECLARE_TYPEINFO(EndOfDirectory, Q_PRIMITIVE_TYPE);
409 
410 struct FileHeader
411 {
412     CentralFileHeader h;
413     QByteArray file_name;
414     QByteArray extra_field;
415     QByteArray file_comment;
416 };
417 Q_DECLARE_TYPEINFO(FileHeader, Q_MOVABLE_TYPE);
418 
419 class QZipPrivate
420 {
421 public:
QZipPrivate(QIODevice * device,bool ownDev)422     QZipPrivate(QIODevice *device, bool ownDev)
423         : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0)
424     {
425     }
426 
~QZipPrivate()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 
fillFileInfo(int index) const443 QZipReader::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     QStringRef 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 
510 class QZipReaderPrivate : public QZipPrivate
511 {
512 public:
QZipReaderPrivate(QIODevice * device,bool ownDev)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 
523 class QZipWriterPrivate : public QZipPrivate
524 {
525 public:
QZipWriterPrivate(QIODevice * device,bool ownDev)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 
toLocalHeader(const CentralFileHeader & ch)543 static 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 
scanFiles()559 void 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 
addEntry(EntryType type,const QString & fileName,const QByteArray & contents)648 void 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 */
QZipReader(const QString & archive,QIODevice::OpenMode mode)823 QZipReader::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  */
QZipReader(QIODevice * device)852 QZipReader::QZipReader(QIODevice *device)
853     : d(new QZipReaderPrivate(device, /*ownDevice=*/false))
854 {
855     Q_ASSERT(device);
856 }
857 
858 /*!
859     Desctructor
860 */
~QZipReader()861 QZipReader::~QZipReader()
862 {
863     close();
864     delete d;
865 }
866 
867 /*!
868     Returns device used for reading zip archive.
869 */
device() const870 QIODevice* 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 */
isReadable() const878 bool 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 */
exists() const886 bool 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 */
fileInfoList() const897 QVector<QZipReader::FileInfo> QZipReader::fileInfoList() const
898 {
899     d->scanFiles();
900     QVector<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 */
count() const912 int 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 */
entryInfoAt(int index) const925 QZipReader::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 */
fileData(const QString & fileName) const936 QByteArray 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 */
extractAll(const QString & destinationDir) const1021 bool QZipReader::extractAll(const QString &destinationDir) const
1022 {
1023     QDir baseDir(destinationDir);
1024 
1025     // create directories first
1026     const QVector<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 */
status() const1087 QZipReader::Status QZipReader::status() const
1088 {
1089     return d->status;
1090 }
1091 
1092 /*!
1093     Close the zip file.
1094 */
close()1095 void 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 */
QZipWriter(const QString & fileName,QIODevice::OpenMode mode)1120 QZipWriter::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  */
QZipWriter(QIODevice * device)1147 QZipWriter::QZipWriter(QIODevice *device)
1148     : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
1149 {
1150     Q_ASSERT(device);
1151 }
1152 
~QZipWriter()1153 QZipWriter::~QZipWriter()
1154 {
1155     close();
1156     delete d;
1157 }
1158 
1159 /*!
1160     Returns device used for writing zip archive.
1161 */
device() const1162 QIODevice* 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 */
isWritable() const1170 bool 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 */
exists() const1178 bool 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 */
status() const1202 QZipWriter::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 */
setCompressionPolicy(CompressionPolicy policy)1223 void 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 */
compressionPolicy() const1233 QZipWriter::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 */
setCreationPermissions(QFile::Permissions permissions)1246 void 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 */
creationPermissions() const1257 QFile::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 */
addFile(const QString & fileName,const QByteArray & data)1274 void 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 */
addFile(const QString & fileName,QIODevice * device)1286 void 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 */
addDirectory(const QString & dirName)1307 void 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 */
addSymLink(const QString & fileName,const QString & destination)1321 void 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 */
close()1329 void 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 
1364 QT_END_NAMESPACE
1365 
1366 #endif // QT_NO_TEXTODFWRITER
1367