summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--tests/auto/websockets/handshakerequest/CMakeLists.txt2
-rw-r--r--tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp74
-rw-r--r--tests/auto/websockets/handshakeresponse/CMakeLists.txt2
-rw-r--r--tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp12
8 files changed, 108 insertions, 153 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());
diff --git a/tests/auto/websockets/handshakerequest/CMakeLists.txt b/tests/auto/websockets/handshakerequest/CMakeLists.txt
index 984f454..3714ef9 100644
--- a/tests/auto/websockets/handshakerequest/CMakeLists.txt
+++ b/tests/auto/websockets/handshakerequest/CMakeLists.txt
@@ -11,6 +11,8 @@ endif()
qt_internal_add_test(tst_handshakerequest
SOURCES
tst_handshakerequest.cpp
+ LIBRARIES
+ Qt::NetworkPrivate
PUBLIC_LIBRARIES
Qt::WebSocketsPrivate
)
diff --git a/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp b/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp
index 230b052..a91f9cb 100644
--- a/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp
+++ b/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp
@@ -213,12 +213,9 @@ void tst_HandshakeRequest::tst_invalidStream()
QFETCH(QString, dataStream);
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << dataStream;
QWebSocketHandshakeRequest request(80, true);
-
- textStream << dataStream;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QVERIFY(!request.isValid());
QCOMPARE(request.port(), 80);
@@ -249,12 +246,9 @@ void tst_HandshakeRequest::tst_multipleValuesInConnectionHeader()
QStringLiteral("Upgrade: websocket\r\n") +
QStringLiteral("Connection: Upgrade,keepalive\r\n\r\n");
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << header;
QWebSocketHandshakeRequest request(80, false);
-
- textStream << header;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QVERIFY(request.isValid());
QCOMPARE(request.port(), 80);
@@ -286,12 +280,9 @@ void tst_HandshakeRequest::tst_parsingWhitespaceInHeaders()
QStringLiteral("Upgrade:websocket \r\n") +
QStringLiteral("Connection: Upgrade,keepalive\r\n\r\n");
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << header;
QWebSocketHandshakeRequest request(80, false);
-
- textStream << header;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QVERIFY(request.isValid());
QCOMPARE(request.key(), QStringLiteral("AVD FBDDFF"));
@@ -307,12 +298,9 @@ void tst_HandshakeRequest::tst_multipleVersions()
QStringLiteral("Upgrade: websocket\r\n") +
QStringLiteral("Connection: Upgrade,keepalive\r\n\r\n");
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << header;
QWebSocketHandshakeRequest request(80, false);
-
- textStream << header;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QVERIFY(request.isValid());
QCOMPARE(request.port(), 80);
@@ -320,11 +308,11 @@ void tst_HandshakeRequest::tst_multipleVersions()
QCOMPARE(request.extensions().length(), 0);
QCOMPARE(request.protocols().length(), 0);
QCOMPARE(request.headers().size(), 5);
- QVERIFY(request.headers().contains(QStringLiteral("host")));
- QVERIFY(request.headers().contains(QStringLiteral("sec-websocket-version")));
- QVERIFY(request.headers().contains(QStringLiteral("sec-websocket-key")));
- QVERIFY(request.headers().contains(QStringLiteral("upgrade")));
- QVERIFY(request.headers().contains(QStringLiteral("connection")));
+ QVERIFY(request.hasHeader("host"));
+ QVERIFY(request.hasHeader("sec-websocket-version"));
+ QVERIFY(request.hasHeader("sec-websocket-key"));
+ QVERIFY(request.hasHeader("upgrade"));
+ QVERIFY(request.hasHeader("connection"));
QCOMPARE(request.key(), QStringLiteral("AVDFBDDFF"));
QCOMPARE(request.origin().length(), 0);
QCOMPARE(request.requestUrl(), QUrl("ws://foo.com/test"));
@@ -343,12 +331,9 @@ void tst_HandshakeRequest::tst_qtbug_39355()
QStringLiteral("Upgrade: websocket\r\n") +
QStringLiteral("Connection: Upgrade\r\n\r\n");
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << header;
QWebSocketHandshakeRequest request(8080, false);
-
- textStream << header;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QVERIFY(request.isValid());
QCOMPARE(request.port(), 1234);
@@ -366,12 +351,21 @@ void tst_HandshakeRequest::tst_qtbug_48123_data()
QStringLiteral("Connection: Upgrade\r\n");
const int numHeaderLines = header.count(QStringLiteral("\r\n")) - 1; //-1: exclude requestline
- //a headerline should not be larger than MAX_HEADERLINE_LENGTH characters (excluding CRLF)
+ // a headerline must contain colon
QString illegalHeader = header;
- illegalHeader.append(QString(MAX_HEADERLINE_LENGTH + 1, QLatin1Char('c')));
+ illegalHeader.append(QString(MAX_HEADERLINE_LENGTH, QLatin1Char('c')));
illegalHeader.append(QStringLiteral("\r\n\r\n"));
- QTest::newRow("headerline too long") << illegalHeader << false;
+ QTest::newRow("headerline missing colon") << illegalHeader << false;
+
+ // a headerline should not be larger than MAX_HEADERLINE_LENGTH characters (excluding CRLF)
+ QString tooLongHeader = header;
+ QString fieldName = "Too-long: ";
+ tooLongHeader.append(fieldName);
+ tooLongHeader.append(QString(MAX_HEADERLINE_LENGTH + 1 - fieldName.size(), QLatin1Char('c')));
+ tooLongHeader.append(QStringLiteral("\r\n\r\n"));
+
+ QTest::newRow("headerline too long") << tooLongHeader << false;
QString legalHeader = header;
const QString headerKey = QStringLiteral("X-CUSTOM-KEY: ");
@@ -408,12 +402,9 @@ void tst_HandshakeRequest::tst_qtbug_48123()
QFETCH(bool, shouldBeValid);
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << header;
QWebSocketHandshakeRequest request(8080, false);
-
- textStream << header;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QCOMPARE(request.isValid(), shouldBeValid);
}
@@ -504,12 +495,9 @@ void tst_HandshakeRequest::tst_qtbug_57357()
QFETCH(int, port);
QByteArray data;
- QTextStream textStream(&data);
+ QTextStream(&data) << header;
QWebSocketHandshakeRequest request(8080, false);
-
- textStream << header;
- textStream.seek(0);
- request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERS);
+ request.readHandshake(data, MAX_HEADERLINE_LENGTH);
QCOMPARE(request.isValid(), valid);
if (valid) {
diff --git a/tests/auto/websockets/handshakeresponse/CMakeLists.txt b/tests/auto/websockets/handshakeresponse/CMakeLists.txt
index 0eac850..1562519 100644
--- a/tests/auto/websockets/handshakeresponse/CMakeLists.txt
+++ b/tests/auto/websockets/handshakeresponse/CMakeLists.txt
@@ -11,6 +11,8 @@ endif()
qt_internal_add_test(tst_handshakeresponse
SOURCES
tst_handshakeresponse.cpp
+ LIBRARIES
+ Qt::NetworkPrivate
PUBLIC_LIBRARIES
Qt::WebSocketsPrivate
)
diff --git a/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp b/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp
index 9a7c590..436681b 100644
--- a/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp
+++ b/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp
@@ -81,13 +81,11 @@ void tst_HandshakeResponse::cleanup()
void tst_HandshakeResponse::tst_date_response()
{
QWebSocketHandshakeRequest request(80, false);
- QString buffer;
- QTextStream input(&buffer);
- input << QStringLiteral("GET / HTTP/1.1\r\nHost: example.com\r\nSec-WebSocket-Version: 13\r\n") +
- QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") +
- QStringLiteral("Upgrade: websocket\r\n") +
- QStringLiteral("Connection: Upgrade\r\n\r\n");
- request.readHandshake(input, 8 * 1024, 100);
+ QByteArray bytes = "GET / HTTP/1.1\r\nHost: example.com\r\nSec-WebSocket-Version: 13\r\n"
+ "Sec-WebSocket-Key: AVDFBDDFF\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n\r\n";
+ request.readHandshake(bytes, 8 * 1024);
QWebSocketHandshakeResponse response(request, "example.com", true,
QList<QWebSocketProtocol::Version>() << QWebSocketProtocol::Version13,