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

KDE's Doxygen guidelines are available online.