diff options
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 |
commit | 8341a3f435cdbcdda25d67f504ca630d285faa63 (patch) | |
tree | 0ef4ea7911b36a0ea5112231d0e9fdfab4bb1dbf /src | |
parent | cb7abc7ac0f19e0076a3927b0dd880606c6f92f6 (diff) | |
download | qtwebsockets-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.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 |
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()); |