KDECore
kgzipfilter.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2000-2005 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 as published by the Free Software Foundation; either 00007 version 2 of the License, or (at your option) any later version. 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., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "kgzipfilter.h" 00021 00022 #include <time.h> 00023 #include <zlib.h> 00024 #include <kdebug.h> 00025 #include <QtCore/QIODevice> 00026 00027 00028 /* gzip flag byte */ 00029 #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ 00030 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ 00031 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ 00032 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ 00033 #define COMMENT 0x10 /* bit 4 set: file comment present */ 00034 #define RESERVED 0xE0 /* bits 5..7: reserved */ 00035 00036 // #define DEBUG_GZIP 00037 00038 class KGzipFilter::Private 00039 { 00040 public: 00041 Private() 00042 : headerWritten(false), footerWritten(false), compressed(false), mode(0), crc(0), isInitialized(false) 00043 { 00044 zStream.zalloc = (alloc_func)0; 00045 zStream.zfree = (free_func)0; 00046 zStream.opaque = (voidpf)0; 00047 } 00048 00049 z_stream zStream; 00050 bool headerWritten; 00051 bool footerWritten; 00052 bool compressed; 00053 int mode; 00054 ulong crc; 00055 bool isInitialized; 00056 }; 00057 00058 KGzipFilter::KGzipFilter() 00059 : d(new Private) 00060 { 00061 } 00062 00063 00064 KGzipFilter::~KGzipFilter() 00065 { 00066 delete d; 00067 } 00068 00069 void KGzipFilter::init(int mode) 00070 { 00071 init(mode, filterFlags() == WithHeaders ? GZipHeader : RawDeflate); 00072 } 00073 00074 void KGzipFilter::init(int mode, Flag flag) 00075 { 00076 if (d->isInitialized) { 00077 terminate(); 00078 } 00079 d->zStream.next_in = Z_NULL; 00080 d->zStream.avail_in = 0; 00081 if ( mode == QIODevice::ReadOnly ) 00082 { 00083 const int windowBits = (flag == RawDeflate) 00084 ? -MAX_WBITS /*no zlib header*/ 00085 : (flag == GZipHeader) ? 00086 MAX_WBITS + 32 /* auto-detect and eat gzip header */ 00087 : MAX_WBITS /*zlib header*/; 00088 const int result = inflateInit2(&d->zStream, windowBits); 00089 if ( result != Z_OK ) 00090 kDebug(7005) << "inflateInit2 returned " << result; 00091 // No idea what to do with result :) 00092 } else if ( mode == QIODevice::WriteOnly ) 00093 { 00094 int result = deflateInit2(&d->zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); // same here 00095 if ( result != Z_OK ) 00096 kDebug(7005) << "deflateInit returned " << result; 00097 } else { 00098 kWarning(7005) << "KGzipFilter: Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported"; 00099 } 00100 d->mode = mode; 00101 d->compressed = true; 00102 d->headerWritten = false; 00103 d->footerWritten = false; 00104 d->isInitialized = true; 00105 } 00106 00107 int KGzipFilter::mode() const 00108 { 00109 return d->mode; 00110 } 00111 00112 void KGzipFilter::terminate() 00113 { 00114 if ( d->mode == QIODevice::ReadOnly ) 00115 { 00116 int result = inflateEnd(&d->zStream); 00117 if ( result != Z_OK ) 00118 kDebug(7005) << "inflateEnd returned " << result; 00119 } else if ( d->mode == QIODevice::WriteOnly ) 00120 { 00121 int result = deflateEnd(&d->zStream); 00122 if ( result != Z_OK ) 00123 kDebug(7005) << "deflateEnd returned " << result; 00124 } 00125 d->isInitialized = false; 00126 } 00127 00128 00129 void KGzipFilter::reset() 00130 { 00131 if ( d->mode == QIODevice::ReadOnly ) 00132 { 00133 int result = inflateReset(&d->zStream); 00134 if ( result != Z_OK ) 00135 kDebug(7005) << "inflateReset returned " << result; 00136 } else if ( d->mode == QIODevice::WriteOnly ) { 00137 int result = deflateReset(&d->zStream); 00138 if ( result != Z_OK ) 00139 kDebug(7005) << "deflateReset returned " << result; 00140 d->headerWritten = false; 00141 d->footerWritten = false; 00142 } 00143 } 00144 00145 bool KGzipFilter::readHeader() 00146 { 00147 // We now rely on zlib to read the full header (see the MAX_WBITS + 32 in init). 00148 // We just use this method to check if the data is actually compressed. 00149 00150 #ifdef DEBUG_GZIP 00151 kDebug(7005) << "avail=" << d->zStream.avail_in; 00152 #endif 00153 // Assume not compressed until we see a gzip header 00154 d->compressed = false; 00155 Bytef *p = d->zStream.next_in; 00156 int i = d->zStream.avail_in; 00157 if ((i -= 10) < 0) return false; // Need at least 10 bytes 00158 #ifdef DEBUG_GZIP 00159 kDebug(7005) << "first byte is " << QString::number(*p,16); 00160 #endif 00161 if (*p++ != 0x1f) return false; // GZip magic 00162 #ifdef DEBUG_GZIP 00163 kDebug(7005) << "second byte is " << QString::number(*p,16); 00164 #endif 00165 if (*p++ != 0x8b) return false; 00166 00167 #if 0 00168 int method = *p++; 00169 int flags = *p++; 00170 if ((method != Z_DEFLATED) || (flags & RESERVED) != 0) return false; 00171 p += 6; 00172 if ((flags & EXTRA_FIELD) != 0) // skip extra field 00173 { 00174 if ((i -= 2) < 0) return false; // Need at least 2 bytes 00175 int len = *p++; 00176 len += (*p++) << 8; 00177 if ((i -= len) < 0) return false; // Need at least len bytes 00178 p += len; 00179 } 00180 if ((flags & ORIG_NAME) != 0) // skip original file name 00181 { 00182 #ifdef DEBUG_GZIP 00183 kDebug(7005) << "ORIG_NAME=" << (char*)p; 00184 #endif 00185 while( (i > 0) && (*p)) 00186 { 00187 i--; p++; 00188 } 00189 if (--i <= 0) return false; 00190 p++; 00191 } 00192 if ((flags & COMMENT) != 0) // skip comment 00193 { 00194 while( (i > 0) && (*p)) 00195 { 00196 i--; p++; 00197 } 00198 if (--i <= 0) return false; 00199 p++; 00200 } 00201 if ((flags & HEAD_CRC) != 0) // skip the header crc 00202 { 00203 if ((i-=2) < 0) return false; 00204 p += 2; 00205 } 00206 00207 d->zStream.avail_in = i; 00208 d->zStream.next_in = p; 00209 #endif 00210 00211 d->compressed = true; 00212 #ifdef DEBUG_GZIP 00213 kDebug(7005) << "header OK"; 00214 #endif 00215 return true; 00216 } 00217 00218 /* Output a 16 bit value, lsb first */ 00219 #define put_short(w) \ 00220 *p++ = (uchar) ((w) & 0xff); \ 00221 *p++ = (uchar) ((ushort)(w) >> 8); 00222 00223 /* Output a 32 bit value to the bit stream, lsb first */ 00224 #define put_long(n) \ 00225 put_short((n) & 0xffff); \ 00226 put_short(((ulong)(n)) >> 16); 00227 00228 bool KGzipFilter::writeHeader( const QByteArray & fileName ) 00229 { 00230 Bytef *p = d->zStream.next_out; 00231 int i = d->zStream.avail_out; 00232 *p++ = 0x1f; 00233 *p++ = 0x8b; 00234 *p++ = Z_DEFLATED; 00235 *p++ = ORIG_NAME; 00236 put_long( time( 0L ) ); // Modification time (in unix format) 00237 *p++ = 0; // Extra flags (2=max compress, 4=fastest compress) 00238 *p++ = 3; // Unix 00239 00240 uint len = fileName.length(); 00241 for ( uint j = 0 ; j < len ; ++j ) 00242 *p++ = fileName[j]; 00243 *p++ = 0; 00244 int headerSize = p - d->zStream.next_out; 00245 i -= headerSize; 00246 Q_ASSERT(i>0); 00247 d->crc = crc32(0L, Z_NULL, 0); 00248 d->zStream.next_out = p; 00249 d->zStream.avail_out = i; 00250 d->headerWritten = true; 00251 return true; 00252 } 00253 00254 void KGzipFilter::writeFooter() 00255 { 00256 Q_ASSERT( d->headerWritten ); 00257 if (!d->headerWritten) kDebug(7005) << kBacktrace(); 00258 Q_ASSERT(!d->footerWritten); 00259 Bytef *p = d->zStream.next_out; 00260 int i = d->zStream.avail_out; 00261 //kDebug(7005) << "avail_out=" << i << "writing CRC=" << QString::number(d->crc, 16) << "at p=" << p; 00262 put_long( d->crc ); 00263 //kDebug(7005) << "writing totalin=" << d->zStream.total_in << "at p=" << p; 00264 put_long( d->zStream.total_in ); 00265 i -= p - d->zStream.next_out; 00266 d->zStream.next_out = p; 00267 d->zStream.avail_out = i; 00268 d->footerWritten = true; 00269 } 00270 00271 void KGzipFilter::setOutBuffer( char * data, uint maxlen ) 00272 { 00273 d->zStream.avail_out = maxlen; 00274 d->zStream.next_out = (Bytef *) data; 00275 } 00276 void KGzipFilter::setInBuffer( const char * data, uint size ) 00277 { 00278 #ifdef DEBUG_GZIP 00279 kDebug(7005) << "avail_in=" << size; 00280 #endif 00281 d->zStream.avail_in = size; 00282 d->zStream.next_in = (Bytef*) data; 00283 } 00284 int KGzipFilter::inBufferAvailable() const 00285 { 00286 return d->zStream.avail_in; 00287 } 00288 int KGzipFilter::outBufferAvailable() const 00289 { 00290 return d->zStream.avail_out; 00291 } 00292 00293 KGzipFilter::Result KGzipFilter::uncompress_noop() 00294 { 00295 // I'm not sure we really need support for that (uncompressed streams), 00296 // but why not, it can't hurt to have it. One case I can think of is someone 00297 // naming a tar file "blah.tar.gz" :-) 00298 if ( d->zStream.avail_in > 0 ) 00299 { 00300 int n = (d->zStream.avail_in < d->zStream.avail_out) ? d->zStream.avail_in : d->zStream.avail_out; 00301 memcpy( d->zStream.next_out, d->zStream.next_in, n ); 00302 d->zStream.avail_out -= n; 00303 d->zStream.next_in += n; 00304 d->zStream.avail_in -= n; 00305 return KFilterBase::Ok; 00306 } else 00307 return KFilterBase::End; 00308 } 00309 00310 KGzipFilter::Result KGzipFilter::uncompress() 00311 { 00312 #ifndef NDEBUG 00313 if (d->mode == 0) 00314 kFatal() << "mode==0; KGzipFilter::init was not called!"; 00315 else if (d->mode == QIODevice::WriteOnly) 00316 kFatal() << "uncompress called but the filter was opened for writing!"; 00317 Q_ASSERT ( d->mode == QIODevice::ReadOnly ); 00318 #endif 00319 00320 if ( d->compressed ) 00321 { 00322 #ifdef DEBUG_GZIP 00323 kDebug(7005) << "Calling inflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 00324 kDebug(7005) << " next_in=" << d->zStream.next_in; 00325 #endif 00326 int result = inflate(&d->zStream, Z_SYNC_FLUSH); 00327 #ifdef DEBUG_GZIP 00328 kDebug(7005) << " -> inflate returned " << result; 00329 kDebug(7005) << " now avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 00330 kDebug(7005) << " next_in=" << d->zStream.next_in; 00331 #else 00332 if ( result != Z_OK && result != Z_STREAM_END ) 00333 kDebug(7005) << "Warning: inflate() returned " << result; 00334 #endif 00335 return ( result == Z_OK ? KFilterBase::Ok : ( result == Z_STREAM_END ? KFilterBase::End : KFilterBase::Error ) ); 00336 } else 00337 return uncompress_noop(); 00338 } 00339 00340 KGzipFilter::Result KGzipFilter::compress( bool finish ) 00341 { 00342 Q_ASSERT ( d->compressed ); 00343 Q_ASSERT ( d->mode == QIODevice::WriteOnly ); 00344 00345 Bytef* p = d->zStream.next_in; 00346 ulong len = d->zStream.avail_in; 00347 #ifdef DEBUG_GZIP 00348 kDebug(7005) << " calling deflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 00349 #endif 00350 const int result = deflate(&d->zStream, finish ? Z_FINISH : Z_NO_FLUSH); 00351 if ( result != Z_OK && result != Z_STREAM_END ) 00352 kDebug(7005) << " deflate returned " << result; 00353 if ( d->headerWritten ) 00354 { 00355 //kDebug(7005) << "Computing CRC for the next " << len - d->zStream.avail_in << " bytes"; 00356 d->crc = crc32(d->crc, p, len - d->zStream.avail_in); 00357 } 00358 KGzipFilter::Result callerResult = result == Z_OK ? KFilterBase::Ok : (Z_STREAM_END ? KFilterBase::End : KFilterBase::Error); 00359 00360 if (result == Z_STREAM_END && d->headerWritten && !d->footerWritten) { 00361 if (d->zStream.avail_out >= 8 /*footer size*/) { 00362 //kDebug(7005) << "finished, write footer"; 00363 writeFooter(); 00364 } else { 00365 // No room to write the footer (#157706/#188415), we'll have to do it on the next pass. 00366 //kDebug(7005) << "finished, but no room for footer yet"; 00367 callerResult = KFilterBase::Ok; 00368 } 00369 } 00370 return callerResult; 00371 }
KDE 4.6 API Reference