kio Library API Documentation

kzip.cpp

00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2000 David Faure <faure@kde.org> 00003 Copyright (C) 2002 Holger Schroeder <holger-kde@holgis.net> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License version 2 as published by the Free Software Foundation. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00017 Boston, MA 02111-1307, USA. 00018 */ 00019 00020 /* 00021 This class implements a kioslave to access ZIP files from KDE. 00022 you can use it in IO_ReadOnly or in IO_WriteOnly mode, and it 00023 behaves just as expected (i hope ;-) ). 00024 It can also be used in IO_ReadWrite mode, in this case one can 00025 append files to an existing zip archive. when you append new files, which 00026 are not yet in the zip, it works as expected, they are appended at the end. 00027 when you append a file, which is already in the file, the reference to the 00028 old file is dropped and the new one is added to the zip. but the 00029 old data from the file itself is not deleted, it is still in the 00030 zipfile. so when you want to have a small and garbagefree zipfile, 00031 just read the contents of the appended zipfile and write it to a new one 00032 in IO_WriteOnly mode. especially take care of this, when you don't want 00033 to leak information of how intermediate versions of files in the zip 00034 were looking. 00035 For more information on the zip fileformat go to 00036 http://www.pkware.com/support/appnote.html . 00037 00038 */ 00039 00040 #include <qasciidict.h> 00041 #include <qfile.h> 00042 #include <qdir.h> 00043 #include <time.h> 00044 #include <string.h> 00045 #include <qdatetime.h> 00046 #include <kdebug.h> 00047 #include <qptrlist.h> 00048 #include <kmimetype.h> 00049 #include <zlib.h> 00050 00051 #include "kfilterdev.h" 00052 #include "kzip.h" 00053 #include "klimitediodevice.h" 00054 00055 const int max_path_len = 4095; // maximum number of character a path may contain 00056 00057 static void transformToMsDos(const QDateTime& dt, char* buffer) 00058 { 00059 if ( dt.isValid() ) 00060 { 00061 const Q_UINT16 time = 00062 ( dt.time().hour() << 11 ) // 5 bit hour 00063 | ( dt.time().minute() << 5 ) // 6 bit minute 00064 | ( dt.time().second() >> 1 ); // 5 bit double seconds 00065 00066 buffer[0] = char(time); 00067 buffer[1] = char(time >> 8); 00068 00069 const Q_UINT16 date = 00070 ( ( dt.date().year() - 1980 ) << 9 ) // 7 bit year 1980-based 00071 | ( dt.date().month() << 5 ) // 4 bit month 00072 | ( dt.date().day() ); // 5 bit day 00073 00074 buffer[2] = char(date); 00075 buffer[3] = char(date >> 8); 00076 } 00077 else // !dt.isValid(), assume 1980-01-01 midnight 00078 { 00079 buffer[0] = 0; 00080 buffer[1] = 0; 00081 buffer[2] = 33; 00082 buffer[3] = 0; 00083 } 00084 } 00085 00086 // == parsing routines for zip headers 00087 00089 struct ParseFileInfo { 00090 // file related info 00091 // QCString name; // filename 00092 mode_t perm; // permissions of this file 00093 time_t atime; // last access time (UNIX format) 00094 time_t mtime; // modification time (UNIX format) 00095 time_t ctime; // creation time (UNIX format) 00096 int uid; // user id (-1 if not specified) 00097 int gid; // group id (-1 if not specified) 00098 QCString guessed_symlink; // guessed symlink target 00099 int extralen; // length of extra field 00100 00101 // parsing related info 00102 bool exttimestamp_seen; // true if extended timestamp extra field 00103 // has been parsed 00104 bool newinfounix_seen; // true if Info-ZIP Unix New extra field has 00105 // been parsed 00106 00107 ParseFileInfo() : perm(0100644), uid(-1), gid(-1), extralen(0), 00108 exttimestamp_seen(false), newinfounix_seen(false) { 00109 ctime = mtime = atime = time(0); 00110 } 00111 }; 00112 00121 static bool parseExtTimestamp(const char *buffer, int size, bool islocal, 00122 ParseFileInfo &pfi) { 00123 if (size < 1) { 00124 kdDebug(7040) << "premature end of extended timestamp (#1)" << endl; 00125 return false; 00126 }/*end if*/ 00127 int flags = *buffer; // read flags 00128 buffer += 1; 00129 00130 if (flags & 1) { // contains modification time 00131 if (size < 5) { 00132 kdDebug(7040) << "premature end of extended timestamp (#2)" << endl; 00133 return false; 00134 }/*end if*/ 00135 pfi.mtime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8 00136 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24); 00137 }/*end if*/ 00138 buffer += 4; 00139 // central extended field cannot contain more than the modification time 00140 // even if other flags are set 00141 if (!islocal) { 00142 pfi.exttimestamp_seen = true; 00143 return true; 00144 }/*end if*/ 00145 00146 if (flags & 2) { // contains last access time 00147 if (size < 9) { 00148 kdDebug(7040) << "premature end of extended timestamp (#3)" << endl; 00149 return false; 00150 }/*end if*/ 00151 pfi.atime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8 00152 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24); 00153 }/*end if*/ 00154 buffer += 4; 00155 00156 if (flags & 4) { // contains creation time 00157 if (size < 13) { 00158 kdDebug(7040) << "premature end of extended timestamp (#4)" << endl; 00159 return false; 00160 }/*end if*/ 00161 pfi.ctime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8 00162 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24); 00163 }/*end if*/ 00164 buffer += 4; 00165 00166 pfi.exttimestamp_seen = true; 00167 return true; 00168 } 00169 00178 static bool parseInfoZipUnixOld(const char *buffer, int size, bool islocal, 00179 ParseFileInfo &pfi) { 00180 // spec mandates to omit this field if one of the newer fields are available 00181 if (pfi.exttimestamp_seen || pfi.newinfounix_seen) return true; 00182 00183 if (size < 8) { 00184 kdDebug(7040) << "premature end of Info-ZIP unix extra field old" << endl; 00185 return false; 00186 }/*end if*/ 00187 00188 pfi.atime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8 00189 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24); 00190 buffer += 4; 00191 pfi.mtime = time_t((uchar)buffer[0] | (uchar)buffer[1] << 8 00192 | (uchar)buffer[2] << 16 | (uchar)buffer[3] << 24); 00193 buffer += 4; 00194 if (islocal && size >= 12) { 00195 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8; 00196 buffer += 2; 00197 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8; 00198 buffer += 2; 00199 }/*end if*/ 00200 return true; 00201 } 00202 00203 #if 0 // not needed yet 00204 00212 static bool parseInfoZipUnixNew(const char *buffer, int size, bool islocal, 00213 ParseFileInfo &pfi) { 00214 if (!islocal) { // contains nothing in central field 00215 pfi.newinfounix = true; 00216 return true; 00217 }/*end if*/ 00218 00219 if (size < 4) { 00220 kdDebug(7040) << "premature end of Info-ZIP unix extra field new" << endl; 00221 return false; 00222 }/*end if*/ 00223 00224 pfi.uid = (uchar)buffer[0] | (uchar)buffer[1] << 8; 00225 buffer += 2; 00226 pfi.gid = (uchar)buffer[0] | (uchar)buffer[1] << 8; 00227 buffer += 2; 00228 00229 pfi.newinfounix = true; 00230 return true; 00231 } 00232 #endif 00233 00242 static bool parseExtraField(const char *buffer, int size, bool islocal, 00243 ParseFileInfo &pfi) { 00244 // extra field in central directory doesn't contain useful data, so we 00245 // don't bother parsing it 00246 if (!islocal) return true; 00247 00248 while (size >= 4) { // as long as a potential extra field can be read 00249 int magic = (uchar)buffer[0] | (uchar)buffer[1] << 8; 00250 buffer += 2; 00251 int fieldsize = (uchar)buffer[0] | (uchar)buffer[1] << 8; 00252 buffer += 2; 00253 size -= 4; 00254 00255 if (fieldsize > size) { 00256 //kdDebug(7040) << "fieldsize: " << fieldsize << " size: " << size << endl; 00257 kdDebug(7040) << "premature end of extra fields reached" << endl; 00258 break; 00259 }/*end if*/ 00260 00261 switch (magic) { 00262 case 0x5455: // extended timestamp 00263 if (!parseExtTimestamp(buffer, fieldsize, islocal, pfi)) return false; 00264 break; 00265 case 0x5855: // old Info-ZIP unix extra field 00266 if (!parseInfoZipUnixOld(buffer, fieldsize, islocal, pfi)) return false; 00267 break; 00268 #if 0 // not needed yet 00269 case 0x7855: // new Info-ZIP unix extra field 00270 if (!parseInfoZipUnixNew(buffer, fieldsize, islocal, pfi)) return false; 00271 break; 00272 #endif 00273 default: 00274 /* ignore everything else */; 00275 }/*end switch*/ 00276 00277 buffer += fieldsize; 00278 size -= fieldsize; 00279 }/*wend*/ 00280 return true; 00281 } 00282 00286 00287 class KZip::KZipPrivate 00288 { 00289 public: 00290 KZipPrivate() 00291 : m_crc( 0 ), 00292 m_currentFile( 0L ), 00293 m_currentDev( 0L ), 00294 m_compression( 8 ), 00295 m_extraField( KZip::NoExtraField ), 00296 m_offset( 0L ) { } 00297 00298 unsigned long m_crc; // checksum 00299 KZipFileEntry* m_currentFile; // file currently being written 00300 QIODevice* m_currentDev; // filterdev used to write to the above file 00301 QPtrList<KZipFileEntry> m_fileList; // flat list of all files, for the index (saves a recursive method ;) 00302 int m_compression; 00303 KZip::ExtraField m_extraField; 00304 unsigned int m_offset; // holds the offset of the place in the zip, 00305 // where new data can be appended. after openarchive it points to 0, when in 00306 // writeonly mode, or it points to the beginning of the central directory. 00307 // each call to writefile updates this value. 00308 }; 00309 00310 KZip::KZip( const QString& filename ) 00311 : KArchive( 0L ) 00312 { 00313 //kdDebug(7040) << "KZip(filename) reached." << endl; 00314 m_filename = filename; 00315 d = new KZipPrivate; 00316 setDevice( new QFile( filename ) ); 00317 } 00318 00319 KZip::KZip( QIODevice * dev ) 00320 : KArchive( dev ) 00321 { 00322 //kdDebug(7040) << "KZip::KZip( QIODevice * dev) reached." << endl; 00323 d = new KZipPrivate; 00324 } 00325 00326 KZip::~KZip() 00327 { 00328 // mjarrett: Closes to prevent ~KArchive from aborting w/o device 00329 //kdDebug(7040) << "~KZip reached." << endl; 00330 if( isOpened() ) 00331 close(); 00332 if ( !m_filename.isEmpty() ) 00333 delete device(); // we created it ourselves 00334 delete d; 00335 } 00336 00337 bool KZip::openArchive( int mode ) 00338 { 00339 //kdDebug(7040) << "openarchive reached." << endl; 00340 d->m_fileList.clear(); 00341 00342 if ( mode == IO_WriteOnly ) 00343 return true; 00344 if ( mode != IO_ReadOnly && mode != IO_ReadWrite ) 00345 { 00346 kdWarning(7040) << "Unsupported mode " << mode << endl; 00347 return false; 00348 } 00349 00350 char buffer[47]; 00351 00352 // Check that it's a valid ZIP file 00353 // KArchive::open() opened the underlying device already. 00354 QIODevice* dev = device(); 00355 00356 uint offset = 0; // holds offset, where we read 00357 int n; 00358 00359 // contains information gathered from the local file headers 00360 QAsciiDict<ParseFileInfo> pfi_map(1009, true /*case sensitive */, true /*copy keys*/); 00361 pfi_map.setAutoDelete(true); 00362 00363 for (;;) // repeat until 'end of entries' signature is reached 00364 { 00365 n = dev->readBlock( buffer, 4 ); 00366 00367 if (n < 4) 00368 { 00369 kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#1)" << endl; 00370 00371 return false; 00372 } 00373 00374 if ( !memcmp( buffer, "PK\5\6", 4 ) ) // 'end of entries' 00375 break; 00376 00377 if ( !memcmp( buffer, "PK\3\4", 4 ) ) // local file header 00378 { 00379 dev->at( dev->at() + 2 ); // skip 'version needed to extract' 00380 00381 // read static header stuff 00382 n = dev->readBlock( buffer, 24 ); 00383 if (n < 24) { 00384 kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#4)" << endl; 00385 return false; 00386 } 00387 00388 int gpf = (uchar)buffer[0]; // "general purpose flag" not "general protection fault" ;-) 00389 int compression_mode = (uchar)buffer[2] | (uchar)buffer[3] << 8; 00390 Q_LONG compr_size = (uchar)buffer[12] | (uchar)buffer[13] << 8 00391 | (uchar)buffer[14] << 16 | (uchar)buffer[15] << 24; 00392 Q_LONG uncomp_size = (uchar)buffer[16] | (uchar)buffer[17] << 8 00393 | (uchar)buffer[18] << 16 | (uchar)buffer[19] << 24; 00394 int namelen = (uchar)buffer[20] | (uchar)buffer[21] << 8; 00395 int extralen = (uchar)buffer[22] | (uchar)buffer[23] << 8; 00396 00397 // read filename 00398 QCString filename(namelen + 1); 00399 n = dev->readBlock(filename.data(), namelen); 00400 if ( n < namelen ) { 00401 kdWarning(7040) << "Invalid ZIP file. Name not completely read (#2)" << endl; 00402 return false; 00403 } 00404 00405 ParseFileInfo *pfi = new ParseFileInfo(); 00406 pfi_map.insert(filename.data(), pfi); 00407 00408 // read and parse extra field 00409 pfi->extralen = extralen; 00410 int handledextralen = QMIN(extralen, (int)sizeof buffer); 00411 n = dev->readBlock(buffer, handledextralen); 00412 // no error msg necessary as we deliberately truncate the extra field 00413 if (!parseExtraField(buffer, handledextralen, true, *pfi)) 00414 return false; 00415 00416 // we have to take care of the 'general purpose bit flag'. 00417 // if bit 3 is set, the header doesn't contain the length of 00418 // the file and we look for the signature 'PK\7\8'. 00419 if ( gpf & 8 ) 00420 { 00421 bool foundSignature = false; 00422 00423 while (!foundSignature) 00424 { 00425 n = dev->readBlock( buffer, 1 ); 00426 if (n < 1) 00427 { 00428 kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#2)" << endl; 00429 return false; 00430 } 00431 00432 if ( buffer[0] != 'P' ) 00433 continue; 00434 00435 n = dev->readBlock( buffer, 3 ); 00436 if (n < 3) 00437 { 00438 kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#3)" << endl; 00439 return false; 00440 } 00441 00442 if ( buffer[0] == 'K' && buffer[1] == 7 && buffer[2] == 8 ) 00443 { 00444 foundSignature = true; 00445 dev->at( dev->at() + 12 ); // skip the 'data_descriptor' 00446 } 00447 } 00448 } 00449 else 00450 { 00451 // check if this could be a symbolic link 00452 if (compression_mode == NoCompression 00453 && uncomp_size <= max_path_len 00454 && uncomp_size > 0) { 00455 // read content and store it 00456 pfi->guessed_symlink.resize(uncomp_size + 1); 00457 n = dev->readBlock(pfi->guessed_symlink.data(), uncomp_size); 00458 if (n < uncomp_size) { 00459 kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#5)" << endl; 00460 return false; 00461 } 00462 } else { 00463 00464 dev->at( dev->at() + compr_size ); 00465 } 00466 // here we calculate the length of the file in the zip 00467 // with headers and jump to the next header. 00468 uint skip = compr_size + namelen + extralen; 00469 offset += 30 + skip; 00470 } 00471 } 00472 else if ( !memcmp( buffer, "PK\1\2", 4 ) ) // central block 00473 { 00474 00475 // so we reached the central header at the end of the zip file 00476 // here we get all interesting data out of the central header 00477 // of a file 00478 offset = dev->at() - 4; 00479 00480 //set offset for appending new files 00481 if ( d->m_offset == 0L ) d->m_offset = offset; 00482 00483 n = dev->readBlock( buffer + 4, 42 ); 00484 if (n < 42) { 00485 kdWarning(7040) << "Invalid ZIP file, central entry too short" << endl; // not long enough for valid entry 00486 return false; 00487 } 00488 // length of the filename (well, pathname indeed) 00489 int namelen = (uchar)buffer[29] << 8 | (uchar)buffer[28]; 00490 QCString bufferName( namelen + 1 ); 00491 n = dev->readBlock( bufferName.data(), namelen ); 00492 if ( n < namelen ) 00493 kdWarning(7040) << "Invalid ZIP file. Name not completely read" << endl; 00494 00495 ParseFileInfo *pfi = pfi_map[bufferName]; 00496 if (!pfi) { // can that happen? 00497 pfi_map.insert(bufferName.data(), pfi = new ParseFileInfo()); 00498 } 00499 QString name( QFile::decodeName(bufferName) ); 00500 00501 //kdDebug(7040) << "name: " << name << endl; 00502 // only in central header ! see below. 00503 // length of extra attributes 00504 int extralen = (uchar)buffer[31] << 8 | (uchar)buffer[30]; 00505 // length of comment for this file 00506 int commlen = (uchar)buffer[33] << 8 | (uchar)buffer[32]; 00507 // compression method of this file 00508 int cmethod = (uchar)buffer[11] << 8 | (uchar)buffer[10]; 00509 00510 //kdDebug(7040) << "cmethod: " << cmethod << endl; 00511 //kdDebug(7040) << "extralen: " << extralen << endl; 00512 00513 // uncompressed file size 00514 uint ucsize = (uchar)buffer[27] << 24 | (uchar)buffer[26] << 16 | 00515 (uchar)buffer[25] << 8 | (uchar)buffer[24]; 00516 // compressed file size 00517 uint csize = (uchar)buffer[23] << 24 | (uchar)buffer[22] << 16 | 00518 (uchar)buffer[21] << 8 | (uchar)buffer[20]; 00519 00520 // offset of local header 00521 uint localheaderoffset = (uchar)buffer[45] << 24 | (uchar)buffer[44] << 16 | 00522 (uchar)buffer[43] << 8 | (uchar)buffer[42]; 00523 00524 // some clever people use different extra field lengths 00525 // in the central header and in the local header... funny. 00526 // so we need to get the localextralen to calculate the offset 00527 // from localheaderstart to dataoffset 00528 int localextralen = pfi->extralen; // FIXME: this will not work if 00529 // no local header exists 00530 00531 //kdDebug(7040) << "localextralen: " << localextralen << endl; 00532 00533 // offset, where the real data for uncompression starts 00534 uint dataoffset = localheaderoffset + 30 + localextralen + namelen; //comment only in central header 00535 00536 //kdDebug(7040) << "esize: " << esize << endl; 00537 //kdDebug(7040) << "eoffset: " << eoffset << endl; 00538 //kdDebug(7040) << "csize: " << csize << endl; 00539 00540 int os_madeby = (uchar)buffer[5]; 00541 bool isdir = false; 00542 int access = 0100644; 00543 00544 if (os_madeby == 3) { // good ole unix 00545 access = (uchar)buffer[40] | (uchar)buffer[41] << 8; 00546 } 00547 00548 QString entryName; 00549 00550 if ( name.endsWith( "/" ) ) // Entries with a trailing slash are directories 00551 { 00552 isdir = true; 00553 name = name.left( name.length() - 1 ); 00554 if (os_madeby != 3) access = S_IFDIR | 0755; 00555 else Q_ASSERT(access & S_IFDIR); 00556 } 00557 00558 int pos = name.findRev( '/' ); 00559 if ( pos == -1 ) 00560 entryName = name; 00561 else 00562 entryName = name.mid( pos + 1 ); 00563 Q_ASSERT( !entryName.isEmpty() ); 00564 00565 KArchiveEntry* entry; 00566 if ( isdir ) 00567 { 00568 QString path = QDir::cleanDirPath( name ); 00569 KArchiveEntry* ent = rootDir()->entry( path ); 00570 if ( ent && ent->isDirectory() ) 00571 { 00572 //kdDebug(7040) << "Directory already exists, NOT going to add it again" << endl; 00573 entry = 0L; 00574 } 00575 else 00576 { 00577 entry = new KArchiveDirectory( this, entryName, access, (int)pfi->mtime, rootDir()->user(), rootDir()->group(), QString::null ); 00578 //kdDebug(7040) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name << endl; 00579 } 00580 } 00581 else 00582 { 00583 QString symlink; 00584 if (S_ISLNK(access)) { 00585 symlink = QFile::decodeName(pfi->guessed_symlink); 00586 } 00587 entry = new KZipFileEntry( this, entryName, access, pfi->mtime, 00588 rootDir()->user(), rootDir()->group(), 00589 symlink, name, dataoffset, 00590 ucsize, cmethod, csize ); 00591 static_cast<KZipFileEntry *>(entry)->setHeaderStart( localheaderoffset ); 00592 //kdDebug(7040) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name << endl; 00593 d->m_fileList.append( static_cast<KZipFileEntry *>( entry ) ); 00594 } 00595 00596 if ( entry ) 00597 { 00598 if ( pos == -1 ) 00599 { 00600 rootDir()->addEntry(entry); 00601 } 00602 else 00603 { 00604 // In some tar files we can find dir/./file => call cleanDirPath 00605 QString path = QDir::cleanDirPath( name.left( pos ) ); 00606 // Ensure container directory exists, create otherwise 00607 KArchiveDirectory * tdir = findOrCreate( path ); 00608 tdir->addEntry(entry); 00609 } 00610 } 00611 00612 //calculate offset to next entry 00613 offset += 46 + commlen + extralen + namelen; 00614 bool b = dev->at(offset); 00615 Q_ASSERT( b ); 00616 if ( !b ) 00617 return false; 00618 } 00619 else 00620 { 00621 kdWarning(7040) << "Invalid ZIP file. Unrecognized header at offset " << offset << endl; 00622 00623 return false; 00624 } 00625 } 00626 //kdDebug(7040) << "*** done *** " << endl; 00627 return true; 00628 } 00629 00630 bool KZip::closeArchive() 00631 { 00632 if ( ! ( mode() & IO_WriteOnly ) ) 00633 { 00634 //kdDebug(7040) << "closearchive readonly reached." << endl; 00635 return true; 00636 } 00637 //ReadWrite or WriteOnly 00638 //write all central dir file entries 00639 00640 // to be written at the end of the file... 00641 char buffer[ 22 ]; // first used for 12, then for 22 at the end 00642 uLong crc = crc32(0L, Z_NULL, 0); 00643 00644 Q_LONG centraldiroffset = device()->at(); 00645 //kdDebug(7040) << "closearchive: centraldiroffset: " << centraldiroffset << endl; 00646 Q_LONG atbackup = centraldiroffset; 00647 QPtrListIterator<KZipFileEntry> it( d->m_fileList ); 00648 00649 for ( ; it.current() ; ++it ) 00650 { //set crc and compressed size in each local file header 00651 if ( !device()->at( it.current()->headerStart() + 14 ) ) 00652 return false; 00653 //kdDebug(7040) << "closearchive setcrcandcsize: filename: " 00654 // << it.current()->path() 00655 // << " encoding: "<< it.current()->encoding() << endl; 00656 00657 uLong mycrc = it.current()->crc32(); 00658 buffer[0] = char(mycrc); // crc checksum, at headerStart+14 00659 buffer[1] = char(mycrc >> 8); 00660 buffer[2] = char(mycrc >> 16); 00661 buffer[3] = char(mycrc >> 24); 00662 00663 int mysize1 = it.current()->compressedSize(); 00664 buffer[4] = char(mysize1); // compressed file size, at headerStart+18 00665 buffer[5] = char(mysize1 >> 8); 00666 buffer[6] = char(mysize1 >> 16); 00667 buffer[7] = char(mysize1 >> 24); 00668 00669 int myusize = it.current()->size(); 00670 buffer[8] = char(myusize); // uncompressed file size, at headerStart+22 00671 buffer[9] = char(myusize >> 8); 00672 buffer[10] = char(myusize >> 16); 00673 buffer[11] = char(myusize >> 24); 00674 00675 if ( device()->writeBlock( buffer, 12 ) != 12 ) 00676 return false; 00677 } 00678 device()->at( atbackup ); 00679 00680 for ( it.toFirst(); it.current() ; ++it ) 00681 { 00682 //kdDebug(7040) << "closearchive: filename: " << it.current()->path() 00683 // << " encoding: "<< it.current()->encoding() << endl; 00684 00685 QCString path = QFile::encodeName(it.current()->path()); 00686 00687 const int extra_field_len = 9; 00688 int bufferSize = extra_field_len + path.length() + 46; 00689 char* buffer = new char[ bufferSize ]; 00690 00691 memset(buffer, 0, 46); // zero is a nice default for most header fields 00692 00693 const char head[] = 00694 { 00695 'P', 'K', 1, 2, // central file header signature 00696 0x14, 3, // version made by (3 == UNIX) 00697 0x14, 0 // version needed to extract 00698 }; 00699 00700 // I do not know why memcpy is not working here 00701 //memcpy(buffer, head, sizeof(head)); 00702 qmemmove(buffer, head, sizeof(head)); 00703 00704 buffer[ 10 ] = char(it.current()->encoding()); // compression method 00705 buffer[ 11 ] = char(it.current()->encoding() >> 8); 00706 00707 transformToMsDos( it.current()->datetime(), &buffer[ 12 ] ); 00708 00709 uLong mycrc = it.current()->crc32(); 00710 buffer[ 16 ] = char(mycrc); // crc checksum 00711 buffer[ 17 ] = char(mycrc >> 8); 00712 buffer[ 18 ] = char(mycrc >> 16); 00713 buffer[ 19 ] = char(mycrc >> 24); 00714 00715 int mysize1 = it.current()->compressedSize(); 00716 buffer[ 20 ] = char(mysize1); // compressed file size 00717 buffer[ 21 ] = char(mysize1 >> 8); 00718 buffer[ 22 ] = char(mysize1 >> 16); 00719 buffer[ 23 ] = char(mysize1 >> 24); 00720 00721 int mysize = it.current()->size(); 00722 buffer[ 24 ] = char(mysize); // uncompressed file size 00723 buffer[ 25 ] = char(mysize >> 8); 00724 buffer[ 26 ] = char(mysize >> 16); 00725 buffer[ 27 ] = char(mysize >> 24); 00726 00727 buffer[ 28 ] = char(it.current()->path().length()); // filename length 00728 buffer[ 29 ] = char(it.current()->path().length() >> 8); 00729 00730 buffer[ 30 ] = char(extra_field_len); 00731 buffer[ 31 ] = char(extra_field_len >> 8); 00732 00733 buffer[ 40 ] = char(it.current()->permissions()); 00734 buffer[ 41 ] = char(it.current()->permissions() >> 8); 00735 00736 int myhst = it.current()->headerStart(); 00737 buffer[ 42 ] = char(myhst); //relative offset of local header 00738 buffer[ 43 ] = char(myhst >> 8); 00739 buffer[ 44 ] = char(myhst >> 16); 00740 buffer[ 45 ] = char(myhst >> 24); 00741 00742 // file name 00743 strncpy( buffer + 46, path, path.length() ); 00744 //kdDebug(7040) << "closearchive length to write: " << bufferSize << endl; 00745 00746 // extra field 00747 char *extfield = buffer + 46 + path.length(); 00748 extfield[0] = 'U'; 00749 extfield[1] = 'T'; 00750 extfield[2] = 5; 00751 extfield[3] = 0; 00752 extfield[4] = 1 | 2 | 4; // specify flags from local field 00753 // (unless I misread the spec) 00754 // provide only modification time 00755 unsigned long time = (unsigned long)it.current()->date(); 00756 extfield[5] = char(time); 00757 extfield[6] = char(time >> 8); 00758 extfield[7] = char(time >> 16); 00759 extfield[8] = char(time >> 24); 00760 00761 crc = crc32(crc, (Bytef *)buffer, bufferSize ); 00762 bool ok = ( device()->writeBlock( buffer, bufferSize ) == bufferSize ); 00763 delete[] buffer; 00764 if ( !ok ) 00765 return false; 00766 } 00767 Q_LONG centraldirendoffset = device()->at(); 00768 //kdDebug(7040) << "closearchive: centraldirendoffset: " << centraldirendoffset << endl; 00769 //kdDebug(7040) << "closearchive: device()->at(): " << device()->at() << endl; 00770 00771 //write end of central dir record. 00772 buffer[ 0 ] = 'P'; //end of central dir signature 00773 buffer[ 1 ] = 'K'; 00774 buffer[ 2 ] = 5; 00775 buffer[ 3 ] = 6; 00776 00777 buffer[ 4 ] = 0; // number of this disk 00778 buffer[ 5 ] = 0; 00779 00780 buffer[ 6 ] = 0; // number of disk with start of central dir 00781 buffer[ 7 ] = 0; 00782 00783 int count = d->m_fileList.count(); 00784 //kdDebug(7040) << "number of files (count): " << count << endl; 00785 00786 00787 buffer[ 8 ] = char(count); // total number of entries in central dir of 00788 buffer[ 9 ] = char(count >> 8); // this disk 00789 00790 buffer[ 10 ] = buffer[ 8 ]; // total number of entries in the central dir 00791 buffer[ 11 ] = buffer[ 9 ]; 00792 00793 int cdsize = centraldirendoffset - centraldiroffset; 00794 buffer[ 12 ] = char(cdsize); // size of the central dir 00795 buffer[ 13 ] = char(cdsize >> 8); 00796 buffer[ 14 ] = char(cdsize >> 16); 00797 buffer[ 15 ] = char(cdsize >> 24); 00798 00799 //kdDebug(7040) << "end : centraldiroffset: " << centraldiroffset << endl; 00800 //kdDebug(7040) << "end : centraldirsize: " << cdsize << endl; 00801 00802 buffer[ 16 ] = char(centraldiroffset); // central dir offset 00803 buffer[ 17 ] = char(centraldiroffset >> 8); 00804 buffer[ 18 ] = char(centraldiroffset >> 16); 00805 buffer[ 19 ] = char(centraldiroffset >> 24); 00806 00807 buffer[ 20 ] = 0; //zipfile comment length 00808 buffer[ 21 ] = 0; 00809 00810 if ( device()->writeBlock( buffer, 22 ) != 22 ) 00811 return false; 00812 00813 //kdDebug(7040) << "kzip.cpp reached." << endl; 00814 return true; 00815 } 00816 00817 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0 00818 bool KZip::writeFile( const QString& name, const QString& user, const QString& group, uint size, const char* data ) 00819 { 00820 mode_t mode = 0100644; 00821 time_t the_time = time(0); 00822 return KArchive::writeFile( name, user, group, size, mode, the_time, 00823 the_time, the_time, data ); 00824 } 00825 00826 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0 00827 bool KZip::writeFile( const QString& name, const QString& user, 00828 const QString& group, uint size, mode_t perm, 00829 time_t atime, time_t mtime, time_t ctime, 00830 const char* data ) { 00831 return KArchive::writeFile(name, user, group, size, perm, atime, mtime, 00832 ctime, data); 00833 } 00834 00835 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0 00836 bool KZip::prepareWriting( const QString& name, const QString& user, const QString& group, uint size ) 00837 { 00838 mode_t dflt_perm = 0100644; 00839 time_t the_time = time(0); 00840 return prepareWriting(name,user,group,size,dflt_perm, 00841 the_time,the_time,the_time); 00842 } 00843 00844 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0 00845 bool KZip::prepareWriting(const QString& name, const QString& user, 00846 const QString& group, uint size, mode_t perm, 00847 time_t atime, time_t mtime, time_t ctime) { 00848 return KArchive::prepareWriting(name,user,group,size,perm,atime,mtime,ctime); 00849 } 00850 00851 bool KZip::prepareWriting_impl(const QString &name, const QString &user, 00852 const QString &group, uint /*size*/, mode_t perm, 00853 time_t atime, time_t mtime, time_t ctime) { 00854 //kdDebug(7040) << "prepareWriting reached." << endl; 00855 if ( !isOpened() ) 00856 { 00857 qWarning( "KZip::writeFile: You must open the zip file before writing to it\n"); 00858 return false; 00859 } 00860 00861 if ( ! ( mode() & IO_WriteOnly ) ) // accept WriteOnly and ReadWrite 00862 { 00863 qWarning( "KZip::writeFile: You must open the zip file for writing\n"); 00864 return false; 00865 } 00866 00867 // set right offset in zip. 00868 if ( !device()->at( d->m_offset ) ) { 00869 kdWarning(7040) << "prepareWriting_impl: cannot seek in ZIP file. Disk full?" << endl; 00870 return false; 00871 } 00872 00873 // delete entries in the filelist with the same filename as the one we want 00874 // to save, so that we donīt have duplicate file entries when viewing the zip 00875 // with konqi... 00876 // CAUTION: the old file itself is still in the zip and won't be removed !!! 00877 QPtrListIterator<KZipFileEntry> it( d->m_fileList ); 00878 00879 //kdDebug(7040) << "filename to write: " << name <<endl; 00880 for ( ; it.current() ; ++it ) 00881 { 00882 //kdDebug(7040) << "prepfilename: " << it.current()->path() <<endl; 00883 if (name == it.current()->path() ) 00884 { 00885 //kdDebug(7040) << "removing following entry: " << it.current()->path() <<endl; 00886 d->m_fileList.remove(); 00887 } 00888 00889 } 00890 // Find or create parent dir 00891 KArchiveDirectory* parentDir = rootDir(); 00892 QString fileName( name ); 00893 int i = name.findRev( '/' ); 00894 if ( i != -1 ) 00895 { 00896 QString dir = name.left( i ); 00897 fileName = name.mid( i + 1 ); 00898 //kdDebug(7040) << "KZip::prepareWriting ensuring " << dir << " exists. fileName=" << fileName << endl; 00899 parentDir = findOrCreate( dir ); 00900 } 00901 00902 // construct a KZipFileEntry and add it to list 00903 KZipFileEntry * e = new KZipFileEntry( this, fileName, perm, mtime, user, group, QString::null, 00904 name, device()->at() + 30 + name.length(), // start 00905 0 /*size unknown yet*/, d->m_compression, 0 /*csize unknown yet*/ ); 00906 e->setHeaderStart( device()->at() ); 00907 //kdDebug(7040) << "wrote file start: " << e->position() << " name: " << name << endl; 00908 parentDir->addEntry( e ); 00909 00910 d->m_currentFile = e; 00911 d->m_fileList.append( e ); 00912 00913 int extra_field_len = 0; 00914 if ( d->m_extraField == ModificationTime ) 00915 extra_field_len = 17; // value also used in doneWriting() 00916 00917 // write out zip header 00918 QCString encodedName = QFile::encodeName(name); 00919 int bufferSize = extra_field_len + encodedName.length() + 30; 00920 //kdDebug(7040) << "KZip::prepareWriting bufferSize=" << bufferSize << endl; 00921 char* buffer = new char[ bufferSize ]; 00922 00923 buffer[ 0 ] = 'P'; //local file header signature 00924 buffer[ 1 ] = 'K'; 00925 buffer[ 2 ] = 3; 00926 buffer[ 3 ] = 4; 00927 00928 buffer[ 4 ] = 0x14; // version needed to extract 00929 buffer[ 5 ] = 0; 00930 00931 buffer[ 6 ] = 0; // general purpose bit flag 00932 buffer[ 7 ] = 0; 00933 00934 buffer[ 8 ] = char(e->encoding()); // compression method 00935 buffer[ 9 ] = char(e->encoding() >> 8); 00936 00937 transformToMsDos( e->datetime(), &buffer[ 10 ] ); 00938 00939 buffer[ 14 ] = 'C'; //dummy crc 00940 buffer[ 15 ] = 'R'; 00941 buffer[ 16 ] = 'C'; 00942 buffer[ 17 ] = 'q'; 00943 00944 buffer[ 18 ] = 'C'; //compressed file size 00945 buffer[ 19 ] = 'S'; 00946 buffer[ 20 ] = 'I'; 00947 buffer[ 21 ] = 'Z'; 00948 00949 buffer[ 22 ] = 'U'; //uncompressed file size 00950 buffer[ 23 ] = 'S'; 00951 buffer[ 24 ] = 'I'; 00952 buffer[ 25 ] = 'Z'; 00953 00954 buffer[ 26 ] = (uchar)(encodedName.length()); //filename length 00955 buffer[ 27 ] = (uchar)(encodedName.length() >> 8); 00956 00957 buffer[ 28 ] = (uchar)(extra_field_len); // extra field length 00958 buffer[ 29 ] = (uchar)(extra_field_len >> 8); 00959 00960 // file name 00961 strncpy( buffer + 30, encodedName, encodedName.length() ); 00962 00963 // extra field 00964 if ( d->m_extraField == ModificationTime ) 00965 { 00966 char *extfield = buffer + 30 + encodedName.length(); 00967 // "Extended timestamp" header (0x5455) 00968 extfield[0] = 'U'; 00969 extfield[1] = 'T'; 00970 extfield[2] = 13; // data size 00971 extfield[3] = 0; 00972 extfield[4] = 1 | 2 | 4; // contains mtime, atime, ctime 00973 00974 extfield[5] = char(mtime); 00975 extfield[6] = char(mtime >> 8); 00976 extfield[7] = char(mtime >> 16); 00977 extfield[8] = char(mtime >> 24); 00978 00979 extfield[9] = char(atime); 00980 extfield[10] = char(atime >> 8); 00981 extfield[11] = char(atime >> 16); 00982 extfield[12] = char(atime >> 24); 00983 00984 extfield[13] = char(ctime); 00985 extfield[14] = char(ctime >> 8); 00986 extfield[15] = char(ctime >> 16); 00987 extfield[16] = char(ctime >> 24); 00988 } 00989 00990 // Write header 00991 bool b = (device()->writeBlock( buffer, bufferSize ) == bufferSize ); 00992 d->m_crc = 0L; 00993 delete[] buffer; 00994 00995 Q_ASSERT( b ); 00996 if (!b) 00997 return false; 00998 00999 // Prepare device for writing the data 01000 // Either device() if no compression, or a KFilterDev to compress 01001 if ( d->m_compression == 0 ) { 01002 d->m_currentDev = device(); 01003 return true; 01004 } 01005 01006 d->m_currentDev = KFilterDev::device( device(), "application/x-gzip", false ); 01007 Q_ASSERT( d->m_currentDev ); 01008 if ( !d->m_currentDev ) 01009 return false; // ouch 01010 static_cast<KFilterDev *>(d->m_currentDev)->setSkipHeaders(); // Just zlib, not gzip 01011 01012 b = d->m_currentDev->open( IO_WriteOnly ); 01013 Q_ASSERT( b ); 01014 return b; 01015 } 01016 01017 bool KZip::doneWriting( uint size ) 01018 { 01019 if ( d->m_currentFile->encoding() == 8 ) { 01020 // Finish 01021 (void)d->m_currentDev->writeBlock( 0, 0 ); 01022 delete d->m_currentDev; 01023 } 01024 // If 0, d->m_currentDev was device() - don't delete ;) 01025 d->m_currentDev = 0L; 01026 01027 Q_ASSERT( d->m_currentFile ); 01028 //kdDebug(7040) << "donewriting reached." << endl; 01029 //kdDebug(7040) << "filename: " << d->m_currentFile->path() << endl; 01030 //kdDebug(7040) << "getpos (at): " << device()->at() << endl; 01031 d->m_currentFile->setSize(size); 01032 int extra_field_len = 0; 01033 if ( d->m_extraField == ModificationTime ) 01034 extra_field_len = 17; // value also used in doneWriting() 01035 01036 int csize = device()->at() - 01037 d->m_currentFile->headerStart() - 30 - 01038 d->m_currentFile->path().length() - extra_field_len; 01039 d->m_currentFile->setCompressedSize(csize); 01040 //kdDebug(7040) << "usize: " << d->m_currentFile->size() << endl; 01041 //kdDebug(7040) << "csize: " << d->m_currentFile->compressedSize() << endl; 01042 //kdDebug(7040) << "headerstart: " << d->m_currentFile->headerStart() << endl; 01043 01044 //kdDebug(7040) << "crc: " << d->m_crc << endl; 01045 d->m_currentFile->setCRC32( d->m_crc ); 01046 01047 d->m_currentFile = 0L; 01048 01049 // update saved offset for appending new files 01050 d->m_offset = device()->at(); 01051 return true; 01052 } 01053 01054 bool KZip::writeSymLink(const QString &name, const QString &target, 01055 const QString &user, const QString &group, 01056 mode_t perm, time_t atime, time_t mtime, time_t ctime) { 01057 return KArchive::writeSymLink(name,target,user,group,perm,atime,mtime,ctime); 01058 } 01059 01060 bool KZip::writeSymLink_impl(const QString &name, const QString &target, 01061 const QString &user, const QString &group, 01062 mode_t perm, time_t atime, time_t mtime, time_t ctime) { 01063 01064 // reassure that symlink flag is set, otherwise strange things happen on 01065 // extraction 01066 perm |= S_IFLNK; 01067 Compression c = compression(); 01068 setCompression(NoCompression); // link targets are never compressed 01069 01070 if (!prepareWriting(name, user, group, 0, perm, atime, mtime, ctime)) { 01071 kdWarning() << "KZip::writeFile prepareWriting failed" << endl; 01072 setCompression(c); 01073 return false; 01074 } 01075 01076 QCString symlink_target = QFile::encodeName(target); 01077 if (!writeData(symlink_target, symlink_target.length())) { 01078 kdWarning() << "KZip::writeFile writeData failed" << endl; 01079 setCompression(c); 01080 return false; 01081 } 01082 01083 if (!doneWriting(symlink_target.length())) { 01084 kdWarning() << "KZip::writeFile doneWriting failed" << endl; 01085 setCompression(c); 01086 return false; 01087 } 01088 01089 setCompression(c); 01090 return true; 01091 } 01092 01093 void KZip::virtual_hook( int id, void* data ) 01094 { 01095 switch (id) { 01096 case VIRTUAL_WRITE_DATA: { 01097 WriteDataParams* params = reinterpret_cast<WriteDataParams *>(data); 01098 params->retval = writeData_impl( params->data, params->size ); 01099 break; 01100 } 01101 case VIRTUAL_WRITE_SYMLINK: { 01102 WriteSymlinkParams *params = reinterpret_cast<WriteSymlinkParams *>(data); 01103 params->retval = writeSymLink_impl(*params->name,*params->target, 01104 *params->user,*params->group,params->perm, 01105 params->atime,params->mtime,params->ctime); 01106 break; 01107 } 01108 case VIRTUAL_PREPARE_WRITING: { 01109 PrepareWritingParams *params = reinterpret_cast<PrepareWritingParams *>(data); 01110 params->retval = prepareWriting_impl(*params->name,*params->user, 01111 *params->group,params->size,params->perm, 01112 params->atime,params->mtime,params->ctime); 01113 break; 01114 } 01115 default: 01116 KArchive::virtual_hook( id, data ); 01117 }/*end switch*/ 01118 } 01119 01120 // made virtual using virtual_hook 01121 bool KZip::writeData(const char * c, uint i) 01122 { 01123 return KArchive::writeData( c, i ); 01124 } 01125 01126 bool KZip::writeData_impl(const char * c, uint i) 01127 { 01128 Q_ASSERT( d->m_currentFile ); 01129 Q_ASSERT( d->m_currentDev ); 01130 if (!d->m_currentFile || !d->m_currentDev) 01131 return false; 01132 01133 // crc to be calculated over uncompressed stuff... 01134 // and they didn't mention it in their docs... 01135 d->m_crc = crc32(d->m_crc, (const Bytef *) c , i); 01136 01137 Q_LONG written = d->m_currentDev->writeBlock( c, i ); 01138 //kdDebug(7040) << "KZip::writeData wrote " << i << " bytes." << endl; 01139 return written == (Q_LONG)i; 01140 } 01141 01142 void KZip::setCompression( Compression c ) 01143 { 01144 d->m_compression = ( c == NoCompression ) ? 0 : 8; 01145 } 01146 01147 KZip::Compression KZip::compression() const 01148 { 01149 return ( d->m_compression == 8 ) ? DeflateCompression : NoCompression; 01150 } 01151 01152 void KZip::setExtraField( ExtraField ef ) 01153 { 01154 d->m_extraField = ef; 01155 } 01156 01157 KZip::ExtraField KZip::extraField() const 01158 { 01159 return d->m_extraField; 01160 } 01161 01163 01164 QByteArray KZipFileEntry::data() const 01165 { 01166 QIODevice* dev = device(); 01167 QByteArray arr; 01168 if ( dev ) { 01169 arr = dev->readAll(); 01170 delete dev; 01171 } 01172 return arr; 01173 } 01174 01175 QIODevice* KZipFileEntry::device() const 01176 { 01177 //kdDebug(7040) << "KZipFileEntry::device creating iodevice limited to pos=" << position() << ", csize=" << compressedSize() << endl; 01178 // Limit the reading to the appropriate part of the underlying device (e.g. file) 01179 KLimitedIODevice* limitedDev = new KLimitedIODevice( archive()->device(), position(), compressedSize() ); 01180 if ( encoding() == 0 || compressedSize() == 0 ) // no compression (or even no data) 01181 return limitedDev; 01182 01183 if ( encoding() == 8 ) 01184 { 01185 // On top of that, create a device that uncompresses the zlib data 01186 QIODevice* filterDev = KFilterDev::device( limitedDev, "application/x-gzip" ); 01187 if ( !filterDev ) 01188 return 0L; // ouch 01189 static_cast<KFilterDev *>(filterDev)->setSkipHeaders(); // Just zlib, not gzip 01190 bool b = filterDev->open( IO_ReadOnly ); 01191 Q_ASSERT( b ); 01192 return filterDev; 01193 } 01194 01195 kdError() << "This zip file contains files compressed with method " 01196 << encoding() <<", this method is currently not supported by KZip," 01197 <<" please use a command-line tool to handle this file." << endl; 01198 return 0L; 01199 } 01200
KDE Logo
This file is part of the documentation for kio Library Version 3.2.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Mon Aug 30 22:54:43 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003