summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorØystein Heskestad <oystein.heskestad@qt.io>2021-11-11 11:26:32 +0100
committerØystein Heskestad <oystein.heskestad@qt.io>2021-12-09 17:56:53 +0100
commit8341a3f435cdbcdda25d67f504ca630d285faa63 (patch)
tree0ef4ea7911b36a0ea5112231d0e9fdfab4bb1dbf /src
parentcb7abc7ac0f19e0076a3927b0dd880606c6f92f6 (diff)
downloadqtwebsockets-8341a3f435cdbcdda25d67f504ca630d285faa63.tar.gz
Reuse qtbase's HTTP header parser to avoid a reinvented wheel
Fixes: QTBUG-80700 Change-Id: I7b713fd869ac802d5eee8ebb8d90a2115365b509 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/websockets/qwebsocket_p.cpp2
-rw-r--r--src/websockets/qwebsockethandshakerequest.cpp158
-rw-r--r--src/websockets/qwebsockethandshakerequest_p.h8
-rw-r--r--src/websockets/qwebsocketserver_p.cpp3
4 files changed, 68 insertions, 103 deletions
diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp
index 94ea60e..62d53c5 100644
--- a/src/websockets/qwebsocket_p.cpp
+++ b/src/websockets/qwebsocket_p.cpp
@@ -334,7 +334,7 @@ QWebSocket *QWebSocketPrivate::upgradeFrom(QTcpSocket *pTcpSocket,
QNetworkRequest netRequest(request.requestUrl());
const auto headers = request.headers();
for (auto it = headers.begin(), end = headers.end(); it != end; ++it)
- netRequest.setRawHeader(it.key().toLatin1(), it.value().toLatin1());
+ netRequest.setRawHeader(it->first, it->second);
#ifndef QT_NO_SSL
if (QSslSocket *sslSock = qobject_cast<QSslSocket *>(pTcpSocket))
pWebSocket->setSslConfiguration(sslSock->sslConfiguration());
diff --git a/src/websockets/qwebsockethandshakerequest.cpp b/src/websockets/qwebsockethandshakerequest.cpp
index a40242d..0b1f8ae 100644
--- a/src/websockets/qwebsockethandshakerequest.cpp
+++ b/src/websockets/qwebsockethandshakerequest.cpp
@@ -59,7 +59,7 @@ QWebSocketHandshakeRequest::QWebSocketHandshakeRequest(int port, bool isSecure)
m_port(port),
m_isSecure(isSecure),
m_isValid(false),
- m_headers(),
+ m_parser(),
m_versions(),
m_key(),
m_origin(),
@@ -83,7 +83,7 @@ QWebSocketHandshakeRequest::~QWebSocketHandshakeRequest()
void QWebSocketHandshakeRequest::clear()
{
m_isValid = false;
- m_headers.clear();
+ m_parser.clear();
m_versions.clear();
m_key.clear();
m_origin.clear();
@@ -119,9 +119,17 @@ bool QWebSocketHandshakeRequest::isValid() const
/*!
\internal
*/
-QMultiMap<QString, QString> QWebSocketHandshakeRequest::headers() const
+QList<QPair<QByteArray, QByteArray>> QWebSocketHandshakeRequest::headers() const
{
- return m_headers;
+ return m_parser.headers();
+}
+
+/*!
+ \internal
+ */
+bool QWebSocketHandshakeRequest::hasHeader(const QByteArray &name) const
+{
+ return !m_parser.firstHeaderField(name).isEmpty();
}
/*!
@@ -189,43 +197,43 @@ QUrl QWebSocketHandshakeRequest::requestUrl() const
}
/*!
- Reads a line of text from the given textstream (terminated by CR/LF).
+ Reads a line of text from the given QByteArrayView (terminated by CR/LF),
+ and removes the read bytes from the QByteArrayView.
If an empty line was detected, an empty string is returned.
When an error occurs, a null string is returned.
\internal
*/
-static QString readLine(QTextStream &stream, int maxHeaderLineLength)
+static QByteArrayView readLine(QByteArrayView &header, int maxHeaderLineLength)
{
- QString line;
- char c;
- while (!stream.atEnd()) {
- stream >> c;
- if (stream.status() != QTextStream::Ok)
- return QString();
- if (c == char('\r')) {
- //eat the \n character
- stream >> c;
- line.append(QStringLiteral(""));
- break;
- } else {
- line.append(QChar::fromLatin1(c));
- if (line.length() > maxHeaderLineLength)
- return QString();
- }
+ qsizetype length = qMin(maxHeaderLineLength, header.size());
+ QByteArrayView line = header.first(length);
+ qsizetype end = line.indexOf('\n');
+ if (end != -1) {
+ line = line.first(end);
+ if (line.endsWith('\r'))
+ line.chop(1);
+ header = header.sliced(end + 1);
+ return line;
}
- return line;
+ return QByteArrayView();
}
/*!
\internal
*/
-void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxHeaderLineLength,
- int maxHeaders)
+static void appendCommmaSeparatedLineToList(QStringList &list, QByteArrayView line)
+{
+ for (auto &c : QLatin1String(line).tokenize(QLatin1String(","), Qt::SkipEmptyParts))
+ list << c.trimmed().toString();
+}
+
+/*!
+ \internal
+ */
+void QWebSocketHandshakeRequest::readHandshake(QByteArrayView header, int maxHeaderLineLength)
{
clear();
- if (Q_UNLIKELY(textStream.status() != QTextStream::Ok))
- return;
- const QString requestLine = readLine(textStream, maxHeaderLineLength);
+ QString requestLine = QString::fromLatin1(readLine(header, maxHeaderLineLength));
if (requestLine.isNull()) {
clear();
return;
@@ -245,45 +253,15 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH
clear();
return;
}
- QString headerLine = readLine(textStream, maxHeaderLineLength);
- if (headerLine.isNull()) {
+
+ bool parsed = m_parser.parseHeaders(header);
+ if (!parsed) {
clear();
return;
}
- m_headers.clear();
- // TODO: this should really use the existing code from QHttpNetworkReplyPrivate::parseHeader
- auto lastHeader = m_headers.end();
- while (!headerLine.isEmpty()) {
- if (headerLine.startsWith(QLatin1Char(' ')) || headerLine.startsWith(QLatin1Char('\t'))) {
- // continuation line -- add this to the last header field
- if (Q_UNLIKELY(lastHeader == m_headers.end())) {
- clear();
- return;
- }
- lastHeader.value().append(QLatin1Char(' '));
- lastHeader.value().append(headerLine.trimmed());
- } else {
- int colonPos = headerLine.indexOf(QLatin1Char(':'));
- if (Q_UNLIKELY(colonPos <= 0)) {
- clear();
- return;
- }
- lastHeader = m_headers.insert(headerLine.left(colonPos).trimmed().toLower(),
- headerLine.mid(colonPos + 1).trimmed());
- }
- if (m_headers.size() > maxHeaders) {
- clear();
- return;
- }
- headerLine = readLine(textStream, maxHeaderLineLength);
- if (headerLine.isNull()) {
- clear();
- return;
- }
- }
m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1());
- QString host = m_headers.value(QStringLiteral("host"), QString());
+ QString host = QString::fromLatin1(m_parser.firstHeaderField("host"));
if (m_requestUrl.isRelative()) {
// see https://tools.ietf.org/html/rfc6455#page-17
// No. 4 item in "The requirements for this handshake"
@@ -299,59 +277,45 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH
m_requestUrl.setScheme(scheme);
}
- const QStringList versionLines = m_headers.values(QStringLiteral("sec-websocket-version"));
- for (QStringList::const_iterator v = versionLines.begin(); v != versionLines.end(); ++v) {
- const QStringList versions = (*v).split(QStringLiteral(","), Qt::SkipEmptyParts);
- for (QStringList::const_iterator i = versions.begin(); i != versions.end(); ++i) {
+ const QList<QByteArray> versionLines = m_parser.headerFieldValues("sec-websocket-version");
+ for (auto v = versionLines.begin(); v != versionLines.end(); ++v) {
+ for (const auto &version :
+ QString::fromLatin1(*v).tokenize(QStringLiteral(","), Qt::SkipEmptyParts)) {
bool ok = false;
- (void)(*i).toUInt(&ok);
+ (void)version.toUInt(&ok);
if (!ok) {
clear();
return;
}
const QWebSocketProtocol::Version ver =
- QWebSocketProtocol::versionFromString((*i).trimmed());
+ QWebSocketProtocol::versionFromString(version.trimmed().toString());
m_versions << ver;
}
}
//sort in descending order
std::sort(m_versions.begin(), m_versions.end(), std::greater<QWebSocketProtocol::Version>());
- m_key = m_headers.value(QStringLiteral("sec-websocket-key"), QString());
- //must contain "Upgrade", case-insensitive
- const QString upgrade = m_headers.value(QStringLiteral("upgrade"), QString());
- //must be equal to "websocket", case-insensitive
- const QString connection = m_headers.value(QStringLiteral("connection"), QString());
- const QStringList connectionLine = connection.split(QStringLiteral(","), Qt::SkipEmptyParts);
+
+ m_key = QString::fromLatin1(m_parser.firstHeaderField("sec-websocket-key"));
+ const QByteArray upgrade = m_parser.firstHeaderField("upgrade");
QStringList connectionValues;
- for (QStringList::const_iterator c = connectionLine.begin(); c != connectionLine.end(); ++c)
- connectionValues << (*c).trimmed();
+ appendCommmaSeparatedLineToList(connectionValues, m_parser.firstHeaderField("connection"));
//optional headers
- m_origin = m_headers.value(QStringLiteral("origin"), QString());
- const QStringList protocolLines = m_headers.values(QStringLiteral("sec-websocket-protocol"));
- for (const QString& pl : protocolLines) {
- const QStringList protocols = pl.split(QStringLiteral(","), Qt::SkipEmptyParts);
- for (const QString& p : protocols)
- m_protocols << p.trimmed();
- }
+ m_origin = QString::fromLatin1(m_parser.firstHeaderField("origin"));
+ const QList<QByteArray> protocolLines = m_parser.headerFieldValues("sec-websocket-protocol");
+ for (const QByteArray &pl : protocolLines)
+ appendCommmaSeparatedLineToList(m_protocols, pl);
- const QStringList extensionLines = m_headers.values(QStringLiteral("sec-websocket-extensions"));
- for (const QString& el : extensionLines) {
- const QStringList extensions = el.split(QStringLiteral(","), Qt::SkipEmptyParts);
- for (const QString& e : extensions)
- m_extensions << e.trimmed();
- }
+ const QList<QByteArray> extensionLines = m_parser.headerFieldValues("sec-websocket-extensions");
+ for (const QByteArray &el : extensionLines)
+ appendCommmaSeparatedLineToList(m_extensions, el);
//TODO: authentication field
- m_isValid = !(m_requestUrl.host().isEmpty() ||
- resourceName.isEmpty() ||
- m_versions.isEmpty() ||
- m_key.isEmpty() ||
- (verb != QStringLiteral("GET")) ||
- (!conversionOk || (httpVersion < 1.1f)) ||
- (upgrade.toLower() != QStringLiteral("websocket")) ||
- (!connectionValues.contains(QStringLiteral("upgrade"), Qt::CaseInsensitive)));
+ m_isValid = !(m_requestUrl.host().isEmpty() || resourceName.isEmpty() || m_versions.isEmpty()
+ || m_key.isEmpty() || verb != QStringLiteral("GET") || !conversionOk
+ || httpVersion < 1.1f || upgrade.compare("websocket", Qt::CaseInsensitive) != 0
+ || !connectionValues.contains(QStringLiteral("upgrade"), Qt::CaseInsensitive));
if (Q_UNLIKELY(!m_isValid))
clear();
}
diff --git a/src/websockets/qwebsockethandshakerequest_p.h b/src/websockets/qwebsockethandshakerequest_p.h
index c5e9c44..d3681a9 100644
--- a/src/websockets/qwebsockethandshakerequest_p.h
+++ b/src/websockets/qwebsockethandshakerequest_p.h
@@ -54,6 +54,7 @@
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtCore/QUrl>
+#include <QtNetwork/private/qhttpheaderparser_p.h>
#include "qwebsocketprotocol.h"
@@ -74,7 +75,8 @@ public:
int port() const;
bool isSecure() const;
bool isValid() const;
- QMultiMap<QString, QString> headers() const;
+ QList<QPair<QByteArray, QByteArray>> headers() const;
+ bool hasHeader(const QByteArray &name) const;
QList<QWebSocketProtocol::Version> versions() const;
QString key() const;
QString origin() const;
@@ -84,14 +86,14 @@ public:
QString resourceName() const;
QString host() const;
- void readHandshake(QTextStream &textStream, int maxHeaderLineLength, int maxHeaders);
+ void readHandshake(QByteArrayView header, int maxHeaderLineLength);
private:
int m_port;
bool m_isSecure;
bool m_isValid;
- QMultiMap<QString, QString> m_headers;
+ QHttpHeaderParser m_parser;
QList<QWebSocketProtocol::Version> m_versions;
QString m_key;
QString m_origin;
diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp
index f922098..23b38aa 100644
--- a/src/websockets/qwebsocketserver_p.cpp
+++ b/src/websockets/qwebsocketserver_p.cpp
@@ -479,8 +479,7 @@ void QWebSocketServerPrivate::handshakeReceived()
}
QWebSocketHandshakeRequest request(pTcpSocket->peerPort(), isSecure);
- QTextStream textStream(header, QIODevice::ReadOnly);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERLINES);
+ request.readHandshake(header, MAX_HEADERLINE_LENGTH);
if (request.isValid()) {
QWebSocketCorsAuthenticator corsAuthenticator(request.origin());