| 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(n: 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 QZipReader::FileInfo::filePath | 
| 734 |     The full filepath inside the archive. | 
| 735 | */ | 
| 736 |  | 
| 737 | /*! | 
| 738 |     \variable QZipReader::FileInfo::isDir | 
| 739 |     A boolean type indicating if the entry is a directory. | 
| 740 | */ | 
| 741 |  | 
| 742 | /*! | 
| 743 |     \variable QZipReader::FileInfo::isFile | 
| 744 |     A boolean type, if it is one this entry is a file. | 
| 745 | */ | 
| 746 |  | 
| 747 | /*! | 
| 748 |     \variable QZipReader::FileInfo::isSymLink | 
| 749 |     A boolean type, if it is one this entry is symbolic link. | 
| 750 | */ | 
| 751 |  | 
| 752 | /*! | 
| 753 |     \variable QZipReader::FileInfo::permissions | 
| 754 |     A list of flags for the permissions of this entry. | 
| 755 | */ | 
| 756 |  | 
| 757 | /*! | 
| 758 |     \variable QZipReader::FileInfo::crc | 
| 759 |     The calculated checksum as a crc type. | 
| 760 | */ | 
| 761 |  | 
| 762 | /*! | 
| 763 |     \variable QZipReader::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 |  |