Move QNAM's http header parsing into separate class
Fixes: QTBUG-80701 Change-Id: I43f5e102c15d121dba74e07e3cd4bb8aded1c763 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>bb10
parent
1fb242ba9f
commit
9a602e68cf
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
parser.parseHeaders(header);
|
||||
}
|
||||
|
||||
fields.append(qMakePair(field, value));
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ bool Http2Server::verifyProtocolUpgradeRequest()
|
|||
QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func();
|
||||
|
||||
// That's how we append them, that's what I expect to find:
|
||||
for (const auto &header : firstRequestReader->fields) {
|
||||
for (const auto &header : firstRequestReader->headers()) {
|
||||
if (header.first == "Connection")
|
||||
connectionOk = header.second.contains("Upgrade, HTTP2-Settings");
|
||||
else if (header.first == "Upgrade")
|
||||
|
|
|
|||
Loading…
Reference in New Issue