File indexing completed on 2025-02-16 13:00:38
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, QByteArray(), 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::writeData(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 }