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