kio Library API Documentation

ktar.cpp

00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2000 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00016    Boston, MA 02111-1307, USA.
00017 */
00018 
00019 //#include <stdio.h>
00020 #include <stdlib.h> // strtol
00021 #include <time.h> // time()
00022 /*#include <unistd.h>
00023 #include <grp.h>
00024 #include <pwd.h>*/
00025 #include <assert.h>
00026 
00027 #include <qcstring.h>
00028 #include <qdir.h>
00029 #include <qfile.h>
00030 #include <kdebug.h>
00031 #include <kmimetype.h>
00032 
00033 #include <kfilterdev.h>
00034 #include <kfilterbase.h>
00035 
00036 #include "ktar.h"
00037 
00041 
00042 class KTar::KTarPrivate
00043 {
00044 public:
00045     KTarPrivate() {}
00046     QStringList dirList;
00047 };
00048 
00049 KTar::KTar( const QString& filename, const QString & _mimetype )
00050     : KArchive( 0L )
00051 {
00052     m_filename = filename;
00053     d = new KTarPrivate;
00054     QString mimetype( _mimetype );
00055     bool forced = true;
00056     if ( mimetype.isEmpty() )
00057     {
00058     if ( QFile::exists( filename ) )
00059             mimetype = KMimeType::findByFileContent( filename )->name();
00060     else
00061         mimetype = KMimeType::findByPath( filename, 0, true )->name();
00062         kdDebug(7041) << "KTar::KTar mimetype=" << mimetype << endl;
00063 
00064         // Don't move to prepareDevice - the other constructor theoretically allows ANY filter
00065         if ( mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around
00066              mimetype == "application/x-webarchive" )
00067             // that's a gzipped tar file, so ask for gzip filter
00068             mimetype = "application/x-gzip";
00069         else if ( mimetype == "application/x-tbz" ) // that's a bzipped2 tar file, so ask for bz2 filter
00070             mimetype = "application/x-bzip2";
00071         else
00072         {
00073             // Something else. Check if it's not really gzip though (e.g. for KOffice docs)
00074             QFile file( filename );
00075             if ( file.open( IO_ReadOnly ) )
00076             {
00077                 unsigned char firstByte = file.getch();
00078                 unsigned char secondByte = file.getch();
00079                 unsigned char thirdByte = file.getch();
00080                 if ( firstByte == 0037 && secondByte == 0213 )
00081                     mimetype = "application/x-gzip";
00082                 else if ( firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h' )
00083                     mimetype = "application/x-bzip2";
00084                 else if ( firstByte == 'P' && secondByte == 'K' && thirdByte == 3 )
00085                 {
00086                     unsigned char fourthByte = file.getch();
00087                     if ( fourthByte == 4 )
00088                         mimetype = "application/x-zip";
00089                 }
00090             }
00091         }
00092         forced = false;
00093     }
00094 
00095     prepareDevice( filename, mimetype, forced );
00096 }
00097 
00098 void KTar::prepareDevice( const QString & filename,
00099                             const QString & mimetype, bool forced )
00100 {
00101   if( "application/x-tar" == mimetype )
00102       setDevice( new QFile( filename ) );
00103   else
00104   {
00105     if( "application/x-gzip" == mimetype
00106        || "application/x-bzip2" == mimetype)
00107         forced = true;
00108 
00109     QIODevice *dev = KFilterDev::deviceForFile( filename, mimetype, forced );
00110     if( dev )
00111       setDevice( dev );
00112   }
00113 }
00114 
00115 KTar::KTar( QIODevice * dev )
00116     : KArchive( dev )
00117 {
00118     d = new KTarPrivate;
00119 }
00120 
00121 KTar::~KTar()
00122 {
00123     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
00124     if( isOpened() )
00125         close();
00126     if ( !m_filename.isEmpty() )
00127         delete device(); // we created it ourselves
00128     delete d;
00129 }
00130 
00131 void KTar::setOrigFileName( const QCString & fileName )
00132 {
00133     if ( !isOpened() || !(mode() & IO_WriteOnly) )
00134     {
00135         kdWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
00136         return;
00137     }
00138     static_cast<KFilterDev *>(device())->setOrigFileName( fileName );
00139 }
00140 
00141 
00142 bool KTar::openArchive( int mode )
00143 {
00144     if ( !(mode & IO_ReadOnly) )
00145         return true;
00146 
00147     // We'll use the permission and user/group of d->rootDir
00148     // for any directory we emulate (see findOrCreate)
00149     //struct stat buf;
00150     //stat( m_filename, &buf );
00151 
00152     d->dirList.clear();
00153     QIODevice* dev = device();
00154 
00155     // read dir infos
00156     char buffer[ 0x200 ];
00157     bool ende = false;
00158     do
00159     {
00160         // Read header
00161         int n = dev->readBlock( buffer, 0x200 );
00162         if ( n == 0x200 && buffer[0] != 0 )
00163         {
00164             // Make sure this is actually a tar header
00165             if (strncmp(buffer + 257, "ustar", 5))
00166             {
00167                 // The magic isn't there (broken/old tars), but maybe a correct checksum?
00168                 QCString s;
00169 
00170                 int check = 0;
00171                 for( uint j = 0; j < 0x200; ++j )
00172                     check += buffer[j];
00173 
00174                 // adjust checksum to count the checksum fields as blanks
00175                 for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
00176                     check -= buffer[148 + j];
00177                 check += 8 * ' ';
00178 
00179                 s.sprintf("%o", check );
00180 
00181                 // only compare those of the 6 checksum digits that mean something,
00182                 // because the other digits are filled with all sorts of different chars by different tars ...
00183                 if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() ) )
00184                 {
00185                     kdWarning(7041) << "KTar: invalid TAR file. Header is: " << QCString( buffer+257, 5 ) << endl;
00186                     return false;
00187                 }
00188         }
00189 
00190             QString name( QString::fromLocal8Bit(buffer) );
00191 
00192             // If filename is longer than 100 (0x64) chars, then tar uses ././@LongLink (David)
00193             if ( name == "././@LongLink" )
00194             {
00195                 // in this case, here's what happens (according to od -cx !)
00196                 // 1) the filename is stored in the next 512b buffer
00197                 n = dev->readBlock( buffer, 0x200 );
00198                 if ( n == 0x200 && buffer[0] != 0 )
00199                 {
00200                     name = QString::fromLocal8Bit(buffer);
00201                     // 2) read yet another 512b buffer, for permissions, time, size ...
00202                     n = dev->readBlock( buffer, 0x200 );
00203                     if (!( n == 0x200 && buffer[0] != 0 ))
00204                         break;
00205                 }
00206                 else
00207                     break;
00208             }
00209 
00210             bool isdir = false;
00211             QString nm;
00212 
00213             if ( name.right(1) == "/" )
00214             {
00215                 isdir = true;
00216                 name = name.left( name.length() - 1 );
00217             }
00218 
00219             int pos = name.findRev( '/' );
00220             if ( pos == -1 )
00221                 nm = name;
00222             else
00223                 nm = name.mid( pos + 1 );
00224 
00225             // read access
00226             buffer[ 0x6b ] = 0;
00227             char *dummy;
00228             const char* p = buffer + 0x64;
00229             while( *p == ' ' ) ++p;
00230             int access = (int)strtol( p, &dummy, 8 );
00231 
00232             // read user and group
00233             QString user( buffer + 0x109 );
00234             QString group( buffer + 0x129 );
00235             // read symlink dest (if any)
00236             QString symlink(buffer + 0x9d );
00237 
00238             // read time
00239             buffer[ 0x93 ] = 0;
00240             p = buffer + 0x88;
00241             while( *p == ' ' ) ++p;
00242             int time = (int)strtol( p, &dummy, 8 );
00243 
00244             // read type flag
00245             char typeflag = buffer[ 0x9c ];
00246             // '0' for files, '1' hard link, '2' symlink, '5' for directory (and 'L' for longlink)
00247             // and 'D' for GNU tar extension DUMPDIR
00248             if ( typeflag == '1' )
00249                 isdir = true;
00250 
00251             bool isDumpDir = false;
00252             if ( typeflag == 'D' )
00253             {
00254                 isdir = false;
00255                 isDumpDir = true;
00256             }
00257             //bool islink = ( typeflag == '1' || typeflag == '2' );
00258             //kdDebug(7041) << "typeflag=" << typeflag << " islink=" << islink << endl;
00259 
00260             if (isdir)
00261                 access |= S_IFDIR; // f*cking broken tar files
00262 
00263             KArchiveEntry* e;
00264             if ( isdir )
00265             {
00266                 //kdDebug(7041) << "KArchive::open directory " << nm << endl;
00267                 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
00268             }
00269             else
00270             {
00271                 // read size
00272                 buffer[ 0x88 ] = 0; // was 0x87, but 0x88 fixes BR #26437
00273                 char *dummy;
00274                 const char* p = buffer + 0x7c;
00275                 while( *p == ' ' ) ++p;
00276                 int size = (int)strtol( p, &dummy, 8 );
00277 
00278                 // for isDumpDir we will skip the additional info about that dirs contents
00279                 if ( isDumpDir )
00280                 {
00281             e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
00282                 }
00283         else
00284         {
00285 
00286                     // Let's hack around hard links. Our classes don't support that, so make them symlinks
00287                     if ( typeflag == '1' )
00288                     {
00289                         size = nm.length(); // in any case, we don't want to skip the real size, hence this resetting of size
00290                         kdDebug(7041) << "HARD LINK, setting size to " << size << endl;
00291                     }
00292 
00293                     //kdDebug(7041) << "KArchive::open file " << nm << " size=" << size << endl;
00294 
00295                     e = new KArchiveFile( this, nm, access, time, user, group, symlink,
00296                                           dev->at(), size );
00297         }
00298 
00299                 // Skip contents + align bytes
00300                 int rest = size % 0x200;
00301                 int skip = size + (rest ? 0x200 - rest : 0);
00302                 //kdDebug(7041) << "KArchive::open, at()=" << dev->at() << " rest=" << rest << " skipping " << skip << endl;
00303                 if (! dev->at( dev->at() + skip ) )
00304                     kdWarning(7041) << "KArchive::open skipping " << skip << " failed" << endl;
00305             }
00306 
00307             if ( pos == -1 )
00308             {
00309                 if ( nm == "." ) // special case
00310                 {
00311                     Q_ASSERT( isdir );
00312                     if ( isdir )
00313                         setRootDir( static_cast<KArchiveDirectory *>( e ) );
00314                 }
00315                 else
00316                     rootDir()->addEntry( e );
00317             }
00318             else
00319             {
00320                 // In some tar files we can find dir/./file => call cleanDirPath
00321                 QString path = QDir::cleanDirPath( name.left( pos ) );
00322                 // Ensure container directory exists, create otherwise
00323                 KArchiveDirectory * d = findOrCreate( path );
00324                 d->addEntry( e );
00325             }
00326         }
00327         else
00328         {
00329             //qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
00330             ende = true;
00331         }
00332     } while( !ende );
00333     return true;
00334 }
00335 
00336 bool KTar::closeArchive()
00337 {
00338     d->dirList.clear();
00339     return true;
00340 }
00341 
00342 bool KTar::writeDir( const QString& name, const QString& user, const QString& group )
00343 {
00344     if ( !isOpened() )
00345     {
00346         kdWarning(7041) << "KTar::writeDir: You must open the tar file before writing to it\n";
00347         return false;
00348     }
00349 
00350     if ( !(mode() & IO_WriteOnly) )
00351     {
00352         kdWarning(7041) << "KTar::writeDir: You must open the tar file for writing\n";
00353         return false;
00354     }
00355 
00356     // In some tar files we can find dir/./ => call cleanDirPath
00357     QString dirName ( QDir::cleanDirPath( name ) );
00358 
00359     // Need trailing '/'
00360     if ( dirName.right(1) != "/" )
00361         dirName += "/";
00362 
00363     if ( d->dirList.contains( dirName ) )
00364         return true; // already there
00365 
00366     char buffer[ 0x201 ];
00367     memset( buffer, 0, 0x200 );
00368 
00369     // If more than 100 chars, we need to use the LongLink trick
00370     if ( dirName.length() > 99 )
00371     {
00372         strcpy( buffer, "././@LongLink" );
00373         fillBuffer( buffer, "     0", dirName.length()+1, 'L', user.local8Bit(), group.local8Bit() );
00374         device()->writeBlock( buffer, 0x200 );
00375         strncpy( buffer, QFile::encodeName(dirName), 0x200 );
00376         buffer[0x200] = 0;
00377         // write long name
00378         device()->writeBlock( buffer, 0x200 );
00379         // not even needed to reclear the buffer, tar doesn't do it
00380     }
00381     else
00382     {
00383         // Write name
00384         strncpy( buffer, QFile::encodeName(dirName), 0x200 );
00385         buffer[0x200] = 0;
00386     }
00387 
00388     fillBuffer( buffer, " 40755", 0, 0x35, user.local8Bit(), group.local8Bit());
00389 
00390     // Write header
00391     device()->writeBlock( buffer, 0x200 );
00392 
00393     d->dirList.append( dirName ); // contains trailing slash
00394     return true; // TODO if wanted, better error control
00395 }
00396 
00397 bool KTar::prepareWriting( const QString& name, const QString& user, const QString& group, uint size )
00398 {
00399     if ( !isOpened() )
00400     {
00401         kdWarning(7041) << "KArchive::writeFile: You must open the tar file before writing to it\n";
00402         return false;
00403     }
00404 
00405     if ( !(mode() & IO_WriteOnly) )
00406     {
00407         kdWarning(7041) << "KArchive::writeFile: You must open the tar file for writing\n";
00408         return false;
00409     }
00410 
00411     // In some tar files we can find dir/./file => call cleanDirPath
00412     QString fileName ( QDir::cleanDirPath( name ) );
00413 
00414     /*
00415       // Create toplevel dirs
00416       // Commented out by David since it's not necessary, and if anybody thinks it is,
00417       // he needs to implement a findOrCreate equivalent in writeDir.
00418       // But as KTar and the "tar" program both handle tar files without
00419       // dir entries, there's really no need for that
00420       QString tmp ( fileName );
00421       int i = tmp.findRev( '/' );
00422       if ( i != -1 )
00423       {
00424       QString d = tmp.left( i + 1 ); // contains trailing slash
00425       if ( !m_dirList.contains( d ) )
00426       {
00427       tmp = tmp.mid( i + 1 );
00428       writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
00429       }
00430       }
00431     */
00432 
00433     char buffer[ 0x201 ];
00434     memset( buffer, 0, 0x200 );
00435 
00436     // If more than 100 chars, we need to use the LongLink trick
00437     if ( fileName.length() > 99 )
00438     {
00439         strcpy( buffer, "././@LongLink" );
00440         fillBuffer( buffer, "     0", fileName.length()+1, 'L', user.local8Bit(), group.local8Bit() );
00441         device()->writeBlock( buffer, 0x200 );
00442 
00443         strncpy( buffer, QFile::encodeName(fileName), 0x200 );
00444         buffer[0x200] = 0;
00445         // write long name
00446         device()->writeBlock( buffer, 0x200 );
00447         // not even needed to reclear the buffer, tar doesn't do it
00448     }
00449     else
00450     {
00451         // Write name
00452         strncpy( buffer, QFile::encodeName(fileName), 0x200 );
00453         buffer[0x200] = 0;
00454     }
00455 
00456     fillBuffer( buffer, "100644", size, 0x30, user.local8Bit(), group.local8Bit() );
00457 
00458     // Write header
00459     return device()->writeBlock( buffer, 0x200 ) == 0x200;
00460 }
00461 
00462 bool KTar::doneWriting( uint size )
00463 {
00464     // Write alignment
00465     int rest = size % 0x200;
00466     if ( rest )
00467     {
00468         char buffer[ 0x201 ];
00469         for( uint i = 0; i < 0x200; ++i )
00470             buffer[i] = 0;
00471         Q_LONG nwritten = device()->writeBlock( buffer, 0x200 - rest );
00472         return nwritten == 0x200 - rest;
00473     }
00474     return true;
00475 }
00476 
00477 /*** Some help from the tar sources
00478 struct posix_header
00479 {                               byte offset
00480   char name[100];               *   0 *     0x0
00481   char mode[8];                 * 100 *     0x64
00482   char uid[8];                  * 108 *     0x6c
00483   char gid[8];                  * 116 *     0x74
00484   char size[12];                * 124 *     0x7c
00485   char mtime[12];               * 136 *     0x88
00486   char chksum[8];               * 148 *     0x94
00487   char typeflag;                * 156 *     0x9c
00488   char linkname[100];           * 157 *     0x9d
00489   char magic[6];                * 257 *     0x101
00490   char version[2];              * 263 *     0x107
00491   char uname[32];               * 265 *     0x109
00492   char gname[32];               * 297 *     0x129
00493   char devmajor[8];             * 329 *     0x149
00494   char devminor[8];             * 337 *     ...
00495   char prefix[155];             * 345 *
00496                                 * 500 *
00497 };
00498 */
00499 
00500 void KTar::fillBuffer( char * buffer,
00501     const char * mode, int size, char typeflag, const char * uname, const char * gname )
00502 {
00503   // mode (as in stat())
00504   assert( strlen(mode) == 6 );
00505   strcpy( buffer+0x64, mode );
00506   buffer[ 0x6a ] = ' ';
00507   buffer[ 0x6b ] = '\0';
00508 
00509   // dummy uid
00510   strcpy( buffer + 0x6c, "   765 ");
00511   // dummy gid
00512   strcpy( buffer + 0x74, "   144 ");
00513 
00514   // size
00515   QCString s;
00516   s.sprintf("%o", size); // OCT
00517   s = s.rightJustify( 11, ' ' );
00518   strcpy( buffer + 0x7c, s.data() );
00519   buffer[ 0x87 ] = ' '; // space-terminate (no null after)
00520 
00521   // Dummy time
00522   s.sprintf("%lo", static_cast<unsigned long>(time( 0 )) ); // OCT
00523   s = s.rightJustify( 11, ' ' );
00524   strcpy( buffer + 0x88, s.data() );
00525   buffer[ 0x93 ] = ' '; // space-terminate (no null after)
00526 
00527   // spaces, replaced by the check sum later
00528   buffer[ 0x94 ] = 0x20;
00529   buffer[ 0x95 ] = 0x20;
00530   buffer[ 0x96 ] = 0x20;
00531   buffer[ 0x97 ] = 0x20;
00532   buffer[ 0x98 ] = 0x20;
00533   buffer[ 0x99 ] = 0x20;
00534 
00535   /* From the tar sources :
00536      Fill in the checksum field.  It's formatted differently from the
00537      other fields: it has [6] digits, a null, then a space -- rather than
00538      digits, a space, then a null. */
00539 
00540   buffer[ 0x9a ] = '\0';
00541   buffer[ 0x9b ] = ' ';
00542 
00543   // type flag (dir, file, link)
00544   buffer[ 0x9c ] = typeflag;
00545 
00546  // magic + version
00547   strcpy( buffer + 0x101, "ustar");
00548   strcpy( buffer + 0x107, "00" );
00549 
00550   // user
00551   strcpy( buffer + 0x109, uname );
00552   // group
00553   strcpy( buffer + 0x129, gname );
00554 
00555   // Header check sum
00556   int check = 32;
00557   for( uint j = 0; j < 0x200; ++j )
00558     check += buffer[j];
00559   s.sprintf("%o", check ); // OCT
00560   s = s.rightJustify( 7, ' ' );
00561   strcpy( buffer + 0x94, s.data() );
00562 }
00563 
00564 void KTar::virtual_hook( int id, void* data )
00565 { KArchive::virtual_hook( id, data ); }
00566 
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:32 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001