/**************************************************************************** ** ** Copyright (C) 2016 Kurt Pattyn . ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** 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 General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** 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-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #ifndef QT_NO_OPENSSL #include #include #endif #ifndef QT_NO_SSL #include #endif #include #include #include #include QT_USE_NAMESPACE Q_DECLARE_METATYPE(QWebSocketProtocol::Version) Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) Q_DECLARE_METATYPE(QWebSocketServer::SslMode) Q_DECLARE_METATYPE(QWebSocketCorsAuthenticator *) #ifndef QT_NO_SSL Q_DECLARE_METATYPE(QSslError) #endif #ifndef QT_NO_OPENSSL // Use this cipher to force PSK key sharing. static const QString PSK_CIPHER_WITHOUT_AUTH = QStringLiteral("PSK-AES256-CBC-SHA"); static const QByteArray PSK_CLIENT_PRESHAREDKEY = QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"); static const QByteArray PSK_SERVER_IDENTITY_HINT = QByteArrayLiteral("QtTestServerHint"); static const QByteArray PSK_CLIENT_IDENTITY = QByteArrayLiteral("Client_identity"); class PskProvider : public QObject { Q_OBJECT public: bool m_server = false; QByteArray m_identity; QByteArray m_psk; public slots: void providePsk(QSslPreSharedKeyAuthenticator *authenticator) { QVERIFY(authenticator); QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT); if (m_server) QCOMPARE(authenticator->maximumIdentityLength(), 0); else QVERIFY(authenticator->maximumIdentityLength() > 0); QVERIFY(authenticator->maximumPreSharedKeyLength() > 0); if (!m_identity.isEmpty()) { authenticator->setIdentity(m_identity); QCOMPARE(authenticator->identity(), m_identity); } if (!m_psk.isEmpty()) { authenticator->setPreSharedKey(m_psk); QCOMPARE(authenticator->preSharedKey(), m_psk); } } }; #endif class tst_QWebSocketServer : public QObject { Q_OBJECT public: tst_QWebSocketServer(); private Q_SLOTS: void init(); void initTestCase(); void cleanupTestCase(); void tst_initialisation(); void tst_settersAndGetters(); void tst_listening(); void tst_connectivity(); void tst_preSharedKey(); void tst_maxPendingConnections(); void tst_serverDestroyedWhileSocketConnected(); void tst_scheme(); // qtbug-55927 void tst_handleConnection(); }; tst_QWebSocketServer::tst_QWebSocketServer() { } void tst_QWebSocketServer::init() { qRegisterMetaType("QWebSocketProtocol::Version"); qRegisterMetaType("QWebSocketProtocol::CloseCode"); qRegisterMetaType("QWebSocketServer::SslMode"); qRegisterMetaType("QWebSocketCorsAuthenticator *"); #ifndef QT_NO_SSL qRegisterMetaType("QSslError"); #ifndef QT_NO_OPENSSL qRegisterMetaType(); #endif #endif } void tst_QWebSocketServer::initTestCase() { } void tst_QWebSocketServer::cleanupTestCase() { } void tst_QWebSocketServer::tst_initialisation() { { QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); QVERIFY(server.serverName().isEmpty()); QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode); QVERIFY(!server.isListening()); QCOMPARE(server.maxPendingConnections(), 30); QCOMPARE(server.serverPort(), quint16(0)); QCOMPARE(server.serverAddress(), QHostAddress()); QCOMPARE(server.socketDescriptor(), -1); QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal); QVERIFY(server.errorString().isEmpty()); #ifndef QT_NO_NETWORKPROXY QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy); #endif #ifndef QT_NO_SSL QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); #endif QCOMPARE(server.supportedVersions().count(), 1); QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest); QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13); server.close(); //closing a server should not affect any of the parameters //certainly if the server was not opened before QVERIFY(server.serverName().isEmpty()); QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode); QVERIFY(!server.isListening()); QCOMPARE(server.maxPendingConnections(), 30); QCOMPARE(server.serverPort(), quint16(0)); QCOMPARE(server.serverAddress(), QHostAddress()); QCOMPARE(server.socketDescriptor(), -1); QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal); QVERIFY(server.errorString().isEmpty()); #ifndef QT_NO_NETWORKPROXY QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy); #endif #ifndef QT_NO_SSL QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); #endif QCOMPARE(server.supportedVersions().count(), 1); QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest); QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13); QCOMPARE(server.serverUrl(), QUrl()); } { #ifndef QT_NO_SSL QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode); QCOMPARE(sslServer.secureMode(), QWebSocketServer::SecureMode); #endif } } void tst_QWebSocketServer::tst_settersAndGetters() { QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); server.setMaxPendingConnections(23); QCOMPARE(server.maxPendingConnections(), 23); server.setMaxPendingConnections(INT_MIN); QCOMPARE(server.maxPendingConnections(), INT_MIN); server.setMaxPendingConnections(INT_MAX); QCOMPARE(server.maxPendingConnections(), INT_MAX); QVERIFY(!server.setSocketDescriptor(-2)); QCOMPARE(server.socketDescriptor(), -1); server.setServerName(QStringLiteral("Qt WebSocketServer")); QCOMPARE(server.serverName(), QStringLiteral("Qt WebSocketServer")); #ifndef QT_NO_NETWORKPROXY QNetworkProxy proxy(QNetworkProxy::Socks5Proxy); server.setProxy(proxy); QCOMPARE(server.proxy(), proxy); #endif #ifndef QT_NO_SSL //cannot set an ssl configuration on a non secure server QSslConfiguration sslConfiguration = QSslConfiguration::defaultConfiguration(); sslConfiguration.setPeerVerifyDepth(sslConfiguration.peerVerifyDepth() + 1); server.setSslConfiguration(sslConfiguration); QVERIFY(server.sslConfiguration() != sslConfiguration); QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode); sslServer.setSslConfiguration(sslConfiguration); QCOMPARE(sslServer.sslConfiguration(), sslConfiguration); QVERIFY(sslServer.sslConfiguration() != QSslConfiguration::defaultConfiguration()); #endif } void tst_QWebSocketServer::tst_listening() { //These listening tests are not too extensive, as the implementation of QWebSocketServer //relies on QTcpServer QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError))); QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); QSignalSpy serverErrorSpy(&server, SIGNAL(serverError(QWebSocketProtocol::CloseCode))); QSignalSpy corsAuthenticationSpy(&server, SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); #ifndef QT_NO_SSL QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList))); #endif QVERIFY(server.listen()); //listen on all network interface, choose an appropriate port QVERIFY(server.isListening()); QCOMPARE(serverClosedSpy.count(), 0); server.close(); QTRY_COMPARE(serverClosedSpy.count(), 1); QVERIFY(!server.isListening()); QCOMPARE(serverErrorSpy.count(), 0); QVERIFY(!server.listen(QHostAddress(QStringLiteral("1.2.3.4")), 0)); QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeAbnormalDisconnection); QCOMPARE(server.errorString().toLatin1().constData(), "The address is not available"); QVERIFY(!server.isListening()); QCOMPARE(serverAcceptErrorSpy.count(), 0); QCOMPARE(serverConnectionSpy.count(), 0); QCOMPARE(corsAuthenticationSpy.count(), 0); #ifndef QT_NO_SSL QCOMPARE(peerVerifyErrorSpy.count(), 0); QCOMPARE(sslErrorsSpy.count(), 0); #endif QCOMPARE(serverErrorSpy.count(), 1); QCOMPARE(serverClosedSpy.count(), 1); } void tst_QWebSocketServer::tst_connectivity() { QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); QSignalSpy serverErrorSpy(&server, SIGNAL(serverError(QWebSocketProtocol::CloseCode))); QSignalSpy corsAuthenticationSpy(&server, SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); #ifndef QT_NO_SSL QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList))); #endif QWebSocket socket; QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); QVERIFY(server.listen()); QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any)); QCOMPARE(server.serverUrl(), QUrl(QStringLiteral("ws://") + QHostAddress(QHostAddress::LocalHost).toString() + QStringLiteral(":").append(QString::number(server.serverPort())))); socket.open(server.serverUrl().toString()); QTRY_COMPARE(socketConnectedSpy.count(), 1); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QCOMPARE(serverConnectionSpy.count(), 1); QCOMPARE(corsAuthenticationSpy.count(), 1); QCOMPARE(serverClosedSpy.count(), 0); server.close(); QTRY_COMPARE(serverClosedSpy.count(), 1); #ifndef QT_NO_SSL QCOMPARE(peerVerifyErrorSpy.count(), 0); QCOMPARE(sslErrorsSpy.count(), 0); #endif QCOMPARE(serverErrorSpy.count(), 0); } void tst_QWebSocketServer::tst_preSharedKey() { #ifndef QT_NO_OPENSSL QWebSocketServer server(QString(), QWebSocketServer::SecureMode); bool cipherFound = false; const QList supportedCiphers = QSslSocket::supportedCiphers(); for (const QSslCipher &cipher : supportedCiphers) { if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) { cipherFound = true; break; } } if (!cipherFound) QSKIP("SSL implementation does not support the necessary cipher"); QSslCipher cipher(PSK_CIPHER_WITHOUT_AUTH); QList list; list << cipher; QSslConfiguration config = QSslConfiguration::defaultConfiguration(); config.setCiphers(list); config.setPeerVerifyMode(QSslSocket::VerifyNone); config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT); server.setSslConfiguration(config); PskProvider providerServer; providerServer.m_server = true; providerServer.m_identity = PSK_CLIENT_IDENTITY; providerServer.m_psk = PSK_CLIENT_PRESHAREDKEY; connect(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired, &providerServer, &PskProvider::providePsk); QSignalSpy serverPskRequiredSpy(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired); QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection); QSignalSpy serverErrorSpy(&server, SIGNAL(serverError(QWebSocketProtocol::CloseCode))); QSignalSpy serverClosedSpy(&server, &QWebSocketServer::closed); QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList))); QWebSocket socket; QSslConfiguration socketConfig = QSslConfiguration::defaultConfiguration(); socketConfig.setPeerVerifyMode(QSslSocket::VerifyNone); socketConfig.setCiphers(list); socket.setSslConfiguration(socketConfig); PskProvider providerClient; providerClient.m_identity = PSK_CLIENT_IDENTITY; providerClient.m_psk = PSK_CLIENT_PRESHAREDKEY; connect(&socket, &QWebSocket::preSharedKeyAuthenticationRequired, &providerClient, &PskProvider::providePsk); QSignalSpy socketPskRequiredSpy(&socket, &QWebSocket::preSharedKeyAuthenticationRequired); QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); QVERIFY(server.listen()); QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any)); QCOMPARE(server.serverUrl(), QUrl(QString::asprintf("wss://%ls:%d", qUtf16Printable(QHostAddress(QHostAddress::LocalHost).toString()), server.serverPort()))); socket.open(server.serverUrl().toString()); QTRY_COMPARE(socketConnectedSpy.count(), 1); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QCOMPARE(serverConnectionSpy.count(), 1); QCOMPARE(serverPskRequiredSpy.count(), 1); QCOMPARE(socketPskRequiredSpy.count(), 1); QCOMPARE(serverClosedSpy.count(), 0); server.close(); QTRY_COMPARE(serverClosedSpy.count(), 1); QCOMPARE(sslErrorsSpy.count(), 0); QCOMPARE(serverErrorSpy.count(), 0); #endif } void tst_QWebSocketServer::tst_maxPendingConnections() { //tests if maximum connections are respected //also checks if there are no side-effects like signals that are unexpectedly thrown QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); server.setMaxPendingConnections(2); QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); QSignalSpy serverErrorSpy(&server, SIGNAL(serverError(QWebSocketProtocol::CloseCode))); QSignalSpy corsAuthenticationSpy(&server, SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); #ifndef QT_NO_SSL QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList))); #endif QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError))); QWebSocket socket1; QWebSocket socket2; QWebSocket socket3; QSignalSpy socket1ConnectedSpy(&socket1, SIGNAL(connected())); QSignalSpy socket2ConnectedSpy(&socket2, SIGNAL(connected())); QSignalSpy socket3ConnectedSpy(&socket3, SIGNAL(connected())); QVERIFY(server.listen()); socket1.open(server.serverUrl().toString()); QTRY_COMPARE(socket1ConnectedSpy.count(), 1); QCOMPARE(socket1.state(), QAbstractSocket::ConnectedState); QCOMPARE(serverConnectionSpy.count(), 1); QCOMPARE(corsAuthenticationSpy.count(), 1); socket2.open(server.serverUrl().toString()); QTRY_COMPARE(socket2ConnectedSpy.count(), 1); QCOMPARE(socket2.state(), QAbstractSocket::ConnectedState); QCOMPARE(serverConnectionSpy.count(), 2); QCOMPARE(corsAuthenticationSpy.count(), 2); socket3.open(server.serverUrl().toString()); QVERIFY(!socket3ConnectedSpy.wait(250)); QCOMPARE(socket3ConnectedSpy.count(), 0); QCOMPARE(socket3.state(), QAbstractSocket::UnconnectedState); QCOMPARE(serverConnectionSpy.count(), 2); QCOMPARE(corsAuthenticationSpy.count(), 2); QVERIFY(server.hasPendingConnections()); QWebSocket *pSocket = server.nextPendingConnection(); QVERIFY(pSocket); delete pSocket; QVERIFY(server.hasPendingConnections()); pSocket = server.nextPendingConnection(); QVERIFY(pSocket); delete pSocket; QVERIFY(!server.hasPendingConnections()); QVERIFY(!server.nextPendingConnection()); //will resolve in another commit #ifndef Q_OS_WIN QCOMPARE(serverErrorSpy.count(), 1); QCOMPARE(serverErrorSpy.at(0).at(0).value(), QWebSocketProtocol::CloseCodeAbnormalDisconnection); #endif QCOMPARE(serverClosedSpy.count(), 0); server.close(); QTRY_COMPARE(serverClosedSpy.count(), 1); #ifndef QT_NO_SSL QCOMPARE(peerVerifyErrorSpy.count(), 0); QCOMPARE(sslErrorsSpy.count(), 0); #endif QCOMPARE(serverAcceptErrorSpy.count(), 0); } void tst_QWebSocketServer::tst_serverDestroyedWhileSocketConnected() { QWebSocketServer * server = new QWebSocketServer(QString(), QWebSocketServer::NonSecureMode); QSignalSpy serverConnectionSpy(server, SIGNAL(newConnection())); QSignalSpy corsAuthenticationSpy(server, SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); QSignalSpy serverClosedSpy(server, SIGNAL(closed())); QWebSocket socket; QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); QSignalSpy socketDisconnectedSpy(&socket, SIGNAL(disconnected())); QVERIFY(server->listen()); QCOMPARE(server->serverAddress(), QHostAddress(QHostAddress::Any)); QCOMPARE(server->serverUrl(), QUrl(QStringLiteral("ws://") + QHostAddress(QHostAddress::LocalHost).toString() + QStringLiteral(":").append(QString::number(server->serverPort())))); socket.open(server->serverUrl().toString()); QTRY_COMPARE(socketConnectedSpy.count(), 1); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QCOMPARE(serverConnectionSpy.count(), 1); QCOMPARE(corsAuthenticationSpy.count(), 1); QCOMPARE(serverClosedSpy.count(), 0); delete server; QTRY_COMPARE(socketDisconnectedSpy.count(), 1); } void tst_QWebSocketServer::tst_scheme() { QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode); QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection())); QVERIFY(plainServer.listen()); QWebSocket plainSocket; plainSocket.open(plainServer.serverUrl().toString()); QTRY_COMPARE(plainServerConnectionSpy.count(), 1); QScopedPointer plainServerSocket(plainServer.nextPendingConnection()); QVERIFY(!plainServerSocket.isNull()); QCOMPARE(plainServerSocket->requestUrl().scheme(), QStringLiteral("ws")); plainServer.close(); #ifndef QT_NO_SSL QWebSocketServer secureServer(QString(), QWebSocketServer::SecureMode); QSslConfiguration sslConfiguration; QFile certFile(QStringLiteral(":/localhost.cert")); QFile keyFile(QStringLiteral(":/localhost.key")); QVERIFY(certFile.open(QIODevice::ReadOnly)); QVERIFY(keyFile.open(QIODevice::ReadOnly)); QSslCertificate certificate(&certFile, QSsl::Pem); QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem); certFile.close(); keyFile.close(); sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); sslConfiguration.setLocalCertificate(certificate); sslConfiguration.setPrivateKey(sslKey); sslConfiguration.setProtocol(QSsl::TlsV1SslV3); secureServer.setSslConfiguration(sslConfiguration); QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection())); QVERIFY(secureServer.listen()); QWebSocket secureSocket; typedef void (QWebSocket::* ignoreSslErrorsSlot)(); connect(&secureSocket, &QWebSocket::sslErrors, &secureSocket, static_cast(&QWebSocket::ignoreSslErrors)); secureSocket.open(secureServer.serverUrl().toString()); QTRY_COMPARE(secureServerConnectionSpy.count(), 1); QScopedPointer secureServerSocket(secureServer.nextPendingConnection()); QVERIFY(!secureServerSocket.isNull()); QCOMPARE(secureServerSocket->requestUrl().scheme(), QStringLiteral("wss")); secureServer.close(); #endif } void tst_QWebSocketServer::tst_handleConnection() { QWebSocketServer wsServer(QString(), QWebSocketServer::NonSecureMode); QSignalSpy wsServerConnectionSpy(&wsServer, &QWebSocketServer::newConnection); QTcpServer tcpServer; connect(&tcpServer, &QTcpServer::newConnection, [&tcpServer, &wsServer]() { wsServer.handleConnection(tcpServer.nextPendingConnection()); }); QVERIFY(tcpServer.listen()); QWebSocket webSocket; QSignalSpy wsConnectedSpy(&webSocket, &QWebSocket::connected); webSocket.open(QStringLiteral("ws://localhost:%1").arg(tcpServer.serverPort())); QTRY_COMPARE(wsConnectedSpy.count(), 1); QTRY_COMPARE(wsServerConnectionSpy.count(), 1); QScopedPointer webServerSocket(wsServer.nextPendingConnection()); QVERIFY(!webServerSocket.isNull()); QSignalSpy wsMessageReceivedSpy(webServerSocket.data(), &QWebSocket::textMessageReceived); webSocket.sendTextMessage("dummy"); QTRY_COMPARE(wsMessageReceivedSpy.count(), 1); QList arguments = wsMessageReceivedSpy.takeFirst(); QCOMPARE(arguments.first().toString(), QString("dummy")); } QTEST_MAIN(tst_QWebSocketServer) #include "tst_qwebsocketserver.moc"