File indexing completed on 2024-04-21 03:52:33

0001 /* This file is part of the KDE libraries
0002    SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0003    SPDX-FileCopyrightText: 2002 Holger Schroeder <holger-kde@holgis.net>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kzip.h"
0009 #include "karchive_p.h"
0010 #include "kcompressiondevice.h"
0011 #include "klimitediodevice_p.h"
0012 #include "loggingcategory.h"
0013 
0014 #include <QByteArray>
0015 #include <QDate>
0016 #include <QDebug>
0017 #include <QDir>
0018 #include <QFile>
0019 #include <QHash>
0020 #include <QList>
0021 #include <QtEndian>
0022 #include <qplatformdefs.h>
0023 
0024 #include <string.h>
0025 #include <time.h>
0026 #include <zlib.h>
0027 
0028 #ifndef QT_STAT_LNK
0029 #define QT_STAT_LNK 0120000
0030 #endif // QT_STAT_LNK
0031 
0032 static const int max_path_len = 4095; // maximum number of character a path may contain
0033 
0034 static void transformToMsDos(const QDateTime &_dt, char *buffer)
0035 {
0036     const QDateTime dt = _dt.isValid() ? _dt : QDateTime::currentDateTime();
0037     /* clang-format off */
0038     const quint16 time = (dt.time().hour() << 11) // 5 bit hour
0039                          | (dt.time().minute() << 5) // 6 bit minute
0040                          | (dt.time().second() >> 1); // 5 bit double seconds
0041     /* clang-format on */
0042 
0043     buffer[0] = char(time);
0044     buffer[1] = char(time >> 8);
0045 
0046     /* clang-format off */
0047     const quint16 date = ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
0048                          | (dt.date().month() << 5) // 4 bit month
0049                          | (dt.date().day()); // 5 bit day
0050     /* clang-format on */
0051 
0052     buffer[2] = char(date);
0053     buffer[3] = char(date >> 8);
0054 }
0055 
0056 static uint transformFromMsDos(const char *buffer)
0057 {
0058     quint16 time = (uchar)buffer[0] | ((uchar)buffer[1] << 8);
0059     int h = time >> 11;
0060     int m = (time & 0x7ff) >> 5;
0061     int s = (time & 0x1f) * 2;
0062     QTime qt(h, m, s);
0063 
0064     quint16 date = (uchar)buffer[2] | ((uchar)buffer[3] << 8);
0065     int y = (date >> 9) + 1980;
0066     int o = (date & 0x1ff) >> 5;
0067     int d = (date & 0x1f);
0068     QDate qd(y, o, d);
0069 
0070     QDateTime dt(qd, qt);
0071     return dt.toSecsSinceEpoch();
0072 }
0073 
0074 // == parsing routines for zip headers
0075 
0076 /** all relevant information about parsing file information */
0077 struct ParseFileInfo {
0078     // file related info
0079     mode_t perm; // permissions of this file
0080     // TODO: use quint32 instead of a uint?
0081     uint atime; // last access time (UNIX format)
0082     uint mtime; // modification time (UNIX format)
0083     uint ctime; // creation time (UNIX format)
0084     int uid; // user id (-1 if not specified)
0085     int gid; // group id (-1 if not specified)
0086     QByteArray guessed_symlink; // guessed symlink target
0087     int extralen; // length of extra field
0088 
0089     // parsing related info
0090     bool exttimestamp_seen; // true if extended timestamp extra field
0091     // has been parsed
0092     bool newinfounix_seen; // true if Info-ZIP Unix New extra field has
0093     // been parsed
0094 
0095     // file sizes from a ZIP64 extra field
0096     quint64 uncompressedSize = 0;
0097     quint64 compressedSize = 0;
0098 
0099     ParseFileInfo()
0100         : perm(0100644)
0101         , uid(-1)
0102         , gid(-1)
0103         , extralen(0)
0104         , exttimestamp_seen(false)
0105         , newinfounix_seen(false)
0106     {
0107         ctime = mtime = atime = time(nullptr);
0108     }
0109 };
0110 
0111 /** updates the parse information with the given extended timestamp extra field.
0112  * @param buffer start content of buffer known to contain an extended
0113  *  timestamp extra field (without magic & size)
0114  * @param size size of field content (must not count magic and size entries)
0115  * @param islocal true if this is a local field, false if central
0116  * @param pfi ParseFileInfo object to be updated
0117  * @return true if processing was successful
0118  */
0119 static bool parseExtTimestamp(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
0120 {
0121     if (size < 1) {
0122         // qCDebug(KArchiveLog) << "premature end of extended timestamp (#1)";
0123         return false;
0124     } /*end if*/
0125     int flags = *buffer; // read flags
0126     buffer += 1;
0127     size -= 1;
0128 
0129     if (flags & 1) { // contains modification time
0130         if (size < 4) {
0131             // qCDebug(KArchiveLog) << "premature end of extended timestamp (#2)";
0132             return false;
0133         } /*end if*/
0134         pfi.mtime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
0135         buffer += 4;
0136         size -= 4;
0137     } /*end if*/
0138     // central extended field cannot contain more than the modification time
0139     // even if other flags are set
0140     if (!islocal) {
0141         pfi.exttimestamp_seen = true;
0142         return true;
0143     } /*end if*/
0144 
0145     if (flags & 2) { // contains last access time
0146         if (size < 4) {
0147             // qCDebug(KArchiveLog) << "premature end of extended timestamp (#3)";
0148             return true;
0149         } /*end if*/
0150         pfi.atime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
0151         buffer += 4;
0152         size -= 4;
0153     } /*end if*/
0154 
0155     if (flags & 4) { // contains creation time
0156         if (size < 4) {
0157             // qCDebug(KArchiveLog) << "premature end of extended timestamp (#4)";
0158             return true;
0159         } /*end if*/
0160         pfi.ctime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
0161         buffer += 4;
0162     } /*end if*/
0163 
0164     pfi.exttimestamp_seen = true;
0165     return true;
0166 }
0167 
0168 /** updates the parse information with the given Info-ZIP Unix old extra field.
0169  * @param buffer start of content of buffer known to contain an Info-ZIP
0170  *  Unix old extra field (without magic & size)
0171  * @param size size of field content (must not count magic and size entries)
0172  * @param islocal true if this is a local field, false if central
0173  * @param pfi ParseFileInfo object to be updated
0174  * @return true if processing was successful
0175  */
0176 static bool parseInfoZipUnixOld(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
0177 {
0178     // spec mandates to omit this field if one of the newer fields are available
0179     if (pfi.exttimestamp_seen || pfi.newinfounix_seen) {
0180         return true;
0181     }
0182 
0183     if (size < 8) {
0184         // qCDebug(KArchiveLog) << "premature end of Info-ZIP unix extra field old";
0185         return false;
0186     }
0187 
0188     pfi.atime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
0189     buffer += 4;
0190     pfi.mtime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
0191     buffer += 4;
0192     if (islocal && size >= 12) {
0193         pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
0194         buffer += 2;
0195         pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
0196         buffer += 2;
0197     } /*end if*/
0198     return true;
0199 }
0200 
0201 #if 0 // not needed yet
0202 /** updates the parse information with the given Info-ZIP Unix new extra field.
0203  * @param buffer start of content of buffer known to contain an Info-ZIP
0204  *      Unix new extra field (without magic & size)
0205  * @param size size of field content (must not count magic and size entries)
0206  * @param islocal true if this is a local field, false if central
0207  * @param pfi ParseFileInfo object to be updated
0208  * @return true if processing was successful
0209  */
0210 static bool parseInfoZipUnixNew(const char *buffer, int size, bool islocal,
0211                                 ParseFileInfo &pfi)
0212 {
0213     if (!islocal) { // contains nothing in central field
0214         pfi.newinfounix = true;
0215         return true;
0216     }
0217 
0218     if (size < 4) {
0219         qCDebug(KArchiveLog) << "premature end of Info-ZIP unix extra field new";
0220         return false;
0221     }
0222 
0223     pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
0224     buffer += 2;
0225     pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
0226     buffer += 2;
0227 
0228     pfi.newinfounix = true;
0229     return true;
0230 }
0231 #endif
0232 
0233 /**
0234  * parses the extra field
0235  * @param buffer start of buffer where the extra field is to be found
0236  * @param size size of the extra field
0237  * @param islocal true if this is part of a local header, false if of central
0238  * @param pfi ParseFileInfo object which to write the results into
0239  * @return true if parsing was successful
0240  */
0241 static bool parseExtraField(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
0242 {
0243     // extra field in central directory doesn't contain useful data, so we
0244     // don't bother parsing it
0245     if (!islocal) {
0246         return true;
0247     }
0248 
0249     while (size >= 4) { // as long as a potential extra field can be read
0250         int magic = (uchar)buffer[0] | (uchar)buffer[1] << 8;
0251         buffer += 2;
0252         int fieldsize = (uchar)buffer[0] | (uchar)buffer[1] << 8;
0253         buffer += 2;
0254         size -= 4;
0255 
0256         if (fieldsize > size) {
0257             // qCDebug(KArchiveLog) << "fieldsize: " << fieldsize << " size: " << size;
0258             // qCDebug(KArchiveLog) << "premature end of extra fields reached";
0259             break;
0260         }
0261 
0262         switch (magic) {
0263         case 0x0001: // ZIP64 extended file information
0264             if (size >= 8) {
0265                 pfi.uncompressedSize = qFromLittleEndian(*reinterpret_cast<const quint64 *>(buffer));
0266             }
0267             if (size >= 16) {
0268                 pfi.compressedSize = qFromLittleEndian(*reinterpret_cast<const quint64 *>(buffer + 8));
0269             }
0270             break;
0271         case 0x5455: // extended timestamp
0272             if (!parseExtTimestamp(buffer, fieldsize, islocal, pfi)) {
0273                 return false;
0274             }
0275             break;
0276         case 0x5855: // old Info-ZIP unix extra field
0277             if (!parseInfoZipUnixOld(buffer, fieldsize, islocal, pfi)) {
0278                 return false;
0279             }
0280             break;
0281 #if 0 // not needed yet
0282         case 0x7855:        // new Info-ZIP unix extra field
0283             if (!parseInfoZipUnixNew(buffer, fieldsize, islocal, pfi)) {
0284                 return false;
0285             }
0286             break;
0287 #endif
0288         default:
0289             /* ignore everything else */
0290             ;
0291         } /*end switch*/
0292 
0293         buffer += fieldsize;
0294         size -= fieldsize;
0295     } /*wend*/
0296     return true;
0297 }
0298 
0299 /**
0300  * Checks if a token for a central or local header has been found and resets
0301  * the device to the begin of the token. If a token for the data descriptor is
0302  * found it is assumed there is a central or local header token starting right
0303  * behind the data descriptor, and the device is set accordingly to the begin
0304  * of that token.
0305  * To be called when a 'P' has been found.
0306  * @param buffer start of buffer with the 3 bytes behind 'P'
0307  * @param dev device that is read from
0308  * @param dataDescriptor only search for data descriptor
0309  * @return true if a local or central header begin is or could be reached
0310  */
0311 static bool handlePossibleHeaderBegin(const char *buffer, QIODevice *dev, bool dataDescriptor)
0312 {
0313     // we have to detect three magic tokens here:
0314     // PK34 for the next local header in case there is no data descriptor
0315     // PK12 for the central header in case there is no data descriptor
0316     // PK78 for the data descriptor in case it is following the compressed data
0317     // TODO: optimize using 32bit const data for comparison instead of byte-wise,
0318     // given we run at least on 32bit CPUs
0319 
0320     if (buffer[0] == 'K') {
0321         if (buffer[1] == 7 && buffer[2] == 8) {
0322             // data descriptor token found
0323             dev->seek(dev->pos() + 12); // skip the 'data_descriptor'
0324             return true;
0325         }
0326 
0327         if (!dataDescriptor
0328             && ((buffer[1] == 1 && buffer[2] == 2) //
0329                 || (buffer[1] == 3 && buffer[2] == 4))) {
0330             // central/local header token found
0331             dev->seek(dev->pos() - 4);
0332             // go back 4 bytes, so that the magic bytes can be found
0333             // in the next cycle...
0334             return true;
0335         }
0336     }
0337     return false;
0338 }
0339 
0340 /**
0341  * Reads the device forwards from the current pos until a token for a central or
0342  * local header has been found or is to be assumed.
0343  * @param dev device that is read from
0344  * @return true if a local or central header token could be reached, false on error
0345  */
0346 static bool seekToNextHeaderToken(QIODevice *dev, bool dataDescriptor)
0347 {
0348     bool headerTokenFound = false;
0349     char buffer[3];
0350 
0351     while (!headerTokenFound) {
0352         int n = dev->read(buffer, 1);
0353         if (n < 1) {
0354             // qCWarning(KArchiveLog) << "Invalid ZIP file. Unexpected end of file. (#2)";
0355             return false;
0356         }
0357 
0358         if (buffer[0] != 'P') {
0359             continue;
0360         }
0361 
0362         n = dev->read(buffer, 3);
0363         if (n < 3) {
0364             // qCWarning(KArchiveLog) << "Invalid ZIP file. Unexpected end of file. (#3)";
0365             return false;
0366         }
0367 
0368         if (handlePossibleHeaderBegin(buffer, dev, dataDescriptor)) {
0369             headerTokenFound = true;
0370         } else {
0371             for (int i = 0; i < 3; ++i) {
0372                 if (buffer[i] == 'P') {
0373                     // We have another P character so we must go back a little to check if it is a magic
0374                     dev->seek(dev->pos() - 3 + i);
0375                     break;
0376                 }
0377             }
0378         }
0379     }
0380     return true;
0381 }
0382 
0383 ////////////////////////////////////////////////////////////////////////
0384 /////////////////////////// KZip ///////////////////////////////////////
0385 ////////////////////////////////////////////////////////////////////////
0386 
0387 class Q_DECL_HIDDEN KZip::KZipPrivate
0388 {
0389 public:
0390     KZipPrivate()
0391         : m_crc(0)
0392         , m_currentFile(nullptr)
0393         , m_currentDev(nullptr)
0394         , m_compression(8)
0395         , m_extraField(KZip::NoExtraField)
0396         , m_offset(0)
0397     {
0398     }
0399 
0400     unsigned long m_crc; // checksum
0401     KZipFileEntry *m_currentFile; // file currently being written
0402     QIODevice *m_currentDev; // filterdev used to write to the above file
0403     QList<KZipFileEntry *> m_fileList; // flat list of all files, for the index (saves a recursive method ;)
0404     int m_compression;
0405     KZip::ExtraField m_extraField;
0406     // m_offset holds the offset of the place in the zip,
0407     // where new data can be appended. after openarchive it points to 0, when in
0408     // writeonly mode, or it points to the beginning of the central directory.
0409     // each call to writefile updates this value.
0410     quint64 m_offset;
0411 };
0412 
0413 KZip::KZip(const QString &fileName)
0414     : KArchive(fileName)
0415     , d(new KZipPrivate)
0416 {
0417 }
0418 
0419 KZip::KZip(QIODevice *dev)
0420     : KArchive(dev)
0421     , d(new KZipPrivate)
0422 {
0423 }
0424 
0425 KZip::~KZip()
0426 {
0427     // qCDebug(KArchiveLog) << this;
0428     if (isOpen()) {
0429         close();
0430     }
0431     delete d;
0432 }
0433 
0434 bool KZip::openArchive(QIODevice::OpenMode mode)
0435 {
0436     // qCDebug(KArchiveLog);
0437     d->m_fileList.clear();
0438 
0439     if (mode == QIODevice::WriteOnly) {
0440         return true;
0441     }
0442 
0443     char buffer[47];
0444 
0445     // Check that it's a valid ZIP file
0446     // KArchive::open() opened the underlying device already.
0447 
0448     quint64 offset = 0; // holds offset, where we read
0449     // contains information gathered from the local file headers
0450     QHash<QByteArray, ParseFileInfo> pfi_map;
0451 
0452     QIODevice *dev = device();
0453 
0454     // We set a bool for knowing if we are allowed to skip the start of the file
0455     bool startOfFile = true;
0456 
0457     for (;;) { // repeat until 'end of entries' signature is reached
0458         // qCDebug(KArchiveLog) << "loop starts";
0459         // qCDebug(KArchiveLog) << "dev->pos() now : " << dev->pos();
0460         int n = dev->read(buffer, 4);
0461 
0462         if (n < 4) {
0463             setErrorString(tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(1));
0464             return false;
0465         }
0466 
0467         if (!memcmp(buffer, "PK\5\6", 4)) { // 'end of entries'
0468             // qCDebug(KArchiveLog) << "PK56 found end of archive";
0469             startOfFile = false;
0470             break;
0471         }
0472 
0473         if (!memcmp(buffer, "PK\3\4", 4)) { // local file header
0474             // qCDebug(KArchiveLog) << "PK34 found local file header";
0475             startOfFile = false;
0476             // can this fail ???
0477             dev->seek(dev->pos() + 2); // skip 'version needed to extract'
0478 
0479             // read static header stuff
0480             n = dev->read(buffer, 24);
0481             if (n < 24) {
0482                 setErrorString(tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(4));
0483                 return false;
0484             }
0485 
0486             int gpf = (uchar)buffer[0]; // "general purpose flag" not "general protection fault" ;-)
0487             int compression_mode = (uchar)buffer[2] | (uchar)buffer[3] << 8;
0488             uint mtime = transformFromMsDos(buffer + 4);
0489 
0490             const qint64 compr_size = uint(uchar(buffer[12])) | uint(uchar(buffer[13])) << 8 | uint(uchar(buffer[14])) << 16 | uint(uchar(buffer[15])) << 24;
0491             const qint64 uncomp_size = uint(uchar(buffer[16])) | uint(uchar(buffer[17])) << 8 | uint(uchar(buffer[18])) << 16 | uint(uchar(buffer[19])) << 24;
0492             const int namelen = uint(uchar(buffer[20])) | uint(uchar(buffer[21])) << 8;
0493             const int extralen = uint(uchar(buffer[22])) | uint(uchar(buffer[23])) << 8;
0494 
0495             /*
0496               qCDebug(KArchiveLog) << "general purpose bit flag: " << gpf;
0497               qCDebug(KArchiveLog) << "compressed size: " << compr_size;
0498               qCDebug(KArchiveLog) << "uncompressed size: " << uncomp_size;
0499               qCDebug(KArchiveLog) << "namelen: " << namelen;
0500               qCDebug(KArchiveLog) << "extralen: " << extralen;
0501               qCDebug(KArchiveLog) << "archive size: " << dev->size();
0502             */
0503 
0504             // read fileName
0505             if (namelen <= 0) {
0506                 setErrorString(tr("Invalid ZIP file. Negative name length"));
0507                 return false;
0508             }
0509             QByteArray fileName = dev->read(namelen);
0510             if (fileName.size() < namelen) {
0511                 setErrorString(tr("Invalid ZIP file. Name not completely read (#2)"));
0512                 return false;
0513             }
0514 
0515             ParseFileInfo pfi;
0516             pfi.mtime = mtime;
0517 
0518             // read and parse the beginning of the extra field,
0519             // skip rest of extra field in case it is too long
0520             unsigned int extraFieldEnd = dev->pos() + extralen;
0521             pfi.extralen = extralen;
0522             int handledextralen = qMin(extralen, (int)sizeof buffer);
0523 
0524             // if (handledextralen)
0525             //    qCDebug(KArchiveLog) << "handledextralen: " << handledextralen;
0526 
0527             n = dev->read(buffer, handledextralen);
0528             // no error msg necessary as we deliberately truncate the extra field
0529             if (!parseExtraField(buffer, n, true, pfi)) {
0530                 setErrorString(tr("Invalid ZIP File. Broken ExtraField."));
0531                 return false;
0532             }
0533 
0534             // jump to end of extra field
0535             dev->seek(extraFieldEnd);
0536 
0537             // we have to take care of the 'general purpose bit flag'.
0538             // if bit 3 is set, the header doesn't contain the length of
0539             // the file and we look for the signature 'PK\7\8'.
0540             if (gpf & 8) {
0541                 // here we have to read through the compressed data to find
0542                 // the next PKxx
0543                 if (!seekToNextHeaderToken(dev, true)) {
0544                     setErrorString(tr("Could not seek to next header token"));
0545                     return false;
0546                 }
0547             } else {
0548                 // here we skip the compressed data and jump to the next header
0549                 // qCDebug(KArchiveLog) << "general purpose bit flag indicates, that local file header contains valid size";
0550                 bool foundSignature = false;
0551                 // check if this could be a symbolic link
0552                 if (compression_mode == NoCompression //
0553                     && uncomp_size <= max_path_len //
0554                     && uncomp_size > 0) {
0555                     // read content and store it
0556                     // If it's not a symlink, then we'll just discard the data for now.
0557                     pfi.guessed_symlink = dev->read(uncomp_size);
0558                     if (pfi.guessed_symlink.size() < uncomp_size) {
0559                         setErrorString(tr("Invalid ZIP file. Unexpected end of file. (#5)"));
0560                         return false;
0561                     }
0562                 } else {
0563                     if (compr_size > dev->size()) {
0564                         // here we cannot trust the compressed size, so scan through the compressed
0565                         // data to find the next header
0566                         if (!seekToNextHeaderToken(dev, false)) {
0567                             setErrorString(tr("Could not seek to next header token"));
0568                             return false;
0569                         }
0570                         foundSignature = true;
0571                     } else {
0572                         //          qCDebug(KArchiveLog) << "before interesting dev->pos(): " << dev->pos();
0573                         const bool success = dev->seek(dev->pos() + compr_size);
0574                         if (!success) {
0575                             setErrorString(tr("Could not seek to file compressed size"));
0576                             return false;
0577                         }
0578                         /*          qCDebug(KArchiveLog) << "after interesting dev->pos(): " << dev->pos();
0579                                                 if (success)
0580                                                 qCDebug(KArchiveLog) << "dev->at was successful... ";
0581                                                 else
0582                                                 qCDebug(KArchiveLog) << "dev->at failed... ";*/
0583                     }
0584                 }
0585                 // test for optional data descriptor
0586                 if (!foundSignature) {
0587                     //                     qCDebug(KArchiveLog) << "Testing for optional data descriptor";
0588                     // read static data descriptor
0589                     n = dev->read(buffer, 4);
0590                     if (n < 4) {
0591                         setErrorString(tr("Invalid ZIP file. Unexpected end of file. (#1)"));
0592                         return false;
0593                     }
0594 
0595                     if (buffer[0] != 'P' || !handlePossibleHeaderBegin(buffer + 1, dev, false)) {
0596                         // assume data descriptor without signature
0597                         dev->seek(dev->pos() + 8); // skip rest of the 'data_descriptor'
0598                     }
0599                 }
0600 
0601                 // not needed any more
0602                 /*                // here we calculate the length of the file in the zip
0603                 // with headers and jump to the next header.
0604                 uint skip = compr_size + namelen + extralen;
0605                 offset += 30 + skip;*/
0606             }
0607             pfi_map.insert(fileName, pfi);
0608         } else if (!memcmp(buffer, "PK\1\2", 4)) { // central block
0609             // qCDebug(KArchiveLog) << "PK12 found central block";
0610             startOfFile = false;
0611 
0612             // so we reached the central header at the end of the zip file
0613             // here we get all interesting data out of the central header
0614             // of a file
0615             offset = dev->pos() - 4;
0616 
0617             // set offset for appending new files
0618             if (d->m_offset == 0) {
0619                 d->m_offset = offset;
0620             }
0621 
0622             n = dev->read(buffer + 4, 42);
0623             if (n < 42) {
0624                 setErrorString(tr("Invalid ZIP file, central entry too short (not long enough for valid entry)"));
0625                 return false;
0626             }
0627 
0628             // int gpf = (uchar)buffer[9] << 8 | (uchar)buffer[10];
0629             // qCDebug(KArchiveLog) << "general purpose flag=" << gpf;
0630             // length of the fileName (well, pathname indeed)
0631             int namelen = (uchar)buffer[29] << 8 | (uchar)buffer[28];
0632             if (namelen <= 0) {
0633                 setErrorString(tr("Invalid ZIP file, file path name length smaller or equal to zero"));
0634                 return false;
0635             }
0636             QByteArray bufferName = dev->read(namelen);
0637             if (bufferName.size() < namelen) {
0638                 // qCWarning(KArchiveLog) << "Invalid ZIP file. Name not completely read";
0639             }
0640 
0641             ParseFileInfo pfi = pfi_map.value(bufferName, ParseFileInfo());
0642 
0643             QString name(QFile::decodeName(bufferName));
0644 
0645             // qCDebug(KArchiveLog) << "name: " << name;
0646             // only in central header ! see below.
0647             // length of extra attributes
0648             int extralen = (uchar)buffer[31] << 8 | (uchar)buffer[30];
0649             // length of comment for this file
0650             int commlen = (uchar)buffer[33] << 8 | (uchar)buffer[32];
0651             // compression method of this file
0652             int cmethod = (uchar)buffer[11] << 8 | (uchar)buffer[10];
0653 
0654             // qCDebug(KArchiveLog) << "cmethod: " << cmethod;
0655             // qCDebug(KArchiveLog) << "extralen: " << extralen;
0656 
0657             // crc32 of the file
0658             uint crc32 = (uchar)buffer[19] << 24 | (uchar)buffer[18] << 16 | (uchar)buffer[17] << 8 | (uchar)buffer[16];
0659 
0660             // uncompressed file size
0661             quint64 ucsize = uint32_t((uchar)buffer[27] << 24 | (uchar)buffer[26] << 16 | (uchar)buffer[25] << 8 | (uchar)buffer[24]);
0662             if (ucsize == 0xFFFFFFFF) {
0663                 ucsize = pfi.uncompressedSize;
0664             }
0665             // compressed file size
0666             quint64 csize = uint32_t((uchar)buffer[23] << 24 | (uchar)buffer[22] << 16 | (uchar)buffer[21] << 8 | (uchar)buffer[20]);
0667             if (csize == 0xFFFFFFFF) {
0668                 csize = pfi.compressedSize;
0669             }
0670 
0671             // offset of local header
0672             uint localheaderoffset = (uchar)buffer[45] << 24 | (uchar)buffer[44] << 16 | (uchar)buffer[43] << 8 | (uchar)buffer[42];
0673 
0674             // some clever people use different extra field lengths
0675             // in the central header and in the local header... funny.
0676             // so we need to get the localextralen to calculate the offset
0677             // from localheaderstart to dataoffset
0678             int localextralen = pfi.extralen; // FIXME: this will not work if
0679             // no local header exists
0680 
0681             // qCDebug(KArchiveLog) << "localextralen: " << localextralen;
0682 
0683             // offset, where the real data for uncompression starts
0684             uint dataoffset = localheaderoffset + 30 + localextralen + namelen; // comment only in central header
0685 
0686             // qCDebug(KArchiveLog) << "csize: " << csize;
0687 
0688             int os_madeby = (uchar)buffer[5];
0689             bool isdir = false;
0690             int access = 0100644;
0691 
0692             if (os_madeby == 3) { // good ole unix
0693                 access = (uchar)buffer[40] | (uchar)buffer[41] << 8;
0694             }
0695 
0696             QString entryName;
0697 
0698             if (name.endsWith(QLatin1Char('/'))) { // Entries with a trailing slash are directories
0699                 isdir = true;
0700                 name = name.left(name.length() - 1);
0701                 if (os_madeby != 3) {
0702                     access = S_IFDIR | 0755;
0703                 } else {
0704                     access |= S_IFDIR | 0700;
0705                 }
0706             }
0707 
0708             int pos = name.lastIndexOf(QLatin1Char('/'));
0709             if (pos == -1) {
0710                 entryName = name;
0711             } else {
0712                 entryName = name.mid(pos + 1);
0713             }
0714             if (entryName.isEmpty()) {
0715                 setErrorString(tr("Invalid ZIP file, found empty entry name"));
0716                 return false;
0717             }
0718 
0719             KArchiveEntry *entry;
0720             if (isdir) {
0721                 QString path = QDir::cleanPath(name);
0722                 const KArchiveEntry *ent = rootDir()->entry(path);
0723                 if (ent && ent->isDirectory()) {
0724                     // qCDebug(KArchiveLog) << "Directory already exists, NOT going to add it again";
0725                     entry = nullptr;
0726                 } else {
0727                     QDateTime mtime = KArchivePrivate::time_tToDateTime(pfi.mtime);
0728                     entry = new KArchiveDirectory(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), QString());
0729                     // qCDebug(KArchiveLog) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name;
0730                 }
0731             } else {
0732                 QString symlink;
0733                 if ((access & QT_STAT_MASK) == QT_STAT_LNK) {
0734                     symlink = QFile::decodeName(pfi.guessed_symlink);
0735                 }
0736                 QDateTime mtime = KArchivePrivate::time_tToDateTime(pfi.mtime);
0737                 entry =
0738                     new KZipFileEntry(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), symlink, name, dataoffset, ucsize, cmethod, csize);
0739                 static_cast<KZipFileEntry *>(entry)->setHeaderStart(localheaderoffset);
0740                 static_cast<KZipFileEntry *>(entry)->setCRC32(crc32);
0741                 // qCDebug(KArchiveLog) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name;
0742                 d->m_fileList.append(static_cast<KZipFileEntry *>(entry));
0743             }
0744 
0745             if (entry) {
0746                 if (pos == -1) {
0747                     rootDir()->addEntry(entry);
0748                 } else {
0749                     // In some tar files we can find dir/./file => call cleanPath
0750                     QString path = QDir::cleanPath(name.left(pos));
0751                     // Ensure container directory exists, create otherwise
0752                     KArchiveDirectory *tdir = findOrCreate(path);
0753                     if (tdir) {
0754                         tdir->addEntry(entry);
0755                     } else {
0756                         setErrorString(tr("File %1 is in folder %2, but %3 is actually a file.").arg(entryName, path, path));
0757                         delete entry;
0758                         return false;
0759                     }
0760                 }
0761             }
0762 
0763             // calculate offset to next entry
0764             offset += 46 + commlen + extralen + namelen;
0765             const bool b = dev->seek(offset);
0766             if (!b) {
0767                 setErrorString(tr("Could not seek to next entry"));
0768                 return false;
0769             }
0770         } else if (startOfFile) {
0771             // The file does not start with any ZIP header (e.g. self-extractable ZIP files)
0772             // Therefore we need to find the first PK\003\004 (local header)
0773             // qCDebug(KArchiveLog) << "Try to skip start of file";
0774             startOfFile = false;
0775             bool foundSignature = false;
0776 
0777             while (!foundSignature) {
0778                 n = dev->read(buffer, 1);
0779                 if (n < 1) {
0780                     setErrorString(tr("Invalid ZIP file. Unexpected end of file."));
0781                     return false;
0782                 }
0783 
0784                 if (buffer[0] != 'P') {
0785                     continue;
0786                 }
0787 
0788                 n = dev->read(buffer, 3);
0789                 if (n < 3) {
0790                     setErrorString(tr("Invalid ZIP file. Unexpected end of file."));
0791                     return false;
0792                 }
0793 
0794                 // We have to detect the magic token for a local header: PK\003\004
0795                 /*
0796                  * Note: we do not need to check the other magics, if the ZIP file has no
0797                  * local header, then it has not any files!
0798                  */
0799                 if (buffer[0] == 'K' && buffer[1] == 3 && buffer[2] == 4) {
0800                     foundSignature = true;
0801                     dev->seek(dev->pos() - 4); // go back 4 bytes, so that the magic bytes can be found...
0802                 } else {
0803                     for (int i = 0; i < 3; ++i) {
0804                         if (buffer[i] == 'P') {
0805                             // We have another P character so we must go back a little to check if it is a magic
0806                             dev->seek(dev->pos() - 3 + i);
0807                             break;
0808                         }
0809                     }
0810                 }
0811             }
0812         } else {
0813             setErrorString(tr("Invalid ZIP file. Unrecognized header at offset %1").arg(dev->pos() - 4));
0814             return false;
0815         }
0816     }
0817     // qCDebug(KArchiveLog) << "*** done *** ";
0818     return true;
0819 }
0820 
0821 bool KZip::closeArchive()
0822 {
0823     if (!(mode() & QIODevice::WriteOnly)) {
0824         // qCDebug(KArchiveLog) << "readonly";
0825         return true;
0826     }
0827 
0828     // ReadWrite or WriteOnly
0829     // write all central dir file entries
0830 
0831     // to be written at the end of the file...
0832     char buffer[22]; // first used for 12, then for 22 at the end
0833     uLong crc = crc32(0L, nullptr, 0);
0834 
0835     qint64 centraldiroffset = device()->pos();
0836     // qCDebug(KArchiveLog) << "closearchive: centraldiroffset: " << centraldiroffset;
0837     qint64 atbackup = centraldiroffset;
0838     QMutableListIterator<KZipFileEntry *> it(d->m_fileList);
0839 
0840     while (it.hasNext()) {
0841         // set crc and compressed size in each local file header
0842         it.next();
0843         if (!device()->seek(it.value()->headerStart() + 14)) {
0844             setErrorString(tr("Could not seek to next file header: %1").arg(device()->errorString()));
0845             return false;
0846         }
0847         // qCDebug(KArchiveLog) << "closearchive setcrcandcsize: fileName:"
0848         //    << it.value()->path()
0849         //    << "encoding:" << it.value()->encoding();
0850 
0851         uLong mycrc = it.value()->crc32();
0852         buffer[0] = char(mycrc); // crc checksum, at headerStart+14
0853         buffer[1] = char(mycrc >> 8);
0854         buffer[2] = char(mycrc >> 16);
0855         buffer[3] = char(mycrc >> 24);
0856 
0857         int mysize1 = it.value()->compressedSize();
0858         buffer[4] = char(mysize1); // compressed file size, at headerStart+18
0859         buffer[5] = char(mysize1 >> 8);
0860         buffer[6] = char(mysize1 >> 16);
0861         buffer[7] = char(mysize1 >> 24);
0862 
0863         int myusize = it.value()->size();
0864         buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
0865         buffer[9] = char(myusize >> 8);
0866         buffer[10] = char(myusize >> 16);
0867         buffer[11] = char(myusize >> 24);
0868 
0869         if (device()->write(buffer, 12) != 12) {
0870             setErrorString(tr("Could not write file header: %1").arg(device()->errorString()));
0871             return false;
0872         }
0873     }
0874     device()->seek(atbackup);
0875 
0876     it.toFront();
0877     while (it.hasNext()) {
0878         it.next();
0879         // qCDebug(KArchiveLog) << "fileName:" << it.value()->path()
0880         //              << "encoding:" << it.value()->encoding();
0881 
0882         QByteArray path = QFile::encodeName(it.value()->path());
0883 
0884         const int extra_field_len = (d->m_extraField == ModificationTime) ? 9 : 0;
0885         const int bufferSize = extra_field_len + path.length() + 46;
0886         char *buffer = new char[bufferSize];
0887 
0888         memset(buffer, 0, 46); // zero is a nice default for most header fields
0889 
0890         /* clang-format off */
0891         const char head[] = {
0892             'P', 'K', 1, 2, // central file header signature
0893             0x14, 3, // version made by (3 == UNIX)
0894             0x14, 0 // version needed to extract
0895         };
0896         /* clang-format on */
0897 
0898         // I do not know why memcpy is not working here
0899         // memcpy(buffer, head, sizeof(head));
0900         memmove(buffer, head, sizeof(head));
0901 
0902         buffer[10] = char(it.value()->encoding()); // compression method
0903         buffer[11] = char(it.value()->encoding() >> 8);
0904 
0905         transformToMsDos(it.value()->date(), &buffer[12]);
0906 
0907         uLong mycrc = it.value()->crc32();
0908         buffer[16] = char(mycrc); // crc checksum
0909         buffer[17] = char(mycrc >> 8);
0910         buffer[18] = char(mycrc >> 16);
0911         buffer[19] = char(mycrc >> 24);
0912 
0913         int mysize1 = it.value()->compressedSize();
0914         buffer[20] = char(mysize1); // compressed file size
0915         buffer[21] = char(mysize1 >> 8);
0916         buffer[22] = char(mysize1 >> 16);
0917         buffer[23] = char(mysize1 >> 24);
0918 
0919         int mysize = it.value()->size();
0920         buffer[24] = char(mysize); // uncompressed file size
0921         buffer[25] = char(mysize >> 8);
0922         buffer[26] = char(mysize >> 16);
0923         buffer[27] = char(mysize >> 24);
0924 
0925         buffer[28] = char(path.length()); // fileName length
0926         buffer[29] = char(path.length() >> 8);
0927 
0928         buffer[30] = char(extra_field_len);
0929         buffer[31] = char(extra_field_len >> 8);
0930 
0931         buffer[40] = char(it.value()->permissions());
0932         buffer[41] = char(it.value()->permissions() >> 8);
0933 
0934         int myhst = it.value()->headerStart();
0935         buffer[42] = char(myhst); // relative offset of local header
0936         buffer[43] = char(myhst >> 8);
0937         buffer[44] = char(myhst >> 16);
0938         buffer[45] = char(myhst >> 24);
0939 
0940         // file name
0941         strncpy(buffer + 46, path.constData(), path.length());
0942         // qCDebug(KArchiveLog) << "closearchive length to write: " << bufferSize;
0943 
0944         // extra field
0945         if (d->m_extraField == ModificationTime) {
0946             char *extfield = buffer + 46 + path.length();
0947             // "Extended timestamp" header (0x5455)
0948             extfield[0] = 'U';
0949             extfield[1] = 'T';
0950             extfield[2] = 5; // data size
0951             extfield[3] = 0;
0952             extfield[4] = 1 | 2 | 4; // specify flags from local field
0953             // (unless I misread the spec)
0954             // provide only modification time
0955             unsigned long time = (unsigned long)it.value()->date().toSecsSinceEpoch();
0956             extfield[5] = char(time);
0957             extfield[6] = char(time >> 8);
0958             extfield[7] = char(time >> 16);
0959             extfield[8] = char(time >> 24);
0960         }
0961 
0962         crc = crc32(crc, (Bytef *)buffer, bufferSize);
0963         bool ok = (device()->write(buffer, bufferSize) == bufferSize);
0964         delete[] buffer;
0965         if (!ok) {
0966             setErrorString(tr("Could not write file header: %1").arg(device()->errorString()));
0967             return false;
0968         }
0969     }
0970     qint64 centraldirendoffset = device()->pos();
0971     // qCDebug(KArchiveLog) << "closearchive: centraldirendoffset: " << centraldirendoffset;
0972     // qCDebug(KArchiveLog) << "closearchive: device()->pos(): " << device()->pos();
0973 
0974     // write end of central dir record.
0975     buffer[0] = 'P'; // end of central dir signature
0976     buffer[1] = 'K';
0977     buffer[2] = 5;
0978     buffer[3] = 6;
0979 
0980     buffer[4] = 0; // number of this disk
0981     buffer[5] = 0;
0982 
0983     buffer[6] = 0; // number of disk with start of central dir
0984     buffer[7] = 0;
0985 
0986     int count = d->m_fileList.count();
0987     // qCDebug(KArchiveLog) << "number of files (count): " << count;
0988 
0989     buffer[8] = char(count); // total number of entries in central dir of
0990     buffer[9] = char(count >> 8); // this disk
0991 
0992     buffer[10] = buffer[8]; // total number of entries in the central dir
0993     buffer[11] = buffer[9];
0994 
0995     int cdsize = centraldirendoffset - centraldiroffset;
0996     buffer[12] = char(cdsize); // size of the central dir
0997     buffer[13] = char(cdsize >> 8);
0998     buffer[14] = char(cdsize >> 16);
0999     buffer[15] = char(cdsize >> 24);
1000 
1001     // qCDebug(KArchiveLog) << "end : centraldiroffset: " << centraldiroffset;
1002     // qCDebug(KArchiveLog) << "end : centraldirsize: " << cdsize;
1003 
1004     buffer[16] = char(centraldiroffset); // central dir offset
1005     buffer[17] = char(centraldiroffset >> 8);
1006     buffer[18] = char(centraldiroffset >> 16);
1007     buffer[19] = char(centraldiroffset >> 24);
1008 
1009     buffer[20] = 0; // zipfile comment length
1010     buffer[21] = 0;
1011 
1012     if (device()->write(buffer, 22) != 22) {
1013         setErrorString(tr("Could not write central dir record: %1").arg(device()->errorString()));
1014         return false;
1015     }
1016 
1017     return true;
1018 }
1019 
1020 bool KZip::doWriteDir(const QString &name,
1021                       const QString &user,
1022                       const QString &group,
1023                       mode_t perm,
1024                       const QDateTime &atime,
1025                       const QDateTime &mtime,
1026                       const QDateTime &ctime)
1027 {
1028     // Zip files have no explicit directories, they are implicitly created during extraction time
1029     // when file entries have paths in them.
1030     // However, to support empty directories, we must create a dummy file entry which ends with '/'.
1031     QString dirName = name;
1032     if (!name.endsWith(QLatin1Char('/'))) {
1033         dirName = dirName.append(QLatin1Char('/'));
1034     }
1035     return writeFile(dirName, QByteArrayView(), perm, user, group, atime, mtime, ctime);
1036 }
1037 
1038 bool KZip::doPrepareWriting(const QString &name,
1039                             const QString &user,
1040                             const QString &group,
1041                             qint64 /*size*/,
1042                             mode_t perm,
1043                             const QDateTime &accessTime,
1044                             const QDateTime &modificationTime,
1045                             const QDateTime &creationTime)
1046 {
1047     // qCDebug(KArchiveLog);
1048     if (!isOpen()) {
1049         setErrorString(tr("Application error: ZIP file must be open before being written into"));
1050         qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
1051         return false;
1052     }
1053 
1054     if (!(mode() & QIODevice::WriteOnly)) { // accept WriteOnly and ReadWrite
1055         setErrorString(tr("Application error: attempted to write into non-writable ZIP file"));
1056         qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
1057         return false;
1058     }
1059 
1060     if (!device()) {
1061         setErrorString(tr("Cannot create a device. Disk full?"));
1062         return false;
1063     }
1064 
1065     // set right offset in zip.
1066     if (!device()->seek(d->m_offset)) {
1067         setErrorString(tr("Cannot seek in ZIP file. Disk full?"));
1068         return false;
1069     }
1070 
1071     uint atime = accessTime.toSecsSinceEpoch();
1072     uint mtime = modificationTime.toSecsSinceEpoch();
1073     uint ctime = creationTime.toSecsSinceEpoch();
1074 
1075     // Find or create parent dir
1076     KArchiveDirectory *parentDir = rootDir();
1077     QString fileName(name);
1078     int i = name.lastIndexOf(QLatin1Char('/'));
1079     if (i != -1) {
1080         QString dir = name.left(i);
1081         fileName = name.mid(i + 1);
1082         // qCDebug(KArchiveLog) << "ensuring" << dir << "exists. fileName=" << fileName;
1083         parentDir = findOrCreate(dir);
1084     }
1085 
1086     // delete entries in the filelist with the same fileName as the one we want
1087     // to save, so that we don't have duplicate file entries when viewing the zip
1088     // with konqi...
1089     // CAUTION: the old file itself is still in the zip and won't be removed !!!
1090     QMutableListIterator<KZipFileEntry *> it(d->m_fileList);
1091     // qCDebug(KArchiveLog) << "fileName to write: " << name;
1092     while (it.hasNext()) {
1093         it.next();
1094         // qCDebug(KArchiveLog) << "prepfileName: " << it.value()->path();
1095         if (name == it.value()->path()) {
1096             // also remove from the parentDir
1097             parentDir->removeEntry(it.value());
1098             // qCDebug(KArchiveLog) << "removing following entry: " << it.value()->path();
1099             delete it.value();
1100             it.remove();
1101         }
1102     }
1103 
1104     // construct a KZipFileEntry and add it to list
1105     KZipFileEntry *e = new KZipFileEntry(this,
1106                                          fileName,
1107                                          perm,
1108                                          modificationTime,
1109                                          user,
1110                                          group,
1111                                          QString(),
1112                                          name,
1113                                          device()->pos() + 30 + name.length(), // start
1114                                          0 /*size unknown yet*/,
1115                                          d->m_compression,
1116                                          0 /*csize unknown yet*/);
1117     e->setHeaderStart(device()->pos());
1118     // qCDebug(KArchiveLog) << "wrote file start: " << e->position() << " name: " << name;
1119     if (!parentDir->addEntryV2(e)) {
1120         return false;
1121     }
1122 
1123     d->m_currentFile = e;
1124     d->m_fileList.append(e);
1125 
1126     int extra_field_len = 0;
1127     if (d->m_extraField == ModificationTime) {
1128         extra_field_len = 17; // value also used in finishWriting()
1129     }
1130 
1131     // write out zip header
1132     QByteArray encodedName = QFile::encodeName(name);
1133     int bufferSize = extra_field_len + encodedName.length() + 30;
1134     // qCDebug(KArchiveLog) << "bufferSize=" << bufferSize;
1135     char *buffer = new char[bufferSize];
1136 
1137     buffer[0] = 'P'; // local file header signature
1138     buffer[1] = 'K';
1139     buffer[2] = 3;
1140     buffer[3] = 4;
1141 
1142     buffer[4] = 0x14; // version needed to extract
1143     buffer[5] = 0;
1144 
1145     buffer[6] = 0; // general purpose bit flag
1146     buffer[7] = 0;
1147 
1148     buffer[8] = char(e->encoding()); // compression method
1149     buffer[9] = char(e->encoding() >> 8);
1150 
1151     transformToMsDos(e->date(), &buffer[10]);
1152 
1153     buffer[14] = 'C'; // dummy crc
1154     buffer[15] = 'R';
1155     buffer[16] = 'C';
1156     buffer[17] = 'q';
1157 
1158     buffer[18] = 'C'; // compressed file size
1159     buffer[19] = 'S';
1160     buffer[20] = 'I';
1161     buffer[21] = 'Z';
1162 
1163     buffer[22] = 'U'; // uncompressed file size
1164     buffer[23] = 'S';
1165     buffer[24] = 'I';
1166     buffer[25] = 'Z';
1167 
1168     buffer[26] = (uchar)(encodedName.length()); // fileName length
1169     buffer[27] = (uchar)(encodedName.length() >> 8);
1170 
1171     buffer[28] = (uchar)(extra_field_len); // extra field length
1172     buffer[29] = (uchar)(extra_field_len >> 8);
1173 
1174     // file name
1175     strncpy(buffer + 30, encodedName.constData(), encodedName.length());
1176 
1177     // extra field
1178     if (d->m_extraField == ModificationTime) {
1179         char *extfield = buffer + 30 + encodedName.length();
1180         // "Extended timestamp" header (0x5455)
1181         extfield[0] = 'U';
1182         extfield[1] = 'T';
1183         extfield[2] = 13; // data size
1184         extfield[3] = 0;
1185         extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime
1186 
1187         extfield[5] = char(mtime);
1188         extfield[6] = char(mtime >> 8);
1189         extfield[7] = char(mtime >> 16);
1190         extfield[8] = char(mtime >> 24);
1191 
1192         extfield[9] = char(atime);
1193         extfield[10] = char(atime >> 8);
1194         extfield[11] = char(atime >> 16);
1195         extfield[12] = char(atime >> 24);
1196 
1197         extfield[13] = char(ctime);
1198         extfield[14] = char(ctime >> 8);
1199         extfield[15] = char(ctime >> 16);
1200         extfield[16] = char(ctime >> 24);
1201     }
1202 
1203     // Write header
1204     bool b = (device()->write(buffer, bufferSize) == bufferSize);
1205     d->m_crc = 0;
1206     delete[] buffer;
1207 
1208     if (!b) {
1209         setErrorString(tr("Could not write to the archive. Disk full?"));
1210         return false;
1211     }
1212 
1213     // Prepare device for writing the data
1214     // Either device() if no compression, or a KCompressionDevice to compress
1215     if (d->m_compression == 0) {
1216         d->m_currentDev = device();
1217         return true;
1218     }
1219 
1220     auto compressionDevice = new KCompressionDevice(device(), false, KCompressionDevice::GZip);
1221     d->m_currentDev = compressionDevice;
1222     compressionDevice->setSkipHeaders(); // Just zlib, not gzip
1223 
1224     b = d->m_currentDev->open(QIODevice::WriteOnly);
1225     Q_ASSERT(b);
1226 
1227     if (!b) {
1228         setErrorString(tr("Could not open compression device: %1").arg(d->m_currentDev->errorString()));
1229     }
1230 
1231     return b;
1232 }
1233 
1234 bool KZip::doFinishWriting(qint64 size)
1235 {
1236     if (d->m_currentFile->encoding() == 8) {
1237         // Finish
1238         (void)d->m_currentDev->write(nullptr, 0);
1239         delete d->m_currentDev;
1240     }
1241     // If 0, d->m_currentDev was device() - don't delete ;)
1242     d->m_currentDev = nullptr;
1243 
1244     Q_ASSERT(d->m_currentFile);
1245     // qCDebug(KArchiveLog) << "fileName: " << d->m_currentFile->path();
1246     // qCDebug(KArchiveLog) << "getpos (at): " << device()->pos();
1247     d->m_currentFile->setSize(size);
1248     int extra_field_len = 0;
1249     if (d->m_extraField == ModificationTime) {
1250         extra_field_len = 17; // value also used in finishWriting()
1251     }
1252 
1253     const QByteArray encodedName = QFile::encodeName(d->m_currentFile->path());
1254     int csize = device()->pos() - d->m_currentFile->headerStart() - 30 - encodedName.length() - extra_field_len;
1255     d->m_currentFile->setCompressedSize(csize);
1256     // qCDebug(KArchiveLog) << "usize: " << d->m_currentFile->size();
1257     // qCDebug(KArchiveLog) << "csize: " << d->m_currentFile->compressedSize();
1258     // qCDebug(KArchiveLog) << "headerstart: " << d->m_currentFile->headerStart();
1259 
1260     // qCDebug(KArchiveLog) << "crc: " << d->m_crc;
1261     d->m_currentFile->setCRC32(d->m_crc);
1262 
1263     d->m_currentFile = nullptr;
1264 
1265     // update saved offset for appending new files
1266     d->m_offset = device()->pos();
1267     return true;
1268 }
1269 
1270 bool KZip::doWriteSymLink(const QString &name,
1271                           const QString &target,
1272                           const QString &user,
1273                           const QString &group,
1274                           mode_t perm,
1275                           const QDateTime &atime,
1276                           const QDateTime &mtime,
1277                           const QDateTime &ctime)
1278 {
1279     // reassure that symlink flag is set, otherwise strange things happen on
1280     // extraction
1281     perm |= QT_STAT_LNK;
1282     Compression c = compression();
1283     setCompression(NoCompression); // link targets are never compressed
1284 
1285     if (!doPrepareWriting(name, user, group, 0, perm, atime, mtime, ctime)) {
1286         setCompression(c);
1287         return false;
1288     }
1289 
1290     QByteArray symlink_target = QFile::encodeName(target);
1291     if (!writeData(symlink_target.constData(), symlink_target.length())) {
1292         setCompression(c);
1293         return false;
1294     }
1295 
1296     if (!finishWriting(symlink_target.length())) {
1297         setCompression(c);
1298         return false;
1299     }
1300 
1301     setCompression(c);
1302     return true;
1303 }
1304 
1305 void KZip::virtual_hook(int id, void *data)
1306 {
1307     KArchive::virtual_hook(id, data);
1308 }
1309 
1310 bool KZip::doWriteData(const char *data, qint64 size)
1311 {
1312     Q_ASSERT(d->m_currentFile);
1313     Q_ASSERT(d->m_currentDev);
1314     if (!d->m_currentFile || !d->m_currentDev) {
1315         setErrorString(tr("No file or device"));
1316         return false;
1317     }
1318 
1319     // crc to be calculated over uncompressed stuff...
1320     // and they didn't mention it in their docs...
1321     d->m_crc = crc32(d->m_crc, (const Bytef *)data, size);
1322 
1323     qint64 written = d->m_currentDev->write(data, size);
1324     // qCDebug(KArchiveLog) << "wrote" << size << "bytes.";
1325     const bool ok = written == size;
1326 
1327     if (!ok) {
1328         setErrorString(tr("Error writing data: %1").arg(d->m_currentDev->errorString()));
1329     }
1330 
1331     return ok;
1332 }
1333 
1334 void KZip::setCompression(Compression c)
1335 {
1336     d->m_compression = (c == NoCompression) ? 0 : 8;
1337 }
1338 
1339 KZip::Compression KZip::compression() const
1340 {
1341     return (d->m_compression == 8) ? DeflateCompression : NoCompression;
1342 }
1343 
1344 void KZip::setExtraField(ExtraField ef)
1345 {
1346     d->m_extraField = ef;
1347 }
1348 
1349 KZip::ExtraField KZip::extraField() const
1350 {
1351     return d->m_extraField;
1352 }
1353 
1354 ////////////////////////////////////////////////////////////////////////
1355 ////////////////////// KZipFileEntry////////////////////////////////////
1356 ////////////////////////////////////////////////////////////////////////
1357 class Q_DECL_HIDDEN KZipFileEntry::KZipFileEntryPrivate
1358 {
1359 public:
1360     KZipFileEntryPrivate()
1361         : crc(0)
1362         , compressedSize(0)
1363         , headerStart(0)
1364         , encoding(0)
1365     {
1366     }
1367     unsigned long crc;
1368     qint64 compressedSize;
1369     qint64 headerStart;
1370     int encoding;
1371     QString path;
1372 };
1373 
1374 KZipFileEntry::KZipFileEntry(KZip *zip,
1375                              const QString &name,
1376                              int access,
1377                              const QDateTime &date,
1378                              const QString &user,
1379                              const QString &group,
1380                              const QString &symlink,
1381                              const QString &path,
1382                              qint64 start,
1383                              qint64 uncompressedSize,
1384                              int encoding,
1385                              qint64 compressedSize)
1386     : KArchiveFile(zip, name, access, date, user, group, symlink, start, uncompressedSize)
1387     , d(new KZipFileEntryPrivate)
1388 {
1389     d->path = path;
1390     d->encoding = encoding;
1391     d->compressedSize = compressedSize;
1392 }
1393 
1394 KZipFileEntry::~KZipFileEntry()
1395 {
1396     delete d;
1397 }
1398 
1399 int KZipFileEntry::encoding() const
1400 {
1401     return d->encoding;
1402 }
1403 
1404 qint64 KZipFileEntry::compressedSize() const
1405 {
1406     return d->compressedSize;
1407 }
1408 
1409 void KZipFileEntry::setCompressedSize(qint64 compressedSize)
1410 {
1411     d->compressedSize = compressedSize;
1412 }
1413 
1414 void KZipFileEntry::setHeaderStart(qint64 headerstart)
1415 {
1416     d->headerStart = headerstart;
1417 }
1418 
1419 qint64 KZipFileEntry::headerStart() const
1420 {
1421     return d->headerStart;
1422 }
1423 
1424 unsigned long KZipFileEntry::crc32() const
1425 {
1426     return d->crc;
1427 }
1428 
1429 void KZipFileEntry::setCRC32(unsigned long crc32)
1430 {
1431     d->crc = crc32;
1432 }
1433 
1434 const QString &KZipFileEntry::path() const
1435 {
1436     return d->path;
1437 }
1438 
1439 QByteArray KZipFileEntry::data() const
1440 {
1441     QIODevice *dev = createDevice();
1442     QByteArray arr;
1443     if (dev) {
1444         arr = dev->readAll();
1445         delete dev;
1446     }
1447     return arr;
1448 }
1449 
1450 QIODevice *KZipFileEntry::createDevice() const
1451 {
1452     // qCDebug(KArchiveLog) << "creating iodevice limited to pos=" << position() << ", csize=" << compressedSize();
1453     // Limit the reading to the appropriate part of the underlying device (e.g. file)
1454     KLimitedIODevice *limitedDev = new KLimitedIODevice(archive()->device(), position(), compressedSize());
1455     if (encoding() == 0 || compressedSize() == 0) { // no compression (or even no data)
1456         return limitedDev;
1457     }
1458 
1459     if (encoding() == 8) {
1460         // On top of that, create a device that uncompresses the zlib data
1461         KCompressionDevice *filterDev = new KCompressionDevice(limitedDev, true, KCompressionDevice::GZip);
1462 
1463         if (!filterDev) {
1464             return nullptr; // ouch
1465         }
1466         filterDev->setSkipHeaders(); // Just zlib, not gzip
1467         bool b = filterDev->open(QIODevice::ReadOnly);
1468         Q_UNUSED(b);
1469         Q_ASSERT(b);
1470         return filterDev;
1471     }
1472 
1473     qCCritical(KArchiveLog) << "This zip file contains files compressed with method" << encoding() << ", this method is currently not supported by KZip,"
1474                             << "please use a command-line tool to handle this file.";
1475     delete limitedDev;
1476     return nullptr;
1477 }