diff options
author | Øystein Heskestad <oystein.heskestad@qt.io> | 2021-08-03 17:17:41 +0200 |
---|---|---|
committer | Øystein Heskestad <oystein.heskestad@qt.io> | 2021-10-08 10:33:48 +0200 |
commit | bbd9f2f4f5e0fda85029fa320f793973ea607c2b (patch) | |
tree | bb84f8eb6ed473b48afd664f42413b0a33d3b5da /src/websockets/qwebsocket_p.cpp | |
parent | 86a321907ca827bbca239c14efa1ff7ad8364e9a (diff) | |
download | qtwebsockets-bbd9f2f4f5e0fda85029fa320f793973ea607c2b.tar.gz |
Reuse header http parsing code from QtNetwork
Fixes: QTBUG-80701
Change-Id: Ic430a7dbc4448fc6d5fc000129a8e08bbed7e77d
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/websockets/qwebsocket_p.cpp')
-rw-r--r-- | src/websockets/qwebsocket_p.cpp | 276 |
1 files changed, 103 insertions, 173 deletions
diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp index 5d5b15f..94ea60e 100644 --- a/src/websockets/qwebsocket_p.cpp +++ b/src/websockets/qwebsocket_p.cpp @@ -63,14 +63,22 @@ #include <QtNetwork/QSslPreSharedKeyAuthenticator> #endif +#include <QtNetwork/private/qhttpheaderparser_p.h> + #include <QtCore/QDebug> #include <limits> QT_BEGIN_NAMESPACE -const quint64 MAX_OUTGOING_FRAME_SIZE_IN_BYTES = std::numeric_limits<int>::max() - 1; -const quint64 DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //default size of a frame when sending a message +namespace { + +constexpr int MAX_HEADERLINE_LENGTH = 8 * 1024; // maximum length of a http request header line +constexpr int MAX_HEADERLINES = 100; // maximum number of http request header lines +constexpr quint64 MAX_OUTGOING_FRAME_SIZE_IN_BYTES = std::numeric_limits<int>::max() - 1; +constexpr quint64 DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES = 512 * 512 * 2; // default size of a frame when sending a message + +} QWebSocketConfiguration::QWebSocketConfiguration() : #ifndef QT_NO_SSL @@ -111,7 +119,6 @@ QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol:: m_configuration(), m_pMaskGenerator(&m_defaultMaskGenerator), m_defaultMaskGenerator(), - m_handshakeState(NothingDoneState), m_outgoingFrameSize(DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES) { m_pingTimer.start(); @@ -143,7 +150,6 @@ QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol: m_configuration(), m_pMaskGenerator(&m_defaultMaskGenerator), m_defaultMaskGenerator(), - m_handshakeState(NothingDoneState), m_outgoingFrameSize(DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES) { m_pingTimer.start(); @@ -918,203 +924,127 @@ qint64 QWebSocketPrivate::writeFrame(const QByteArray &frame) return written; } +//called on the client for a server handshake response /*! \internal */ -static QString readLine(QTcpSocket *pSocket) +void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) { - Q_ASSERT(pSocket); - QString line; - char c; - while (pSocket->getChar(&c)) { - if (c == char('\r')) { - pSocket->getChar(&c); - break; - } else { - line.append(QChar::fromLatin1(c)); + Q_Q(QWebSocket); + if (Q_UNLIKELY(!pSocket)) + return; + + static const QByteArray endOfHeaderMarker = QByteArrayLiteral("\r\n\r\n"); + const qint64 byteAvailable = pSocket->bytesAvailable(); + QByteArray available = pSocket->peek(byteAvailable); + const int endOfHeaderIndex = available.indexOf(endOfHeaderMarker); + if (endOfHeaderIndex < 0) { + //then we don't have our header complete yet + //check that no one is trying to exhaust our virtual memory + const qint64 maxHeaderLength = MAX_HEADERLINE_LENGTH * MAX_HEADERLINES + endOfHeaderMarker.size(); + if (Q_UNLIKELY(byteAvailable > maxHeaderLength)) { + setErrorString(QWebSocket::tr("Header is too large")); + Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); } + return; } - return line; -} - -// this function is a copy of QHttpNetworkReplyPrivate::parseStatus -static bool parseStatusLine(const QByteArray &status, int *majorVersion, int *minorVersion, - int *statusCode, QString *reasonPhrase) -{ - // 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; + const int headerSize = endOfHeaderIndex + endOfHeaderMarker.size(); + //don't read past the header + QByteArrayView headers = QByteArrayView(available).first(headerSize); + //remove our header from the tcpSocket + qint64 skippedSize = pSocket->skip(headerSize); + + if (Q_UNLIKELY(skippedSize != headerSize)) { + setErrorString(QWebSocket::tr("Read handshake request header failed")); + Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); + return; } - // 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; -} + QHttpHeaderParser parser; + static const QByteArray endOfStatusMarker = QByteArrayLiteral("\r\n"); + const int endOfStatusIndex = headers.indexOf(endOfStatusMarker); + const QByteArrayView status = headers.first(endOfStatusIndex); + if (!parser.parseStatus(status)) { + setErrorString(QWebSocket::tr("Read handshake request status failed")); + Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); + return; + } -//called on the client for a server handshake response -/*! - \internal - */ -void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) -{ - Q_Q(QWebSocket); - if (Q_UNLIKELY(!pSocket)) + if (!parser.parseHeaders(headers.sliced(endOfStatusIndex + endOfStatusMarker.size()))) { + setErrorString(QWebSocket::tr("Parsing handshake request header failed")); + Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); return; - // Reset handshake on a new connection. - if (m_handshakeState == AllDoneState) - m_handshakeState = NothingDoneState; + } + const QString acceptKey = QString::fromLatin1(parser.combinedHeaderValue( + QByteArrayLiteral("sec-websocket-accept"))); + const QString upgrade = QString::fromLatin1(parser.combinedHeaderValue( + QByteArrayLiteral("upgrade"))); + const QString connection = QString::fromLatin1(parser.combinedHeaderValue( + QByteArrayLiteral("connection"))); +#if 0 // unused for the moment + const QString extensions = QString::fromLatin1(parser.combinedHeaderValue( + QByteArrayLiteral("sec-websocket-extensions")); + const QString protocol = QString::fromLatin1(parser.combinedHeaderValue( + QByteArrayLiteral("sec-websocket-protocol")); +#endif + const QString version = QString::fromLatin1(parser.combinedHeaderValue( + QByteArrayLiteral("sec-websocket-version"))); + bool ok = false; QString errorDescription; - - switch (m_handshakeState) { - case NothingDoneState: - m_headers.clear(); - m_handshakeState = ReadingStatusState; - Q_FALLTHROUGH(); - case ReadingStatusState: - if (!pSocket->canReadLine()) - return; - m_statusLine = pSocket->readLine().trimmed(); - if (Q_UNLIKELY(!parseStatusLine(m_statusLine, &m_httpMajorVersion, &m_httpMinorVersion, &m_httpStatusCode, &m_httpStatusMessage))) { - errorDescription = QWebSocket::tr("Invalid statusline in response: %1.").arg(QString::fromLatin1(m_statusLine)); - break; - } - m_handshakeState = ReadingHeaderState; - Q_FALLTHROUGH(); - case ReadingHeaderState: { - // TODO: this should really use the existing code from QHttpNetworkReplyPrivate::parseHeader - auto lastHeader = m_headers.end(); - while (pSocket->canReadLine()) { - QString headerLine = readLine(pSocket); - - if (headerLine.isEmpty()) { - // end of headers - m_handshakeState = ParsingHeaderState; - break; - } else if (headerLine.startsWith(QLatin1Char(' ')) || headerLine.startsWith(QLatin1Char('\t'))) { - // continuation line -- add this to the last header field - if (Q_UNLIKELY(lastHeader == m_headers.end())) { - errorDescription = QWebSocket::tr("Malformed header in response: %1.").arg(headerLine); - break; - } - lastHeader.value().append(QLatin1Char(' ')); - lastHeader.value().append(headerLine.trimmed()); - } else { - int colonPos = headerLine.indexOf(QLatin1Char(':')); - if (Q_UNLIKELY(colonPos <= 0)) { - errorDescription = QWebSocket::tr("Malformed header in response: %1.").arg(headerLine); - break; - } - lastHeader = m_headers.insert(headerLine.left(colonPos).trimmed().toLower(), - headerLine.mid(colonPos + 1).trimmed()); - } - } - - if (m_handshakeState != ParsingHeaderState) { - if (pSocket->state() != QAbstractSocket::ConnectedState) { - errorDescription = QWebSocket::tr("QWebSocketPrivate::processHandshake: Connection closed while reading header."); - break; + if (Q_LIKELY(parser.getStatusCode() == 101)) { + //HTTP/x.y 101 Switching Protocols + //TODO: do not check the httpStatusText right now + ok = (acceptKey.size() > 0 + && parser.getMajorVersion() > 0 && parser.getMinorVersion() > 0 + && upgrade.compare(u"websocket", Qt::CaseInsensitive) == 0 + && connection.compare(u"upgrade", Qt::CaseInsensitive) == 0); + + if (ok) { + const QString accept = calculateAcceptKey(m_key); + if (accept != acceptKey) { + ok = false; + errorDescription = QWebSocket::tr( + "Accept-Key received from server %1 does not match the client key %2.") + .arg(acceptKey, accept); } - return; + } else { + errorDescription = QWebSocket::tr( + "QWebSocketPrivate::processHandshake: Invalid status line in response: %1.") + .arg(QString::fromLatin1(m_statusLine)); } - Q_FALLTHROUGH(); - } - case ParsingHeaderState: { - const QString acceptKey = m_headers.value(QStringLiteral("sec-websocket-accept"), QString()); - const QString upgrade = m_headers.value(QStringLiteral("upgrade"), QString()); - const QString connection = m_headers.value(QStringLiteral("connection"), QString()); -// unused for the moment -// const QString extensions = m_headers.value(QStringLiteral("sec-websocket-extensions"), -// QString()); -// const QString protocol = m_headers.value(QStringLiteral("sec-websocket-protocol"), -// QString()); - const QString version = m_headers.value(QStringLiteral("sec-websocket-version"), QString()); - - bool ok = false; - if (Q_LIKELY(m_httpStatusCode == 101)) { - //HTTP/x.y 101 Switching Protocols - //TODO: do not check the httpStatusText right now - ok = !(acceptKey.isEmpty() || - (m_httpMajorVersion < 1 || m_httpMinorVersion < 1) || - (upgrade.toLower() != QStringLiteral("websocket")) || - (connection.toLower() != QStringLiteral("upgrade"))); - if (ok) { - const QString accept = calculateAcceptKey(m_key); - ok = (accept == acceptKey); - if (!ok) - errorDescription = - QWebSocket::tr("Accept-Key received from server %1 does not match the client key %2.") - .arg(acceptKey, accept); - } else { + } else if (parser.getStatusCode() == 400) { + //HTTP/1.1 400 Bad Request + if (!version.isEmpty()) { + const QStringList versions = version.split(QStringLiteral(", "), Qt::SkipEmptyParts); + if (!versions.contains(QString::number(QWebSocketProtocol::currentVersion()))) { + //if needed to switch protocol version, then we are finished here + //because we cannot handle other protocols than the RFC one (v13) errorDescription = - QWebSocket::tr("QWebSocketPrivate::processHandshake: Invalid statusline in response: %1.") - .arg(QString::fromLatin1(m_statusLine)); - } - } else if (m_httpStatusCode == 400) { - //HTTP/1.1 400 Bad Request - if (!version.isEmpty()) { - const QStringList versions = version.split(QStringLiteral(", "), Qt::SkipEmptyParts); - if (!versions.contains(QString::number(QWebSocketProtocol::currentVersion()))) { - //if needed to switch protocol version, then we are finished here - //because we cannot handle other protocols than the RFC one (v13) - errorDescription = - QWebSocket::tr("Handshake: Server requests a version that we don't support: %1.") - .arg(versions.join(QStringLiteral(", "))); - } else { - //we tried v13, but something different went wrong - errorDescription = - QWebSocket::tr("QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection."); - } + QWebSocket::tr("Handshake: Server requests a version that we don't support: %1.") + .arg(versions.join(QStringLiteral(", "))); } else { - errorDescription = - QWebSocket::tr("QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection."); + //we tried v13, but something different went wrong + errorDescription = + QWebSocket::tr("QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection."); } } else { errorDescription = - QWebSocket::tr("QWebSocketPrivate::processHandshake: Unhandled http status code: %1 (%2).") - .arg(m_httpStatusCode).arg(m_httpStatusMessage); + QWebSocket::tr("QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection."); } - if (ok) - m_handshakeState = AllDoneState; - break; - } - case AllDoneState: - Q_UNREACHABLE(); - break; + } else { + errorDescription = + QWebSocket::tr("QWebSocketPrivate::processHandshake: Unhandled http status code: %1 (%2).") + .arg(m_httpStatusCode).arg(m_httpStatusMessage); } - if (m_handshakeState == AllDoneState) { + if (ok) { // handshake succeeded setSocketState(QAbstractSocket::ConnectedState); Q_EMIT q->connected(); } else { // handshake failed - m_handshakeState = AllDoneState; setErrorString(errorDescription); Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); } |