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

KDE's Doxygen guidelines are available online.