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