fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kio / kio / kzip.cpp
bloba91edf5dbd8e9e3fb4a6629e8b802e5aa99314b8
1 /* This file is part of the KDE libraries
2 Copyright (C) 2000 David Faure <faure@kde.org>
3 Copyright (C) 2002 Holger Schroeder <holger-kde@holgis.net>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 This class implements a kioslave to access ZIP files from KDE.
22 you can use it in QIODevice::ReadOnly or in QIODevice::WriteOnly mode, and it
23 behaves just as expected (i hope ;-) ).
24 It can also be used in QIODevice::ReadWrite mode, in this case one can
25 append files to an existing zip archive. when you append new files, which
26 are not yet in the zip, it works as expected, they are appended at the end.
27 when you append a file, which is already in the file, the reference to the
28 old file is dropped and the new one is added to the zip. but the
29 old data from the file itself is not deleted, it is still in the
30 zipfile. so when you want to have a small and garbagefree zipfile,
31 just read the contents of the appended zipfile and write it to a new one
32 in QIODevice::WriteOnly mode. especially take care of this, when you don't want
33 to leak information of how intermediate versions of files in the zip
34 were looking.
35 For more information on the zip fileformat go to
36 http://www.pkware.com/support/appnote.html .
40 #include "kzip.h"
41 #include "kfilterdev.h"
42 #include "klimitediodevice.h"
43 #include <kdebug.h>
45 #include <QtCore/QHash>
46 #include <QtCore/QByteArray>
47 #include <QtCore/QFile>
48 #include <QtCore/QDir>
49 #include <QtCore/QDate>
50 #include <QtCore/QList>
52 #include <zlib.h>
53 #include <time.h>
54 #include <string.h>
56 const int max_path_len = 4095; // maximum number of character a path may contain
58 static void transformToMsDos(const QDateTime& dt, char* buffer)
60 if ( dt.isValid() )
62 const quint16 time =
63 ( dt.time().hour() << 11 ) // 5 bit hour
64 | ( dt.time().minute() << 5 ) // 6 bit minute
65 | ( dt.time().second() >> 1 ); // 5 bit double seconds
67 buffer[0] = char(time);
68 buffer[1] = char(time >> 8);
70 const quint16 date =
71 ( ( dt.date().year() - 1980 ) << 9 ) // 7 bit year 1980-based
72 | ( dt.date().month() << 5 ) // 4 bit month
73 | ( dt.date().day() ); // 5 bit day
75 buffer[2] = char(date);
76 buffer[3] = char(date >> 8);
78 else // !dt.isValid(), assume 1980-01-01 midnight
80 buffer[0] = 0;
81 buffer[1] = 0;
82 buffer[2] = 33;
83 buffer[3] = 0;
87 static time_t transformFromMsDos(const char* buffer)
89 quint16 time = (uchar)buffer[0] | ( (uchar)buffer[1] << 8 );
90 int h = time >> 11;
91 int m = ( time & 0x7ff ) >> 5;
92 int s = ( time & 0x1f ) * 2 ;
93 QTime qt(h, m, s);
95 quint16 date = (uchar)buffer[2] | ( (uchar)buffer[3] << 8 );
96 int y = ( date >> 9 ) + 1980;
97 int o = ( date & 0x1ff ) >> 5;
98 int d = ( date & 0x1f );
99 QDate qd(y, o, d);
101 QDateTime dt( qd, qt );
102 return dt.toTime_t();
105 // == parsing routines for zip headers
107 /** all relevant information about parsing file information */
108 struct ParseFileInfo {
109 // file related info
110 mode_t perm; // permissions of this file
111 time_t atime; // last access time (UNIX format)
112 time_t mtime; // modification time (UNIX format)
113 time_t ctime; // creation time (UNIX format)
114 int uid; // user id (-1 if not specified)
115 int gid; // group id (-1 if not specified)
116 QByteArray guessed_symlink; // guessed symlink target
117 int extralen; // length of extra field
119 // parsing related info
120 bool exttimestamp_seen; // true if extended timestamp extra field
121 // has been parsed
122 bool newinfounix_seen; // true if Info-ZIP Unix New extra field has
123 // been parsed
125 ParseFileInfo() : perm(0100644), uid(-1), gid(-1), extralen(0),
126 exttimestamp_seen(false), newinfounix_seen(false) {
127 ctime = mtime = atime = time(0);
131 /** updates the parse information with the given extended timestamp extra field.
132 * @param buffer start content of buffer known to contain an extended
133 * timestamp extra field (without magic & size)
134 * @param size size of field content (must not count magic and size entries)
135 * @param islocal true if this is a local field, false if central
136 * @param pfi ParseFileInfo object to be updated
137 * @return true if processing was successful
139 static bool parseExtTimestamp(const char *buffer, int size, bool islocal,
140 ParseFileInfo &pfi) {
141 if (size < 1) {
142 kDebug(7040) << "premature end of extended timestamp (#1)";
143 return false;
144 }/*end if*/
145 int flags = *buffer; // read flags
146 buffer += 1;
147 size -= 1;
149 if (flags & 1) { // contains modification time
150 if (size < 4) {
151 kDebug(7040) << "premature end of extended timestamp (#2)";
152 return false;
153 }/*end if*/
154 pfi.mtime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8
155 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
156 buffer += 4;
157 size -= 4;
158 }/*end if*/
159 // central extended field cannot contain more than the modification time
160 // even if other flags are set
161 if (!islocal) {
162 pfi.exttimestamp_seen = true;
163 return true;
164 }/*end if*/
166 if (flags & 2) { // contains last access time
167 if (size < 4) {
168 kDebug(7040) << "premature end of extended timestamp (#3)";
169 return true;
170 }/*end if*/
171 pfi.atime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8
172 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
173 buffer += 4;
174 size -= 4;
175 }/*end if*/
177 if (flags & 4) { // contains creation time
178 if (size < 4) {
179 kDebug(7040) << "premature end of extended timestamp (#4)";
180 return true;
181 }/*end if*/
182 pfi.ctime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8
183 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
184 buffer += 4;
185 }/*end if*/
187 pfi.exttimestamp_seen = true;
188 return true;
191 /** updates the parse information with the given Info-ZIP Unix old extra field.
192 * @param buffer start of content of buffer known to contain an Info-ZIP
193 * Unix old extra field (without magic & size)
194 * @param size size of field content (must not count magic and size entries)
195 * @param islocal true if this is a local field, false if central
196 * @param pfi ParseFileInfo object to be updated
197 * @return true if processing was successful
199 static bool parseInfoZipUnixOld(const char *buffer, int size, bool islocal,
200 ParseFileInfo &pfi) {
201 // spec mandates to omit this field if one of the newer fields are available
202 if (pfi.exttimestamp_seen || pfi.newinfounix_seen) return true;
204 if (size < 8) {
205 kDebug(7040) << "premature end of Info-ZIP unix extra field old";
206 return false;
207 }/*end if*/
209 pfi.atime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8
210 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
211 buffer += 4;
212 pfi.mtime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8
213 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24);
214 buffer += 4;
215 if (islocal && size >= 12) {
216 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
217 buffer += 2;
218 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
219 buffer += 2;
220 }/*end if*/
221 return true;
224 #if 0 // not needed yet
225 /** updates the parse information with the given Info-ZIP Unix new extra field.
226 * @param buffer start of content of buffer known to contain an Info-ZIP
227 * Unix new extra field (without magic & size)
228 * @param size size of field content (must not count magic and size entries)
229 * @param islocal true if this is a local field, false if central
230 * @param pfi ParseFileInfo object to be updated
231 * @return true if processing was successful
233 static bool parseInfoZipUnixNew(const char *buffer, int size, bool islocal,
234 ParseFileInfo &pfi) {
235 if (!islocal) { // contains nothing in central field
236 pfi.newinfounix = true;
237 return true;
238 }/*end if*/
240 if (size < 4) {
241 kDebug(7040) << "premature end of Info-ZIP unix extra field new";
242 return false;
243 }/*end if*/
245 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
246 buffer += 2;
247 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8;
248 buffer += 2;
250 pfi.newinfounix = true;
251 return true;
253 #endif
256 * parses the extra field
257 * @param buffer start of buffer where the extra field is to be found
258 * @param size size of the extra field
259 * @param islocal true if this is part of a local header, false if of central
260 * @param pfi ParseFileInfo object which to write the results into
261 * @return true if parsing was successful
263 static bool parseExtraField(const char *buffer, int size, bool islocal,
264 ParseFileInfo &pfi) {
265 // extra field in central directory doesn't contain useful data, so we
266 // don't bother parsing it
267 if (!islocal) return true;
269 while (size >= 4) { // as long as a potential extra field can be read
270 int magic = (uchar)buffer[0] | (uchar)buffer[1] << 8;
271 buffer += 2;
272 int fieldsize = (uchar)buffer[0] | (uchar)buffer[1] << 8;
273 buffer += 2;
274 size -= 4;
276 if (fieldsize > size) {
277 //kDebug(7040) << "fieldsize: " << fieldsize << " size: " << size;
278 kDebug(7040) << "premature end of extra fields reached";
279 break;
280 }/*end if*/
282 switch (magic) {
283 case 0x5455: // extended timestamp
284 if (!parseExtTimestamp(buffer, fieldsize, islocal, pfi)) return false;
285 break;
286 case 0x5855: // old Info-ZIP unix extra field
287 if (!parseInfoZipUnixOld(buffer, fieldsize, islocal, pfi)) return false;
288 break;
289 #if 0 // not needed yet
290 case 0x7855: // new Info-ZIP unix extra field
291 if (!parseInfoZipUnixNew(buffer, fieldsize, islocal, pfi)) return false;
292 break;
293 #endif
294 default:
295 /* ignore everything else */;
296 }/*end switch*/
298 buffer += fieldsize;
299 size -= fieldsize;
300 }/*wend*/
301 return true;
304 ////////////////////////////////////////////////////////////////////////
305 /////////////////////////// KZip ///////////////////////////////////////
306 ////////////////////////////////////////////////////////////////////////
308 class KZip::KZipPrivate
310 public:
311 KZipPrivate()
312 : m_crc( 0 ),
313 m_currentFile( 0 ),
314 m_currentDev( 0 ),
315 m_compression( 8 ),
316 m_extraField( KZip::NoExtraField ),
317 m_offset( 0 )
320 unsigned long m_crc; // checksum
321 KZipFileEntry* m_currentFile; // file currently being written
322 QIODevice* m_currentDev; // filterdev used to write to the above file
323 QList<KZipFileEntry*> m_fileList; // flat list of all files, for the index (saves a recursive method ;)
324 int m_compression;
325 KZip::ExtraField m_extraField;
326 // m_offset holds the offset of the place in the zip,
327 // where new data can be appended. after openarchive it points to 0, when in
328 // writeonly mode, or it points to the beginning of the central directory.
329 // each call to writefile updates this value.
330 unsigned int m_offset;
333 KZip::KZip( const QString& fileName )
334 : KArchive( fileName ),d(new KZipPrivate)
338 KZip::KZip( QIODevice * dev )
339 : KArchive( dev ),d(new KZipPrivate)
343 KZip::~KZip()
345 //kDebug(7040) << this;
346 if( isOpen() )
347 close();
348 delete d;
351 bool KZip::openArchive( QIODevice::OpenMode mode )
353 //kDebug(7040);
354 d->m_fileList.clear();
356 if ( mode == QIODevice::WriteOnly )
357 return true;
359 char buffer[47];
361 // Check that it's a valid ZIP file
362 // KArchive::open() opened the underlying device already.
364 uint offset = 0; // holds offset, where we read
365 int n;
367 // contains information gathered from the local file headers
368 QHash<QByteArray, ParseFileInfo> pfi_map;
370 QIODevice* dev = device();
372 // We set a bool for knowing if we are allowed to skip the start of the file
373 bool startOfFile = true;
375 for (;;) // repeat until 'end of entries' signature is reached
377 //kDebug(7040) << "loop starts";
378 //kDebug(7040) << "dev->pos() now : " << dev->pos();
379 n = dev->read( buffer, 4 );
381 if (n < 4)
383 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#1)";
385 return false;
388 if ( !memcmp( buffer, "PK\5\6", 4 ) ) // 'end of entries'
390 //kDebug(7040) << "PK56 found end of archive";
391 startOfFile = false;
392 break;
395 if ( !memcmp( buffer, "PK\3\4", 4 ) ) // local file header
397 //kDebug(7040) << "PK34 found local file header";
398 startOfFile = false;
399 // can this fail ???
400 dev->seek( dev->pos() + 2 ); // skip 'version needed to extract'
402 // read static header stuff
403 n = dev->read( buffer, 24 );
404 if (n < 24) {
405 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#4)";
406 return false;
409 int gpf = (uchar)buffer[0]; // "general purpose flag" not "general protection fault" ;-)
410 int compression_mode = (uchar)buffer[2] | (uchar)buffer[3] << 8;
411 time_t mtime = transformFromMsDos( buffer+4 );
413 qint64 compr_size = (uchar)buffer[12] | (uchar)buffer[13] << 8
414 | (uchar)buffer[14] << 16 | (uchar)buffer[15] << 24;
415 qint64 uncomp_size = (uchar)buffer[16] | (uchar)buffer[17] << 8
416 | (uchar)buffer[18] << 16 | (uchar)buffer[19] << 24;
417 int namelen = (uchar)buffer[20] | (uchar)buffer[21] << 8;
418 int extralen = (uchar)buffer[22] | (uchar)buffer[23] << 8;
421 kDebug(7040) << "general purpose bit flag: " << gpf;
422 kDebug(7040) << "compressed size: " << compr_size;
423 kDebug(7040) << "uncompressed size: " << uncomp_size;
424 kDebug(7040) << "namelen: " << namelen;
425 kDebug(7040) << "extralen: " << extralen;
426 kDebug(7040) << "archive size: " << dev->size();
429 // read fileName
430 Q_ASSERT( namelen > 0 );
431 QByteArray fileName = dev->read(namelen);
432 if ( fileName.size() < namelen ) {
433 kWarning(7040) << "Invalid ZIP file. Name not completely read (#2)";
434 return false;
437 ParseFileInfo pfi;
438 pfi.mtime = mtime;
440 // read and parse the beginning of the extra field,
441 // skip rest of extra field in case it is too long
442 unsigned int extraFieldEnd = dev->pos() + extralen;
443 pfi.extralen = extralen;
444 int handledextralen = qMin(extralen, (int)sizeof buffer);
446 //if ( handledextralen )
447 // kDebug(7040) << "handledextralen: " << handledextralen;
449 n = dev->read(buffer, handledextralen);
450 // no error msg necessary as we deliberately truncate the extra field
451 if (!parseExtraField(buffer, handledextralen, true, pfi))
453 kWarning(7040) << "Invalid ZIP File. Broken ExtraField.";
454 return false;
457 // jump to end of extra field
458 dev->seek( extraFieldEnd );
460 // we have to take care of the 'general purpose bit flag'.
461 // if bit 3 is set, the header doesn't contain the length of
462 // the file and we look for the signature 'PK\7\8'.
463 if ( gpf & 8 )
465 // here we have to read through the compressed data to find
466 // the next PKxx
467 kDebug(7040) << "trying to seek for next PK78";
468 bool foundSignature = false;
470 while (!foundSignature)
472 n = dev->read( buffer, 1 );
473 if (n < 1)
475 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#2)";
476 return false;
479 if ( buffer[0] != 'P' )
480 continue;
482 n = dev->read( buffer, 3 );
483 if (n < 3)
485 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#3)";
486 return false;
489 // we have to detect three magic tokens here:
490 // PK34 for the next local header in case there is no data descriptor
491 // PK12 for the central header in case there is no data descriptor
492 // PK78 for the data descriptor in case it is following the compressed data
494 if ( buffer[0] == 'K' && buffer[1] == 7 && buffer[2] == 8 )
496 foundSignature = true;
497 dev->seek( dev->pos() + 12 ); // skip the 'data_descriptor'
499 else if ( ( buffer[0] == 'K' && buffer[1] == 1 && buffer[2] == 2 )
500 || ( buffer[0] == 'K' && buffer[1] == 3 && buffer[2] == 4 ) )
502 foundSignature = true;
503 dev->seek( dev->pos() - 4 ); // go back 4 bytes, so that the magic bytes can be found...
505 else if ( buffer[0] == 'P' || buffer[1] == 'P' || buffer[2] == 'P' )
507 // We have another P character so we must go back a little to check if it is a magic
508 dev->seek( dev->pos() - 3 );
513 else
515 // here we skip the compressed data and jump to the next header
516 //kDebug(7040) << "general purpose bit flag indicates, that local file header contains valid size";
517 // check if this could be a symbolic link
518 if (compression_mode == NoCompression
519 && uncomp_size <= max_path_len
520 && uncomp_size > 0) {
521 // read content and store it
522 // If it's not a symlink, then we'll just discard the data for now.
523 pfi.guessed_symlink = dev->read(uncomp_size);
524 if (pfi.guessed_symlink.size() < uncomp_size) {
525 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#5)";
526 return false;
528 } else {
530 if ( compr_size > dev->size() )
532 // here we cannot trust the compressed size, so scan through the compressed
533 // data to find the next header
534 bool foundSignature = false;
536 while (!foundSignature)
538 n = dev->read( buffer, 1 );
539 if (n < 1)
541 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#2)";
542 return false;
545 if ( buffer[0] != 'P' )
546 continue;
548 n = dev->read( buffer, 3 );
549 if (n < 3)
551 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#3)";
552 return false;
555 // we have to detect three magic tokens here:
556 // PK34 for the next local header in case there is no data descriptor
557 // PK12 for the central header in case there is no data descriptor
558 // PK78 for the data descriptor in case it is following the compressed data
560 if ( buffer[0] == 'K' && buffer[1] == 7 && buffer[2] == 8 )
562 foundSignature = true;
563 dev->seek( dev->pos() + 12 ); // skip the 'data_descriptor'
566 if ( ( buffer[0] == 'K' && buffer[1] == 1 && buffer[2] == 2 )
567 || ( buffer[0] == 'K' && buffer[1] == 3 && buffer[2] == 4 ) )
569 foundSignature = true;
570 dev->seek( dev->pos() - 4 );
571 // go back 4 bytes, so that the magic bytes can be found
572 // in the next cycle...
576 else
578 // kDebug(7040) << "before interesting dev->pos(): " << dev->pos();
579 bool success = dev->seek( dev->pos() + compr_size ); // can this fail ???
580 Q_ASSERT( success ); // let's see...
581 /* kDebug(7040) << "after interesting dev->pos(): " << dev->pos();
582 if ( success )
583 kDebug(7040) << "dev->at was successful... ";
584 else
585 kDebug(7040) << "dev->at failed... ";*/
590 // not needed any more
591 /* // here we calculate the length of the file in the zip
592 // with headers and jump to the next header.
593 uint skip = compr_size + namelen + extralen;
594 offset += 30 + skip;*/
596 pfi_map.insert(fileName, pfi);
598 else if ( !memcmp( buffer, "PK\1\2", 4 ) ) // central block
600 //kDebug(7040) << "PK12 found central block";
601 startOfFile = false;
603 // so we reached the central header at the end of the zip file
604 // here we get all interesting data out of the central header
605 // of a file
606 offset = dev->pos() - 4;
608 //set offset for appending new files
609 if ( d->m_offset == 0L ) d->m_offset = offset;
611 n = dev->read( buffer + 4, 42 );
612 if (n < 42) {
613 kWarning(7040) << "Invalid ZIP file, central entry too short"; // not long enough for valid entry
614 return false;
617 //int gpf = (uchar)buffer[9] << 8 | (uchar)buffer[10];
618 //kDebug() << "general purpose flag=" << gpf;
619 // length of the fileName (well, pathname indeed)
620 int namelen = (uchar)buffer[29] << 8 | (uchar)buffer[28];
621 Q_ASSERT( namelen > 0 );
622 QByteArray bufferName = dev->read( namelen );
623 if ( bufferName.size() < namelen )
624 kWarning(7040) << "Invalid ZIP file. Name not completely read";
626 ParseFileInfo pfi = pfi_map.value( bufferName, ParseFileInfo() );
628 QString name( QFile::decodeName(bufferName) );
630 //kDebug(7040) << "name: " << name;
631 // only in central header ! see below.
632 // length of extra attributes
633 int extralen = (uchar)buffer[31] << 8 | (uchar)buffer[30];
634 // length of comment for this file
635 int commlen = (uchar)buffer[33] << 8 | (uchar)buffer[32];
636 // compression method of this file
637 int cmethod = (uchar)buffer[11] << 8 | (uchar)buffer[10];
639 //kDebug(7040) << "cmethod: " << cmethod;
640 //kDebug(7040) << "extralen: " << extralen;
642 // crc32 of the file
643 uint crc32 = (uchar)buffer[19] << 24 | (uchar)buffer[18] << 16 |
644 (uchar)buffer[17] << 8 | (uchar)buffer[16];
646 // uncompressed file size
647 uint ucsize = (uchar)buffer[27] << 24 | (uchar)buffer[26] << 16 |
648 (uchar)buffer[25] << 8 | (uchar)buffer[24];
649 // compressed file size
650 uint csize = (uchar)buffer[23] << 24 | (uchar)buffer[22] << 16 |
651 (uchar)buffer[21] << 8 | (uchar)buffer[20];
653 // offset of local header
654 uint localheaderoffset = (uchar)buffer[45] << 24 | (uchar)buffer[44] << 16 |
655 (uchar)buffer[43] << 8 | (uchar)buffer[42];
657 // some clever people use different extra field lengths
658 // in the central header and in the local header... funny.
659 // so we need to get the localextralen to calculate the offset
660 // from localheaderstart to dataoffset
661 int localextralen = pfi.extralen; // FIXME: this will not work if
662 // no local header exists
664 //kDebug(7040) << "localextralen: " << localextralen;
666 // offset, where the real data for uncompression starts
667 uint dataoffset = localheaderoffset + 30 + localextralen + namelen; //comment only in central header
669 //kDebug(7040) << "esize: " << esize;
670 //kDebug(7040) << "eoffset: " << eoffset;
671 //kDebug(7040) << "csize: " << csize;
673 int os_madeby = (uchar)buffer[5];
674 bool isdir = false;
675 int access = 0100644;
677 if (os_madeby == 3) { // good ole unix
678 access = (uchar)buffer[40] | (uchar)buffer[41] << 8;
681 QString entryName;
683 if ( name.endsWith( '/' ) ) // Entries with a trailing slash are directories
685 isdir = true;
686 name = name.left( name.length() - 1 );
687 if (os_madeby != 3) access = S_IFDIR | 0755;
688 else Q_ASSERT(access & S_IFDIR);
691 int pos = name.lastIndexOf( '/' );
692 if ( pos == -1 )
693 entryName = name;
694 else
695 entryName = name.mid( pos + 1 );
696 Q_ASSERT( !entryName.isEmpty() );
698 KArchiveEntry* entry;
699 if ( isdir )
701 QString path = QDir::cleanPath( name );
702 const KArchiveEntry* ent = rootDir()->entry( path );
703 if ( ent && ent->isDirectory() )
705 //kDebug(7040) << "Directory already exists, NOT going to add it again";
706 entry = 0;
708 else
710 entry = new KArchiveDirectory( this, entryName, access, (int)pfi.mtime, rootDir()->user(), rootDir()->group(), QString() );
711 //kDebug(7040) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name;
714 else
716 QString symlink;
717 if (S_ISLNK(access)) {
718 symlink = QFile::decodeName(pfi.guessed_symlink);
720 entry = new KZipFileEntry( this, entryName, access, pfi.mtime,
721 rootDir()->user(), rootDir()->group(),
722 symlink, name, dataoffset,
723 ucsize, cmethod, csize );
724 static_cast<KZipFileEntry *>(entry)->setHeaderStart( localheaderoffset );
725 static_cast<KZipFileEntry*>(entry)->setCRC32(crc32);
726 //kDebug(7040) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name;
727 d->m_fileList.append( static_cast<KZipFileEntry *>( entry ) );
730 if ( entry )
732 if ( pos == -1 )
734 rootDir()->addEntry(entry);
736 else
738 // In some tar files we can find dir/./file => call cleanPath
739 QString path = QDir::cleanPath( name.left( pos ) );
740 // Ensure container directory exists, create otherwise
741 KArchiveDirectory * tdir = findOrCreate( path );
742 tdir->addEntry(entry);
746 //calculate offset to next entry
747 offset += 46 + commlen + extralen + namelen;
748 bool b = dev->seek(offset);
749 Q_ASSERT( b );
750 if ( !b )
751 return false;
753 else if ( startOfFile )
755 // The file does not start with any ZIP header (e.g. self-extractable ZIP files)
756 // Therefore we need to find the first PK\003\004 (local header)
757 kDebug(7040) << "Try to skip start of file";
758 startOfFile = false;
759 bool foundSignature = false;
761 while (!foundSignature)
763 n = dev->read( buffer, 1 );
764 if (n < 1)
766 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. " ;
767 return false;
770 if ( buffer[0] != 'P' )
771 continue;
773 n = dev->read( buffer, 3 );
774 if (n < 3)
776 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. " ;
777 return false;
780 // We have to detect the magic token for a local header: PK\003\004
782 * Note: we do not need to check the other magics, if the ZIP file has no
783 * local header, then it has not any files!
785 if ( buffer[0] == 'K' && buffer[1] == 3 && buffer[2] == 4 )
787 foundSignature = true;
788 dev->seek( dev->pos() - 4 ); // go back 4 bytes, so that the magic bytes can be found...
790 else if ( buffer[0] == 'P' || buffer[1] == 'P' || buffer[2] == 'P' )
792 // We have another P character so we must go back a little to check if it is a magic
793 dev->seek( dev->pos() - 3 );
797 else
799 kWarning(7040) << "Invalid ZIP file. Unrecognized header at offset " << offset;
801 return false;
804 //kDebug(7040) << "*** done *** ";
805 return true;
808 bool KZip::closeArchive()
810 if ( ! ( mode() & QIODevice::WriteOnly ) )
812 //kDebug(7040) << "readonly";
813 return true;
816 //ReadWrite or WriteOnly
817 //write all central dir file entries
819 // to be written at the end of the file...
820 char buffer[ 22 ]; // first used for 12, then for 22 at the end
821 uLong crc = crc32(0L, Z_NULL, 0);
823 qint64 centraldiroffset = device()->pos();
824 //kDebug(7040) << "closearchive: centraldiroffset: " << centraldiroffset;
825 qint64 atbackup = centraldiroffset;
826 QMutableListIterator<KZipFileEntry*> it( d->m_fileList );
828 while(it.hasNext())
829 { //set crc and compressed size in each local file header
830 it.next();
831 if ( !device()->seek( it.value()->headerStart() + 14 ) )
832 return false;
833 //kDebug(7040) << "closearchive setcrcandcsize: fileName:"
834 // << it.current()->path()
835 // << "encoding:" << it.current()->encoding();
837 uLong mycrc = it.value()->crc32();
838 buffer[0] = char(mycrc); // crc checksum, at headerStart+14
839 buffer[1] = char(mycrc >> 8);
840 buffer[2] = char(mycrc >> 16);
841 buffer[3] = char(mycrc >> 24);
843 int mysize1 = it.value()->compressedSize();
844 buffer[4] = char(mysize1); // compressed file size, at headerStart+18
845 buffer[5] = char(mysize1 >> 8);
846 buffer[6] = char(mysize1 >> 16);
847 buffer[7] = char(mysize1 >> 24);
849 int myusize = it.value()->size();
850 buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
851 buffer[9] = char(myusize >> 8);
852 buffer[10] = char(myusize >> 16);
853 buffer[11] = char(myusize >> 24);
855 if ( device()->write( buffer, 12 ) != 12 )
856 return false;
858 device()->seek( atbackup );
860 it.toFront();
861 while (it.hasNext())
863 it.next();
864 //kDebug(7040) << "fileName:" << it.current()->path()
865 // << "encoding:" << it.current()->encoding();
867 QByteArray path = QFile::encodeName(it.value()->path());
869 const int extra_field_len = 9;
870 int bufferSize = extra_field_len + path.length() + 46;
871 char* buffer = new char[ bufferSize ];
873 memset(buffer, 0, 46); // zero is a nice default for most header fields
875 const char head[] =
877 'P', 'K', 1, 2, // central file header signature
878 0x14, 3, // version made by (3 == UNIX)
879 0x14, 0 // version needed to extract
882 // I do not know why memcpy is not working here
883 //memcpy(buffer, head, sizeof(head));
884 memmove(buffer, head, sizeof(head));
886 buffer[ 10 ] = char(it.value()->encoding()); // compression method
887 buffer[ 11 ] = char(it.value()->encoding() >> 8);
889 transformToMsDos( it.value()->datetime(), &buffer[ 12 ] );
891 uLong mycrc = it.value()->crc32();
892 buffer[ 16 ] = char(mycrc); // crc checksum
893 buffer[ 17 ] = char(mycrc >> 8);
894 buffer[ 18 ] = char(mycrc >> 16);
895 buffer[ 19 ] = char(mycrc >> 24);
897 int mysize1 = it.value()->compressedSize();
898 buffer[ 20 ] = char(mysize1); // compressed file size
899 buffer[ 21 ] = char(mysize1 >> 8);
900 buffer[ 22 ] = char(mysize1 >> 16);
901 buffer[ 23 ] = char(mysize1 >> 24);
903 int mysize = it.value()->size();
904 buffer[ 24 ] = char(mysize); // uncompressed file size
905 buffer[ 25 ] = char(mysize >> 8);
906 buffer[ 26 ] = char(mysize >> 16);
907 buffer[ 27 ] = char(mysize >> 24);
909 buffer[ 28 ] = char(it.value()->path().length()); // fileName length
910 buffer[ 29 ] = char(it.value()->path().length() >> 8);
912 buffer[ 30 ] = char(extra_field_len);
913 buffer[ 31 ] = char(extra_field_len >> 8);
915 buffer[ 40 ] = char(it.value()->permissions());
916 buffer[ 41 ] = char(it.value()->permissions() >> 8);
918 int myhst = it.value()->headerStart();
919 buffer[ 42 ] = char(myhst); //relative offset of local header
920 buffer[ 43 ] = char(myhst >> 8);
921 buffer[ 44 ] = char(myhst >> 16);
922 buffer[ 45 ] = char(myhst >> 24);
924 // file name
925 strncpy( buffer + 46, path, path.length() );
926 //kDebug(7040) << "closearchive length to write: " << bufferSize;
928 // extra field
929 char *extfield = buffer + 46 + path.length();
930 extfield[0] = 'U';
931 extfield[1] = 'T';
932 extfield[2] = 5;
933 extfield[3] = 0;
934 extfield[4] = 1 | 2 | 4; // specify flags from local field
935 // (unless I misread the spec)
936 // provide only modification time
937 unsigned long time = (unsigned long)it.value()->date();
938 extfield[5] = char(time);
939 extfield[6] = char(time >> 8);
940 extfield[7] = char(time >> 16);
941 extfield[8] = char(time >> 24);
943 crc = crc32(crc, (Bytef *)buffer, bufferSize );
944 bool ok = ( device()->write( buffer, bufferSize ) == bufferSize );
945 delete[] buffer;
946 if ( !ok )
947 return false;
949 qint64 centraldirendoffset = device()->pos();
950 //kDebug(7040) << "closearchive: centraldirendoffset: " << centraldirendoffset;
951 //kDebug(7040) << "closearchive: device()->pos(): " << device()->pos();
953 //write end of central dir record.
954 buffer[ 0 ] = 'P'; //end of central dir signature
955 buffer[ 1 ] = 'K';
956 buffer[ 2 ] = 5;
957 buffer[ 3 ] = 6;
959 buffer[ 4 ] = 0; // number of this disk
960 buffer[ 5 ] = 0;
962 buffer[ 6 ] = 0; // number of disk with start of central dir
963 buffer[ 7 ] = 0;
965 int count = d->m_fileList.count();
966 //kDebug(7040) << "number of files (count): " << count;
969 buffer[ 8 ] = char(count); // total number of entries in central dir of
970 buffer[ 9 ] = char(count >> 8); // this disk
972 buffer[ 10 ] = buffer[ 8 ]; // total number of entries in the central dir
973 buffer[ 11 ] = buffer[ 9 ];
975 int cdsize = centraldirendoffset - centraldiroffset;
976 buffer[ 12 ] = char(cdsize); // size of the central dir
977 buffer[ 13 ] = char(cdsize >> 8);
978 buffer[ 14 ] = char(cdsize >> 16);
979 buffer[ 15 ] = char(cdsize >> 24);
981 //kDebug(7040) << "end : centraldiroffset: " << centraldiroffset;
982 //kDebug(7040) << "end : centraldirsize: " << cdsize;
984 buffer[ 16 ] = char(centraldiroffset); // central dir offset
985 buffer[ 17 ] = char(centraldiroffset >> 8);
986 buffer[ 18 ] = char(centraldiroffset >> 16);
987 buffer[ 19 ] = char(centraldiroffset >> 24);
989 buffer[ 20 ] = 0; //zipfile comment length
990 buffer[ 21 ] = 0;
992 if ( device()->write( buffer, 22 ) != 22 )
993 return false;
995 return true;
998 bool KZip::doWriteDir( const QString&, const QString&, const QString&,
999 mode_t, time_t, time_t, time_t ) {
1000 return true;
1003 bool KZip::doPrepareWriting(const QString &name, const QString &user,
1004 const QString &group, qint64 /*size*/, mode_t perm,
1005 time_t atime, time_t mtime, time_t ctime) {
1006 //kDebug(7040);
1007 if ( !isOpen() )
1009 qWarning( "KZip::writeFile: You must open the zip file before writing to it\n");
1010 return false;
1013 if ( ! ( mode() & QIODevice::WriteOnly ) ) // accept WriteOnly and ReadWrite
1015 qWarning( "KZip::writeFile: You must open the zip file for writing\n");
1016 return false;
1019 Q_ASSERT( device() );
1021 // set right offset in zip.
1022 if ( !device()->seek( d->m_offset ) ) {
1023 kWarning(7040) << "doPrepareWriting: cannot seek in ZIP file. Disk full?";
1024 return false;
1027 // delete entries in the filelist with the same fileName as the one we want
1028 // to save, so that we don't have duplicate file entries when viewing the zip
1029 // with konqi...
1030 // CAUTION: the old file itself is still in the zip and won't be removed !!!
1031 QMutableListIterator<KZipFileEntry*> it( d->m_fileList );
1032 //kDebug(7040) << "fileName to write: " << name;
1033 while(it.hasNext())
1035 it.next();
1036 //kDebug(7040) << "prepfileName: " << it.current()->path();
1037 if (name == it.value()->path() )
1039 //kDebug(7040) << "removing following entry: " << it.current()->path();
1040 delete it.value();
1041 it.remove();
1045 // Find or create parent dir
1046 KArchiveDirectory* parentDir = rootDir();
1047 QString fileName( name );
1048 int i = name.lastIndexOf( '/' );
1049 if ( i != -1 )
1051 QString dir = name.left( i );
1052 fileName = name.mid( i + 1 );
1053 //kDebug(7040) << "ensuring" << dir << "exists. fileName=" << fileName;
1054 parentDir = findOrCreate( dir );
1057 // construct a KZipFileEntry and add it to list
1058 KZipFileEntry * e = new KZipFileEntry( this, fileName, perm, mtime, user, group, QString(),
1059 name, device()->pos() + 30 + name.length(), // start
1060 0 /*size unknown yet*/, d->m_compression, 0 /*csize unknown yet*/ );
1061 e->setHeaderStart( device()->pos() );
1062 //kDebug(7040) << "wrote file start: " << e->position() << " name: " << name;
1063 parentDir->addEntry( e );
1065 d->m_currentFile = e;
1066 d->m_fileList.append( e );
1068 int extra_field_len = 0;
1069 if ( d->m_extraField == ModificationTime )
1070 extra_field_len = 17; // value also used in finishWriting()
1072 // write out zip header
1073 QByteArray encodedName = QFile::encodeName(name);
1074 int bufferSize = extra_field_len + encodedName.length() + 30;
1075 //kDebug(7040) << "bufferSize=" << bufferSize;
1076 char* buffer = new char[ bufferSize ];
1078 buffer[ 0 ] = 'P'; //local file header signature
1079 buffer[ 1 ] = 'K';
1080 buffer[ 2 ] = 3;
1081 buffer[ 3 ] = 4;
1083 buffer[ 4 ] = 0x14; // version needed to extract
1084 buffer[ 5 ] = 0;
1086 buffer[ 6 ] = 0; // general purpose bit flag
1087 buffer[ 7 ] = 0;
1089 buffer[ 8 ] = char(e->encoding()); // compression method
1090 buffer[ 9 ] = char(e->encoding() >> 8);
1092 transformToMsDos( e->datetime(), &buffer[ 10 ] );
1094 buffer[ 14 ] = 'C'; //dummy crc
1095 buffer[ 15 ] = 'R';
1096 buffer[ 16 ] = 'C';
1097 buffer[ 17 ] = 'q';
1099 buffer[ 18 ] = 'C'; //compressed file size
1100 buffer[ 19 ] = 'S';
1101 buffer[ 20 ] = 'I';
1102 buffer[ 21 ] = 'Z';
1104 buffer[ 22 ] = 'U'; //uncompressed file size
1105 buffer[ 23 ] = 'S';
1106 buffer[ 24 ] = 'I';
1107 buffer[ 25 ] = 'Z';
1109 buffer[ 26 ] = (uchar)(encodedName.length()); //fileName length
1110 buffer[ 27 ] = (uchar)(encodedName.length() >> 8);
1112 buffer[ 28 ] = (uchar)(extra_field_len); // extra field length
1113 buffer[ 29 ] = (uchar)(extra_field_len >> 8);
1115 // file name
1116 strncpy( buffer + 30, encodedName, encodedName.length() );
1118 // extra field
1119 if ( d->m_extraField == ModificationTime )
1121 char *extfield = buffer + 30 + encodedName.length();
1122 // "Extended timestamp" header (0x5455)
1123 extfield[0] = 'U';
1124 extfield[1] = 'T';
1125 extfield[2] = 13; // data size
1126 extfield[3] = 0;
1127 extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime
1129 extfield[5] = char(mtime);
1130 extfield[6] = char(mtime >> 8);
1131 extfield[7] = char(mtime >> 16);
1132 extfield[8] = char(mtime >> 24);
1134 extfield[9] = char(atime);
1135 extfield[10] = char(atime >> 8);
1136 extfield[11] = char(atime >> 16);
1137 extfield[12] = char(atime >> 24);
1139 extfield[13] = char(ctime);
1140 extfield[14] = char(ctime >> 8);
1141 extfield[15] = char(ctime >> 16);
1142 extfield[16] = char(ctime >> 24);
1145 // Write header
1146 bool b = (device()->write( buffer, bufferSize ) == bufferSize );
1147 d->m_crc = 0L;
1148 delete[] buffer;
1150 Q_ASSERT( b );
1151 if (!b) {
1152 return false;
1155 // Prepare device for writing the data
1156 // Either device() if no compression, or a KFilterDev to compress
1157 if ( d->m_compression == 0 ) {
1158 d->m_currentDev = device();
1159 return true;
1162 d->m_currentDev = KFilterDev::device( device(), "application/x-gzip", false );
1163 Q_ASSERT( d->m_currentDev );
1164 if ( !d->m_currentDev ) {
1165 return false; // ouch
1167 static_cast<KFilterDev *>(d->m_currentDev)->setSkipHeaders(); // Just zlib, not gzip
1169 b = d->m_currentDev->open( QIODevice::WriteOnly );
1170 Q_ASSERT( b );
1171 return b;
1174 bool KZip::doFinishWriting( qint64 size )
1176 if ( d->m_currentFile->encoding() == 8 ) {
1177 // Finish
1178 (void)d->m_currentDev->write( 0, 0 );
1179 delete d->m_currentDev;
1181 // If 0, d->m_currentDev was device() - don't delete ;)
1182 d->m_currentDev = 0L;
1184 Q_ASSERT( d->m_currentFile );
1185 //kDebug(7040) << "fileName: " << d->m_currentFile->path();
1186 //kDebug(7040) << "getpos (at): " << device()->pos();
1187 d->m_currentFile->setSize(size);
1188 int extra_field_len = 0;
1189 if ( d->m_extraField == ModificationTime )
1190 extra_field_len = 17; // value also used in finishWriting()
1192 int csize = device()->pos() -
1193 d->m_currentFile->headerStart() - 30 -
1194 d->m_currentFile->path().length() - extra_field_len;
1195 d->m_currentFile->setCompressedSize(csize);
1196 //kDebug(7040) << "usize: " << d->m_currentFile->size();
1197 //kDebug(7040) << "csize: " << d->m_currentFile->compressedSize();
1198 //kDebug(7040) << "headerstart: " << d->m_currentFile->headerStart();
1200 //kDebug(7040) << "crc: " << d->m_crc;
1201 d->m_currentFile->setCRC32( d->m_crc );
1203 d->m_currentFile = 0L;
1205 // update saved offset for appending new files
1206 d->m_offset = device()->pos();
1207 return true;
1210 bool KZip::doWriteSymLink(const QString &name, const QString &target,
1211 const QString &user, const QString &group,
1212 mode_t perm, time_t atime, time_t mtime, time_t ctime) {
1213 // reassure that symlink flag is set, otherwise strange things happen on
1214 // extraction
1215 perm |= S_IFLNK;
1216 Compression c = compression();
1217 setCompression(NoCompression); // link targets are never compressed
1219 if (!doPrepareWriting(name, user, group, 0, perm, atime, mtime, ctime)) {
1220 kWarning() << "prepareWriting failed";
1221 setCompression(c);
1222 return false;
1225 QByteArray symlink_target = QFile::encodeName(target);
1226 if (!writeData(symlink_target, symlink_target.length())) {
1227 kWarning() << "writeData failed";
1228 setCompression(c);
1229 return false;
1232 if (!finishWriting(symlink_target.length())) {
1233 kWarning() << "finishWriting failed";
1234 setCompression(c);
1235 return false;
1238 setCompression(c);
1239 return true;
1242 void KZip::virtual_hook( int id, void* data )
1244 KArchive::virtual_hook( id, data );
1247 bool KZip::writeData(const char * data, qint64 size)
1249 Q_ASSERT( d->m_currentFile );
1250 Q_ASSERT( d->m_currentDev );
1251 if (!d->m_currentFile || !d->m_currentDev) {
1252 return false;
1255 // crc to be calculated over uncompressed stuff...
1256 // and they didn't mention it in their docs...
1257 d->m_crc = crc32(d->m_crc, (const Bytef *) data , size);
1259 qint64 written = d->m_currentDev->write( data, size );
1260 //kDebug(7040) << "wrote" << size << "bytes.";
1261 return written == size;
1264 void KZip::setCompression( Compression c )
1266 d->m_compression = ( c == NoCompression ) ? 0 : 8;
1269 KZip::Compression KZip::compression() const
1271 return ( d->m_compression == 8 ) ? DeflateCompression : NoCompression;
1274 void KZip::setExtraField( ExtraField ef )
1276 d->m_extraField = ef;
1279 KZip::ExtraField KZip::extraField() const
1281 return d->m_extraField;
1284 ////////////////////////////////////////////////////////////////////////
1285 ////////////////////// KZipFileEntry////////////////////////////////////
1286 ////////////////////////////////////////////////////////////////////////
1287 class KZipFileEntry::KZipFileEntryPrivate
1289 public:
1290 KZipFileEntryPrivate()
1291 : crc(0),
1292 compressedSize(0),
1293 headerStart(0),
1294 encoding(0)
1296 unsigned long crc;
1297 qint64 compressedSize;
1298 qint64 headerStart;
1299 int encoding;
1300 QString path;
1303 KZipFileEntry::KZipFileEntry(KZip* zip, const QString& name, int access, int date,
1304 const QString& user, const QString& group, const QString& symlink,
1305 const QString& path, qint64 start, qint64 uncompressedSize,
1306 int encoding, qint64 compressedSize)
1307 : KArchiveFile(zip, name, access, date, user, group, symlink, start, uncompressedSize ),
1308 d(new KZipFileEntryPrivate)
1310 d->path = path;
1311 d->encoding = encoding;
1312 d->compressedSize = compressedSize;
1315 KZipFileEntry::~KZipFileEntry()
1317 delete d;
1320 int KZipFileEntry::encoding() const
1322 return d->encoding;
1325 qint64 KZipFileEntry::compressedSize() const
1327 return d->compressedSize;
1330 void KZipFileEntry::setCompressedSize(qint64 compressedSize)
1332 d->compressedSize = compressedSize;
1335 void KZipFileEntry::setHeaderStart(qint64 headerstart)
1337 d->headerStart = headerstart;
1340 qint64 KZipFileEntry::headerStart() const
1342 return d->headerStart;
1345 unsigned long KZipFileEntry::crc32() const
1347 return d->crc;
1350 void KZipFileEntry::setCRC32(unsigned long crc32)
1352 d->crc=crc32;
1355 const QString &KZipFileEntry::path() const
1357 return d->path;
1360 QByteArray KZipFileEntry::data() const
1362 QIODevice* dev = createDevice();
1363 QByteArray arr;
1364 if ( dev ) {
1365 arr = dev->readAll();
1366 delete dev;
1368 return arr;
1371 QIODevice* KZipFileEntry::createDevice() const
1373 //kDebug(7040) << "creating iodevice limited to pos=" << position() << ", csize=" << compressedSize();
1374 // Limit the reading to the appropriate part of the underlying device (e.g. file)
1375 KLimitedIODevice* limitedDev = new KLimitedIODevice( archive()->device(), position(), compressedSize() );
1376 if ( encoding() == 0 || compressedSize() == 0 ) // no compression (or even no data)
1377 return limitedDev;
1379 if ( encoding() == 8 )
1381 // On top of that, create a device that uncompresses the zlib data
1382 QIODevice* filterDev = KFilterDev::device( limitedDev, "application/x-gzip" );
1383 if ( !filterDev )
1384 return 0L; // ouch
1385 static_cast<KFilterDev *>(filterDev)->setSkipHeaders(); // Just zlib, not gzip
1386 bool b = filterDev->open( QIODevice::ReadOnly );
1387 Q_ASSERT( b );
1388 return filterDev;
1391 kError() << "This zip file contains files compressed with method"
1392 << encoding() << ", this method is currently not supported by KZip,"
1393 << "please use a command-line tool to handle this file.";
1394 return 0L;