summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorØystein Heskestad <oystein.heskestad@qt.io>2021-08-03 16:31:52 +0200
committerØystein Heskestad <oystein.heskestad@qt.io>2021-09-09 16:55:50 +0200
commit9a602e68cf9dd29638027894032b6052c0526426 (patch)
tree288622ec0b595a7ecbd33413c3ff0a2b13e9b9c3 /src/network
parent1fb242ba9f9b7d96c34acafadac3815821ea2f7d (diff)
Move QNAM's http header parsing into separate class
Fixes: QTBUG-80701 Change-Id: I43f5e102c15d121dba74e07e3cd4bb8aded1c763 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r--src/network/CMakeLists.txt1
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp8
-rw-r--r--src/network/access/qhttpheaderparser.cpp235
-rw-r--r--src/network/access/qhttpheaderparser_p.h104
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp2
-rw-r--r--src/network/access/qhttpnetworkheader.cpp41
-rw-r--r--src/network/access/qhttpnetworkheader_p.h6
-rw-r--r--src/network/access/qhttpnetworkreply.cpp133
-rw-r--r--src/network/access/qhttpnetworkreply_p.h13
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp2
-rw-r--r--src/network/access/qhttpprotocolhandler.cpp4
-rw-r--r--src/network/socket/qhttpsocketengine.cpp2
12 files changed, 419 insertions, 132 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
index f5887aaa51..7c587b5c20 100644
--- a/src/network/CMakeLists.txt
+++ b/src/network/CMakeLists.txt
@@ -20,6 +20,7 @@ qt_internal_add_module(Network
access/qnetworkcookie.cpp access/qnetworkcookie.h access/qnetworkcookie_p.h
access/qnetworkcookiejar.cpp access/qnetworkcookiejar.h access/qnetworkcookiejar_p.h
access/qnetworkfile.cpp access/qnetworkfile_p.h
+ access/qhttpheaderparser.cpp access/qhttpheaderparser_p.h
access/qnetworkreply.cpp access/qnetworkreply.h access/qnetworkreply_p.h
access/qnetworkreplydataimpl.cpp access/qnetworkreplydataimpl_p.h
access/qnetworkreplyfileimpl.cpp access/qnetworkreplyfileimpl_p.h
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index b892a6b2a2..26d80f5298 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -1150,10 +1150,10 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
statusCode = value.left(3).toInt();
httpReply->setStatusCode(statusCode);
m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
- httpReplyPrivate->reasonPhrase = QString::fromLatin1(value.mid(4));
+ httpReply->setReasonPhrase(QString::fromLatin1(value.mid(4)));
} else if (name == ":version") {
- httpReplyPrivate->majorVersion = value.at(5) - '0';
- httpReplyPrivate->minorVersion = value.at(7) - '0';
+ httpReply->setMajorVersion(value.at(5) - '0');
+ httpReply->setMinorVersion(value.at(7) - '0');
} else if (name == "content-length") {
bool ok = false;
const qlonglong length = value.toLongLong(&ok);
@@ -1165,7 +1165,7 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
QByteArray binder(", ");
if (name == "set-cookie")
binder = "\n";
- httpReplyPrivate->fields.append(qMakePair(name, value.replace('\0', binder)));
+ httpReply->appendHeaderField(name, value.replace('\0', binder));
}
}
diff --git a/src/network/access/qhttpheaderparser.cpp b/src/network/access/qhttpheaderparser.cpp
new file mode 100644
index 0000000000..4a6bbd49a7
--- /dev/null
+++ b/src/network/access/qhttpheaderparser.cpp
@@ -0,0 +1,235 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpheaderparser_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QHttpHeaderParser::QHttpHeaderParser()
+ : statusCode(100) // Required by tst_QHttpNetworkConnection::ignoresslerror(failure)
+ , majorVersion(0)
+ , minorVersion(0)
+{
+}
+
+void QHttpHeaderParser::clear()
+{
+ statusCode = 100;
+ majorVersion = 0;
+ minorVersion = 0;
+ reasonPhrase.clear();
+ fields.clear();
+}
+
+bool QHttpHeaderParser::parseHeaders(QByteArrayView header)
+{
+ // see rfc2616, sec 4 for information about HTTP/1.1 headers.
+ // allows relaxed parsing here, accepts both CRLF & LF line endings
+ int i = 0;
+ while (i < header.size()) {
+ int j = header.indexOf(':', i); // field-name
+ if (j == -1)
+ break;
+ QByteArrayView field = header.sliced(i, j - i).trimmed();
+ j++;
+ // any number of LWS is allowed before and after the value
+ QByteArray value;
+ do {
+ i = header.indexOf('\n', j);
+ if (i == -1)
+ break;
+ if (!value.isEmpty())
+ value += ' ';
+ // check if we have CRLF or only LF
+ bool hasCR = i && header[i - 1] == '\r';
+ int length = i - (hasCR ? 1: 0) - j;
+ value += header.sliced(j, length).trimmed();
+ j = ++i;
+ } while (i < header.size() && (header.at(i) == ' ' || header.at(i) == '\t'));
+ if (i == -1)
+ return false; // something is wrong
+
+ fields.append(qMakePair(field.toByteArray(), value));
+ }
+ return true;
+}
+
+bool QHttpHeaderParser::parseStatus(QByteArrayView status)
+{
+ // from RFC 2616:
+ // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+ // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
+ // that makes: 'HTTP/n.n xxx Message'
+ // byte count: 0123456789012
+
+ static const int minLength = 11;
+ static const int dotPos = 6;
+ static const int spacePos = 8;
+ static const char httpMagic[] = "HTTP/";
+
+ if (status.length() < minLength
+ || !status.startsWith(httpMagic)
+ || status.at(dotPos) != '.'
+ || status.at(spacePos) != ' ') {
+ // I don't know how to parse this status line
+ return false;
+ }
+
+ // optimize for the valid case: defer checking until the end
+ majorVersion = status.at(dotPos - 1) - '0';
+ minorVersion = status.at(dotPos + 1) - '0';
+
+ int i = spacePos;
+ int j = status.indexOf(' ', i + 1);
+ const QByteArrayView code = j > i ? status.sliced(i + 1, j - i - 1)
+ : status.sliced(i + 1);
+
+ bool ok;
+ statusCode = code.toInt(&ok);
+
+ reasonPhrase = j > i ? QString::fromLatin1(status.sliced(j + 1))
+ : QString();
+
+ return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9;
+}
+
+const QList<QPair<QByteArray, QByteArray> >& QHttpHeaderParser::headers() const
+{
+ return fields;
+}
+
+QByteArray QHttpHeaderParser::firstHeaderField(const QByteArray &name,
+ const QByteArray &defaultValue) const
+{
+ for (auto it = fields.constBegin(); it != fields.constEnd(); ++it) {
+ if (name.compare(it->first, Qt::CaseInsensitive) == 0)
+ return it->second;
+ }
+ return defaultValue;
+}
+
+QByteArray QHttpHeaderParser::combinedHeaderValue(const QByteArray &name, const QByteArray &defaultValue) const
+{
+ const QList<QByteArray> allValues = headerFieldValues(name);
+ if (allValues.isEmpty())
+ return defaultValue;
+ else
+ return allValues.join(", ");
+}
+
+QList<QByteArray> QHttpHeaderParser::headerFieldValues(const QByteArray &name) const
+{
+ QList<QByteArray> result;
+ for (auto it = fields.constBegin(); it != fields.constEnd(); ++it)
+ if (name.compare(it->first, Qt::CaseInsensitive) == 0)
+ result += it->second;
+
+ return result;
+}
+
+void QHttpHeaderParser::removeHeaderField(const QByteArray &name)
+{
+ auto firstEqualsName = [&name](const QPair<QByteArray, QByteArray> &header) {
+ return name.compare(header.first, Qt::CaseInsensitive) == 0;
+ };
+ fields.removeIf(firstEqualsName);
+}
+
+void QHttpHeaderParser::setHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ removeHeaderField(name);
+ fields.append(qMakePair(name, data));
+}
+
+void QHttpHeaderParser::prependHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ fields.prepend(qMakePair(name, data));
+}
+
+void QHttpHeaderParser::appendHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ fields.append(qMakePair(name, data));
+}
+
+void QHttpHeaderParser::clearHeaders()
+{
+ fields.clear();
+}
+
+int QHttpHeaderParser::getStatusCode() const
+{
+ return statusCode;
+}
+
+void QHttpHeaderParser::setStatusCode(int code)
+{
+ statusCode = code;
+}
+
+int QHttpHeaderParser::getMajorVersion() const
+{
+ return majorVersion;
+}
+
+void QHttpHeaderParser::setMajorVersion(int version)
+{
+ majorVersion = version;
+}
+
+int QHttpHeaderParser::getMinorVersion() const
+{
+ return minorVersion;
+}
+
+void QHttpHeaderParser::setMinorVersion(int version)
+{
+ minorVersion = version;
+}
+
+QString QHttpHeaderParser::getReasonPhrase() const
+{
+ return reasonPhrase;
+}
+
+void QHttpHeaderParser::setReasonPhrase(const QString &reason)
+{
+ reasonPhrase = reason;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpheaderparser_p.h b/src/network/access/qhttpheaderparser_p.h
new file mode 100644
index 0000000000..cc8b91c84f
--- /dev/null
+++ b/src/network/access/qhttpheaderparser_p.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPHEADERPARSER_H
+#define QHTTPHEADERPARSER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
+#include <QByteArray>
+#include <QList>
+#include <QPair>
+#include <QString>
+
+QT_BEGIN_NAMESPACE
+
+class Q_NETWORK_PRIVATE_EXPORT QHttpHeaderParser
+{
+public:
+ QHttpHeaderParser();
+
+ void clear();
+ bool parseHeaders(QByteArrayView headers);
+ bool parseStatus(QByteArrayView status);
+
+ const QList<QPair<QByteArray, QByteArray> >& headers() const;
+ void setStatusCode(int code);
+ int getStatusCode() const;
+ int getMajorVersion() const;
+ void setMajorVersion(int version);
+ int getMinorVersion() const;
+ void setMinorVersion(int version);
+ QString getReasonPhrase() const;
+ void setReasonPhrase(const QString &reason);
+
+ QByteArray firstHeaderField(const QByteArray &name,
+ const QByteArray &defaultValue = QByteArray()) const;
+ QByteArray combinedHeaderValue(const QByteArray &name,
+ const QByteArray &defaultValue = QByteArray()) const;
+ QList<QByteArray> headerFieldValues(const QByteArray &name) const;
+ void setHeaderField(const QByteArray &name, const QByteArray &data);
+ void prependHeaderField(const QByteArray &name, const QByteArray &data);
+ void appendHeaderField(const QByteArray &name, const QByteArray &data);
+ void removeHeaderField(const QByteArray &name);
+ void clearHeaders();
+
+private:
+ QList<QPair<QByteArray, QByteArray> > fields;
+ QString reasonPhrase;
+ int statusCode;
+ int majorVersion;
+ int minorVersion;
+};
+
+
+QT_END_NAMESPACE
+
+#endif // QHTTPHEADERPARSER_H
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 458b729548..f1b2d8edbf 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -584,7 +584,7 @@ void QHttpNetworkConnectionChannel::detectPipeliningSupport()
QByteArray serverHeaderField;
if (
// check for HTTP/1.1
- (reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1)
+ (reply->majorVersion() == 1 && reply->minorVersion() == 1)
// check for not having connection close
&& (!reply->d_func()->isConnectionCloseEnabled())
// check if it is still connected
diff --git a/src/network/access/qhttpnetworkheader.cpp b/src/network/access/qhttpnetworkheader.cpp
index 5901a146dc..78b79d89e5 100644
--- a/src/network/access/qhttpnetworkheader.cpp
+++ b/src/network/access/qhttpnetworkheader.cpp
@@ -48,27 +48,12 @@ QHttpNetworkHeaderPrivate::QHttpNetworkHeaderPrivate(const QUrl &newUrl)
{
}
-QHttpNetworkHeaderPrivate::QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other)
- :QSharedData(other)
-{
- url = other.url;
- fields = other.fields;
-}
-
qint64 QHttpNetworkHeaderPrivate::contentLength() const
{
bool ok = false;
// We are not using the headerField() method here because servers might send us multiple content-length
// headers which is crap (see QTBUG-15311). Therefore just take the first content-length header field.
- QByteArray value;
- QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(),
- end = fields.constEnd();
- for ( ; it != end; ++it)
- if (it->first.compare("content-length", Qt::CaseInsensitive) == 0) {
- value = it->second;
- break;
- }
-
+ QByteArray value = parser.firstHeaderField("content-length");
qint64 length = value.toULongLong(&ok);
if (ok)
return length;
@@ -91,33 +76,27 @@ QByteArray QHttpNetworkHeaderPrivate::headerField(const QByteArray &name, const
QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray &name) const
{
- QList<QByteArray> result;
- QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(),
- end = fields.constEnd();
- for ( ; it != end; ++it)
- if (name.compare(it->first, Qt::CaseInsensitive) == 0)
- result += it->second;
-
- return result;
+ return parser.headerFieldValues(name);
}
void QHttpNetworkHeaderPrivate::setHeaderField(const QByteArray &name, const QByteArray &data)
{
- auto firstEqualsName = [&name](const QPair<QByteArray, QByteArray> &header) {
- return name.compare(header.first, Qt::CaseInsensitive) == 0;
- };
- fields.removeIf(firstEqualsName);
- fields.append(qMakePair(name, data));
+ parser.setHeaderField(name, data);
}
void QHttpNetworkHeaderPrivate::prependHeaderField(const QByteArray &name, const QByteArray &data)
{
- fields.prepend(qMakePair(name, data));
+ parser.prependHeaderField(name, data);
+}
+
+QList<QPair<QByteArray, QByteArray> > QHttpNetworkHeaderPrivate::headers() const
+{
+ return parser.headers();
}
void QHttpNetworkHeaderPrivate::clearHeaders()
{
- fields.clear();
+ parser.clearHeaders();
}
bool QHttpNetworkHeaderPrivate::operator==(const QHttpNetworkHeaderPrivate &other) const
diff --git a/src/network/access/qhttpnetworkheader_p.h b/src/network/access/qhttpnetworkheader_p.h
index c8186eeda0..8447474158 100644
--- a/src/network/access/qhttpnetworkheader_p.h
+++ b/src/network/access/qhttpnetworkheader_p.h
@@ -52,6 +52,7 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
+#include <QtNetwork/private/qhttpheaderparser_p.h>
#include <qshareddata.h>
#include <qurl.h>
@@ -84,10 +85,10 @@ class Q_AUTOTEST_EXPORT QHttpNetworkHeaderPrivate : public QSharedData
{
public:
QUrl url;
- QList<QPair<QByteArray, QByteArray> > fields;
+ QHttpHeaderParser parser;
QHttpNetworkHeaderPrivate(const QUrl &newUrl = QUrl());
- QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other);
+ QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other) = default;
qint64 contentLength() const;
void setContentLength(qint64 length);
@@ -96,6 +97,7 @@ public:
void setHeaderField(const QByteArray &name, const QByteArray &data);
void prependHeaderField(const QByteArray &name, const QByteArray &data);
void clearHeaders();
+ QList<QPair<QByteArray, QByteArray> > headers() const;
bool operator==(const QHttpNetworkHeaderPrivate &other) const;
};
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
index 0ad5ee372e..7582ce179f 100644
--- a/src/network/access/qhttpnetworkreply.cpp
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -103,7 +103,7 @@ void QHttpNetworkReply::setContentLength(qint64 length)
QList<QPair<QByteArray, QByteArray> > QHttpNetworkReply::header() const
{
- return d_func()->fields;
+ return d_func()->parser.headers();
}
QByteArray QHttpNetworkReply::headerField(const QByteArray &name, const QByteArray &defaultValue) const
@@ -117,6 +117,12 @@ void QHttpNetworkReply::setHeaderField(const QByteArray &name, const QByteArray
d->setHeaderField(name, data);
}
+void QHttpNetworkReply::appendHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ Q_D(QHttpNetworkReply);
+ d->appendHeaderField(name, data);
+}
+
void QHttpNetworkReply::parseHeader(const QByteArray &header)
{
Q_D(QHttpNetworkReply);
@@ -137,13 +143,13 @@ void QHttpNetworkReply::setRequest(const QHttpNetworkRequest &request)
int QHttpNetworkReply::statusCode() const
{
- return d_func()->statusCode;
+ return d_func()->parser.getStatusCode();
}
void QHttpNetworkReply::setStatusCode(int code)
{
Q_D(QHttpNetworkReply);
- d->statusCode = code;
+ d->parser.setStatusCode(code);
}
QString QHttpNetworkReply::errorString() const
@@ -158,7 +164,12 @@ QNetworkReply::NetworkError QHttpNetworkReply::errorCode() const
QString QHttpNetworkReply::reasonPhrase() const
{
- return d_func()->reasonPhrase;
+ return d_func()->parser.getReasonPhrase();
+}
+
+void QHttpNetworkReply::setReasonPhrase(const QString &reason)
+{
+ d_func()->parser.setReasonPhrase(reason);
}
void QHttpNetworkReply::setErrorString(const QString &error)
@@ -169,12 +180,22 @@ void QHttpNetworkReply::setErrorString(const QString &error)
int QHttpNetworkReply::majorVersion() const
{
- return d_func()->majorVersion;
+ return d_func()->parser.getMajorVersion();
}
int QHttpNetworkReply::minorVersion() const
{
- return d_func()->minorVersion;
+ return d_func()->parser.getMinorVersion();
+}
+
+void QHttpNetworkReply::setMajorVersion(int version)
+{
+ d_func()->parser.setMajorVersion(version);
+}
+
+void QHttpNetworkReply::setMinorVersion(int version)
+{
+ d_func()->parser.setMinorVersion(version);
}
qint64 QHttpNetworkReply::bytesAvailable() const
@@ -248,7 +269,8 @@ void QHttpNetworkReply::setReadBufferSize(qint64 size)
bool QHttpNetworkReply::supportsUserProvidedDownloadBuffer()
{
Q_D(QHttpNetworkReply);
- return (!d->isChunked() && !d->autoDecompress && d->bodyLength > 0 && d->statusCode == 200);
+ return !d->isChunked() && !d->autoDecompress &&
+ d->bodyLength > 0 && d->parser.getStatusCode() == 200;
}
void QHttpNetworkReply::setUserProvidedDownloadBuffer(char* b)
@@ -314,9 +336,8 @@ QHttpNetworkConnection* QHttpNetworkReply::connection()
QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl)
: QHttpNetworkHeaderPrivate(newUrl)
, state(NothingDoneState)
- , ssl(false)
- , statusCode(100),
- majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0),
+ , ssl(false),
+ bodyLength(0), contentRead(0), totalProgress(0),
chunkedTransferEncoding(false),
connectionCloseEnabled(true),
forceConnectionCloseEnabled(false),
@@ -342,7 +363,6 @@ QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() = default;
void QHttpNetworkReplyPrivate::clearHttpLayerInformation()
{
state = NothingDoneState;
- statusCode = 100;
bodyLength = 0;
contentRead = 0;
totalProgress = 0;
@@ -350,7 +370,7 @@ void QHttpNetworkReplyPrivate::clearHttpLayerInformation()
currentChunkRead = 0;
lastChunkRead = false;
connectionCloseEnabled = true;
- fields.clear();
+ parser.clear();
}
// TODO: Isn't everything HTTP layer related? We don't need to set connection and connectionChannel to 0 at all
@@ -384,15 +404,12 @@ void QHttpNetworkReplyPrivate::removeAutoDecompressHeader()
// The header "Content-Encoding = gzip" is retained.
// Content-Length is removed since the actual one sent by the server is for compressed data
QByteArray name("content-length");
- QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(),
- end = fields.end();
- while (it != end) {
- if (name.compare(it->first, Qt::CaseInsensitive) == 0) {
- removedContentLength = strtoull(it->second.constData(), nullptr, 0);
- fields.erase(it);
- break;
- }
- ++it;
+ QByteArray contentLength = parser.firstHeaderField(name);
+ bool parseOk = false;
+ qint64 value = contentLength.toLongLong(&parseOk);
+ if (parseOk) {
+ removedContentLength = value;
+ parser.removeHeaderField(name);
}
}
@@ -463,38 +480,7 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket)
bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status)
{
- // from RFC 2616:
- // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
- // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
- // that makes: 'HTTP/n.n xxx Message'
- // byte count: 0123456789012
-
- static const int minLength = 11;
- static const int dotPos = 6;
- static const int spacePos = 8;
- static const char httpMagic[] = "HTTP/";
-
- if (status.length() < minLength
- || !status.startsWith(httpMagic)
- || status.at(dotPos) != '.'
- || status.at(spacePos) != ' ') {
- // I don't know how to parse this status line
- return false;
- }
-
- // optimize for the valid case: defer checking until the end
- majorVersion = status.at(dotPos - 1) - '0';
- minorVersion = status.at(dotPos + 1) - '0';
-
- int i = spacePos;
- int j = status.indexOf(' ', i + 1); // j == -1 || at(j) == ' ' so j+1 == 0 && j+1 <= length()
- const QByteArray code = status.mid(i + 1, j - i - 1);
-
- bool ok;
- statusCode = code.toInt(&ok);
- reasonPhrase = QString::fromLatin1(status.constData() + j + 1);
-
- return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9;
+ return parser.parseStatus(status);
}
qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
@@ -553,7 +539,7 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
// check for explicit indication of close or the implicit connection close of HTTP/1.0
connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") ||
headerField("proxy-connection").toLower().contains("close")) ||
- (majorVersion == 1 && minorVersion == 0 &&
+ (parser.getMajorVersion() == 1 && parser.getMinorVersion() == 0 &&
(connectionHeaderField.isEmpty() && !headerField("proxy-connection").toLower().contains("keep-alive")));
}
return bytes;
@@ -561,34 +547,12 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header)
{
- // see rfc2616, sec 4 for information about HTTP/1.1 headers.
- // allows relaxed parsing here, accepts both CRLF & LF line endings
- int i = 0;
- while (i < header.count()) {
- int j = header.indexOf(':', i); // field-name
- if (j == -1)
- break;
- const QByteArray field = header.mid(i, j - i).trimmed();
- j++;
- // any number of LWS is allowed before and after the value
- QByteArray value;
- do {
- i = header.indexOf('\n', j);
- if (i == -1)
- break;
- if (!value.isEmpty())
- value += ' ';
- // check if we have CRLF or only LF
- bool hasCR = (i && header[i-1] == '\r');
- int length = i -(hasCR ? 1: 0) - j;
- value += header.mid(j, length).trimmed();
- j = ++i;
- } while (i < header.count() && (header.at(i) == ' ' || header.at(i) == '\t'));
- if (i == -1)
- break; // something is wrong
-
- fields.append(qMakePair(field, value));
- }
+ parser.parseHeaders(header);
+}
+
+void QHttpNetworkReplyPrivate::appendHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ parser.appendHeaderField(name, data);
}
bool QHttpNetworkReplyPrivate::isChunked()
@@ -811,7 +775,7 @@ bool QHttpNetworkReplyPrivate::isRedirecting() const
{
// We're in the process of redirecting - if the HTTP status code says so and
// followRedirect is switched on
- return (QHttpNetworkReply::isHttpRedirect(statusCode)
+ return (QHttpNetworkReply::isHttpRedirect(parser.getStatusCode())
&& request.isFollowRedirects());
}
@@ -819,11 +783,12 @@ bool QHttpNetworkReplyPrivate::shouldEmitSignals()
{
// for 401 & 407 don't emit the data signals. Content along with these
// responses are sent only if the authentication fails.
- return (statusCode != 401 && statusCode != 407);
+ return parser.getStatusCode() != 401 && parser.getStatusCode() != 407;
}
bool QHttpNetworkReplyPrivate::expectContent()
{
+ int statusCode = parser.getStatusCode();
// check whether we can expect content after the headers (rfc 2616, sec4.4)
if ((statusCode >= 100 && statusCode < 200)
|| statusCode == 204 || statusCode == 304)
diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h
index 5c062259ad..39e8b24303 100644
--- a/src/network/access/qhttpnetworkreply_p.h
+++ b/src/network/access/qhttpnetworkreply_p.h
@@ -87,7 +87,7 @@ class QHttpNetworkConnectionChannel;
class QHttpNetworkRequest;
class QHttpNetworkConnectionPrivate;
class QHttpNetworkReplyPrivate;
-class Q_NETWORK_PRIVATE_EXPORT QHttpNetworkReply : public QObject, public QHttpNetworkHeader
+class Q_AUTOTEST_EXPORT QHttpNetworkReply : public QObject, public QHttpNetworkHeader
{
Q_OBJECT
public:
@@ -100,6 +100,8 @@ public:
int majorVersion() const override;
int minorVersion() const override;
+ void setMajorVersion(int version);
+ void setMinorVersion(int version);
qint64 contentLength() const override;
void setContentLength(qint64 length) override;
@@ -107,7 +109,8 @@ public:
QList<QPair<QByteArray, QByteArray> > header() const override;
QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const override;
void setHeaderField(const QByteArray &name, const QByteArray &data) override;
- void parseHeader(const QByteArray &header); // used by QtWebSockets
+ void appendHeaderField(const QByteArray &name, const QByteArray &data);
+ void parseHeader(const QByteArray &header); // used for testing
QHttpNetworkRequest request() const;
void setRequest(const QHttpNetworkRequest &request);
@@ -121,6 +124,7 @@ public:
QNetworkReply::NetworkError errorCode() const;
QString reasonPhrase() const;
+ void setReasonPhrase(const QString &reason);
qint64 bytesAvailable() const;
qint64 bytesAvailableNextBlock() const;
@@ -205,6 +209,7 @@ public:
bool parseStatus(const QByteArray &status);
qint64 readHeader(QAbstractSocket *socket);
void parseHeader(const QByteArray &header);
+ void appendHeaderField(const QByteArray &name, const QByteArray &data);
qint64 readBody(QAbstractSocket *socket, QByteDataBuffer *out);
qint64 readBodyVeryFast(QAbstractSocket *socket, char *b);
qint64 readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb);
@@ -243,11 +248,7 @@ public:
QHttpNetworkRequest request;
bool ssl;
- int statusCode;
- int majorVersion;
- int minorVersion;
QString errorString;
- QString reasonPhrase;
qint64 bodyLength;
qint64 contentRead;
qint64 totalProgress;
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
index e3c699efed..f370445059 100644
--- a/src/network/access/qhttpnetworkrequest.cpp
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -271,7 +271,7 @@ void QHttpNetworkRequest::setContentLength(qint64 length)
QList<QPair<QByteArray, QByteArray> > QHttpNetworkRequest::header() const
{
- return d->fields;
+ return d->parser.headers();
}
QByteArray QHttpNetworkRequest::headerField(const QByteArray &name, const QByteArray &defaultValue) const
diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
index 578dcfa1b0..488c226459 100644
--- a/src/network/access/qhttpprotocolhandler.cpp
+++ b/src/network/access/qhttpprotocolhandler.cpp
@@ -107,7 +107,7 @@ void QHttpProtocolHandler::_q_receiveReply()
return;
}
bytes += statusBytes;
- m_channel->lastStatus = m_reply->d_func()->statusCode;
+ m_channel->lastStatus = m_reply->statusCode();
break;
}
case QHttpNetworkReplyPrivate::ReadingHeaderState: {
@@ -127,7 +127,7 @@ void QHttpProtocolHandler::_q_receiveReply()
} else {
replyPrivate->autoDecompress = false;
}
- if (replyPrivate->statusCode == 100) {
+ if (m_reply->statusCode() == 100) {
replyPrivate->clearHttpLayerInformation();
replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
break; // ignore
diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp
index 7ba3e17873..0a8b01efbd 100644
--- a/src/network/socket/qhttpsocketengine.cpp
+++ b/src/network/socket/qhttpsocketengine.cpp
@@ -579,7 +579,7 @@ void QHttpSocketEngine::slotSocketReadNotification()
d->pendingResponseData -= uint(skipped);
if (d->pendingResponseData > 0)
return;
- if (d->reply->d_func()->statusCode == 407)
+ if (d->reply->statusCode() == 407)
d->state = SendAuthentication;
}