diff options
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 |
commit | 9a602e68cf9dd29638027894032b6052c0526426 (patch) | |
tree | 288622ec0b595a7ecbd33413c3ff0a2b13e9b9c3 /src/network | |
parent | 1fb242ba9f9b7d96c34acafadac3815821ea2f7d (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.txt | 1 | ||||
-rw-r--r-- | src/network/access/qhttp2protocolhandler.cpp | 8 | ||||
-rw-r--r-- | src/network/access/qhttpheaderparser.cpp | 235 | ||||
-rw-r--r-- | src/network/access/qhttpheaderparser_p.h | 104 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 2 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkheader.cpp | 41 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkheader_p.h | 6 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply.cpp | 133 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply_p.h | 13 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest.cpp | 2 | ||||
-rw-r--r-- | src/network/access/qhttpprotocolhandler.cpp | 4 | ||||
-rw-r--r-- | src/network/socket/qhttpsocketengine.cpp | 2 |
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; } |