Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "zipcontainer.h"
23 :
24 : #include <algorithm>
25 : #include <cstddef>
26 : #include <ctime>
27 : #include <cstring>
28 : #include <zlib.h>
29 :
30 : #include "global/io/dir.h"
31 :
32 : #include "log.h"
33 :
34 : // Zip standard version for archives handled by this API
35 : // (actually, the only basic support of this version is implemented but it is enough for now)
36 : #define ZIP_VERSION 20
37 :
38 : #if 0
39 : #define ZDEBUG LOGD
40 : #else
41 : #define ZDEBUG if (0) LOGD
42 : #endif
43 :
44 : using namespace muse::io;
45 :
46 : typedef unsigned long int ulong;
47 : typedef unsigned short int ushort;
48 : typedef unsigned int uint;
49 :
50 : namespace muse {
51 6656 : static inline uint readUInt(const uint8_t* data)
52 : {
53 6656 : return (data[0]) + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
54 : }
55 :
56 13056 : static inline ushort readUShort(const uint8_t* data)
57 : {
58 13056 : return (data[0]) + (data[1] << 8);
59 : }
60 :
61 137 : static inline void writeUInt(uint8_t* data, uint i)
62 : {
63 137 : data[0] = i & 0xff;
64 137 : data[1] = (i >> 8) & 0xff;
65 137 : data[2] = (i >> 16) & 0xff;
66 137 : data[3] = (i >> 24) & 0xff;
67 137 : }
68 :
69 103 : static inline void writeUShort(uint8_t* data, ushort i)
70 : {
71 103 : data[0] = i & 0xff;
72 103 : data[1] = (i >> 8) & 0xff;
73 103 : }
74 :
75 68 : static inline void copyUInt(uint8_t* dest, const uint8_t* src)
76 : {
77 68 : dest[0] = src[0];
78 68 : dest[1] = src[1];
79 68 : dest[2] = src[2];
80 68 : dest[3] = src[3];
81 68 : }
82 :
83 85 : static inline void copyUShort(uint8_t* dest, const uint8_t* src)
84 : {
85 85 : dest[0] = src[0];
86 85 : dest[1] = src[1];
87 85 : }
88 :
89 17 : static void writeMSDosDate(uint8_t* dest, const std::tm& dt)
90 : {
91 17 : if (dt.tm_year > 0) {
92 17 : uint16_t time
93 17 : =(dt.tm_hour << 11) // 5 bit hour
94 17 : | (dt.tm_min << 5) // 6 bit minute
95 17 : | (dt.tm_sec >> 1); // 5 bit double seconds
96 :
97 17 : dest[0] = time & 0xff;
98 17 : dest[1] = time >> 8;
99 :
100 17 : uint16_t date
101 17 : =((dt.tm_year + 1900 - 1980) << 9) // 7 bit year 1980-based
102 17 : | ((dt.tm_mon + 1) << 5) // 4 bit month
103 17 : | (dt.tm_mday); // 5 bit day
104 :
105 17 : dest[2] = char(date);
106 17 : dest[3] = char(date >> 8);
107 : } else {
108 0 : dest[0] = 0;
109 0 : dest[1] = 0;
110 0 : dest[2] = 0;
111 0 : dest[3] = 0;
112 : }
113 17 : }
114 :
115 514 : static int inflate(Bytef* dest, ulong* destLen, const Bytef* source, ulong sourceLen)
116 : {
117 : z_stream stream;
118 : int err;
119 :
120 514 : stream.next_in = const_cast<Bytef*>(source);
121 514 : stream.avail_in = (uInt)sourceLen;
122 514 : if ((uLong)stream.avail_in != sourceLen) {
123 0 : return Z_BUF_ERROR;
124 : }
125 :
126 514 : stream.next_out = dest;
127 514 : stream.avail_out = (uInt) * destLen;
128 514 : if ((uLong)stream.avail_out != *destLen) {
129 0 : return Z_BUF_ERROR;
130 : }
131 :
132 514 : stream.zalloc = (alloc_func)0;
133 514 : stream.zfree = (free_func)0;
134 :
135 514 : err = inflateInit2(&stream, -MAX_WBITS);
136 514 : if (err != Z_OK) {
137 0 : return err;
138 : }
139 :
140 514 : err = inflate(&stream, Z_FINISH);
141 514 : if (err != Z_STREAM_END) {
142 0 : inflateEnd(&stream);
143 0 : if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) {
144 0 : return Z_DATA_ERROR;
145 : }
146 0 : return err;
147 : }
148 514 : *destLen = stream.total_out;
149 :
150 514 : err = inflateEnd(&stream);
151 514 : return err;
152 : }
153 :
154 17 : static int deflate(Bytef* dest, ulong* destLen, const Bytef* source, ulong sourceLen)
155 : {
156 : z_stream stream;
157 : int err;
158 :
159 17 : stream.next_in = const_cast<Bytef*>(source);
160 17 : stream.avail_in = (uInt)sourceLen;
161 17 : stream.next_out = dest;
162 17 : stream.avail_out = (uInt) * destLen;
163 17 : if ((uLong)stream.avail_out != *destLen) {
164 0 : return Z_BUF_ERROR;
165 : }
166 :
167 17 : stream.zalloc = (alloc_func)0;
168 17 : stream.zfree = (free_func)0;
169 17 : stream.opaque = (voidpf)0;
170 :
171 17 : err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
172 17 : if (err != Z_OK) {
173 0 : return err;
174 : }
175 :
176 17 : err = deflate(&stream, Z_FINISH);
177 17 : if (err != Z_STREAM_END) {
178 0 : deflateEnd(&stream);
179 0 : return err == Z_OK ? Z_BUF_ERROR : err;
180 : }
181 17 : *destLen = stream.total_out;
182 :
183 17 : err = deflateEnd(&stream);
184 17 : return err;
185 : }
186 :
187 : namespace WindowsFileAttributes {
188 : enum {
189 : Dir = 0x10, // FILE_ATTRIBUTE_DIRECTORY
190 : File = 0x80, // FILE_ATTRIBUTE_NORMAL
191 : TypeMask = 0x90,
192 :
193 : ReadOnly = 0x01, // FILE_ATTRIBUTE_READONLY
194 : PermMask = 0x01
195 : };
196 : }
197 :
198 : namespace UnixFileAttributes {
199 : enum {
200 : Dir = 0040000, // __S_IFDIR
201 : File = 0100000, // __S_IFREG
202 : SymLink = 0120000, // __S_IFLNK
203 : TypeMask = 0170000, // __S_IFMT
204 :
205 : ReadUser = 0400, // __S_IRUSR
206 : WriteUser = 0200, // __S_IWUSR
207 : ExeUser = 0100, // __S_IXUSR
208 : ReadGroup = 0040, // __S_IRGRP
209 : WriteGroup = 0020, // __S_IWGRP
210 : ExeGroup = 0010, // __S_IXGRP
211 : ReadOther = 0004, // __S_IROTH
212 : WriteOther = 0002, // __S_IWOTH
213 : ExeOther = 0001, // __S_IXOTH
214 : PermMask = 0777
215 : };
216 : }
217 :
218 263 : static std::tm readMSDosDate(const uint8_t* src)
219 : {
220 : std::tm tm;
221 263 : uint dosDate = readUInt(src);
222 : uint64_t uDate;
223 263 : uDate = (uint64_t)(dosDate >> 16);
224 263 : tm.tm_mday = (uDate & 0x1f);
225 263 : tm.tm_mon = ((uDate & 0x1E0) >> 5) - 1;
226 263 : tm.tm_year = (((uDate & 0x0FE00) >> 9) + 1980 - 1900);
227 263 : tm.tm_hour = ((dosDate & 0xF800) >> 11);
228 263 : tm.tm_min = ((dosDate & 0x7E0) >> 5);
229 263 : tm.tm_sec = ((dosDate & 0x1f) << 1);
230 :
231 263 : return tm;
232 : }
233 :
234 : // for details, see http://www.pkware.com/documents/casestudies/APPNOTE.TXT
235 :
236 : enum HostOS {
237 : HostFAT = 0,
238 : HostAMIGA = 1,
239 : HostVMS = 2, // VAX/VMS
240 : HostUnix = 3,
241 : HostVM_CMS = 4,
242 : HostAtari = 5, // what if it's a minix filesystem? [cjh]
243 : HostHPFS = 6, // filesystem used by OS/2 (and NT 3.x)
244 : HostMac = 7,
245 : HostZ_System = 8,
246 : HostCPM = 9,
247 : HostTOPS20 = 10, // pkzip 2.50 NTFS
248 : HostNTFS = 11, // filesystem used by Windows NT
249 : HostQDOS = 12, // SMS/QDOS
250 : HostAcorn = 13, // Archimedes Acorn RISC OS
251 : HostVFAT = 14, // filesystem used by Windows 95, NT
252 : HostMVS = 15,
253 : HostBeOS = 16, // hybrid POSIX/database filesystem
254 : HostTandem = 17,
255 : HostOS400 = 18,
256 : HostOSX = 19
257 : };
258 :
259 : enum GeneralPurposeFlag {
260 : Encrypted = 0x01,
261 : AlgTune1 = 0x02,
262 : AlgTune2 = 0x04,
263 : HasDataDescriptor = 0x08,
264 : PatchedData = 0x20,
265 : StrongEncrypted = 0x40,
266 : Utf8Names = 0x0800,
267 : CentralDirectoryEncrypted = 0x2000
268 : };
269 :
270 : enum CompressionMethod {
271 : CompressionMethodStored = 0,
272 : CompressionMethodShrunk = 1,
273 : CompressionMethodReduced1 = 2,
274 : CompressionMethodReduced2 = 3,
275 : CompressionMethodReduced3 = 4,
276 : CompressionMethodReduced4 = 5,
277 : CompressionMethodImploded = 6,
278 : CompressionMethodReservedTokenizing = 7, // reserved for tokenizing
279 : CompressionMethodDeflated = 8,
280 : CompressionMethodDeflated64 = 9,
281 : CompressionMethodPKImploding = 10,
282 :
283 : CompressionMethodBZip2 = 12,
284 :
285 : CompressionMethodLZMA = 14,
286 :
287 : CompressionMethodTerse = 18,
288 : CompressionMethodLz77 = 19,
289 :
290 : CompressionMethodJpeg = 96,
291 : CompressionMethodWavPack = 97,
292 : CompressionMethodPPMd = 98,
293 : CompressionMethodWzAES = 99
294 : };
295 :
296 : struct LocalFileHeader
297 : {
298 : uint8_t signature[4]; // 0x04034b50
299 : uint8_t version_needed[2];
300 : uint8_t general_purpose_bits[2];
301 : uint8_t compression_method[2];
302 : uint8_t last_mod_file[4];
303 : uint8_t crc_32[4];
304 : uint8_t compressed_size[4];
305 : uint8_t uncompressed_size[4];
306 : uint8_t file_name_length[2];
307 : uint8_t extra_field_length[2];
308 : };
309 :
310 208 : bool ZipContainer::startsWithEntry(const uint8_t* data, size_t size, std::span<const uint8_t> name)
311 : {
312 208 : if (size < sizeof(LocalFileHeader) + name.size()) {
313 0 : return false;
314 : }
315 :
316 208 : return readUInt(data + offsetof(LocalFileHeader, signature)) == 0x04034b50
317 0 : && readUShort(data + offsetof(LocalFileHeader, file_name_length)) == name.size()
318 208 : && std::equal(name.begin(), name.end(), data + sizeof(LocalFileHeader));
319 : }
320 :
321 : struct DataDescriptor
322 : {
323 : uint8_t crc_32[4];
324 : uint8_t compressed_size[4];
325 : uint8_t uncompressed_size[4];
326 : };
327 :
328 : struct CentralFileHeader
329 : {
330 : uint8_t signature[4]; // 0x02014b50
331 : uint8_t version_made[2];
332 : uint8_t version_needed[2];
333 : uint8_t general_purpose_bits[2];
334 : uint8_t compression_method[2];
335 : uint8_t last_mod_file[4];
336 : uint8_t crc_32[4];
337 : uint8_t compressed_size[4];
338 : uint8_t uncompressed_size[4];
339 : uint8_t file_name_length[2];
340 : uint8_t extra_field_length[2];
341 : uint8_t file_comment_length[2];
342 : uint8_t disk_start[2];
343 : uint8_t internal_file_attributes[2];
344 : uint8_t external_file_attributes[4];
345 : uint8_t offset_local_header[4];
346 : LocalFileHeader toLocalHeader() const;
347 : };
348 :
349 : struct EndOfDirectory
350 : {
351 : uint8_t signature[4]; // 0x06054b50
352 : uint8_t this_disk[2];
353 : uint8_t start_of_directory_disk[2];
354 : uint8_t num_dir_entries_this_disk[2];
355 : uint8_t num_dir_entries[2];
356 : uint8_t directory_size[4];
357 : uint8_t dir_start_offset[4];
358 : uint8_t comment_length[2];
359 : };
360 :
361 : struct FileHeader
362 : {
363 : CentralFileHeader h;
364 : ByteArray file_name;
365 : ByteArray extra_field;
366 : ByteArray file_comment;
367 : };
368 :
369 17 : LocalFileHeader CentralFileHeader::toLocalHeader() const
370 : {
371 : LocalFileHeader h;
372 17 : writeUInt(h.signature, 0x04034b50);
373 17 : copyUShort(h.version_needed, version_needed);
374 17 : copyUShort(h.general_purpose_bits, general_purpose_bits);
375 17 : copyUShort(h.compression_method, compression_method);
376 17 : copyUInt(h.last_mod_file, last_mod_file);
377 17 : copyUInt(h.crc_32, crc_32);
378 17 : copyUInt(h.compressed_size, compressed_size);
379 17 : copyUInt(h.uncompressed_size, uncompressed_size);
380 17 : copyUShort(h.file_name_length, file_name_length);
381 17 : copyUShort(h.extra_field_length, extra_field_length);
382 17 : return h;
383 : }
384 :
385 : struct ZipContainer::Impl {
386 : IODevice* device = nullptr;
387 :
388 : bool dirtyFileTree = true;
389 : std::vector<FileHeader> fileHeaders;
390 : ByteArray comment;
391 : uint start_of_directory = 0;
392 : ZipContainer::Status status = ZipContainer::NoError;
393 :
394 : ZipContainer::CompressionPolicy compressionPolicy = ZipContainer::AlwaysCompress;
395 :
396 : enum EntryType {
397 : Directory, File, Symlink
398 : };
399 :
400 : void addEntry(EntryType type, const std::string& fileName, const ByteArray& contents);
401 : bool writeToDevice(const uint8_t* data, size_t len);
402 : bool writeToDevice(const ByteArray& data);
403 :
404 195 : Impl(IODevice* d)
405 195 : : device(d) {}
406 :
407 : void scanFiles();
408 : ZipContainer::FileInfo fillFileInfo(size_t index) const;
409 :
410 : std::string fixFilePath(const ByteArray& path) const;
411 : };
412 :
413 587 : void ZipContainer::Impl::scanFiles()
414 : {
415 587 : if (!dirtyFileTree) {
416 398 : return;
417 : }
418 :
419 189 : if (!(device->isOpen() || device->open(IODevice::ReadOnly))) {
420 0 : status = ZipContainer::FileOpenError;
421 0 : return;
422 : }
423 :
424 189 : if ((device->openMode() & IODevice::ReadOnly) == 0) { // only read the index from readable files.
425 0 : status = ZipContainer::FileReadError;
426 0 : return;
427 : }
428 :
429 189 : dirtyFileTree = false;
430 : uint8_t tmp[4];
431 189 : device->read(tmp, 4);
432 189 : if (readUInt(tmp) != 0x04034b50) {
433 0 : LOGW("Zip: not a zip file!");
434 0 : return;
435 : }
436 :
437 : // find EndOfDirectory header
438 189 : int i = 0;
439 189 : int start_of_directory_local = -1;
440 189 : int num_dir_entries = 0;
441 : EndOfDirectory eod;
442 189 : while (start_of_directory_local == -1) {
443 189 : const int pos = int(device->size()) - int(sizeof(EndOfDirectory)) - i;
444 189 : if (pos < 0 || i > 65535) {
445 0 : LOGW("Zip: EndOfDirectory not found");
446 0 : return;
447 : }
448 :
449 189 : device->seek(pos);
450 189 : device->read((uint8_t*)&eod, sizeof(EndOfDirectory));
451 189 : if (readUInt(eod.signature) == 0x06054b50) {
452 189 : break;
453 : }
454 0 : ++i;
455 : }
456 :
457 : // have the eod
458 189 : start_of_directory_local = readUInt(eod.dir_start_offset);
459 189 : num_dir_entries = readUShort(eod.num_dir_entries);
460 : ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory_local, num_dir_entries);
461 189 : int comment_length = readUShort(eod.comment_length);
462 189 : if (comment_length != i) {
463 0 : LOGW("Zip: failed to parse zip file.");
464 : }
465 189 : comment = device->read(std::min(comment_length, i));
466 :
467 189 : device->seek(start_of_directory_local);
468 3464 : for (i = 0; i < num_dir_entries; ++i) {
469 3275 : FileHeader header;
470 3275 : int read = (int)device->read((uint8_t*)&header.h, sizeof(CentralFileHeader));
471 3275 : if (read < (int)sizeof(CentralFileHeader)) {
472 0 : LOGW("Zip: Failed to read complete header, index may be incomplete");
473 0 : break;
474 : }
475 3275 : if (readUInt(header.h.signature) != 0x02014b50) {
476 0 : LOGW("Zip: invalid header signature, index may be incomplete");
477 0 : break;
478 : }
479 :
480 3275 : size_t l = readUShort(header.h.file_name_length);
481 3275 : header.file_name = device->read(l);
482 3275 : if (header.file_name.size() != l) {
483 0 : LOGW("Zip: Failed to read filename from zip index, index may be incomplete");
484 0 : break;
485 : }
486 3275 : l = readUShort(header.h.extra_field_length);
487 3275 : header.extra_field = device->read(l);
488 3275 : if (header.extra_field.size() != l) {
489 0 : LOGW("Zip: Failed to read extra field in zip file, skipping file, index may be incomplete");
490 0 : break;
491 : }
492 3275 : l = readUShort(header.h.file_comment_length);
493 3275 : header.file_comment = device->read(l);
494 3275 : if (header.file_comment.size() != l) {
495 0 : LOGW("Zip: Failed to read read file comment, index may be incomplete");
496 0 : break;
497 : }
498 :
499 : ZDEBUG("found file '%s'", header.file_name.data());
500 3275 : fileHeaders.push_back(header);
501 3275 : }
502 : }
503 :
504 263 : ZipContainer::FileInfo ZipContainer::Impl::fillFileInfo(size_t index) const
505 : {
506 263 : ZipContainer::FileInfo fileInfo;
507 263 : FileHeader header = fileHeaders.at(index);
508 263 : uint32_t mode = readUInt(header.h.external_file_attributes);
509 263 : const HostOS hostOS = HostOS(readUShort(header.h.version_made) >> 8);
510 263 : switch (hostOS) {
511 263 : case HostUnix:
512 263 : mode = (mode >> 16) & 0xffff;
513 263 : switch (mode & UnixFileAttributes::TypeMask) {
514 0 : case UnixFileAttributes::SymLink:
515 0 : fileInfo.isSymLink = true;
516 0 : break;
517 11 : case UnixFileAttributes::Dir:
518 11 : fileInfo.isDir = true;
519 11 : break;
520 252 : case UnixFileAttributes::File:
521 : default: // ### just for the case; should we warn?
522 252 : fileInfo.isFile = true;
523 252 : break;
524 : }
525 263 : break;
526 0 : case HostFAT:
527 : case HostNTFS:
528 : case HostHPFS:
529 : case HostVFAT:
530 0 : switch (mode & WindowsFileAttributes::TypeMask) {
531 0 : case WindowsFileAttributes::Dir:
532 0 : fileInfo.isDir = true;
533 0 : break;
534 0 : case WindowsFileAttributes::File:
535 : default:
536 0 : fileInfo.isFile = true;
537 0 : break;
538 : }
539 :
540 0 : break;
541 0 : default:
542 0 : LOGW("Zip: Zip entry format at %zu is not supported.", index);
543 0 : return fileInfo; // we don't support anything else
544 : }
545 :
546 : // ushort general_purpose_bits = readUShort(header.h.general_purpose_bits);
547 : // if bit 11 is set, the filename and comment fields must be encoded using UTF-8
548 : // const bool inUtf8 = (general_purpose_bits & Utf8Names) != 0;
549 263 : fileInfo.filePath = fixFilePath(header.file_name);
550 263 : fileInfo.crc = readUInt(header.h.crc_32);
551 263 : fileInfo.size = readUInt(header.h.uncompressed_size);
552 263 : fileInfo.lastModified = readMSDosDate(header.h.last_mod_file);
553 :
554 263 : return fileInfo;
555 263 : }
556 :
557 17 : void ZipContainer::Impl::addEntry(EntryType type, const std::string& fileName, const ByteArray& contents)
558 : {
559 17 : if (!(device->isOpen() || device->open(IODevice::WriteOnly))) {
560 0 : status = ZipContainer::FileOpenError;
561 0 : return;
562 : }
563 17 : device->seek(start_of_directory);
564 :
565 : // don't compress small files
566 17 : ZipContainer::CompressionPolicy compression = compressionPolicy;
567 17 : if (compressionPolicy == ZipContainer::AutoCompress) {
568 0 : if (contents.size() < 64) {
569 0 : compression = ZipContainer::NeverCompress;
570 : } else {
571 0 : compression = ZipContainer::AlwaysCompress;
572 : }
573 : }
574 :
575 17 : FileHeader header;
576 17 : std::memset(&header.h, 0, sizeof(CentralFileHeader));
577 17 : writeUInt(header.h.signature, 0x02014b50);
578 :
579 17 : writeUShort(header.h.version_needed, ZIP_VERSION);
580 17 : writeUInt(header.h.uncompressed_size, (uint)contents.size());
581 :
582 17 : std::time_t t = std::time(0); // get time now
583 : std::tm now;
584 : #ifdef WIN32
585 : localtime_s(&now, &t);
586 : #else
587 17 : localtime_r(&t, &now);
588 : #endif
589 17 : writeMSDosDate(header.h.last_mod_file, now);
590 17 : ByteArray data = contents;
591 17 : if (compression == ZipContainer::AlwaysCompress) {
592 17 : writeUShort(header.h.compression_method, CompressionMethodDeflated);
593 :
594 17 : ulong len = (ulong)contents.size();
595 : // shamelessly copied form zlib
596 17 : len += (len >> 12) + (len >> 14) + 11;
597 : int res;
598 : do {
599 17 : data.resize(len);
600 17 : res = deflate((uint8_t*)data.data(), &len, (const uint8_t*)contents.constData(), (ulong)contents.size());
601 :
602 17 : switch (res) {
603 17 : case Z_OK:
604 17 : data.resize(len);
605 17 : break;
606 0 : case Z_MEM_ERROR:
607 0 : LOGW("Zip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
608 0 : data.resize(0);
609 0 : break;
610 0 : case Z_BUF_ERROR:
611 0 : len *= 2;
612 0 : break;
613 : }
614 17 : } while (res == Z_BUF_ERROR);
615 : }
616 : // TODO add a check if data.size() > contents.size(). Then try to store the original and revert the compression method to be uncompressed
617 17 : writeUInt(header.h.compressed_size, (uint)data.size());
618 17 : uint crc_32 = ::crc32(0, 0, 0);
619 17 : crc_32 = ::crc32(crc_32, (const uint8_t*)contents.constData(), (uint)contents.size());
620 17 : writeUInt(header.h.crc_32, crc_32);
621 :
622 : // if bit 11 is set, the filename and comment fields must be encoded using UTF-8
623 17 : ushort general_purpose_bits = Utf8Names; // always use utf-8
624 17 : writeUShort(header.h.general_purpose_bits, general_purpose_bits);
625 :
626 : //const bool inUtf8 = (general_purpose_bits & Utf8Names) != 0;
627 17 : header.file_name = ByteArray(fileName.c_str(), fileName.size());
628 17 : if (header.file_name.size() > 0xffff) {
629 0 : LOGW("Zip: Filename is too long, chopping it to 65535 bytes");
630 0 : header.file_name = header.file_name.left(0xffff); // ### don't break the utf-8 sequence, if any
631 : }
632 17 : if (header.file_comment.size() + header.file_name.size() > 0xffff) {
633 0 : LOGW("Zip: File comment is too long, chopping it to 65535 bytes");
634 0 : header.file_comment.truncate(0xffff - header.file_name.size()); // ### don't break the utf-8 sequence, if any
635 : }
636 17 : writeUShort(header.h.file_name_length, (ushort)header.file_name.size());
637 : //h.extra_field_length[2];
638 :
639 17 : writeUShort(header.h.version_made, HostUnix << 8);
640 : //uint8_t internal_file_attributes[2];
641 : //uint8_t external_file_attributes[4];
642 17 : uint32_t mode = UnixFileAttributes::ReadUser
643 : | UnixFileAttributes::WriteUser
644 : | UnixFileAttributes::ExeUser
645 : | UnixFileAttributes::ReadGroup
646 : | UnixFileAttributes::ReadOther;
647 17 : switch (type) {
648 0 : case Symlink:
649 0 : mode |= UnixFileAttributes::SymLink;
650 0 : break;
651 0 : case Directory:
652 0 : mode |= UnixFileAttributes::Dir;
653 0 : break;
654 17 : case File:
655 17 : mode |= UnixFileAttributes::File;
656 17 : break;
657 0 : default:
658 0 : UNREACHABLE;
659 : break;
660 : }
661 17 : writeUInt(header.h.external_file_attributes, mode << 16);
662 17 : writeUInt(header.h.offset_local_header, start_of_directory);
663 :
664 17 : fileHeaders.push_back(header);
665 :
666 17 : bool ok = true;
667 :
668 17 : LocalFileHeader h = header.h.toLocalHeader();
669 17 : ok &= writeToDevice((const uint8_t*)&h, sizeof(LocalFileHeader));
670 17 : ok &= writeToDevice(header.file_name);
671 17 : ok &= writeToDevice(data);
672 :
673 17 : start_of_directory = (uint)device->pos();
674 17 : dirtyFileTree = true;
675 :
676 17 : if (!ok) {
677 0 : status = ZipContainer::FileWriteError;
678 : }
679 17 : }
680 :
681 40 : bool ZipContainer::Impl::writeToDevice(const uint8_t* data, size_t len)
682 : {
683 40 : return device->write(data, len) == len;
684 : }
685 :
686 91 : bool ZipContainer::Impl::writeToDevice(const ByteArray& data)
687 : {
688 91 : return device->write(data) == data.size();
689 : }
690 :
691 20683 : std::string ZipContainer::Impl::fixFilePath(const ByteArray& path) const
692 : {
693 : // fix the file path, if broken (convert separators, eat leading and trailing ones)
694 20683 : std::string fixed = Dir::fromNativeSeparators(path.constChar()).toStdString();
695 : {
696 20683 : bool frontOk = false;
697 41366 : while (!fixed.empty() && !frontOk) {
698 20683 : if (fixed.front() == '/') {
699 0 : fixed = fixed.substr(1);
700 20683 : } else if (fixed.rfind("./", 0) == 0) {
701 0 : fixed = fixed.substr(2);
702 20683 : } else if (fixed.rfind("../", 0) == 0) {
703 0 : fixed = fixed.substr(3);
704 : } else {
705 20683 : frontOk = true;
706 : }
707 : }
708 : }
709 :
710 21019 : while (!fixed.empty() && fixed.back() == '/') {
711 336 : fixed = fixed.substr(0, fixed.size() - 1);
712 : }
713 :
714 20683 : return fixed;
715 0 : }
716 :
717 195 : ZipContainer::ZipContainer(IODevice* device)
718 195 : : p(new Impl(device))
719 : {
720 195 : assert(device);
721 195 : }
722 :
723 195 : ZipContainer::~ZipContainer()
724 : {
725 195 : close();
726 195 : delete p;
727 195 : }
728 :
729 30 : std::vector<ZipContainer::FileInfo> ZipContainer::fileInfoList() const
730 : {
731 30 : p->scanFiles();
732 30 : std::vector<FileInfo> files;
733 30 : const size_t numFileHeaders = p->fileHeaders.size();
734 30 : files.reserve(numFileHeaders);
735 293 : for (size_t i = 0; i < numFileHeaders; ++i) {
736 263 : files.push_back(p->fillFileInfo(i));
737 : }
738 30 : return files;
739 0 : }
740 :
741 0 : int ZipContainer::count() const
742 : {
743 0 : p->scanFiles();
744 0 : return (int)p->fileHeaders.size();
745 : }
746 :
747 39 : bool ZipContainer::fileExists(const std::string& fileName) const
748 : {
749 39 : p->scanFiles();
750 :
751 277 : for (size_t i = 0; i < p->fileHeaders.size(); ++i) {
752 256 : std::string currentFileName = p->fixFilePath(p->fileHeaders.at(i).file_name.constChar());
753 256 : if (currentFileName == fileName) {
754 18 : return true;
755 : }
756 256 : }
757 :
758 21 : return false;
759 : }
760 :
761 518 : ByteArray ZipContainer::fileData(const std::string& fileName) const
762 : {
763 518 : p->scanFiles();
764 :
765 518 : size_t i = 0;
766 20164 : for (i = 0; i < p->fileHeaders.size(); ++i) {
767 20164 : std::string currentFileName = p->fixFilePath(p->fileHeaders.at(i).file_name.constChar());
768 20164 : if (currentFileName == fileName) {
769 518 : break;
770 : }
771 20164 : }
772 :
773 518 : if (i == p->fileHeaders.size()) {
774 0 : return ByteArray();
775 : }
776 :
777 518 : FileHeader header = p->fileHeaders.at(i);
778 :
779 518 : ushort version_needed = readUShort(header.h.version_needed);
780 518 : if (version_needed > ZIP_VERSION) {
781 0 : LOGW("Zip: .ZIP specification version %d implementation is needed to extract the data.", version_needed);
782 0 : return ByteArray();
783 : }
784 :
785 518 : ushort general_purpose_bits = readUShort(header.h.general_purpose_bits);
786 518 : int compressed_size = readUInt(header.h.compressed_size);
787 518 : int uncompressed_size = readUInt(header.h.uncompressed_size);
788 518 : int start = readUInt(header.h.offset_local_header);
789 :
790 518 : p->device->seek(start);
791 : LocalFileHeader lh;
792 518 : p->device->read((uint8_t*)&lh, sizeof(LocalFileHeader));
793 518 : uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
794 518 : p->device->seek(p->device->pos() + skip);
795 :
796 518 : int compression_method = readUShort(lh.compression_method);
797 :
798 518 : if ((general_purpose_bits & Encrypted) != 0) {
799 0 : LOGW("Zip: Unsupported encryption method is needed to extract the data.");
800 0 : return ByteArray();
801 : }
802 :
803 518 : ByteArray compressed = p->device->read(compressed_size);
804 518 : if (compression_method == CompressionMethodStored) {
805 : // no compression
806 4 : compressed.truncate(uncompressed_size);
807 4 : return compressed;
808 514 : } else if (compression_method == CompressionMethodDeflated) {
809 : // Deflate
810 : //qDebug("compressed=%d", compressed.size());
811 514 : compressed.truncate(compressed_size);
812 514 : ByteArray baunzip;
813 514 : ulong len = std::max(uncompressed_size, 1);
814 : int res;
815 : do {
816 514 : baunzip.resize(len);
817 514 : res = inflate((uint8_t*)baunzip.data(), &len,
818 : (const uint8_t*)compressed.constData(), compressed_size);
819 :
820 514 : switch (res) {
821 514 : case Z_OK:
822 514 : if ((size_t)len != baunzip.size()) {
823 0 : baunzip.resize(len);
824 : }
825 514 : break;
826 0 : case Z_MEM_ERROR:
827 0 : LOGW("Zip: Z_MEM_ERROR: Not enough memory");
828 0 : break;
829 0 : case Z_BUF_ERROR:
830 0 : len *= 2;
831 0 : break;
832 0 : case Z_DATA_ERROR:
833 0 : LOGW("Zip: Z_DATA_ERROR: Input data is corrupted");
834 0 : break;
835 : }
836 514 : } while (res == Z_BUF_ERROR);
837 514 : return baunzip;
838 514 : }
839 :
840 0 : LOGW("Zip: Unsupported compression method %d is needed to extract the data.", compression_method);
841 0 : return ByteArray();
842 518 : }
843 :
844 120 : ZipContainer::Status ZipContainer::status() const
845 : {
846 120 : return p->status;
847 : }
848 :
849 0 : void ZipContainer::setCompressionPolicy(CompressionPolicy policy)
850 : {
851 0 : p->compressionPolicy = policy;
852 0 : }
853 :
854 0 : ZipContainer::CompressionPolicy ZipContainer::compressionPolicy() const
855 : {
856 0 : return p->compressionPolicy;
857 : }
858 :
859 17 : void ZipContainer::addFile(const std::string& fileName, const ByteArray& data)
860 : {
861 17 : p->addEntry(Impl::File, Dir::fromNativeSeparators(fileName).toStdString(), data);
862 17 : }
863 :
864 0 : void ZipContainer::addDirectory(const std::string& dirName)
865 : {
866 0 : std::string name(Dir::fromNativeSeparators(dirName).toStdString());
867 : // separator is mandatory
868 0 : if (name.back() != '/') {
869 0 : name.push_back('/');
870 : }
871 0 : p->addEntry(Impl::Directory, name, ByteArray());
872 0 : }
873 :
874 553 : void ZipContainer::close()
875 : {
876 553 : if (!(p->device->openMode() & IODevice::WriteOnly)) {
877 547 : p->device->close();
878 547 : return;
879 : }
880 :
881 6 : bool ok = true;
882 :
883 : //qDebug("Zip::close writing directory, %d entries", p->fileHeaders.size());
884 6 : p->device->seek(p->start_of_directory);
885 : // write new directory
886 23 : for (size_t i = 0; i < p->fileHeaders.size(); ++i) {
887 17 : const FileHeader& header = p->fileHeaders.at(i);
888 17 : ok &= p->writeToDevice((const uint8_t*)&header.h, sizeof(CentralFileHeader));
889 17 : ok &= p->writeToDevice(header.file_name);
890 17 : ok &= p->writeToDevice(header.extra_field);
891 17 : ok &= p->writeToDevice(header.file_comment);
892 : }
893 6 : int dir_size = (int)p->device->pos() - (int)p->start_of_directory;
894 : // write end of directory
895 : EndOfDirectory eod;
896 6 : memset(&eod, 0, sizeof(EndOfDirectory));
897 6 : writeUInt(eod.signature, 0x06054b50);
898 : //uint8_t this_disk[2];
899 : //uint8_t start_of_directory_disk[2];
900 6 : writeUShort(eod.num_dir_entries_this_disk, (ushort)p->fileHeaders.size());
901 6 : writeUShort(eod.num_dir_entries, (ushort)p->fileHeaders.size());
902 6 : writeUInt(eod.directory_size, dir_size);
903 6 : writeUInt(eod.dir_start_offset, p->start_of_directory);
904 6 : writeUShort(eod.comment_length, (ushort)p->comment.size());
905 :
906 6 : ok &= p->writeToDevice((const uint8_t*)&eod, sizeof(EndOfDirectory));
907 6 : ok &= p->writeToDevice(p->comment);
908 6 : p->device->close();
909 :
910 6 : if (!ok) {
911 0 : p->status = ZipContainer::FileWriteError;
912 : }
913 : }
914 : }
|