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 | |
21 | static_assert(Z_NULL == 0, "zlib API has changed. We no longer can use nullptr or zero instead of Z_NULL." ); |
22 | |
23 | class Q_DECL_HIDDEN KGzipFilter::Private |
24 | { |
25 | public: |
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 ; |
41 | bool ; |
42 | bool compressed; |
43 | int mode; |
44 | ulong crc; |
45 | bool isInitialized; |
46 | }; |
47 | |
48 | KGzipFilter::KGzipFilter() |
49 | : d(new Private) |
50 | { |
51 | } |
52 | |
53 | KGzipFilter::~KGzipFilter() |
54 | { |
55 | delete d; |
56 | } |
57 | |
58 | bool KGzipFilter::init(int mode) |
59 | { |
60 | switch (filterFlags()) { |
61 | case NoHeaders: |
62 | return init(mode, flag: RawDeflate); |
63 | case WithHeaders: |
64 | return init(mode, flag: GZipHeader); |
65 | case ZlibHeaders: |
66 | return init(mode, flag: ZlibHeader); |
67 | } |
68 | return false; |
69 | } |
70 | |
71 | bool 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; |
78 | if (mode == QIODevice::ReadOnly) { |
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 | |
105 | int KGzipFilter::mode() const |
106 | { |
107 | return d->mode; |
108 | } |
109 | |
110 | bool KGzipFilter::terminate() |
111 | { |
112 | if (d->mode == QIODevice::ReadOnly) { |
113 | int result = inflateEnd(strm: &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(strm: &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 | |
129 | void KGzipFilter::reset() |
130 | { |
131 | if (d->mode == QIODevice::ReadOnly) { |
132 | int result = inflateReset(strm: &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(strm: &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 | |
148 | bool KGzipFilter::() |
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 | |
193 | bool KGzipFilter::(const QByteArray &fileName) |
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 = p - d->zStream.next_out; |
211 | i -= headerSize; |
212 | Q_ASSERT(i > 0); |
213 | d->crc = crc32(crc: 0L, buf: nullptr, len: 0); |
214 | d->zStream.next_out = p; |
215 | d->zStream.avail_out = i; |
216 | d->headerWritten = true; |
217 | return true; |
218 | } |
219 | |
220 | void KGzipFilter::() |
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 | |
236 | void KGzipFilter::setOutBuffer(char *data, uint maxlen) |
237 | { |
238 | d->zStream.avail_out = maxlen; |
239 | d->zStream.next_out = reinterpret_cast<Bytef *>(data); |
240 | } |
241 | void 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 | } |
249 | int KGzipFilter::inBufferAvailable() const |
250 | { |
251 | return d->zStream.avail_in; |
252 | } |
253 | int KGzipFilter::outBufferAvailable() const |
254 | { |
255 | return d->zStream.avail_out; |
256 | } |
257 | |
258 | KGzipFilter::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(dest: d->zStream.next_out, src: d->zStream.next_in, n: 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 | |
275 | KGzipFilter::Result KGzipFilter::uncompress() |
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(strm: &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(mode: 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 | |
337 | KGzipFilter::Result KGzipFilter::compress(bool finish) |
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(strm: &d->zStream, flush: 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(crc: d->crc, buf: p, len: 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 | } |
369 | |