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;
838 for (KZipFileEntry *entry : d->m_fileList) {
839 // set crc and compressed size in each local file header
840 if (!device()->seek(entry->headerStart() + 14)) {
841 setErrorString(tr("Could not seek to next file header: %1").arg(device()->errorString()));
842 return false;
843 }
844 // qCDebug(KArchiveLog) << "closearchive setcrcandcsize: fileName:"
845 // << entry->path()
846 // << "encoding:" << entry->encoding();
847
848 uLong mycrc = entry->crc32();
849 buffer[0] = char(mycrc); // crc checksum, at headerStart+14
850 buffer[1] = char(mycrc >> 8);
851 buffer[2] = char(mycrc >> 16);
852 buffer[3] = char(mycrc >> 24);
853
854 int mysize1 = entry->compressedSize();
855 buffer[4] = char(mysize1); // compressed file size, at headerStart+18
856 buffer[5] = char(mysize1 >> 8);
857 buffer[6] = char(mysize1 >> 16);
858 buffer[7] = char(mysize1 >> 24);
859
860 int myusize = entry->size();
861 buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
862 buffer[9] = char(myusize >> 8);
863 buffer[10] = char(myusize >> 16);
864 buffer[11] = char(myusize >> 24);
865
866 if (device()->write(buffer, 12) != 12) {
867 setErrorString(tr("Could not write file header: %1").arg(device()->errorString()));
868 return false;
869 }
870 }
871 device()->seek(atbackup);
872
873 for (KZipFileEntry *entry : d->m_fileList) {
874 // qCDebug(KArchiveLog) << "fileName:" << entry->path()
875 // << "encoding:" << entry->encoding();
876
877 QByteArray path = QFile::encodeName(entry->path());
878
879 const int extra_field_len = (d->m_extraField == ModificationTime) ? 9 : 0;
880 const int bufferSize = extra_field_len + path.length() + 46;
881 char *buffer = new char[bufferSize];
882
883 memset(buffer, 0, 46); // zero is a nice default for most header fields
884
885 /* clang-format off */
886 const char head[] = {
887 'P', 'K', 1, 2, // central file header signature
888 0x14, 3, // version made by (3 == UNIX)
889 0x14, 0 // version needed to extract
890 };
891 /* clang-format on */
892
893 // I do not know why memcpy is not working here
894 // memcpy(buffer, head, sizeof(head));
895 memmove(buffer, head, sizeof(head));
896
897 buffer[10] = char(entry->encoding()); // compression method
898 buffer[11] = char(entry->encoding() >> 8);
899
900 transformToMsDos(entry->date(), &buffer[12]);
901
902 uLong mycrc = entry->crc32();
903 buffer[16] = char(mycrc); // crc checksum
904 buffer[17] = char(mycrc >> 8);
905 buffer[18] = char(mycrc >> 16);
906 buffer[19] = char(mycrc >> 24);
907
908 int mysize1 = entry->compressedSize();
909 buffer[20] = char(mysize1); // compressed file size
910 buffer[21] = char(mysize1 >> 8);
911 buffer[22] = char(mysize1 >> 16);
912 buffer[23] = char(mysize1 >> 24);
913
914 int mysize = entry->size();
915 buffer[24] = char(mysize); // uncompressed file size
916 buffer[25] = char(mysize >> 8);
917 buffer[26] = char(mysize >> 16);
918 buffer[27] = char(mysize >> 24);
919
920 buffer[28] = char(path.length()); // fileName length
921 buffer[29] = char(path.length() >> 8);
922
923 buffer[30] = char(extra_field_len);
924 buffer[31] = char(extra_field_len >> 8);
925
926 buffer[40] = char(entry->permissions());
927 buffer[41] = char(entry->permissions() >> 8);
928
929 int myhst = entry->headerStart();
930 buffer[42] = char(myhst); // relative offset of local header
931 buffer[43] = char(myhst >> 8);
932 buffer[44] = char(myhst >> 16);
933 buffer[45] = char(myhst >> 24);
934
935 // file name
936 strncpy(buffer + 46, path.constData(), path.length());
937 // qCDebug(KArchiveLog) << "closearchive length to write: " << bufferSize;
938
939 // extra field
940 if (d->m_extraField == ModificationTime) {
941 char *extfield = buffer + 46 + path.length();
942 // "Extended timestamp" header (0x5455)
943 extfield[0] = 'U';
944 extfield[1] = 'T';
945 extfield[2] = 5; // data size
946 extfield[3] = 0;
947 extfield[4] = 1 | 2 | 4; // specify flags from local field
948 // (unless I misread the spec)
949 // provide only modification time
950 unsigned long time = (unsigned long)entry->date().toSecsSinceEpoch();
951 extfield[5] = char(time);
952 extfield[6] = char(time >> 8);
953 extfield[7] = char(time >> 16);
954 extfield[8] = char(time >> 24);
955 }
956
957 crc = crc32(crc, (Bytef *)buffer, bufferSize);
958 bool ok = (device()->write(buffer, bufferSize) == bufferSize);
959 delete[] buffer;
960 if (!ok) {
961 setErrorString(tr("Could not write file header: %1").arg(device()->errorString()));
962 return false;
963 }
964 }
965 qint64 centraldirendoffset = device()->pos();
966 // qCDebug(KArchiveLog) << "closearchive: centraldirendoffset: " << centraldirendoffset;
967 // qCDebug(KArchiveLog) << "closearchive: device()->pos(): " << device()->pos();
968
969 // write end of central dir record.
970 buffer[0] = 'P'; // end of central dir signature
971 buffer[1] = 'K';
972 buffer[2] = 5;
973 buffer[3] = 6;
974
975 buffer[4] = 0; // number of this disk
976 buffer[5] = 0;
977
978 buffer[6] = 0; // number of disk with start of central dir
979 buffer[7] = 0;
980
981 int count = d->m_fileList.count();
982 // qCDebug(KArchiveLog) << "number of files (count): " << count;
983
984 buffer[8] = char(count); // total number of entries in central dir of
985 buffer[9] = char(count >> 8); // this disk
986
987 buffer[10] = buffer[8]; // total number of entries in the central dir
988 buffer[11] = buffer[9];
989
990 int cdsize = centraldirendoffset - centraldiroffset;
991 buffer[12] = char(cdsize); // size of the central dir
992 buffer[13] = char(cdsize >> 8);
993 buffer[14] = char(cdsize >> 16);
994 buffer[15] = char(cdsize >> 24);
995
996 // qCDebug(KArchiveLog) << "end : centraldiroffset: " << centraldiroffset;
997 // qCDebug(KArchiveLog) << "end : centraldirsize: " << cdsize;
998
999 buffer[16] = char(centraldiroffset); // central dir offset
1000 buffer[17] = char(centraldiroffset >> 8);
1001 buffer[18] = char(centraldiroffset >> 16);
1002 buffer[19] = char(centraldiroffset >> 24);
1003
1004 buffer[20] = 0; // zipfile comment length
1005 buffer[21] = 0;
1006
1007 if (device()->write(buffer, 22) != 22) {
1008 setErrorString(tr("Could not write central dir record: %1").arg(device()->errorString()));
1009 return false;
1010 }
1011
1012 return true;
1013}
1014
1015bool KZip::doWriteDir(const QString &name,
1016 const QString &user,
1017 const QString &group,
1018 mode_t perm,
1019 const QDateTime &atime,
1020 const QDateTime &mtime,
1021 const QDateTime &ctime)
1022{
1023 // Zip files have no explicit directories, they are implicitly created during extraction time
1024 // when file entries have paths in them.
1025 // However, to support empty directories, we must create a dummy file entry which ends with '/'.
1026 QString dirName = name;
1027 if (!name.endsWith(QLatin1Char('/'))) {
1028 dirName = dirName.append(QLatin1Char('/'));
1029 }
1030 return writeFile(dirName, QByteArrayView(), perm, user, group, atime, mtime, ctime);
1031}
1032
1034 const QString &user,
1035 const QString &group,
1036 qint64 /*size*/,
1037 mode_t perm,
1038 const QDateTime &accessTime,
1039 const QDateTime &modificationTime,
1040 const QDateTime &creationTime)
1041{
1042 // qCDebug(KArchiveLog);
1043 if (!isOpen()) {
1044 setErrorString(tr("Application error: ZIP file must be open before being written into"));
1045 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
1046 return false;
1047 }
1048
1049 if (!(mode() & QIODevice::WriteOnly)) { // accept WriteOnly and ReadWrite
1050 setErrorString(tr("Application error: attempted to write into non-writable ZIP file"));
1051 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
1052 return false;
1053 }
1054
1055 if (!device()) {
1056 setErrorString(tr("Cannot create a device. Disk full?"));
1057 return false;
1058 }
1059
1060 // set right offset in zip.
1061 if (!device()->seek(d->m_offset)) {
1062 setErrorString(tr("Cannot seek in ZIP file. Disk full?"));
1063 return false;
1064 }
1065
1066 uint atime = accessTime.toSecsSinceEpoch();
1067 uint mtime = modificationTime.toSecsSinceEpoch();
1068 uint ctime = creationTime.toSecsSinceEpoch();
1069
1070 // Find or create parent dir
1071 KArchiveDirectory *parentDir = rootDir();
1072 QString fileName(name);
1073 int i = name.lastIndexOf(QLatin1Char('/'));
1074 if (i != -1) {
1075 QString dir = name.left(i);
1076 fileName = name.mid(i + 1);
1077 // qCDebug(KArchiveLog) << "ensuring" << dir << "exists. fileName=" << fileName;
1078 parentDir = findOrCreate(dir);
1079 }
1080
1081 // delete entries in the filelist with the same fileName as the one we want
1082 // to save, so that we don't have duplicate file entries when viewing the zip
1083 // with konqi...
1084 // CAUTION: the old file itself is still in the zip and won't be removed !!!
1085 // qCDebug(KArchiveLog) << "fileName to write: " << name;
1086 for (auto it = d->m_fileList.begin(); it != d->m_fileList.end();) {
1087 // qCDebug(KArchiveLog) << "prepfileName: " << entry->path();
1088 if (name == (*it)->path()) {
1089 // also remove from the parentDir
1090 parentDir->removeEntry(*it);
1091 // qCDebug(KArchiveLog) << "removing following entry: " << entry->path();
1092 delete *it;
1093 it = d->m_fileList.erase(it);
1094 } else {
1095 it++;
1096 }
1097 }
1098
1099 // construct a KZipFileEntry and add it to list
1100 KZipFileEntry *e = new KZipFileEntry(this,
1101 fileName,
1102 perm,
1103 modificationTime,
1104 user,
1105 group,
1106 QString(),
1107 name,
1108 device()->pos() + 30 + name.length(), // start
1109 0 /*size unknown yet*/,
1110 d->m_compression,
1111 0 /*csize unknown yet*/);
1112 e->setHeaderStart(device()->pos());
1113 // qCDebug(KArchiveLog) << "wrote file start: " << e->position() << " name: " << name;
1114 if (!parentDir->addEntryV2(e)) {
1115 return false;
1116 }
1117
1118 d->m_currentFile = e;
1119 d->m_fileList.append(e);
1120
1121 int extra_field_len = 0;
1122 if (d->m_extraField == ModificationTime) {
1123 extra_field_len = 17; // value also used in finishWriting()
1124 }
1125
1126 // write out zip header
1127 QByteArray encodedName = QFile::encodeName(name);
1128 int bufferSize = extra_field_len + encodedName.length() + 30;
1129 // qCDebug(KArchiveLog) << "bufferSize=" << bufferSize;
1130 char *buffer = new char[bufferSize];
1131
1132 buffer[0] = 'P'; // local file header signature
1133 buffer[1] = 'K';
1134 buffer[2] = 3;
1135 buffer[3] = 4;
1136
1137 buffer[4] = 0x14; // version needed to extract
1138 buffer[5] = 0;
1139
1140 buffer[6] = 0; // general purpose bit flag
1141 buffer[7] = 0;
1142
1143 buffer[8] = char(e->encoding()); // compression method
1144 buffer[9] = char(e->encoding() >> 8);
1145
1146 transformToMsDos(e->date(), &buffer[10]);
1147
1148 buffer[14] = 'C'; // dummy crc
1149 buffer[15] = 'R';
1150 buffer[16] = 'C';
1151 buffer[17] = 'q';
1152
1153 buffer[18] = 'C'; // compressed file size
1154 buffer[19] = 'S';
1155 buffer[20] = 'I';
1156 buffer[21] = 'Z';
1157
1158 buffer[22] = 'U'; // uncompressed file size
1159 buffer[23] = 'S';
1160 buffer[24] = 'I';
1161 buffer[25] = 'Z';
1162
1163 buffer[26] = (uchar)(encodedName.length()); // fileName length
1164 buffer[27] = (uchar)(encodedName.length() >> 8);
1165
1166 buffer[28] = (uchar)(extra_field_len); // extra field length
1167 buffer[29] = (uchar)(extra_field_len >> 8);
1168
1169 // file name
1170 strncpy(buffer + 30, encodedName.constData(), encodedName.length());
1171
1172 // extra field
1173 if (d->m_extraField == ModificationTime) {
1174 char *extfield = buffer + 30 + encodedName.length();
1175 // "Extended timestamp" header (0x5455)
1176 extfield[0] = 'U';
1177 extfield[1] = 'T';
1178 extfield[2] = 13; // data size
1179 extfield[3] = 0;
1180 extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime
1181
1182 extfield[5] = char(mtime);
1183 extfield[6] = char(mtime >> 8);
1184 extfield[7] = char(mtime >> 16);
1185 extfield[8] = char(mtime >> 24);
1186
1187 extfield[9] = char(atime);
1188 extfield[10] = char(atime >> 8);
1189 extfield[11] = char(atime >> 16);
1190 extfield[12] = char(atime >> 24);
1191
1192 extfield[13] = char(ctime);
1193 extfield[14] = char(ctime >> 8);
1194 extfield[15] = char(ctime >> 16);
1195 extfield[16] = char(ctime >> 24);
1196 }
1197
1198 // Write header
1199 bool b = (device()->write(buffer, bufferSize) == bufferSize);
1200 d->m_crc = 0;
1201 delete[] buffer;
1202
1203 if (!b) {
1204 setErrorString(tr("Could not write to the archive. Disk full?"));
1205 return false;
1206 }
1207
1208 // Prepare device for writing the data
1209 // Either device() if no compression, or a KCompressionDevice to compress
1210 if (d->m_compression == 0) {
1211 d->m_currentDev = device();
1212 return true;
1213 }
1214
1215 auto compressionDevice = new KCompressionDevice(device(), false, KCompressionDevice::GZip);
1216 d->m_currentDev = compressionDevice;
1217 compressionDevice->setSkipHeaders(); // Just zlib, not gzip
1218
1219 b = d->m_currentDev->open(QIODevice::WriteOnly);
1220 Q_ASSERT(b);
1221
1222 if (!b) {
1223 setErrorString(tr("Could not open compression device: %1").arg(d->m_currentDev->errorString()));
1224 }
1225
1226 return b;
1227}
1228
1229bool KZip::doFinishWriting(qint64 size)
1230{
1231 if (d->m_currentFile->encoding() == 8) {
1232 // Finish
1233 (void)d->m_currentDev->write(nullptr, 0);
1234 delete d->m_currentDev;
1235 }
1236 // If 0, d->m_currentDev was device() - don't delete ;)
1237 d->m_currentDev = nullptr;
1238
1239 Q_ASSERT(d->m_currentFile);
1240 // qCDebug(KArchiveLog) << "fileName: " << d->m_currentFile->path();
1241 // qCDebug(KArchiveLog) << "getpos (at): " << device()->pos();
1242 d->m_currentFile->setSize(size);
1243 int extra_field_len = 0;
1244 if (d->m_extraField == ModificationTime) {
1245 extra_field_len = 17; // value also used in finishWriting()
1246 }
1247
1248 const QByteArray encodedName = QFile::encodeName(d->m_currentFile->path());
1249 int csize = device()->pos() - d->m_currentFile->headerStart() - 30 - encodedName.length() - extra_field_len;
1250 d->m_currentFile->setCompressedSize(csize);
1251 // qCDebug(KArchiveLog) << "usize: " << d->m_currentFile->size();
1252 // qCDebug(KArchiveLog) << "csize: " << d->m_currentFile->compressedSize();
1253 // qCDebug(KArchiveLog) << "headerstart: " << d->m_currentFile->headerStart();
1254
1255 // qCDebug(KArchiveLog) << "crc: " << d->m_crc;
1256 d->m_currentFile->setCRC32(d->m_crc);
1257
1258 d->m_currentFile = nullptr;
1259
1260 // update saved offset for appending new files
1261 d->m_offset = device()->pos();
1262 return true;
1263}
1264
1266 const QString &target,
1267 const QString &user,
1268 const QString &group,
1269 mode_t perm,
1270 const QDateTime &atime,
1271 const QDateTime &mtime,
1272 const QDateTime &ctime)
1273{
1274 // reassure that symlink flag is set, otherwise strange things happen on
1275 // extraction
1276 perm |= QT_STAT_LNK;
1278 setCompression(NoCompression); // link targets are never compressed
1279
1280 if (!doPrepareWriting(name, user, group, 0, perm, atime, mtime, ctime)) {
1281 setCompression(c);
1282 return false;
1283 }
1284
1285 QByteArray symlink_target = QFile::encodeName(target);
1286 if (!writeData(symlink_target.constData(), symlink_target.length())) {
1287 setCompression(c);
1288 return false;
1289 }
1290
1291 if (!finishWriting(symlink_target.length())) {
1292 setCompression(c);
1293 return false;
1294 }
1295
1296 setCompression(c);
1297 return true;
1298}
1299
1300void KZip::virtual_hook(int id, void *data)
1301{
1302 KArchive::virtual_hook(id, data);
1303}
1304
1305bool KZip::doWriteData(const char *data, qint64 size)
1306{
1307 Q_ASSERT(d->m_currentFile);
1308 Q_ASSERT(d->m_currentDev);
1309 if (!d->m_currentFile || !d->m_currentDev) {
1310 setErrorString(tr("No file or device"));
1311 return false;
1312 }
1313
1314 // crc to be calculated over uncompressed stuff...
1315 // and they didn't mention it in their docs...
1316 d->m_crc = crc32(d->m_crc, (const Bytef *)data, size);
1317
1318 qint64 written = d->m_currentDev->write(data, size);
1319 // qCDebug(KArchiveLog) << "wrote" << size << "bytes.";
1320 const bool ok = written == size;
1321
1322 if (!ok) {
1323 setErrorString(tr("Error writing data: %1").arg(d->m_currentDev->errorString()));
1324 }
1325
1326 return ok;
1327}
1328
1330{
1331 d->m_compression = (c == NoCompression) ? 0 : 8;
1332}
1333
1335{
1336 return (d->m_compression == 8) ? DeflateCompression : NoCompression;
1337}
1338
1340{
1341 d->m_extraField = ef;
1342}
1343
1345{
1346 return d->m_extraField;
1347}
1348
1349////////////////////////////////////////////////////////////////////////
1350////////////////////// KZipFileEntry////////////////////////////////////
1351////////////////////////////////////////////////////////////////////////
1352class Q_DECL_HIDDEN KZipFileEntry::KZipFileEntryPrivate
1353{
1354public:
1355 KZipFileEntryPrivate()
1356 : crc(0)
1357 , compressedSize(0)
1358 , headerStart(0)
1359 , encoding(0)
1360 {
1361 }
1362 unsigned long crc;
1363 qint64 compressedSize;
1364 qint64 headerStart;
1365 int encoding;
1366 QString path;
1367};
1368
1370 const QString &name,
1371 int access,
1372 const QDateTime &date,
1373 const QString &user,
1374 const QString &group,
1375 const QString &symlink,
1376 const QString &path,
1377 qint64 start,
1378 qint64 uncompressedSize,
1379 int encoding,
1380 qint64 compressedSize)
1381 : KArchiveFile(zip, name, access, date, user, group, symlink, start, uncompressedSize)
1382 , d(new KZipFileEntryPrivate)
1383{
1384 d->path = path;
1385 d->encoding = encoding;
1386 d->compressedSize = compressedSize;
1387}
1388
1390{
1391 delete d;
1392}
1393
1394int KZipFileEntry::encoding() const
1395{
1396 return d->encoding;
1397}
1398
1399qint64 KZipFileEntry::compressedSize() const
1400{
1401 return d->compressedSize;
1402}
1403
1404void KZipFileEntry::setCompressedSize(qint64 compressedSize)
1405{
1406 d->compressedSize = compressedSize;
1407}
1408
1409void KZipFileEntry::setHeaderStart(qint64 headerstart)
1410{
1411 d->headerStart = headerstart;
1412}
1413
1414qint64 KZipFileEntry::headerStart() const
1415{
1416 return d->headerStart;
1417}
1418
1419unsigned long KZipFileEntry::crc32() const
1420{
1421 return d->crc;
1422}
1423
1424void KZipFileEntry::setCRC32(unsigned long crc32)
1425{
1426 d->crc = crc32;
1427}
1428
1430{
1431 return d->path;
1432}
1433
1435{
1436 QIODevice *dev = createDevice();
1437 QByteArray arr;
1438 if (dev) {
1439 arr = dev->readAll();
1440 delete dev;
1441 }
1442 return arr;
1443}
1444
1446{
1447 // qCDebug(KArchiveLog) << "creating iodevice limited to pos=" << position() << ", csize=" << compressedSize();
1448 // Limit the reading to the appropriate part of the underlying device (e.g. file)
1449 KLimitedIODevice *limitedDev = new KLimitedIODevice(archive()->device(), position(), compressedSize());
1450 if (encoding() == 0 || compressedSize() == 0) { // no compression (or even no data)
1451 return limitedDev;
1452 }
1453
1454 if (encoding() == 8) {
1455 // On top of that, create a device that uncompresses the zlib data
1456 KCompressionDevice *filterDev = new KCompressionDevice(limitedDev, true, KCompressionDevice::GZip);
1457
1458 if (!filterDev) {
1459 return nullptr; // ouch
1460 }
1461 filterDev->setSkipHeaders(); // Just zlib, not gzip
1462 bool b = filterDev->open(QIODevice::ReadOnly);
1463 Q_UNUSED(b);
1464 Q_ASSERT(b);
1465 return filterDev;
1466 }
1467
1468 qCCritical(KArchiveLog) << "This zip file contains files compressed with method" << encoding() << ", this method is currently not supported by KZip,"
1469 << "please use a command-line tool to handle this file.";
1470 delete limitedDev;
1471 return nullptr;
1472}
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:1369
~KZipFileEntry() override
Destructor.
Definition kzip.cpp:1389
void setCompressedSize(qint64 compressedSize)
Only used when writing.
Definition kzip.cpp:1404
unsigned long crc32() const
CRC: only used when writing.
Definition kzip.cpp:1419
const QString & path() const
Name with complete path - KArchiveFile::name() is the filename only (no path)
Definition kzip.cpp:1429
QIODevice * createDevice() const override
This method returns a QIODevice to read the file contents.
Definition kzip.cpp:1445
QByteArray data() const override
Definition kzip.cpp:1434
void setHeaderStart(qint64 headerstart)
Header start: only used when writing.
Definition kzip.cpp:1409
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:1015
void setCompression(Compression c)
Call this before writeFile or prepareWriting, to define whether the next files to be written should b...
Definition kzip.cpp:1329
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:1033
void setExtraField(ExtraField ef)
Call this before writeFile or prepareWriting, to define what the next file to be written should have ...
Definition kzip.cpp:1339
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:1229
~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:1334
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:1265
bool doWriteData(const char *data, qint64 size) override
Write data to a file that has been created using prepareWriting().
Definition kzip.cpp:1305
ExtraField extraField() const
The current type of "extra field" that will be used for new files.
Definition kzip.cpp:1344
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 Fri Nov 22 2024 12:02:32 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.