KArchive

kgzipfilter.cpp
1 /* This file is part of the KDE libraries
2  SPDX-FileCopyrightText: 2000-2005 David Faure <[email protected]>
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 class Q_DECL_HIDDEN KGzipFilter::Private
22 {
23 public:
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 
46 KGzipFilter::KGzipFilter()
47  : d(new Private)
48 {
49 }
50 
51 KGzipFilter::~KGzipFilter()
52 {
53  delete d;
54 }
55 
56 bool 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 
69 bool 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;
76  if (mode == QIODevice::ReadOnly) {
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 
103 int KGzipFilter::mode() const
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 
218 void 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 
234 void KGzipFilter::setOutBuffer(char *data, uint maxlen)
235 {
236  d->zStream.avail_out = maxlen;
237  d->zStream.next_out = reinterpret_cast<Bytef *>(data);
238 }
239 void 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 
256 KGzipFilter::Result KGzipFilter::uncompress_noop()
257 {
258  // I'm not sure 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 }
QString number(int n, int base)
bool readHeader() override
int outBufferAvailable() const override
bool terminate() override
Result uncompress() override
int mode() const override
Internal class used by KCompressionDevice.
Definition: kgzipfilter.h:19
Result compress(bool finish) override
int length() const const
bool init(int mode) override
Definition: kgzipfilter.cpp:56
void setInBuffer(const char *data, uint size) override
void reset() override
void setOutBuffer(char *data, uint maxlen) override
int inBufferAvailable() const override
bool writeHeader(const QByteArray &fileName) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Aug 16 2022 04:03:12 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.