Skip to content

Commit

Permalink
QNetworkReply: Decompress when reading
Browse files Browse the repository at this point in the history
Rather than when the data is received. Source compatibility is
achieved through double-decompressing the data. This lets us know
how many bytes are available just as before but without having the
uncompressed data left in memory.

Fixes: QTBUG-83269
Change-Id: I352bd09581614c582e4628243e2a0e895ba4946b
Reviewed-by: Edward Welbourne <[email protected]>
  • Loading branch information
Morten242 committed Jun 11, 2021
1 parent f9b8672 commit 6f25051
Show file tree
Hide file tree
Showing 16 changed files with 1,670 additions and 125 deletions.
24 changes: 2 additions & 22 deletions src/network/access/qhttp2protocolhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1233,13 +1233,8 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
if (QHttpNetworkReply::isHttpRedirect(statusCode) && redirectUrl.isValid())
httpReply->setRedirectUrl(redirectUrl);

if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress) {
if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress)
httpReplyPrivate->removeAutoDecompressHeader();
httpReplyPrivate->decompressHelper.setEncoding(
httpReplyPrivate->headerField("content-encoding"));
httpReplyPrivate->decompressHelper.setMinimumArchiveBombSize(
httpReplyPrivate->request.minimumArchiveBombSize());
}

if (QHttpNetworkReply::isHttpRedirect(statusCode)) {
// Note: This status code can trigger uploadByteDevice->reset() in
Expand Down Expand Up @@ -1276,27 +1271,12 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame,

if (const auto length = frame.dataSize()) {
const char *data = reinterpret_cast<const char *>(frame.dataBegin());
auto &httpRequest = stream.request();
auto replyPrivate = httpReply->d_func();

replyPrivate->totalProgress += length;

const QByteArray wrapped(data, length);
if (httpRequest.d->autoDecompress && replyPrivate->isCompressed()) {
Q_ASSERT(replyPrivate->decompressHelper.isValid());

replyPrivate->decompressHelper.feed(wrapped);
while (replyPrivate->decompressHelper.hasData()) {
QByteArray output(4 * 1024, Qt::Uninitialized);
qint64 read = replyPrivate->decompressHelper.read(output.data(), output.size());
if (read > 0) {
output.resize(read);
replyPrivate->responseData.append(std::move(output));
}
}
} else {
replyPrivate->responseData.append(wrapped);
}
replyPrivate->responseData.append(wrapped);

if (replyPrivate->shouldEmitSignals()) {
if (connectionType == Qt::DirectConnection) {
Expand Down
1 change: 1 addition & 0 deletions src/network/access/qhttpnetworkconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <qnetworkproxy.h>
#include <qauthenticator.h>
#include <qcoreapplication.h>
#include <private/qdecompresshelper_p.h>

#include <qbuffer.h>
#include <qpair.h>
Expand Down
38 changes: 5 additions & 33 deletions src/network/access/qhttpnetworkreply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
# include <QtNetwork/qsslconfiguration.h>
#endif

#include <private/qdecompresshelper_p.h>

QT_BEGIN_NAMESPACE

QHttpNetworkReply::QHttpNetworkReply(const QUrl &url, QObject *parent)
Expand Down Expand Up @@ -348,7 +350,6 @@ void QHttpNetworkReplyPrivate::clearHttpLayerInformation()
currentChunkRead = 0;
lastChunkRead = false;
connectionCloseEnabled = true;
decompressHelper.clear();
fields.clear();
}

Expand Down Expand Up @@ -554,11 +555,6 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
headerField("proxy-connection").toLower().contains("close")) ||
(majorVersion == 1 && minorVersion == 0 &&
(connectionHeaderField.isEmpty() && !headerField("proxy-connection").toLower().contains("keep-alive")));
if (autoDecompress && isCompressed()) {
if (!decompressHelper.setEncoding(headerField("content-encoding")))
return -1; // Either the encoding was unsupported or the decoder could not be set up
decompressHelper.setMinimumArchiveBombSize(request.minimumArchiveBombSize());
}
}
return bytes;
}
Expand Down Expand Up @@ -660,42 +656,18 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff
{
qint64 bytes = 0;

// for compressed data we'll allocate a temporary one that we then decompress
QByteDataBuffer *tempOutDataBuffer = (autoDecompress ? new QByteDataBuffer : out);


if (isChunked()) {
// chunked transfer encoding (rfc 2616, sec 3.6)
bytes += readReplyBodyChunked(socket, tempOutDataBuffer);
bytes += readReplyBodyChunked(socket, out);
} else if (bodyLength > 0) {
// we have a Content-Length
bytes += readReplyBodyRaw(socket, tempOutDataBuffer, bodyLength - contentRead);
bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead);
if (contentRead + bytes == bodyLength)
state = AllDoneState;
} else {
// no content length. just read what's possible
bytes += readReplyBodyRaw(socket, tempOutDataBuffer, socket->bytesAvailable());
}

// This is true if there is compressed encoding and we're supposed to use it.
if (autoDecompress) {
QScopedPointer holder(tempOutDataBuffer);
if (!decompressHelper.isValid())
return -1;

decompressHelper.feed(std::move(*tempOutDataBuffer));
while (decompressHelper.hasData()) {
QByteArray output(4 * 1024, Qt::Uninitialized);
qint64 read = decompressHelper.read(output.data(), output.size());
if (read < 0) {
return -1;
} else if (read > 0) {
output.resize(read);
out->append(std::move(output));
}
}
bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable());
}

