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
35 For more information on the zip fileformat go to
36 http://www.pkware.com/support/appnote.html .
41 #include "kfilterdev.h"
42 #include "klimitediodevice.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>
56 const int max_path_len
= 4095; // maximum number of character a path may contain
58 static void transformToMsDos(const QDateTime
& dt
, char* buffer
)
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);
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
87 static time_t transformFromMsDos(const char* buffer
)
89 quint16 time
= (uchar
)buffer
[0] | ( (uchar
)buffer
[1] << 8 );
91 int m
= ( time
& 0x7ff ) >> 5;
92 int s
= ( time
& 0x1f ) * 2 ;
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 );
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
{
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
122 bool newinfounix_seen
; // true if Info-ZIP Unix New extra field has
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
) {
142 kDebug(7040) << "premature end of extended timestamp (#1)";
145 int flags
= *buffer
; // read flags
149 if (flags
& 1) { // contains modification time
151 kDebug(7040) << "premature end of extended timestamp (#2)";
154 pfi
.mtime
= time_t((uchar
)buffer
[0] | (uchar
)buffer
[1] << 8
155 | (uchar
)buffer
[2] << 16 | (uchar
)buffer
[3] << 24);
159 // central extended field cannot contain more than the modification time
160 // even if other flags are set
162 pfi
.exttimestamp_seen
= true;
166 if (flags
& 2) { // contains last access time
168 kDebug(7040) << "premature end of extended timestamp (#3)";
171 pfi
.atime
= time_t((uchar
)buffer
[0] | (uchar
)buffer
[1] << 8
172 | (uchar
)buffer
[2] << 16 | (uchar
)buffer
[3] << 24);
177 if (flags
& 4) { // contains creation time
179 kDebug(7040) << "premature end of extended timestamp (#4)";
182 pfi
.ctime
= time_t((uchar
)buffer
[0] | (uchar
)buffer
[1] << 8
183 | (uchar
)buffer
[2] << 16 | (uchar
)buffer
[3] << 24);
187 pfi
.exttimestamp_seen
= 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;
205 kDebug(7040) << "premature end of Info-ZIP unix extra field old";
209 pfi
.atime
= time_t((uchar
)buffer
[0] | (uchar
)buffer
[1] << 8
210 | (uchar
)buffer
[2] << 16 | (uchar
)buffer
[3] << 24);
212 pfi
.mtime
= time_t((uchar
)buffer
[0] | (uchar
)buffer
[1] << 8
213 | (uchar
)buffer
[2] << 16 | (uchar
)buffer
[3] << 24);
215 if (islocal
&& size
>= 12) {
216 pfi
.uid
= (uchar
)buffer
[0] | (uchar
)buffer
[1] << 8;
218 pfi
.gid
= (uchar
)buffer
[0] | (uchar
)buffer
[1] << 8;
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;
241 kDebug(7040) << "premature end of Info-ZIP unix extra field new";
245 pfi
.uid
= (uchar
)buffer
[0] | (uchar
)buffer
[1] << 8;
247 pfi
.gid
= (uchar
)buffer
[0] | (uchar
)buffer
[1] << 8;
250 pfi
.newinfounix
= true;
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;
272 int fieldsize
= (uchar
)buffer
[0] | (uchar
)buffer
[1] << 8;
276 if (fieldsize
> size
) {
277 //kDebug(7040) << "fieldsize: " << fieldsize << " size: " << size;
278 kDebug(7040) << "premature end of extra fields reached";
283 case 0x5455: // extended timestamp
284 if (!parseExtTimestamp(buffer
, fieldsize
, islocal
, pfi
)) return false;
286 case 0x5855: // old Info-ZIP unix extra field
287 if (!parseInfoZipUnixOld(buffer
, fieldsize
, islocal
, pfi
)) return false;
289 #if 0 // not needed yet
290 case 0x7855: // new Info-ZIP unix extra field
291 if (!parseInfoZipUnixNew(buffer
, fieldsize
, islocal
, pfi
)) return false;
295 /* ignore everything else */;
304 ////////////////////////////////////////////////////////////////////////
305 /////////////////////////// KZip ///////////////////////////////////////
306 ////////////////////////////////////////////////////////////////////////
308 class KZip::KZipPrivate
316 m_extraField( KZip::NoExtraField
),
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 ;)
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
)
345 //kDebug(7040) << this;
351 bool KZip::openArchive( QIODevice::OpenMode mode
)
354 d
->m_fileList
.clear();
356 if ( mode
== QIODevice::WriteOnly
)
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
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 );
383 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#1)";
388 if ( !memcmp( buffer
, "PK\5\6", 4 ) ) // 'end of entries'
390 //kDebug(7040) << "PK56 found end of archive";
395 if ( !memcmp( buffer
, "PK\3\4", 4 ) ) // local file header
397 //kDebug(7040) << "PK34 found local file header";
400 dev
->seek( dev
->pos() + 2 ); // skip 'version needed to extract'
402 // read static header stuff
403 n
= dev
->read( buffer
, 24 );
405 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#4)";
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();
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)";
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.";
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'.
465 // here we have to read through the compressed data to find
467 kDebug(7040) << "trying to seek for next PK78";
468 bool foundSignature
= false;
470 while (!foundSignature
)
472 n
= dev
->read( buffer
, 1 );
475 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#2)";
479 if ( buffer
[0] != 'P' )
482 n
= dev
->read( buffer
, 3 );
485 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#3)";
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 );
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)";
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 );
541 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#2)";
545 if ( buffer
[0] != 'P' )
548 n
= dev
->read( buffer
, 3 );
551 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#3)";
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...
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();
583 kDebug(7040) << "dev->at was successful... ";
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";
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
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 );
613 kWarning(7040) << "Invalid ZIP file, central entry too short"; // not long enough for valid entry
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;
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];
675 int access
= 0100644;
677 if (os_madeby
== 3) { // good ole unix
678 access
= (uchar
)buffer
[40] | (uchar
)buffer
[41] << 8;
683 if ( name
.endsWith( '/' ) ) // Entries with a trailing slash are directories
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( '/' );
695 entryName
= name
.mid( pos
+ 1 );
696 Q_ASSERT( !entryName
.isEmpty() );
698 KArchiveEntry
* entry
;
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";
710 entry
= new KArchiveDirectory( this, entryName
, access
, (int)pfi
.mtime
, rootDir()->user(), rootDir()->group(), QString() );
711 //kDebug(7040) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name;
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
) );
734 rootDir()->addEntry(entry
);
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
);
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";
759 bool foundSignature
= false;
761 while (!foundSignature
)
763 n
= dev
->read( buffer
, 1 );
766 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. " ;
770 if ( buffer
[0] != 'P' )
773 n
= dev
->read( buffer
, 3 );
776 kWarning(7040) << "Invalid ZIP file. Unexpected end of file. " ;
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 );
799 kWarning(7040) << "Invalid ZIP file. Unrecognized header at offset " << offset
;
804 //kDebug(7040) << "*** done *** ";
808 bool KZip::closeArchive()
810 if ( ! ( mode() & QIODevice::WriteOnly
) )
812 //kDebug(7040) << "readonly";
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
);
829 { //set crc and compressed size in each local file header
831 if ( !device()->seek( it
.value()->headerStart() + 14 ) )
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 )
858 device()->seek( atbackup
);
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
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);
925 strncpy( buffer
+ 46, path
, path
.length() );
926 //kDebug(7040) << "closearchive length to write: " << bufferSize;
929 char *extfield
= buffer
+ 46 + path
.length();
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
);
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
959 buffer
[ 4 ] = 0; // number of this disk
962 buffer
[ 6 ] = 0; // number of disk with start of central dir
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
992 if ( device()->write( buffer
, 22 ) != 22 )
998 bool KZip::doWriteDir( const QString
&, const QString
&, const QString
&,
999 mode_t
, time_t, time_t, time_t ) {
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
) {
1009 qWarning( "KZip::writeFile: You must open the zip file before writing to it\n");
1013 if ( ! ( mode() & QIODevice::WriteOnly
) ) // accept WriteOnly and ReadWrite
1015 qWarning( "KZip::writeFile: You must open the zip file for writing\n");
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?";
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
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;
1036 //kDebug(7040) << "prepfileName: " << it.current()->path();
1037 if (name
== it
.value()->path() )
1039 //kDebug(7040) << "removing following entry: " << it.current()->path();
1045 // Find or create parent dir
1046 KArchiveDirectory
* parentDir
= rootDir();
1047 QString
fileName( name
);
1048 int i
= name
.lastIndexOf( '/' );
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
1083 buffer
[ 4 ] = 0x14; // version needed to extract
1086 buffer
[ 6 ] = 0; // general purpose bit flag
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
1099 buffer
[ 18 ] = 'C'; //compressed file size
1104 buffer
[ 22 ] = 'U'; //uncompressed file size
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);
1116 strncpy( buffer
+ 30, encodedName
, encodedName
.length() );
1119 if ( d
->m_extraField
== ModificationTime
)
1121 char *extfield
= buffer
+ 30 + encodedName
.length();
1122 // "Extended timestamp" header (0x5455)
1125 extfield
[2] = 13; // data size
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);
1146 bool b
= (device()->write( buffer
, bufferSize
) == bufferSize
);
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();
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
);
1174 bool KZip::doFinishWriting( qint64 size
)
1176 if ( d
->m_currentFile
->encoding() == 8 ) {
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();
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
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";
1225 QByteArray symlink_target
= QFile::encodeName(target
);
1226 if (!writeData(symlink_target
, symlink_target
.length())) {
1227 kWarning() << "writeData failed";
1232 if (!finishWriting(symlink_target
.length())) {
1233 kWarning() << "finishWriting failed";
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
) {
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
1290 KZipFileEntryPrivate()
1297 qint64 compressedSize
;
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
)
1311 d
->encoding
= encoding
;
1312 d
->compressedSize
= compressedSize
;
1315 KZipFileEntry::~KZipFileEntry()
1320 int KZipFileEntry::encoding() const
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
1350 void KZipFileEntry::setCRC32(unsigned long crc32
)
1355 const QString
&KZipFileEntry::path() const
1360 QByteArray
KZipFileEntry::data() const
1362 QIODevice
* dev
= createDevice();
1365 arr
= dev
->readAll();
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)
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" );
1385 static_cast<KFilterDev
*>(filterDev
)->setSkipHeaders(); // Just zlib, not gzip
1386 bool b
= filterDev
->open( QIODevice::ReadOnly
);
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.";