summaryrefslogtreecommitdiff
path: root/src/websockets/qwebsocket_p.cpp
diff options
context:
space:
mode:
authorArno Rehn <a.rehn@menlosystems.com>2022-01-02 22:31:03 +0100
committerArno Rehn <a.rehn@menlosystems.com>2022-03-30 17:45:18 +0200
commitcc4c24b99a87629aeb60df5af96d9bb991b56635 (patch)
tree62459a42457b0680022bb1fcb759f9c4e64d2c4b /src/websockets/qwebsocket_p.cpp
parent8545bb57efbfabf0dc7bc4b76efd6a99b4022669 (diff)
downloadqtwebsockets-cc4c24b99a87629aeb60df5af96d9bb991b56635.tar.gz
Add support for WebSocket Sub-Protocols
Sub-Protocol support follows RFC 6455 Sections 4.1 and 4.2. See also https://datatracker.ietf.org/doc/html/rfc6455. This patch introduces a new class QWebSocketHandshakeOptions which collects options for the WebSocket handshake. At the moment, this contains only accessors for sub-protocols. In the future, it can be extended with things like WebSocket extensions. [ChangeLog] Add support for WebSocket Sub-Protocols Fixes: QTBUG-38742 Change-Id: Ibdcef17f717f0a76caab54f65c550865df1ec78d Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/websockets/qwebsocket_p.cpp')
-rw-r--r--src/websockets/qwebsocket_p.cpp100
1 files changed, 85 insertions, 15 deletions
diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp
index b0c3e4e..2cb5419 100644
--- a/src/websockets/qwebsocket_p.cpp
+++ b/src/websockets/qwebsocket_p.cpp
@@ -78,6 +78,40 @@ constexpr int MAX_HEADERLINES = 100; // maximum number of http reques
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
+// Based on isSeperator() from qtbase/src/network/access/qhsts.cpp
+// https://datatracker.ietf.org/doc/html/rfc2616#section-2.2:
+//
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+// TODO: Should probably make things like this re-usable as private API of QtNetwork
+bool isSeparator(char c)
+{
+ // separators = "(" | ")" | "<" | ">" | "@"
+ // | "," | ";" | ":" | "\" | <">
+ // | "/" | "[" | "]" | "?" | "="
+ // | "{" | "}" | SP | HT
+ static const char separators[] = "()<>@,;:\\\"/[]?={} \t";
+ static const char *end = separators + sizeof separators - 1;
+ return std::find(separators, end, c) != end;
+}
+
+// https://datatracker.ietf.org/doc/html/rfc6455#section-4.1:
+// 10. The request MAY include a header field with the name
+// |Sec-WebSocket-Protocol|. If present, this value indicates one
+// or more comma-separated subprotocol the client wishes to speak,
+// ordered by preference. The elements that comprise this value
+// MUST be non-empty strings with characters in the range U+0021 to
+// U+007E not including separator characters as defined in
+// [RFC2616] and MUST all be unique strings.
+bool isValidSubProtocolName(const QString &protocol)
+{
+ return std::all_of(protocol.begin(), protocol.end(), [](const QChar &c) {
+ return c.unicode() >= 0x21 && c.unicode() <= 0x7E && !isSeparator(c.toLatin1());
+ });
+}
+
}
QWebSocketConfiguration::QWebSocketConfiguration() :
@@ -339,9 +373,12 @@ QWebSocket *QWebSocketPrivate::upgradeFrom(QTcpSocket *pTcpSocket,
if (QSslSocket *sslSock = qobject_cast<QSslSocket *>(pTcpSocket))
pWebSocket->setSslConfiguration(sslSock->sslConfiguration());
#endif
+ QWebSocketHandshakeOptions options;
+ options.setSubprotocols(request.protocols());
+
pWebSocket->d_func()->setExtension(response.acceptedExtension());
pWebSocket->d_func()->setOrigin(request.origin());
- pWebSocket->d_func()->setRequest(netRequest);
+ pWebSocket->d_func()->setRequest(netRequest, options);
pWebSocket->d_func()->setProtocol(response.acceptedProtocol());
pWebSocket->d_func()->setResourceName(request.requestUrl().toString(QUrl::RemoveUserInfo));
//a server should not send masked frames
@@ -394,7 +431,8 @@ void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString r
/*!
\internal
*/
-void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask)
+void QWebSocketPrivate::open(const QNetworkRequest &request,
+ const QWebSocketHandshakeOptions &options, bool mask)
{
//just delete the old socket for the moment;
//later, we can add more 'intelligent' handling by looking at the URL
@@ -417,7 +455,7 @@ void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask)
m_isClosingHandshakeReceived = false;
m_isClosingHandshakeSent = false;
- setRequest(request);
+ setRequest(request, options);
QString resourceName = url.path(QUrl::FullyEncoded);
// Check for encoded \r\n
if (resourceName.contains(QStringLiteral("%0D%0A"))) {
@@ -553,10 +591,13 @@ void QWebSocketPrivate::setResourceName(const QString &resourceName)
/*!
\internal
*/
-void QWebSocketPrivate::setRequest(const QNetworkRequest &request)
+void QWebSocketPrivate::setRequest(const QNetworkRequest &request,
+ const QWebSocketHandshakeOptions &options)
{
if (m_request != request)
m_request = request;
+ if (m_options != options)
+ m_options = options;
}
/*!
@@ -721,6 +762,14 @@ QString QWebSocketPrivate::origin() const
/*!
\internal
*/
+QWebSocketHandshakeOptions QWebSocketPrivate::handshakeOptions() const
+{
+ return m_options;
+}
+
+/*!
+ \internal
+ */
QString QWebSocketPrivate::protocol() const
{
return m_protocol;
@@ -988,9 +1037,18 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket)
#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 protocol = QString::fromLatin1(parser.combinedHeaderValue(
+ QByteArrayLiteral("sec-websocket-protocol")));
+
+ if (!protocol.isEmpty() && !handshakeOptions().subprotocols().contains(protocol)) {
+ setErrorString(QWebSocket::tr("WebSocket server has chosen protocol %1 which has not been "
+ "requested")
+ .arg(protocol));
+ Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError);
+ return;
+ }
+
const QString version = QString::fromLatin1(parser.combinedHeaderValue(
QByteArrayLiteral("sec-websocket-version")));
bool ok = false;
@@ -1043,6 +1101,7 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket)
if (ok) {
// handshake succeeded
+ setProtocol(protocol);
setSocketState(QAbstractSocket::ConnectedState);
Q_EMIT q->connected();
} else {
@@ -1084,7 +1143,7 @@ void QWebSocketPrivate::processStateChanged(QAbstractSocket::SocketState socketS
host,
origin(),
QString(),
- QString(),
+ m_options.subprotocols(),
m_key,
headers);
if (handshake.isEmpty()) {
@@ -1189,7 +1248,7 @@ QString QWebSocketPrivate::createHandShakeRequest(QString resourceName,
QString host,
QString origin,
QString extensions,
- QString protocols,
+ const QStringList &protocols,
QByteArray key,
const QList<QPair<QString, QString> > &headers)
{
@@ -1214,11 +1273,6 @@ QString QWebSocketPrivate::createHandShakeRequest(QString resourceName,
"Possible attack detected."));
return QString();
}
- if (protocols.contains(QStringLiteral("\r\n"))) {
- setErrorString(QWebSocket::tr("The protocols attribute contains newlines. " \
- "Possible attack detected."));
- return QString();
- }
handshakeRequest << QStringLiteral("GET ") % resourceName % QStringLiteral(" HTTP/1.1") <<
QStringLiteral("Host: ") % host <<
@@ -1231,8 +1285,24 @@ QString QWebSocketPrivate::createHandShakeRequest(QString resourceName,
% QString::number(QWebSocketProtocol::currentVersion());
if (extensions.length() > 0)
handshakeRequest << QStringLiteral("Sec-WebSocket-Extensions: ") % extensions;
- if (protocols.length() > 0)
- handshakeRequest << QStringLiteral("Sec-WebSocket-Protocol: ") % protocols;
+
+ const QStringList validProtocols = [&] {
+ QStringList validProtocols;
+ validProtocols.reserve(protocols.size());
+ for (const auto &p : protocols) {
+ if (isValidSubProtocolName(p))
+ validProtocols.append(p);
+ else
+ qWarning() << "Ignoring invalid WebSocket subprotocol name" << p;
+ }
+
+ return validProtocols;
+ }();
+
+ if (!protocols.isEmpty()) {
+ handshakeRequest << QStringLiteral("Sec-WebSocket-Protocol: ")
+ % validProtocols.join(QLatin1String(", "));
+ }
for (const auto &header : headers)
handshakeRequest << header.first % QStringLiteral(": ") % header.second;