File indexing completed on 2024-04-28 15:15:58
0001 // SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LGPL-3.0-only WITH Qt-LGPL-exception-1.1 OR GPL-3.0-only OR LicenseRef-Qt-Commercial 0002 // 0003 // SPDX-FileCopyrightText: 2015 The Qt Company Ltd. <https://www.qt.io/licensing/> 0004 // This file is based on qzip.cpp from Qt with the original license 0005 // below, taken from 0006 // https://code.qt.io/cgit/qt/qt.git/plain/src/gui/text/qzip.cpp 0007 0008 #include <qglobal.h> 0009 0010 #ifndef QT_NO_TEXTODFWRITER 0011 0012 #include "MarbleZipReader.h" 0013 #include "MarbleZipWriter.h" 0014 #include <QDateTime> 0015 #include <qplatformdefs.h> 0016 #include <qendian.h> 0017 #include <QDebug> 0018 #include <QDir> 0019 0020 #include <zlib.h> 0021 0022 #if defined(Q_OS_WIN) 0023 # undef S_IFREG 0024 # define S_IFREG 0100000 0025 # ifndef S_IFDIR 0026 # define S_IFDIR 0040000 0027 # endif 0028 # ifndef S_ISDIR 0029 # define S_ISDIR(x) ((x) & S_IFDIR) > 0 0030 # endif 0031 # ifndef S_ISREG 0032 # define S_ISREG(x) ((x) & 0170000) == S_IFREG 0033 # endif 0034 # define S_IFLNK 020000 0035 # define S_ISLNK(x) ((x) & S_IFLNK) > 0 0036 # ifndef S_IRUSR 0037 # define S_IRUSR 0400 0038 # endif 0039 # ifndef S_IWUSR 0040 # define S_IWUSR 0200 0041 # endif 0042 # ifndef S_IXUSR 0043 # define S_IXUSR 0100 0044 # endif 0045 # define S_IRGRP 0040 0046 # define S_IWGRP 0020 0047 # define S_IXGRP 0010 0048 # define S_IROTH 0004 0049 # define S_IWOTH 0002 0050 # define S_IXOTH 0001 0051 #endif 0052 0053 #if 0 0054 #define ZDEBUG qDebug 0055 #else 0056 #define ZDEBUG if (0) qDebug 0057 #endif 0058 0059 namespace Marble { 0060 0061 static inline uint readUInt(const uchar *data) 0062 { 0063 return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24); 0064 } 0065 0066 static inline ushort readUShort(const uchar *data) 0067 { 0068 return (data[0]) + (data[1]<<8); 0069 } 0070 0071 static inline void writeUInt(uchar *data, uint i) 0072 { 0073 data[0] = i & 0xff; 0074 data[1] = (i>>8) & 0xff; 0075 data[2] = (i>>16) & 0xff; 0076 data[3] = (i>>24) & 0xff; 0077 } 0078 0079 static inline void writeUShort(uchar *data, ushort i) 0080 { 0081 data[0] = i & 0xff; 0082 data[1] = (i>>8) & 0xff; 0083 } 0084 0085 static inline void copyUInt(uchar *dest, const uchar *src) 0086 { 0087 dest[0] = src[0]; 0088 dest[1] = src[1]; 0089 dest[2] = src[2]; 0090 dest[3] = src[3]; 0091 } 0092 0093 static inline void copyUShort(uchar *dest, const uchar *src) 0094 { 0095 dest[0] = src[0]; 0096 dest[1] = src[1]; 0097 } 0098 0099 static void writeMSDosDate(uchar *dest, const QDateTime& dt) 0100 { 0101 if (dt.isValid()) { 0102 quint16 time = 0103 (dt.time().hour() << 11) // 5 bit hour 0104 | (dt.time().minute() << 5) // 6 bit minute 0105 | (dt.time().second() >> 1); // 5 bit double seconds 0106 0107 dest[0] = time & 0xff; 0108 dest[1] = time >> 8; 0109 0110 quint16 date = 0111 ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based 0112 | (dt.date().month() << 5) // 4 bit month 0113 | (dt.date().day()); // 5 bit day 0114 0115 dest[2] = char(date); 0116 dest[3] = char(date >> 8); 0117 } else { 0118 dest[0] = 0; 0119 dest[1] = 0; 0120 dest[2] = 0; 0121 dest[3] = 0; 0122 } 0123 } 0124 0125 static quint32 permissionsToMode(QFile::Permissions perms) 0126 { 0127 quint32 mode = 0; 0128 if (perms & QFile::ReadOwner) 0129 mode |= S_IRUSR; 0130 if (perms & QFile::WriteOwner) 0131 mode |= S_IWUSR; 0132 if (perms & QFile::ExeOwner) 0133 mode |= S_IXUSR; 0134 if (perms & QFile::ReadUser) 0135 mode |= S_IRUSR; 0136 if (perms & QFile::WriteUser) 0137 mode |= S_IWUSR; 0138 if (perms & QFile::ExeUser) 0139 mode |= S_IXUSR; 0140 if (perms & QFile::ReadGroup) 0141 mode |= S_IRGRP; 0142 if (perms & QFile::WriteGroup) 0143 mode |= S_IWGRP; 0144 if (perms & QFile::ExeGroup) 0145 mode |= S_IXGRP; 0146 if (perms & QFile::ReadOther) 0147 mode |= S_IROTH; 0148 if (perms & QFile::WriteOther) 0149 mode |= S_IWOTH; 0150 if (perms & QFile::ExeOther) 0151 mode |= S_IXOTH; 0152 return mode; 0153 } 0154 0155 static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen) 0156 { 0157 z_stream stream; 0158 int err; 0159 0160 stream.next_in = (Bytef*)source; 0161 stream.avail_in = (uInt)sourceLen; 0162 if ((uLong)stream.avail_in != sourceLen) 0163 return Z_BUF_ERROR; 0164 0165 stream.next_out = dest; 0166 stream.avail_out = (uInt)*destLen; 0167 if ((uLong)stream.avail_out != *destLen) 0168 return Z_BUF_ERROR; 0169 0170 stream.zalloc = (alloc_func)nullptr; 0171 stream.zfree = (free_func)nullptr; 0172 0173 err = inflateInit2(&stream, -MAX_WBITS); 0174 if (err != Z_OK) 0175 return err; 0176 0177 err = inflate(&stream, Z_FINISH); 0178 if (err != Z_STREAM_END) { 0179 inflateEnd(&stream); 0180 if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) 0181 return Z_DATA_ERROR; 0182 return err; 0183 } 0184 *destLen = stream.total_out; 0185 0186 err = inflateEnd(&stream); 0187 return err; 0188 } 0189 0190 static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen) 0191 { 0192 z_stream stream; 0193 int err; 0194 0195 stream.next_in = (Bytef*)source; 0196 stream.avail_in = (uInt)sourceLen; 0197 stream.next_out = dest; 0198 stream.avail_out = (uInt)*destLen; 0199 if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; 0200 0201 stream.zalloc = (alloc_func)nullptr; 0202 stream.zfree = (free_func)nullptr; 0203 stream.opaque = (voidpf)nullptr; 0204 0205 err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); 0206 if (err != Z_OK) return err; 0207 0208 err = deflate(&stream, Z_FINISH); 0209 if (err != Z_STREAM_END) { 0210 deflateEnd(&stream); 0211 return err == Z_OK ? Z_BUF_ERROR : err; 0212 } 0213 *destLen = stream.total_out; 0214 0215 err = deflateEnd(&stream); 0216 return err; 0217 } 0218 0219 static QFile::Permissions modeToPermissions(quint32 mode) 0220 { 0221 QFile::Permissions ret; 0222 if (mode & S_IRUSR) 0223 ret |= QFile::ReadOwner; 0224 if (mode & S_IWUSR) 0225 ret |= QFile::WriteOwner; 0226 if (mode & S_IXUSR) 0227 ret |= QFile::ExeOwner; 0228 if (mode & S_IRUSR) 0229 ret |= QFile::ReadUser; 0230 if (mode & S_IWUSR) 0231 ret |= QFile::WriteUser; 0232 if (mode & S_IXUSR) 0233 ret |= QFile::ExeUser; 0234 if (mode & S_IRGRP) 0235 ret |= QFile::ReadGroup; 0236 if (mode & S_IWGRP) 0237 ret |= QFile::WriteGroup; 0238 if (mode & S_IXGRP) 0239 ret |= QFile::ExeGroup; 0240 if (mode & S_IROTH) 0241 ret |= QFile::ReadOther; 0242 if (mode & S_IWOTH) 0243 ret |= QFile::WriteOther; 0244 if (mode & S_IXOTH) 0245 ret |= QFile::ExeOther; 0246 return ret; 0247 } 0248 0249 static QDateTime readMSDosDate(const uchar *src) 0250 { 0251 uint dosDate = readUInt(src); 0252 quint64 uDate; 0253 uDate = (quint64)(dosDate >> 16); 0254 uint tm_mday = (uDate & 0x1f); 0255 uint tm_mon = ((uDate & 0x1E0) >> 5); 0256 uint tm_year = (((uDate & 0x0FE00) >> 9) + 1980); 0257 uint tm_hour = ((dosDate & 0xF800) >> 11); 0258 uint tm_min = ((dosDate & 0x7E0) >> 5); 0259 uint tm_sec = ((dosDate & 0x1f) << 1); 0260 0261 return QDateTime(QDate(tm_year, tm_mon, tm_mday), QTime(tm_hour, tm_min, tm_sec)); 0262 } 0263 0264 struct LocalFileHeader 0265 { 0266 uchar signature[4]; // 0x04034b50 0267 uchar version_needed[2]; 0268 uchar general_purpose_bits[2]; 0269 uchar compression_method[2]; 0270 uchar last_mod_file[4]; 0271 uchar crc_32[4]; 0272 uchar compressed_size[4]; 0273 uchar uncompressed_size[4]; 0274 uchar file_name_length[2]; 0275 uchar extra_field_length[2]; 0276 }; 0277 0278 struct DataDescriptor 0279 { 0280 uchar crc_32[4]; 0281 uchar compressed_size[4]; 0282 uchar uncompressed_size[4]; 0283 }; 0284 0285 struct CentralFileHeader 0286 { 0287 uchar signature[4]; // 0x02014b50 0288 uchar version_made[2]; 0289 uchar version_needed[2]; 0290 uchar general_purpose_bits[2]; 0291 uchar compression_method[2]; 0292 uchar last_mod_file[4]; 0293 uchar crc_32[4]; 0294 uchar compressed_size[4]; 0295 uchar uncompressed_size[4]; 0296 uchar file_name_length[2]; 0297 uchar extra_field_length[2]; 0298 uchar file_comment_length[2]; 0299 uchar disk_start[2]; 0300 uchar internal_file_attributes[2]; 0301 uchar external_file_attributes[4]; 0302 uchar offset_local_header[4]; 0303 LocalFileHeader toLocalHeader() const; 0304 }; 0305 0306 struct EndOfDirectory 0307 { 0308 uchar signature[4]; // 0x06054b50 0309 uchar this_disk[2]; 0310 uchar start_of_directory_disk[2]; 0311 uchar num_dir_entries_this_disk[2]; 0312 uchar num_dir_entries[2]; 0313 uchar directory_size[4]; 0314 uchar dir_start_offset[4]; 0315 uchar comment_length[2]; 0316 }; 0317 0318 struct FileHeader 0319 { 0320 CentralFileHeader h; 0321 QByteArray file_name; 0322 QByteArray extra_field; 0323 QByteArray file_comment; 0324 }; 0325 0326 MarbleZipReader::FileInfo::FileInfo() 0327 : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0) 0328 { 0329 } 0330 0331 MarbleZipReader::FileInfo::~FileInfo() 0332 { 0333 } 0334 0335 MarbleZipReader::FileInfo::FileInfo(const FileInfo &other) 0336 { 0337 operator=(other); 0338 } 0339 0340 MarbleZipReader::FileInfo& MarbleZipReader::FileInfo::operator=(const FileInfo &other) 0341 { 0342 filePath = other.filePath; 0343 isDir = other.isDir; 0344 isFile = other.isFile; 0345 isSymLink = other.isSymLink; 0346 permissions = other.permissions; 0347 crc32 = other.crc32; 0348 size = other.size; 0349 lastModified = other.lastModified; 0350 return *this; 0351 } 0352 0353 bool MarbleZipReader::FileInfo::isValid() const 0354 { 0355 return isDir || isFile || isSymLink; 0356 } 0357 0358 class QZipPrivate 0359 { 0360 public: 0361 QZipPrivate(QIODevice *device, bool ownDev) 0362 : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0) 0363 { 0364 } 0365 0366 ~QZipPrivate() 0367 { 0368 if (ownDevice) 0369 delete device; 0370 } 0371 0372 void fillFileInfo(int index, MarbleZipReader::FileInfo &fileInfo) const; 0373 0374 QIODevice *device; 0375 bool ownDevice; 0376 bool dirtyFileTree; 0377 QList<FileHeader> fileHeaders; 0378 QByteArray comment; 0379 uint start_of_directory; 0380 }; 0381 0382 void QZipPrivate::fillFileInfo(int index, MarbleZipReader::FileInfo &fileInfo) const 0383 { 0384 FileHeader header = fileHeaders.at(index); 0385 fileInfo.filePath = QString::fromLocal8Bit(header.file_name); 0386 const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF; 0387 if (mode == 0) { 0388 fileInfo.isDir = false; 0389 fileInfo.isFile = true; 0390 fileInfo.isSymLink = false; 0391 fileInfo.permissions = QFile::ReadOwner; 0392 } else { 0393 fileInfo.isDir = S_ISDIR(mode); 0394 fileInfo.isFile = S_ISREG(mode); 0395 fileInfo.isSymLink = S_ISLNK(mode); 0396 fileInfo.permissions = modeToPermissions(mode); 0397 } 0398 fileInfo.crc32 = readUInt(header.h.crc_32); 0399 fileInfo.size = readUInt(header.h.uncompressed_size); 0400 fileInfo.lastModified = readMSDosDate(header.h.last_mod_file); 0401 } 0402 0403 class MarbleZipReaderPrivate : public QZipPrivate 0404 { 0405 public: 0406 MarbleZipReaderPrivate(QIODevice *device, bool ownDev) 0407 : QZipPrivate(device, ownDev), status(MarbleZipReader::NoError) 0408 { 0409 } 0410 0411 void scanFiles(); 0412 0413 MarbleZipReader::Status status; 0414 }; 0415 0416 class MarbleZipWriterPrivate : public QZipPrivate 0417 { 0418 public: 0419 MarbleZipWriterPrivate(QIODevice *device, bool ownDev) 0420 : QZipPrivate(device, ownDev), 0421 status(MarbleZipWriter::NoError), 0422 permissions(QFile::ReadOwner | QFile::WriteOwner), 0423 compressionPolicy(MarbleZipWriter::AlwaysCompress) 0424 { 0425 } 0426 0427 MarbleZipWriter::Status status; 0428 QFile::Permissions permissions; 0429 MarbleZipWriter::CompressionPolicy compressionPolicy; 0430 0431 enum EntryType { Directory, File, Symlink }; 0432 0433 void addEntry(EntryType type, const QString &fileName, const QByteArray &contents); 0434 }; 0435 0436 LocalFileHeader CentralFileHeader::toLocalHeader() const 0437 { 0438 LocalFileHeader h; 0439 writeUInt(h.signature, 0x04034b50); 0440 copyUShort(h.version_needed, version_needed); 0441 copyUShort(h.general_purpose_bits, general_purpose_bits); 0442 copyUShort(h.compression_method, compression_method); 0443 copyUInt(h.last_mod_file, last_mod_file); 0444 copyUInt(h.crc_32, crc_32); 0445 copyUInt(h.compressed_size, compressed_size); 0446 copyUInt(h.uncompressed_size, uncompressed_size); 0447 copyUShort(h.file_name_length, file_name_length); 0448 copyUShort(h.extra_field_length, extra_field_length); 0449 return h; 0450 } 0451 0452 void MarbleZipReaderPrivate::scanFiles() 0453 { 0454 if (!dirtyFileTree) 0455 return; 0456 0457 if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) { 0458 status = MarbleZipReader::FileOpenError; 0459 return; 0460 } 0461 0462 if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files. 0463 status = MarbleZipReader::FileReadError; 0464 return; 0465 } 0466 0467 dirtyFileTree = false; 0468 uchar tmp[4]; 0469 device->read((char *)tmp, 4); 0470 if (readUInt(tmp) != 0x04034b50) { 0471 qWarning() << "QZip: not a zip file!"; 0472 return; 0473 } 0474 0475 // find EndOfDirectory header 0476 int i = 0; 0477 int start_of_directory = -1; 0478 int num_dir_entries = 0; 0479 EndOfDirectory eod; 0480 while (start_of_directory == -1) { 0481 int pos = device->size() - sizeof(EndOfDirectory) - i; 0482 if (pos < 0 || i > 65535) { 0483 qWarning() << "QZip: EndOfDirectory not found"; 0484 return; 0485 } 0486 0487 device->seek(pos); 0488 device->read((char *)&eod, sizeof(EndOfDirectory)); 0489 if (readUInt(eod.signature) == 0x06054b50) 0490 break; 0491 ++i; 0492 } 0493 0494 // have the eod 0495 start_of_directory = readUInt(eod.dir_start_offset); 0496 num_dir_entries = readUShort(eod.num_dir_entries); 0497 ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries); 0498 int comment_length = readUShort(eod.comment_length); 0499 if (comment_length != i) 0500 qWarning() << "QZip: failed to parse zip file."; 0501 comment = device->read(qMin(comment_length, i)); 0502 0503 0504 device->seek(start_of_directory); 0505 for (i = 0; i < num_dir_entries; ++i) { 0506 FileHeader header; 0507 int read = device->read((char *) &header.h, sizeof(CentralFileHeader)); 0508 if (read < (int)sizeof(CentralFileHeader)) { 0509 qWarning() << "QZip: Failed to read complete header, index may be incomplete"; 0510 break; 0511 } 0512 if (readUInt(header.h.signature) != 0x02014b50) { 0513 qWarning() << "QZip: invalid header signature, index may be incomplete"; 0514 break; 0515 } 0516 0517 int l = readUShort(header.h.file_name_length); 0518 header.file_name = device->read(l); 0519 if (header.file_name.length() != l) { 0520 qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete"; 0521 break; 0522 } 0523 l = readUShort(header.h.extra_field_length); 0524 header.extra_field = device->read(l); 0525 if (header.extra_field.length() != l) { 0526 qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete"; 0527 break; 0528 } 0529 l = readUShort(header.h.file_comment_length); 0530 header.file_comment = device->read(l); 0531 if (header.file_comment.length() != l) { 0532 qWarning() << "QZip: Failed to read file comment, index may be incomplete"; 0533 break; 0534 } 0535 0536 ZDEBUG("found file '%s'", header.file_name.data()); 0537 fileHeaders.append(header); 0538 } 0539 } 0540 0541 void MarbleZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/) 0542 { 0543 #ifndef NDEBUG 0544 static const char *entryTypes[] = { 0545 "directory", 0546 "file ", 0547 "symlink " }; 0548 ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? QByteArray(" -> " + contents).constData() : ""); 0549 #endif 0550 0551 if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) { 0552 status = MarbleZipWriter::FileOpenError; 0553 return; 0554 } 0555 device->seek(start_of_directory); 0556 0557 // don't compress small files 0558 MarbleZipWriter::CompressionPolicy compression = compressionPolicy; 0559 if (compressionPolicy == MarbleZipWriter::AutoCompress) { 0560 if (contents.length() < 64) 0561 compression = MarbleZipWriter::NeverCompress; 0562 else 0563 compression = MarbleZipWriter::AlwaysCompress; 0564 } 0565 0566 FileHeader header; 0567 memset(&header.h, 0, sizeof(CentralFileHeader)); 0568 writeUInt(header.h.signature, 0x02014b50); 0569 0570 writeUShort(header.h.version_needed, 0x14); 0571 writeUInt(header.h.uncompressed_size, contents.length()); 0572 writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime()); 0573 QByteArray data = contents; 0574 if (compression == MarbleZipWriter::AlwaysCompress) { 0575 writeUShort(header.h.compression_method, 8); 0576 0577 ulong len = contents.length(); 0578 // shamelessly copied form zlib 0579 len += (len >> 12) + (len >> 14) + 11; 0580 int res; 0581 do { 0582 data.resize(len); 0583 res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length()); 0584 0585 switch (res) { 0586 case Z_OK: 0587 data.resize(len); 0588 break; 0589 case Z_MEM_ERROR: 0590 qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping"); 0591 data.resize(0); 0592 break; 0593 case Z_BUF_ERROR: 0594 len *= 2; 0595 break; 0596 } 0597 } while (res == Z_BUF_ERROR); 0598 } 0599 // TODO add a check if data.length() > contents.length(). Then try to store the original and revert the compression method to be uncompressed 0600 writeUInt(header.h.compressed_size, data.length()); 0601 uint crc_32 = ::crc32(0, nullptr, 0); 0602 crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length()); 0603 writeUInt(header.h.crc_32, crc_32); 0604 0605 header.file_name = fileName.toLocal8Bit(); 0606 if (header.file_name.size() > 0xffff) { 0607 qWarning("QZip: Filename too long, chopping it to 65535 characters"); 0608 header.file_name = header.file_name.left(0xffff); 0609 } 0610 writeUShort(header.h.file_name_length, header.file_name.length()); 0611 //h.extra_field_length[2]; 0612 0613 writeUShort(header.h.version_made, 3 << 8); 0614 //uchar internal_file_attributes[2]; 0615 //uchar external_file_attributes[4]; 0616 quint32 mode = permissionsToMode(permissions); 0617 switch (type) { 0618 case File: mode |= S_IFREG; break; 0619 case Directory: mode |= S_IFDIR; break; 0620 case Symlink: mode |= S_IFLNK; break; 0621 } 0622 writeUInt(header.h.external_file_attributes, mode << 16); 0623 writeUInt(header.h.offset_local_header, start_of_directory); 0624 0625 0626 fileHeaders.append(header); 0627 0628 LocalFileHeader h = header.h.toLocalHeader(); 0629 device->write((const char *)&h, sizeof(LocalFileHeader)); 0630 device->write(header.file_name); 0631 device->write(data); 0632 start_of_directory = device->pos(); 0633 dirtyFileTree = true; 0634 } 0635 0636 ////////////////////////////// Reader 0637 0638 /*! 0639 \class QZipReader::FileInfo 0640 \internal 0641 Represents one entry in the zip table of contents. 0642 */ 0643 0644 /*! 0645 \variable FileInfo::filePath 0646 The full filepath inside the archive. 0647 */ 0648 0649 /*! 0650 \variable FileInfo::isDir 0651 A boolean type indicating if the entry is a directory. 0652 */ 0653 0654 /*! 0655 \variable FileInfo::isFile 0656 A boolean type, if it is one this entry is a file. 0657 */ 0658 0659 /*! 0660 \variable FileInfo::isSymLink 0661 A boolean type, if it is one this entry is symbolic link. 0662 */ 0663 0664 /*! 0665 \variable FileInfo::permissions 0666 A list of flags for the permissions of this entry. 0667 */ 0668 0669 /*! 0670 \variable FileInfo::crc32 0671 The calculated checksum as a crc32 type. 0672 */ 0673 0674 /*! 0675 \variable FileInfo::size 0676 The total size of the unpacked content. 0677 */ 0678 0679 /*! 0680 \variable FileInfo::d 0681 \internal 0682 private pointer. 0683 */ 0684 0685 /*! 0686 \class QZipReader 0687 \internal 0688 \since 4.5 0689 0690 \brief the QZipReader class provides a way to inspect the contents of a zip 0691 archive and extract individual files from it. 0692 0693 QZipReader can be used to read a zip archive either from a file or from any 0694 device. An in-memory QBuffer for instance. The reader can be used to read 0695 which files are in the archive using fileInfoList() and entryInfoAt() but 0696 also to extract individual files using fileData() or even to extract all 0697 files in the archive using extractAll() 0698 */ 0699 0700 /*! 0701 Create a new zip archive that operates on the \a fileName. The file will be 0702 opened with the \a mode. 0703 */ 0704 MarbleZipReader::MarbleZipReader(const QString &archive, QIODevice::OpenMode mode) 0705 { 0706 QScopedPointer<QFile> f(new QFile(archive)); 0707 f->open(mode); 0708 MarbleZipReader::Status status; 0709 if (f->error() == QFile::NoError) 0710 status = NoError; 0711 else { 0712 if (f->error() == QFile::ReadError) 0713 status = FileReadError; 0714 else if (f->error() == QFile::OpenError) 0715 status = FileOpenError; 0716 else if (f->error() == QFile::PermissionsError) 0717 status = FilePermissionsError; 0718 else 0719 status = FileError; 0720 } 0721 0722 d = new MarbleZipReaderPrivate(f.data(), /*ownDevice=*/true); 0723 f.take(); 0724 d->status = status; 0725 } 0726 0727 /*! 0728 Create a new zip archive that operates on the archive found in \a device. 0729 You have to open the device previous to calling the constructor and only a 0730 device that is readable will be scanned for zip filecontent. 0731 */ 0732 MarbleZipReader::MarbleZipReader(QIODevice *device) 0733 : d(new MarbleZipReaderPrivate(device, /*ownDevice=*/false)) 0734 { 0735 Q_ASSERT(device); 0736 } 0737 0738 /*! 0739 Desctructor 0740 */ 0741 MarbleZipReader::~MarbleZipReader() 0742 { 0743 close(); 0744 delete d; 0745 } 0746 0747 /*! 0748 Returns device used for reading zip archive. 0749 */ 0750 QIODevice* MarbleZipReader::device() const 0751 { 0752 return d->device; 0753 } 0754 0755 /*! 0756 Returns true if the user can read the file; otherwise returns false. 0757 */ 0758 bool MarbleZipReader::isReadable() const 0759 { 0760 return d->device->isReadable(); 0761 } 0762 0763 /*! 0764 Returns true if the file exists; otherwise returns false. 0765 */ 0766 bool MarbleZipReader::exists() const 0767 { 0768 QFile *f = qobject_cast<QFile*> (d->device); 0769 if (f == nullptr) 0770 return true; 0771 return f->exists(); 0772 } 0773 0774 /*! 0775 Returns the list of files the archive contains. 0776 */ 0777 QList<MarbleZipReader::FileInfo> MarbleZipReader::fileInfoList() const 0778 { 0779 d->scanFiles(); 0780 QList<MarbleZipReader::FileInfo> files; 0781 for (int i = 0; i < d->fileHeaders.size(); ++i) { 0782 MarbleZipReader::FileInfo fi; 0783 d->fillFileInfo(i, fi); 0784 files.append(fi); 0785 } 0786 return files; 0787 0788 } 0789 0790 /*! 0791 Return the number of items in the zip archive. 0792 */ 0793 int MarbleZipReader::count() const 0794 { 0795 d->scanFiles(); 0796 return d->fileHeaders.count(); 0797 } 0798 0799 /*! 0800 Returns a FileInfo of an entry in the zipfile. 0801 The \a index is the index into the directory listing of the zipfile. 0802 Returns an invalid FileInfo if \a index is out of boundaries. 0803 0804 \sa fileInfoList() 0805 */ 0806 MarbleZipReader::FileInfo MarbleZipReader::entryInfoAt(int index) const 0807 { 0808 d->scanFiles(); 0809 MarbleZipReader::FileInfo fi; 0810 if (index >= 0 && index < d->fileHeaders.count()) 0811 d->fillFileInfo(index, fi); 0812 return fi; 0813 } 0814 0815 /*! 0816 Fetch the file contents from the zip archive and return the uncompressed bytes. 0817 */ 0818 QByteArray MarbleZipReader::fileData(const QString &fileName) const 0819 { 0820 d->scanFiles(); 0821 int i; 0822 for (i = 0; i < d->fileHeaders.size(); ++i) { 0823 if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName) 0824 break; 0825 } 0826 if (i == d->fileHeaders.size()) 0827 return QByteArray(); 0828 0829 FileHeader header = d->fileHeaders.at(i); 0830 0831 int compressed_size = readUInt(header.h.compressed_size); 0832 int uncompressed_size = readUInt(header.h.uncompressed_size); 0833 int start = readUInt(header.h.offset_local_header); 0834 //qDebug("uncompressing file %d: local header at %d", i, start); 0835 0836 d->device->seek(start); 0837 LocalFileHeader lh; 0838 d->device->read((char *)&lh, sizeof(LocalFileHeader)); 0839 uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length); 0840 d->device->seek(d->device->pos() + skip); 0841 0842 int compression_method = readUShort(lh.compression_method); 0843 //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size); 0844 0845 //qDebug("file at %lld", d->device->pos()); 0846 QByteArray compressed = d->device->read(compressed_size); 0847 if (compression_method == 0) { 0848 // no compression 0849 compressed.truncate(uncompressed_size); 0850 return compressed; 0851 } else if (compression_method == 8) { 0852 // Deflate 0853 //qDebug("compressed=%d", compressed.size()); 0854 compressed.truncate(compressed_size); 0855 QByteArray baunzip; 0856 ulong len = qMax(uncompressed_size, 1); 0857 int res; 0858 do { 0859 baunzip.resize(len); 0860 res = inflate((uchar*)baunzip.data(), &len, 0861 (uchar*)compressed.constData(), compressed_size); 0862 0863 switch (res) { 0864 case Z_OK: 0865 if ((int)len != baunzip.size()) 0866 baunzip.resize(len); 0867 break; 0868 case Z_MEM_ERROR: 0869 qWarning("QZip: Z_MEM_ERROR: Not enough memory"); 0870 break; 0871 case Z_BUF_ERROR: 0872 len *= 2; 0873 break; 0874 case Z_DATA_ERROR: 0875 qWarning("QZip: Z_DATA_ERROR: Input data is corrupted"); 0876 break; 0877 } 0878 } while (res == Z_BUF_ERROR); 0879 return baunzip; 0880 } 0881 qWarning() << "QZip: Unknown compression method"; 0882 return QByteArray(); 0883 } 0884 0885 /*! 0886 Extracts the full contents of the zip file into \a destinationDir on 0887 the local filesystem. 0888 In case writing or linking a file fails, the extraction will be aborted. 0889 */ 0890 bool MarbleZipReader::extractAll(const QString &destinationDir) const 0891 { 0892 QDir baseDir(destinationDir); 0893 0894 // create directories first 0895 QList<FileInfo> allFiles = fileInfoList(); 0896 for (const FileInfo& fi: allFiles) { 0897 const QString absPath = destinationDir + QDir::separator() + fi.filePath; 0898 if (fi.isDir) { 0899 if (!baseDir.mkpath(fi.filePath)) 0900 return false; 0901 if (!QFile::setPermissions(absPath, fi.permissions)) 0902 return false; 0903 } 0904 } 0905 0906 // set up symlinks 0907 for (const FileInfo& fi: allFiles) { 0908 const QString absPath = destinationDir + QDir::separator() + fi.filePath; 0909 if (fi.isSymLink) { 0910 QString destination = QFile::decodeName(fileData(fi.filePath)); 0911 if (destination.isEmpty()) 0912 return false; 0913 QFileInfo linkFi(absPath); 0914 if (!QFile::exists(linkFi.absolutePath())) 0915 QDir::root().mkpath(linkFi.absolutePath()); 0916 if (!QFile::link(destination, absPath)) 0917 return false; 0918 /* cannot change permission of links 0919 if (!QFile::setPermissions(absPath, fi.permissions)) 0920 return false; 0921 */ 0922 } 0923 } 0924 0925 for (const FileInfo& fi: allFiles) { 0926 const QString absPath = destinationDir + QDir::separator() + fi.filePath; 0927 if (fi.isFile) { 0928 QDir::root().mkpath(QFileInfo(absPath).dir().absolutePath()); 0929 QFile f(absPath); 0930 if (!f.open(QIODevice::WriteOnly)) 0931 return false; 0932 f.write(fileData(fi.filePath)); 0933 f.setPermissions(fi.permissions); 0934 f.close(); 0935 } 0936 } 0937 0938 return true; 0939 } 0940 0941 /*! 0942 \enum QZipReader::Status 0943 0944 The following status values are possible: 0945 0946 \value NoError No error occurred. 0947 \value FileReadError An error occurred when reading from the file. 0948 \value FileOpenError The file could not be opened. 0949 \value FilePermissionsError The file could not be accessed. 0950 \value FileError Another file error occurred. 0951 */ 0952 0953 /*! 0954 Returns a status code indicating the first error that was met by QZipReader, 0955 or QZipReader::NoError if no error occurred. 0956 */ 0957 MarbleZipReader::Status MarbleZipReader::status() const 0958 { 0959 return d->status; 0960 } 0961 0962 /*! 0963 Close the zip file. 0964 */ 0965 void MarbleZipReader::close() 0966 { 0967 d->device->close(); 0968 } 0969 0970 ////////////////////////////// Writer 0971 0972 /*! 0973 \class QZipWriter 0974 \internal 0975 \since 4.5 0976 0977 \brief the QZipWriter class provides a way to create a new zip archive. 0978 0979 QZipWriter can be used to create a zip archive containing any number of files 0980 and directories. The files in the archive will be compressed in a way that is 0981 compatible with common zip reader applications. 0982 */ 0983 0984 0985 /*! 0986 Create a new zip archive that operates on the \a archive filename. The file will 0987 be opened with the \a mode. 0988 \sa isValid() 0989 */ 0990 MarbleZipWriter::MarbleZipWriter(const QString &fileName, QIODevice::OpenMode mode) 0991 { 0992 QScopedPointer<QFile> f(new QFile(fileName)); 0993 f->open(mode); 0994 MarbleZipWriter::Status status; 0995 if (f->error() == QFile::NoError) 0996 status = MarbleZipWriter::NoError; 0997 else { 0998 if (f->error() == QFile::WriteError) 0999 status = MarbleZipWriter::FileWriteError; 1000 else if (f->error() == QFile::OpenError) 1001 status = MarbleZipWriter::FileOpenError; 1002 else if (f->error() == QFile::PermissionsError) 1003 status = MarbleZipWriter::FilePermissionsError; 1004 else 1005 status = MarbleZipWriter::FileError; 1006 } 1007 1008 d = new MarbleZipWriterPrivate(f.data(), /*ownDevice=*/true); 1009 f.take(); 1010 d->status = status; 1011 } 1012 1013 /*! 1014 Create a new zip archive that operates on the archive found in \a device. 1015 You have to open the device previous to calling the constructor and 1016 only a device that is readable will be scanned for zip filecontent. 1017 */ 1018 MarbleZipWriter::MarbleZipWriter(QIODevice *device) 1019 : d(new MarbleZipWriterPrivate(device, /*ownDevice=*/false)) 1020 { 1021 Q_ASSERT(device); 1022 } 1023 1024 MarbleZipWriter::~MarbleZipWriter() 1025 { 1026 close(); 1027 delete d; 1028 } 1029 1030 /*! 1031 Returns device used for writing zip archive. 1032 */ 1033 QIODevice* MarbleZipWriter::device() const 1034 { 1035 return d->device; 1036 } 1037 1038 /*! 1039 Returns true if the user can write to the archive; otherwise returns false. 1040 */ 1041 bool MarbleZipWriter::isWritable() const 1042 { 1043 return d->device->isWritable(); 1044 } 1045 1046 /*! 1047 Returns true if the file exists; otherwise returns false. 1048 */ 1049 bool MarbleZipWriter::exists() const 1050 { 1051 QFile *f = qobject_cast<QFile*> (d->device); 1052 if (f == nullptr) 1053 return true; 1054 return f->exists(); 1055 } 1056 1057 /*! 1058 \enum QZipWriter::Status 1059 1060 The following status values are possible: 1061 1062 \var NoError 1063 No error occurred. 1064 \var FileWriteError 1065 An error occurred when writing to the device. 1066 \var FileOpenError 1067 The file could not be opened. 1068 \var FilePermissionsError 1069 The file could not be accessed. 1070 \var FileError 1071 Another file error occurred. 1072 */ 1073 1074 /*! 1075 Returns a status code indicating the first error that was met by QZipWriter, 1076 or QZipWriter::NoError if no error occurred. 1077 */ 1078 MarbleZipWriter::Status MarbleZipWriter::status() const 1079 { 1080 return d->status; 1081 } 1082 1083 /*! 1084 \enum CompressionPolicy 1085 1086 \var AlwaysCompress 1087 A file that is added is compressed. 1088 \var NeverCompress 1089 A file that is added will be stored without changes. 1090 \var AutoCompress 1091 A file that is added will be compressed only if that will give a smaller file. 1092 */ 1093 1094 /*! 1095 Sets the policy for compressing newly added files to the new \a policy. 1096 1097 \note the default policy is AlwaysCompress 1098 1099 \sa compressionPolicy() 1100 \sa addFile() 1101 */ 1102 void MarbleZipWriter::setCompressionPolicy(CompressionPolicy policy) 1103 { 1104 d->compressionPolicy = policy; 1105 } 1106 1107 /*! 1108 Returns the currently set compression policy. 1109 \sa setCompressionPolicy() 1110 \sa addFile() 1111 */ 1112 MarbleZipWriter::CompressionPolicy MarbleZipWriter::compressionPolicy() const 1113 { 1114 return d->compressionPolicy; 1115 } 1116 1117 /*! 1118 Sets the permissions that will be used for newly added files. 1119 1120 \note the default permissions are QFile::ReadOwner | QFile::WriteOwner. 1121 1122 \sa creationPermissions() 1123 \sa addFile() 1124 */ 1125 void MarbleZipWriter::setCreationPermissions(QFile::Permissions permissions) 1126 { 1127 d->permissions = permissions; 1128 } 1129 1130 /*! 1131 Returns the currently set creation permissions. 1132 1133 \sa setCreationPermissions() 1134 \sa addFile() 1135 */ 1136 QFile::Permissions MarbleZipWriter::creationPermissions() const 1137 { 1138 return d->permissions; 1139 } 1140 1141 /*! 1142 Add a file to the archive with \a data as the file contents. 1143 The file will be stored in the archive using the \a fileName which 1144 includes the full path in the archive. 1145 1146 The new file will get the file permissions based on the current 1147 creationPermissions and it will be compressed using the zip compression 1148 based on the current compression policy. 1149 1150 \sa setCreationPermissions() 1151 \sa setCompressionPolicy() 1152 */ 1153 void MarbleZipWriter::addFile(const QString &fileName, const QByteArray &data) 1154 { 1155 d->addEntry(MarbleZipWriterPrivate::File, QDir::fromNativeSeparators(fileName), data); 1156 } 1157 1158 /*! 1159 Add a file to the archive with \a device as the source of the contents. 1160 The contents returned from QIODevice::readAll() will be used as the 1161 filedata. 1162 The file will be stored in the archive using the \a fileName which 1163 includes the full path in the archive. 1164 */ 1165 void MarbleZipWriter::addFile(const QString &fileName, QIODevice *device) 1166 { 1167 Q_ASSERT(device); 1168 QIODevice::OpenMode mode = device->openMode(); 1169 bool opened = false; 1170 if ((mode & QIODevice::ReadOnly) == 0) { 1171 opened = true; 1172 if (! device->open(QIODevice::ReadOnly)) { 1173 d->status = FileOpenError; 1174 return; 1175 } 1176 } 1177 d->addEntry(MarbleZipWriterPrivate::File, QDir::fromNativeSeparators(fileName), device->readAll()); 1178 if (opened) 1179 device->close(); 1180 } 1181 1182 /*! 1183 Create a new directory in the archive with the specified \a dirName and 1184 the \a permissions; 1185 */ 1186 void MarbleZipWriter::addDirectory(const QString &dirName) 1187 { 1188 QString name(QDir::fromNativeSeparators(dirName)); 1189 // separator is mandatory 1190 if (!name.endsWith(QLatin1Char('/'))) 1191 name.append(QLatin1Char('/')); 1192 d->addEntry(MarbleZipWriterPrivate::Directory, name, QByteArray()); 1193 } 1194 1195 /*! 1196 Create a new symbolic link in the archive with the specified \a dirName 1197 and the \a permissions; 1198 A symbolic link contains the destination (relative) path and name. 1199 */ 1200 void MarbleZipWriter::addSymLink(const QString &fileName, const QString &destination) 1201 { 1202 d->addEntry(MarbleZipWriterPrivate::Symlink, QDir::fromNativeSeparators(fileName), QFile::encodeName(destination)); 1203 } 1204 1205 /*! 1206 Closes the zip file. 1207 */ 1208 void MarbleZipWriter::close() 1209 { 1210 if (!(d->device->openMode() & QIODevice::WriteOnly)) { 1211 d->device->close(); 1212 return; 1213 } 1214 1215 //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size()); 1216 d->device->seek(d->start_of_directory); 1217 // write new directory 1218 for (int i = 0; i < d->fileHeaders.size(); ++i) { 1219 const FileHeader &header = d->fileHeaders.at(i); 1220 d->device->write((const char *)&header.h, sizeof(CentralFileHeader)); 1221 d->device->write(header.file_name); 1222 d->device->write(header.extra_field); 1223 d->device->write(header.file_comment); 1224 } 1225 int dir_size = d->device->pos() - d->start_of_directory; 1226 // write end of directory 1227 EndOfDirectory eod; 1228 memset(&eod, 0, sizeof(EndOfDirectory)); 1229 writeUInt(eod.signature, 0x06054b50); 1230 //uchar this_disk[2]; 1231 //uchar start_of_directory_disk[2]; 1232 writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size()); 1233 writeUShort(eod.num_dir_entries, d->fileHeaders.size()); 1234 writeUInt(eod.directory_size, dir_size); 1235 writeUInt(eod.dir_start_offset, d->start_of_directory); 1236 writeUShort(eod.comment_length, d->comment.length()); 1237 1238 d->device->write((const char *)&eod, sizeof(EndOfDirectory)); 1239 d->device->write(d->comment); 1240 d->device->close(); 1241 } 1242 1243 } 1244 1245 #endif // QT_NO_TEXTODFWRITER