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