File indexing completed on 2025-02-16 13:00:36
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kgzipfilter.h" 0008 #include "loggingcategory.h" 0009 0010 #include <QDebug> 0011 #include <QIODevice> 0012 0013 #include <time.h> 0014 #include <zlib.h> 0015 0016 /* gzip flag byte */ 0017 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ 0018 0019 // #define DEBUG_GZIP 0020 0021 class Q_DECL_HIDDEN KGzipFilter::Private 0022 { 0023 public: 0024 Private() 0025 : headerWritten(false) 0026 , footerWritten(false) 0027 , compressed(false) 0028 , mode(0) 0029 , crc(0) 0030 , isInitialized(false) 0031 { 0032 zStream.zalloc = static_cast<alloc_func>(nullptr); 0033 zStream.zfree = static_cast<free_func>(nullptr); 0034 zStream.opaque = static_cast<voidpf>(nullptr); 0035 } 0036 0037 z_stream zStream; 0038 bool headerWritten; 0039 bool footerWritten; 0040 bool compressed; 0041 int mode; 0042 ulong crc; 0043 bool isInitialized; 0044 }; 0045 0046 KGzipFilter::KGzipFilter() 0047 : d(new Private) 0048 { 0049 } 0050 0051 KGzipFilter::~KGzipFilter() 0052 { 0053 delete d; 0054 } 0055 0056 bool KGzipFilter::init(int mode) 0057 { 0058 switch (filterFlags()) { 0059 case NoHeaders: 0060 return init(mode, RawDeflate); 0061 case WithHeaders: 0062 return init(mode, GZipHeader); 0063 case ZlibHeaders: 0064 return init(mode, ZlibHeader); 0065 } 0066 return false; 0067 } 0068 0069 bool KGzipFilter::init(int mode, Flag flag) 0070 { 0071 if (d->isInitialized) { 0072 terminate(); 0073 } 0074 d->zStream.next_in = Z_NULL; 0075 d->zStream.avail_in = 0; 0076 if (mode == QIODevice::ReadOnly) { 0077 const int windowBits = (flag == RawDeflate) ? -MAX_WBITS /*no zlib header*/ 0078 : (flag == GZipHeader) ? MAX_WBITS + 32 /* auto-detect and eat gzip header */ 0079 : MAX_WBITS /*zlib header*/; 0080 const int result = inflateInit2(&d->zStream, windowBits); 0081 if (result != Z_OK) { 0082 // qCDebug(KArchiveLog) << "inflateInit2 returned " << result; 0083 return false; 0084 } 0085 } else if (mode == QIODevice::WriteOnly) { 0086 int result = deflateInit2(&d->zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); // same here 0087 if (result != Z_OK) { 0088 // qCDebug(KArchiveLog) << "deflateInit returned " << result; 0089 return false; 0090 } 0091 } else { 0092 // qCWarning(KArchiveLog) << "KGzipFilter: Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported"; 0093 return false; 0094 } 0095 d->mode = mode; 0096 d->compressed = true; 0097 d->headerWritten = false; 0098 d->footerWritten = false; 0099 d->isInitialized = true; 0100 return true; 0101 } 0102 0103 int KGzipFilter::mode() const 0104 { 0105 return d->mode; 0106 } 0107 0108 bool KGzipFilter::terminate() 0109 { 0110 if (d->mode == QIODevice::ReadOnly) { 0111 int result = inflateEnd(&d->zStream); 0112 if (result != Z_OK) { 0113 // qCDebug(KArchiveLog) << "inflateEnd returned " << result; 0114 return false; 0115 } 0116 } else if (d->mode == QIODevice::WriteOnly) { 0117 int result = deflateEnd(&d->zStream); 0118 if (result != Z_OK) { 0119 // qCDebug(KArchiveLog) << "deflateEnd returned " << result; 0120 return false; 0121 } 0122 } 0123 d->isInitialized = false; 0124 return true; 0125 } 0126 0127 void KGzipFilter::reset() 0128 { 0129 if (d->mode == QIODevice::ReadOnly) { 0130 int result = inflateReset(&d->zStream); 0131 if (result != Z_OK) { 0132 // qCDebug(KArchiveLog) << "inflateReset returned " << result; 0133 // TODO return false 0134 } 0135 } else if (d->mode == QIODevice::WriteOnly) { 0136 int result = deflateReset(&d->zStream); 0137 if (result != Z_OK) { 0138 // qCDebug(KArchiveLog) << "deflateReset returned " << result; 0139 // TODO return false 0140 } 0141 d->headerWritten = false; 0142 d->footerWritten = false; 0143 } 0144 } 0145 0146 bool KGzipFilter::readHeader() 0147 { 0148 // We now rely on zlib to read the full header (see the MAX_WBITS + 32 in init). 0149 // We just use this method to check if the data is actually compressed. 0150 0151 #ifdef DEBUG_GZIP 0152 qCDebug(KArchiveLog) << "avail=" << d->zStream.avail_in; 0153 #endif 0154 // Assume not compressed until we see a gzip header 0155 d->compressed = false; 0156 const Bytef *p = d->zStream.next_in; 0157 int i = d->zStream.avail_in; 0158 if ((i -= 10) < 0) { 0159 return false; // Need at least 10 bytes 0160 } 0161 #ifdef DEBUG_GZIP 0162 qCDebug(KArchiveLog) << "first byte is " << QString::number(*p, 16); 0163 #endif 0164 if (*p++ != 0x1f) { 0165 return false; // GZip magic 0166 } 0167 #ifdef DEBUG_GZIP 0168 qCDebug(KArchiveLog) << "second byte is " << QString::number(*p, 16); 0169 #endif 0170 if (*p++ != 0x8b) { 0171 return false; 0172 } 0173 0174 d->compressed = true; 0175 #ifdef DEBUG_GZIP 0176 qCDebug(KArchiveLog) << "header OK"; 0177 #endif 0178 return true; 0179 } 0180 0181 /* Output a 16 bit value, lsb first */ 0182 #define put_short(w) \ 0183 *p++ = uchar((w)&0xff); \ 0184 *p++ = uchar(ushort(w) >> 8); 0185 0186 /* Output a 32 bit value to the bit stream, lsb first */ 0187 #define put_long(n) \ 0188 put_short((n)&0xffff); \ 0189 put_short((ulong(n)) >> 16); 0190 0191 bool KGzipFilter::writeHeader(const QByteArray &fileName) 0192 { 0193 Bytef *p = d->zStream.next_out; 0194 int i = d->zStream.avail_out; 0195 *p++ = 0x1f; 0196 *p++ = 0x8b; 0197 *p++ = Z_DEFLATED; 0198 *p++ = ORIG_NAME; 0199 put_long(time(nullptr)); // Modification time (in unix format) 0200 *p++ = 0; // Extra flags (2=max compress, 4=fastest compress) 0201 *p++ = 3; // Unix 0202 0203 uint len = fileName.length(); 0204 for (uint j = 0; j < len; ++j) { 0205 *p++ = fileName[j]; 0206 } 0207 *p++ = 0; 0208 int headerSize = p - d->zStream.next_out; 0209 i -= headerSize; 0210 Q_ASSERT(i > 0); 0211 d->crc = crc32(0L, nullptr, 0); 0212 d->zStream.next_out = p; 0213 d->zStream.avail_out = i; 0214 d->headerWritten = true; 0215 return true; 0216 } 0217 0218 void KGzipFilter::writeFooter() 0219 { 0220 Q_ASSERT(d->headerWritten); 0221 Q_ASSERT(!d->footerWritten); 0222 Bytef *p = d->zStream.next_out; 0223 int i = d->zStream.avail_out; 0224 // qCDebug(KArchiveLog) << "avail_out=" << i << "writing CRC=" << QString::number(d->crc, 16) << "at p=" << p; 0225 put_long(d->crc); 0226 // qCDebug(KArchiveLog) << "writing totalin=" << d->zStream.total_in << "at p=" << p; 0227 put_long(d->zStream.total_in); 0228 i -= p - d->zStream.next_out; 0229 d->zStream.next_out = p; 0230 d->zStream.avail_out = i; 0231 d->footerWritten = true; 0232 } 0233 0234 void KGzipFilter::setOutBuffer(char *data, uint maxlen) 0235 { 0236 d->zStream.avail_out = maxlen; 0237 d->zStream.next_out = reinterpret_cast<Bytef *>(data); 0238 } 0239 void KGzipFilter::setInBuffer(const char *data, uint size) 0240 { 0241 #ifdef DEBUG_GZIP 0242 qCDebug(KArchiveLog) << "avail_in=" << size; 0243 #endif 0244 d->zStream.avail_in = size; 0245 d->zStream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data)); 0246 } 0247 int KGzipFilter::inBufferAvailable() const 0248 { 0249 return d->zStream.avail_in; 0250 } 0251 int KGzipFilter::outBufferAvailable() const 0252 { 0253 return d->zStream.avail_out; 0254 } 0255 0256 KGzipFilter::Result KGzipFilter::uncompress_noop() 0257 { 0258 // I'm not sure we really need support for that (uncompressed streams), 0259 // but why not, it can't hurt to have it. One case I can think of is someone 0260 // naming a tar file "blah.tar.gz" :-) 0261 if (d->zStream.avail_in > 0) { 0262 int n = (d->zStream.avail_in < d->zStream.avail_out) ? d->zStream.avail_in : d->zStream.avail_out; 0263 memcpy(d->zStream.next_out, d->zStream.next_in, n); 0264 d->zStream.avail_out -= n; 0265 d->zStream.next_in += n; 0266 d->zStream.avail_in -= n; 0267 return KFilterBase::Ok; 0268 } else { 0269 return KFilterBase::End; 0270 } 0271 } 0272 0273 KGzipFilter::Result KGzipFilter::uncompress() 0274 { 0275 #ifndef NDEBUG 0276 if (d->mode == 0) { 0277 // qCWarning(KArchiveLog) << "mode==0; KGzipFilter::init was not called!"; 0278 return KFilterBase::Error; 0279 } else if (d->mode == QIODevice::WriteOnly) { 0280 // qCWarning(KArchiveLog) << "uncompress called but the filter was opened for writing!"; 0281 return KFilterBase::Error; 0282 } 0283 Q_ASSERT(d->mode == QIODevice::ReadOnly); 0284 #endif 0285 0286 if (!d->compressed) { 0287 return uncompress_noop(); 0288 } 0289 0290 #ifdef DEBUG_GZIP 0291 qCDebug(KArchiveLog) << "Calling inflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 0292 qCDebug(KArchiveLog) << " next_in=" << d->zStream.next_in; 0293 #endif 0294 0295 while (d->zStream.avail_in > 0) { 0296 int result = inflate(&d->zStream, Z_SYNC_FLUSH); 0297 0298 #ifdef DEBUG_GZIP 0299 qCDebug(KArchiveLog) << " -> inflate returned " << result; 0300 qCDebug(KArchiveLog) << " now avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 0301 qCDebug(KArchiveLog) << " next_in=" << d->zStream.next_in; 0302 #endif 0303 0304 if (result == Z_OK) { 0305 return KFilterBase::Ok; 0306 } 0307 0308 // We can't handle any other results 0309 if (result != Z_STREAM_END) { 0310 return KFilterBase::Error; 0311 } 0312 0313 // It really was the end 0314 if (d->zStream.avail_in == 0) { 0315 return KFilterBase::End; 0316 } 0317 0318 // Store before resetting 0319 Bytef *data = d->zStream.next_in; // This is increased appropriately by zlib beforehand 0320 uInt size = d->zStream.avail_in; 0321 0322 // Reset the stream, if that fails we assume we're at the end 0323 if (!init(d->mode)) { 0324 return KFilterBase::End; 0325 } 0326 0327 // Reset the data to where we left off 0328 d->zStream.next_in = data; 0329 d->zStream.avail_in = size; 0330 } 0331 0332 return KFilterBase::End; 0333 } 0334 0335 KGzipFilter::Result KGzipFilter::compress(bool finish) 0336 { 0337 Q_ASSERT(d->compressed); 0338 Q_ASSERT(d->mode == QIODevice::WriteOnly); 0339 0340 const Bytef *p = d->zStream.next_in; 0341 ulong len = d->zStream.avail_in; 0342 #ifdef DEBUG_GZIP 0343 qCDebug(KArchiveLog) << " calling deflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable(); 0344 #endif 0345 const int result = deflate(&d->zStream, finish ? Z_FINISH : Z_NO_FLUSH); 0346 if (result != Z_OK && result != Z_STREAM_END) { 0347 // qCDebug(KArchiveLog) << " deflate returned " << result; 0348 } 0349 if (d->headerWritten) { 0350 // qCDebug(KArchiveLog) << "Computing CRC for the next " << len - d->zStream.avail_in << " bytes"; 0351 d->crc = crc32(d->crc, p, len - d->zStream.avail_in); 0352 } 0353 KGzipFilter::Result callerResult = result == Z_OK ? KFilterBase::Ok : (Z_STREAM_END ? KFilterBase::End : KFilterBase::Error); 0354 0355 if (result == Z_STREAM_END && d->headerWritten && !d->footerWritten) { 0356 if (d->zStream.avail_out >= 8 /*footer size*/) { 0357 // qCDebug(KArchiveLog) << "finished, write footer"; 0358 writeFooter(); 0359 } else { 0360 // No room to write the footer (#157706/#188415), we'll have to do it on the next pass. 0361 // qCDebug(KArchiveLog) << "finished, but no room for footer yet"; 0362 callerResult = KFilterBase::Ok; 0363 } 0364 } 0365 return callerResult; 0366 }