diff options
-rw-r--r-- | src/websockets/qwebsocket_p.cpp | 2 | ||||
-rw-r--r-- | src/websockets/qwebsockethandshakerequest.cpp | 158 | ||||
-rw-r--r-- | src/websockets/qwebsockethandshakerequest_p.h | 8 | ||||
-rw-r--r-- | src/websockets/qwebsocketserver_p.cpp | 3 | ||||
-rw-r--r-- | tests/auto/websockets/handshakerequest/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp | 74 | ||||
-rw-r--r-- | tests/auto/websockets/handshakeresponse/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp | 12 |
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, |