summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/imports/qmlwebsockets/qqmlwebsocket.cpp43
-rw-r--r--src/imports/qmlwebsockets/qqmlwebsocket.h12
-rw-r--r--src/imports/qmlwebsockets/qqmlwebsocketserver.cpp25
-rw-r--r--src/imports/qmlwebsockets/qqmlwebsocketserver.h7
-rw-r--r--src/websockets/CMakeLists.txt1
-rw-r--r--src/websockets/qwebsocket.cpp63
-rw-r--r--src/websockets/qwebsocket.h8
-rw-r--r--src/websockets/qwebsocket_p.cpp100
-rw-r--r--src/websockets/qwebsocket_p.h9
-rw-r--r--src/websockets/qwebsocket_wasm_p.cpp16
-rw-r--r--src/websockets/qwebsockethandshakeoptions.cpp147
-rw-r--r--src/websockets/qwebsockethandshakeoptions.h83
-rw-r--r--src/websockets/qwebsockethandshakeoptions_p.h71
-rw-r--r--src/websockets/qwebsockethandshakeresponse.cpp22
-rw-r--r--src/websockets/qwebsocketserver.cpp24
-rw-r--r--src/websockets/qwebsocketserver.h3
-rw-r--r--src/websockets/qwebsocketserver_p.cpp15
-rw-r--r--src/websockets/qwebsocketserver_p.h4
18 files changed, 611 insertions, 42 deletions
diff --git a/src/imports/qmlwebsockets/qqmlwebsocket.cpp b/src/imports/qmlwebsockets/qqmlwebsocket.cpp
index a7bb0e0..a96b2c0 100644
--- a/src/imports/qmlwebsockets/qqmlwebsocket.cpp
+++ b/src/imports/qmlwebsockets/qqmlwebsocket.cpp
@@ -58,6 +58,18 @@
*/
/*!
+ \qmlproperty QStringList WebSocket::requestedSubprotocols
+ \since 6.4
+ The list of WebSocket subprotocols to send in the WebSocket handshake.
+*/
+
+/*!
+ \qmlproperty QString WebSocket::negotiatedSubprotocol
+ \since 6.4
+ The WebSocket subprotocol that has been negotiated with the server.
+*/
+
+/*!
\qmlproperty Status WebSocket::status
Status of the WebSocket.
@@ -118,6 +130,7 @@
#include "qqmlwebsocket.h"
#include <QtWebSockets/QWebSocket>
+#include <QtWebSockets/QWebSocketHandshakeOptions>
QT_BEGIN_NAMESPACE
@@ -136,6 +149,7 @@ QQmlWebSocket::QQmlWebSocket(QWebSocket *socket, QObject *parent) :
QObject(parent),
m_status(Closed),
m_url(socket->requestUrl()),
+ m_requestedProtocols(socket->handshakeOptions().subprotocols()),
m_isActive(true),
m_componentCompleted(true),
m_errorString(socket->errorString())
@@ -168,6 +182,20 @@ qint64 QQmlWebSocket::sendBinaryMessage(const QByteArray &message)
return m_webSocket->sendBinaryMessage(message);
}
+QStringList QQmlWebSocket::requestedSubprotocols() const
+{
+ return m_requestedProtocols;
+}
+
+void QQmlWebSocket::setRequestedSubprotocols(const QStringList &requestedSubprotocols)
+{
+ if (m_requestedProtocols == requestedSubprotocols)
+ return;
+
+ m_requestedProtocols = requestedSubprotocols;
+ emit requestedSubprotocolsChanged();
+}
+
QUrl QQmlWebSocket::url() const
{
return m_url;
@@ -186,6 +214,11 @@ void QQmlWebSocket::setUrl(const QUrl &url)
open();
}
+QString QQmlWebSocket::negotiatedSubprotocol() const
+{
+ return m_negotiatedProtocol;
+}
+
QQmlWebSocket::Status QQmlWebSocket::status() const
{
return m_status;
@@ -281,6 +314,12 @@ void QQmlWebSocket::setStatus(QQmlWebSocket::Status status)
setErrorString();
}
Q_EMIT statusChanged(m_status);
+
+ const auto protocol = m_status == Open && m_webSocket ? m_webSocket->subprotocol() : QString();
+ if (m_negotiatedProtocol != protocol) {
+ m_negotiatedProtocol = protocol;
+ Q_EMIT negotiatedSubprotocolChanged();
+ }
}
void QQmlWebSocket::setActive(bool active)
@@ -308,7 +347,9 @@ bool QQmlWebSocket::isActive() const
void QQmlWebSocket::open()
{
if (m_componentCompleted && m_isActive && m_url.isValid() && Q_LIKELY(m_webSocket)) {
- m_webSocket->open(m_url);
+ QWebSocketHandshakeOptions opt;
+ opt.setSubprotocols(m_requestedProtocols);
+ m_webSocket->open(m_url, opt);
}
}
diff --git a/src/imports/qmlwebsockets/qqmlwebsocket.h b/src/imports/qmlwebsockets/qqmlwebsocket.h
index 7662607..1cd28c4 100644
--- a/src/imports/qmlwebsockets/qqmlwebsocket.h
+++ b/src/imports/qmlwebsockets/qqmlwebsocket.h
@@ -55,9 +55,13 @@ class QQmlWebSocket : public QObject, public QQmlParserStatus
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(QStringList requestedSubprotocols READ requestedSubprotocols
+ WRITE setRequestedSubprotocols NOTIFY requestedSubprotocolsChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
+ Q_PROPERTY(QString negotiatedSubprotocol READ negotiatedSubprotocol
+ NOTIFY negotiatedSubprotocolChanged)
public:
explicit QQmlWebSocket(QObject *parent = 0);
@@ -76,6 +80,10 @@ public:
QUrl url() const;
void setUrl(const QUrl &url);
+ QStringList requestedSubprotocols() const;
+ void setRequestedSubprotocols(const QStringList &subprotocols);
+
+ QString negotiatedSubprotocol() const;
Status status() const;
QString errorString() const;
@@ -92,6 +100,8 @@ Q_SIGNALS:
void activeChanged(bool isActive);
void errorStringChanged(QString errorString);
void urlChanged();
+ void requestedSubprotocolsChanged();
+ void negotiatedSubprotocolChanged();
public:
void classBegin() override;
@@ -103,8 +113,10 @@ private Q_SLOTS:
private:
QScopedPointer<QWebSocket> m_webSocket;
+ QString m_negotiatedProtocol;
Status m_status;
QUrl m_url;
+ QStringList m_requestedProtocols;
bool m_isActive;
bool m_componentCompleted;
QString m_errorString;
diff --git a/src/imports/qmlwebsockets/qqmlwebsocketserver.cpp b/src/imports/qmlwebsockets/qqmlwebsocketserver.cpp
index 4ff304c..a59ca94 100644
--- a/src/imports/qmlwebsockets/qqmlwebsocketserver.cpp
+++ b/src/imports/qmlwebsockets/qqmlwebsocketserver.cpp
@@ -76,6 +76,12 @@ QT_USE_NAMESPACE
*/
/*!
+ \qmlproperty QStringList WebSocketServer::supportedSubprotocols
+ \since 6.4
+ The list of protocols supported by the server.
+*/
+
+/*!
\qmlproperty QString WebSocketServer::errorString
The stringified error message in case an error occurred.
*/
@@ -200,6 +206,19 @@ void QQmlWebSocketServer::setName(const QString &name)
}
}
+void QQmlWebSocketServer::setSupportedSubprotocols(const QStringList &supportedSubprotocols)
+{
+ if (m_supportedSubprotocols == supportedSubprotocols)
+ return;
+
+ m_supportedSubprotocols = supportedSubprotocols;
+
+ if (m_server)
+ m_server->setSupportedSubprotocols(m_supportedSubprotocols);
+
+ emit supportedSubprotocolsChanged(m_supportedSubprotocols);
+}
+
bool QQmlWebSocketServer::listen() const
{
return m_listen;
@@ -252,6 +271,8 @@ void QQmlWebSocketServer::init()
connect(m_server.data(), &QWebSocketServer::closed,
this, &QQmlWebSocketServer::closed);
+ m_server->setSupportedSubprotocols(m_supportedSubprotocols);
+
updateListening();
}
@@ -287,3 +308,7 @@ void QQmlWebSocketServer::closed()
setListen(false);
}
+QStringList QQmlWebSocketServer::supportedSubprotocols() const
+{
+ return m_supportedSubprotocols;
+}
diff --git a/src/imports/qmlwebsockets/qqmlwebsocketserver.h b/src/imports/qmlwebsockets/qqmlwebsocketserver.h
index 98dbfc4..d0f6a63 100644
--- a/src/imports/qmlwebsockets/qqmlwebsocketserver.h
+++ b/src/imports/qmlwebsockets/qqmlwebsocketserver.h
@@ -57,6 +57,8 @@ class QQmlWebSocketServer : public QObject, public QQmlParserStatus
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+ Q_PROPERTY(QStringList supportedSubprotocols READ supportedSubprotocols
+ WRITE setSupportedSubprotocols NOTIFY supportedSubprotocolsChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
Q_PROPERTY(bool listen READ listen WRITE setListen NOTIFY listenChanged)
Q_PROPERTY(bool accept READ accept WRITE setAccept NOTIFY acceptChanged)
@@ -79,6 +81,9 @@ public:
QString name() const;
void setName(const QString &name);
+ QStringList supportedSubprotocols() const;
+ void setSupportedSubprotocols(const QStringList &supportedSubprotocols);
+
QString errorString() const;
bool listen() const;
@@ -94,6 +99,7 @@ Q_SIGNALS:
void urlChanged(const QUrl &url);
void portChanged(int port);
void nameChanged(const QString &name);
+ void supportedSubprotocolsChanged(const QStringList &supportedProtocols);
void hostChanged(const QString &host);
void listenChanged(bool listen);
void acceptChanged(bool accept);
@@ -108,6 +114,7 @@ private:
QScopedPointer<QWebSocketServer> m_server;
QString m_host;
QString m_name;
+ QStringList m_supportedSubprotocols;
int m_port;
bool m_listen;
bool m_accept;
diff --git a/src/websockets/CMakeLists.txt b/src/websockets/CMakeLists.txt
index e03ba54..9dc96cd 100644
--- a/src/websockets/CMakeLists.txt
+++ b/src/websockets/CMakeLists.txt
@@ -12,6 +12,7 @@ qt_internal_add_module(WebSockets
qwebsocketcorsauthenticator.cpp qwebsocketcorsauthenticator.h qwebsocketcorsauthenticator_p.h
qwebsocketdataprocessor.cpp qwebsocketdataprocessor_p.h
qwebsocketframe.cpp qwebsocketframe_p.h
+ qwebsockethandshakeoptions.cpp qwebsockethandshakeoptions.h qwebsockethandshakeoptions_p.h
qwebsockethandshakerequest.cpp qwebsockethandshakerequest_p.h
qwebsockethandshakeresponse.cpp qwebsockethandshakeresponse_p.h
qwebsocketprotocol.cpp qwebsocketprotocol.h qwebsocketprotocol_p.h
diff --git a/src/websockets/qwebsocket.cpp b/src/websockets/qwebsocket.cpp
index 63122d1..956c9dc 100644
--- a/src/websockets/qwebsocket.cpp
+++ b/src/websockets/qwebsocket.cpp
@@ -52,9 +52,7 @@
This class was modeled after QAbstractSocket.
- QWebSocket currently does not support
- \l {WebSocket Extensions} and
- \l {WebSocket Subprotocols}.
+ QWebSocket currently does not support \l {WebSocket Extensions}.
QWebSocket only supports version 13 of the WebSocket protocol, as outlined in
\l {RFC 6455}.
@@ -332,6 +330,7 @@ not been filled in with new information when the signal returns.
*/
#include "qwebsocket.h"
#include "qwebsocket_p.h"
+#include "qwebsockethandshakeoptions.h"
#include <QtCore/QUrl>
#include <QtNetwork/QTcpSocket>
@@ -484,7 +483,7 @@ void QWebSocket::open(const QUrl &url)
{
Q_D(QWebSocket);
QNetworkRequest request(url);
- d->open(request, true);
+ d->open(request, QWebSocketHandshakeOptions{}, true);
}
/*!
@@ -498,7 +497,41 @@ void QWebSocket::open(const QUrl &url)
void QWebSocket::open(const QNetworkRequest &request)
{
Q_D(QWebSocket);
- d->open(request, true);
+ d->open(request, QWebSocketHandshakeOptions{}, true);
+}
+
+/*!
+ \brief Opens a WebSocket connection using the given \a url and \a options.
+ \since 6.4
+
+ If the url contains newline characters (\\r\\n), then the error signal will be emitted
+ with QAbstractSocket::ConnectionRefusedError as error type.
+
+ Additional options for the WebSocket handshake such as subprotocols can be specified in
+ \a options.
+ */
+void QWebSocket::open(const QUrl &url, const QWebSocketHandshakeOptions &options)
+{
+ Q_D(QWebSocket);
+ QNetworkRequest request(url);
+ d->open(request, options, true);
+}
+
+/*!
+ \brief Opens a WebSocket connection using the given \a request and \a options.
+ \since 6.4
+
+ The \a request url will be used to open the WebSocket connection.
+ Headers present in the request will be sent to the server in the upgrade request,
+ together with the ones needed for the websocket handshake.
+
+ Additional options for the WebSocket handshake such as subprotocols can be specified in
+ \a options.
+ */
+void QWebSocket::open(const QNetworkRequest &request, const QWebSocketHandshakeOptions &options)
+{
+ Q_D(QWebSocket);
+ d->open(request, options, true);
}
/*!
@@ -654,6 +687,16 @@ QNetworkRequest QWebSocket::request() const
}
/*!
+ \brief Returns the handshake options that were used to open this socket.
+ \since 6.4
+ */
+QWebSocketHandshakeOptions QWebSocket::handshakeOptions() const
+{
+ Q_D(const QWebSocket);
+ return d->handshakeOptions();
+}
+
+/*!
\brief Returns the current origin.
*/
QString QWebSocket::origin() const
@@ -663,6 +706,16 @@ QString QWebSocket::origin() const
}
/*!
+ \brief Returns the used WebSocket protocol.
+ \since 6.4
+ */
+QString QWebSocket::subprotocol() const
+{
+ Q_D(const QWebSocket);
+ return d->protocol();
+}
+
+/*!
\brief Returns the code indicating why the socket was closed.
\sa QWebSocketProtocol::CloseCode, closeReason()
*/
diff --git a/src/websockets/qwebsocket.h b/src/websockets/qwebsocket.h
index 18e8019..7337588 100644
--- a/src/websockets/qwebsocket.h
+++ b/src/websockets/qwebsocket.h
@@ -58,6 +58,7 @@ QT_BEGIN_NAMESPACE
class QTcpSocket;
class QWebSocketPrivate;
class QMaskGenerator;
+class QWebSocketHandshakeOptions;
class Q_WEBSOCKETS_EXPORT QWebSocket : public QObject
{
@@ -100,7 +101,9 @@ public:
QString resourceName() const;
QUrl requestUrl() const;
QNetworkRequest request() const;
+ QWebSocketHandshakeOptions handshakeOptions() const;
QString origin() const;
+ QString subprotocol() const;
QWebSocketProtocol::CloseCode closeCode() const;
QString closeReason() const;
@@ -131,8 +134,13 @@ public:
public Q_SLOTS:
void close(QWebSocketProtocol::CloseCode closeCode = QWebSocketProtocol::CloseCodeNormal,
const QString &reason = QString());
+
+ // ### Qt7: Merge overloads
void open(const QUrl &url);
void open(const QNetworkRequest &request);
+ void open(const QUrl &url, const QWebSocketHandshakeOptions &options);
+ void open(const QNetworkRequest &request, const QWebSocketHandshakeOptions &options);
+
void ping(const QByteArray &payload = QByteArray());
#ifndef QT_NO_SSL
void ignoreSslErrors();
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;
diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h
index 77cb3e8..5461e2f 100644
--- a/src/websockets/qwebsocket_p.h
+++ b/src/websockets/qwebsocket_p.h
@@ -65,6 +65,7 @@
#include <private/qobject_p.h>
#include "qwebsocket.h"
+#include "qwebsockethandshakeoptions.h"
#include "qwebsocketprotocol.h"
#include "qwebsocketdataprocessor_p.h"
#include "qdefaultmaskgenerator_p.h"
@@ -138,6 +139,7 @@ public:
QString resourceName() const;
QNetworkRequest request() const;
QString origin() const;
+ QWebSocketHandshakeOptions handshakeOptions() const;
QString protocol() const;
QString extension() const;
QWebSocketProtocol::CloseCode closeCode() const;
@@ -157,7 +159,7 @@ public:
void closeGoingAway();
void close(QWebSocketProtocol::CloseCode closeCode, QString reason);
- void open(const QNetworkRequest &request, bool mask);
+ void open(const QNetworkRequest &request, const QWebSocketHandshakeOptions &options, bool mask);
void ping(const QByteArray &payload);
void setSocketState(QAbstractSocket::SocketState state);
@@ -176,7 +178,7 @@ private:
QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version);
void setVersion(QWebSocketProtocol::Version version);
void setResourceName(const QString &resourceName);
- void setRequest(const QNetworkRequest &request);
+ void setRequest(const QNetworkRequest &request, const QWebSocketHandshakeOptions &options = {});
void setOrigin(const QString &origin);
void setProtocol(const QString &protocol);
void setExtension(const QString &extension);
@@ -204,7 +206,7 @@ private:
QString host,
QString origin,
QString extensions,
- QString protocols,
+ const QStringList &options,
QByteArray key,
const QList<QPair<QString, QString> > &headers);
@@ -225,6 +227,7 @@ private:
QUrl m_resource;
QString m_resourceName;
QNetworkRequest m_request;
+ QWebSocketHandshakeOptions m_options;
QString m_origin;
QString m_protocol;
QString m_extension;
diff --git a/src/websockets/qwebsocket_wasm_p.cpp b/src/websockets/qwebsocket_wasm_p.cpp
index 73c596c..dfbffaf 100644
--- a/src/websockets/qwebsocket_wasm_p.cpp
+++ b/src/websockets/qwebsocket_wasm_p.cpp
@@ -165,7 +165,8 @@ void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString r
reason.toLatin1().toStdString());
}
-void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask)
+void QWebSocketPrivate::open(const QNetworkRequest &request,
+ const QWebSocketHandshakeOptions &options, bool mask)
{
Q_UNUSED(mask);
Q_Q(QWebSocket);
@@ -183,11 +184,16 @@ void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask)
// do support the WebSocket protocol header. This header is
// required for some use cases like MQTT.
const std::string protocolHeaderValue = request.rawHeader("Sec-WebSocket-Protocol").toStdString();
- val webSocket = val::global("WebSocket");
- socketContext = !protocolHeaderValue.empty()
- ? webSocket.new_(urlbytes, protocolHeaderValue)
- : webSocket.new_(urlbytes);
+ val jsSubprotocols = val::array();
+ if (!protocolHeaderValue.empty())
+ jsSubprotocols.call<void>("push", protocolHeaderValue);
+ const auto requestedSubProtocols = options.subprotocols();
+ for (const auto &protocol : requestedSubProtocols)
+ jsSubprotocols.call<void>("push", protocol.toStdString());
+
+ val webSocket = val::global("WebSocket");
+ socketContext = webSocket.new_(urlbytes, jsSubprotocols);
socketContext.set("onerror", val::module_property("QWebSocketPrivate_onErrorCallback"));
socketContext.set("onclose", val::module_property("QWebSocketPrivate_onCloseCallback"));
diff --git a/src/websockets/qwebsockethandshakeoptions.cpp b/src/websockets/qwebsockethandshakeoptions.cpp
new file mode 100644
index 0000000..0d90a92
--- /dev/null
+++ b/src/websockets/qwebsockethandshakeoptions.cpp
@@ -0,0 +1,147 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 Menlo Systems GmbH, author Arno Rehn <a.rehn@menlosystems.com>.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebSockets module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qwebsockethandshakeoptions_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QWebSocketHandshakeOptions
+
+ \inmodule QtWebSockets
+ \since 6.4
+ \brief Collects options for the WebSocket handshake.
+
+ QWebSocketHandshakeOptions collects options that are passed along to the
+ WebSocket handshake, such as WebSocket subprotocols and WebSocket
+ Extensions.
+
+ At the moment, only WebSocket subprotocols are supported.
+
+ \sa QWebSocket::open()
+*/
+
+/*!
+ \brief Constructs an empty QWebSocketHandshakeOptions object.
+*/
+QWebSocketHandshakeOptions::QWebSocketHandshakeOptions()
+ : d(new QWebSocketHandshakeOptionsPrivate)
+{
+}
+
+/*!
+ \brief Constructs a QWebSocketHandshakeOptions that is a copy of \a other.
+*/
+QWebSocketHandshakeOptions::QWebSocketHandshakeOptions(const QWebSocketHandshakeOptions &other)
+ : d(other.d)
+{
+}
+
+/*!
+ \brief Constructs a QWebSocketHandshakeOptions that is moved from \a other.
+*/
+QWebSocketHandshakeOptions::QWebSocketHandshakeOptions(QWebSocketHandshakeOptions &&other)
+ : d(std::move(other.d))
+{
+}
+
+/*!
+ \brief Destroys this object.
+*/
+QWebSocketHandshakeOptions::~QWebSocketHandshakeOptions()
+{
+}
+
+/*!
+ \fn QWebSocketHandshakeOptions &operator=(QWebSocketHandshakeOptions &&other) noexcept
+ \brief Moves \a other to this object.
+*/
+
+/*!
+ \brief Assigns \a other to this object.
+*/
+QWebSocketHandshakeOptions &QWebSocketHandshakeOptions::operator=(
+ const QWebSocketHandshakeOptions &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ \fn void swap(QWebSocketHandshakeOptions &other) noexcept
+ \brief Swaps this object with \a other.
+*/
+
+/*!
+ \brief Returns the list of WebSocket subprotocols to send along with the
+ websocket handshake.
+*/
+QStringList QWebSocketHandshakeOptions::subprotocols() const
+{
+ return d->subprotocols;
+}
+
+/*!
+ \brief Sets the list of WebSocket subprotocols to send along with the
+ websocket handshake.
+
+ WebSocket subprotocol names may only consist of those US-ASCII characters
+ that are in the unreserved group. Invalid protocol names will not be
+ included in the handshake.
+*/
+void QWebSocketHandshakeOptions::setSubprotocols(const QStringList &protocols)
+{
+ d->subprotocols = protocols;
+}
+
+bool QWebSocketHandshakeOptions::equals(const QWebSocketHandshakeOptions &other) const
+{
+ return *d == *other.d;
+}
+
+/*!
+ \fn operator==(const QWebSocketHandshakeOptions &lhs, const QWebSocketHandshakeOptions &rhs)
+ \brief Compares \a lhs for equality with \a rhs.
+*/
+/*!
+ \fn operator!=(const QWebSocketHandshakeOptions &lhs, const QWebSocketHandshakeOptions &rhs)
+ \brief Compares \a lhs for inequality with \a rhs.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/websockets/qwebsockethandshakeoptions.h b/src/websockets/qwebsockethandshakeoptions.h
new file mode 100644
index 0000000..82eaec7
--- /dev/null
+++ b/src/websockets/qwebsockethandshakeoptions.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 Menlo Systems GmbH, author Arno Rehn <a.rehn@menlosystems.com>.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebSockets module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QWEBSOCKETHANDSHAKEOPTIONS_H
+#define QWEBSOCKETHANDSHAKEOPTIONS_H
+
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QStringList>
+
+#include "QtWebSockets/qwebsockets_global.h"
+
+QT_BEGIN_NAMESPACE
+
+class QWebSocketHandshakeOptionsPrivate;
+
+class Q_WEBSOCKETS_EXPORT QWebSocketHandshakeOptions
+{
+public:
+ QWebSocketHandshakeOptions();
+ QWebSocketHandshakeOptions(const QWebSocketHandshakeOptions &other);
+ QWebSocketHandshakeOptions(QWebSocketHandshakeOptions &&other);
+ ~QWebSocketHandshakeOptions();
+
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QWebSocketHandshakeOptions)
+ QWebSocketHandshakeOptions &operator=(const QWebSocketHandshakeOptions &other);
+
+ void swap(QWebSocketHandshakeOptions &other) noexcept { qSwap(d, other.d); }
+
+ QStringList subprotocols() const;
+ void setSubprotocols(const QStringList &protocols);
+
+private:
+ bool equals(const QWebSocketHandshakeOptions &other) const;
+
+ friend bool operator==(const QWebSocketHandshakeOptions &lhs,
+ const QWebSocketHandshakeOptions &rhs) { return lhs.equals(rhs); }
+ friend bool operator!=(const QWebSocketHandshakeOptions &lhs,
+ const QWebSocketHandshakeOptions &rhs) { return !lhs.equals(rhs); }
+
+ QSharedDataPointer<QWebSocketHandshakeOptionsPrivate> d;
+ friend class QWebSocketHandshakeOptionsPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBSOCKETHANDSHAKEOPTIONS_H
diff --git a/src/websockets/qwebsockethandshakeoptions_p.h b/src/websockets/qwebsockethandshakeoptions_p.h
new file mode 100644
index 0000000..2a7e77e
--- /dev/null
+++ b/src/websockets/qwebsockethandshakeoptions_p.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 Menlo Systems GmbH, author Arno Rehn <a.rehn@menlosystems.com>.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebSockets module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QWEBSOCKETHANDSHAKEOPTIONS_P_H
+#define QWEBSOCKETHANDSHAKEOPTIONS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QSharedData>
+
+#include "qwebsockethandshakeoptions.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QWebSocketHandshakeOptionsPrivate : public QSharedData
+{
+public:
+ inline bool operator==(const QWebSocketHandshakeOptionsPrivate &other) const
+ { return subprotocols == other.subprotocols; }
+
+ QStringList subprotocols;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBSOCKETHANDSHAKEOPTIONS_P_H
diff --git a/src/websockets/qwebsockethandshakeresponse.cpp b/src/websockets/qwebsockethandshakeresponse.cpp
index d3ef609..5e4a061 100644
--- a/src/websockets/qwebsockethandshakeresponse.cpp
+++ b/src/websockets/qwebsockethandshakeresponse.cpp
@@ -161,9 +161,21 @@ QString QWebSocketHandshakeResponse::getHandshakeResponse(
} else {
if (request.isValid()) {
const QString acceptKey = calculateAcceptKey(request.key());
- const QList<QString> matchingProtocols =
- listIntersection(supportedProtocols, request.protocols(),
- std::less<QString>());
+
+ // Find first client protocol that is supported. Order is important!
+ const QString protocol = [&] {
+ const auto clientProtocols = request.protocols();
+
+ const auto isSupportedProtocol = [&](const QString &protocol) {
+ return supportedProtocols.contains(protocol);
+ };
+ const auto it = std::find_if(
+ clientProtocols.constBegin(), clientProtocols.constEnd(),
+ isSupportedProtocol);
+
+ return it == clientProtocols.constEnd() ? QString() : *it;
+ }();
+
//TODO: extensions must be kept in the order in which they arrive
//cannot use set.intersect() to get the supported extensions
const QList<QString> matchingExtensions =
@@ -182,8 +194,8 @@ QString QWebSocketHandshakeResponse::getHandshakeResponse(
QStringLiteral("Upgrade: websocket") <<
QStringLiteral("Connection: Upgrade") <<
QStringLiteral("Sec-WebSocket-Accept: ") % acceptKey;
- if (!matchingProtocols.isEmpty()) {
- m_acceptedProtocol = matchingProtocols.first();
+ if (!protocol.isEmpty()) {
+ m_acceptedProtocol = protocol;
response << QStringLiteral("Sec-WebSocket-Protocol: ") % m_acceptedProtocol;
}
if (!matchingExtensions.isEmpty()) {
diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp
index 5cbdbec..e132915 100644
--- a/src/websockets/qwebsocketserver.cpp
+++ b/src/websockets/qwebsocketserver.cpp
@@ -65,9 +65,7 @@
Calling close() makes QWebSocketServer stop listening for incoming connections.
- QWebSocketServer currently does not support
- \l {WebSocket Extensions} and
- \l {WebSocket Subprotocols}.
+ QWebSocketServer currently does not support \l {WebSocket Extensions}.
\note When working with self-signed certificates, \l{Firefox bug 594502} prevents \l{Firefox} to
connect to a secure WebSocket server. To work around this problem, first browse to the
@@ -537,6 +535,26 @@ QString QWebSocketServer::serverName() const
}
/*!
+ \brief Sets the list of protocols supported by the server to \a protocols.
+ \since 6.4
+ */
+void QWebSocketServer::setSupportedSubprotocols(const QStringList &protocols)
+{
+ Q_D(QWebSocketServer);
+ d->setSupportedSubprotocols(protocols);
+}
+
+/*!
+ \brief Returns the list of protocols supported by the server.
+ \since 6.4
+ */
+QStringList QWebSocketServer::supportedSubprotocols() const
+{
+ Q_D(const QWebSocketServer);
+ return d->supportedSubprotocols();
+}
+
+/*!
Returns the server's address if the server is listening for connections; otherwise returns
QHostAddress::Null.
diff --git a/src/websockets/qwebsocketserver.h b/src/websockets/qwebsocketserver.h
index 686c281..e6085f9 100644
--- a/src/websockets/qwebsocketserver.h
+++ b/src/websockets/qwebsocketserver.h
@@ -130,6 +130,9 @@ public:
void setServerName(const QString &serverName);
QString serverName() const;
+ void setSupportedSubprotocols(const QStringList &protocols);
+ QStringList supportedSubprotocols() const;
+
#ifndef QT_NO_NETWORKPROXY
void setProxy(const QNetworkProxy &networkProxy);
QNetworkProxy proxy() const;
diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp
index 23b38aa..c91c436 100644
--- a/src/websockets/qwebsocketserver_p.cpp
+++ b/src/websockets/qwebsocketserver_p.cpp
@@ -320,10 +320,17 @@ QList<QWebSocketProtocol::Version> QWebSocketServerPrivate::supportedVersions()
/*!
\internal
*/
-QStringList QWebSocketServerPrivate::supportedProtocols() const
+void QWebSocketServerPrivate::setSupportedSubprotocols(const QStringList &protocols)
{
- QStringList supportedProtocols;
- return supportedProtocols; //no protocols are currently supported
+ m_supportedSubprotocols = protocols;
+}
+
+/*!
+ \internal
+ */
+QStringList QWebSocketServerPrivate::supportedSubprotocols() const
+{
+ return m_supportedSubprotocols;
}
/*!
@@ -489,7 +496,7 @@ void QWebSocketServerPrivate::handshakeReceived()
m_serverName,
corsAuthenticator.allowed(),
supportedVersions(),
- supportedProtocols(),
+ supportedSubprotocols(),
supportedExtensions());
if (Q_LIKELY(response.isValid())) {
diff --git a/src/websockets/qwebsocketserver_p.h b/src/websockets/qwebsocketserver_p.h
index 1f1cbd9..baf60d1 100644
--- a/src/websockets/qwebsocketserver_p.h
+++ b/src/websockets/qwebsocketserver_p.h
@@ -111,7 +111,8 @@ public:
qintptr socketDescriptor() const;
QList<QWebSocketProtocol::Version> supportedVersions() const;
- QStringList supportedProtocols() const;
+ void setSupportedSubprotocols(const QStringList &protocols);
+ QStringList supportedSubprotocols() const;
QStringList supportedExtensions() const;
void setServerName(const QString &serverName);
@@ -135,6 +136,7 @@ private:
QTcpServer *m_pTcpServer;
QString m_serverName;
SslMode m_secureMode;
+ QStringList m_supportedSubprotocols;
QQueue<QWebSocket *> m_pendingConnections;
QWebSocketProtocol::CloseCode m_error;
QString m_errorString;