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