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