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 | 0111;
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.left( pos ) );
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         device()->at( it.current()->headerStart() + 14 );
00652     //kdDebug(7040) << "closearchive setcrcandcsize: filename: "
00653     //    << it.current()->path()
00654     //    << " encoding: "<< it.current()->encoding() << endl;
00655 
00656         uLong mycrc = it.current()->crc32();
00657         buffer[0] = char(mycrc); // crc checksum, at headerStart+14
00658         buffer[1] = char(mycrc >> 8);
00659         buffer[2] = char(mycrc >> 16);
00660         buffer[3] = char(mycrc >> 24);
00661 
00662         int mysize1 = it.current()->compressedSize();
00663         buffer[4] = char(mysize1); // compressed file size, at headerStart+18
00664         buffer[5] = char(mysize1 >> 8);
00665         buffer[6] = char(mysize1 >> 16);
00666         buffer[7] = char(mysize1 >> 24);
00667 
00668         int myusize = it.current()->size();
00669         buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
00670         buffer[9] = char(myusize >> 8);
00671         buffer[10] = char(myusize >> 16);
00672         buffer[11] = char(myusize >> 24);
00673 
00674         device()->writeBlock( buffer, 12 );
00675     }
00676     device()->at( atbackup );
00677 
00678     for ( it.toFirst(); it.current() ; ++it )
00679     {
00680         //kdDebug(7040) << "closearchive: filename: " << it.current()->path()
00681         //              << " encoding: "<< it.current()->encoding() << endl;
00682 
00683         QCString path = QFile::encodeName(it.current()->path());
00684 
00685     const int extra_field_len = 9;
00686         int bufferSize = extra_field_len + path.length() + 46;
00687         char* buffer = new char[ bufferSize ];
00688 
00689         memset(buffer, 0, 46); // zero is a nice default for most header fields
00690 
00691         const char head[] =
00692         {
00693             'P', 'K', 1, 2, // central file header signature
00694             0x14, 3,        // version made by (3 == UNIX)
00695             0x14, 0         // version needed to extract
00696         };
00697 
00698     // I do not know why memcpy is not working here
00699         //memcpy(buffer, head, sizeof(head));
00700         qmemmove(buffer, head, sizeof(head));
00701 
00702         buffer[ 10 ] = char(it.current()->encoding()); // compression method
00703         buffer[ 11 ] = char(it.current()->encoding() >> 8);
00704 
00705         transformToMsDos( it.current()->datetime(), &buffer[ 12 ] );
00706 
00707         uLong mycrc = it.current()->crc32();
00708         buffer[ 16 ] = char(mycrc); // crc checksum
00709         buffer[ 17 ] = char(mycrc >> 8);
00710         buffer[ 18 ] = char(mycrc >> 16);
00711         buffer[ 19 ] = char(mycrc >> 24);
00712 
00713         int mysize1 = it.current()->compressedSize();
00714         buffer[ 20 ] = char(mysize1); // compressed file size
00715         buffer[ 21 ] = char(mysize1 >> 8);
00716         buffer[ 22 ] = char(mysize1 >> 16);
00717         buffer[ 23 ] = char(mysize1 >> 24);
00718 
00719         int mysize = it.current()->size();
00720         buffer[ 24 ] = char(mysize); // uncompressed file size
00721         buffer[ 25 ] = char(mysize >> 8);
00722         buffer[ 26 ] = char(mysize >> 16);
00723         buffer[ 27 ] = char(mysize >> 24);
00724 
00725         buffer[ 28 ] = char(it.current()->path().length()); // filename length
00726         buffer[ 29 ] = char(it.current()->path().length() >> 8);
00727 
00728     buffer[ 30 ] = char(extra_field_len);
00729     buffer[ 31 ] = char(extra_field_len >> 8);
00730 
00731     buffer[ 40 ] = char(it.current()->permissions());
00732     buffer[ 41 ] = char(it.current()->permissions() >> 8);
00733 
00734         int myhst = it.current()->headerStart();
00735         buffer[ 42 ] = char(myhst); //relative offset of local header
00736         buffer[ 43 ] = char(myhst >> 8);
00737         buffer[ 44 ] = char(myhst >> 16);
00738         buffer[ 45 ] = char(myhst >> 24);
00739 
00740         // file name
00741         strncpy( buffer + 46, path, path.length() );
00742     //kdDebug(7040) << "closearchive length to write: " << bufferSize << endl;
00743 
00744     // extra field
00745     char *extfield = buffer + 46 + path.length();
00746     extfield[0] = 'U';
00747     extfield[1] = 'T';
00748     extfield[2] = 5;
00749     extfield[3] = 0;
00750     extfield[4] = 1 | 2 | 4;    // specify flags from local field
00751                     // (unless I misread the spec)
00752     // provide only modification time
00753     unsigned long time = (unsigned long)it.current()->date();
00754     extfield[5] = char(time);
00755     extfield[6] = char(time >> 8);
00756     extfield[7] = char(time >> 16);
00757     extfield[8] = char(time >> 24);
00758 
00759         crc = crc32(crc, (Bytef *)buffer, bufferSize );
00760         device()->writeBlock( buffer, bufferSize );
00761         delete[] buffer;
00762     }
00763     Q_LONG centraldirendoffset = device()->at();
00764     //kdDebug(7040) << "closearchive: centraldirendoffset: " << centraldirendoffset << endl;
00765     //kdDebug(7040) << "closearchive: device()->at(): " << device()->at() << endl;
00766 
00767     //write end of central dir record.
00768     buffer[ 0 ] = 'P'; //end of central dir signature
00769     buffer[ 1 ] = 'K';
00770     buffer[ 2 ] = 5;
00771     buffer[ 3 ] = 6;
00772 
00773     buffer[ 4 ] = 0; // number of this disk
00774     buffer[ 5 ] = 0;
00775 
00776     buffer[ 6 ] = 0; // number of disk with start of central dir
00777     buffer[ 7 ] = 0;
00778 
00779     int count = d->m_fileList.count();
00780     //kdDebug(7040) << "number of files (count): " << count << endl;
00781 
00782 
00783     buffer[ 8 ] = char(count); // total number of entries in central dir of
00784     buffer[ 9 ] = char(count >> 8); // this disk
00785 
00786     buffer[ 10 ] = buffer[ 8 ]; // total number of entries in the central dir
00787     buffer[ 11 ] = buffer[ 9 ];
00788 
00789     int cdsize = centraldirendoffset - centraldiroffset;
00790     buffer[ 12 ] = char(cdsize); // size of the central dir
00791     buffer[ 13 ] = char(cdsize >> 8);
00792     buffer[ 14 ] = char(cdsize >> 16);
00793     buffer[ 15 ] = char(cdsize >> 24);
00794 
00795     //kdDebug(7040) << "end : centraldiroffset: " << centraldiroffset << endl;
00796     //kdDebug(7040) << "end : centraldirsize: " << cdsize << endl;
00797 
00798     buffer[ 16 ] = char(centraldiroffset); // central dir offset
00799     buffer[ 17 ] = char(centraldiroffset >> 8);
00800     buffer[ 18 ] = char(centraldiroffset >> 16);
00801     buffer[ 19 ] = char(centraldiroffset >> 24);
00802 
00803     buffer[ 20 ] = 0; //zipfile comment length
00804     buffer[ 21 ] = 0;
00805 
00806     device()->writeBlock( buffer, 22);
00807 
00808     //kdDebug(7040) << "kzip.cpp reached." << endl;
00809     return true;
00810 }
00811 
00812 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0
00813 bool KZip::writeFile( const QString& name, const QString& user, const QString& group, uint size, const char* data )
00814 {
00815     mode_t mode = 0100644;
00816     time_t the_time = time(0);
00817     return KArchive::writeFile( name, user, group, size, mode, the_time,
00818                 the_time, the_time, data );
00819 }
00820 
00821 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0
00822 bool KZip::writeFile( const QString& name, const QString& user,
00823                         const QString& group, uint size, mode_t perm,
00824                         time_t atime, time_t mtime, time_t ctime,
00825                         const char* data ) {
00826   return KArchive::writeFile(name, user, group, size, perm, atime, mtime,
00827             ctime, data);
00828 }
00829 
00830 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0
00831 bool KZip::prepareWriting( const QString& name, const QString& user, const QString& group, uint size )
00832 {
00833     mode_t dflt_perm = 0100644;
00834     time_t the_time = time(0);
00835     return prepareWriting(name,user,group,size,dflt_perm,
00836             the_time,the_time,the_time);
00837 }
00838 
00839 // Doesn't need to be reimplemented anymore. Remove for KDE-4.0
00840 bool KZip::prepareWriting(const QString& name, const QString& user,
00841                 const QString& group, uint size, mode_t perm,
00842                 time_t atime, time_t mtime, time_t ctime) {
00843   return KArchive::prepareWriting(name,user,group,size,perm,atime,mtime,ctime);
00844 }
00845 
00846 bool KZip::prepareWriting_impl(const QString &name, const QString &user,
00847                 const QString &group, uint /*size*/, mode_t perm,
00848                 time_t atime, time_t mtime, time_t ctime) {
00849     //kdDebug(7040) << "prepareWriting reached." << endl;
00850     if ( !isOpened() )
00851     {
00852         qWarning( "KZip::writeFile: You must open the zip file before writing to it\n");
00853         return false;
00854     }
00855 
00856     if ( ! ( mode() & IO_WriteOnly ) ) // accept WriteOnly and ReadWrite
00857     {
00858         qWarning( "KZip::writeFile: You must open the zip file for writing\n");
00859         return false;
00860     }
00861 
00862     // set right offset in zip.
00863     device()->at( d->m_offset );
00864 
00865     // delete entries in the filelist with the same filename as the one we want
00866     // to save, so that we donīt have duplicate file entries when viewing the zip
00867     // with konqi...
00868     // CAUTION: the old file itself is still in the zip and won't be removed !!!
00869     QPtrListIterator<KZipFileEntry> it( d->m_fileList );
00870 
00871     //kdDebug(7040) << "filename to write: " << name <<endl;
00872     for ( ; it.current() ; ++it )
00873     {
00874         //kdDebug(7040) << "prepfilename: " << it.current()->path() <<endl;
00875         if (name == it.current()->path() )
00876         {
00877             //kdDebug(7040) << "removing following entry: " << it.current()->path() <<endl;
00878             d->m_fileList.remove();
00879         }
00880 
00881     }
00882     // Find or create parent dir
00883     KArchiveDirectory* parentDir = rootDir();
00884     QString fileName( name );
00885     int i = name.findRev( '/' );
00886     if ( i != -1 )
00887     {
00888         QString dir = name.left( i );
00889         fileName = name.mid( i + 1 );
00890         //kdDebug(7040) << "KZip::prepareWriting ensuring " << dir << " exists. fileName=" << fileName << endl;
00891         parentDir = findOrCreate( dir );
00892     }
00893 
00894     // construct a KZipFileEntry and add it to list
00895     KZipFileEntry * e = new KZipFileEntry( this, fileName, perm, mtime, user, group, QString::null,
00896                                            name, device()->at() + 30 + name.length(), // start
00897                                            0 /*size unknown yet*/, d->m_compression, 0 /*csize unknown yet*/ );
00898     e->setHeaderStart( device()->at() );
00899     //kdDebug(7040) << "wrote file start: " << e->position() << " name: " << name << endl;
00900     parentDir->addEntry( e );
00901 
00902     d->m_currentFile = e;
00903     d->m_fileList.append( e );
00904 
00905     int extra_field_len = 0;
00906     if ( d->m_extraField == ModificationTime )
00907         extra_field_len = 17;   // value also used in doneWriting()
00908 
00909     // write out zip header
00910     QCString encodedName = QFile::encodeName(name);
00911     int bufferSize = extra_field_len + encodedName.length() + 30;
00912     //kdDebug(7040) << "KZip::prepareWriting bufferSize=" << bufferSize << endl;
00913     char* buffer = new char[ bufferSize ];
00914 
00915     buffer[ 0 ] = 'P'; //local file header signature
00916     buffer[ 1 ] = 'K';
00917     buffer[ 2 ] = 3;
00918     buffer[ 3 ] = 4;
00919 
00920     buffer[ 4 ] = 0x14; // version needed to extract
00921     buffer[ 5 ] = 0;
00922 
00923     buffer[ 6 ] = 0; // general purpose bit flag
00924     buffer[ 7 ] = 0;
00925 
00926     buffer[ 8 ] = char(e->encoding()); // compression method
00927     buffer[ 9 ] = char(e->encoding() >> 8);
00928 
00929     transformToMsDos( e->datetime(), &buffer[ 10 ] );
00930 
00931     buffer[ 14 ] = 'C'; //dummy crc
00932     buffer[ 15 ] = 'R';
00933     buffer[ 16 ] = 'C';
00934     buffer[ 17 ] = 'q';
00935 
00936     buffer[ 18 ] = 'C'; //compressed file size
00937     buffer[ 19 ] = 'S';
00938     buffer[ 20 ] = 'I';
00939     buffer[ 21 ] = 'Z';
00940 
00941     buffer[ 22 ] = 'U'; //uncompressed file size
00942     buffer[ 23 ] = 'S';
00943     buffer[ 24 ] = 'I';
00944     buffer[ 25 ] = 'Z';
00945 
00946     buffer[ 26 ] = (uchar)(encodedName.length()); //filename length
00947     buffer[ 27 ] = (uchar)(encodedName.length() >> 8);
00948 
00949     buffer[ 28 ] = (uchar)(extra_field_len); // extra field length
00950     buffer[ 29 ] = (uchar)(extra_field_len >> 8);
00951 
00952     // file name
00953     strncpy( buffer + 30, encodedName, encodedName.length() );
00954 
00955     // extra field
00956     if ( d->m_extraField == ModificationTime )
00957     {
00958         char *extfield = buffer + 30 + encodedName.length();
00959         // "Extended timestamp" header (0x5455)
00960         extfield[0] = 'U';
00961         extfield[1] = 'T';
00962         extfield[2] = 13; // data size
00963         extfield[3] = 0;
00964         extfield[4] = 1 | 2 | 4;    // contains mtime, atime, ctime
00965 
00966         extfield[5] = char(mtime);
00967         extfield[6] = char(mtime >> 8);
00968         extfield[7] = char(mtime >> 16);
00969         extfield[8] = char(mtime >> 24);
00970 
00971         extfield[9] = char(atime);
00972         extfield[10] = char(atime >> 8);
00973         extfield[11] = char(atime >> 16);
00974         extfield[12] = char(atime >> 24);
00975 
00976         extfield[13] = char(ctime);
00977         extfield[14] = char(ctime >> 8);
00978         extfield[15] = char(ctime >> 16);
00979         extfield[16] = char(ctime >> 24);
00980     }
00981 
00982     // Write header
00983     bool b = (device()->writeBlock( buffer, bufferSize ) == bufferSize );
00984     d->m_crc = 0L;
00985     delete[] buffer;
00986 
00987     Q_ASSERT( b );
00988     if (!b)
00989         return false;
00990 
00991     // Prepare device for writing the data
00992     // Either device() if no compression, or a KFilterDev to compress
00993     if ( d->m_compression == 0 ) {
00994         d->m_currentDev = device();
00995         return true;
00996     }
00997 
00998     d->m_currentDev = KFilterDev::device( device(), "application/x-gzip", false );
00999     Q_ASSERT( d->m_currentDev );
01000     if ( !d->m_currentDev )
01001         return false; // ouch
01002     static_cast<KFilterDev *>(d->m_currentDev)->setSkipHeaders(); // Just zlib, not gzip
01003 
01004     b = d->m_currentDev->open( IO_WriteOnly );
01005     Q_ASSERT( b );
01006     return b;
01007 }
01008 
01009 bool KZip::doneWriting( uint size )
01010 {
01011     if ( d->m_currentFile->encoding() == 8 ) {
01012         // Finish
01013         (void)d->m_currentDev->writeBlock( 0, 0 );
01014         delete d->m_currentDev;
01015     }
01016     // If 0, d->m_currentDev was device() - don't delete ;)
01017     d->m_currentDev = 0L;
01018 
01019     Q_ASSERT( d->m_currentFile );
01020     //kdDebug(7040) << "donewriting reached." << endl;
01021     //kdDebug(7040) << "filename: " << d->m_currentFile->path() << endl;
01022     //kdDebug(7040) << "getpos (at): " << device()->at() << endl;
01023     d->m_currentFile->setSize(size);
01024     int extra_field_len = 0;
01025     if ( d->m_extraField == ModificationTime )
01026         extra_field_len = 17;   // value also used in doneWriting()
01027 
01028     int csize = device()->at() -
01029         d->m_currentFile->headerStart() - 30 -
01030         d->m_currentFile->path().length() - extra_field_len;
01031     d->m_currentFile->setCompressedSize(csize);
01032     //kdDebug(7040) << "usize: " << d->m_currentFile->size() << endl;
01033     //kdDebug(7040) << "csize: " << d->m_currentFile->compressedSize() << endl;
01034     //kdDebug(7040) << "headerstart: " << d->m_currentFile->headerStart() << endl;
01035 
01036     //kdDebug(7040) << "crc: " << d->m_crc << endl;
01037     d->m_currentFile->setCRC32( d->m_crc );
01038 
01039     d->m_currentFile = 0L;
01040 
01041     // update saved offset for appending new files
01042     d->m_offset = device()->at();
01043     return true;
01044 }
01045 
01046 bool KZip::writeSymLink(const QString &name, const QString &target,
01047                 const QString &user, const QString &group,
01048                 mode_t perm, time_t atime, time_t mtime, time_t ctime) {
01049   return KArchive::writeSymLink(name,target,user,group,perm,atime,mtime,ctime);
01050 }
01051 
01052 bool KZip::writeSymLink_impl(const QString &name, const QString &target,
01053                 const QString &user, const QString &group,
01054                 mode_t perm, time_t atime, time_t mtime, time_t ctime) {
01055 
01056   // reassure that symlink flag is set, otherwise strange things happen on
01057   // extraction
01058   perm |= S_IFLNK;
01059   Compression c = compression();
01060   setCompression(NoCompression);    // link targets are never compressed
01061 
01062   if (!prepareWriting(name, user, group, 0, perm, atime, mtime, ctime)) {
01063     kdWarning() << "KZip::writeFile prepareWriting failed" << endl;
01064     setCompression(c);
01065     return false;
01066   }
01067 
01068   QCString symlink_target = QFile::encodeName(target);
01069   if (!writeData(symlink_target, symlink_target.length())) {
01070     kdWarning() << "KZip::writeFile writeData failed" << endl;
01071     setCompression(c);
01072     return false;
01073   }
01074 
01075   if (!doneWriting(symlink_target.length())) {
01076     kdWarning() << "KZip::writeFile doneWriting failed" << endl;
01077     setCompression(c);
01078     return false;
01079   }
01080 
01081   setCompression(c);
01082   return true;
01083 }
01084 
01085 void KZip::virtual_hook( int id, void* data )
01086 {
01087     switch (id) {
01088       case VIRTUAL_WRITE_DATA: {
01089         WriteDataParams* params = reinterpret_cast<WriteDataParams *>(data);
01090         params->retval = writeData_impl( params->data, params->size );
01091         break;
01092       }
01093       case VIRTUAL_WRITE_SYMLINK: {
01094         WriteSymlinkParams *params = reinterpret_cast<WriteSymlinkParams *>(data);
01095         params->retval = writeSymLink_impl(*params->name,*params->target,
01096                 *params->user,*params->group,params->perm,
01097                 params->atime,params->mtime,params->ctime);
01098         break;
01099       }
01100       case VIRTUAL_PREPARE_WRITING: {
01101         PrepareWritingParams *params = reinterpret_cast<PrepareWritingParams *>(data);
01102         params->retval = prepareWriting_impl(*params->name,*params->user,
01103                 *params->group,params->size,params->perm,
01104                 params->atime,params->mtime,params->ctime);
01105         break;
01106       }
01107       default:
01108         KArchive::virtual_hook( id, data );
01109     }/*end switch*/
01110 }
01111 
01112 // made virtual using virtual_hook
01113 bool KZip::writeData(const char * c, uint i)
01114 {
01115     return KArchive::writeData( c, i );
01116 }
01117 
01118 bool KZip::writeData_impl(const char * c, uint i)
01119 {
01120     Q_ASSERT( d->m_currentFile );
01121     Q_ASSERT( d->m_currentDev );
01122     if (!d->m_currentFile || !d->m_currentDev)
01123         return false;
01124 
01125     // crc to be calculated over uncompressed stuff...
01126     // and they didn't mention it in their docs...
01127     d->m_crc = crc32(d->m_crc, (const Bytef *) c , i);
01128 
01129     Q_LONG written = d->m_currentDev->writeBlock( c, i );
01130     //kdDebug(7040) << "KZip::writeData wrote " << i << " bytes." << endl;
01131     Q_ASSERT( written == (Q_LONG)i );
01132     return written == (Q_LONG)i;
01133 }
01134 
01135 void KZip::setCompression( Compression c )
01136 {
01137     d->m_compression = ( c == NoCompression ) ? 0 : 8;
01138 }
01139 
01140 KZip::Compression KZip::compression() const
01141 {
01142    return ( d->m_compression == 8 ) ? DeflateCompression : NoCompression;
01143 }
01144 
01145 void KZip::setExtraField( ExtraField ef )
01146 {
01147     d->m_extraField = ef;
01148 }
01149 
01150 KZip::ExtraField KZip::extraField() const
01151 {
01152     return d->m_extraField;
01153 }
01154 
01156 
01157 QByteArray KZipFileEntry::data() const
01158 {
01159     QIODevice* dev = device();
01160     QByteArray arr;
01161     if ( dev ) {
01162         arr = dev->readAll();
01163         delete dev;
01164     }
01165     return arr;
01166 }
01167 
01168 QIODevice* KZipFileEntry::device() const
01169 {
01170     //kdDebug(7040) << "KZipFileEntry::device creating iodevice limited to pos=" << position() << ", csize=" << compressedSize() << endl;
01171     // Limit the reading to the appropriate part of the underlying device (e.g. file)
01172     KLimitedIODevice* limitedDev = new KLimitedIODevice( archive()->device(), position(), compressedSize() );
01173     if ( encoding() == 0 || compressedSize() == 0 ) // no compression (or even no data)
01174         return limitedDev;
01175 
01176     if ( encoding() == 8 )
01177     {
01178         // On top of that, create a device that uncompresses the zlib data
01179         QIODevice* filterDev = KFilterDev::device( limitedDev, "application/x-gzip" );
01180         if ( !filterDev )
01181             return 0L; // ouch
01182         static_cast<KFilterDev *>(filterDev)->setSkipHeaders(); // Just zlib, not gzip
01183         bool b = filterDev->open( IO_ReadOnly );
01184         Q_ASSERT( b );
01185         return filterDev;
01186     }
01187 
01188     kdError() << "This zip file contains files compressed with method "
01189               << encoding() <<", this method is currently not supported by KZip,"
01190               <<" please use a command-line tool to handle this file." << endl;
01191     return 0L;
01192 }
01193 
KDE Logo
This file is part of the documentation for kio Library Version 3.2.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Feb 4 12:35:08 2004 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2003