QNetworkReply: update decompress error message and handling
The error message was quite vague since it would then not require any additional translations. However, in hindsight this was a mistake since now developers just thought their downloads were being corrupted. Fixes: QTBUG-101942 Change-Id: Ie9af42510ca027d15248e5bcf21e836e709898d9 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> (cherry picked from commit d642c16fe745e156a93129fbb3750784b361ee39) Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
parent
606ca8a590
commit
427d2c0966
@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#include <QtCore/private/qbytearray_p.h>
|
#include <QtCore/private/qbytearray_p.h>
|
||||||
#include <QtCore/qiodevice.h>
|
#include <QtCore/qiodevice.h>
|
||||||
|
#include <QtCore/qcoreapplication.h>
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
@ -131,13 +132,16 @@ bool QDecompressHelper::setEncoding(const QByteArray &encoding)
|
|||||||
Q_ASSERT(contentEncoding == QDecompressHelper::None);
|
Q_ASSERT(contentEncoding == QDecompressHelper::None);
|
||||||
if (contentEncoding != QDecompressHelper::None) {
|
if (contentEncoding != QDecompressHelper::None) {
|
||||||
qWarning("Encoding is already set.");
|
qWarning("Encoding is already set.");
|
||||||
|
// This isn't an error, so it doesn't set errorStr, it's just wrong usage.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ContentEncoding ce = encodingFromByteArray(encoding);
|
ContentEncoding ce = encodingFromByteArray(encoding);
|
||||||
if (ce == None) {
|
if (ce == None) {
|
||||||
qWarning("An unsupported content encoding was selected: %s", encoding.data());
|
errorStr = QCoreApplication::translate("QHttp", "Unsupported content encoding: %1")
|
||||||
|
.arg(QLatin1String(encoding));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
errorStr = QString(); // clear error
|
||||||
return setEncoding(ce);
|
return setEncoding(ce);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +183,8 @@ bool QDecompressHelper::setEncoding(ContentEncoding ce)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!decoderPointer) {
|
if (!decoderPointer) {
|
||||||
qWarning("Failed to initialize the decoder.");
|
errorStr = QCoreApplication::translate("QHttp",
|
||||||
|
"Failed to initialize the compression decoder.");
|
||||||
contentEncoding = QDecompressHelper::None;
|
contentEncoding = QDecompressHelper::None;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -386,8 +391,13 @@ qsizetype QDecompressHelper::read(char *data, qsizetype maxSize)
|
|||||||
uncompressedBytes -= bytesRead;
|
uncompressedBytes -= bytesRead;
|
||||||
|
|
||||||
totalUncompressedBytes += bytesRead;
|
totalUncompressedBytes += bytesRead;
|
||||||
if (isPotentialArchiveBomb())
|
if (isPotentialArchiveBomb()) {
|
||||||
|
errorStr = QCoreApplication::translate(
|
||||||
|
"QHttp",
|
||||||
|
"The decompressed output exceeds the limits specified by "
|
||||||
|
"QNetworkRequest::decompressedSafetyCheckThreshold()");
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
@ -458,11 +468,29 @@ qint64 QDecompressHelper::encodedBytesAvailable() const
|
|||||||
return compressedDataBuffer.byteAmount();
|
return compressedDataBuffer.byteAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
Returns whether or not the object is valid.
|
||||||
|
If it becomes invalid after an operation has been performed
|
||||||
|
then an error has occurred.
|
||||||
|
\sa errorString()
|
||||||
|
*/
|
||||||
bool QDecompressHelper::isValid() const
|
bool QDecompressHelper::isValid() const
|
||||||
{
|
{
|
||||||
return contentEncoding != None;
|
return contentEncoding != None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
Returns a string describing the error that occurred or an empty
|
||||||
|
string if no error occurred.
|
||||||
|
\sa isValid()
|
||||||
|
*/
|
||||||
|
QString QDecompressHelper::errorString() const
|
||||||
|
{
|
||||||
|
return errorStr;
|
||||||
|
}
|
||||||
|
|
||||||
void QDecompressHelper::clear()
|
void QDecompressHelper::clear()
|
||||||
{
|
{
|
||||||
switch (contentEncoding) {
|
switch (contentEncoding) {
|
||||||
@ -504,6 +532,8 @@ void QDecompressHelper::clear()
|
|||||||
uncompressedBytes = 0;
|
uncompressedBytes = 0;
|
||||||
totalUncompressedBytes = 0;
|
totalUncompressedBytes = 0;
|
||||||
totalCompressedBytes = 0;
|
totalCompressedBytes = 0;
|
||||||
|
|
||||||
|
errorStr.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
qsizetype QDecompressHelper::readZLib(char *data, const qsizetype maxSize)
|
qsizetype QDecompressHelper::readZLib(char *data, const qsizetype maxSize)
|
||||||
@ -659,8 +689,9 @@ qsizetype QDecompressHelper::readBrotli(char *data, const qsizetype maxSize)
|
|||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case BROTLI_DECODER_RESULT_ERROR:
|
case BROTLI_DECODER_RESULT_ERROR:
|
||||||
qWarning("Brotli error: %s",
|
errorStr = QLatin1String("Brotli error: %1")
|
||||||
BrotliDecoderErrorString(BrotliDecoderGetErrorCode(brotliDecoderState)));
|
.arg(QString::fromUtf8(BrotliDecoderErrorString(
|
||||||
|
BrotliDecoderGetErrorCode(brotliDecoderState))));
|
||||||
return -1;
|
return -1;
|
||||||
case BROTLI_DECODER_RESULT_SUCCESS:
|
case BROTLI_DECODER_RESULT_SUCCESS:
|
||||||
BrotliDecoderDestroyInstance(brotliDecoderState);
|
BrotliDecoderDestroyInstance(brotliDecoderState);
|
||||||
@ -706,7 +737,8 @@ qsizetype QDecompressHelper::readZstandard(char *data, const qsizetype maxSize)
|
|||||||
while (outBuf.pos < outBuf.size && (inBuf.pos < inBuf.size || decoderHasData)) {
|
while (outBuf.pos < outBuf.size && (inBuf.pos < inBuf.size || decoderHasData)) {
|
||||||
size_t retValue = ZSTD_decompressStream(zstdStream, &outBuf, &inBuf);
|
size_t retValue = ZSTD_decompressStream(zstdStream, &outBuf, &inBuf);
|
||||||
if (ZSTD_isError(retValue)) {
|
if (ZSTD_isError(retValue)) {
|
||||||
qWarning("ZStandard error: %s", ZSTD_getErrorName(retValue));
|
errorStr = QLatin1String("ZStandard error: %1")
|
||||||
|
.arg(QString::fromUtf8(ZSTD_getErrorName(retValue)));
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
decoderHasData = false;
|
decoderHasData = false;
|
||||||
|
@ -96,6 +96,8 @@ public:
|
|||||||
static bool isSupportedEncoding(const QByteArray &encoding);
|
static bool isSupportedEncoding(const QByteArray &encoding);
|
||||||
static QByteArrayList acceptedEncoding();
|
static QByteArrayList acceptedEncoding();
|
||||||
|
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isPotentialArchiveBomb() const;
|
bool isPotentialArchiveBomb() const;
|
||||||
|
|
||||||
@ -117,6 +119,8 @@ private:
|
|||||||
std::unique_ptr<QDecompressHelper> countHelper;
|
std::unique_ptr<QDecompressHelper> countHelper;
|
||||||
qint64 uncompressedBytes = 0;
|
qint64 uncompressedBytes = 0;
|
||||||
|
|
||||||
|
QString errorStr;
|
||||||
|
|
||||||
// Used for calculating the ratio
|
// Used for calculating the ratio
|
||||||
qint64 archiveBombCheckThreshold = 10 * 1024 * 1024;
|
qint64 archiveBombCheckThreshold = 10 * 1024 * 1024;
|
||||||
qint64 totalUncompressedBytes = 0;
|
qint64 totalUncompressedBytes = 0;
|
||||||
|
@ -1297,9 +1297,11 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame,
|
|||||||
output.resize(read);
|
output.resize(read);
|
||||||
replyPrivate->responseData.append(std::move(output));
|
replyPrivate->responseData.append(std::move(output));
|
||||||
} else if (read < 0) {
|
} else if (read < 0) {
|
||||||
finishStreamWithError(
|
const QString decompressError =
|
||||||
stream, QNetworkReply::ProtocolFailure,
|
QCoreApplication::translate("QHttp", "Decompression failed: %1")
|
||||||
QCoreApplication::translate("QHttp", "Data corrupted"));
|
.arg(replyPrivate->decompressHelper.errorString());
|
||||||
|
finishStreamWithError(stream, QNetworkReply::UnknownContentError,
|
||||||
|
decompressError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,13 @@ void QHttpProtocolHandler::_q_receiveReply()
|
|||||||
}
|
}
|
||||||
} else if (haveRead == -1) {
|
} else if (haveRead == -1) {
|
||||||
// Some error occurred
|
// Some error occurred
|
||||||
m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
|
if (replyPrivate->autoDecompress && !replyPrivate->decompressHelper.isValid()) {
|
||||||
|
m_connection->d_func()->emitReplyError(m_socket, m_reply,
|
||||||
|
QNetworkReply::UnknownContentError);
|
||||||
|
} else {
|
||||||
|
m_connection->d_func()->emitReplyError(m_socket, m_reply,
|
||||||
|
QNetworkReply::ProtocolFailure);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,4 +193,14 @@
|
|||||||
\code
|
\code
|
||||||
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
|
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
|
\section2 QNetworkAccessManager now guards against archive bombs
|
||||||
|
|
||||||
|
Starting with Qt 6.2 QNetworkAccessManager will guard against compressed
|
||||||
|
files that decompress to files which are much larger than their compressed
|
||||||
|
form by erroring out the reply if the decompression ratio exceeds a certain
|
||||||
|
threshold.
|
||||||
|
This check is only applied to files larger than a certain size, which can be
|
||||||
|
customized (or disabled by passing -1) by calling
|
||||||
|
\l{QNetworkRequest::setDecompressedSafetyCheckThreshold()}.
|
||||||
*/
|
*/
|
||||||
|
@ -424,10 +424,13 @@ void tst_QDecompressHelper::archiveBomb()
|
|||||||
QVERIFY(bytesRead <= output.size());
|
QVERIFY(bytesRead <= output.size());
|
||||||
QVERIFY(helper.isValid());
|
QVERIFY(helper.isValid());
|
||||||
|
|
||||||
if (shouldFail)
|
if (shouldFail) {
|
||||||
QCOMPARE(bytesRead, -1);
|
QCOMPARE(bytesRead, -1);
|
||||||
else
|
QVERIFY(!helper.errorString().isEmpty());
|
||||||
|
} else {
|
||||||
QVERIFY(bytesRead > 0);
|
QVERIFY(bytesRead > 0);
|
||||||
|
QVERIFY(helper.errorString().isEmpty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QDecompressHelper::bigZlib()
|
void tst_QDecompressHelper::bigZlib()
|
||||||
|
@ -7077,7 +7077,7 @@ void tst_QNetworkReply::compressedHttpReplyBrokenGzip()
|
|||||||
|
|
||||||
QCOMPARE(waitForFinish(reply), int(Failure));
|
QCOMPARE(waitForFinish(reply), int(Failure));
|
||||||
|
|
||||||
QCOMPARE(reply->error(), QNetworkReply::ProtocolFailure);
|
QCOMPARE(reply->error(), QNetworkReply::UnknownContentError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add similar test for FTP
|
// TODO add similar test for FTP
|
||||||
|
Loading…
x
Reference in New Issue
Block a user