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