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 void transformToMsDos(const QDateTime &_dt, char *buffer)
35{
36 const QDateTime dt = _dt.isValid() ? _dt : QDateTime::currentDateTime();
37 /* clang-format off */
38 const quint16 time = (dt.time().hour() << 11) // 5 bit hour
39 | (dt.time().minute() << 5) // 6 bit minute
40 | (dt.time().second() >> 1); // 5 bit double seconds
41 /* clang-format on */
42
43 buffer[0] = char(time);
44 buffer[1] = char(time >> 8);
45
46 /* clang-format off */
47 const quint16 date = ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
48 | (dt.date().month() << 5) // 4 bit month
49 | (dt.date().day()); // 5 bit day
50 /* clang-format on */
51
52 buffer[2] = char(date);
53 buffer[3] = char(date >> 8);
54}
55
56static uint transformFromMsDos(const char *buffer)
57{
58 quint16 time = (uchar)buffer[0] | ((uchar)buffer[1] << 8);
59 int h = time >> 11;
60 int m = (time & 0x7ff) >> 5;
61 int s = (time & 0x1f) * 2;
62 QTime qt(h, m, s);
63
64 quint16 date = (uchar)buffer[2] | ((uchar)buffer[3] << 8);
65 int y = (date >> 9) + 1980;
66 int o = (date & 0x1ff) >> 5;
67 int d = (date & 0x1f);
68 QDate qd(y, o, d);
69
70 QDateTime dt(qd, qt);
71 return dt.toSecsSinceEpoch();
72}
73
74static uint parseUi32(const char *buffer)
75{
76 return uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
77}
78
79static quint64 parseUi64(const char *buffer)
80{
81 const uint a = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
82 const uint b = uint((uchar)buffer[4] | (uchar)buffer[5] << 8 | (uchar)buffer[6] << 16 | (uchar)buffer[7] << 24);
83 return (a | (quint64)b << 32);
84}
85
86// == parsing routines for zip headers
87
88/** all relevant information about parsing file information */
89struct ParseFileInfo {
90 // file related info
91 mode_t perm; // permissions of this file
92 // TODO: use quint32 instead of a uint?
93 uint atime; // last access time (UNIX format)
94 uint mtime; // modification time (UNIX format)
95 uint ctime; // creation time (UNIX format)
96 int uid; // user id (-1 if not specified)
97 int gid; // group id (-1 if not specified)
98 QByteArray guessed_symlink; // guessed symlink target
99 uint dataoffset = 0; // offset from start of local header to data
100
101 // parsing related info
102 bool exttimestamp_seen; // true if extended timestamp extra field
103 // has been parsed
104 bool newinfounix_seen; // true if Info-ZIP Unix New extra field has
105 // been parsed
106
107 // file sizes from a ZIP64 extra field
108 quint64 uncompressedSize = 0;
109 quint64 compressedSize = 0;
110 // position of the Local File Header itself, or from the
111 // ZIP64 extra field in the Central Directory
112 quint64 localheaderoffset = 0;
113
114 ParseFileInfo()
115 : perm(0100644)
116 , uid(-1)
117 , gid(-1)
118 , exttimestamp_seen(false)
119 , newinfounix_seen(false)
120 {
121 ctime = mtime = atime = time(timer: nullptr);
122 }
123};
124
125/** updates the parse information with the given extended timestamp extra field.
126 * @param buffer start content of buffer known to contain an extended
127 * timestamp extra field (without magic & size)
128 * @param size size of field content (must not count magic and size entries)
129 * @param islocal true if this is a local field, false if central
130 * @param pfi ParseFileInfo object to be updated
131 * @return true if processing was successful
132 */
133static bool parseExtTimestamp(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
134{
135 if (size < 1) {
136 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#1)";
137 return false;
138 } /*end if*/
139 int flags = *buffer; // read flags
140 buffer += 1;
141 size -= 1;
142
143 if (flags & 1) { // contains modification time
144 if (size < 4) {
145 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#2)";
146 return false;
147 } /*end if*/
148 pfi.mtime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
149 buffer += 4;
150 size -= 4;
151 } /*end if*/
152 // central extended field cannot contain more than the modification time
153 // even if other flags are set
154 if (!islocal) {
155 pfi.exttimestamp_seen = true;
156 return true;
157 } /*end if*/
158
159 if (flags & 2) { // contains last access time
160 if (size < 4) {
161 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#3)";
162 return true;
163 } /*end if*/
164 pfi.atime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
165 buffer += 4;
166 size -= 4;
167 } /*end if*/
168
169 if (flags & 4) { // contains creation time
170 if (size < 4) {
171 // qCDebug(KArchiveLog) << "premature end of extended timestamp (#4)";
172 return true;
173 } /*end if*/
174 pfi.ctime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
175 buffer += 4;
176 } /*end if*/
177
178 pfi.exttimestamp_seen = true;
179 return true;
180}
181
182/** updates the parse information with the given Info-ZIP Unix old extra field.
183 * @param buffer start of content of buffer known to contain an Info-ZIP
184 * Unix old extra field (without magic & size)
185 * @param size size of field content (must not count magic and size entries)
186 * @param islocal true if this is a local field, false if central
187 * @param pfi ParseFileInfo object to be updated
188 * @return true if processing was successful
189 */
190static bool parseInfoZipUnixOld(const char *buffer, int size, bool islocal, ParseFileInfo &pfi)
191{
192 // spec mandates to omit this field if one of the newer fields are available
193 if (pfi.exttimestamp_seen || pfi.newinfounix_seen) {
194 return true;
195 }
196
197 if (size < 8) {
198 // qCDebug(KArchiveLog) << "premature end of Info-ZIP unix extra field old";
199 return false;
200 }
201
202 pfi.atime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
203 buffer += 4;
204 pfi.mtime = uint((uchar)buffer[0] | (uchar)buffer[1] << 8 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
205 buffer += 4;
206 if (islocal && size >= 12) {
207 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
208 buffer += 2;
209 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
210 buffer += 2;
211 } /*end if*/
212 return true;
213}
214
215#if 0 // not needed yet
216/** updates the parse information with the given Info-ZIP Unix new extra field.
217 * @param buffer start of content of buffer known to contain an Info-ZIP
218 * Unix new extra field (without magic & size)
219 * @param size size of field content (must not count magic and size entries)
220 * @param islocal true if this is a local field, false if central
221 * @param pfi ParseFileInfo object to be updated
222 * @return true if processing was successful
223 */
224static bool parseInfoZipUnixNew(const char *buffer, int size, bool islocal,
225 ParseFileInfo &pfi)
226{
227 if (!islocal) { // contains nothing in central field
228 pfi.newinfounix = true;
229 return true;
230 }
231
232 if (size < 4) {
233 qCDebug(KArchiveLog) << "premature end of Info-ZIP unix extra field new";
234 return false;
235 }
236
237 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
238 buffer += 2;
239 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
240 buffer += 2;
241
242 pfi.newinfounix = true;
243 return true;
244}
245#endif
246
247/**
248 * parses the extra field
249 * @param buffer start of buffer where the extra field is to be found
250 * @param size size of the extra field
251 * @param pfi ParseFileInfo object which to write the results into
252 * @return true if parsing was successful
253 */
254static bool parseExtraField(const char *buffer, int size, ParseFileInfo &pfi)
255{
256 while (size >= 4) { // as long as a potential extra field can be read
257 int magic = (uchar)buffer[0] | (uchar)buffer[1] << 8;
258 buffer += 2;
259 int fieldsize = (uchar)buffer[0] | (uchar)buffer[1] << 8;
260 buffer += 2;
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->seek(pos: dev->pos() + 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->seek(pos: dev->pos() + (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->seek(pos: dev->pos() + descriptorSize);
369 return true;
370 }
371 dev->seek(pos: dev->pos() + 4); // Skip found 'PK\7\8'
372 } else {
373 dev->seek(pos: dev->pos() + 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->seek(pos: dev->pos() + 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 = (uchar)buffer[0] | (uchar)buffer[1] << 8;
513 bool isZip64 = neededVersion >= 45;
514
515 int gpf = (uchar)buffer[2]; // "general purpose flag" not "general protection fault" ;-)
516 int compression_mode = (uchar)buffer[4] | (uchar)buffer[5] << 8;
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 = uint(uchar(buffer[22])) | uint(uchar(buffer[23])) << 8;
522 const int extralen = uint(uchar(buffer[24])) | uint(uchar(buffer[25])) << 8;
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 const bool success = dev->seek(pos: dev->pos() + compr_size);
602 if (!success) {
603 setErrorString(tr(sourceText: "Could not seek to file compressed size"));
604 return false;
605 }
606 /* qCDebug(KArchiveLog) << "after interesting dev->pos(): " << dev->pos();
607 if (success)
608 qCDebug(KArchiveLog) << "dev->at was successful... ";
609 else
610 qCDebug(KArchiveLog) << "dev->at failed... ";*/
611 }
612 }
613 // test for optional data descriptor
614 if (!foundSignature) {
615 // qCDebug(KArchiveLog) << "Testing for optional data descriptor";
616 // read static data descriptor
617 n = dev->peek(data: buffer, maxlen: 4);
618 if (n < 4) {
619 setErrorString(tr(sourceText: "Invalid ZIP file. Unexpected end of file. (#1)"));
620 return false;
621 }
622
623 QByteArrayView currentHead(buffer, 4);
624 // qCDebug(KArchiveLog) << "Testing for optional data descriptor @ " << dev->pos() << currentHead;
625 if (currentHead.startsWith(other: "PK\x07\x08")) {
626 dev->seek(pos: dev->pos() + 16); // skip rest of the 'data_descriptor'
627 } else if (!(currentHead.startsWith(other: "PK\x03\x04") || currentHead.startsWith(other: "PK\x01\x02"))) {
628 // assume data descriptor without signature
629 dev->seek(pos: dev->pos() + 12); // skip rest of the 'data_descriptor'
630 }
631 }
632
633 // not needed any more
634 /* // here we calculate the length of the file in the zip
635 // with headers and jump to the next header.
636 uint skip = compr_size + namelen + extralen;
637 offset += 30 + skip;*/
638 }
639 pfi_map.insert(key: fileName, value: pfi);
640 } else if (!memcmp(s1: buffer, s2: "PK\1\2", n: 4)) { // central block
641 // qCDebug(KArchiveLog) << "PK12 found central block at offset" << dev->pos();
642 startOfFile = false;
643
644 // so we reached the central header at the end of the zip file
645 // here we get all interesting data out of the central header
646 // of a file
647 auto offset = dev->pos() - 4;
648
649 // set offset for appending new files
650 if (d->m_offset == 0) {
651 d->m_offset = offset;
652 }
653
654 n = dev->read(data: buffer + 4, maxlen: 42);
655 if (n < 42) {
656 setErrorString(tr(sourceText: "Invalid ZIP file, central entry too short (not long enough for valid entry)"));
657 return false;
658 }
659
660 // length of extra attributes
661 int extralen = (uchar)buffer[31] << 8 | (uchar)buffer[30];
662 // length of comment for this file
663 int commlen = (uchar)buffer[33] << 8 | (uchar)buffer[32];
664 // compression method of this file
665 int cmethod = (uchar)buffer[11] << 8 | (uchar)buffer[10];
666 // int gpf = (uchar)buffer[9] << 8 | (uchar)buffer[10];
667 // qCDebug(KArchiveLog) << "general purpose flag=" << gpf;
668 // length of the fileName (well, pathname indeed)
669 int namelen = (uchar)buffer[29] << 8 | (uchar)buffer[28];
670 if (namelen <= 0) {
671 setErrorString(tr(sourceText: "Invalid ZIP file, file path name length is zero"));
672 return false;
673 }
674 const int varDataDesiredLength = namelen + extralen;
675 const QByteArray varData = dev->read(maxlen: varDataDesiredLength);
676 if (varData.length() < varDataDesiredLength) {
677 setErrorString(tr(sourceText: "Invalid ZIP file, unable to read %1 + %2 bytes for filename and extra data at offset %3")
678 .arg(a: namelen, fieldWidth: extralen, base: dev->pos() - varData.size()));
679 return false;
680 }
681
682 ParseFileInfo extrafi;
683 if (extralen) {
684 parseExtraField(buffer: varData.constData() + namelen, size: extralen, pfi&: extrafi);
685 }
686
687 QByteArray bufferName(varData.constData(), namelen);
688
689 ParseFileInfo pfi = pfi_map.value(key: bufferName, defaultValue: ParseFileInfo());
690
691 QString name(QFile::decodeName(localFileName: bufferName));
692
693 // qCDebug(KArchiveLog) << "name: " << name;
694
695 // qCDebug(KArchiveLog) << "cmethod: " << cmethod;
696 // qCDebug(KArchiveLog) << "extralen: " << extralen;
697
698 // crc32 of the file
699 uint crc32 = parseUi32(buffer: buffer + 16);
700
701 // uncompressed file size
702 quint64 ucsize = parseUi32(buffer: buffer + 24);
703 if (ucsize == 0xFFFFFFFF) {
704 ucsize = extrafi.uncompressedSize;
705 }
706 // compressed file size
707 quint64 csize = parseUi32(buffer: buffer + 20);
708 if (csize == 0xFFFFFFFF) {
709 csize = extrafi.compressedSize;
710 }
711
712 // offset of local header
713 quint64 localheaderoffset = parseUi32(buffer: buffer + 42);
714 if (localheaderoffset == 0xFFFFFFFF) {
715 localheaderoffset = extrafi.localheaderoffset;
716 }
717
718 // qCDebug(KArchiveLog) << "localheader dataoffset: " << pfi.dataoffset;
719
720 // offset, where the real data for uncompression starts
721 qint64 dataoffset = localheaderoffset + pfi.dataoffset;
722 if (pfi.localheaderoffset != expectedStartPos + localheaderoffset) {
723 if (pfi.localheaderoffset == d->m_startPos + localheaderoffset) {
724 qCDebug(KArchiveLog) << "warning:" << d->m_startPos << "extra bytes at beginning of zipfile";
725 expectedStartPos = d->m_startPos;
726 } else {
727 setErrorString(tr(sourceText: "Invalid ZIP file, inconsistent Local Header Offset in Central Directory at %1").arg(a: offset));
728 return false;
729 }
730 dataoffset = localheaderoffset + pfi.dataoffset + d->m_startPos;
731 }
732
733 // qCDebug(KArchiveLog) << "csize: " << csize;
734
735 int os_madeby = (uchar)buffer[5];
736 bool isdir = false;
737 int access = 0100644;
738
739 if (os_madeby == 3) { // good ole unix
740 access = (uchar)buffer[40] | (uchar)buffer[41] << 8;
741 }
742
743 QString entryName;
744
745 if (name.endsWith(c: QLatin1Char('/'))) { // Entries with a trailing slash are directories
746 isdir = true;
747 name = name.left(n: name.length() - 1);
748 if (os_madeby != 3) {
749 access = S_IFDIR | 0755;
750 } else {
751 access |= S_IFDIR | 0700;
752 }
753 }
754
755 int pos = name.lastIndexOf(c: QLatin1Char('/'));
756 if (pos == -1) {
757 entryName = name;
758 } else {
759 entryName = name.mid(position: pos + 1);
760 }
761 if (entryName.isEmpty()) {
762 setErrorString(tr(sourceText: "Invalid ZIP file, found empty entry name"));
763 return false;
764 }
765
766 KArchiveEntry *entry;
767 if (isdir) {
768 QString path = QDir::cleanPath(path: name);
769 const KArchiveEntry *ent = rootDir()->entry(name: path);
770 if (ent && ent->isDirectory()) {
771 // qCDebug(KArchiveLog) << "Directory already exists, NOT going to add it again";
772 entry = nullptr;
773 } else {
774 QDateTime mtime = KArchivePrivate::time_tToDateTime(time_t: pfi.mtime);
775 entry = new KArchiveDirectory(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), QString());
776 // qCDebug(KArchiveLog) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name;
777 }
778 } else {
779 QString symlink;
780 if ((access & QT_STAT_MASK) == QT_STAT_LNK) {
781 symlink = QFile::decodeName(localFileName: pfi.guessed_symlink);
782 }
783 QDateTime mtime = KArchivePrivate::time_tToDateTime(time_t: pfi.mtime);
784 entry =
785 new KZipFileEntry(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), symlink, name, dataoffset, ucsize, cmethod, csize);
786 static_cast<KZipFileEntry *>(entry)->setHeaderStart(localheaderoffset);
787 static_cast<KZipFileEntry *>(entry)->setCRC32(crc32);
788 // qCDebug(KArchiveLog) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name;
789 d->m_fileList.append(t: static_cast<KZipFileEntry *>(entry));
790 }
791
792 if (entry) {
793 if (pos == -1) {
794 // We don't want to fail opening potentially malformed files, so void the return value
795 (void)rootDir()->addEntryV2(entry);
796 } else {
797 // In some tar files we can find dir/./file => call cleanPath
798 QString path = QDir::cleanPath(path: name.left(n: pos));
799 // Ensure container directory exists, create otherwise
800 KArchiveDirectory *tdir = findOrCreate(path);
801 if (tdir) {
802 (void)tdir->addEntryV2(entry);
803 } else {
804 setErrorString(tr(sourceText: "File %1 is in folder %2, but %3 is actually a file.").arg(args&: entryName, args&: path, args&: path));
805 delete entry;
806 return false;
807 }
808 }
809 }
810
811 // calculate offset to next entry
812 offset += 46 + commlen + extralen + namelen;
813 const bool b = dev->seek(pos: offset);
814 if (!b) {
815 setErrorString(tr(sourceText: "Could not seek to next entry"));
816 return false;
817 }
818 } else if (startOfFile) {
819 // The file does not start with any ZIP header (e.g. self-extractable ZIP files)
820 // Therefore we need to find the first PK\003\004 (local header)
821 // qCDebug(KArchiveLog) << "Try to skip start of file";
822 startOfFile = false;
823 bool foundSignature = false;
824
825 QByteArray header;
826 while (seekAnyHeader(dev, header, minSize: 4)) {
827 // We have to detect the magic token for a local header: PK\003\004
828 /*
829 * Note: we do not need to check the other magics, if the ZIP file has no
830 * local header, then it has not any files!
831 */
832 if (header.startsWith(bv: "PK\x03\x04")) {
833 foundSignature = true;
834 d->m_startPos = dev->pos();
835 break;
836 }
837 if (dev->pos() > 4 * 1024 * 1024) {
838 break;
839 }
840 dev->seek(pos: dev->pos() + 2); // Skip found 'PK'
841 }
842
843 if (!foundSignature) {
844 setErrorString(tr(sourceText: "Not a ZIP file, no Local File Header found."));
845 return false;
846 }
847 } else {
848 setErrorString(tr(sourceText: "Invalid ZIP file. Unrecognized header at offset %1").arg(a: dev->pos() - 4));
849 return false;
850 }
851 }
852 // qCDebug(KArchiveLog) << "*** done *** ";
853 return true;
854}
855
856bool KZip::closeArchive()
857{
858 if (!(mode() & QIODevice::WriteOnly)) {
859 // qCDebug(KArchiveLog) << "readonly";
860 return true;
861 }
862
863 // ReadWrite or WriteOnly
864 // write all central dir file entries
865
866 // to be written at the end of the file...
867 char buffer[22]; // first used for 12, then for 22 at the end
868 uLong crc = crc32(crc: 0L, buf: nullptr, len: 0);
869
870 qint64 centraldiroffset = device()->pos();
871 // qCDebug(KArchiveLog) << "closearchive: centraldiroffset: " << centraldiroffset;
872 qint64 atbackup = centraldiroffset;
873 for (KZipFileEntry *entry : d->m_fileList) {
874 // set crc and compressed size in each local file header
875 if (!device()->seek(pos: entry->headerStart() + 14)) {
876 setErrorString(tr(sourceText: "Could not seek to next file header: %1").arg(a: device()->errorString()));
877 return false;
878 }
879 // qCDebug(KArchiveLog) << "closearchive setcrcandcsize: fileName:"
880 // << entry->path()
881 // << "encoding:" << entry->encoding();
882
883 uLong mycrc = entry->crc32();
884 buffer[0] = char(mycrc); // crc checksum, at headerStart+14
885 buffer[1] = char(mycrc >> 8);
886 buffer[2] = char(mycrc >> 16);
887 buffer[3] = char(mycrc >> 24);
888
889 int mysize1 = entry->compressedSize();
890 buffer[4] = char(mysize1); // compressed file size, at headerStart+18
891 buffer[5] = char(mysize1 >> 8);
892 buffer[6] = char(mysize1 >> 16);
893 buffer[7] = char(mysize1 >> 24);
894
895 int myusize = entry->size();
896 buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
897 buffer[9] = char(myusize >> 8);
898 buffer[10] = char(myusize >> 16);
899 buffer[11] = char(myusize >> 24);
900
901 if (device()->write(data: buffer, len: 12) != 12) {
902 setErrorString(tr(sourceText: "Could not write file header: %1").arg(a: device()->errorString()));
903 return false;
904 }
905 }
906 device()->seek(pos: atbackup);
907
908 for (KZipFileEntry *entry : d->m_fileList) {
909 // qCDebug(KArchiveLog) << "fileName:" << entry->path()
910 // << "encoding:" << entry->encoding();
911
912 QByteArray path = QFile::encodeName(fileName: entry->path());
913
914 const int extra_field_len = (d->m_extraField == ModificationTime) ? 9 : 0;
915 const int bufferSize = extra_field_len + path.length() + 46;
916 char *buffer = new char[bufferSize];
917
918 memset(s: buffer, c: 0, n: 46); // zero is a nice default for most header fields
919
920 /* clang-format off */
921 const char head[] = {
922 'P', 'K', 1, 2, // central file header signature
923 0x14, 3, // version made by (3 == UNIX)
924 0x14, 0 // version needed to extract
925 };
926 /* clang-format on */
927
928 // I do not know why memcpy is not working here
929 // memcpy(buffer, head, sizeof(head));
930 memmove(dest: buffer, src: head, n: sizeof(head));
931
932 buffer[10] = char(entry->encoding()); // compression method
933 buffer[11] = char(entry->encoding() >> 8);
934
935 transformToMsDos(dt: entry->date(), buffer: &buffer[12]);
936
937 uLong mycrc = entry->crc32();
938 buffer[16] = char(mycrc); // crc checksum
939 buffer[17] = char(mycrc >> 8);
940 buffer[18] = char(mycrc >> 16);
941 buffer[19] = char(mycrc >> 24);
942
943 int mysize1 = entry->compressedSize();
944 buffer[20] = char(mysize1); // compressed file size
945 buffer[21] = char(mysize1 >> 8);
946 buffer[22] = char(mysize1 >> 16);
947 buffer[23] = char(mysize1 >> 24);
948
949 int mysize = entry->size();
950 buffer[24] = char(mysize); // uncompressed file size
951 buffer[25] = char(mysize >> 8);
952 buffer[26] = char(mysize >> 16);
953 buffer[27] = char(mysize >> 24);
954
955 buffer[28] = char(path.length()); // fileName length
956 buffer[29] = char(path.length() >> 8);
957
958 buffer[30] = char(extra_field_len);
959 buffer[31] = char(extra_field_len >> 8);
960
961 buffer[40] = char(entry->permissions());
962 buffer[41] = char(entry->permissions() >> 8);
963
964 int myhst = entry->headerStart();
965 buffer[42] = char(myhst); // relative offset of local header
966 buffer[43] = char(myhst >> 8);
967 buffer[44] = char(myhst >> 16);
968 buffer[45] = char(myhst >> 24);
969
970 // file name
971 strncpy(dest: buffer + 46, src: path.constData(), n: path.length());
972 // qCDebug(KArchiveLog) << "closearchive length to write: " << bufferSize;
973
974 // extra field
975 if (d->m_extraField == ModificationTime) {
976 char *extfield = buffer + 46 + path.length();
977 // "Extended timestamp" header (0x5455)
978 extfield[0] = 'U';
979 extfield[1] = 'T';
980 extfield[2] = 5; // data size
981 extfield[3] = 0;
982 extfield[4] = 1 | 2 | 4; // specify flags from local field
983 // (unless I misread the spec)
984 // provide only modification time
985 unsigned long time = (unsigned long)entry->date().toSecsSinceEpoch();
986 extfield[5] = char(time);
987 extfield[6] = char(time >> 8);
988 extfield[7] = char(time >> 16);
989 extfield[8] = char(time >> 24);
990 }
991
992 crc = crc32(crc, buf: (Bytef *)buffer, len: bufferSize);
993 bool ok = (device()->write(data: buffer, len: bufferSize) == bufferSize);
994 delete[] buffer;
995 if (!ok) {
996 setErrorString(tr(sourceText: "Could not write file header: %1").arg(a: device()->errorString()));
997 return false;
998 }
999 }
1000 qint64 centraldirendoffset = device()->pos();
1001 // qCDebug(KArchiveLog) << "closearchive: centraldirendoffset: " << centraldirendoffset;
1002 // qCDebug(KArchiveLog) << "closearchive: device()->pos(): " << device()->pos();
1003
1004 // write end of central dir record.
1005 buffer[0] = 'P'; // end of central dir signature
1006 buffer[1] = 'K';
1007 buffer[2] = 5;
1008 buffer[3] = 6;
1009
1010 buffer[4] = 0; // number of this disk
1011 buffer[5] = 0;
1012
1013 buffer[6] = 0; // number of disk with start of central dir
1014 buffer[7] = 0;
1015
1016 int count = d->m_fileList.count();
1017 // qCDebug(KArchiveLog) << "number of files (count): " << count;
1018
1019 buffer[8] = char(count); // total number of entries in central dir of
1020 buffer[9] = char(count >> 8); // this disk
1021
1022 buffer[10] = buffer[8]; // total number of entries in the central dir
1023 buffer[11] = buffer[9];
1024
1025 int cdsize = centraldirendoffset - centraldiroffset;
1026 buffer[12] = char(cdsize); // size of the central dir
1027 buffer[13] = char(cdsize >> 8);
1028 buffer[14] = char(cdsize >> 16);
1029 buffer[15] = char(cdsize >> 24);
1030
1031 // qCDebug(KArchiveLog) << "end : centraldiroffset: " << centraldiroffset;
1032 // qCDebug(KArchiveLog) << "end : centraldirsize: " << cdsize;
1033
1034 buffer[16] = char(centraldiroffset); // central dir offset
1035 buffer[17] = char(centraldiroffset >> 8);
1036 buffer[18] = char(centraldiroffset >> 16);
1037 buffer[19] = char(centraldiroffset >> 24);
1038
1039 buffer[20] = 0; // zipfile comment length
1040 buffer[21] = 0;
1041
1042 if (device()->write(data: buffer, len: 22) != 22) {
1043 setErrorString(tr(sourceText: "Could not write central dir record: %1").arg(a: device()->errorString()));
1044 return false;
1045 }
1046
1047 return true;
1048}
1049
1050bool KZip::doWriteDir(const QString &name,
1051 const QString &user,
1052 const QString &group,
1053 mode_t perm,
1054 const QDateTime &atime,
1055 const QDateTime &mtime,
1056 const QDateTime &ctime)
1057{
1058 // Zip files have no explicit directories, they are implicitly created during extraction time
1059 // when file entries have paths in them.
1060 // However, to support empty directories, we must create a dummy file entry which ends with '/'.
1061 QString dirName = name;
1062 if (!name.endsWith(c: QLatin1Char('/'))) {
1063 dirName = dirName.append(c: QLatin1Char('/'));
1064 }
1065 return writeFile(name: dirName, data: QByteArrayView(), perm, user, group, atime, mtime, ctime);
1066}
1067
1068bool KZip::doPrepareWriting(const QString &name,
1069 const QString &user,
1070 const QString &group,
1071 qint64 /*size*/,
1072 mode_t perm,
1073 const QDateTime &accessTime,
1074 const QDateTime &modificationTime,
1075 const QDateTime &creationTime)
1076{
1077 // qCDebug(KArchiveLog);
1078 if (!isOpen()) {
1079 setErrorString(tr(sourceText: "Application error: ZIP file must be open before being written into"));
1080 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
1081 return false;
1082 }
1083
1084 if (!(mode() & QIODevice::WriteOnly)) { // accept WriteOnly and ReadWrite
1085 setErrorString(tr(sourceText: "Application error: attempted to write into non-writable ZIP file"));
1086 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
1087 return false;
1088 }
1089
1090 if (!device()) {
1091 setErrorString(tr(sourceText: "Cannot create a device. Disk full?"));
1092 return false;
1093 }
1094
1095 // set right offset in zip.
1096 if (!device()->seek(pos: d->m_offset)) {
1097 setErrorString(tr(sourceText: "Cannot seek in ZIP file. Disk full?"));
1098 return false;
1099 }
1100
1101 uint atime = accessTime.toSecsSinceEpoch();
1102 uint mtime = modificationTime.toSecsSinceEpoch();
1103 uint ctime = creationTime.toSecsSinceEpoch();
1104
1105 // Find or create parent dir
1106 KArchiveDirectory *parentDir = rootDir();
1107 QString fileName(name);
1108 int i = name.lastIndexOf(c: QLatin1Char('/'));
1109 if (i != -1) {
1110 QString dir = name.left(n: i);
1111 fileName = name.mid(position: i + 1);
1112 // qCDebug(KArchiveLog) << "ensuring" << dir << "exists. fileName=" << fileName;
1113 parentDir = findOrCreate(path: dir);
1114 }
1115
1116 // delete entries in the filelist with the same fileName as the one we want
1117 // to save, so that we don't have duplicate file entries when viewing the zip
1118 // with konqi...
1119 // CAUTION: the old file itself is still in the zip and won't be removed !!!
1120 // qCDebug(KArchiveLog) << "fileName to write: " << name;
1121 for (auto it = d->m_fileList.begin(); it != d->m_fileList.end();) {
1122 // qCDebug(KArchiveLog) << "prepfileName: " << entry->path();
1123 if (name == (*it)->path()) {
1124 // also remove from the parentDir
1125 if (!parentDir->removeEntryV2(*it)) {
1126 return false;
1127 }
1128 // qCDebug(KArchiveLog) << "removing following entry: " << entry->path();
1129 delete *it;
1130 it = d->m_fileList.erase(pos: it);
1131 } else {
1132 it++;
1133 }
1134 }
1135
1136 // construct a KZipFileEntry and add it to list
1137 KZipFileEntry *e = new KZipFileEntry(this,
1138 fileName,
1139 perm,
1140 modificationTime,
1141 user,
1142 group,
1143 QString(),
1144 name,
1145 device()->pos() + 30 + name.length(), // start
1146 0 /*size unknown yet*/,
1147 d->m_compression,
1148 0 /*csize unknown yet*/);
1149 e->setHeaderStart(device()->pos());
1150 // qCDebug(KArchiveLog) << "wrote file start: " << e->position() << " name: " << name;
1151 if (!parentDir->addEntryV2(e)) {
1152 return false;
1153 }
1154
1155 d->m_currentFile = e;
1156 d->m_fileList.append(t: e);
1157
1158 int extra_field_len = 0;
1159 if (d->m_extraField == ModificationTime) {
1160 extra_field_len = 17; // value also used in finishWriting()
1161 }
1162
1163 // write out zip header
1164 QByteArray encodedName = QFile::encodeName(fileName: name);
1165 int bufferSize = extra_field_len + encodedName.length() + 30;
1166 // qCDebug(KArchiveLog) << "bufferSize=" << bufferSize;
1167 char *buffer = new char[bufferSize];
1168
1169 buffer[0] = 'P'; // local file header signature
1170 buffer[1] = 'K';
1171 buffer[2] = 3;
1172 buffer[3] = 4;
1173
1174 buffer[4] = 0x14; // version needed to extract
1175 buffer[5] = 0;
1176
1177 buffer[6] = 0; // general purpose bit flag
1178 buffer[7] = 0;
1179
1180 buffer[8] = char(e->encoding()); // compression method
1181 buffer[9] = char(e->encoding() >> 8);
1182
1183 transformToMsDos(dt: e->date(), buffer: &buffer[10]);
1184
1185 buffer[14] = 'C'; // dummy crc
1186 buffer[15] = 'R';
1187 buffer[16] = 'C';
1188 buffer[17] = 'q';
1189
1190 buffer[18] = 'C'; // compressed file size
1191 buffer[19] = 'S';
1192 buffer[20] = 'I';
1193 buffer[21] = 'Z';
1194
1195 buffer[22] = 'U'; // uncompressed file size
1196 buffer[23] = 'S';
1197 buffer[24] = 'I';
1198 buffer[25] = 'Z';
1199
1200 buffer[26] = (uchar)(encodedName.length()); // fileName length
1201 buffer[27] = (uchar)(encodedName.length() >> 8);
1202
1203 buffer[28] = (uchar)(extra_field_len); // extra field length
1204 buffer[29] = (uchar)(extra_field_len >> 8);
1205
1206 // file name
1207 strncpy(dest: buffer + 30, src: encodedName.constData(), n: encodedName.length());
1208
1209 // extra field
1210 if (d->m_extraField == ModificationTime) {
1211 char *extfield = buffer + 30 + encodedName.length();
1212 // "Extended timestamp" header (0x5455)
1213 extfield[0] = 'U';
1214 extfield[1] = 'T';
1215 extfield[2] = 13; // data size
1216 extfield[3] = 0;
1217 extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime
1218
1219 extfield[5] = char(mtime);
1220 extfield[6] = char(mtime >> 8);
1221 extfield[7] = char(mtime >> 16);
1222 extfield[8] = char(mtime >> 24);
1223
1224 extfield[9] = char(atime);
1225 extfield[10] = char(atime >> 8);
1226 extfield[11] = char(atime >> 16);
1227 extfield[12] = char(atime >> 24);
1228
1229 extfield[13] = char(ctime);
1230 extfield[14] = char(ctime >> 8);
1231 extfield[15] = char(ctime >> 16);
1232 extfield[16] = char(ctime >> 24);
1233 }
1234
1235 // Write header
1236 bool b = (device()->write(data: buffer, len: bufferSize) == bufferSize);
1237 d->m_crc = 0;
1238 delete[] buffer;
1239
1240 if (!b) {
1241 setErrorString(tr(sourceText: "Could not write to the archive. Disk full?"));
1242 return false;
1243 }
1244
1245 // Prepare device for writing the data
1246 // Either device() if no compression, or a KCompressionDevice to compress
1247 if (d->m_compression == 0) {
1248 d->m_currentDev = device();
1249 return true;
1250 }
1251
1252 auto compressionDevice = new KCompressionDevice(device(), false, KCompressionDevice::GZip);
1253 d->m_currentDev = compressionDevice;
1254 compressionDevice->setSkipHeaders(); // Just zlib, not gzip
1255
1256 b = d->m_currentDev->open(mode: QIODevice::WriteOnly);
1257 Q_ASSERT(b);
1258
1259 if (!b) {
1260 setErrorString(tr(sourceText: "Could not open compression device: %1").arg(a: d->m_currentDev->errorString()));
1261 }
1262
1263 return b;
1264}
1265
1266bool KZip::doFinishWriting(qint64 size)
1267{
1268 if (d->m_currentFile->encoding() == 8) {
1269 // Finish
1270 (void)d->m_currentDev->write(data: nullptr, len: 0);
1271 delete d->m_currentDev;
1272 }
1273 // If 0, d->m_currentDev was device() - don't delete ;)
1274 d->m_currentDev = nullptr;
1275
1276 Q_ASSERT(d->m_currentFile);
1277 // qCDebug(KArchiveLog) << "fileName: " << d->m_currentFile->path();
1278 // qCDebug(KArchiveLog) << "getpos (at): " << device()->pos();
1279 d->m_currentFile->setSize(size);
1280 int extra_field_len = 0;
1281 if (d->m_extraField == ModificationTime) {
1282 extra_field_len = 17; // value also used in finishWriting()
1283 }
1284
1285 const QByteArray encodedName = QFile::encodeName(fileName: d->m_currentFile->path());
1286 int csize = device()->pos() - d->m_currentFile->headerStart() - 30 - encodedName.length() - extra_field_len;
1287 d->m_currentFile->setCompressedSize(csize);
1288 // qCDebug(KArchiveLog) << "usize: " << d->m_currentFile->size();
1289 // qCDebug(KArchiveLog) << "csize: " << d->m_currentFile->compressedSize();
1290 // qCDebug(KArchiveLog) << "headerstart: " << d->m_currentFile->headerStart();
1291
1292 // qCDebug(KArchiveLog) << "crc: " << d->m_crc;
1293 d->m_currentFile->setCRC32(d->m_crc);
1294
1295 d->m_currentFile = nullptr;
1296
1297 // update saved offset for appending new files
1298 d->m_offset = device()->pos();
1299 return true;
1300}
1301
1302bool KZip::doWriteSymLink(const QString &name,
1303 const QString &target,
1304 const QString &user,
1305 const QString &group,
1306 mode_t perm,
1307 const QDateTime &atime,
1308 const QDateTime &mtime,
1309 const QDateTime &ctime)
1310{
1311 // reassure that symlink flag is set, otherwise strange things happen on
1312 // extraction
1313 perm |= QT_STAT_LNK;
1314 Compression c = compression();
1315 setCompression(NoCompression); // link targets are never compressed
1316
1317 if (!doPrepareWriting(name, user, group, 0, perm, accessTime: atime, modificationTime: mtime, creationTime: ctime)) {
1318 setCompression(c);
1319 return false;
1320 }
1321
1322 QByteArray symlink_target = QFile::encodeName(fileName: target);
1323 if (!writeData(data: symlink_target.constData(), size: symlink_target.length())) {
1324 setCompression(c);
1325 return false;
1326 }
1327
1328 if (!finishWriting(size: symlink_target.length())) {
1329 setCompression(c);
1330 return false;
1331 }
1332
1333 setCompression(c);
1334 return true;
1335}
1336
1337void KZip::virtual_hook(int id, void *data)
1338{
1339 KArchive::virtual_hook(id, data);
1340}
1341
1342bool KZip::doWriteData(const char *data, qint64 size)
1343{
1344 Q_ASSERT(d->m_currentFile);
1345 Q_ASSERT(d->m_currentDev);
1346 if (!d->m_currentFile || !d->m_currentDev) {
1347 setErrorString(tr(sourceText: "No file or device"));
1348 return false;
1349 }
1350
1351 // crc to be calculated over uncompressed stuff...
1352 // and they didn't mention it in their docs...
1353 d->m_crc = crc32(crc: d->m_crc, buf: (const Bytef *)data, len: size);
1354
1355 qint64 written = d->m_currentDev->write(data, len: size);
1356 // qCDebug(KArchiveLog) << "wrote" << size << "bytes.";
1357 const bool ok = written == size;
1358
1359 if (!ok) {
1360 setErrorString(tr(sourceText: "Error writing data: %1").arg(a: d->m_currentDev->errorString()));
1361 }
1362
1363 return ok;
1364}
1365
1366void KZip::setCompression(Compression c)
1367{
1368 d->m_compression = (c == NoCompression) ? 0 : 8;
1369}
1370
1371KZip::Compression KZip::compression() const
1372{
1373 return (d->m_compression == 8) ? DeflateCompression : NoCompression;
1374}
1375
1376void KZip::setExtraField(ExtraField ef)
1377{
1378 d->m_extraField = ef;
1379}
1380
1381KZip::ExtraField KZip::extraField() const
1382{
1383 return d->m_extraField;
1384}
1385
1386////////////////////////////////////////////////////////////////////////
1387////////////////////// KZipFileEntry////////////////////////////////////
1388////////////////////////////////////////////////////////////////////////
1389class Q_DECL_HIDDEN KZipFileEntry::KZipFileEntryPrivate
1390{
1391public:
1392 KZipFileEntryPrivate()
1393 : crc(0)
1394 , compressedSize(0)
1395 , headerStart(0)
1396 , encoding(0)
1397 {
1398 }
1399 unsigned long crc;
1400 qint64 compressedSize;
1401 qint64 headerStart;
1402 int encoding;
1403 QString path;
1404};
1405
1406KZipFileEntry::KZipFileEntry(KZip *zip,
1407 const QString &name,
1408 int access,
1409 const QDateTime &date,
1410 const QString &user,
1411 const QString &group,
1412 const QString &symlink,
1413 const QString &path,
1414 qint64 start,
1415 qint64 uncompressedSize,
1416 int encoding,
1417 qint64 compressedSize)
1418 : KArchiveFile(zip, name, access, date, user, group, symlink, start, uncompressedSize)
1419 , d(new KZipFileEntryPrivate)
1420{
1421 d->path = path;
1422 d->encoding = encoding;
1423 d->compressedSize = compressedSize;
1424}
1425
1426KZipFileEntry::~KZipFileEntry()
1427{
1428 delete d;
1429}
1430
1431int KZipFileEntry::encoding() const
1432{
1433 return d->encoding;
1434}
1435
1436qint64 KZipFileEntry::compressedSize() const
1437{
1438 return d->compressedSize;
1439}
1440
1441void KZipFileEntry::setCompressedSize(qint64 compressedSize)
1442{
1443 d->compressedSize = compressedSize;
1444}
1445
1446void KZipFileEntry::setHeaderStart(qint64 headerstart)
1447{
1448 d->headerStart = headerstart;
1449}
1450
1451qint64 KZipFileEntry::headerStart() const
1452{
1453 return d->headerStart;
1454}
1455
1456unsigned long KZipFileEntry::crc32() const
1457{
1458 return d->crc;
1459}
1460
1461void KZipFileEntry::setCRC32(unsigned long crc32)
1462{
1463 d->crc = crc32;
1464}
1465
1466const QString &KZipFileEntry::path() const
1467{
1468 return d->path;
1469}
1470
1471QByteArray KZipFileEntry::data() const
1472{
1473 QIODevice *dev = createDevice();
1474 QByteArray arr;
1475 if (dev) {
1476 arr = dev->readAll();
1477 delete dev;
1478 }
1479 return arr;
1480}
1481
1482QIODevice *KZipFileEntry::createDevice() const
1483{
1484 // qCDebug(KArchiveLog) << "creating iodevice limited to pos=" << position() << ", csize=" << compressedSize();
1485 // Limit the reading to the appropriate part of the underlying device (e.g. file)
1486 std::unique_ptr limitedDev = std::make_unique<KLimitedIODevice>(args: archive()->device(), args: position(), args: compressedSize());
1487 if (encoding() == 0 || compressedSize() == 0) { // no compression (or even no data)
1488 return limitedDev.release();
1489 }
1490
1491 if (encoding() == 8) {
1492 // On top of that, create a device that uncompresses the zlib data
1493 KCompressionDevice *filterDev = new KCompressionDevice(std::move(limitedDev), KCompressionDevice::GZip, size());
1494
1495 if (!filterDev) {
1496 return nullptr; // ouch
1497 }
1498 filterDev->setSkipHeaders(); // Just zlib, not gzip
1499 bool b = filterDev->open(mode: QIODevice::ReadOnly);
1500 Q_UNUSED(b);
1501 Q_ASSERT(b);
1502 return filterDev;
1503 }
1504
1505 qCCritical(KArchiveLog) << "This zip file contains files compressed with method" << encoding() << ", this method is currently not supported by KZip,"
1506 << "please use a command-line tool to handle this file.";
1507 return nullptr;
1508}
1509

source code of karchive/src/kzip.cpp