Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
22QT_BEGIN_NAMESPACE
23namespace {
29
31#if QT_CONFIG(zstd)
32 { "zstd", QDecompressHelper::Zstandard },
33#endif
34#if QT_CONFIG(brotli)
35 { "br", QDecompressHelper::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 }
47 return QDecompressHelper::None;
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
70bool QDecompressHelper::isSupportedEncoding(QByteArrayView encoding)
71{
72 return encodingFromByteArray(encoding) != QDecompressHelper::None;
73}
74
75QByteArrayList QDecompressHelper::acceptedEncoding()
76{
77 QByteArrayList list;
78 list.reserve(std::size(contentEncodingMapping));
79 for (const auto &mapping : contentEncodingMapping) {
80 list << mapping.name.toByteArray();
81 }
82 return list;
83}
84
85QDecompressHelper::~QDecompressHelper()
86{
87 clear();
88}
89
90bool QDecompressHelper::setEncoding(QByteArrayView encoding)
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
154/*!
155 \internal
156
157 Returns true if the QDecompressHelper is measuring the
158 size of the decompressed data.
159
160 \sa setCountingBytesEnabled, uncompressedSize
161*/
162bool QDecompressHelper::isCountingBytes() const
163{
164 return countDecompressed;
165}
166
167/*!
168 \internal
169
170 Enable or disable counting the decompressed size of the data
171 based on \a shouldCount. Enabling this means the data will be
172 decompressed twice (once for counting and once when data is
173 being read).
174
175 \note Can only be called before contentEncoding is set and data
176 is fed to the object.
177
178 \sa isCountingBytes, uncompressedSize
179*/
180void QDecompressHelper::setCountingBytesEnabled(bool shouldCount)
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
189/*!
190 \internal
191
192 Returns the amount of uncompressed bytes left.
193
194 \note Since this is only based on the data received
195 so far the final size could be larger.
196
197 \note It is only valid to call this if isCountingBytes()
198 returns true
199
200 \sa isCountingBytes, setCountBytes
201*/
202qint64 QDecompressHelper::uncompressedSize() const
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
214/*!
215 \internal
216 \overload
217*/
218void QDecompressHelper::feed(const QByteArray &data)
219{
220 return feed(QByteArray(data));
221}
222
223/*!
224 \internal
225 Give \a data to the QDecompressHelper which will be stored until
226 a read is attempted.
227
228 If \c isCountingBytes() is true then it will decompress immediately
229 before discarding the data, but will count the uncompressed byte
230 size.
231*/
232void QDecompressHelper::feed(QByteArray &&data)
233{
234 Q_ASSERT(contentEncoding != None);
235 totalCompressedBytes += data.size();
236 compressedDataBuffer.append(std::move(data));
237 if (!countInternal(compressedDataBuffer[compressedDataBuffer.bufferCount() - 1])) {
238 if (errorStr.isEmpty() && countHelper)
239 errorStr = countHelper->errorString();
240 clear(); // If our counting brother failed then so will we :|
241 }
242}
243
244/*!
245 \internal
246 \overload
247*/
248void QDecompressHelper::feed(const QByteDataBuffer &buffer)
249{
250 Q_ASSERT(contentEncoding != None);
251 totalCompressedBytes += buffer.byteAmount();
252 compressedDataBuffer.append(buffer);
253 if (!countInternal(buffer)) {
254 if (errorStr.isEmpty() && countHelper)
255 errorStr = countHelper->errorString();
256 clear(); // If our counting brother failed then so will we :|
257 }
258}
259
260/*!
261 \internal
262 \overload
263*/
264void QDecompressHelper::feed(QByteDataBuffer &&buffer)
265{
266 Q_ASSERT(contentEncoding != None);
267 totalCompressedBytes += buffer.byteAmount();
268 const QByteDataBuffer copy(buffer);
269 compressedDataBuffer.append(std::move(buffer));
270 if (!countInternal(copy)) {
271 if (errorStr.isEmpty() && countHelper)
272 errorStr = countHelper->errorString();
273 clear(); // If our counting brother failed then so will we :|
274 }
275}
276
277/*!
278 \internal
279 Decompress the data internally and immediately discard the
280 uncompressed data, but count how many bytes were decoded.
281 This lets us know the final size, unfortunately at the cost of
282 increased computation.
283
284 To save on some of the computation we will store the data until
285 we reach \c MaxDecompressedDataBufferSize stored. In this case the
286 "penalty" is completely removed from users who read the data on
287 readyRead rather than waiting for it all to be received. And
288 any file smaller than \c MaxDecompressedDataBufferSize will
289 avoid this issue as well.
290*/
291bool QDecompressHelper::countInternal()
292{
293 Q_ASSERT(countDecompressed);
294 while (hasDataInternal()
295 && decompressedDataBuffer.byteAmount() < MaxDecompressedDataBufferSize) {
296 const qsizetype toRead = 256 * 1024;
297 QByteArray buffer(toRead, Qt::Uninitialized);
298 qsizetype bytesRead = readInternal(buffer.data(), buffer.size());
299 if (bytesRead == -1)
300 return false;
301 buffer.truncate(bytesRead);
302 decompressedDataBuffer.append(std::move(buffer));
303 }
304 if (!hasDataInternal())
305 return true; // handled all the data so far, just return
306
307 while (countHelper->hasData()) {
308 std::array<char, 1024> temp;
309 qsizetype bytesRead = countHelper->read(temp.data(), temp.size());
310 if (bytesRead == -1)
311 return false;
312 }
313 return true;
314}
315
316/*!
317 \internal
318 \overload
319*/
320bool QDecompressHelper::countInternal(const QByteArray &data)
321{
322 if (countDecompressed) {
323 if (!countHelper) {
324 countHelper = std::make_unique<QDecompressHelper>();
325 countHelper->setDecompressedSafetyCheckThreshold(archiveBombCheckThreshold);
326 countHelper->setEncoding(contentEncoding);
327 }
328 countHelper->feed(data);
329 return countInternal();
330 }
331 return true;
332}
333
334/*!
335 \internal
336 \overload
337*/
338bool QDecompressHelper::countInternal(const QByteDataBuffer &buffer)
339{
340 if (countDecompressed) {
341 if (!countHelper) {
342 countHelper = std::make_unique<QDecompressHelper>();
343 countHelper->setDecompressedSafetyCheckThreshold(archiveBombCheckThreshold);
344 countHelper->setEncoding(contentEncoding);
345 }
346 countHelper->feed(buffer);
347 return countInternal();
348 }
349 return true;
350}
351
352qsizetype QDecompressHelper::read(char *data, qsizetype maxSize)
353{
354 if (maxSize <= 0)
355 return 0;
356
357 if (!isValid())
358 return -1;
359
360 if (!hasData())
361 return 0;
362
363 qsizetype cachedRead = 0;
364 if (!decompressedDataBuffer.isEmpty()) {
365 cachedRead = decompressedDataBuffer.read(data, maxSize);
366 data += cachedRead;
367 maxSize -= cachedRead;
368 }
369
370 qsizetype bytesRead = readInternal(data, maxSize);
371 if (bytesRead == -1)
372 return -1;
373 totalBytesRead += bytesRead + cachedRead;
374 return bytesRead + cachedRead;
375}
376
377/*!
378 \internal
379 Like read() but without attempting to read the
380 cached/already-decompressed data.
381*/
382qsizetype QDecompressHelper::readInternal(char *data, qsizetype maxSize)
383{
384 Q_ASSERT(isValid());
385
386 if (maxSize <= 0)
387 return 0;
388
389 if (!hasDataInternal())
390 return 0;
391
392 qsizetype bytesRead = -1;
393 switch (contentEncoding) {
394 case None:
395 Q_UNREACHABLE();
396 break;
397 case Deflate:
398 case GZip:
399 bytesRead = readZLib(data, maxSize);
400 break;
401 case Brotli:
402 bytesRead = readBrotli(data, maxSize);
403 break;
404 case Zstandard:
405 bytesRead = readZstandard(data, maxSize);
406 break;
407 }
408 if (bytesRead == -1)
409 clear();
410
411 totalUncompressedBytes += bytesRead;
412 if (isPotentialArchiveBomb()) {
413 errorStr = QCoreApplication::translate(
414 "QHttp",
415 "The decompressed output exceeds the limits specified by "
416 "QNetworkRequest::decompressedSafetyCheckThreshold()");
417 return -1;
418 }
419
420 return bytesRead;
421}
422
423/*!
424 \internal
425 Set the \a threshold required before the archive bomb detection kicks in.
426 By default this is 10MB. Setting it to -1 is treated as disabling the
427 feature.
428*/
429void QDecompressHelper::setDecompressedSafetyCheckThreshold(qint64 threshold)
430{
431 if (threshold == -1)
432 threshold = std::numeric_limits<qint64>::max();
433 archiveBombCheckThreshold = threshold;
434}
435
436bool QDecompressHelper::isPotentialArchiveBomb() const
437{
438 if (totalCompressedBytes == 0)
439 return false;
440
441 if (totalUncompressedBytes <= archiveBombCheckThreshold)
442 return false;
443
444 // Some protection against malicious or corrupted compressed files that expand far more than
445 // is reasonable.
446 double ratio = double(totalUncompressedBytes) / double(totalCompressedBytes);
447 switch (contentEncoding) {
448 case None:
449 Q_UNREACHABLE();
450 break;
451 case Deflate:
452 case GZip:
453 // This value is mentioned in docs for
454 // QNetworkRequest::setMinimumArchiveBombSize, keep synchronized
455 if (ratio > 40) {
456 return true;
457 }
458 break;
459 case Brotli:
460 case Zstandard:
461 // This value is mentioned in docs for
462 // QNetworkRequest::setMinimumArchiveBombSize, keep synchronized
463 if (ratio > 100) {
464 return true;
465 }
466 break;
467 }
468 return false;
469}
470
471/*!
472 \internal
473 Returns true if there are encoded bytes left or there is some
474 indication that the decoder still has data left internally.
475
476 \note Even if this returns true the next call to read() might
477 read 0 bytes. This most likely means the decompression is done.
478*/
479bool QDecompressHelper::hasData() const
480{
481 return hasDataInternal() || !decompressedDataBuffer.isEmpty();
482}
483
484/*!
485 \internal
486 Like hasData() but internally the buffer of decompressed data is
487 not interesting.
488*/
489bool QDecompressHelper::hasDataInternal() const
490{
491 return encodedBytesAvailable() || decoderHasData;
492}
493
494qint64 QDecompressHelper::encodedBytesAvailable() const
495{
496 return compressedDataBuffer.byteAmount();
497}
498
499/*!
500 \internal
501 Returns whether or not the object is valid.
502 If it becomes invalid after an operation has been performed
503 then an error has occurred.
504 \sa errorString()
505*/
506bool QDecompressHelper::isValid() const
507{
508 return contentEncoding != None;
509}
510
511/*!
512 \internal
513 Returns a string describing the error that occurred or an empty
514 string if no error occurred.
515 \sa isValid()
516*/
517QString QDecompressHelper::errorString() const
518{
519 return errorStr;
520}
521
522void QDecompressHelper::clear()
523{
524 switch (contentEncoding) {
525 case None:
526 break;
527 case Deflate:
528 case GZip: {
529 z_stream *inflateStream = toZlibPointer(decoderPointer);
530 if (inflateStream)
531 inflateEnd(inflateStream);
532 delete inflateStream;
533 break;
534 }
535 case Brotli: {
536#if QT_CONFIG(brotli)
537 BrotliDecoderState *brotliDecoderState = toBrotliPointer(decoderPointer);
538 if (brotliDecoderState)
539 BrotliDecoderDestroyInstance(brotliDecoderState);
540#endif
541 break;
542 }
543 case Zstandard: {
544#if QT_CONFIG(zstd)
545 ZSTD_DStream *zstdStream = toZstandardPointer(decoderPointer);
546 if (zstdStream)
547 ZSTD_freeDStream(zstdStream);
548#endif
549 break;
550 }
551 }
552 decoderPointer = nullptr;
553 contentEncoding = None;
554
555 compressedDataBuffer.clear();
556 decompressedDataBuffer.clear();
557 decoderHasData = false;
558
559 countDecompressed = false;
560 countHelper.reset();
561 totalBytesRead = 0;
562 totalUncompressedBytes = 0;
563 totalCompressedBytes = 0;
564}
565
566qsizetype QDecompressHelper::readZLib(char *data, const qsizetype maxSize)
567{
568 bool triedRawDeflate = false;
569
570 z_stream *inflateStream = toZlibPointer(decoderPointer);
571 static const size_t zlibMaxSize =
572 size_t(std::numeric_limits<decltype(inflateStream->avail_in)>::max());
573
574 QByteArrayView input = compressedDataBuffer.readPointer();
575 if (size_t(input.size()) > zlibMaxSize)
576 input = input.sliced(zlibMaxSize);
577
578 inflateStream->avail_in = input.size();
579 inflateStream->next_in = reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
580
581 bool bigMaxSize = (zlibMaxSize < size_t(maxSize));
582 qsizetype adjustedAvailableOut = bigMaxSize ? qsizetype(zlibMaxSize) : maxSize;
583 inflateStream->avail_out = adjustedAvailableOut;
584 inflateStream->next_out = reinterpret_cast<Bytef *>(data);
585
586 qsizetype bytesDecoded = 0;
587 do {
588 auto previous_avail_out = inflateStream->avail_out;
589 int ret = inflate(inflateStream, Z_NO_FLUSH);
590 // All negative return codes are errors, in the context of HTTP compression, Z_NEED_DICT is
591 // also an error.
592 // in the case where we get Z_DATA_ERROR this could be because we received raw deflate
593 // compressed data.
594 if (ret == Z_DATA_ERROR && !triedRawDeflate) Q_UNLIKELY_BRANCH {
595 inflateEnd(inflateStream);
596 triedRawDeflate = true;
597 inflateStream->zalloc = Z_NULL;
598 inflateStream->zfree = Z_NULL;
599 inflateStream->opaque = Z_NULL;
600 inflateStream->avail_in = 0;
601 inflateStream->next_in = Z_NULL;
602 int ret = inflateInit2(inflateStream, -MAX_WBITS);
603 if (ret != Z_OK) {
604 return -1;
605 } else {
606 inflateStream->avail_in = input.size();
607 inflateStream->next_in =
608 reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
609 continue;
610 }
611 } else if (ret < 0 || ret == Z_NEED_DICT) {
612 return -1;
613 }
614 bytesDecoded += qsizetype(previous_avail_out - inflateStream->avail_out);
615 if (ret == Z_STREAM_END) {
616
617 // If there's more data after the stream then this is probably composed of multiple
618 // streams.
619 if (inflateStream->avail_in != 0) {
620 inflateEnd(inflateStream);
621 Bytef *next_in = inflateStream->next_in;
622 uInt avail_in = inflateStream->avail_in;
623 inflateStream->zalloc = Z_NULL;
624 inflateStream->zfree = Z_NULL;
625 inflateStream->opaque = Z_NULL;
626 if (inflateInit2(inflateStream, MAX_WBITS + 32) != Z_OK) {
627 delete inflateStream;
628 decoderPointer = nullptr;
629 // Failed to reinitialize, so we'll just return what we have
630 compressedDataBuffer.advanceReadPointer(input.size() - avail_in);
631 return bytesDecoded;
632 } else {
633 inflateStream->next_in = next_in;
634 inflateStream->avail_in = avail_in;
635 // Keep going to handle the other cases below
636 }
637 } else {
638 // No extra data, stream is at the end. We're done.
639 compressedDataBuffer.advanceReadPointer(input.size());
640 return bytesDecoded;
641 }
642 }
643
644 if (bigMaxSize && inflateStream->avail_out == 0) {
645 // Need to adjust the next_out and avail_out parameters since we reached the end
646 // of the current range
647 bigMaxSize = (zlibMaxSize < size_t(maxSize - bytesDecoded));
648 inflateStream->avail_out = bigMaxSize ? qsizetype(zlibMaxSize) : maxSize - bytesDecoded;
649 inflateStream->next_out = reinterpret_cast<Bytef *>(data + bytesDecoded);
650 }
651
652 if (inflateStream->avail_in == 0 && inflateStream->avail_out > 0) {
653 // Grab the next input!
654 compressedDataBuffer.advanceReadPointer(input.size());
655 input = compressedDataBuffer.readPointer();
656 if (size_t(input.size()) > zlibMaxSize)
657 input = input.sliced(zlibMaxSize);
658 inflateStream->avail_in = input.size();
659 inflateStream->next_in =
660 reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
661 }
662 } while (inflateStream->avail_out > 0 && inflateStream->avail_in > 0);
663
664 compressedDataBuffer.advanceReadPointer(input.size() - inflateStream->avail_in);
665
666 return bytesDecoded;
667}
668
669qsizetype QDecompressHelper::readBrotli(char *data, const qsizetype maxSize)
670{
671#if !QT_CONFIG(brotli)
672 Q_UNUSED(data);
673 Q_UNUSED(maxSize);
674 Q_UNREACHABLE();
675#else
676 qint64 bytesDecoded = 0;
677
678 BrotliDecoderState *brotliDecoderState = toBrotliPointer(decoderPointer);
679
680 while (decoderHasData && bytesDecoded < maxSize) {
681 Q_ASSERT(brotliUnconsumedDataPtr || BrotliDecoderHasMoreOutput(brotliDecoderState));
682 if (brotliUnconsumedDataPtr) {
683 Q_ASSERT(brotliUnconsumedAmount);
684 size_t toRead = std::min(size_t(maxSize - bytesDecoded), brotliUnconsumedAmount);
685 memcpy(data + bytesDecoded, brotliUnconsumedDataPtr, toRead);
686 bytesDecoded += toRead;
687 brotliUnconsumedAmount -= toRead;
688 brotliUnconsumedDataPtr += toRead;
689 if (brotliUnconsumedAmount == 0) {
690 brotliUnconsumedDataPtr = nullptr;
691 decoderHasData = false;
692 }
693 }
694 if (BrotliDecoderHasMoreOutput(brotliDecoderState) == BROTLI_TRUE) {
695 brotliUnconsumedDataPtr =
696 BrotliDecoderTakeOutput(brotliDecoderState, &brotliUnconsumedAmount);
697 decoderHasData = true;
698 }
699 }
700 if (bytesDecoded == maxSize)
701 return bytesDecoded;
702 Q_ASSERT(bytesDecoded < maxSize);
703
704 QByteArrayView input = compressedDataBuffer.readPointer();
705 const uint8_t *encodedPtr = reinterpret_cast<const uint8_t *>(input.data());
706 size_t encodedBytesRemaining = input.size();
707
708 uint8_t *decodedPtr = reinterpret_cast<uint8_t *>(data + bytesDecoded);
709 size_t unusedDecodedSize = size_t(maxSize - bytesDecoded);
710 while (unusedDecodedSize > 0) {
711 auto previousUnusedDecodedSize = unusedDecodedSize;
712 BrotliDecoderResult result = BrotliDecoderDecompressStream(
713 brotliDecoderState, &encodedBytesRemaining, &encodedPtr, &unusedDecodedSize,
714 &decodedPtr, nullptr);
715 bytesDecoded += previousUnusedDecodedSize - unusedDecodedSize;
716
717 switch (result) {
718 Q_UNLIKELY_BRANCH
719 case BROTLI_DECODER_RESULT_ERROR:
720 errorStr = QLatin1String("Brotli error: %1")
721 .arg(QString::fromUtf8(BrotliDecoderErrorString(
722 BrotliDecoderGetErrorCode(brotliDecoderState))));
723 return -1;
724 case BROTLI_DECODER_RESULT_SUCCESS:
725 BrotliDecoderDestroyInstance(brotliDecoderState);
726 decoderPointer = nullptr;
727 compressedDataBuffer.clear();
728 return bytesDecoded;
729 case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
730 compressedDataBuffer.advanceReadPointer(input.size());
731 input = compressedDataBuffer.readPointer();
732 if (!input.isEmpty()) {
733 encodedPtr = reinterpret_cast<const uint8_t *>(input.constData());
734 encodedBytesRemaining = input.size();
735 break;
736 }
737 return bytesDecoded;
738 case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
739 // Some data is leftover inside the brotli decoder, remember for next time
740 decoderHasData = BrotliDecoderHasMoreOutput(brotliDecoderState);
741 Q_ASSERT(unusedDecodedSize == 0);
742 break;
743 }
744 }
745 compressedDataBuffer.advanceReadPointer(input.size() - encodedBytesRemaining);
746 return bytesDecoded;
747#endif
748}
749
750qsizetype QDecompressHelper::readZstandard(char *data, const qsizetype maxSize)
751{
752#if !QT_CONFIG(zstd)
753 Q_UNUSED(data);
754 Q_UNUSED(maxSize);
755 Q_UNREACHABLE();
756#else
757 ZSTD_DStream *zstdStream = toZstandardPointer(decoderPointer);
758
759 QByteArrayView input = compressedDataBuffer.readPointer();
760 ZSTD_inBuffer inBuf { input.data(), size_t(input.size()), 0 };
761
762 ZSTD_outBuffer outBuf { data, size_t(maxSize), 0 };
763
764 qsizetype bytesDecoded = 0;
765 while (outBuf.pos < outBuf.size && (inBuf.pos < inBuf.size || decoderHasData)) {
766 size_t retValue = ZSTD_decompressStream(zstdStream, &outBuf, &inBuf);
767 if (ZSTD_isError(retValue)) Q_UNLIKELY_BRANCH {
768 errorStr = QLatin1String("ZStandard error: %1")
769 .arg(QString::fromUtf8(ZSTD_getErrorName(retValue)));
770 return -1;
771 }
772 decoderHasData = false;
773 bytesDecoded = outBuf.pos;
774 // if pos == size then there may be data left over in internal buffers
775 if (outBuf.pos == outBuf.size) {
776 decoderHasData = true;
777 } else if (inBuf.pos == inBuf.size) {
778 compressedDataBuffer.advanceReadPointer(input.size());
779 input = compressedDataBuffer.readPointer();
780 inBuf = { input.constData(), size_t(input.size()), 0 };
781 }
782 }
783 compressedDataBuffer.advanceReadPointer(inBuf.pos);
784 return bytesDecoded;
785#endif
786}
787
788QT_END_NAMESPACE
constexpr ContentEncodingMapping contentEncodingMapping[]
QDecompressHelper::ContentEncoding encodingFromByteArray(QByteArrayView ce) noexcept
z_stream * toZlibPointer(void *ptr)