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

KDE's Doxygen guidelines are available online.