LCOV - code coverage report
Current view: top level - muse/framework/global/serialization/internal - zipcontainer.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 73.9 % 436 322
Test Date: 2026-11-05 04:07:02

            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              : }
        

Generated by: LCOV version 2.0-1