KArchive

kzip.cpp
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(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 + 8);
276 }
277 if (size >= 24) {
278 pfi.localheaderoffset = parseUi64(buffer + 16);
279 }
280 break;
281 case 0x5455: // extended timestamp
282 if (!parseExtTimestamp(buffer, fieldsize, true, pfi)) {
283 return false;
284 }
285 break;
286 case 0x5855: // old Info-ZIP unix extra field
287 if (!parseInfoZipUnixOld(buffer, fieldsize, 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(1024);
321 int n = dev->peek(header.data(), header.size());
322
323 while (n >= minSize) {
324 if (auto i = QByteArrayView(header.data(), n).indexOf("PK"); i >= 0) {
325 dev->seek(dev->pos() + i);
326 if ((i + minSize) < n) {
327 header.remove(0, i);
328 header.resize(minSize);
329 return true;
330 } else {
331 n = dev->peek(header.data(), minSize);
332 header.resize(n);
333 return n >= minSize;
334 }
335 }
336 dev->seek(dev->pos() + (n - 1));
337 n = dev->peek(header.data(), header.size());
338 }
339 header.resize(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 auto oldPos = dev->pos();
359
360 while (seekAnyHeader(dev, header, descriptorSize)) {
361 // qCDebug(KArchiveLog) << "Possible data descriptor header at" << dev->pos() << header;
362 if (header.startsWith("PK\x07\x08")) {
363 qint64 compressedSize = isZip64 ? parseUi64(header.constData() + 8) //
364 : parseUi32(header.constData() + 8);
365 if (oldPos + compressedSize == dev->pos()) {
366 dev->seek(dev->pos() + descriptorSize);
367 return true;
368 }
369 dev->seek(dev->pos() + 4); // Skip found 'PK\7\8'
370 } else {
371 dev->seek(dev->pos() + 2); // Skip found 'PK'
372 }
373 }
374 return false;
375}
376
377/**
378 * Advance the current file position to the next header
379 * @param dev device that is read from
380 * @return true if a local or central header token could be reached, false on error
381 */
382static bool seekToNextHeaderToken(QIODevice *dev)
383{
384 QByteArray header;
385
386 while (seekAnyHeader(dev, header, 4)) {
387 // qCDebug(KArchiveLog) << "Possible header at" << dev->pos() << header;
388 // PK34 for the next local header in case there is no data descriptor
389 // PK12 for the central header in case there is no data descriptor
390 if (header.startsWith("PK\x03\x04") || header.startsWith("PK\x01\x02")) {
391 return true;
392 } else {
393 dev->seek(dev->pos() + 2); // Skip found 'PK'
394 }
395 }
396 return false;
397}
398
399////////////////////////////////////////////////////////////////////////
400/////////////////////////// KZip ///////////////////////////////////////
401////////////////////////////////////////////////////////////////////////
402
403class Q_DECL_HIDDEN KZip::KZipPrivate
404{
405public:
406 KZipPrivate()
407 : m_crc(0)
408 , m_currentFile(nullptr)
409 , m_currentDev(nullptr)
410 , m_compression(8)
411 , m_extraField(KZip::NoExtraField)
412 , m_offset(0)
413 {
414 }
415
416 unsigned long m_crc; // checksum
417 KZipFileEntry *m_currentFile; // file currently being written
418 QIODevice *m_currentDev; // filterdev used to write to the above file
419 QList<KZipFileEntry *> m_fileList; // flat list of all files, for the index (saves a recursive method ;)
420 int m_compression;
421 KZip::ExtraField m_extraField;
422 // m_offset holds the offset of the place in the zip,
423 // where new data can be appended. after openarchive it points to 0, when in
424 // writeonly mode, or it points to the beginning of the central directory.
425 // each call to writefile updates this value.
426 quint64 m_offset;
427 // Position of the first Local File Header
428 quint64 m_startPos = 0;
429};
430
433 , d(new KZipPrivate)
434{
435}
436
438 : KArchive(dev)
439 , d(new KZipPrivate)
440{
441}
442
444{
445 // qCDebug(KArchiveLog) << this;
446 if (isOpen()) {
447 close();
448 }
449 delete d;
450}
451
453{
454 // qCDebug(KArchiveLog);
455 d->m_fileList.clear();
456
457 if (mode == QIODevice::WriteOnly) {
458 return true;
459 }
460
461 char buffer[47];
462
463 // Check that it's a valid ZIP file
464 // KArchive::open() opened the underlying device already.
465
466 // contains information gathered from the local file headers
468
469 QIODevice *dev = device();
470
471 // We set a bool for knowing if we are allowed to skip the start of the file
472 bool startOfFile = true;
473
474 qint64 expectedStartPos = 0;
475
476 for (;;) { // repeat until 'end of entries' signature is reached
477 // qCDebug(KArchiveLog) << "loop starts";
478 // qCDebug(KArchiveLog) << "dev->pos() now : " << dev->pos();
479 int n = dev->read(buffer, 4);
480
481 if (n < 4) {
482 setErrorString(tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(1));
483 return false;
484 }
485
486 if (!memcmp(buffer, "PK\6\6", 4)) { // 'Zip64 end of central directory record'
487 startOfFile = false;
488 break;
489 }
490
491 if (!memcmp(buffer, "PK\5\6", 4)) { // 'end of entries'
492 // qCDebug(KArchiveLog) << "PK56 found end of archive at offset" << dev->pos();
493 startOfFile = false;
494 break;
495 }
496
497 if (!memcmp(buffer, "PK\3\4", 4)) { // local file header
498 // qCDebug(KArchiveLog) << "PK34 found local file header at offset" << dev->pos();
499 startOfFile = false;
500
501 ParseFileInfo pfi;
502 pfi.localheaderoffset = dev->pos() - 4;
503
504 // read static header stuff
505 n = dev->read(buffer, 26);
506 if (n < 26) {
507 setErrorString(tr("Invalid ZIP file. Unexpected end of file. (Error code: %1)").arg(4));
508 return false;
509 }
510 int neededVersion = (uchar)buffer[0] | (uchar)buffer[1] << 8;
511 bool isZip64 = neededVersion >= 45;
512
513 int gpf = (uchar)buffer[2]; // "general purpose flag" not "general protection fault" ;-)
514 int compression_mode = (uchar)buffer[4] | (uchar)buffer[5] << 8;
515 uint mtime = transformFromMsDos(buffer + 6);
516
517 const qint64 compr_size = parseUi32(buffer + 14);
518 const qint64 uncomp_size = parseUi32(buffer + 18);
519 const int namelen = uint(uchar(buffer[22])) | uint(uchar(buffer[23])) << 8;
520 const int extralen = uint(uchar(buffer[24])) | uint(uchar(buffer[25])) << 8;
521
522 /*
523 qCDebug(KArchiveLog) << "general purpose bit flag: " << gpf;
524 qCDebug(KArchiveLog) << "compressed size: " << compr_size;
525 qCDebug(KArchiveLog) << "uncompressed size: " << uncomp_size;
526 qCDebug(KArchiveLog) << "namelen: " << namelen;
527 qCDebug(KArchiveLog) << "extralen: " << extralen;
528 qCDebug(KArchiveLog) << "archive size: " << dev->size();
529 */
530
531 // read fileName
532 if (namelen <= 0) {
533 setErrorString(tr("Invalid ZIP file. Negative name length"));
534 return false;
535 }
536 QByteArray fileName = dev->read(namelen);
537 if (fileName.size() < namelen) {
538 setErrorString(tr("Invalid ZIP file. Name not completely read (#2)"));
539 return false;
540 }
541
542 pfi.mtime = mtime;
543 pfi.dataoffset = 30 + extralen + namelen;
544
545 // read and parse the beginning of the extra field,
546 // skip rest of extra field in case it is too long
547 unsigned int extraFieldEnd = dev->pos() + extralen;
548 int handledextralen = qMin(extralen, (int)sizeof buffer);
549
550 // if (handledextralen)
551 // qCDebug(KArchiveLog) << "handledextralen: " << handledextralen;
552
553 n = dev->read(buffer, handledextralen);
554 // no error msg necessary as we deliberately truncate the extra field
555 if (!parseExtraField(buffer, n, pfi)) {
556 setErrorString(tr("Invalid ZIP File. Broken ExtraField."));
557 return false;
558 }
559
560 // jump to end of extra field
561 dev->seek(extraFieldEnd);
562
563 // we have to take care of the 'general purpose bit flag'.
564 // if bit 3 is set, the header doesn't contain the length of
565 // the file and we look for the signature 'PK\7\8'.
566 if (gpf & 8) {
567 // here we have to read through the compressed data to find
568 // the next PKxx
569 if (!seekPostDataDescriptor(dev, isZip64)) {
570 setErrorString(tr("Could not seek to next header token"));
571 return false;
572 }
573 } else {
574 // here we skip the compressed data and jump to the next header
575 // qCDebug(KArchiveLog) << "general purpose bit flag indicates, that local file header contains valid size";
576 bool foundSignature = false;
577 // check if this could be a symbolic link
578 if (compression_mode == NoCompression //
579 && uncomp_size <= max_path_len //
580 && uncomp_size > 0) {
581 // read content and store it
582 // If it's not a symlink, then we'll just discard the data for now.
583 pfi.guessed_symlink = dev->read(uncomp_size);
584 if (pfi.guessed_symlink.size() < uncomp_size) {
585 setErrorString(tr("Invalid ZIP file. Unexpected end of file. (#5)"));
586 return false;
587 }
588 } else {
589 if (compr_size > dev->size()) {
590 // here we cannot trust the compressed size, so scan through the compressed
591 // data to find the next header
592 if (!seekToNextHeaderToken(dev)) {
593 setErrorString(tr("Could not seek to next header token"));
594 return false;
595 }
596 foundSignature = true;
597 } else {
598 // qCDebug(KArchiveLog) << "before interesting dev->pos(): " << dev->pos();
599 const bool success = dev->seek(dev->pos() + compr_size);
600 if (!success) {
601 setErrorString(tr("Could not seek to file compressed size"));
602 return false;
603 }
604 /* qCDebug(KArchiveLog) << "after interesting dev->pos(): " << dev->pos();
605 if (success)
606 qCDebug(KArchiveLog) << "dev->at was successful... ";
607 else
608 qCDebug(KArchiveLog) << "dev->at failed... ";*/
609 }
610 }
611 // test for optional data descriptor
612 if (!foundSignature) {
613 // qCDebug(KArchiveLog) << "Testing for optional data descriptor";
614 // read static data descriptor
615 n = dev->peek(buffer, 4);
616 if (n < 4) {
617 setErrorString(tr("Invalid ZIP file. Unexpected end of file. (#1)"));
618 return false;
619 }
620
621 QByteArrayView currentHead(buffer, 4);
622 // qCDebug(KArchiveLog) << "Testing for optional data descriptor @ " << dev->pos() << currentHead;
623 if (currentHead.startsWith("PK\x07\x08")) {
624 dev->seek(dev->pos() + 16); // skip rest of the 'data_descriptor'
625 } else if (!(currentHead.startsWith("PK\x03\x04") || currentHead.startsWith("PK\x01\x02"))) {
626 // assume data descriptor without signature
627 dev->seek(dev->pos() + 12); // skip rest of the 'data_descriptor'
628 }
629 }
630
631 // not needed any more
632 /* // here we calculate the length of the file in the zip
633 // with headers and jump to the next header.
634 uint skip = compr_size + namelen + extralen;
635 offset += 30 + skip;*/
636 }
637 pfi_map.insert(fileName, pfi);
638 } else if (!memcmp(buffer, "PK\1\2", 4)) { // central block
639 // qCDebug(KArchiveLog) << "PK12 found central block at offset" << dev->pos();
640 startOfFile = false;
641
642 // so we reached the central header at the end of the zip file
643 // here we get all interesting data out of the central header
644 // of a file
645 auto offset = dev->pos() - 4;
646
647 // set offset for appending new files
648 if (d->m_offset == 0) {
649 d->m_offset = offset;
650 }
651
652 n = dev->read(buffer + 4, 42);
653 if (n < 42) {
654 setErrorString(tr("Invalid ZIP file, central entry too short (not long enough for valid entry)"));
655 return false;
656 }
657
658 // length of extra attributes
659 int extralen = (uchar)buffer[31] << 8 | (uchar)buffer[30];
660 // length of comment for this file
661 int commlen = (uchar)buffer[33] << 8 | (uchar)buffer[32];
662 // compression method of this file
663 int cmethod = (uchar)buffer[11] << 8 | (uchar)buffer[10];
664 // int gpf = (uchar)buffer[9] << 8 | (uchar)buffer[10];
665 // qCDebug(KArchiveLog) << "general purpose flag=" << gpf;
666 // length of the fileName (well, pathname indeed)
667 int namelen = (uchar)buffer[29] << 8 | (uchar)buffer[28];
668 if (namelen <= 0) {
669 setErrorString(tr("Invalid ZIP file, file path name length is zero"));
670 return false;
671 }
672 QByteArray varData = dev->read(namelen + extralen);
673
674 ParseFileInfo extrafi;
675 if (extralen) {
676 parseExtraField(varData.constData() + namelen, extralen, extrafi);
677 }
678
679 QByteArray bufferName(varData.constData(), namelen);
680 if (bufferName.size() < namelen) {
681 // qCWarning(KArchiveLog) << "Invalid ZIP file. Name not completely read";
682 }
683
684 ParseFileInfo pfi = pfi_map.value(bufferName, ParseFileInfo());
685
686 QString name(QFile::decodeName(bufferName));
687
688 // qCDebug(KArchiveLog) << "name: " << name;
689
690 // qCDebug(KArchiveLog) << "cmethod: " << cmethod;
691 // qCDebug(KArchiveLog) << "extralen: " << extralen;
692
693 // crc32 of the file
694 uint crc32 = parseUi32(buffer + 16);
695
696 // uncompressed file size
697 quint64 ucsize = parseUi32(buffer + 24);
698 if (ucsize == 0xFFFFFFFF) {
699 ucsize = extrafi.uncompressedSize;
700 }
701 // compressed file size
702 quint64 csize = parseUi32(buffer + 20);
703 if (csize == 0xFFFFFFFF) {
704 csize = extrafi.compressedSize;
705 }
706
707 // offset of local header
708 quint64 localheaderoffset = parseUi32(buffer + 42);
709 if (localheaderoffset == 0xFFFFFFFF) {
710 localheaderoffset = extrafi.localheaderoffset;
711 }
712
713 // qCDebug(KArchiveLog) << "localheader dataoffset: " << pfi.dataoffset;
714
715 // offset, where the real data for uncompression starts
716 qint64 dataoffset = localheaderoffset + pfi.dataoffset;
717 if (pfi.localheaderoffset != expectedStartPos + localheaderoffset) {
718 if (pfi.localheaderoffset == d->m_startPos + localheaderoffset) {
719 qCDebug(KArchiveLog) << "warning:" << d->m_startPos << "extra bytes at beginning of zipfile";
720 expectedStartPos = d->m_startPos;
721 } else {
722 setErrorString(tr("Invalid ZIP file, inconsistent Local Header Offset in Central Directory at %1").arg(offset));
723 return false;
724 }
725 dataoffset = localheaderoffset + pfi.dataoffset + d->m_startPos;
726 }
727
728 // qCDebug(KArchiveLog) << "csize: " << csize;
729
730 int os_madeby = (uchar)buffer[5];
731 bool isdir = false;
732 int access = 0100644;
733
734 if (os_madeby == 3) { // good ole unix
735 access = (uchar)buffer[40] | (uchar)buffer[41] << 8;
736 }
737
738 QString entryName;
739
740 if (name.endsWith(QLatin1Char('/'))) { // Entries with a trailing slash are directories
741 isdir = true;
742 name = name.left(name.length() - 1);
743 if (os_madeby != 3) {
744 access = S_IFDIR | 0755;
745 } else {
746 access |= S_IFDIR | 0700;
747 }
748 }
749
750 int pos = name.lastIndexOf(QLatin1Char('/'));
751 if (pos == -1) {
752 entryName = name;
753 } else {
754 entryName = name.mid(pos + 1);
755 }
756 if (entryName.isEmpty()) {
757 setErrorString(tr("Invalid ZIP file, found empty entry name"));
758 return false;
759 }
760
761 KArchiveEntry *entry;
762 if (isdir) {
763 QString path = QDir::cleanPath(name);
764 const KArchiveEntry *ent = rootDir()->entry(path);
765 if (ent && ent->isDirectory()) {
766 // qCDebug(KArchiveLog) << "Directory already exists, NOT going to add it again";
767 entry = nullptr;
768 } else {
769 QDateTime mtime = KArchivePrivate::time_tToDateTime(pfi.mtime);
770 entry = new KArchiveDirectory(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), QString());
771 // qCDebug(KArchiveLog) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name;
772 }
773 } else {
774 QString symlink;
775 if ((access & QT_STAT_MASK) == QT_STAT_LNK) {
776 symlink = QFile::decodeName(pfi.guessed_symlink);
777 }
778 QDateTime mtime = KArchivePrivate::time_tToDateTime(pfi.mtime);
779 entry =
780 new KZipFileEntry(this, entryName, access, mtime, rootDir()->user(), rootDir()->group(), symlink, name, dataoffset, ucsize, cmethod, csize);
781 static_cast<KZipFileEntry *>(entry)->setHeaderStart(localheaderoffset);
782 static_cast<KZipFileEntry *>(entry)->setCRC32(crc32);
783 // qCDebug(KArchiveLog) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name;
784 d->m_fileList.append(static_cast<KZipFileEntry *>(entry));
785 }
786
787 if (entry) {
788 if (pos == -1) {
789 // We don't want to fail opening potentially malformed files, so void the return value
790 (void)rootDir()->addEntryV2(entry);
791 } else {
792 // In some tar files we can find dir/./file => call cleanPath
793 QString path = QDir::cleanPath(name.left(pos));
794 // Ensure container directory exists, create otherwise
795 KArchiveDirectory *tdir = findOrCreate(path);
796 if (tdir) {
797 (void)tdir->addEntryV2(entry);
798 } else {
799 setErrorString(tr("File %1 is in folder %2, but %3 is actually a file.").arg(entryName, path, path));
800 delete entry;
801 return false;
802 }
803 }
804 }
805
806 // calculate offset to next entry
807 offset += 46 + commlen + extralen + namelen;
808 const bool b = dev->seek(offset);
809 if (!b) {
810 setErrorString(tr("Could not seek to next entry"));
811 return false;
812 }
813 } else if (startOfFile) {
814 // The file does not start with any ZIP header (e.g. self-extractable ZIP files)
815 // Therefore we need to find the first PK\003\004 (local header)
816 // qCDebug(KArchiveLog) << "Try to skip start of file";
817 startOfFile = false;
818 bool foundSignature = false;
819
820 QByteArray header;
821 while (seekAnyHeader(dev, header, 4)) {
822 // We have to detect the magic token for a local header: PK\003\004
823 /*
824 * Note: we do not need to check the other magics, if the ZIP file has no
825 * local header, then it has not any files!
826 */
827 if (header.startsWith("PK\x03\x04")) {
828 foundSignature = true;
829 d->m_startPos = dev->pos();
830 break;
831 }
832 if (dev->pos() > 4 * 1024 * 1024) {
833 break;
834 }
835 dev->seek(dev->pos() + 2); // Skip found 'PK'
836 }
837
838 if (!foundSignature) {
839 setErrorString(tr("Not a ZIP file, no Local File Header found."));
840 return false;
841 }
842 } else {
843 setErrorString(tr("Invalid ZIP file. Unrecognized header at offset %1").arg(dev->pos() - 4));
844 return false;
845 }
846 }
847 // qCDebug(KArchiveLog) << "*** done *** ";
848 return true;
849}
850
852{
853 if (!(mode() & QIODevice::WriteOnly)) {
854 // qCDebug(KArchiveLog) << "readonly";
855 return true;
856 }
857
858 // ReadWrite or WriteOnly
859 // write all central dir file entries
860
861 // to be written at the end of the file...
862 char buffer[22]; // first used for 12, then for 22 at the end
863 uLong crc = crc32(0L, nullptr, 0);
864
865 qint64 centraldiroffset = device()->pos();
866 // qCDebug(KArchiveLog) << "closearchive: centraldiroffset: " << centraldiroffset;
867 qint64 atbackup = centraldiroffset;
868 for (KZipFileEntry *entry : d->m_fileList) {
869 // set crc and compressed size in each local file header
870 if (!device()->seek(entry->headerStart() + 14)) {
871 setErrorString(tr("Could not seek to next file header: %1").arg(device()->errorString()));
872 return false;
873 }
874 // qCDebug(KArchiveLog) << "closearchive setcrcandcsize: fileName:"
875 // << entry->path()
876 // << "encoding:" << entry->encoding();
877
878 uLong mycrc = entry->crc32();
879 buffer[0] = char(mycrc); // crc checksum, at headerStart+14
880 buffer[1] = char(mycrc >> 8);
881 buffer[2] = char(mycrc >> 16);
882 buffer[3] = char(mycrc >> 24);
883
884 int mysize1 = entry->compressedSize();
885 buffer[4] = char(mysize1); // compressed file size, at headerStart+18
886 buffer[5] = char(mysize1 >> 8);
887 buffer[6] = char(mysize1 >> 16);
888 buffer[7] = char(mysize1 >> 24);
889
890 int myusize = entry->size();
891 buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
892 buffer[9] = char(myusize >> 8);
893 buffer[10] = char(myusize >> 16);
894 buffer[11] = char(myusize >> 24);
895
896 if (device()->write(buffer, 12) != 12) {
897 setErrorString(tr("Could not write file header: %1").arg(device()->errorString()));
898 return false;
899 }
900 }
901 device()->seek(atbackup);
902
903 for (KZipFileEntry *entry : d->m_fileList) {
904 // qCDebug(KArchiveLog) << "fileName:" << entry->path()
905 // << "encoding:" << entry->encoding();
906
907 QByteArray path = QFile::encodeName(entry->path());
908
909 const int extra_field_len = (d->m_extraField == ModificationTime) ? 9 : 0;
910 const int bufferSize = extra_field_len + path.length() + 46;
911 char *buffer = new char[bufferSize];
912
913 memset(buffer, 0, 46); // zero is a nice default for most header fields
914
915 /* clang-format off */
916 const char head[] = {
917 'P', 'K', 1, 2, // central file header signature
918 0x14, 3, // version made by (3 == UNIX)
919 0x14, 0 // version needed to extract
920 };
921 /* clang-format on */
922
923 // I do not know why memcpy is not working here
924 // memcpy(buffer, head, sizeof(head));
925 memmove(buffer, head, sizeof(head));
926
927 buffer[10] = char(entry->encoding()); // compression method
928 buffer[11] = char(entry->encoding() >> 8);
929
930 transformToMsDos(entry->date(), &buffer[12]);
931
932 uLong mycrc = entry->crc32();
933 buffer[16] = char(mycrc); // crc checksum
934 buffer[17] = char(mycrc >> 8);
935 buffer[18] = char(mycrc >> 16);
936 buffer[19] = char(mycrc >> 24);
937
938 int mysize1 = entry->compressedSize();
939 buffer[20] = char(mysize1); // compressed file size
940 buffer[21] = char(mysize1 >> 8);
941 buffer[22] = char(mysize1 >> 16);
942 buffer[23] = char(mysize1 >> 24);
943
944 int mysize = entry->size();
945 buffer[24] = char(mysize); // uncompressed file size
946 buffer[25] = char(mysize >> 8);
947 buffer[26] = char(mysize >> 16);
948 buffer[27] = char(mysize >> 24);
949
950 buffer[28] = char(path.length()); // fileName length
951 buffer[29] = char(path.length() >> 8);
952
953 buffer[30] = char(extra_field_len);
954 buffer[31] = char(extra_field_len >> 8);
955
956 buffer[40] = char(entry->permissions());
957 buffer[41] = char(entry->permissions() >> 8);
958
959 int myhst = entry->headerStart();
960 buffer[42] = char(myhst); // relative offset of local header
961 buffer[43] = char(myhst >> 8);
962 buffer[44] = char(myhst >> 16);
963 buffer[45] = char(myhst >> 24);
964
965 // file name
966 strncpy(buffer + 46, path.constData(), path.length());
967 // qCDebug(KArchiveLog) << "closearchive length to write: " << bufferSize;
968
969 // extra field
970 if (d->m_extraField == ModificationTime) {
971 char *extfield = buffer + 46 + path.length();
972 // "Extended timestamp" header (0x5455)
973 extfield[0] = 'U';
974 extfield[1] = 'T';
975 extfield[2] = 5; // data size
976 extfield[3] = 0;
977 extfield[4] = 1 | 2 | 4; // specify flags from local field
978 // (unless I misread the spec)
979 // provide only modification time
980 unsigned long time = (unsigned long)entry->date().toSecsSinceEpoch();
981 extfield[5] = char(time);
982 extfield[6] = char(time >> 8);
983 extfield[7] = char(time >> 16);
984 extfield[8] = char(time >> 24);
985 }
986
987 crc = crc32(crc, (Bytef *)buffer, bufferSize);
988 bool ok = (device()->write(buffer, bufferSize) == bufferSize);
989 delete[] buffer;
990 if (!ok) {
991 setErrorString(tr("Could not write file header: %1").arg(device()->errorString()));
992 return false;
993 }
994 }
995 qint64 centraldirendoffset = device()->pos();
996 // qCDebug(KArchiveLog) << "closearchive: centraldirendoffset: " << centraldirendoffset;
997 // qCDebug(KArchiveLog) << "closearchive: device()->pos(): " << device()->pos();
998
999 // write end of central dir record.
1000 buffer[0] = 'P'; // end of central dir signature
1001 buffer[1] = 'K';
1002 buffer[2] = 5;
1003 buffer[3] = 6;
1004
1005 buffer[4] = 0; // number of this disk
1006 buffer[5] = 0;
1007
1008 buffer[6] = 0; // number of disk with start of central dir
1009 buffer[7] = 0;
1010
1011 int count = d->m_fileList.count();
1012 // qCDebug(KArchiveLog) << "number of files (count): " << count;
1013
1014 buffer[8] = char(count); // total number of entries in central dir of
1015 buffer[9] = char(count >> 8); // this disk
1016
1017 buffer[10] = buffer[8]; // total number of entries in the central dir
1018 buffer[11] = buffer[9];
1019
1020 int cdsize = centraldirendoffset - centraldiroffset;
1021 buffer[12] = char(cdsize); // size of the central dir
1022 buffer[13] = char(cdsize >> 8);
1023 buffer[14] = char(cdsize >> 16);
1024 buffer[15] = char(cdsize >> 24);
1025
1026 // qCDebug(KArchiveLog) << "end : centraldiroffset: " << centraldiroffset;
1027 // qCDebug(KArchiveLog) << "end : centraldirsize: " << cdsize;
1028
1029 buffer[16] = char(centraldiroffset); // central dir offset
1030 buffer[17] = char(centraldiroffset >> 8);
1031 buffer[18] = char(centraldiroffset >> 16);
1032 buffer[19] = char(centraldiroffset >> 24);
1033
1034 buffer[20] = 0; // zipfile comment length
1035 buffer[21] = 0;
1036
1037 if (device()->write(buffer, 22) != 22) {
1038 setErrorString(tr("Could not write central dir record: %1").arg(device()->errorString()));
1039 return false;
1040 }
1041
1042 return true;
1043}
1044
1045bool KZip::doWriteDir(const QString &name,
1046 const QString &user,
1047 const QString &group,
1048 mode_t perm,
1049 const QDateTime &atime,
1050 const QDateTime &mtime,
1051 const QDateTime &ctime)
1052{
1053 // Zip files have no explicit directories, they are implicitly created during extraction time
1054 // when file entries have paths in them.
1055 // However, to support empty directories, we must create a dummy file entry which ends with '/'.
1056 QString dirName = name;
1057 if (!name.endsWith(QLatin1Char('/'))) {
1058 dirName = dirName.append(QLatin1Char('/'));
1059 }
1060 return writeFile(dirName, QByteArrayView(), perm, user, group, atime, mtime, ctime);
1061}
1062
1064 const QString &user,
1065 const QString &group,
1066 qint64 /*size*/,
1067 mode_t perm,
1068 const QDateTime &accessTime,
1069 const QDateTime &modificationTime,
1070 const QDateTime &creationTime)
1071{
1072 // qCDebug(KArchiveLog);
1073 if (!isOpen()) {
1074 setErrorString(tr("Application error: ZIP file must be open before being written into"));
1075 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
1076 return false;
1077 }
1078
1079 if (!(mode() & QIODevice::WriteOnly)) { // accept WriteOnly and ReadWrite
1080 setErrorString(tr("Application error: attempted to write into non-writable ZIP file"));
1081 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
1082 return false;
1083 }
1084
1085 if (!device()) {
1086 setErrorString(tr("Cannot create a device. Disk full?"));
1087 return false;
1088 }
1089
1090 // set right offset in zip.
1091 if (!device()->seek(d->m_offset)) {
1092 setErrorString(tr("Cannot seek in ZIP file. Disk full?"));
1093 return false;
1094 }
1095
1096 uint atime = accessTime.toSecsSinceEpoch();
1097 uint mtime = modificationTime.toSecsSinceEpoch();
1098 uint ctime = creationTime.toSecsSinceEpoch();
1099
1100 // Find or create parent dir
1101 KArchiveDirectory *parentDir = rootDir();
1102 QString fileName(name);
1103 int i = name.lastIndexOf(QLatin1Char('/'));
1104 if (i != -1) {
1105 QString dir = name.left(i);
1106 fileName = name.mid(i + 1);
1107 // qCDebug(KArchiveLog) << "ensuring" << dir << "exists. fileName=" << fileName;
1108 parentDir = findOrCreate(dir);
1109 }
1110
1111 // delete entries in the filelist with the same fileName as the one we want
1112 // to save, so that we don't have duplicate file entries when viewing the zip
1113 // with konqi...
1114 // CAUTION: the old file itself is still in the zip and won't be removed !!!
1115 // qCDebug(KArchiveLog) << "fileName to write: " << name;
1116 for (auto it = d->m_fileList.begin(); it != d->m_fileList.end();) {
1117 // qCDebug(KArchiveLog) << "prepfileName: " << entry->path();
1118 if (name == (*it)->path()) {
1119 // also remove from the parentDir
1120 if (!parentDir->removeEntryV2(*it)) {
1121 return false;
1122 }
1123 // qCDebug(KArchiveLog) << "removing following entry: " << entry->path();
1124 delete *it;
1125 it = d->m_fileList.erase(it);
1126 } else {
1127 it++;
1128 }
1129 }
1130
1131 // construct a KZipFileEntry and add it to list
1132 KZipFileEntry *e = new KZipFileEntry(this,
1133 fileName,
1134 perm,
1135 modificationTime,
1136 user,
1137 group,
1138 QString(),
1139 name,
1140 device()->pos() + 30 + name.length(), // start
1141 0 /*size unknown yet*/,
1142 d->m_compression,
1143 0 /*csize unknown yet*/);
1144 e->setHeaderStart(device()->pos());
1145 // qCDebug(KArchiveLog) << "wrote file start: " << e->position() << " name: " << name;
1146 if (!parentDir->addEntryV2(e)) {
1147 return false;
1148 }
1149
1150 d->m_currentFile = e;
1151 d->m_fileList.append(e);
1152
1153 int extra_field_len = 0;
1154 if (d->m_extraField == ModificationTime) {
1155 extra_field_len = 17; // value also used in finishWriting()
1156 }
1157
1158 // write out zip header
1159 QByteArray encodedName = QFile::encodeName(name);
1160 int bufferSize = extra_field_len + encodedName.length() + 30;
1161 // qCDebug(KArchiveLog) << "bufferSize=" << bufferSize;
1162 char *buffer = new char[bufferSize];
1163
1164 buffer[0] = 'P'; // local file header signature
1165 buffer[1] = 'K';
1166 buffer[2] = 3;
1167 buffer[3] = 4;
1168
1169 buffer[4] = 0x14; // version needed to extract
1170 buffer[5] = 0;
1171
1172 buffer[6] = 0; // general purpose bit flag
1173 buffer[7] = 0;
1174
1175 buffer[8] = char(e->encoding()); // compression method
1176 buffer[9] = char(e->encoding() >> 8);
1177
1178 transformToMsDos(e->date(), &buffer[10]);
1179
1180 buffer[14] = 'C'; // dummy crc
1181 buffer[15] = 'R';
1182 buffer[16] = 'C';
1183 buffer[17] = 'q';
1184
1185 buffer[18] = 'C'; // compressed file size
1186 buffer[19] = 'S';
1187 buffer[20] = 'I';
1188 buffer[21] = 'Z';
1189
1190 buffer[22] = 'U'; // uncompressed file size
1191 buffer[23] = 'S';
1192 buffer[24] = 'I';
1193 buffer[25] = 'Z';
1194
1195 buffer[26] = (uchar)(encodedName.length()); // fileName length
1196 buffer[27] = (uchar)(encodedName.length() >> 8);
1197
1198 buffer[28] = (uchar)(extra_field_len); // extra field length
1199 buffer[29] = (uchar)(extra_field_len >> 8);
1200
1201 // file name
1202 strncpy(buffer + 30, encodedName.constData(), encodedName.length());
1203
1204 // extra field
1205 if (d->m_extraField == ModificationTime) {
1206 char *extfield = buffer + 30 + encodedName.length();
1207 // "Extended timestamp" header (0x5455)
1208 extfield[0] = 'U';
1209 extfield[1] = 'T';
1210 extfield[2] = 13; // data size
1211 extfield[3] = 0;
1212 extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime
1213
1214 extfield[5] = char(mtime);
1215 extfield[6] = char(mtime >> 8);
1216 extfield[7] = char(mtime >> 16);
1217 extfield[8] = char(mtime >> 24);
1218
1219 extfield[9] = char(atime);
1220 extfield[10] = char(atime >> 8);
1221 extfield[11] = char(atime >> 16);
1222 extfield[12] = char(atime >> 24);
1223
1224 extfield[13] = char(ctime);
1225 extfield[14] = char(ctime >> 8);
1226 extfield[15] = char(ctime >> 16);
1227 extfield[16] = char(ctime >> 24);
1228 }
1229
1230 // Write header
1231 bool b = (device()->write(buffer, bufferSize) == bufferSize);
1232 d->m_crc = 0;
1233 delete[] buffer;
1234
1235 if (!b) {
1236 setErrorString(tr("Could not write to the archive. Disk full?"));
1237 return false;
1238 }
1239
1240 // Prepare device for writing the data
1241 // Either device() if no compression, or a KCompressionDevice to compress
1242 if (d->m_compression == 0) {
1243 d->m_currentDev = device();
1244 return true;
1245 }
1246
1247 auto compressionDevice = new KCompressionDevice(device(), false, KCompressionDevice::GZip);
1248 d->m_currentDev = compressionDevice;
1249 compressionDevice->setSkipHeaders(); // Just zlib, not gzip
1250
1251 b = d->m_currentDev->open(QIODevice::WriteOnly);
1252 Q_ASSERT(b);
1253
1254 if (!b) {
1255 setErrorString(tr("Could not open compression device: %1").arg(d->m_currentDev->errorString()));
1256 }
1257
1258 return b;
1259}
1260
1261bool KZip::doFinishWriting(qint64 size)
1262{
1263 if (d->m_currentFile->encoding() == 8) {
1264 // Finish
1265 (void)d->m_currentDev->write(nullptr, 0);
1266 delete d->m_currentDev;
1267 }
1268 // If 0, d->m_currentDev was device() - don't delete ;)
1269 d->m_currentDev = nullptr;
1270
1271 Q_ASSERT(d->m_currentFile);
1272 // qCDebug(KArchiveLog) << "fileName: " << d->m_currentFile->path();
1273 // qCDebug(KArchiveLog) << "getpos (at): " << device()->pos();
1274 d->m_currentFile->setSize(size);
1275 int extra_field_len = 0;
1276 if (d->m_extraField == ModificationTime) {
1277 extra_field_len = 17; // value also used in finishWriting()
1278 }
1279
1280 const QByteArray encodedName = QFile::encodeName(d->m_currentFile->path());
1281 int csize = device()->pos() - d->m_currentFile->headerStart() - 30 - encodedName.length() - extra_field_len;
1282 d->m_currentFile->setCompressedSize(csize);
1283 // qCDebug(KArchiveLog) << "usize: " << d->m_currentFile->size();
1284 // qCDebug(KArchiveLog) << "csize: " << d->m_currentFile->compressedSize();
1285 // qCDebug(KArchiveLog) << "headerstart: " << d->m_currentFile->headerStart();
1286
1287 // qCDebug(KArchiveLog) << "crc: " << d->m_crc;
1288 d->m_currentFile->setCRC32(d->m_crc);
1289
1290 d->m_currentFile = nullptr;
1291
1292 // update saved offset for appending new files
1293 d->m_offset = device()->pos();
1294 return true;
1295}
1296
1298 const QString &target,
1299 const QString &user,
1300 const QString &group,
1301 mode_t perm,
1302 const QDateTime &atime,
1303 const QDateTime &mtime,
1304 const QDateTime &ctime)
1305{
1306 // reassure that symlink flag is set, otherwise strange things happen on
1307 // extraction
1308 perm |= QT_STAT_LNK;
1310 setCompression(NoCompression); // link targets are never compressed
1311
1312 if (!doPrepareWriting(name, user, group, 0, perm, atime, mtime, ctime)) {
1313 setCompression(c);
1314 return false;
1315 }
1316
1317 QByteArray symlink_target = QFile::encodeName(target);
1318 if (!writeData(symlink_target.constData(), symlink_target.length())) {
1319 setCompression(c);
1320 return false;
1321 }
1322
1323 if (!finishWriting(symlink_target.length())) {
1324 setCompression(c);
1325 return false;
1326 }
1327
1328 setCompression(c);
1329 return true;
1330}
1331
1332void KZip::virtual_hook(int id, void *data)
1333{
1334 KArchive::virtual_hook(id, data);
1335}
1336
1337bool KZip::doWriteData(const char *data, qint64 size)
1338{
1339 Q_ASSERT(d->m_currentFile);
1340 Q_ASSERT(d->m_currentDev);
1341 if (!d->m_currentFile || !d->m_currentDev) {
1342 setErrorString(tr("No file or device"));
1343 return false;
1344 }
1345
1346 // crc to be calculated over uncompressed stuff...
1347 // and they didn't mention it in their docs...
1348 d->m_crc = crc32(d->m_crc, (const Bytef *)data, size);
1349
1350 qint64 written = d->m_currentDev->write(data, size);
1351 // qCDebug(KArchiveLog) << "wrote" << size << "bytes.";
1352 const bool ok = written == size;
1353
1354 if (!ok) {
1355 setErrorString(tr("Error writing data: %1").arg(d->m_currentDev->errorString()));
1356 }
1357
1358 return ok;
1359}
1360
1362{
1363 d->m_compression = (c == NoCompression) ? 0 : 8;
1364}
1365
1367{
1368 return (d->m_compression == 8) ? DeflateCompression : NoCompression;
1369}
1370
1372{
1373 d->m_extraField = ef;
1374}
1375
1377{
1378 return d->m_extraField;
1379}
1380
1381////////////////////////////////////////////////////////////////////////
1382////////////////////// KZipFileEntry////////////////////////////////////
1383////////////////////////////////////////////////////////////////////////
1384class Q_DECL_HIDDEN KZipFileEntry::KZipFileEntryPrivate
1385{
1386public:
1387 KZipFileEntryPrivate()
1388 : crc(0)
1389 , compressedSize(0)
1390 , headerStart(0)
1391 , encoding(0)
1392 {
1393 }
1394 unsigned long crc;
1395 qint64 compressedSize;
1396 qint64 headerStart;
1397 int encoding;
1398 QString path;
1399};
1400
1402 const QString &name,
1403 int access,
1404 const QDateTime &date,
1405 const QString &user,
1406 const QString &group,
1407 const QString &symlink,
1408 const QString &path,
1409 qint64 start,
1410 qint64 uncompressedSize,
1411 int encoding,
1412 qint64 compressedSize)
1413 : KArchiveFile(zip, name, access, date, user, group, symlink, start, uncompressedSize)
1414 , d(new KZipFileEntryPrivate)
1415{
1416 d->path = path;
1417 d->encoding = encoding;
1418 d->compressedSize = compressedSize;
1419}
1420
1422{
1423 delete d;
1424}
1425
1426int KZipFileEntry::encoding() const
1427{
1428 return d->encoding;
1429}
1430
1431qint64 KZipFileEntry::compressedSize() const
1432{
1433 return d->compressedSize;
1434}
1435
1436void KZipFileEntry::setCompressedSize(qint64 compressedSize)
1437{
1438 d->compressedSize = compressedSize;
1439}
1440
1441void KZipFileEntry::setHeaderStart(qint64 headerstart)
1442{
1443 d->headerStart = headerstart;
1444}
1445
1446qint64 KZipFileEntry::headerStart() const
1447{
1448 return d->headerStart;
1449}
1450
1451unsigned long KZipFileEntry::crc32() const
1452{
1453 return d->crc;
1454}
1455
1456void KZipFileEntry::setCRC32(unsigned long crc32)
1457{
1458 d->crc = crc32;
1459}
1460
1462{
1463 return d->path;
1464}
1465
1467{
1468 QIODevice *dev = createDevice();
1469 QByteArray arr;
1470 if (dev) {
1471 arr = dev->readAll();
1472 delete dev;
1473 }
1474 return arr;
1475}
1476
1478{
1479 // qCDebug(KArchiveLog) << "creating iodevice limited to pos=" << position() << ", csize=" << compressedSize();
1480 // Limit the reading to the appropriate part of the underlying device (e.g. file)
1481 KLimitedIODevice *limitedDev = new KLimitedIODevice(archive()->device(), position(), compressedSize());
1482 if (encoding() == 0 || compressedSize() == 0) { // no compression (or even no data)
1483 return limitedDev;
1484 }
1485
1486 if (encoding() == 8) {
1487 // On top of that, create a device that uncompresses the zlib data
1488 KCompressionDevice *filterDev = new KCompressionDevice(limitedDev, true, KCompressionDevice::GZip);
1489
1490 if (!filterDev) {
1491 return nullptr; // ouch
1492 }
1493 filterDev->setSkipHeaders(); // Just zlib, not gzip
1494 bool b = filterDev->open(QIODevice::ReadOnly);
1495 Q_UNUSED(b);
1496 Q_ASSERT(b);
1497 return filterDev;
1498 }
1499
1500 qCCritical(KArchiveLog) << "This zip file contains files compressed with method" << encoding() << ", this method is currently not supported by KZip,"
1501 << "please use a command-line tool to handle this file.";
1502 delete limitedDev;
1503 return nullptr;
1504}
Represents a directory entry in a KArchive.
bool removeEntryV2(KArchiveEntry *)
Removes an entry from the directory.
Definition karchive.cpp:948
bool addEntryV2(KArchiveEntry *)
Definition karchive.cpp:930
const KArchiveEntry * entry(const QString &name) const
Returns the entry in the archive with the given name.
Definition karchive.cpp:908
A base class for entries in an KArchive.
mode_t permissions() const
The permissions and mode flags as returned by the stat() function in st_mode.
Definition karchive.cpp:721
QString user() const
User who created the file.
Definition karchive.cpp:726
virtual bool isDirectory() const
Checks whether the entry is a directory.
Definition karchive.cpp:746
QDateTime date() const
Creation date of the file.
Definition karchive.cpp:711
QString group() const
Group of the user who created the file.
Definition karchive.cpp:731
QString name() const
Name of the file without path.
Definition karchive.cpp:716
KArchiveFile(KArchive *archive, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink, qint64 pos, qint64 size)
Creates a new file entry.
Definition karchive.cpp:772
qint64 size() const
Size of the data.
Definition karchive.cpp:796
qint64 position() const
Position of the data in the [uncompressed] archive.
Definition karchive.cpp:791
QString errorString() const
Returns a description of the last error.
Definition karchive.cpp:251
QIODevice * device() const
The underlying device.
Definition karchive.cpp:630
virtual bool close()
Closes the archive.
Definition karchive.cpp:214
virtual KArchiveDirectory * rootDir()
Retrieves or create the root directory.
Definition karchive.cpp:517
bool finishWriting(qint64 size)
Call finishWriting after writing the data.
Definition karchive.cpp:477
KArchive(const QString &fileName)
Base constructor (protected since this is a pure virtual class).
Definition karchive.cpp:115
bool writeFile(const QString &name, QByteArrayView data, mode_t perm=0100644, const QString &user=QString(), const QString &group=QString(), const QDateTime &atime=QDateTime(), const QDateTime &mtime=QDateTime(), const QDateTime &ctime=QDateTime())
Writes a new file into the archive.
Definition karchive.cpp:383
bool writeData(const char *data, qint64 size)
Write data into the current file - to be called after calling prepareWriting.
Definition karchive.cpp:412
KArchiveDirectory * findOrCreate(const QString &path)
Ensures that path exists, create otherwise.
Definition karchive.cpp:529
QIODevice::OpenMode mode() const
Returns the mode in which the archive was opened.
Definition karchive.cpp:625
QString fileName() const
The name of the archive file, as passed to the constructor that takes a fileName, or an empty string ...
Definition karchive.cpp:640
bool isOpen() const
Checks whether the archive is open.
Definition karchive.cpp:635
void setErrorString(const QString &errorStr)
Sets error description.
Definition karchive.cpp:482
A class for reading and writing compressed data onto a device (e.g.
void setSkipHeaders()
Call this let this device skip the gzip headers when reading/writing.
bool open(QIODevice::OpenMode mode) override
Open for reading or writing.
A KZipFileEntry represents a file in a zip archive.
KZipFileEntry(KZip *zip, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink, const QString &path, qint64 start, qint64 uncompressedSize, int encoding, qint64 compressedSize)
Creates a new zip file entry.
Definition kzip.cpp:1401
~KZipFileEntry() override
Destructor.
Definition kzip.cpp:1421
void setCompressedSize(qint64 compressedSize)
Only used when writing.
Definition kzip.cpp:1436
unsigned long crc32() const
CRC: only used when writing.
Definition kzip.cpp:1451
const QString & path() const
Name with complete path - KArchiveFile::name() is the filename only (no path)
Definition kzip.cpp:1461
QIODevice * createDevice() const override
This method returns a QIODevice to read the file contents.
Definition kzip.cpp:1477
QByteArray data() const override
Definition kzip.cpp:1466
void setHeaderStart(qint64 headerstart)
Header start: only used when writing.
Definition kzip.cpp:1441
A class for reading / writing zip archives.
Definition kzip.h:38
bool openArchive(QIODevice::OpenMode mode) override
Opens the archive for reading.
Definition kzip.cpp:452
bool doWriteDir(const QString &name, const QString &user, const QString &group, mode_t perm, const QDateTime &atime, const QDateTime &mtime, const QDateTime &ctime) override
Reimplemented from KArchive.
Definition kzip.cpp:1045
void setCompression(Compression c)
Call this before writeFile or prepareWriting, to define whether the next files to be written should b...
Definition kzip.cpp:1361
ExtraField
Describes the contents of the "extra field" for a given file in the Zip archive.
Definition kzip.h:68
@ ModificationTime
Modification time ("extended timestamp" header)
Definition kzip.h:70
bool doPrepareWriting(const QString &name, const QString &user, const QString &group, qint64 size, mode_t perm, const QDateTime &atime, const QDateTime &mtime, const QDateTime &creationTime) override
Reimplemented from KArchive.
Definition kzip.cpp:1063
void setExtraField(ExtraField ef)
Call this before writeFile or prepareWriting, to define what the next file to be written should have ...
Definition kzip.cpp:1371
KZip(const QString &filename)
Creates an instance that operates on the given filename.
Definition kzip.cpp:431
bool doFinishWriting(qint64 size) override
Write data to a file that has been created using prepareWriting().
Definition kzip.cpp:1261
~KZip() override
If the zip file is still opened, then it will be closed automatically by the destructor.
Definition kzip.cpp:443
bool closeArchive() override
Closes the archive.
Definition kzip.cpp:851
Compression
Describes the compression type for a given file in the Zip archive.
Definition kzip.h:92
@ DeflateCompression
Deflate compression method.
Definition kzip.h:94
@ NoCompression
Uncompressed.
Definition kzip.h:93
Compression compression() const
The current compression mode that will be used for new files.
Definition kzip.cpp:1366
bool doWriteSymLink(const QString &name, const QString &target, const QString &user, const QString &group, mode_t perm, const QDateTime &atime, const QDateTime &mtime, const QDateTime &ctime) override
Reimplemented from KArchive.
Definition kzip.cpp:1297
bool doWriteData(const char *data, qint64 size) override
Write data to a file that has been created using prepareWriting().
Definition kzip.cpp:1337
ExtraField extraField() const
The current type of "extra field" that will be used for new files.
Definition kzip.cpp:1376
Q_SCRIPTABLE QString start(QString train="")
QString path(const QString &relativePath)
const char * constData() const const
char * data()
qsizetype length() const const
QByteArray & remove(qsizetype pos, qsizetype len)
void resize(qsizetype newSize, char c)
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool startsWith(QByteArrayView bv) const const
QDateTime currentDateTime()
QDate date() const const
bool isValid() const const
QTime time() const const
qint64 toSecsSinceEpoch() const const
QString cleanPath(const QString &path)
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
QByteArray peek(qint64 maxSize)
virtual qint64 pos() const const
QByteArray read(qint64 maxSize)
QByteArray readAll()
virtual bool seek(qint64 pos)
virtual qint64 size() const const
qint64 write(const QByteArray &data)
typedef OpenMode
QString & append(QChar ch)
const QChar * constData() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:52:48 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.