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         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.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Apr 22 14:24:13 2004 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2003