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

source code of qtbase/src/network/access/qdecompresshelper.cpp