Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qdecompresshelper.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/qiodevice.h>
7#include <QtCore/qcoreapplication.h>
8
9#include <limits>
10#include <zlib.h>
11
12#if QT_CONFIG(brotli)
13# include <brotli/decode.h>
14#endif
15
16#if QT_CONFIG(zstd)
17# include <zstd.h>
18#endif
19
20#include <array>
21
23namespace {
29
31#if QT_CONFIG(zstd)
33#endif
34#if QT_CONFIG(brotli)
36#endif
37 { "gzip", QDecompressHelper::GZip },
38 { "deflate", QDecompressHelper::Deflate },
39};
40
42{
43 for (const auto &mapping : contentEncodingMapping) {
44 if (ce.compare(mapping.name, Qt::CaseInsensitive) == 0)
45 return mapping.encoding;
46 }
48}
49
50z_stream *toZlibPointer(void *ptr)
51{
52 return static_cast<z_stream_s *>(ptr);
53}
54
55#if QT_CONFIG(brotli)
56BrotliDecoderState *toBrotliPointer(void *ptr)
57{
58 return static_cast<BrotliDecoderState *>(ptr);
59}
60#endif
61
62#if QT_CONFIG(zstd)
63ZSTD_DStream *toZstandardPointer(void *ptr)
64{
65 return static_cast<ZSTD_DStream *>(ptr);
66}
67#endif
68}
69
71{
72 return encodingFromByteArray(encoding) != QDecompressHelper::None;
73}
74
76{
78 list.reserve(std::size(contentEncodingMapping));
79 for (const auto &mapping : contentEncodingMapping) {
80 list << mapping.name.toByteArray();
81 }
82 return list;
83}
84
89
91{
92 Q_ASSERT(contentEncoding == QDecompressHelper::None);
93 if (contentEncoding != QDecompressHelper::None) {
94 qWarning("Encoding is already set.");
95 // This isn't an error, so it doesn't set errorStr, it's just wrong usage.
96 return false;
97 }
98 ContentEncoding ce = encodingFromByteArray(encoding);
99 if (ce == None) {
100 errorStr = QCoreApplication::translate("QHttp", "Unsupported content encoding: %1")
101 .arg(QLatin1String(encoding));
102 return false;
103 }
104 errorStr = QString(); // clear error
105 return setEncoding(ce);
106}
107
108bool QDecompressHelper::setEncoding(ContentEncoding ce)
109{
110 Q_ASSERT(contentEncoding == None);
111 contentEncoding = ce;
112 switch (contentEncoding) {
113 case None:
114 Q_UNREACHABLE();
115 break;
116 case Deflate:
117 case GZip: {
118 z_stream *inflateStream = new z_stream;
119 memset(inflateStream, 0, sizeof(z_stream));
120 // "windowBits can also be greater than 15 for optional gzip decoding.
121 // Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
122 // http://www.zlib.net/manual.html
123 if (inflateInit2(inflateStream, MAX_WBITS + 32) != Z_OK) {
124 delete inflateStream;
125 inflateStream = nullptr;
126 }
127 decoderPointer = inflateStream;
128 break;
129 }
130 case Brotli:
131#if QT_CONFIG(brotli)
132 decoderPointer = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
133#else
134 Q_UNREACHABLE();
135#endif
136 break;
137 case Zstandard:
138#if QT_CONFIG(zstd)
139 decoderPointer = ZSTD_createDStream();
140#else
141 Q_UNREACHABLE();
142#endif
143 break;
144 }
145 if (!decoderPointer) {
146 errorStr = QCoreApplication::translate("QHttp",
147 "Failed to initialize the compression decoder.");
148 contentEncoding = QDecompressHelper::None;
149 return false;
150 }
151 return true;
152}
153
163{
164 return countDecompressed;
165}
166
181{
182 // These are a best-effort check to ensure that no data has already been processed before this
183 // gets enabled
184 Q_ASSERT(compressedDataBuffer.byteAmount() == 0);
185 Q_ASSERT(contentEncoding == None);
186 countDecompressed = shouldCount;
187}
188
203{
204 Q_ASSERT(countDecompressed);
205 // Use the 'totalUncompressedBytes' from the countHelper if it exceeds the amount of bytes
206 // that we know about.
207 auto totalUncompressed =
208 countHelper && countHelper->totalUncompressedBytes > totalUncompressedBytes
209 ? countHelper->totalUncompressedBytes
210 : totalUncompressedBytes;
211 return totalUncompressed - totalBytesRead;
212}
213
219{
220 return feed(QByteArray(data));
221}
222
233{
234 Q_ASSERT(contentEncoding != None);
235 totalCompressedBytes += data.size();
236 compressedDataBuffer.append(std::move(data));
237 if (!countInternal(compressedDataBuffer[compressedDataBuffer.bufferCount() - 1]))
238 clear(); // If our counting brother failed then so will we :|
239}
240
246{
247 Q_ASSERT(contentEncoding != None);
248 totalCompressedBytes += buffer.byteAmount();
249 compressedDataBuffer.append(buffer);
250 if (!countInternal(buffer))
251 clear(); // If our counting brother failed then so will we :|
252}
253
259{
260 Q_ASSERT(contentEncoding != None);
261 totalCompressedBytes += buffer.byteAmount();
263 compressedDataBuffer.append(std::move(buffer));
264 if (!countInternal(copy))
265 clear(); // If our counting brother failed then so will we :|
266}
267
282bool QDecompressHelper::countInternal()
283{
284 Q_ASSERT(countDecompressed);
285 while (hasDataInternal()
286 && decompressedDataBuffer.byteAmount() < MaxDecompressedDataBufferSize) {
287 const qsizetype toRead = 256 * 1024;
289 qsizetype bytesRead = readInternal(buffer.data(), buffer.size());
290 if (bytesRead == -1)
291 return false;
292 buffer.truncate(bytesRead);
293 decompressedDataBuffer.append(std::move(buffer));
294 }
295 if (!hasDataInternal())
296 return true; // handled all the data so far, just return
297
298 while (countHelper->hasData()) {
299 std::array<char, 1024> temp;
300 qsizetype bytesRead = countHelper->read(temp.data(), temp.size());
301 if (bytesRead == -1)
302 return false;
303 }
304 return true;
305}
306
311bool QDecompressHelper::countInternal(const QByteArray &data)
312{
313 if (countDecompressed) {
314 if (!countHelper) {
315 countHelper = std::make_unique<QDecompressHelper>();
316 countHelper->setDecompressedSafetyCheckThreshold(archiveBombCheckThreshold);
317 countHelper->setEncoding(contentEncoding);
318 }
319 countHelper->feed(data);
320 return countInternal();
321 }
322 return true;
323}
324
329bool QDecompressHelper::countInternal(const QByteDataBuffer &buffer)
330{
331 if (countDecompressed) {
332 if (!countHelper) {
333 countHelper = std::make_unique<QDecompressHelper>();
334 countHelper->setDecompressedSafetyCheckThreshold(archiveBombCheckThreshold);
335 countHelper->setEncoding(contentEncoding);
336 }
337 countHelper->feed(buffer);
338 return countInternal();
339 }
340 return true;
341}
342
344{
345 if (maxSize <= 0)
346 return 0;
347
348 if (!isValid())
349 return -1;
350
351 if (!hasData())
352 return 0;
353
354 qsizetype cachedRead = 0;
355 if (!decompressedDataBuffer.isEmpty()) {
356 cachedRead = decompressedDataBuffer.read(data, maxSize);
357 data += cachedRead;
358 maxSize -= cachedRead;
359 }
360
361 qsizetype bytesRead = readInternal(data, maxSize);
362 if (bytesRead == -1)
363 return -1;
364 totalBytesRead += bytesRead + cachedRead;
365 return bytesRead + cachedRead;
366}
367
373qsizetype QDecompressHelper::readInternal(char *data, qsizetype maxSize)
374{
375 Q_ASSERT(isValid());
376
377 if (maxSize <= 0)
378 return 0;
379
380 if (!hasDataInternal())
381 return 0;
382
383 qsizetype bytesRead = -1;
384 switch (contentEncoding) {
385 case None:
386 Q_UNREACHABLE();
387 break;
388 case Deflate:
389 case GZip:
390 bytesRead = readZLib(data, maxSize);
391 break;
392 case Brotli:
393 bytesRead = readBrotli(data, maxSize);
394 break;
395 case Zstandard:
396 bytesRead = readZstandard(data, maxSize);
397 break;
398 }
399 if (bytesRead == -1)
400 clear();
401
402 totalUncompressedBytes += bytesRead;
403 if (isPotentialArchiveBomb()) {
405 "QHttp",
406 "The decompressed output exceeds the limits specified by "
407 "QNetworkRequest::decompressedSafetyCheckThreshold()");
408 return -1;
409 }
410
411 return bytesRead;
412}
413
421{
422 if (threshold == -1)
423 threshold = std::numeric_limits<qint64>::max();
424 archiveBombCheckThreshold = threshold;
425}
426
427bool QDecompressHelper::isPotentialArchiveBomb() const
428{
429 if (totalCompressedBytes == 0)
430 return false;
431
432 if (totalUncompressedBytes <= archiveBombCheckThreshold)
433 return false;
434
435 // Some protection against malicious or corrupted compressed files that expand far more than
436 // is reasonable.
437 double ratio = double(totalUncompressedBytes) / double(totalCompressedBytes);
438 switch (contentEncoding) {
439 case None:
440 Q_UNREACHABLE();
441 break;
442 case Deflate:
443 case GZip:
444 // This value is mentioned in docs for
445 // QNetworkRequest::setMinimumArchiveBombSize, keep synchronized
446 if (ratio > 40) {
447 return true;
448 }
449 break;
450 case Brotli:
451 case Zstandard:
452 // This value is mentioned in docs for
453 // QNetworkRequest::setMinimumArchiveBombSize, keep synchronized
454 if (ratio > 100) {
455 return true;
456 }
457 break;
458 }
459 return false;
460}
461
471{
472 return hasDataInternal() || !decompressedDataBuffer.isEmpty();
473}
474
480bool QDecompressHelper::hasDataInternal() const
481{
482 return encodedBytesAvailable() || decoderHasData;
483}
484
485qint64 QDecompressHelper::encodedBytesAvailable() const
486{
487 return compressedDataBuffer.byteAmount();
488}
489
498{
499 return contentEncoding != None;
500}
501
509{
510 return errorStr;
511}
512
514{
515 switch (contentEncoding) {
516 case None:
517 break;
518 case Deflate:
519 case GZip: {
520 z_stream *inflateStream = toZlibPointer(decoderPointer);
521 if (inflateStream)
522 inflateEnd(inflateStream);
523 delete inflateStream;
524 break;
525 }
526 case Brotli: {
527#if QT_CONFIG(brotli)
528 BrotliDecoderState *brotliDecoderState = toBrotliPointer(decoderPointer);
529 if (brotliDecoderState)
530 BrotliDecoderDestroyInstance(brotliDecoderState);
531#endif
532 break;
533 }
534 case Zstandard: {
535#if QT_CONFIG(zstd)
536 ZSTD_DStream *zstdStream = toZstandardPointer(decoderPointer);
537 if (zstdStream)
538 ZSTD_freeDStream(zstdStream);
539#endif
540 break;
541 }
542 }
543 decoderPointer = nullptr;
544 contentEncoding = None;
545
546 compressedDataBuffer.clear();
547 decompressedDataBuffer.clear();
548 decoderHasData = false;
549
550 countDecompressed = false;
551 countHelper.reset();
552 totalBytesRead = 0;
553 totalUncompressedBytes = 0;
554 totalCompressedBytes = 0;
555
556 errorStr.clear();
557}
558
559qsizetype QDecompressHelper::readZLib(char *data, const qsizetype maxSize)
560{
561 bool triedRawDeflate = false;
562
563 z_stream *inflateStream = toZlibPointer(decoderPointer);
564 static const size_t zlibMaxSize =
565 size_t(std::numeric_limits<decltype(inflateStream->avail_in)>::max());
566
567 QByteArrayView input = compressedDataBuffer.readPointer();
568 if (size_t(input.size()) > zlibMaxSize)
569 input = input.sliced(zlibMaxSize);
570
571 inflateStream->avail_in = input.size();
572 inflateStream->next_in = reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
573
574 bool bigMaxSize = (zlibMaxSize < size_t(maxSize));
575 qsizetype adjustedAvailableOut = bigMaxSize ? qsizetype(zlibMaxSize) : maxSize;
576 inflateStream->avail_out = adjustedAvailableOut;
577 inflateStream->next_out = reinterpret_cast<Bytef *>(data);
578
579 qsizetype bytesDecoded = 0;
580 do {
581 auto previous_avail_out = inflateStream->avail_out;
582 int ret = inflate(inflateStream, Z_NO_FLUSH);
583 // All negative return codes are errors, in the context of HTTP compression, Z_NEED_DICT is
584 // also an error.
585 // in the case where we get Z_DATA_ERROR this could be because we received raw deflate
586 // compressed data.
587 if (ret == Z_DATA_ERROR && !triedRawDeflate) {
588 inflateEnd(inflateStream);
589 triedRawDeflate = true;
590 inflateStream->zalloc = Z_NULL;
591 inflateStream->zfree = Z_NULL;
592 inflateStream->opaque = Z_NULL;
593 inflateStream->avail_in = 0;
594 inflateStream->next_in = Z_NULL;
595 int ret = inflateInit2(inflateStream, -MAX_WBITS);
596 if (ret != Z_OK) {
597 return -1;
598 } else {
599 inflateStream->avail_in = input.size();
600 inflateStream->next_in =
601 reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
602 continue;
603 }
604 } else if (ret < 0 || ret == Z_NEED_DICT) {
605 return -1;
606 }
607 bytesDecoded += qsizetype(previous_avail_out - inflateStream->avail_out);
608 if (ret == Z_STREAM_END) {
609
610 // If there's more data after the stream then this is probably composed of multiple
611 // streams.
612 if (inflateStream->avail_in != 0) {
613 inflateEnd(inflateStream);
614 Bytef *next_in = inflateStream->next_in;
615 uInt avail_in = inflateStream->avail_in;
616 inflateStream->zalloc = Z_NULL;
617 inflateStream->zfree = Z_NULL;
618 inflateStream->opaque = Z_NULL;
619 if (inflateInit2(inflateStream, MAX_WBITS + 32) != Z_OK) {
620 delete inflateStream;
621 decoderPointer = nullptr;
622 // Failed to reinitialize, so we'll just return what we have
623 compressedDataBuffer.advanceReadPointer(input.size() - avail_in);
624 return bytesDecoded;
625 } else {
626 inflateStream->next_in = next_in;
627 inflateStream->avail_in = avail_in;
628 // Keep going to handle the other cases below
629 }
630 } else {
631 // No extra data, stream is at the end. We're done.
632 compressedDataBuffer.advanceReadPointer(input.size());
633 return bytesDecoded;
634 }
635 }
636
637 if (bigMaxSize && inflateStream->avail_out == 0) {
638 // Need to adjust the next_out and avail_out parameters since we reached the end
639 // of the current range
640 bigMaxSize = (zlibMaxSize < size_t(maxSize - bytesDecoded));
641 inflateStream->avail_out = bigMaxSize ? qsizetype(zlibMaxSize) : maxSize - bytesDecoded;
642 inflateStream->next_out = reinterpret_cast<Bytef *>(data + bytesDecoded);
643 }
644
645 if (inflateStream->avail_in == 0 && inflateStream->avail_out > 0) {
646 // Grab the next input!
647 compressedDataBuffer.advanceReadPointer(input.size());
648 input = compressedDataBuffer.readPointer();
649 if (size_t(input.size()) > zlibMaxSize)
650 input = input.sliced(zlibMaxSize);
651 inflateStream->avail_in = input.size();
652 inflateStream->next_in =
653 reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
654 }
655 } while (inflateStream->avail_out > 0 && inflateStream->avail_in > 0);
656
657 compressedDataBuffer.advanceReadPointer(input.size() - inflateStream->avail_in);
658
659 return bytesDecoded;
660}
661
662qsizetype QDecompressHelper::readBrotli(char *data, const qsizetype maxSize)
663{
664#if !QT_CONFIG(brotli)
665 Q_UNUSED(data);
666 Q_UNUSED(maxSize);
667 Q_UNREACHABLE();
668#else
669 qint64 bytesDecoded = 0;
670
671 BrotliDecoderState *brotliDecoderState = toBrotliPointer(decoderPointer);
672
673 while (decoderHasData && bytesDecoded < maxSize) {
674 Q_ASSERT(brotliUnconsumedDataPtr || BrotliDecoderHasMoreOutput(brotliDecoderState));
675 if (brotliUnconsumedDataPtr) {
676 Q_ASSERT(brotliUnconsumedAmount);
677 size_t toRead = std::min(size_t(maxSize - bytesDecoded), brotliUnconsumedAmount);
678 memcpy(data + bytesDecoded, brotliUnconsumedDataPtr, toRead);
679 bytesDecoded += toRead;
680 brotliUnconsumedAmount -= toRead;
681 brotliUnconsumedDataPtr += toRead;
682 if (brotliUnconsumedAmount == 0) {
683 brotliUnconsumedDataPtr = nullptr;
684 decoderHasData = false;
685 }
686 }
687 if (BrotliDecoderHasMoreOutput(brotliDecoderState) == BROTLI_TRUE) {
688 brotliUnconsumedDataPtr =
689 BrotliDecoderTakeOutput(brotliDecoderState, &brotliUnconsumedAmount);
690 decoderHasData = true;
691 }
692 }
693 if (bytesDecoded == maxSize)
694 return bytesDecoded;
695 Q_ASSERT(bytesDecoded < maxSize);
696
697 QByteArrayView input = compressedDataBuffer.readPointer();
698 const uint8_t *encodedPtr = reinterpret_cast<const uint8_t *>(input.data());
699 size_t encodedBytesRemaining = input.size();
700
701 uint8_t *decodedPtr = reinterpret_cast<uint8_t *>(data + bytesDecoded);
702 size_t unusedDecodedSize = size_t(maxSize - bytesDecoded);
703 while (unusedDecodedSize > 0) {
704 auto previousUnusedDecodedSize = unusedDecodedSize;
705 BrotliDecoderResult result = BrotliDecoderDecompressStream(
706 brotliDecoderState, &encodedBytesRemaining, &encodedPtr, &unusedDecodedSize,
707 &decodedPtr, nullptr);
708 bytesDecoded += previousUnusedDecodedSize - unusedDecodedSize;
709
710 switch (result) {
711 case BROTLI_DECODER_RESULT_ERROR:
712 errorStr = QLatin1String("Brotli error: %1")
713 .arg(QString::fromUtf8(BrotliDecoderErrorString(
714 BrotliDecoderGetErrorCode(brotliDecoderState))));
715 return -1;
716 case BROTLI_DECODER_RESULT_SUCCESS:
717 BrotliDecoderDestroyInstance(brotliDecoderState);
718 decoderPointer = nullptr;
719 compressedDataBuffer.clear();
720 return bytesDecoded;
721 case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
722 compressedDataBuffer.advanceReadPointer(input.size());
723 input = compressedDataBuffer.readPointer();
724 if (!input.isEmpty()) {
725 encodedPtr = reinterpret_cast<const uint8_t *>(input.constData());
726 encodedBytesRemaining = input.size();
727 break;
728 }
729 return bytesDecoded;
730 case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
731 // Some data is leftover inside the brotli decoder, remember for next time
732 decoderHasData = BrotliDecoderHasMoreOutput(brotliDecoderState);
733 Q_ASSERT(unusedDecodedSize == 0);
734 break;
735 }
736 }
737 compressedDataBuffer.advanceReadPointer(input.size() - encodedBytesRemaining);
738 return bytesDecoded;
739#endif
740}
741
742qsizetype QDecompressHelper::readZstandard(char *data, const qsizetype maxSize)
743{
744#if !QT_CONFIG(zstd)
745 Q_UNUSED(data);
746 Q_UNUSED(maxSize);
747 Q_UNREACHABLE();
748#else
749 ZSTD_DStream *zstdStream = toZstandardPointer(decoderPointer);
750
751 QByteArrayView input = compressedDataBuffer.readPointer();
752 ZSTD_inBuffer inBuf { input.data(), size_t(input.size()), 0 };
753
754 ZSTD_outBuffer outBuf { data, size_t(maxSize), 0 };
755
756 qsizetype bytesDecoded = 0;
757 while (outBuf.pos < outBuf.size && (inBuf.pos < inBuf.size || decoderHasData)) {
758 size_t retValue = ZSTD_decompressStream(zstdStream, &outBuf, &inBuf);
759 if (ZSTD_isError(retValue)) {
760 errorStr = QLatin1String("ZStandard error: %1")
761 .arg(QString::fromUtf8(ZSTD_getErrorName(retValue)));
762 return -1;
763 } else {
764 decoderHasData = false;
765 bytesDecoded = outBuf.pos;
766 // if pos == size then there may be data left over in internal buffers
767 if (outBuf.pos == outBuf.size) {
768 decoderHasData = true;
769 } else if (inBuf.pos == inBuf.size) {
770 compressedDataBuffer.advanceReadPointer(input.size());
771 input = compressedDataBuffer.readPointer();
772 inBuf = { input.constData(), size_t(input.size()), 0 };
773 }
774 }
775 }
776 compressedDataBuffer.advanceReadPointer(inBuf.pos);
777 return bytesDecoded;
778#endif
779}
780
\inmodule QtCore
constexpr QByteArrayView sliced(qsizetype pos) const
constexpr qsizetype size() const noexcept
constexpr const_pointer data() const noexcept
constexpr const_pointer constData() const noexcept
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const
void append(const QByteDataBuffer &other)
Definition qbytedata_p.h:48
void advanceReadPointer(qint64 distance)
QByteArrayView readPointer() const
qsizetype bufferCount() const
QByteArray read()
qint64 byteAmount() const
static QString translate(const char *context, const char *key, const char *disambiguation=nullptr, int n=-1)
\threadsafe
void setCountingBytesEnabled(bool shouldCount)
bool setEncoding(QByteArrayView contentEncoding)
void feed(const QByteArray &data)
qint64 uncompressedSize() const
static QByteArrayList acceptedEncoding()
static bool isSupportedEncoding(QByteArrayView encoding)
qsizetype read(char *data, qsizetype maxSize)
void setDecompressedSafetyCheckThreshold(qint64 threshold)
QString errorString() const
QString arg(Args &&...args) const
void reserve(qsizetype size)
Definition qlist.h:753
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
Combined button and popup list for selecting options.
constexpr ContentEncodingMapping contentEncodingMapping[]
QDecompressHelper::ContentEncoding encodingFromByteArray(QByteArrayView ce) noexcept
z_stream * toZlibPointer(void *ptr)
@ CaseInsensitive
constexpr Initialization Uninitialized
static jboolean copy(JNIEnv *, jobject)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define qWarning
Definition qlogging.h:166
return ret
static ControlElement< T > * ptr(QWidget *widget)
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLuint64EXT * result
[6]
GLenum GLenum GLenum GLenum mapping
GLenum GLenum GLenum input
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
Definition qzip.cpp:92
QList< int > list
[14]
QDecompressHelper::ContentEncoding encoding