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) noexcept
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#if QT_CONFIG(brotli)
563 // Points into the output of the decoder we just destroyed
564 brotliUnconsumedDataPtr = nullptr;
565 brotliUnconsumedAmount = 0;
566#endif
567
568 countDecompressed = false;
569 countHelper.reset();
570 totalBytesRead = 0;
571 totalUncompressedBytes = 0;
572 totalCompressedBytes = 0;
573}
574
575qsizetype QDecompressHelper::readZLib(char *data, const qsizetype maxSize)
576{
577 bool triedRawDeflate = false;
578
579 z_stream *inflateStream = toZlibPointer(decoderPointer);
580 static const size_t zlibMaxSize =
581 size_t(std::numeric_limits<decltype(inflateStream->avail_in)>::max());
582
583 QByteArrayView input = compressedDataBuffer.readPointer();
584 if (size_t(input.size()) > zlibMaxSize)
585 input = input.sliced(zlibMaxSize);
586
587 inflateStream->avail_in = input.size();
588 inflateStream->next_in = reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
589
590 bool bigMaxSize = (zlibMaxSize < size_t(maxSize));
591 qsizetype adjustedAvailableOut = bigMaxSize ? qsizetype(zlibMaxSize) : maxSize;
592 inflateStream->avail_out = adjustedAvailableOut;
593 inflateStream->next_out = reinterpret_cast<Bytef *>(data);
594
595 qsizetype bytesDecoded = 0;
596 do {
597 auto previous_avail_out = inflateStream->avail_out;
598 int ret = inflate(inflateStream, Z_NO_FLUSH);
599 // All negative return codes are errors, in the context of HTTP compression, Z_NEED_DICT is
600 // also an error.
601 // in the case where we get Z_DATA_ERROR this could be because we received raw deflate
602 // compressed data.
603 if (ret == Z_DATA_ERROR && !triedRawDeflate) { Q_UNLIKELY_BRANCH
604 inflateEnd(inflateStream);
605 triedRawDeflate = true;
606 inflateStream->zalloc = Z_NULL;
607 inflateStream->zfree = Z_NULL;
608 inflateStream->opaque = Z_NULL;
609 inflateStream->avail_in = 0;
610 inflateStream->next_in = Z_NULL;
611 int ret = inflateInit2(inflateStream, -MAX_WBITS);
612 if (ret != Z_OK) {
613 return -1;
614 } else {
615 inflateStream->avail_in = input.size();
616 inflateStream->next_in =
617 reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
618 continue;
619 }
620 } else if (ret < 0 || ret == Z_NEED_DICT) {
621 return -1;
622 }
623 bytesDecoded += qsizetype(previous_avail_out - inflateStream->avail_out);
624 if (ret == Z_STREAM_END) {
625
626 // If there's more data after the stream then this is probably composed of multiple
627 // streams.
628 if (inflateStream->avail_in != 0) {
629 inflateEnd(inflateStream);
630 Bytef *next_in = inflateStream->next_in;
631 uInt avail_in = inflateStream->avail_in;
632 inflateStream->zalloc = Z_NULL;
633 inflateStream->zfree = Z_NULL;
634 inflateStream->opaque = Z_NULL;
635 if (inflateInit2(inflateStream, MAX_WBITS + 32) != Z_OK) {
636 // We can't decode the next member, and returning what we have would leave
637 // us without a decoder for the data that follows. readInternal() calls
638 // clear() on error, which frees the stream.
639 errorStr = QCoreApplication::translate(
640 "QHttp", "Failed to initialize the compression decoder.");
641 return -1;
642 } else {
643 inflateStream->next_in = next_in;
644 inflateStream->avail_in = avail_in;
645 // Keep going to handle the other cases below
646 }
647 } else {
648 // No extra data, stream is at the end. We're done.
649 compressedDataBuffer.advanceReadPointer(input.size());
650 return bytesDecoded;
651 }
652 }
653
654 if (bigMaxSize && inflateStream->avail_out == 0) {
655 // Need to adjust the next_out and avail_out parameters since we reached the end
656 // of the current range
657 bigMaxSize = (zlibMaxSize < size_t(maxSize - bytesDecoded));
658 inflateStream->avail_out = bigMaxSize ? qsizetype(zlibMaxSize) : maxSize - bytesDecoded;
659 inflateStream->next_out = reinterpret_cast<Bytef *>(data + bytesDecoded);
660 }
661
662 if (inflateStream->avail_in == 0 && inflateStream->avail_out > 0) {
663 // Grab the next input!
664 compressedDataBuffer.advanceReadPointer(input.size());
665 input = compressedDataBuffer.readPointer();
666 if (size_t(input.size()) > zlibMaxSize)
667 input = input.sliced(zlibMaxSize);
668 inflateStream->avail_in = input.size();
669 inflateStream->next_in =
670 reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
671 }
672 } while (inflateStream->avail_out > 0 && inflateStream->avail_in > 0);
673
674 compressedDataBuffer.advanceReadPointer(input.size() - inflateStream->avail_in);
675
676 return bytesDecoded;
677}
678
679qsizetype QDecompressHelper::readBrotli(char *data, const qsizetype maxSize)
680{
681#if !QT_CONFIG(brotli)
682 Q_UNUSED(data);
683 Q_UNUSED(maxSize);
684 Q_UNREACHABLE();
685#else
686 qint64 bytesDecoded = 0;
687
688 BrotliDecoderState *brotliDecoderState = toBrotliPointer(decoderPointer);
689 if (!brotliDecoderState) {
690 // The stream has ended and the decoder was freed (see BROTLI_DECODER_RESULT_SUCCESS
691 // below). Brotli streams can't be concatenated, so drop anything fed after the end.
692 compressedDataBuffer.clear();
693 return 0;
694 }
695
696 while (decoderHasData && bytesDecoded < maxSize) {
697 Q_ASSERT(brotliUnconsumedDataPtr || BrotliDecoderHasMoreOutput(brotliDecoderState));
698 if (brotliUnconsumedDataPtr) {
699 Q_ASSERT(brotliUnconsumedAmount);
700 size_t toRead = std::min(size_t(maxSize - bytesDecoded), brotliUnconsumedAmount);
701 memcpy(data + bytesDecoded, brotliUnconsumedDataPtr, toRead);
702 bytesDecoded += toRead;
703 brotliUnconsumedAmount -= toRead;
704 brotliUnconsumedDataPtr += toRead;
705 if (brotliUnconsumedAmount == 0) {
706 brotliUnconsumedDataPtr = nullptr;
707 decoderHasData = false;
708 }
709 }
710 if (BrotliDecoderHasMoreOutput(brotliDecoderState) == BROTLI_TRUE) {
711 brotliUnconsumedDataPtr =
712 BrotliDecoderTakeOutput(brotliDecoderState, &brotliUnconsumedAmount);
713 decoderHasData = true;
714 }
715 }
716 if (bytesDecoded == maxSize)
717 return bytesDecoded;
718 Q_ASSERT(bytesDecoded < maxSize);
719
720 QByteArrayView input = compressedDataBuffer.readPointer();
721 const uint8_t *encodedPtr = reinterpret_cast<const uint8_t *>(input.data());
722 size_t encodedBytesRemaining = input.size();
723
724 uint8_t *decodedPtr = reinterpret_cast<uint8_t *>(data + bytesDecoded);
725 size_t unusedDecodedSize = size_t(maxSize - bytesDecoded);
726 while (unusedDecodedSize > 0) {
727 auto previousUnusedDecodedSize = unusedDecodedSize;
728 BrotliDecoderResult result = BrotliDecoderDecompressStream(
729 brotliDecoderState, &encodedBytesRemaining, &encodedPtr, &unusedDecodedSize,
730 &decodedPtr, nullptr);
731 bytesDecoded += previousUnusedDecodedSize - unusedDecodedSize;
732
733 switch (result) {
734 Q_UNLIKELY_BRANCH
735 case BROTLI_DECODER_RESULT_ERROR:
736 //: Brotli (compression algorithm) decoding error, e.g. corrupted input or memory allocation problem.
737 errorStr = QCoreApplication::translate("QHttp", "Brotli error: %1")
738 .arg(QUtf8StringView{BrotliDecoderErrorString(
739 BrotliDecoderGetErrorCode(brotliDecoderState))});
740 return -1;
741 case BROTLI_DECODER_RESULT_SUCCESS:
742 BrotliDecoderDestroyInstance(brotliDecoderState);
743 decoderPointer = nullptr;
744 compressedDataBuffer.clear();
745 return bytesDecoded;
746 case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
747 compressedDataBuffer.advanceReadPointer(input.size());
748 input = compressedDataBuffer.readPointer();
749 if (!input.isEmpty()) {
750 encodedPtr = reinterpret_cast<const uint8_t *>(input.constData());
751 encodedBytesRemaining = input.size();
752 break;
753 }
754 return bytesDecoded;
755 case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
756 // Some data is leftover inside the brotli decoder, remember for next time
757 decoderHasData = BrotliDecoderHasMoreOutput(brotliDecoderState);
758 Q_ASSERT(unusedDecodedSize == 0);
759 break;
760 }
761 }
762 compressedDataBuffer.advanceReadPointer(input.size() - encodedBytesRemaining);
763 return bytesDecoded;
764#endif
765}
766
767qsizetype QDecompressHelper::readZstandard(char *data, const qsizetype maxSize)
768{
769#if !QT_CONFIG(zstd)
770 Q_UNUSED(data);
771 Q_UNUSED(maxSize);
772 Q_UNREACHABLE();
773#else
774 ZSTD_DStream *zstdStream = toZstandardPointer(decoderPointer);
775
776 QByteArrayView input = compressedDataBuffer.readPointer();
777 ZSTD_inBuffer inBuf { input.data(), size_t(input.size()), 0 };
778
779 ZSTD_outBuffer outBuf { data, size_t(maxSize), 0 };
780
781 qsizetype bytesDecoded = 0;
782 while (outBuf.pos < outBuf.size && (inBuf.pos < inBuf.size || decoderHasData)) {
783 size_t retValue = ZSTD_decompressStream(zstdStream, &outBuf, &inBuf);
784 if (ZSTD_isError(retValue)) { Q_UNLIKELY_BRANCH
785 errorStr = QCoreApplication::translate("QHttp", "ZStandard error: %1")
786 .arg(QUtf8StringView{ZSTD_getErrorName(retValue)});
787 return -1;
788 }
789 decoderHasData = false;
790 bytesDecoded = outBuf.pos;
791 // if pos == size then there may be data left over in internal buffers
792 if (outBuf.pos == outBuf.size) {
793 decoderHasData = true;
794 } else if (inBuf.pos == inBuf.size) {
795 compressedDataBuffer.advanceReadPointer(input.size());
796 input = compressedDataBuffer.readPointer();
797 inBuf = { input.constData(), size_t(input.size()), 0 };
798 }
799 }
800 compressedDataBuffer.advanceReadPointer(inBuf.pos);
801 return bytesDecoded;
802#endif
803}
804
805QT_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()