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