1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
3 SPDX-FileCopyrightText: 2002 Holger Schroeder <holger-kde@holgis.net>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kzip.h"
9#include "karchive_p.h"
10#include "kcompressiondevice.h"
11#include "klimitediodevice_p.h"
12#include "loggingcategory.h"
13
14#include <QByteArray>
15#include <QDate>
16#include <QDebug>
17#include <QDir>
18#include <QFile>
19#include <QHash>
20#include <QList>
21#include <QtEndian>
22#include <qplatformdefs.h>
23
24#include <string.h>
25#include <time.h>
26#include <zlib.h>
27
28#ifndef QT_STAT_LNK
29#define QT_STAT_LNK 0120000
30#endif // QT_STAT_LNK
31
32static const int max_path_len = 4095; // maximum number of character a path may contain
33
34static quint16 parseUi16(const char *buffer)
35{
36 return quint16((uchar)buffer[0] | (uchar)buffer[1] << 8);
37}
38
39static uint parseUi32(const char *buffer)
40{
41 return uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
42}
43
44static quint64 parseUi64(const char *buffer)
45{
46 const uint a = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
47 const uint b = uint((uchar)buffer[4] | (uchar)buffer[5] << 8 | (uchar)buffer[6] << 16 | (uchar)buffer[7] << 24);
48 return (a | (quint64)b << 32);
49}
50
51static void transformToMsDos(const QDateTime &_dt, char *buffer)
52{
53 const QDateTime dt = _dt.isValid() ? _dt : QDateTime::currentDateTime();
54 /* clang-format off */
55 const quint16 time = (dt.time().hour() << 11) // 5 bit hour
56 | (dt.time().minute() << 5) // 6 bit minute
57 | (dt.time().second() >> 1); // 5 bit double seconds
58 /* clang-format on */
59
60 buffer[0] = char(time);
61 buffer[1] = char(time >> 8);
62
63 /* clang-format off */
64 const quint16 date = ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
65 | (dt.date().month() << 5) // 4 bit month
66 | (dt.date().day()); // 5 bit day
67 /* clang-format on */
68
69 buffer[2] = char(date);
70 buffer[3] = char(date >> 8);
71}
72
73static uint transformFromMsDos(const char *buffer)
74{
75 const quint16 time = parseUi16(buffer);
76 int h = time >> 11;
77 int m = (time & 0x7ff) >> 5;
78 int s = (time & 0x1f) * 2;
79 QTime qt(h, m, s);
80
81 const quint16 date = parseUi16(buffer: buffer + 2);
82 int y = (date >> 9) + 1980;
83 int o = (date & 0x1ff) >> 5;
84 int d = (date & 0x1f);
85 QDate qd(y, o, d);
86
87 QDateTime dt(qd, qt);
88 return dt.toSecsSinceEpoch();
89}
90
91// == parsing routines for zip headers
92
93/** all relevant information about parsing file information */
94struct ParseFileInfo {
95 // file related info
96 mode_t perm; // permissions of this file
97 // TODO: use quint32 instead of a uint?
98 uint atime; // last access time (UNIX format)
99 uint mtime; // modification time (UNIX format)
100 uint ctime; // creation time (UNIX format)
101 int uid; // user id (-1 if not specified)
102 int gid; // group id (-1 if not specified)
103 QByteArray guessed_symlink; // guessed symlink target
104 uint dataoffset = 0; // offset from start of local header to data
105
106 // parsing related info
107 bool exttimestamp_seen; // true if extended timestamp extra field
108 // has been parsed
109 bool newinfounix_seen; // true if Info-ZIP Unix New extra field has
110 // been parsed
111
112 // file sizes from a ZIP64 extra field
113 quint64 uncompressedSize = 0;
114 quint64 compressedSize = 0;
115 // position of the Local File Header itself, or from the
116 // ZIP64 extra field in the Central Directory
117 quint64 localheaderoffset = 0;
118
119 ParseFileInfo()
120 : perm(0100644)
121 , uid(-1)
122 , gid(-1)
123 , exttimestamp_seen(false)
124 , newinfounix_seen(false)
125 {
126 ctime = mtime = atime = time(timer: nullptr);
127 }
128};
129
130/** updates the parse information with the given extended timestamp extra field.
131 * @param buffer start content of buffer known to contain an extended
132 * timestamp extra field (without magic & size)
133 * @param size size of field content (must not count magic and size entries)
134 * @param islocal true if this is a local field, false if central
135 * @param pfi ParseFileInfo object to be updated
136 * @return true if processing was successful
137 */
138static bool parseExtTimestamp(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
139{
140 if (size < 1) {
141 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#1)";
142 return false;
143 } /*end if*/
144 int flags = *buffer; // read flags
145 buffer += 1;
146 size -= 1;
147
148 if (flags & 1) { // contains modification time
149 if (size < 4) {
150 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#2)";
151 return false;
152 } /*end if*/
153 pfi.mtime = parseUi32(buffer);
154 buffer += 4;
155 size -= 4;
156 } /*end if*/
157 // central extended field cannot contain more than the modification time
158 // even if other flags are set
159 if (!islocal) {
160 pfi.exttimestamp_seen = true;
161 return true;
162 } /*end if*/
163
164 if (flags & 2) { // contains last access time
165 if (size < 4) {
166 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#3)";
167 return true;
168 } /*end if*/
169 pfi.atime = parseUi32(buffer);
170 buffer += 4;
171 size -= 4;
172 } /*end if*/
173
174 if (flags & 4) { // contains creation time
175 if (size < 4) {
176 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#4)";
177 return true;
178 } /*end if*/
179 pfi.ctime = parseUi32(buffer);
180 buffer += 4;
181 } /*end if*/
182
183 pfi.exttimestamp_seen = true;
184 return true;
185}
186
187/** updates the parse information with the given Info-ZIP Unix old extra field.
188 * @param buffer start of content of buffer known to contain an Info-ZIP
189 * Unix old extra field (without magic & size)
190 * @param size size of field content (must not count magic and size entries)
191 * @param islocal true if this is a local field, false if central
192 * @param pfi ParseFileInfo object to be updated
193 * @return true if processing was successful
194 */
195static bool parseInfoZipUnixOld(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
196{
197 // spec mandates to omit this field if one of the newer fields are available
198 if (pfi.exttimestamp_seen || pfi.newinfounix_seen) {
199 return true;
200 }
201
202 if (size < 8) {
203 // qCDebug(KArchiveLog) << "premature end of Info-ZIP unix extra field old";
204 return false;
205 }
206
207 pfi.atime = parseUi32(buffer);
208 pfi.mtime = parseUi32(buffer: buffer + 4);
209 if (islocal && size >= 12) {
210 pfi.uid = parseUi16(buffer: buffer + 8);
211 pfi.gid = parseUi16(buffer: buffer + 10);
212 } /*end if*/
213 return true;
214}
215
216#if 0 // not needed yet
217/** updates the parse information with the given Info-ZIP Unix new extra field.
218 * @param buffer start of content of buffer known to contain an Info-ZIP
219 * Unix new extra field (without magic & size)
220 * @param size size of field content (must not count magic and size entries)
221 * @param islocal true if this is a local field, false if central
222 * @param pfi ParseFileInfo object to be updated
223 * @return true if processing was successful
224 */
225static bool parseInfoZipUnixNew(const char *buffer, int size, bool islocal,
226 ParseFileInfo &pfi)
227{
228 if (!islocal) { // contains nothing in central field
229 pfi.newinfounix = true;
230 return true;
231 }
232
233 if (size < 4) {
234 qCDebug(KArchiveLog) << "premature end of Info-ZIP unix extra field new";
235 return false;
236 }
237
238 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
239 buffer += 2;
240 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
241 buffer += 2;
242
243 pfi.newinfounix = true;
244 return true;
245}
246#endif
247
248/**
249 * parses the extra field
250 * @param buffer start of buffer where the extra field is to be found
251 * @param size size of the extra field
252 * @param pfi ParseFileInfo object which to write the results into
253 * @return true if parsing was successful
254 */
255static bool parseExtraField(const char *buffer, int size, ParseFileInfo &pfi)
256{
257 while (size >= 4) { // as long as a potential extra field can be read
258 int magic = parseUi16(buffer);
259 int fieldsize = parseUi16(buffer: buffer + 2);
260 buffer += 4;
261 size -= 4;
262
263 if (fieldsize > size) {
264 // qCDebug(KArchiveLog) << "fieldsize: " << fieldsize << " size: " << size;
265 // qCDebug(KArchiveLog) << "premature end of extra fields reached";
266 break;
267 }
268
269 switch (magic) {
270 case 0x0001: // ZIP64 extended file information
271 if (size >= 8) {
272 pfi.uncompressedSize = parseUi64(buffer);
273 }
274 if (size >= 16) {
275 pfi.compressedSize = parseUi64(buffer: buffer + 8);
276 }
277 if (size >= 24) {
278 pfi.localheaderoffset = parseUi64(buffer: buffer + 16);
279 }
280 break;
281 case 0x5455: // extended timestamp
282 if (!parseExtTimestamp(buffer, size: fieldsize, islocal: true, pfi)) {
283 return false;
284 }
285 break;
286 case 0x5855: // old Info-ZIP unix extra field
287 if (!parseInfoZipUnixOld(buffer, size: fieldsize, islocal: true, pfi)) {
288 return false;
289 }
290 break;
291#if 0 // not needed yet
292 case 0x7855: // new Info-ZIP unix extra field
293 if (!parseInfoZipUnixNew(buffer, fieldsize, islocal, pfi)) {
294 return false;
295 }
296 break;
297#endif
298 default:
299 /* ignore everything else */
300 ;
301 } /*end switch*/
302
303 buffer += fieldsize;
304 size -= fieldsize;
305 } /*wend*/
306 return true;
307}
308
309/**
310 * Advance the current file position to the next possible header location
311 *
312 * @param dev device that is read from
313 * @param header storage location for the complete header
314 * @param minSize required size of the header
315 * @return true if a possible header location matching the 'PK' signature
316 * bytes have been found, false if the end of file has been reached
317 */
318static bool seekAnyHeader(QIODevice *dev, QByteArray &header, qsizetype minSize)
319{
320 header.resize(size: 1024);
321 int n = dev->peek(data: header.data(), maxlen: header.size());
322
323 while (n >= minSize) {
324 if (auto i = QByteArrayView(header.data(), n).indexOf(a: "PK"); i >= 0) {
325 dev->skip(maxSize: i);
326 if ((i + minSize) < n) {
327 header.remove(index: 0, len: i);
328 header.resize(size: minSize);
329 return true;
330 } else {
331 n = dev->peek(data: header.data(), maxlen: minSize);
332 header.resize(size: n);
333 return n >= minSize;
334 }
335 }
336 dev->skip(maxSize: n - 1);
337 n = dev->peek(data: header.data(), maxlen: header.size());
338 }
339 header.resize(size: n);
340 return false;
341}
342
343/**
344 * Advance the current file position to the next header following
345 * a data descriptor
346 *
347 * @param dev device that is read from
348 * @param isZip64 use Zip64 data descriptor layout
349 * @return true if a data descriptor signature has been found, and the
350 * compressed size matches the current position advance
351 */
352static bool seekPostDataDescriptor(QIODevice *dev, bool isZip64)
353{
354 // Both size fields are uint64 for Zip64, uint32 otherwise
355 const qsizetype descriptorSize = isZip64 ? 24 : 16;
356
357 QByteArray header;
358 const qint64 oldPos = dev->pos();
359
360 while (seekAnyHeader(dev, header, minSize: descriptorSize)) {
361 // qCDebug(KArchiveLog) << "Possible data descriptor header at" << dev->pos() << header;
362 if (header.startsWith(bv: "PK\x07\x08")) {
363 const qint64 compressedSize = isZip64 ? parseUi64(buffer: header.constData() + 8) //
364 : parseUi32(buffer: header.constData() + 8);
365 // Do not move compressedSize to the left side of the check
366 // we have on the left on purpose to prevent addition overflows
367 if ((compressedSize > 0) && (oldPos == dev->pos() - compressedSize)) {
368 dev->skip(maxSize: descriptorSize);
369 return true;
370 }
371 dev->skip(maxSize: 4); // Skip found 'PK\7\8'
372 } else {
373 dev->skip(maxSize: 2); // Skip found 'PK'
374 }
375 }
376 return false;
377}
378
379/**
380 * Advance the current file position to the next header
381 * @param dev device that is read from
382 * @return true if a local or central header token could be reached, false on error
383 */
384static bool seekToNextHeaderToken(QIODevice *dev)
385{
386 QByteArray header;
387
388 while (seekAnyHeader(dev, header, minSize: 4)) {
389 // qCDebug(KArchiveLog) << "Possible header at" << dev->pos() << header;
390 // PK34 for the next local header in case there is no data descriptor
391 // PK12 for the central header in case there is no data descriptor
392 if (header.startsWith(bv: "PK\x03\x04") || header.startsWith(bv: "PK\x01\x02")) {
393 return true;
394 } else {
395 dev->skip(maxSize: 2); // Skip found 'PK'
396 }
397 }
398 return false;
399}
400
401////////////////////////////////////////////////////////////////////////
402/////////////////////////// KZip ///////////////////////////////////////
403////////////////////////////////////////////////////////////////////////
404
405class Q_DECL_HIDDEN KZip::KZipPrivate
406{
407public:
408 KZipPrivate()
409 : m_crc(0)
410 , m_currentFile(nullptr)
411 , m_currentDev(nullptr)
412 , m_compression(8)
413 , m_extraField(KZip::NoExtraField)
414 , m_offset(0)
415 {
416 }
417
418 unsigned long m_crc; // checksum
419 KZipFileEntry *m_currentFile; // file currently being written
420 QIODevice *m_currentDev; // filterdev used to write to the above file
421 QList<KZipFileEntry *> m_fileList; // flat list of all files, for the index (saves a recursive method ;)
422 int m_compression;
423 KZip::ExtraField m_extraField;
424 // m_offset holds the offset of the place in the zip,
425 // where new data can be appended. after openarchive it points to 0, when in
426 // writeonly mode, or it points to the beginning of the central directory.
427 // each call to writefile updates this value.
428 quint64 m_offset;
429 // Position of the first Local File Header
430 quint64 m_startPos = 0;
431};
432
433KZip::KZip(const QString &fileName)
434 : KArchive(fileName)
435 , d(new KZipPrivate)
436{
437}
438
439KZip::KZip(QIODevice *dev)
440 : KArchive(dev)
441 , d(new KZipPrivate)
442{
443}
444
445KZip::~KZip()
446{
447 // qCDebug(KArchiveLog) << this;
448 if (isOpen()) {
449 close();
450 }
451 delete d;
452}
453
454bool KZip::openArchive(QIODevice::OpenMode mode)
455{
456 // qCDebug(KArchiveLog);
457 d->m_fileList.clear();
458
459 if (mode == QIODevice::WriteOnly) {
460 return true;
461 }
462
463 char buffer[47];
464
465 // Check that it's a valid ZIP file
466 // KArchive::open() opened the underlying device already.
467
468 // contains information gathered from the local file headers
469 QHash<QByteArray, ParseFileInfo> pfi_map;
470
471 QIODevice *dev = device();
472
473 // We set a bool for knowing if we are allowed to skip the start of the file
474 bool startOfFile = true;
475
476 qint64 expectedStartPos = 0;
477
478 for (;;) { // repeat until 'end of entries' signature is reached
479 // qCDebug(KArchiveLog) << "loop starts";
480 // qCDebug(KArchiveLog) << "dev->pos() now : " << dev->pos();
481 int n = dev->read(data: buffer, maxlen: 4);
482
483 if (n < 4) {
484 setErrorString(tr(sourceText: "Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(a: 1));
485 return false;
486 }
487
488 if (!memcmp(s1: buffer, s2: "PK\6\6", n: 4)) { // 'Zip64 end of central directory record'
489 startOfFile = false;
490 break;
491 }
492
493 if (!memcmp(s1: buffer, s2: "PK\5\6", n: 4)) { // 'end of entries'
494 // qCDebug(KArchiveLog) << "PK56 found end of archive at offset" << dev->pos();
495 startOfFile = false;
496 break;
497 }
498
499 if (!memcmp(s1: buffer, s2: "PK\3\4", n: 4)) { // local file header
500 // qCDebug(KArchiveLog) << "PK34 found local file header at offset" << dev->pos();
501 startOfFile = false;
502
503 ParseFileInfo pfi;
504 pfi.localheaderoffset = dev->pos() - 4;
505
506 // read static header stuff
507 n = dev->read(data: buffer, maxlen: 26);
508 if (n < 26) {
509 setErrorString(tr(sourceText: "Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(a: 4));
510 return false;
511 }
512 int neededVersion = parseUi16(buffer);
513 bool isZip64 = neededVersion >= 45;
514
515 int gpf = (uchar)buffer[2]; // "general purpose flag" not "general protection fault" ;-)
516 int compression_mode = parseUi16(buffer: buffer + 4);
517 uint mtime = transformFromMsDos(buffer: buffer + 6);
518
519 const qint64 compr_size = parseUi32(buffer: buffer + 14);
520 const qint64 uncomp_size = parseUi32(buffer: buffer + 18);
521 const int namelen = parseUi16(buffer: buffer + 22);
522 const int extralen = parseUi16(buffer: buffer + 24);
523
524 /*
525 qCDebug(KArchiveLog) << "general purpose bit flag: " << gpf;
526 qCDebug(KArchiveLog) << "compressed size: " << compr_size;
527 qCDebug(KArchiveLog) << "uncompressed size: " << uncomp_size;
528 qCDebug(KArchiveLog) << "namelen: " << namelen;
529 qCDebug(KArchiveLog) << "extralen: " << extralen;
530 qCDebug(KArchiveLog) << "archive size: " << dev->size();
531 */
532
533 // read fileName
534 if (namelen <= 0) {
535 setErrorString(tr(sourceText: "Invalid ZIP file. Negative name length"));
536 return false;
537 }
538 QByteArray fileName = dev->read(maxlen: namelen);
539 if (fileName.size() < namelen) {
540 setErrorString(tr(sourceText: "Invalid ZIP file. Name not completely read (#2)"));
541 return false;
542 }
543
544 pfi.mtime = mtime;
545 pfi.dataoffset = 30 + extralen + namelen;
546
547 // read and parse the beginning of the extra field,
548 // skip rest of extra field in case it is too long
549 unsigned int extraFieldEnd = dev->pos() + extralen;
550 int handledextralen = qMin(a: extralen, b: (int)sizeof buffer);
551
552 // if (handledextralen)
553 // qCDebug(KArchiveLog) << "handledextralen: " << handledextralen;
554
555 n = dev->read(data: buffer, maxlen: handledextralen);
556 // no error msg necessary as we deliberately truncate the extra field
557 if (!parseExtraField(buffer, size: n, pfi)) {
558 setErrorString(tr(sourceText: "Invalid ZIP File. Broken ExtraField."));
559 return false;
560 }
561
562 // jump to end of extra field
563 dev->seek(pos: extraFieldEnd);
564
565 // we have to take care of the 'general purpose bit flag'.
566 // if bit 3 is set, the header doesn't contain the length of
567 // the file and we look for the signature 'PK\7\8'.
568 if (gpf & 8) {
569 // here we have to read through the compressed data to find
570 // the next PKxx
571 if (!seekPostDataDescriptor(dev, isZip64)) {
572 setErrorString(tr(sourceText: "Could not seek to next header token"));
573 return false;
574 }
575 } else {
576 // here we skip the compressed data and jump to the next header
577 // qCDebug(KArchiveLog) << "general purpose bit flag indicates, that local file header contains valid size";
578 bool foundSignature = false;
579 // check if this could be a symbolic link
580 if (compression_mode == NoCompression //
581 && uncomp_size <= max_path_len //
582 && uncomp_size > 0) {
583 // read content and store it
584 // If it's not a symlink, then we'll just discard the data for now.
585 pfi.guessed_symlink = dev->read(maxlen: uncomp_size);
586 if (pfi.guessed_symlink.size() < uncomp_size) {
587 setErrorString(tr(sourceText: "Invalid ZIP file. Unexpected end of file. (#5)"));
588 return false;
589 }
590 } else {
591 if (compr_size > dev->size()) {
592 // here we cannot trust the compressed size, so scan through the compressed
593 // data to find the next header
594 if (!seekToNextHeaderToken(dev)) {
595 setErrorString(tr(sourceText: "Could not seek to next header token"));
596 return false;
597 }
598 foundSignature = true;
599 } else {
600 // qCDebug(KArchiveLog) << "before interesting dev->pos(): " << dev->pos();
601 if (dev->skip(maxSize: compr_size) != compr_size) {
602 setErrorString(tr(sourceText: "Could not seek to file compressed size"));
603 return false;
604 }
605 /* qCDebug(KArchiveLog) << "after interesting dev->pos(): " << dev->pos();
606 if (success)
607 qCDebug(KArchiveLog) << "dev->at was successful... ";
608 else
609 qCDebug(KArchiveLog) << "dev->at failed... ";*/
610 }
611 }
612 // test for optional data descriptor
613 if (!foundSignature) {
614 // qCDebug(KArchiveLog) << "Testing for optional data descriptor";
615 // read static data descriptor
616 n = dev->peek(data: buffer, maxlen: 4);
617 if (n < 4) {
618 setErrorString(tr(sourceText: "Invalid ZIP file. Unexpected end of file. (#1)"));
619 return false;
620 }
621
622 QByteArrayView currentHead(buffer, 4);
623 // qCDebug(KArchiveLog) << "Testing for optional data descriptor @ " << dev->pos() << currentHead;
624 if (currentHead.startsWith(other: "PK\x07\x08")) {
625 dev->skip(maxSize: 16); // skip rest of the 'data_descriptor'
626 } else if (!(currentHead.startsWith(other: "PK\x03\x04") || currentHead.startsWith(other: "PK\x01\x02"))) {
627 // assume data descriptor without signature
628 dev->skip(maxSize: 12); // skip rest of the 'data_descriptor'
629 }
630 }
631
632 // not needed any more
633 /* // here we calculate the length of the file in the zip
634 // with headers and jump to the next header.
635 uint skip = compr_size + namelen + extralen;
636 offset += 30 + skip;*/
637 }
638 pfi_map.insert(key: fileName, value: pfi);
639 } else if (!memcmp(s1: buffer, s2: "PK\1\2", n: 4)) { // central block
640 // qCDebug(KArchiveLog) << "PK12 found central block at offset" << dev->pos();
641 startOfFile = false;
642
643 // so we reached the central header at the end of the zip file
644 // here we get all interesting data out of the central header
645 // of a file
646 auto offset = dev->pos() - 4;
647
648 // set offset for appending new files
649 if (d->m_offset == 0) {
650 d->m_offset = offset;
651 }
652
653 n = dev->read(data: buffer + 4, maxlen: 42);
654 if (n < 42) {
655 setErrorString(tr(sourceText: "Invalid ZIP file, central entry too short (not long enough for valid entry)"));
656 return false;
657 }
658
659 // length of extra attributes
660 const int extralen = parseUi16(buffer: buffer + 30);
661 // length of comment for this file
662 const int commlen = parseUi16(buffer: buffer + 32);
663 // compression method of this file
664 const int cmethod = parseUi16(buffer: buffer + 10);
665 // int gpf = parseUi16(buffer + 8);
666 // qCDebug(KArchiveLog) << "general purpose flag=" << gpf;
667 // length of the fileName (well, pathname indeed)
668 const int namelen = parseUi16(buffer: buffer + 28);
669 if (namelen <= 0) {
670 setErrorString(tr(sourceText: "Invalid ZIP file, file path name length is zero"));
671 return false;
672 }
673 const int varDataDesiredLength = namelen + extralen;
674 const QByteArray varData = dev->read(maxlen: varDataDesiredLength);
675 if (varData.length() < varDataDesiredLength) {
676 setErrorString(tr(sourceText: "Invalid ZIP file, unable to read %1 + %2 bytes for filename and extra data at offset %3")
677 .arg(a: namelen, fieldWidth: extralen, base: dev->pos() - varData.size()));
678 return false;
679 }
680
681 ParseFileInfo extrafi;
682 if (extralen) {
683 parseExtraField(buffer: varData.constData() + namelen, size: extralen, pfi&: extrafi);
684 }
685
686 QByteArray bufferName(varData.constData(), namelen);
687
688 ParseFileInfo pfi = pfi_map.value(key: bufferName, defaultValue: ParseFileInfo());
689
690 QString name(QFile::decodeName(localFileName: bufferName));
691
692 // qCDebug(KArchiveLog) << "name: " << name;
693
694 // qCDebug(KArchiveLog) << "cmethod: " << cmethod;
695 // qCDebug(KArchiveLog) << "extralen: " << extralen;
696
697 // crc32 of the file
698 uint crc32 = parseUi32(buffer: buffer + 16);
699
700 // uncompressed file size
701 quint64 ucsize = parseUi32(buffer: buffer + 24);
702 if (ucsize == 0xFFFFFFFF) {
703 ucsize = extrafi.uncompressedSize;
704 }
705 // compressed file size
706 quint64 csize = parseUi32(buffer: buffer + 20);
707 if (csize == 0xFFFFFFFF) {
708 csize = extrafi.compressedSize;
709 }
710
711 // offset of local header
712 quint64 localheaderoffset = parseUi32(buffer: buffer + 42);
713 if (localheaderoffset == 0xFFFFFFFF) {
714 localheaderoffset = extrafi.localheaderoffset;
715 }
716
717 // qCDebug(KArchiveLog) << "localheader dataoffset: " << pfi.dataoffset;
718
719 // offset, where the real data for uncompression starts
720 qint64 dataoffset = localheaderoffset + pfi.dataoffset;
721 if (pfi.localheaderoffset != expectedStartPos + localheaderoffset) {
722 if (pfi.localheaderoffset == d->m_startPos + localheaderoffset) {
723 qCDebug(KArchiveLog) << "warning:" << d->m_startPos << "extra bytes at beginning of zipfile";
724 expectedStartPos = d->m_startPos;
725 } else {
726 setErrorString(tr(sourceText: "Invalid ZIP file, inconsistent Local Header Offset in Central Directory at %1").arg(a: offset));
727 return false;
728 }
729 dataoffset = localheaderoffset + pfi.dataoffset + d->m_startPos;
730 }
731
732 // qCDebug(KArchiveLog) << "csize: " << csize;
733
734 int os_madeby = (uchar)buffer[5];
735 bool isdir = false;
736 int access = 0100644;
737
738 if (os_madeby == 3) { // good ole unix
739 access = parseUi16(buffer: buffer + 40);
740 }
741
742 QString entryName;
743
744 if (name.endsWith(c: QLatin1Char('/'))) { // Entries with a trailing slash are directories
745 isdir = true;
746 name = name.left(n: name.length() - 1);
747 if (os_madeby != 3) {
748 access = S_IFDIR | 0755;
749 } else {
750 access |= S_IFDIR | 0700;
751 }
752 }
753
754 int pos = name.lastIndexOf(c: QLatin1Char('/'));
755 if (pos == -1) {
756 entryName = name;
757 } else {
758 entryName = name.mid(position: pos + 1);
759 }
760 if (entryName.isEmpty()) {
761 setErrorString(tr(sourceText: "Invalid ZIP file, found empty entry name"));
762 return false;
763 }
764
765 KArchiveEntry *entry;
766 if (isdir) {
767 QString path = QDir::cleanPath(path: name);
768 const KArchiveEntry *ent = rootDir()->entry(name: path);
769 if (ent && ent->isDirectory()) {
770 // qCDebug(KArchiveLog) << "Directory already exists, NOT going to add it again";
771 entry = nullptr;
772 } else {
773 QDateTime mtime = KArchivePrivate::time_tToDateTime(seconds: pfi.mtime);
774 entry = new KArchiveDirectory(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), QString());
775 // qCDebug(KArchiveLog) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name;
776 }
777 } else {
778 QString symlink;
779 if ((access & QT_STAT_MASK) == QT_STAT_LNK) {
780 symlink = QFile::decodeName(localFileName: pfi.guessed_symlink);
781 }
782 QDateTime mtime = KArchivePrivate::time_tToDateTime(seconds: pfi.mtime);
783 entry =
784 new KZipFileEntry(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), symlink, name, dataoffset, ucsize, cmethod, csize);
785 static_cast<KZipFileEntry *>(entry)->setHeaderStart(localheaderoffset);
786 static_cast<KZipFileEntry *>(entry)->setCRC32(crc32);
787 // qCDebug(KArchiveLog) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name;
788 d->m_fileList.append(t: static_cast<KZipFileEntry *>(entry));
789 }
790
791 if (entry) {
792 if (pos == -1) {
793 // We don't want to fail opening potentially malformed files, so void the return value
794 (void)rootDir()->addEntryV2(entry);
795 } else {
796 // In some tar files we can find dir/./file => call cleanPath
797 QString path = QDir::cleanPath(path: name.left(n: pos));
798 // Ensure container directory exists, create otherwise
799 KArchiveDirectory *tdir = findOrCreate(path);
800 if (tdir) {
801 (void)tdir->addEntryV2(entry);
802 } else {
803 setErrorString(tr(sourceText: "File %1 is in folder %2, but %3 is actually a file.").arg(args&: entryName, args&: path, args&: path));
804 delete entry;
805 return false;
806 }
807 }
808 }
809
810 // calculate offset to next entry
811 offset += 46 + commlen + extralen + namelen;
812 const bool b = dev->seek(pos: offset);
813 if (!b) {
814 setErrorString(tr(sourceText: "Could not seek to next entry"));
815 return false;
816 }
817 } else if (startOfFile) {
818 // The file does not start with any ZIP header (e.g. self-extractable ZIP files)
819 // Therefore we need to find the first PK\003\004 (local header)
820 // qCDebug(KArchiveLog) << "Try to skip start of file";
821 startOfFile = false;
822 bool foundSignature = false;
823
824 QByteArray header;
825 while (seekAnyHeader(dev, header, minSize: 4)) {
826 // We have to detect the magic token for a local header: PK\003\004
827 /*
828 * Note: we do not need to check the other magics, if the ZIP file has no
829 * local header, then it has not any files!
830 */
831 if (header.startsWith(bv: "PK\x03\x04")) {
832 foundSignature = true;
833 d->m_startPos = dev->pos();
834 break;
835 }
836 if (dev->pos() > 4 * 1024 * 1024) {
837 break;
838 }
839 dev->skip(maxSize: 2); // Skip found 'PK'
840 }
841
842 if (!foundSignature) {
843 setErrorString(tr(sourceText: "Not a ZIP file, no Local File Header found."));
844 return false;
845 }
846 } else {
847 setErrorString(tr(sourceText: "Invalid ZIP file. Unrecognized header at offset %1").arg(a: dev->pos() - 4));
848 return false;
849 }
850 }
851 // qCDebug(KArchiveLog) << "*** done *** ";
852 return true;
853}
854
855bool KZip::closeArchive()
856{
857 if (!(mode() & QIODevice::WriteOnly)) {
858 // qCDebug(KArchiveLog) << "readonly";
859 return true;
860 }
861
862 // ReadWrite or WriteOnly
863 // write all central dir file entries
864
865 // to be written at the end of the file...
866 char buffer[22]; // first used for 12, then for 22 at the end
867 uLong crc = crc32(crc: 0L, buf: nullptr, len: 0);
868
869 qint64 centraldiroffset = device()->pos();
870 // qCDebug(KArchiveLog) << "closearchive: centraldiroffset: " << centraldiroffset;
871 qint64 atbackup = centraldiroffset;
872 for (KZipFileEntry *entry : d->m_fileList) {
873 // set crc and compressed size in each local file header
874 if (!device()->seek(pos: entry->headerStart() + 14)) {
875 setErrorString(tr(sourceText: "Could not seek to next file header: %1").arg(a: device()->errorString()));
876 return false;
877 }
878 // qCDebug(KArchiveLog) << "closearchive setcrcandcsize: fileName:"
879 // << entry->path()
880 // << "encoding:" << entry->encoding();
881
882 uLong mycrc = entry->crc32();
883 buffer[0] = char(mycrc); // crc checksum, at headerStart+14
884 buffer[1] = char(mycrc >> 8);
885 buffer[2] = char(mycrc >> 16);
886 buffer[3] = char(mycrc >> 24);
887
888 int mysize1 = entry->compressedSize();
889 buffer[4] = char(mysize1); // compressed file size, at headerStart+18
890 buffer[5] = char(mysize1 >> 8);
891 buffer[6] = char(mysize1 >> 16);
892 buffer[7] = char(mysize1 >> 24);
893
894 int myusize = entry->size();
895 buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
896 buffer[9] = char(myusize >> 8);
897 buffer[10] = char(myusize >> 16);
898 buffer[11] = char(myusize >> 24);
899
900 if (device()->write(data: buffer, len: 12) != 12) {
901 setErrorString(tr(sourceText: "Could not write file header: %1").arg(a: device()->errorString()));
902 return false;
903 }
904 }
905 device()->seek(pos: atbackup);
906
907 for (KZipFileEntry *entry : d->m_fileList) {
908 // qCDebug(KArchiveLog) << "fileName:" << entry->path()
909 // << "encoding:" << entry->encoding();
910
911 QByteArray path = QFile::encodeName(fileName: entry->path());
912
913 const int extra_field_len = (d->m_extraField == ModificationTime) ? 9 : 0;
914 const int bufferSize = extra_field_len + path.length() + 46;
915 char *buffer = new char[bufferSize];
916
917 memset(s: buffer, c: 0, n: 46); // zero is a nice default for most header fields
918
919 /* clang-format off */
920 const char head[] = {
921 'P', 'K', 1, 2, // central file header signature
922 0x14, 3, // version made by (3 == UNIX)
923 0x14, 0 // version needed to extract
924 };
925 /* clang-format on */
926
927 // I do not know why memcpy is not working here
928 // memcpy(buffer, head, sizeof(head));
929 memmove(dest: buffer, src: head, n: sizeof(head));
930
931 buffer[10] = char(entry->encoding()); // compression method
932 buffer[11] = char(entry->encoding() >> 8);
933
934 transformToMsDos(dt: entry->date(), buffer: &buffer[12]);
935
936 uLong mycrc = entry->crc32();
937 buffer[16] = char(mycrc); // crc checksum
938 buffer[17] = char(mycrc >> 8);
939 buffer[18] = char(mycrc >> 16);
940 buffer[19] = char(mycrc >> 24);
941
942 int mysize1 = entry->compressedSize();
943 buffer[20] = char(mysize1); // compressed file size
944 buffer[21] = char(mysize1 >> 8);
945 buffer[22] = char(mysize1 >> 16);
946 buffer[23] = char(mysize1 >> 24);
947
948 int mysize = entry->size();
949 buffer[24] = char(mysize); // uncompressed file size
950 buffer[25] = char(mysize >> 8);
951 buffer[26] = char(mysize >> 16);
952 buffer[27] = char(mysize >> 24);
953
954 buffer[28] = char(path.length()); // fileName length
955 buffer[29] = char(path.length() >> 8);
956
957 buffer[30] = char(extra_field_len);
958 buffer[31] = char(extra_field_len >> 8);
959
960 buffer[40] = char(entry->permissions());
961 buffer[41] = char(entry->permissions() >> 8);
962
963 int myhst = entry->headerStart();
964 buffer[42] = char(myhst); // relative offset of local header
965 buffer[43] = char(myhst >> 8);
966 buffer[44] = char(myhst >> 16);
967 buffer[45] = char(myhst >> 24);
968
969 // file name
970 strncpy(dest: buffer + 46, src: path.constData(), n: path.length());
971 // qCDebug(KArchiveLog) << "closearchive length to write: " << bufferSize;
972
973 // extra field
974 if (d->m_extraField == ModificationTime) {
975 char *extfield = buffer + 46 + path.length();
976 // "Extended timestamp" header (0x5455)
977 extfield[0] = 'U';
978 extfield[1] = 'T';
979 extfield[2] = 5; // data size
980 extfield[3] = 0;
981 extfield[4] = 1 | 2 | 4; // specify flags from local field
982 // (unless I misread the spec)
983 // provide only modification time
984 unsigned long time = (unsigned long)entry->date().toSecsSinceEpoch();
985 extfield[5] = char(time);
986 extfield[6] = char(time >> 8);
987 extfield[7] = char(time >> 16);
988 extfield[8] = char(time >> 24);
989 }
990
991 crc = crc32(crc, buf: (Bytef *)buffer, len: bufferSize);
992 bool ok = (device()->write(data: buffer, len: bufferSize) == bufferSize);
993 delete[] buffer;
994 if (!ok) {
995 setErrorString(tr(sourceText: "Could not write file header: %1").arg(a: device()->errorString()));
996 return false;
997 }
998 }
999 qint64 centraldirendoffset = device()->pos();
1000 // qCDebug(KArchiveLog) << "closearchive: centraldirendoffset: " << centraldirendoffset;
1001 // qCDebug(KArchiveLog) << "closearchive: device()->pos(): " << device()->pos();
1002
1003 // write end of central dir record.
1004 buffer[0] = 'P'; // end of central dir signature
1005 buffer[1] = 'K';
1006 buffer[2] = 5;
1007 buffer[3] = 6;
1008
1009 buffer[4] = 0; // number of this disk
1010 buffer[5] = 0;
1011
1012 buffer[6] = 0; // number of disk with start of central dir
1013 buffer[7] = 0;
1014
1015 int count = d->m_fileList.count();
1016 // qCDebug(KArchiveLog) << "number of files (count): " << count;
1017
1018 buffer[8] = char(count); // total number of entries in central dir of
1019 buffer[9] = char(count >> 8); // this disk
1020
1021 buffer[10] = buffer[8]; // total number of entries in the central dir
1022 buffer[11] = buffer[9];
1023
1024 int cdsize = centraldirendoffset - centraldiroffset;
1025 buffer[12] = char(cdsize); // size of the central dir
1026 buffer[13] = char(cdsize >> 8);
1027 buffer[14] = char(cdsize >> 16);
1028 buffer[15] = char(cdsize >> 24);
1029
1030 // qCDebug(KArchiveLog) << "end : centraldiroffset: " << centraldiroffset;
1031 // qCDebug(KArchiveLog) << "end : centraldirsize: " << cdsize;
1032
1033 buffer[16] = char(centraldiroffset); // central dir offset
1034 buffer[17] = char(centraldiroffset >> 8);
1035 buffer[18] = char(centraldiroffset >> 16);
1036 buffer[19] = char(centraldiroffset >> 24);
1037
1038 buffer[20] = 0; // zipfile comment length
1039 buffer[21] = 0;
1040
1041 if (device()->write(data: buffer, len: 22) != 22) {
1042 setErrorString(tr(sourceText: "Could not write central dir record: %1").arg(a: device()->errorString()));
1043 return false;
1044 }
1045
1046 return true;
1047}
1048
1049bool KZip::doWriteDir(const QString &name,
1050 const QString &user,
1051 const QString &group,
1052 mode_t perm,
1053 const QDateTime &atime,
1054 const QDateTime &mtime,
1055 const QDateTime &ctime)
1056{
1057 // Zip files have no explicit directories, they are implicitly created during extraction time
1058 // when file entries have paths in them.
1059 // However, to support empty directories, we must create a dummy file entry which ends with '/'.
1060 QString dirName = name;
1061 if (!name.endsWith(c: QLatin1Char('/'))) {
1062 dirName = dirName.append(c: QLatin1Char('/'));
1063 }
1064 return writeFile(name: dirName, data: QByteArrayView(), perm, user, group, atime, mtime, ctime);
1065}
1066
1067bool KZip::doPrepareWriting(const QString &name,
1068 const QString &user,
1069 const QString &group,
1070 qint64 /*size*/,
1071 mode_t perm,
1072 const QDateTime &accessTime,
1073 const QDateTime &modificationTime,
1074 const QDateTime &creationTime)
1075{
1076 // qCDebug(KArchiveLog);
1077 if (!isOpen()) {
1078 setErrorString(tr(sourceText: "Application error: ZIP file must be open before being written into"));
1079 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
1080 return false;
1081 }
1082
1083 if (!(mode() & QIODevice::WriteOnly)) { // accept WriteOnly and ReadWrite
1084 setErrorString(tr(sourceText: "Application error: attempted to write into non-writable ZIP file"));
1085 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
1086 return false;
1087 }
1088
1089 if (!device()) {
1090 setErrorString(tr(sourceText: "Cannot create a device. Disk full?"));
1091 return false;
1092 }
1093
1094 // set right offset in zip.
1095 if (!device()->seek(pos: d->m_offset)) {
1096 setErrorString(tr(sourceText: "Cannot seek in ZIP file. Disk full?"));
1097 return false;
1098 }
1099
1100 uint atime = accessTime.toSecsSinceEpoch();
1101 uint mtime = modificationTime.toSecsSinceEpoch();
1102 uint ctime = creationTime.toSecsSinceEpoch();
1103
1104 // Find or create parent dir
1105 KArchiveDirectory *parentDir = rootDir();
1106 QString fileName(name);
1107 int i = name.lastIndexOf(c: QLatin1Char('/'));
1108 if (i != -1) {
1109 QString dir = name.left(n: i);
1110 fileName = name.mid(position: i + 1);
1111 // qCDebug(KArchiveLog) << "ensuring" << dir << "exists. fileName=" << fileName;
1112 parentDir = findOrCreate(path: dir);
1113 }
1114
1115 // delete entries in the filelist with the same fileName as the one we want
1116 // to save, so that we don't have duplicate file entries when viewing the zip
1117 // with konqi...
1118 // CAUTION: the old file itself is still in the zip and won't be removed !!!
1119 // qCDebug(KArchiveLog) << "fileName to write: " << name;
1120 for (auto it = d->m_fileList.begin(); it != d->m_fileList.end();) {
1121 // qCDebug(KArchiveLog) << "prepfileName: " << entry->path();
1122 if (name == (*it)->path()) {
1123 // also remove from the parentDir
1124 if (!parentDir->removeEntryV2(*it)) {
1125 return false;
1126 }
1127 // qCDebug(KArchiveLog) << "removing following entry: " << entry->path();
1128 delete *it;
1129 it = d->m_fileList.erase(pos: it);
1130 } else {
1131 it++;
1132 }
1133 }
1134
1135 // construct a KZipFileEntry and add it to list
1136 KZipFileEntry *e = new KZipFileEntry(this,
1137 fileName,
1138 perm,
1139 modificationTime,
1140 user,
1141 group,
1142 QString(),
1143 name,
1144 device()->pos() + 30 + name.length(), // start
1145 0 /*size unknown yet*/,
1146 d->m_compression,
1147 0 /*csize unknown yet*/);
1148 e->setHeaderStart(device()->pos());
1149 // qCDebug(KArchiveLog) << "wrote file start: " << e->position() << " name: " << name;
1150 if (!parentDir->addEntryV2(e)) {
1151 return false;
1152 }
1153
1154 d->m_currentFile = e;
1155 d->m_fileList.append(t: e);
1156
1157 int extra_field_len = 0;
1158 if (d->m_extraField == ModificationTime) {
1159 extra_field_len = 17; // value also used in finishWriting()
1160 }
1161
1162 // write out zip header
1163 QByteArray encodedName = QFile::encodeName(fileName: name);
1164 int bufferSize = extra_field_len + encodedName.length() + 30;
1165 // qCDebug(KArchiveLog) << "bufferSize=" << bufferSize;
1166 char *buffer = new char[bufferSize];
1167
1168 buffer[0] = 'P'; // local file header signature
1169 buffer[1] = 'K';
1170 buffer[2] = 3;
1171 buffer[3] = 4;
1172
1173 buffer[4] = 0x14; // version needed to extract
1174 buffer[5] = 0;
1175
1176 buffer[6] = 0; // general purpose bit flag
1177 buffer[7] = 0;
1178
1179 buffer[8] = char(e->encoding()); // compression method
1180 buffer[9] = char(e->encoding() >> 8);
1181
1182 transformToMsDos(dt: e->date(), buffer: &buffer[10]);
1183
1184 buffer[14] = 'C'; // dummy crc
1185 buffer[15] = 'R';
1186 buffer[16] = 'C';
1187 buffer[17] = 'q';
1188
1189 buffer[18] = 'C'; // compressed file size
1190 buffer[19] = 'S';
1191 buffer[20] = 'I';
1192 buffer[21] = 'Z';
1193
1194 buffer[22] = 'U'; // uncompressed file size
1195 buffer[23] = 'S';
1196 buffer[24] = 'I';
1197 buffer[25] = 'Z';
1198
1199 buffer[26] = (uchar)(encodedName.length()); // fileName length
1200 buffer[27] = (uchar)(encodedName.length() >> 8);
1201
1202 buffer[28] = (uchar)(extra_field_len); // extra field length
1203 buffer[29] = (uchar)(extra_field_len >> 8);
1204
1205 // file name
1206 strncpy(dest: buffer + 30, src: encodedName.constData(), n: encodedName.length());
1207
1208 // extra field
1209 if (d->m_extraField == ModificationTime) {
1210 char *extfield = buffer + 30 + encodedName.length();
1211 // "Extended timestamp" header (0x5455)
1212 extfield[0] = 'U';
1213 extfield[1] = 'T';
1214 extfield[2] = 13; // data size
1215 extfield[3] = 0;
1216 extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime
1217
1218 extfield[5] = char(mtime);
1219 extfield[6] = char(mtime >> 8);
1220 extfield[7] = char(mtime >> 16);
1221 extfield[8] = char(mtime >> 24);
1222
1223 extfield[9] = char(atime);
1224 extfield[10] = char(atime >> 8);
1225 extfield[11] = char(atime >> 16);
1226 extfield[12] = char(atime >> 24);
1227
1228 extfield[13] = char(ctime);
1229 extfield[14] = char(ctime >> 8);
1230 extfield[15] = char(ctime >> 16);
1231 extfield[16] = char(ctime >> 24);
1232 }
1233
1234 // Write header
1235 bool b = (device()->write(data: buffer, len: bufferSize) == bufferSize);
1236 d->m_crc = 0;
1237 delete[] buffer;
1238
1239 if (!b) {
1240 setErrorString(tr(sourceText: "Could not write to the archive. Disk full?"));
1241 return false;
1242 }
1243
1244 // Prepare device for writing the data
1245 // Either device() if no compression, or a KCompressionDevice to compress
1246 if (d->m_compression == 0) {
1247 d->m_currentDev = device();
1248 return true;
1249 }
1250
1251 auto compressionDevice = new KCompressionDevice(device(), false, KCompressionDevice::GZip);
1252 d->m_currentDev = compressionDevice;
1253 compressionDevice->setSkipHeaders(); // Just zlib, not gzip
1254
1255 b = d->m_currentDev->open(mode: QIODevice::WriteOnly);
1256 Q_ASSERT(b);
1257
1258 if (!b) {
1259 setErrorString(tr(sourceText: "Could not open compression device: %1").arg(a: d->m_currentDev->errorString()));
1260 }
1261
1262 return b;
1263}
1264
1265bool KZip::doFinishWriting(qint64 size)
1266{
1267 if (d->m_currentFile->encoding() == 8) {
1268 // Finish
1269 (void)d->m_currentDev->write(data: nullptr, len: 0);
1270 delete d->m_currentDev;
1271 }
1272 // If 0, d->m_currentDev was device() - don't delete ;)
1273 d->m_currentDev = nullptr;
1274
1275 Q_ASSERT(d->m_currentFile);
1276 // qCDebug(KArchiveLog) << "fileName: " << d->m_currentFile->path();
1277 // qCDebug(KArchiveLog) << "getpos (at): " << device()->pos();
1278 d->m_currentFile->setSize(size);
1279 int extra_field_len = 0;
1280 if (d->m_extraField == ModificationTime) {
1281 extra_field_len = 17; // value also used in finishWriting()
1282 }
1283
1284 const QByteArray encodedName = QFile::encodeName(fileName: d->m_currentFile->path());
1285 int csize = device()->pos() - d->m_currentFile->headerStart() - 30 - encodedName.length() - extra_field_len;
1286 d->m_currentFile->setCompressedSize(csize);
1287 // qCDebug(KArchiveLog) << "usize: " << d->m_currentFile->size();
1288 // qCDebug(KArchiveLog) << "csize: " << d->m_currentFile->compressedSize();
1289 // qCDebug(KArchiveLog) << "headerstart: " << d->m_currentFile->headerStart();
1290
1291 // qCDebug(KArchiveLog) << "crc: " << d->m_crc;
1292 d->m_currentFile->setCRC32(d->m_crc);
1293
1294 d->m_currentFile = nullptr;
1295
1296 // update saved offset for appending new files
1297 d->m_offset = device()->pos();
1298 return true;
1299}
1300
1301bool KZip::doWriteSymLink(const QString &name,
1302 const QString &target,
1303 const QString &user,
1304 const QString &group,
1305 mode_t perm,
1306 const QDateTime &atime,
1307 const QDateTime &mtime,
1308 const QDateTime &ctime)
1309{
1310 // reassure that symlink flag is set, otherwise strange things happen on
1311 // extraction
1312 perm |= QT_STAT_LNK;
1313 Compression c = compression();
1314 setCompression(NoCompression); // link targets are never compressed
1315
1316 if (!doPrepareWriting(name, user, group, 0, perm, accessTime: atime, modificationTime: mtime, creationTime: ctime)) {
1317 setCompression(c);
1318 return false;
1319 }
1320
1321 QByteArray symlink_target = QFile::encodeName(fileName: target);
1322 if (!writeData(data: symlink_target.constData(), size: symlink_target.length())) {
1323 setCompression(c);
1324 return false;
1325 }
1326
1327 if (!finishWriting(size: symlink_target.length())) {
1328 setCompression(c);
1329 return false;
1330 }
1331
1332 setCompression(c);
1333 return true;
1334}
1335
1336void KZip::virtual_hook(int id, void *data)
1337{
1338 KArchive::virtual_hook(id, data);
1339}
1340
1341bool KZip::doWriteData(const char *data, qint64 size)
1342{
1343 Q_ASSERT(d->m_currentFile);
1344 Q_ASSERT(d->m_currentDev);
1345 if (!d->m_currentFile || !d->m_currentDev) {
1346 setErrorString(tr(sourceText: "No file or device"));
1347 return false;
1348 }
1349
1350 // crc to be calculated over uncompressed stuff...
1351 // and they didn't mention it in their docs...
1352 d->m_crc = crc32(crc: d->m_crc, buf: (const Bytef *)data, len: size);
1353
1354 qint64 written = d->m_currentDev->write(data, len: size);
1355 // qCDebug(KArchiveLog) << "wrote" << size << "bytes.";
1356 const bool ok = written == size;
1357
1358 if (!ok) {
1359 setErrorString(tr(sourceText: "Error writing data: %1").arg(a: d->m_currentDev->errorString()));
1360 }
1361
1362 return ok;
1363}
1364
1365void KZip::setCompression(Compression c)
1366{
1367 d->m_compression = (c == NoCompression) ? 0 : 8;
1368}
1369
1370KZip::Compression KZip::compression() const
1371{
1372 return (d->m_compression == 8) ? DeflateCompression : NoCompression;
1373}
1374
1375void KZip::setExtraField(ExtraField ef)
1376{
1377 d->m_extraField = ef;
1378}
1379
1380KZip::ExtraField KZip::extraField() const
1381{
1382 return d->m_extraField;
1383}
1384
1385////////////////////////////////////////////////////////////////////////
1386////////////////////// KZipFileEntry////////////////////////////////////
1387////////////////////////////////////////////////////////////////////////
1388class Q_DECL_HIDDEN KZipFileEntry::KZipFileEntryPrivate
1389{
1390public:
1391 KZipFileEntryPrivate()
1392 : crc(0)
1393 , compressedSize(0)
1394 , headerStart(0)
1395 , encoding(0)
1396 {
1397 }
1398 unsigned long crc;
1399 qint64 compressedSize;
1400 qint64 headerStart;
1401 int encoding;
1402 QString path;
1403};
1404
1405KZipFileEntry::KZipFileEntry(KZip *zip,
1406 const QString &name,
1407 int access,
1408 const QDateTime &date,
1409 const QString &user,
1410 const QString &group,
1411 const QString &symlink,
1412 const QString &path,
1413 qint64 start,
1414 qint64 uncompressedSize,
1415 int encoding,
1416 qint64 compressedSize)
1417 : KArchiveFile(zip, name, access, date, user, group, symlink, start, uncompressedSize)
1418 , d(new KZipFileEntryPrivate)
1419{
1420 d->path = path;
1421 d->encoding = encoding;
1422 d->compressedSize = compressedSize;
1423}
1424
1425KZipFileEntry::~KZipFileEntry()
1426{
1427 delete d;
1428}
1429
1430int KZipFileEntry::encoding() const
1431{
1432 return d->encoding;
1433}
1434
1435qint64 KZipFileEntry::compressedSize() const
1436{
1437 return d->compressedSize;
1438}
1439
1440void KZipFileEntry::setCompressedSize(qint64 compressedSize)
1441{
1442 d->compressedSize = compressedSize;
1443}
1444
1445void KZipFileEntry::setHeaderStart(qint64 headerstart)
1446{
1447 d->headerStart = headerstart;
1448}
1449
1450qint64 KZipFileEntry::headerStart() const
1451{
1452 return d->headerStart;
1453}
1454
1455unsigned long KZipFileEntry::crc32() const
1456{
1457 return d->crc;
1458}
1459
1460void KZipFileEntry::setCRC32(unsigned long crc32)
1461{
1462 d->crc = crc32;
1463}
1464
1465const QString &KZipFileEntry::path() const
1466{
1467 return d->path;
1468}
1469
1470QByteArray KZipFileEntry::data() const
1471{
1472 QIODevice *dev = createDevice();
1473 QByteArray arr;
1474 if (dev) {
1475 const qint64 devSize = dev->size();
1476 if (devSize > kMaxQByteArraySize) {
1477 qCWarning(KArchiveLog) << "KZipFileEntry::data: Failed to allocate memory for file of size" << devSize;
1478 delete dev;
1479 return {};
1480 }
1481 arr = dev->readAll();
1482 delete dev;
1483 }
1484 return arr;
1485}
1486
1487QIODevice *KZipFileEntry::createDevice() const
1488{
1489 if (compressedSize() < 0) {
1490 qCWarning(KArchiveLog) << "KZipFileEntry::createDevice: Entry with negative size" << path() << compressedSize();
1491 return nullptr;
1492 }
1493 // qCDebug(KArchiveLog) << "creating iodevice limited to pos=" << position() << ", csize=" << compressedSize();
1494 // Limit the reading to the appropriate part of the underlying device (e.g. file)
1495 std::unique_ptr limitedDev = std::make_unique<KLimitedIODevice>(args: archive()->device(), args: position(), args: compressedSize());
1496 if (encoding() == 0 || compressedSize() == 0) { // no compression (or even no data)
1497 return limitedDev.release();
1498 }
1499
1500 if (encoding() == 8) {
1501 // On top of that, create a device that uncompresses the zlib data
1502 KCompressionDevice *filterDev = new KCompressionDevice(std::move(limitedDev), KCompressionDevice::GZip, size());
1503
1504 if (!filterDev) {
1505 return nullptr; // ouch
1506 }
1507 filterDev->setSkipHeaders(); // Just zlib, not gzip
1508 bool b = filterDev->open(mode: QIODevice::ReadOnly);
1509 Q_UNUSED(b);
1510 Q_ASSERT(b);
1511 return filterDev;
1512 }
1513
1514 qCCritical(KArchiveLog) << "This zip file contains files compressed with method" << encoding() << ", this method is currently not supported by KZip,"
1515 << "please use a command-line tool to handle this file.";
1516 return nullptr;
1517}
1518

source code of karchive/src/kzip.cpp