kio Library API Documentation

kzip.cpp

00001 
00002 /* This file is part of the KDE libraries
00003    Copyright (C) 2000 David Faure <faure@kde.org>
00004    Copyright (C) 2002 Holger Schroeder <holger-kde@holgis.net>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License version 2 as published by the Free Software Foundation.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018    Boston, MA 02111-1307, USA.
00019 */
00020 
00021 /*
00022     This class implements a kioslave to acces ZIP files from KDE.
00023     you can use it in IO_ReadOnly or in IO_WriteOnly mode, and it
00024     behaves just as expected (i hope ;-) ).
00025     It can also be used in IO_ReadWrite mode, in this case one can
00026     append files to an existing zip archive. when you append new files, which
00027     are not yet in the zip, it works as expected, they are appended at the end.
00028     when you append a file, which is already in the file, the reference to the
00029     old file is dropped and the new one is added to the zip. but the
00030     old data from the file itself is not deleted, it is still in the
00031     zipfile. so when you want to have a small and garbagefree zipfile,
00032     just read the contents of the appended zipfile and write it to a new one
00033     in IO_WriteOnly mode. exspecially take care of this, when you donīt want
00034     to leak information of how intermediate versions of files in the zip
00035     were looking.
00036     for more information on the zip fileformat go to
00037     http://www.pkware.com/support/appnote.html .
00038 
00039 */
00040 
00041 
00042 
00043 #include <sys/time.h>
00044 
00045 #include <qfile.h>
00046 #include <qdir.h>
00047 #include <time.h>
00048 #include <string.h>
00049 #include <qdatetime.h>
00050 #include <kdebug.h>
00051 #include <qptrlist.h>
00052 #include <kmimetype.h>
00053 #include <zlib.h>
00054 
00055 #include "kfilterdev.h"
00056 #include "kzip.h"
00057 #include "klimitediodevice.h"
00058 
00059 static void transformToMsDos(const QDateTime& dt, char* buffer)
00060 {
00061     if ( dt.isValid() )
00062     {
00063         Q_UINT16 time =
00064              ( dt.time().hour() << 11 )    // 5 bit hour
00065            | ( dt.time().minute() << 5 )   // 6 bit minute
00066            | ( dt.time().second() >> 1 );  // 5 bit double seconds
00067 
00068         buffer[0] = char(time);
00069         buffer[1] = char(time >> 8);
00070 
00071         Q_UINT16 date =
00072              ( ( dt.date().year() - 1980 ) << 9 ) // 7 bit year 1980-based
00073            | ( dt.date().month() << 5 )           // 4 bit month
00074            | ( dt.date().day() );                 // 5 bit day
00075 
00076         buffer[2] = char(date);
00077         buffer[3] = char(date >> 8);
00078     }
00079     else // !dt.isValid(), assume 1980-01-01 midnight
00080     {
00081         buffer[0] = 0;
00082         buffer[1] = 0;
00083         buffer[2] = 33;
00084         buffer[3] = 0;
00085     }
00086 }
00087 
00088 static int getActualTime( void )
00089 {
00090     timeval value;
00091     gettimeofday( &value, NULL );
00092     return value.tv_sec;
00093 }
00094 
00098 
00099 class KZip::KZipPrivate
00100 {
00101 public:
00102     KZipPrivate()
00103         : m_crc( 0 ),
00104           m_currentFile( 0L ),
00105           m_currentDev( 0L ),
00106           m_compression( 8 ),
00107       m_offset( 0L ) { }
00108 
00109     unsigned long           m_crc;         // checksum
00110     KZipFileEntry*          m_currentFile; // file currently being written
00111     QIODevice*              m_currentDev;  // filterdev used to write to the above file
00112     QPtrList<KZipFileEntry> m_fileList;    // flat list of all files, for the index (saves a recursive method ;)
00113     int                     m_compression;
00114     unsigned int            m_offset; // holds the offset of the place in the zip,
00115     // where new data can be appended. after openarchive it points to 0, when in
00116     // writeonly mode, or it points to the beginning of the central directory.
00117     // each call to writefile updates this value.
00118 };
00119 
00120 KZip::KZip( const QString& filename )
00121     : KArchive( 0L )
00122 {
00123     //kdDebug(7040) << "KZip(filename) reached." << endl;
00124     m_filename = filename;
00125     d = new KZipPrivate;
00126     setDevice( new QFile( filename ) );
00127 }
00128 
00129 KZip::KZip( QIODevice * dev )
00130     : KArchive( dev )
00131 {
00132     //kdDebug(7040) << "KZip::KZip( QIODevice * dev) reached." << endl;
00133     d = new KZipPrivate;
00134 }
00135 
00136 KZip::~KZip()
00137 {
00138     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
00139     //kdDebug(7040) << "~KZip reached." << endl;
00140     if( isOpened() )
00141         close();
00142     if ( !m_filename.isEmpty() )
00143         delete device(); // we created it ourselves
00144     delete d;
00145 }
00146 
00147 bool KZip::openArchive( int mode )
00148 {
00149     //kdDebug(7040) << "openarchive reached." << endl;
00150     d->m_fileList.clear();
00151 
00152     if ( mode == IO_WriteOnly )
00153         return true;
00154     if ( mode != IO_ReadOnly && mode != IO_ReadWrite )
00155     {
00156         kdWarning(7040) << "Unsupported mode " << mode << endl;
00157         return false;
00158     }
00159 
00160     char buffer[47];
00161 
00162     // Check that it's a valid ZIP file
00163     // KArchive::open() opened the underlying device already.
00164     QIODevice* dev = device();
00165 
00166     uint offset = 0; // holds offset, where we read
00167     int n;
00168 
00169     for (;;) // repeat until 'end of entries' signature is reached
00170     {
00171         n = dev->readBlock( buffer, 4 );
00172 
00173         if (n < 4)
00174         {
00175             kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#1)" << endl;
00176 
00177             return false;
00178         }
00179 
00180         if ( !memcmp( buffer, "PK\5\6", 4 ) ) // 'end of entries'
00181             break;
00182 
00183         if ( !memcmp( buffer, "PK\3\4", 4 ) ) // local file header
00184         {
00185             dev->at( dev->at() + 2 ); // skip 'version needed to extract'
00186 
00187             // we have to take care of the 'general purpose bit flag'.
00188             // if bit 3 is set, the header doesn't contain the length of
00189             // the file and we look for the signature 'PK\7\8'.
00190 
00191             dev->readBlock( buffer, 2 );
00192             if ( buffer[0] & 8 )
00193             {
00194                 bool foundSignature = false;
00195 
00196                 while (!foundSignature)
00197                 {
00198                     n = dev->readBlock( buffer, 1 );
00199                     if (n < 1)
00200                     {
00201                         kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#2)" << endl;
00202                         return false;
00203                     }
00204 
00205                     if ( buffer[0] != 'P' )
00206                         continue;
00207 
00208                     n = dev->readBlock( buffer, 3 );
00209                     if (n < 3)
00210                     {
00211                         kdWarning(7040) << "Invalid ZIP file. Unexpected end of file. (#3)" << endl;
00212                         return false;
00213                     }
00214 
00215                     if ( buffer[0] == 'K' && buffer[1] == 7 && buffer[2] == 8 )
00216                     {
00217                         foundSignature = true;
00218                         dev->at( dev->at() + 12 ); // skip the 'data_descriptor'
00219                     }
00220                 }
00221             }
00222             else
00223             {
00224                 // here we calculate the length of the file in the zip
00225                 // with headers and jump to the next header.
00226                 dev->at( dev->at() + 10 );
00227 
00228                 uint skip;
00229 
00230                 n = dev->readBlock( buffer, 4 ); // compressed file size
00231                 skip = (uchar)buffer[3] << 24 | (uchar)buffer[2] << 16 |
00232                        (uchar)buffer[1] << 8 | (uchar)buffer[0];
00233 
00234                 dev->at( dev->at() + 4 );
00235                 n = dev->readBlock( buffer, 2 ); // file name length
00236                 skip += (uchar)buffer[1] << 8 | (uchar)buffer[0];
00237 
00238                 n = dev->readBlock( buffer, 2 ); // extra field length
00239                 skip += (uchar)buffer[1] << 8 | (uchar)buffer[0];
00240 
00241                 dev->at( dev->at() + skip );
00242                 offset += 30 + skip;
00243             }
00244         }
00245         else if ( !memcmp( buffer, "PK\1\2", 4 ) ) // central block
00246         {
00247 
00248             // so we reached the central header at the end of the zip file
00249             // here we get all interesting data out of the central header
00250             // of a file
00251             offset = dev->at() - 4;
00252 
00253             //set offset for appending new files
00254             if ( d->m_offset == 0L ) d->m_offset = offset;
00255 
00256             n = dev->readBlock( buffer + 4, 42 );
00257             if (n < 42) {
00258                 kdWarning(7040) << "Invalid ZIP file, central entry too short" << endl; // not long enough for valid entry
00259                 return false;
00260             }
00261             // length of the filename (well, pathname indeed)
00262             int namelen = (uchar)buffer[29] << 8 | (uchar)buffer[28];
00263             char* bufferName = new char[ namelen + 1 ];
00264             n = dev->readBlock( bufferName, namelen );
00265             if ( n < namelen )
00266                 kdWarning(7040) << "Invalid ZIP file. Name not completely read" << endl;
00267             QString name( QString::fromLocal8Bit(bufferName, namelen) );
00268             delete[] bufferName;
00269 
00270             //kdDebug(7040) << "name: " << name << endl;
00271             // only in central header ! see below.
00272             // length of extra attributes
00273             int extralen = (uchar)buffer[31] << 8 | (uchar)buffer[30];
00274             // length of comment for this file
00275             int commlen =  (uchar)buffer[33] << 8 | (uchar)buffer[32];
00276             // compression method of this file
00277             int cmethod =  (uchar)buffer[11] << 8 | (uchar)buffer[10];
00278 
00279             //kdDebug(7040) << "cmethod: " << cmethod << endl;
00280             //kdDebug(7040) << "extralen: " << extralen << endl;
00281 
00282             // uncompressed file size
00283             uint ucsize = (uchar)buffer[27] << 24 | (uchar)buffer[26] << 16 |
00284             (uchar)buffer[25] << 8 | (uchar)buffer[24];
00285             // compressed file size
00286             uint csize = (uchar)buffer[23] << 24 | (uchar)buffer[22] << 16 |
00287             (uchar)buffer[21] << 8 | (uchar)buffer[20];
00288 
00289             // offset of local header
00290             uint localheaderoffset = (uchar)buffer[45] << 24 | (uchar)buffer[44] << 16 |
00291             (uchar)buffer[43] << 8 | (uchar)buffer[42];
00292 
00293             // some clever people use different extra field lengths
00294             // in the central header and in the local header... funny.
00295             // so we need to get the localextralen to calculate the offset
00296             // from localheaderstart to dataoffset
00297             char localbuf[5];
00298             int save_at = dev->at();
00299             dev->at( localheaderoffset + 28 );
00300             dev->readBlock( localbuf, 4);
00301             int localextralen = (uchar)localbuf[1] << 8 | (uchar)localbuf[0];
00302             dev->at(save_at);
00303 
00304             //kdDebug(7040) << "localextralen: " << localextralen << endl;
00305 
00306             // offset, where the real data for uncompression starts
00307             uint dataoffset = localheaderoffset + 30 + localextralen + namelen; //comment only in central header
00308 
00309             //kdDebug(7040) << "esize: " << esize << endl;
00310             //kdDebug(7040) << "eoffset: " << eoffset << endl;
00311             //kdDebug(7040) << "csize: " << csize << endl;
00312 
00313             bool isdir = false;
00314             int access = 0777; // TODO available in zip file?
00315             int time = getActualTime();
00316 
00317             QString entryName;
00318 
00319             if ( name.endsWith( "/" ) ) // Entries with a trailing slash are directories
00320             {
00321                 isdir = true;
00322                 name = name.left( name.length() - 1 );
00323                 access |= S_IFDIR;
00324             }
00325 
00326             int pos = name.findRev( '/' );
00327             if ( pos == -1 )
00328                 entryName = name;
00329             else
00330                 entryName = name.mid( pos + 1 );
00331             Q_ASSERT( !entryName.isEmpty() );
00332 
00333             KArchiveEntry* entry;
00334             if ( isdir )
00335             {
00336                 QString path = QDir::cleanDirPath( name.left( pos ) );
00337                 KArchiveEntry* ent = rootDir()->entry( path );
00338                 if ( ent && ent->isDirectory() )
00339                 {
00340                     //kdDebug(7040) << "Directory already exists, NOT going to add it again" << endl;
00341                     entry = 0L;
00342                 }
00343                 else
00344                 {
00345                     entry = new KArchiveDirectory( this, entryName, access, time, rootDir()->user(), rootDir()->group(), QString::null );
00346                     //kdDebug(7040) << "KArchiveDirectory created, entryName= " << entryName << ", name=" << name << endl;
00347                 }
00348             }
00349             else
00350             {
00351                 entry = new KZipFileEntry( this, entryName, access, time, rootDir()->user(), rootDir()->group(), QString::null,
00352                                         name, dataoffset, ucsize, cmethod, csize );
00353                 static_cast<KZipFileEntry *>(entry)->setHeaderStart( localheaderoffset );
00354                 //kdDebug(7040) << "KZipFileEntry created, entryName= " << entryName << ", name=" << name << endl;
00355                 d->m_fileList.append( static_cast<KZipFileEntry *>( entry ) );
00356             }
00357 
00358             if ( entry )
00359             {
00360                 if ( pos == -1 )
00361                 {
00362                     rootDir()->addEntry(entry);
00363                 }
00364                 else
00365                 {
00366                     // In some tar files we can find dir/./file => call cleanDirPath
00367                     QString path = QDir::cleanDirPath( name.left( pos ) );
00368                     // Ensure container directory exists, create otherwise
00369                     KArchiveDirectory * tdir = findOrCreate( path );
00370                     tdir->addEntry(entry);
00371                 }
00372             }
00373 
00374             //calculate offset to next entry
00375             offset += 46 + commlen + extralen + namelen;
00376             bool b = dev->at(offset);
00377             Q_ASSERT( b );
00378             if ( !b )
00379               return false;
00380         }
00381         else
00382         {
00383             kdWarning(7040) << "Invalid ZIP file. Unrecognized header at offset " << offset << endl;
00384 
00385             return false;
00386         }
00387     }
00388     //kdDebug(7040) << "*** done *** " << endl;
00389     return true;
00390 }
00391 
00392 bool KZip::closeArchive()
00393 {
00394     if ( ! ( mode() & IO_WriteOnly ) )
00395     {
00396         //kdDebug(7040) << "closearchive readonly reached." << endl;
00397         return true;
00398     }
00399     //ReadWrite or WriteOnly
00400     //write all central dir file entries
00401 
00402     // to be written at the end of the file...
00403     char buffer[ 22 ]; // first used for 12, then for 22 at the end
00404     uLong crc = crc32(0L, Z_NULL, 0);
00405 
00406     Q_LONG centraldiroffset = device()->at();
00407     //kdDebug(7040) << "closearchive: centraldiroffset: " << centraldiroffset << endl;
00408     Q_LONG atbackup = device()->at();
00409     QPtrListIterator<KZipFileEntry> it( d->m_fileList );
00410 
00411     for ( ; it.current() ; ++it )
00412     {   //set crc and compressed size in each local file header
00413         device()->at( it.current()->headerStart() + 14 );
00414     //kdDebug(7040) << "closearchive setcrcandcsize: filename: "
00415     //    << it.current()->path()
00416     //    << " encoding: "<< it.current()->encoding() << endl;
00417 
00418         uLong mycrc = it.current()->crc32();
00419         buffer[0] = char(mycrc); // crc checksum, at headerStart+14
00420         buffer[1] = char(mycrc >> 8);
00421         buffer[2] = char(mycrc >> 16);
00422         buffer[3] = char(mycrc >> 24);
00423 
00424         int mysize1 = it.current()->compressedSize();
00425         buffer[4] = char(mysize1); // compressed file size, at headerStart+18
00426         buffer[5] = char(mysize1 >> 8);
00427         buffer[6] = char(mysize1 >> 16);
00428         buffer[7] = char(mysize1 >> 24);
00429 
00430         int myusize = it.current()->size();
00431         buffer[8] = char(myusize); // uncompressed file size, at headerStart+22
00432         buffer[9] = char(myusize >> 8);
00433         buffer[10] = char(myusize >> 16);
00434         buffer[11] = char(myusize >> 24);
00435 
00436         device()->writeBlock( buffer, 12 );
00437     }
00438     device()->at( atbackup );
00439 
00440     for ( it.toFirst(); it.current() ; ++it )
00441     {
00442         //kdDebug(7040) << "closearchive: filename: " << it.current()->path()
00443         //              << " encoding: "<< it.current()->encoding() << endl;
00444 
00445         QCString path = QFile::encodeName(it.current()->path());
00446 
00447         int bufferSize = path.length() + 46;
00448         char* buffer = new char[ bufferSize ];
00449 
00450         memset(buffer, 0, 46); // zero is a nice default for most header fields
00451 
00452         const char head[] =
00453         {
00454             'P', 'K', 1, 2, // central file header signature
00455             0x14, 0,        // version made by
00456             0x14, 0         // version needed to extract
00457         };
00458 
00459     // I do not know why memcpy is not working here
00460         //memcpy(buffer, head, sizeof(head));
00461         qmemmove(buffer, head, sizeof(head));
00462 
00463         buffer[ 10 ] = char(it.current()->encoding()); // compression method
00464         buffer[ 11 ] = char(it.current()->encoding() >> 8);
00465 
00466 
00467         transformToMsDos( it.current()->datetime(), &buffer[ 12 ] );
00468 
00469         uLong mycrc = it.current()->crc32();
00470         buffer[ 16 ] = char(mycrc); // crc checksum
00471         buffer[ 17 ] = char(mycrc >> 8);
00472         buffer[ 18 ] = char(mycrc >> 16);
00473         buffer[ 19 ] = char(mycrc >> 24);
00474 
00475         int mysize1 = it.current()->compressedSize();
00476         buffer[ 20 ] = char(mysize1); // compressed file size
00477         buffer[ 21 ] = char(mysize1 >> 8);
00478         buffer[ 22 ] = char(mysize1 >> 16);
00479         buffer[ 23 ] = char(mysize1 >> 24);
00480 
00481         int mysize = it.current()->size();
00482         buffer[ 24 ] = char(mysize); // uncompressed file size
00483         buffer[ 25 ] = char(mysize >> 8);
00484         buffer[ 26 ] = char(mysize >> 16);
00485         buffer[ 27 ] = char(mysize >> 24);
00486 
00487         buffer[ 28 ] = char(it.current()->path().length()); // filename length
00488         buffer[ 29 ] = char(it.current()->path().length() >> 8);
00489 
00490         int myhst = it.current()->headerStart();
00491         buffer[ 42 ] = char(myhst); //relative offset of local header
00492         buffer[ 43 ] = char(myhst >> 8);
00493         buffer[ 44 ] = char(myhst >> 16);
00494         buffer[ 45 ] = char(myhst >> 24);
00495 
00496         // file name
00497         strncpy( buffer + 46, path, path.length() );
00498     //kdDebug(7040) << "closearchive length to write: " << bufferSize << endl;
00499         crc = crc32(crc, (Bytef *)buffer, bufferSize );
00500         device()->writeBlock( buffer, bufferSize );
00501         delete[] buffer;
00502     }
00503     Q_LONG centraldirendoffset = device()->at();
00504     //kdDebug(7040) << "closearchive: centraldirendoffset: " << centraldirendoffset << endl;
00505     //kdDebug(7040) << "closearchive: device()->at(): " << device()->at() << endl;
00506 
00507     //write end of central dir record.
00508     buffer[ 0 ] = 'P'; //end of central dir signature
00509     buffer[ 1 ] = 'K';
00510     buffer[ 2 ] = 5;
00511     buffer[ 3 ] = 6;
00512 
00513     buffer[ 4 ] = 0; // number of this disk
00514     buffer[ 5 ] = 0;
00515 
00516     buffer[ 6 ] = 0; // number of disk with start of central dir
00517     buffer[ 7 ] = 0;
00518 
00519     int count = d->m_fileList.count();
00520     //kdDebug(7040) << "number of files (count): " << count << endl;
00521 
00522 
00523     buffer[ 8 ] = char(count); // total number of entries in central dir of
00524     buffer[ 9 ] = char(count >> 8); // this disk
00525 
00526     buffer[ 10 ] = buffer[ 8 ]; // total number of entries in the central dir
00527     buffer[ 11 ] = buffer[ 9 ];
00528 
00529     int cdsize = centraldirendoffset - centraldiroffset;
00530     buffer[ 12 ] = char(cdsize); // size of the central dir
00531     buffer[ 13 ] = char(cdsize >> 8);
00532     buffer[ 14 ] = char(cdsize >> 16);
00533     buffer[ 15 ] = char(cdsize >> 24);
00534 
00535     //kdDebug(7040) << "end : centraldiroffset: " << centraldiroffset << endl;
00536     //kdDebug(7040) << "end : centraldirsize: " << cdsize << endl;
00537 
00538     buffer[ 16 ] = char(centraldiroffset); // central dir offset
00539     buffer[ 17 ] = char(centraldiroffset >> 8);
00540     buffer[ 18 ] = char(centraldiroffset >> 16);
00541     buffer[ 19 ] = char(centraldiroffset >> 24);
00542 
00543     buffer[ 20 ] = 0; //zipfile comment length
00544     buffer[ 21 ] = 0;
00545 
00546     device()->writeBlock( buffer, 22);
00547 
00548     //kdDebug(7040) << "kzip.cpp reached." << endl;
00549     return true;
00550 }
00551 
00552 // Reimplemented to replace device()->writeBlock with writeData
00553 bool KZip::writeFile( const QString& name, const QString& user, const QString& group, uint size, const char* data )
00554 {
00555     // set right offset in zip.
00556     device()->at( d->m_offset );
00557     if ( !prepareWriting( name, user, group, size ) )
00558     {
00559         kdWarning() << "KZip::writeFile prepareWriting failed" << endl;
00560         return false;
00561     }
00562 
00563     // Write data
00564     if ( data && size && !writeData( data, size ) )
00565     {
00566         kdWarning() << "KZip::writeFile writeData failed" << endl;
00567         return false;
00568     }
00569 
00570     if ( ! doneWriting( size ) )
00571     {
00572         kdWarning() << "KZip::writeFile doneWriting failed" << endl;
00573         return false;
00574     }
00575     // update saved offset for appending new files
00576     d->m_offset = device()->at();
00577     return true;
00578 }
00579 
00580 bool KZip::prepareWriting( const QString& name, const QString& user, const QString& group, uint /*size*/ )
00581 {
00582     //kdDebug(7040) << "prepareWriting reached." << endl;
00583     if ( !isOpened() )
00584     {
00585         qWarning( "KZip::writeFile: You must open the zip file before writing to it\n");
00586         return false;
00587     }
00588 
00589     if ( ! ( mode() & IO_WriteOnly ) ) // accept WriteOnly and ReadWrite
00590     {
00591         qWarning( "KZip::writeFile: You must open the zip file for writing\n");
00592         return false;
00593     }
00594 
00595     // delete entries in the filelist with the same filename as the one we want
00596     // to save, so that we donīt have duplicate file entries when viewing the zip
00597     // with konqi...
00598     // CAUTION: the old file itself is still in the zip and wonīt be removed !!!
00599     QPtrListIterator<KZipFileEntry> it( d->m_fileList );
00600 
00601     //kdDebug(7040) << "filename to write: " << name <<endl;
00602     for ( ; it.current() ; ++it )
00603     {
00604         //kdDebug(7040) << "prepfilename: " << it.current()->path() <<endl;
00605         if (name == it.current()->path() )
00606         {
00607             //kdDebug(7040) << "removing following entry: " << it.current()->path() <<endl;
00608             d->m_fileList.remove();
00609         }
00610 
00611     }
00612     // Find or create parent dir
00613     KArchiveDirectory* parentDir = rootDir();
00614     QString fileName( name );
00615     int i = name.findRev( '/' );
00616     if ( i != -1 )
00617     {
00618         QString dir = name.left( i );
00619         fileName = name.mid( i + 1 );
00620         //kdDebug(7040) << "KZip::prepareWriting ensuring " << dir << " exists. fileName=" << fileName << endl;
00621         parentDir = findOrCreate( dir );
00622     }
00623 
00624     int time = getActualTime();
00625 
00626     // construct a KZipFileEntry and add it to list
00627     KZipFileEntry * e = new KZipFileEntry( this, fileName, 0777, time, user, group, QString::null,
00628                                            name, device()->at() + 30 + name.length(), // start
00629                                            0 /*size unknown yet*/, d->m_compression, 0 /*csize unknown yet*/ );
00630     e->setHeaderStart( device()->at() );
00631     //kdDebug(7040) << "wrote file start: " << e->position() << " name: " << name << endl;
00632     parentDir->addEntry( e );
00633 
00634     d->m_currentFile = e;
00635     d->m_fileList.append( e );
00636 
00637     // write out zip header
00638     QCString encodedName = QFile::encodeName(name);
00639     int bufferSize = encodedName.length() + 30;
00640     //kdDebug(7040) << "KZip::prepareWriting bufferSize=" << bufferSize << endl;
00641     char* buffer = new char[ bufferSize ];
00642 
00643     buffer[ 0 ] = 'P'; //local file header signature
00644     buffer[ 1 ] = 'K';
00645     buffer[ 2 ] = 3;
00646     buffer[ 3 ] = 4;
00647 
00648     buffer[ 4 ] = 0x14; // version needed to extract
00649     buffer[ 5 ] = 0;
00650 
00651     buffer[ 6 ] = 0; // general purpose bit flag
00652     buffer[ 7 ] = 0;
00653 
00654     buffer[ 8 ] = char(e->encoding()); // compression method
00655     buffer[ 9 ] = char(e->encoding() >> 8);
00656 
00657     transformToMsDos( e->datetime(), &buffer[ 10 ] );
00658 
00659     buffer[ 14 ] = 'C'; //dummy crc
00660     buffer[ 15 ] = 'R';
00661     buffer[ 16 ] = 'C';
00662     buffer[ 17 ] = 'q';
00663 
00664     buffer[ 18 ] = 'C'; //compressed file size
00665     buffer[ 19 ] = 'S';
00666     buffer[ 20 ] = 'I';
00667     buffer[ 21 ] = 'Z';
00668 
00669     buffer[ 22 ] = 'U'; //uncompressed file size
00670     buffer[ 23 ] = 'S';
00671     buffer[ 24 ] = 'I';
00672     buffer[ 25 ] = 'Z';
00673 
00674     buffer[ 26 ] = (uchar)(encodedName.length()); //filename length
00675     buffer[ 27 ] = (uchar)(encodedName.length() >> 8);
00676 
00677     buffer[ 28 ] = 0; // extra field length
00678     buffer[ 29 ] = 0;
00679 
00680     // file name
00681     strncpy( buffer + 30, encodedName, encodedName.length() );
00682 
00683     // Write header
00684     bool b = (device()->writeBlock( buffer, bufferSize ) == bufferSize );
00685     d->m_crc = 0L;
00686     delete[] buffer;
00687 
00688     Q_ASSERT( b );
00689     if (!b)
00690         return false;
00691 
00692     // Prepare device for writing the data
00693     // Either device() if no compression, or a KFilterDev to compress
00694     if ( d->m_compression == 0 ) {
00695         d->m_currentDev = device();
00696         return true;
00697     }
00698 
00699     d->m_currentDev = KFilterDev::device( device(), "application/x-gzip", false );
00700     Q_ASSERT( d->m_currentDev );
00701     if ( !d->m_currentDev )
00702         return false; // ouch
00703     static_cast<KFilterDev *>(d->m_currentDev)->setSkipHeaders(); // Just zlib, not gzip
00704 
00705     b = d->m_currentDev->open( IO_WriteOnly );
00706     Q_ASSERT( b );
00707     return b;
00708 }
00709 
00710 bool KZip::doneWriting( uint size )
00711 {
00712     if ( d->m_currentFile->encoding() == 8 ) {
00713         // Finish
00714         (void)d->m_currentDev->writeBlock( 0, 0 );
00715         delete d->m_currentDev;
00716     }
00717     // If 0, d->m_currentDev was device() - don't delete ;)
00718     d->m_currentDev = 0L;
00719 
00720     Q_ASSERT( d->m_currentFile );
00721     //kdDebug(7040) << "donewriting reached." << endl;
00722     //kdDebug(7040) << "filename: " << d->m_currentFile->path() << endl;
00723     //kdDebug(7040) << "getpos (at): " << device()->at() << endl;
00724     d->m_currentFile->setSize(size);
00725     int csize = device()->at() -
00726         d->m_currentFile->headerStart() - 30 -
00727         d->m_currentFile->path().length();
00728     d->m_currentFile->setCompressedSize(csize);
00729     //kdDebug(7040) << "usize: " << d->m_currentFile->size() << endl;
00730     //kdDebug(7040) << "csize: " << d->m_currentFile->compressedSize() << endl;
00731     //kdDebug(7040) << "headerstart: " << d->m_currentFile->headerStart() << endl;
00732 
00733     //kdDebug(7040) << "crc: " << d->m_crc << endl;
00734     d->m_currentFile->setCRC32( d->m_crc );
00735 
00736     d->m_currentFile = 0L;
00737     return true;
00738 }
00739 
00740 void KZip::virtual_hook( int id, void* data )
00741 {
00742   KArchive::virtual_hook( id, data );
00743 }
00744 
00745 bool KZip::writeData(const char * c, uint i)
00746 {
00747     Q_ASSERT( d->m_currentFile );
00748     Q_ASSERT( d->m_currentDev );
00749     if (!d->m_currentFile || !d->m_currentDev)
00750         return false;
00751 
00752     // crc to be calculated over uncompressed stuff...
00753     // and they didn't mention it in their docs...
00754     d->m_crc = crc32(d->m_crc, (const Bytef *) c , i);
00755 
00756     Q_LONG written = d->m_currentDev->writeBlock( c, i );
00757     //kdDebug(7040) << "KZip::writeData wrote " << i << " bytes." << endl;
00758     Q_ASSERT( written == (Q_LONG)i );
00759     return written == (Q_LONG)i;
00760 }
00761 
00762 void KZip::setCompression( Compression c )
00763 {
00764     d->m_compression = ( c == NoCompression ) ? 0 : 8;
00765 }
00766 
00767 KZip::Compression KZip::compression() const
00768 {
00769    return ( d->m_compression == 8 ) ? DeflateCompression : NoCompression;
00770 }
00771 
00773 
00774 QByteArray KZipFileEntry::data() const
00775 {
00776     QIODevice* dev = device();
00777     QByteArray arr = dev->readAll();
00778     delete dev;
00779     return arr;
00780 }
00781 
00782 QIODevice* KZipFileEntry::device() const
00783 {
00784     //kdDebug(7040) << "KZipFileEntry::device creating iodevice limited to pos=" << position() << ", csize=" << compressedSize() << endl;
00785     // Limit the reading to the appropriate part of the underlying device (e.g. file)
00786     KLimitedIODevice* limitedDev = new KLimitedIODevice( archive()->device(), position(), compressedSize() );
00787     if ( encoding() == 0 || compressedSize() == 0 ) // no compression (or even no data)
00788         return limitedDev;
00789 
00790     if ( encoding() == 8 )
00791     {
00792         // On top of that, create a device that uncompresses the zlib data
00793         QIODevice* filterDev = KFilterDev::device( limitedDev, "application/x-gzip" );
00794         if ( !filterDev )
00795             return 0L; // ouch
00796         static_cast<KFilterDev *>(filterDev)->setSkipHeaders(); // Just zlib, not gzip
00797         bool b = filterDev->open( IO_ReadOnly );
00798         Q_ASSERT( b );
00799         return filterDev;
00800     }
00801 
00802     kdError() << "This zip file contains files compressed with method "
00803               << encoding() <<", this method is currently not supported by KZip,"
00804               <<" please use a command-line tool to handle this file." << endl;
00805     return 0L;
00806 }
00807 
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sun Feb 27 22:15:33 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001