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 <QDebug> 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 qDebug() << "inflateInit2 returned " << result; 00091 // TODO return false 00092 } 00093 } else if ( mode == QIODevice::WriteOnly ) 00094 { 00095 int result = deflateInit2(&d->zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); // same here 00096 if ( result != Z_OK ) { 00097 qDebug() << "deflateInit returned " << result; 00098 // TODO return false 00099 } 00100 } else { 00101 qWarning() << "KGzipFilter: Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported"; 00102 } 00103 d->mode = mode; 00104 d->compressed = true; 00105 d->headerWritten = false; 00106 d->footerWritten = false; 00107 d->isInitialized = true; 00108 } 00109 00110 int KGzipFilter::mode() const 00111 { 00112 return d->mode; 00113 } 00114 00115 void KGzipFilter::terminate() 00116 { 00117 if ( d->mode == QIODevice::ReadOnly ) 00118 { 00119 int result = inflateEnd(&d->zStream); 00120 if ( result != Z_OK ) { 00121 qDebug() << "inflateEnd returned " << result; 00122 // TODO return false 00123 } 00124 } else if ( d->mode == QIODevice::WriteOnly ) 00125 { 00126 int result = deflateEnd(&d->zStream); 00127 if ( result != Z_OK ) { 00128 qDebug() << "deflateEnd returned " << result; 00129 // TODO return false 00130 } 00131 } 00132 d->isInitialized = false; 00133 } 00134 00135 00136 void KGzipFilter::reset() 00137 { 00138 if ( d->mode == QIODevice::ReadOnly ) 00139 { 00140 int result = inflateReset(&d->zStream); 00141 if ( result != Z_OK ) { 00142 qDebug() << "inflateReset returned " << result; 00143 // TODO return false 00144 } 00145 } else if ( d->mode == QIODevice::WriteOnly ) { 00146 int result = deflateReset(&d->zStream); 00147 if ( result != Z_OK ) { 00148 qDebug() << "deflateReset returned " << result; 00149 // TODO return false 00150 } 00151 d->headerWritten = false; 00152 d->footerWritten = false; 00153 } 00154 } 00155 00156 bool KGzipFilter::readHeader() 00157 { 00158 // We now rely on zlib to read the full header (see the MAX_WBITS + 32 in init). 00159 // We just use this method to check if the data is actually compressed. 00160 00161 #ifdef DEBUG_GZIP 00162 qDebug() << "avail=" << d->zStream.avail_in; 00163 #endif 00164 // Assume not compressed until we see a gzip header 00165 d->compressed = false; 00166 Bytef *p = d->zStream.next_in; 00167 int i = d->zStream.avail_in; 00168 if ((i -= 10) < 0) return false; // Need at least 10 bytes 00169 #ifdef DEBUG_GZIP 00170 qDebug() << "first byte is " << QString::number(*p,16); 00171 #endif 00172 if (*p++ != 0x1f) return false; // GZip magic 00173 #ifdef DEBUG_GZIP 00174 qDebug() << "second byte is " << QString::number(*p,16); 00175 #endif 00176 if (*p++ != 0x8b) return false; 00177 00178 #if 0 00179 int method = *p++; 00180 int flags = *p++; 00181 if ((method != Z_DEFLATED) || (flags & RESERVED) != 0) return false; 00182 p += 6; 00183 if ((flags & EXTRA_FIELD) != 0) // skip extra field 00184 { 00185 if ((i -= 2) < 0) return false; // Need at least 2 bytes 00186 int len = *p++; 00187 len += (*p++) << 8; 00188 if ((i -= len) < 0) return false; // Need at least len bytes 00189 p += len; 00190 } 00191 if ((flags & ORIG_NAME) != 0) // skip original file name 00192 { 00193 #ifdef DEBUG_GZIP 00194 qDebug() << "ORIG_NAME=" << (char*)p; 00195 #endif 00196 while( (i > 0) && (*p)) 00197 { 00198 i--; p++; 00199 } 00200 if (--i <= 0) return false; 00201 p++; 00202 } 00203 if ((flags & COMMENT) != 0) // skip comment 00204 { 00205 while( (i > 0) && (*p)) 00206 { 00207 i--; p++; 00208 } 00209 if (--i <= 0) return false; 00210 p++; 00211 } 00212 if ((flags & HEAD_CRC) != 0) // skip the header crc 00213 { 00214 if ((i-=2) < 0) return false; 00215 p += 2; 00216 } 00217 00218 d->zStream.avail_in = i; 00219 d->zStream.next_in = p; 00220 #endif 00221 00222 d->compressed = true; 00223 #ifdef DEBUG_GZIP 00224 qDebug() << "header OK"; 00225 #endif 00226 return true; 00227 } 00228 00229 /* Output a 16 bit value, lsb first */ 00230 #define put_short(w) \ 00231 *p++ = (uchar) ((w) & 0xff); \ 00232 *p++ = (uchar) ((ushort)(w) >> 8); 00233 00234 /* Output a 32 bit value to the bit stream, lsb first */ 00235 #define put_long(n) \ 00236 put_short((n) & 0xffff); \ 00237 put_short(((ulong)(n)) >> 16); 00238 00239 bool KGzipFilter::writeHeader( const QByteArray & fileName ) 00240 { 00241 Bytef *p = d->zStream.next_out; 00242 int i = d->zStream.avail_out; 00243 *p++ = 0x1f; 00244 *p++ = 0x8b; 00245 *p++ = Z_DEFLATED; 00246 *p++ = ORIG_NAME; 00247 put_long( time( 0L ) ); // Modification time (in unix format) 00248 *p++ = 0; // Extra flags (2=max compress, 4=fastest compress) 00249 *p++ = 3; // Unix 00250 00251 uint len = fileName.length(); 00252 for ( uint j = 0 ; j < len ; ++j ) 00253 *p++ = fileName[j]; 00254 *p++ = 0; 00255 int headerSize = p - d->zStream.next_out; 00256 i -= headerSize; 00257 Q_ASSERT(i>0); 00258 d->crc = crc32(0L, Z_NULL, 0); 00259 d->zStream.next_out = p; 00260 d->zStream.avail_out = i; 00261 d->headerWritten = true; 00262 return true; 00263 } 00264 00265 void KGzipFilter::writeFooter() 00266 { 00267 Q_ASSERT( d->headerWritten ); 00268 Q_ASSERT(!d->footerWritten); 00269 Bytef *p = d->zStream.next_out; 00270 int i = d->zStream.avail_out; 00271 //qDebug() << "avail_out=" << i << "writing CRC=" << QString::number(d->crc, 16) << "at p=" << p; 00272 put_long( d->crc ); 00273 //qDebug() << "writing totalin=" << d->zStream.total_in << "at p=" << p; 00274 put_long( d->zStream.total_in ); 00275 i -= p - d->zStream.next_out; 00276 d->zStream.next_out = p; 00277 d->zStream.avail_out = i; 00278 d->footerWritten = true; 00279 } 00280 00281 void KGzipFilter::setOutBuffer( char * data, uint maxlen ) 00282 { 00283 d->zStream.avail_out = maxlen; 00284 d->zStream.next_out = (Bytef *) data; 00285 } 00286 void KGzipFilter::setInBuffer( const char * data, uint size ) 00287 { 00288 #ifdef DEBUG_GZIP 00289 qDebug() << "avail_in=" << size; 00290 #endif 00291 d->zStream.avail_in = size; 00292 d->zStream.next_in = (Bytef*) data; 00293 } 00294 int KGzipFilter::inBufferAvailable() const 00295 { 00296 return d->zStream.avail_in; 00297 } 00298 int KGzipFilter::outBufferAvailable() const 00299 { 00300 return d->zStream.avail_out; 00301 } 00302 00303 KGzipFilter::Result KGzipFilter::uncompress_noop() 00304 { 00305 // I'm not sure we really need support for that (uncompressed streams), 00306 // but why not, it can't hurt to have it. One case I can think of is someone 00307 // naming a tar file "blah.tar.gz" :-) 00308 if ( d->zStream.avail_in > 0 ) 00309 { 00310 int n = (d->zStream.avail_in < d->zStream.avail_out) ? d->zStream.avail_in : d->zStream.avail_out; 00311 memcpy( d->zStream.next_out, d->zStream.next_in, n ); 00312 d->zStream.avail_out -= n; 00313 d->zStream.next_in += n; 00314 d->zStream.avail_in -= n; 00315 return KFilterBase::Ok; 00316 } else 00317 return KFilterBase::End; 00318 } 00319 00320 KGzipFilter::Result KGzipFilter::uncompress() 00321 { 00322 #ifndef NDEBUG 00323 if (d->mode == 0) { 00324 qWarning() << "mode==0; KGzipFilter::init was not called!"; 00325 return KFilterBase::Error; 00326 } else if (d->mode == QIODevice::WriteOnly) { 00327 qWarning() << "uncompress called but the filter was opened for writing!"; 00328 return KFilterBase::Error; 00329 } 00330 Q_ASSERT ( d->mode == QIODevice::ReadOnly ); 00331 #endif 00332 00333 if ( d->compressed ) 00334 { 00335 #ifdef DEBUG_GZIP 00336 qDebug() << "Calling inflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 00337 qDebug() << " next_in=" << d->zStream.next_in; 00338 #endif 00339 int result = inflate(&d->zStream, Z_SYNC_FLUSH); 00340 #ifdef DEBUG_GZIP 00341 qDebug() << " -> inflate returned " << result; 00342 qDebug() << " now avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 00343 qDebug() << " next_in=" << d->zStream.next_in; 00344 #else 00345 if ( result != Z_OK && result != Z_STREAM_END ) 00346 qDebug() << "Warning: inflate() returned " << result; 00347 #endif 00348 return ( result == Z_OK ? KFilterBase::Ok : ( result == Z_STREAM_END ? KFilterBase::End : KFilterBase::Error ) ); 00349 } else 00350 return uncompress_noop(); 00351 } 00352 00353 KGzipFilter::Result KGzipFilter::compress( bool finish ) 00354 { 00355 Q_ASSERT ( d->compressed ); 00356 Q_ASSERT ( d->mode == QIODevice::WriteOnly ); 00357 00358 Bytef* p = d->zStream.next_in; 00359 ulong len = d->zStream.avail_in; 00360 #ifdef DEBUG_GZIP 00361 qDebug() << " calling deflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 00362 #endif 00363 const int result = deflate(&d->zStream, finish ? Z_FINISH : Z_NO_FLUSH); 00364 if ( result != Z_OK && result != Z_STREAM_END ) { 00365 qDebug() << " deflate returned " << result; 00366 } 00367 if ( d->headerWritten ) 00368 { 00369 //qDebug() << "Computing CRC for the next " << len - d->zStream.avail_in << " bytes"; 00370 d->crc = crc32(d->crc, p, len - d->zStream.avail_in); 00371 } 00372 KGzipFilter::Result callerResult = result == Z_OK ? KFilterBase::Ok : (Z_STREAM_END ? KFilterBase::End : KFilterBase::Error); 00373 00374 if (result == Z_STREAM_END && d->headerWritten && !d->footerWritten) { 00375 if (d->zStream.avail_out >= 8 /*footer size*/) { 00376 //qDebug() << "finished, write footer"; 00377 writeFooter(); 00378 } else { 00379 // No room to write the footer (#157706/#188415), we'll have to do it on the next pass. 00380 //qDebug() << "finished, but no room for footer yet"; 00381 callerResult = KFilterBase::Ok; 00382 } 00383 } 00384 return callerResult; 00385 }
KDE 4.7 API Reference