KArchive

kgzipfilter.cpp
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kgzipfilter.h"
8#include "loggingcategory.h"
9
10#include <QDebug>
11#include <QIODevice>
12
13#include <time.h>
14#include <zlib.h>
15
16/* gzip flag byte */
17#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
18
19// #define DEBUG_GZIP
20
21static_assert(Z_NULL == 0, "zlib API has changed. We no longer can use nullptr or zero instead of Z_NULL.");
22
23class Q_DECL_HIDDEN KGzipFilter::Private
24{
25public:
26 Private()
27 : headerWritten(false)
28 , footerWritten(false)
29 , compressed(false)
30 , mode(0)
31 , crc(0)
32 , isInitialized(false)
33 {
34 zStream.zalloc = static_cast<alloc_func>(nullptr);
35 zStream.zfree = static_cast<free_func>(nullptr);
36 zStream.opaque = static_cast<voidpf>(nullptr);
37 }
38
39 z_stream zStream;
40 bool headerWritten;
41 bool footerWritten;
42 bool compressed;
43 int mode;
44 ulong crc;
45 bool isInitialized;
46};
47
48KGzipFilter::KGzipFilter()
49 : d(new Private)
50{
51}
52
53KGzipFilter::~KGzipFilter()
54{
55 delete d;
56}
57
59{
60 switch (filterFlags()) {
61 case NoHeaders:
62 return init(mode, RawDeflate);
63 case WithHeaders:
64 return init(mode, GZipHeader);
65 case ZlibHeaders:
66 return init(mode, ZlibHeader);
67 }
68 return false;
69}
70
71bool KGzipFilter::init(int mode, Flag flag)
72{
73 if (d->isInitialized) {
74 terminate();
75 }
76 d->zStream.next_in = nullptr;
77 d->zStream.avail_in = 0;
79 const int windowBits = (flag == RawDeflate) ? -MAX_WBITS /*no zlib header*/
80 : (flag == GZipHeader) ? MAX_WBITS + 32 /* auto-detect and eat gzip header */
81 : MAX_WBITS /*zlib header*/;
82 const int result = inflateInit2(&d->zStream, windowBits);
83 if (result != Z_OK) {
84 // qCDebug(KArchiveLog) << "inflateInit2 returned " << result;
85 return false;
86 }
87 } else if (mode == QIODevice::WriteOnly) {
88 int result = deflateInit2(&d->zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); // same here
89 if (result != Z_OK) {
90 // qCDebug(KArchiveLog) << "deflateInit returned " << result;
91 return false;
92 }
93 } else {
94 // qCWarning(KArchiveLog) << "KGzipFilter: Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
95 return false;
96 }
97 d->mode = mode;
98 d->compressed = true;
99 d->headerWritten = false;
100 d->footerWritten = false;
101 d->isInitialized = true;
102 return true;
103}
104
106{
107 return d->mode;
108}
109
111{
112 if (d->mode == QIODevice::ReadOnly) {
113 int result = inflateEnd(&d->zStream);
114 if (result != Z_OK) {
115 // qCDebug(KArchiveLog) << "inflateEnd returned " << result;
116 return false;
117 }
118 } else if (d->mode == QIODevice::WriteOnly) {
119 int result = deflateEnd(&d->zStream);
120 if (result != Z_OK) {
121 // qCDebug(KArchiveLog) << "deflateEnd returned " << result;
122 return false;
123 }
124 }
125 d->isInitialized = false;
126 return true;
127}
128
130{
131 if (d->mode == QIODevice::ReadOnly) {
132 int result = inflateReset(&d->zStream);
133 if (result != Z_OK) {
134 // qCDebug(KArchiveLog) << "inflateReset returned " << result;
135 // TODO return false
136 }
137 } else if (d->mode == QIODevice::WriteOnly) {
138 int result = deflateReset(&d->zStream);
139 if (result != Z_OK) {
140 // qCDebug(KArchiveLog) << "deflateReset returned " << result;
141 // TODO return false
142 }
143 d->headerWritten = false;
144 d->footerWritten = false;
145 }
146}
147
149{
150 // We now rely on zlib to read the full header (see the MAX_WBITS + 32 in init).
151 // We just use this method to check if the data is actually compressed.
152
153#ifdef DEBUG_GZIP
154 qCDebug(KArchiveLog) << "avail=" << d->zStream.avail_in;
155#endif
156 // Assume not compressed until we see a gzip header
157 d->compressed = false;
158 const Bytef *p = d->zStream.next_in;
159 int i = d->zStream.avail_in;
160 if ((i -= 10) < 0) {
161 return false; // Need at least 10 bytes
162 }
163#ifdef DEBUG_GZIP
164 qCDebug(KArchiveLog) << "first byte is " << QString::number(*p, 16);
165#endif
166 if (*p++ != 0x1f) {
167 return false; // GZip magic
168 }
169#ifdef DEBUG_GZIP
170 qCDebug(KArchiveLog) << "second byte is " << QString::number(*p, 16);
171#endif
172 if (*p++ != 0x8b) {
173 return false;
174 }
175
176 d->compressed = true;
177#ifdef DEBUG_GZIP
178 qCDebug(KArchiveLog) << "header OK";
179#endif
180 return true;
181}
182
183/* Output a 16 bit value, lsb first */
184#define put_short(w) \
185 *p++ = uchar((w)&0xff); \
186 *p++ = uchar(ushort(w) >> 8);
187
188/* Output a 32 bit value to the bit stream, lsb first */
189#define put_long(n) \
190 put_short((n)&0xffff); \
191 put_short((ulong(n)) >> 16);
192
194{
195 Bytef *p = d->zStream.next_out;
196 int i = d->zStream.avail_out;
197 *p++ = 0x1f;
198 *p++ = 0x8b;
199 *p++ = Z_DEFLATED;
200 *p++ = ORIG_NAME;
201 put_long(time(nullptr)); // Modification time (in unix format)
202 *p++ = 0; // Extra flags (2=max compress, 4=fastest compress)
203 *p++ = 3; // Unix
204
205 uint len = fileName.length();
206 for (uint j = 0; j < len; ++j) {
207 *p++ = fileName[j];
208 }
209 *p++ = 0;
210 int headerSize = p - d->zStream.next_out;
211 i -= headerSize;
212 Q_ASSERT(i > 0);
213 d->crc = crc32(0L, nullptr, 0);
214 d->zStream.next_out = p;
215 d->zStream.avail_out = i;
216 d->headerWritten = true;
217 return true;
218}
219
220void KGzipFilter::writeFooter()
221{
222 Q_ASSERT(d->headerWritten);
223 Q_ASSERT(!d->footerWritten);
224 Bytef *p = d->zStream.next_out;
225 int i = d->zStream.avail_out;
226 // qCDebug(KArchiveLog) << "avail_out=" << i << "writing CRC=" << QString::number(d->crc, 16) << "at p=" << p;
227 put_long(d->crc);
228 // qCDebug(KArchiveLog) << "writing totalin=" << d->zStream.total_in << "at p=" << p;
229 put_long(d->zStream.total_in);
230 i -= p - d->zStream.next_out;
231 d->zStream.next_out = p;
232 d->zStream.avail_out = i;
233 d->footerWritten = true;
234}
235
236void KGzipFilter::setOutBuffer(char *data, uint maxlen)
237{
238 d->zStream.avail_out = maxlen;
239 d->zStream.next_out = reinterpret_cast<Bytef *>(data);
240}
241void KGzipFilter::setInBuffer(const char *data, uint size)
242{
243#ifdef DEBUG_GZIP
244 qCDebug(KArchiveLog) << "avail_in=" << size;
245#endif
246 d->zStream.avail_in = size;
247 d->zStream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data));
248}
250{
251 return d->zStream.avail_in;
252}
254{
255 return d->zStream.avail_out;
256}
257
258KGzipFilter::Result KGzipFilter::uncompress_noop()
259{
260 // I'm not sure that we really need support for that (uncompressed streams),
261 // but why not, it can't hurt to have it. One case I can think of is someone
262 // naming a tar file "blah.tar.gz" :-)
263 if (d->zStream.avail_in > 0) {
264 int n = (d->zStream.avail_in < d->zStream.avail_out) ? d->zStream.avail_in : d->zStream.avail_out;
265 memcpy(d->zStream.next_out, d->zStream.next_in, n);
266 d->zStream.avail_out -= n;
267 d->zStream.next_in += n;
268 d->zStream.avail_in -= n;
269 return KFilterBase::Ok;
270 } else {
271 return KFilterBase::End;
272 }
273}
274
276{
277#ifndef NDEBUG
278 if (d->mode == 0) {
279 // qCWarning(KArchiveLog) << "mode==0; KGzipFilter::init was not called!";
280 return KFilterBase::Error;
281 } else if (d->mode == QIODevice::WriteOnly) {
282 // qCWarning(KArchiveLog) << "uncompress called but the filter was opened for writing!";
283 return KFilterBase::Error;
284 }
285 Q_ASSERT(d->mode == QIODevice::ReadOnly);
286#endif
287
288 if (!d->compressed) {
289 return uncompress_noop();
290 }
291
292#ifdef DEBUG_GZIP
293 qCDebug(KArchiveLog) << "Calling inflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
294 qCDebug(KArchiveLog) << " next_in=" << d->zStream.next_in;
295#endif
296
297 while (d->zStream.avail_in > 0) {
298 int result = inflate(&d->zStream, Z_SYNC_FLUSH);
299
300#ifdef DEBUG_GZIP
301 qCDebug(KArchiveLog) << " -> inflate returned " << result;
302 qCDebug(KArchiveLog) << " now avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
303 qCDebug(KArchiveLog) << " next_in=" << d->zStream.next_in;
304#endif
305
306 if (result == Z_OK) {
307 return KFilterBase::Ok;
308 }
309
310 // We can't handle any other results
311 if (result != Z_STREAM_END) {
312 return KFilterBase::Error;
313 }
314
315 // It really was the end
316 if (d->zStream.avail_in == 0) {
317 return KFilterBase::End;
318 }
319
320 // Store before resetting
321 Bytef *data = d->zStream.next_in; // This is increased appropriately by zlib beforehand
322 uInt size = d->zStream.avail_in;
323
324 // Reset the stream, if that fails we assume we're at the end
325 if (!init(d->mode)) {
326 return KFilterBase::End;
327 }
328
329 // Reset the data to where we left off
330 d->zStream.next_in = data;
331 d->zStream.avail_in = size;
332 }
333
334 return KFilterBase::End;
335}
336
338{
339 Q_ASSERT(d->compressed);
340 Q_ASSERT(d->mode == QIODevice::WriteOnly);
341
342 const Bytef *p = d->zStream.next_in;
343 ulong len = d->zStream.avail_in;
344#ifdef DEBUG_GZIP
345 qCDebug(KArchiveLog) << " calling deflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
346#endif
347 const int result = deflate(&d->zStream, finish ? Z_FINISH : Z_NO_FLUSH);
348 if (result != Z_OK && result != Z_STREAM_END) {
349 // qCDebug(KArchiveLog) << " deflate returned " << result;
350 }
351 if (d->headerWritten) {
352 // qCDebug(KArchiveLog) << "Computing CRC for the next " << len - d->zStream.avail_in << " bytes";
353 d->crc = crc32(d->crc, p, len - d->zStream.avail_in);
354 }
355 KGzipFilter::Result callerResult = result == Z_OK ? KFilterBase::Ok : (Z_STREAM_END ? KFilterBase::End : KFilterBase::Error);
356
357 if (result == Z_STREAM_END && d->headerWritten && !d->footerWritten) {
358 if (d->zStream.avail_out >= 8 /*footer size*/) {
359 // qCDebug(KArchiveLog) << "finished, write footer";
360 writeFooter();
361 } else {
362 // No room to write the footer (#157706/#188415), we'll have to do it on the next pass.
363 // qCDebug(KArchiveLog) << "finished, but no room for footer yet";
364 callerResult = KFilterBase::Ok;
365 }
366 }
367 return callerResult;
368}
Internal class used by KCompressionDevice.
Definition kgzipfilter.h:20
bool writeHeader(const QByteArray &fileName) override
void setInBuffer(const char *data, uint size) override
void reset() override
Result compress(bool finish) override
int inBufferAvailable() const override
void setOutBuffer(char *data, uint maxlen) override
int outBufferAvailable() const override
bool readHeader() override
int mode() const override
bool terminate() override
bool init(int mode) override
Result uncompress() override
qsizetype length() const const
QString number(double n, char format, int precision)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 4 2025 12:10:52 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.