diff options
Diffstat (limited to 'src/network/access/qhttpnetworkreply.cpp')
-rw-r--r-- | src/network/access/qhttpnetworkreply.cpp | 236 |
1 files changed, 76 insertions, 160 deletions
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index 0ad5ee372e..5711c96b18 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qhttpnetworkreply_p.h" #include "qhttpnetworkconnection_p.h" @@ -50,6 +14,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + QHttpNetworkReply::QHttpNetworkReply(const QUrl &url, QObject *parent) : QObject(*new QHttpNetworkReplyPrivate(url), parent) { @@ -101,12 +67,12 @@ void QHttpNetworkReply::setContentLength(qint64 length) d->setContentLength(length); } -QList<QPair<QByteArray, QByteArray> > QHttpNetworkReply::header() const +QHttpHeaders QHttpNetworkReply::header() const { - return d_func()->fields; + return d_func()->parser.headers(); } -QByteArray QHttpNetworkReply::headerField(const QByteArray &name, const QByteArray &defaultValue) const +QByteArray QHttpNetworkReply::headerField(QByteArrayView name, const QByteArray &defaultValue) const { return d_func()->headerField(name, defaultValue); } @@ -117,7 +83,13 @@ void QHttpNetworkReply::setHeaderField(const QByteArray &name, const QByteArray d->setHeaderField(name, data); } -void QHttpNetworkReply::parseHeader(const QByteArray &header) +void QHttpNetworkReply::appendHeaderField(const QByteArray &name, const QByteArray &data) +{ + Q_D(QHttpNetworkReply); + d->appendHeaderField(name, data); +} + +void QHttpNetworkReply::parseHeader(QByteArrayView header) { Q_D(QHttpNetworkReply); d->parseHeader(header); @@ -137,13 +109,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 +130,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 +146,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 +235,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 +302,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), @@ -331,8 +318,7 @@ QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) { QString scheme = newUrl.scheme(); - if (scheme == QLatin1String("preconnect-http") - || scheme == QLatin1String("preconnect-https")) + if (scheme == "preconnect-http"_L1 || scheme == "preconnect-https"_L1) // make sure we do not close the socket after preconnecting connectionCloseEnabled = false; } @@ -342,7 +328,6 @@ QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() = default; void QHttpNetworkReplyPrivate::clearHttpLayerInformation() { state = NothingDoneState; - statusCode = 100; bodyLength = 0; contentRead = 0; totalProgress = 0; @@ -350,7 +335,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 @@ -383,36 +368,17 @@ 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; + constexpr auto name = QByteArrayView("content-length"); + QByteArray contentLength = parser.firstHeaderField(name); + bool parseOk = false; + qint64 value = contentLength.toLongLong(&parseOk); + if (parseOk) { + removedContentLength = value; + parser.removeHeaderField(name); } } -bool QHttpNetworkReplyPrivate::findChallenge(bool forProxy, QByteArray &challenge) const -{ - challenge.clear(); - // find out the type of authentication protocol requested. - QByteArray header = forProxy ? "proxy-authenticate" : "www-authenticate"; - // pick the best protocol (has to match parsing in QAuthenticatorPrivate) - QList<QByteArray> challenges = headerFieldValues(header); - for (int i = 0; i<challenges.size(); i++) { - QByteArray line = challenges.at(i); - // todo use qstrincmp - if (!line.toLower().startsWith("negotiate")) - challenge = line; - } - return !challenge.isEmpty(); -} - -qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) +qint64 QHttpNetworkReplyPrivate::readStatus(QIODevice *socket) { if (fragment.isEmpty()) { // reserve bytes for the status line. This is better than always append() which reallocs the byte array @@ -438,7 +404,7 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) if (c == '\n') { // remove the CR at the end if (fragment.endsWith('\r')) { - fragment.truncate(fragment.length()-1); + fragment.truncate(fragment.size()-1); } bool ok = parseStatus(fragment); state = ReadingHeaderState; @@ -452,7 +418,7 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) } // is this a valid reply? - if (fragment.length() == 5 && !fragment.startsWith("HTTP/")) { + if (fragment.size() == 5 && !fragment.startsWith("HTTP/")) { fragment.clear(); return -1; } @@ -461,43 +427,12 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) return bytes; } -bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status) +bool QHttpNetworkReplyPrivate::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); // 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) +qint64 QHttpNetworkReplyPrivate::readHeader(QIODevice *socket) { if (fragment.isEmpty()) { // according to http://dev.opera.com/articles/view/mama-http-headers/ the average size of the header @@ -531,8 +466,8 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) allHeaders = true; // there is another case: We have no headers. Then the fragment equals just the line ending - if ((fragment.length() == 2 && fragment.endsWith("\r\n")) - || (fragment.length() == 1 && fragment.endsWith("\n"))) + if ((fragment.size() == 2 && fragment.endsWith("\r\n")) + || (fragment.size() == 1 && fragment.endsWith("\n"))) allHeaders = true; } } @@ -553,42 +488,20 @@ 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; } -void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header) +void QHttpNetworkReplyPrivate::parseHeader(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.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() @@ -603,7 +516,7 @@ bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled() // note this function can only be used for non-chunked, non-compressed with // known content length -qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char *b) +qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QIODevice *socket, char *b) { // This first read is to flush the buffer inside the socket qint64 haveRead = 0; @@ -622,7 +535,7 @@ qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char // note this function can only be used for non-chunked, non-compressed with // known content length -qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb) +qint64 QHttpNetworkReplyPrivate::readBodyFast(QIODevice *socket, QByteDataBuffer *rb) { qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead); @@ -652,7 +565,7 @@ qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteData } -qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuffer *out) +qint64 QHttpNetworkReplyPrivate::readBody(QIODevice *socket, QByteDataBuffer *out) { qint64 bytes = 0; @@ -672,7 +585,7 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff return bytes; } -qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByteDataBuffer *out, qint64 size) +qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *socket, QByteDataBuffer *out, qint64 size) { // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable() qint64 bytes = 0; @@ -705,7 +618,7 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByte } -qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, QByteDataBuffer *out) +qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *socket, QByteDataBuffer *out) { qint64 bytes = 0; while (socket->bytesAvailable()) { @@ -767,7 +680,7 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, Q return bytes; } -qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *chunkSize) +qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *socket, qint64 *chunkSize) { qint64 bytes = 0; char crlf[2]; @@ -788,8 +701,8 @@ qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *c bytes += socket->read(crlf, 1); // read the \n bool ok = false; // ignore the chunk-extension - fragment = fragment.mid(0, fragment.indexOf(';')).trimmed(); - *chunkSize = fragment.toLong(&ok, 16); + const auto fragmentView = QByteArrayView(fragment).mid(0, fragment.indexOf(';')).trimmed(); + *chunkSize = fragmentView.toLong(&ok, 16); fragment.clear(); break; // size done } else { @@ -811,7 +724,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 +732,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) @@ -890,3 +804,5 @@ void QHttpNetworkReply::ignoreSslErrors(const QList<QSslError> &errors) QT_END_NAMESPACE + +#include "moc_qhttpnetworkreply_p.cpp" |