contentRead += bytes;
return bytes;
}
Expand Down
2 changes: 0 additions & 2 deletions src/network/access/qhttpnetworkreply_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,6 @@ class Q_AUTOTEST_EXPORT QHttpNetworkReplyPrivate : public QObjectPrivate, public

char* userProvidedDownloadBuffer;
QUrl redirectUrl;

QDecompressHelper decompressHelper;
};


Expand Down
13 changes: 1 addition & 12 deletions src/network/access/qhttpnetworkrequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
customVerb(other.customVerb),
priority(other.priority),
uploadByteDevice(other.uploadByteDevice),
minimumArchiveBombSize(other.minimumArchiveBombSize),
autoDecompress(other.autoDecompress),
pipeliningAllowed(other.pipeliningAllowed),
http2Allowed(other.http2Allowed),
Expand Down Expand Up @@ -94,7 +93,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot
&& (redirectPolicy == other.redirectPolicy)
&& (peerVerifyName == other.peerVerifyName)
&& (needResendWithCredentials == other.needResendWithCredentials)
&& (minimumArchiveBombSize == other.minimumArchiveBombSize);
;
}

QByteArray QHttpNetworkRequest::methodName() const
Expand Down Expand Up @@ -406,15 +405,5 @@ void QHttpNetworkRequest::setPeerVerifyName(const QString &peerName)
d->peerVerifyName = peerName;
}

qint64 QHttpNetworkRequest::minimumArchiveBombSize() const
{
return d->minimumArchiveBombSize;
}

void QHttpNetworkRequest::setMinimumArchiveBombSize(qint64 threshold)
{
d->minimumArchiveBombSize = threshold;
}

QT_END_NAMESPACE

4 changes: 0 additions & 4 deletions src/network/access/qhttpnetworkrequest_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,6 @@ class Q_AUTOTEST_EXPORT QHttpNetworkRequest: public QHttpNetworkHeader
QString peerVerifyName() const;
void setPeerVerifyName(const QString &peerName);

qint64 minimumArchiveBombSize() const;
void setMinimumArchiveBombSize(qint64 threshold);

private:
QSharedDataPointer<QHttpNetworkRequestPrivate> d;
friend class QHttpNetworkRequestPrivate;
Expand All @@ -178,7 +175,6 @@ class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate
QByteArray customVerb;
QHttpNetworkRequest::Priority priority;
mutable QNonContiguousByteDevice* uploadByteDevice;
qint64 minimumArchiveBombSize = 0;
bool autoDecompress;
bool pipeliningAllowed;
bool http2Allowed;
Expand Down
3 changes: 1 addition & 2 deletions src/network/access/qhttpprotocolhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,7 @@ void QHttpProtocolHandler::_q_receiveReply()
m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::RemoteHostClosedError);
break;
}
} else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress
&& replyPrivate->bodyLength > 0) {
} else if (!replyPrivate->isChunked() && replyPrivate->bodyLength > 0) {
// bulk files like images should fulfill these properties and
// we can therefore save on memory copying
qint64 haveRead = replyPrivate->readBodyFast(m_socket, &replyPrivate->responseData);
Expand Down
5 changes: 4 additions & 1 deletion src/network/access/qhttpthreaddelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ void QHttpThreadDelegate::synchronousFinishedSlot()
incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
}

isCompressed = httpReply->isCompressed();
synchronousDownloadData = httpReply->readAll();

QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
Expand Down Expand Up @@ -634,6 +635,7 @@ void QHttpThreadDelegate::headerChangedSlot()
incomingContentLength = httpReply->contentLength();
removedContentLength = httpReply->removedContentLength();
isHttp2Used = httpReply->isHttp2Used();
isCompressed = httpReply->isCompressed();

emit downloadMetaData(incomingHeaders,
incomingStatusCode,
Expand All @@ -642,7 +644,8 @@ void QHttpThreadDelegate::headerChangedSlot()
downloadBuffer,
incomingContentLength,
removedContentLength,
isHttp2Used);
isHttp2Used,
isCompressed);
}

void QHttpThreadDelegate::synchronousHeaderChangedSlot()
Expand Down
4 changes: 3 additions & 1 deletion src/network/access/qhttpthreaddelegate_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ class QHttpThreadDelegate : public QObject
QString incomingErrorDetail;
QHttp2Configuration http2Parameters;

bool isCompressed;

protected:
// The zerocopy download buffer, if used:
QSharedPointer<char> downloadBuffer;
Expand All @@ -142,7 +144,7 @@ class QHttpThreadDelegate : public QObject
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *);
#endif
void downloadMetaData(const QList<QPair<QByteArray,QByteArray> > &, int, const QString &, bool,
QSharedPointer<char>, qint64, qint64, bool);
QSharedPointer<char>, qint64, qint64, bool, bool);
void downloadProgress(qint64, qint64);
void downloadData(const QByteArray &);
void error(QNetworkReply::NetworkError, const QString &);
Expand Down
Loading

0 comments on commit 6f25051

Please sign in to comment.