1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | #include <QString> |
29 | #include <QtTest> |
30 | #include <QNetworkProxy> |
31 | #include <QTcpSocket> |
32 | #include <QTcpServer> |
33 | #include <QtCore/QScopedPointer> |
34 | #ifndef QT_NO_OPENSSL |
35 | #include <QtNetwork/qsslpresharedkeyauthenticator.h> |
36 | #endif |
37 | #ifndef QT_NO_SSL |
38 | #include <QtNetwork/qsslcipher.h> |
39 | #include <QtNetwork/qsslkey.h> |
40 | #include <QtNetwork/qsslsocket.h> |
41 | #endif |
42 | #include <QtWebSockets/QWebSocketServer> |
43 | #include <QtWebSockets/QWebSocket> |
44 | #include <QtWebSockets/QWebSocketCorsAuthenticator> |
45 | #include <QtWebSockets/qwebsocketprotocol.h> |
46 | |
47 | QT_USE_NAMESPACE |
48 | |
49 | Q_DECLARE_METATYPE(QWebSocketProtocol::Version) |
50 | Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) |
51 | Q_DECLARE_METATYPE(QWebSocketServer::SslMode) |
52 | Q_DECLARE_METATYPE(QWebSocketCorsAuthenticator *) |
53 | #ifndef QT_NO_SSL |
54 | Q_DECLARE_METATYPE(QSslError) |
55 | #endif |
56 | |
57 | #ifndef QT_NO_OPENSSL |
58 | // Use this cipher to force PSK key sharing. |
59 | static const QString PSK_CIPHER_WITHOUT_AUTH = QStringLiteral("PSK-AES256-CBC-SHA" ); |
60 | static const QByteArray PSK_CLIENT_PRESHAREDKEY = QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f" ); |
61 | static const QByteArray PSK_SERVER_IDENTITY_HINT = QByteArrayLiteral("QtTestServerHint" ); |
62 | static const QByteArray PSK_CLIENT_IDENTITY = QByteArrayLiteral("Client_identity" ); |
63 | |
64 | class PskProvider : public QObject |
65 | { |
66 | Q_OBJECT |
67 | |
68 | public: |
69 | bool m_server = false; |
70 | QByteArray m_identity; |
71 | QByteArray m_psk; |
72 | |
73 | public slots: |
74 | void providePsk(QSslPreSharedKeyAuthenticator *authenticator) |
75 | { |
76 | QVERIFY(authenticator); |
77 | QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT); |
78 | if (m_server) |
79 | QCOMPARE(authenticator->maximumIdentityLength(), 0); |
80 | else |
81 | QVERIFY(authenticator->maximumIdentityLength() > 0); |
82 | |
83 | QVERIFY(authenticator->maximumPreSharedKeyLength() > 0); |
84 | |
85 | if (!m_identity.isEmpty()) { |
86 | authenticator->setIdentity(m_identity); |
87 | QCOMPARE(authenticator->identity(), m_identity); |
88 | } |
89 | |
90 | if (!m_psk.isEmpty()) { |
91 | authenticator->setPreSharedKey(m_psk); |
92 | QCOMPARE(authenticator->preSharedKey(), m_psk); |
93 | } |
94 | } |
95 | }; |
96 | #endif |
97 | |
98 | class tst_QWebSocketServer : public QObject |
99 | { |
100 | Q_OBJECT |
101 | |
102 | public: |
103 | tst_QWebSocketServer(); |
104 | |
105 | private Q_SLOTS: |
106 | void init(); |
107 | void initTestCase(); |
108 | void cleanupTestCase(); |
109 | void tst_initialisation(); |
110 | void tst_settersAndGetters(); |
111 | void tst_listening(); |
112 | void tst_connectivity(); |
113 | void tst_preSharedKey(); |
114 | void tst_maxPendingConnections(); |
115 | void tst_serverDestroyedWhileSocketConnected(); |
116 | void tst_scheme(); // qtbug-55927 |
117 | void tst_handleConnection(); |
118 | void tst_handshakeTimeout(); // qtbug-63312, qtbug-57026 |
119 | void multipleFrames(); |
120 | |
121 | private: |
122 | bool m_shouldSkipUnsupportedIpv6Test; |
123 | #ifdef SHOULD_CHECK_SYSCALL_SUPPORT |
124 | bool ipv6GetsockoptionMissing(int level, int optname); |
125 | #endif |
126 | }; |
127 | |
128 | tst_QWebSocketServer::tst_QWebSocketServer() : m_shouldSkipUnsupportedIpv6Test(false) |
129 | { |
130 | } |
131 | |
132 | void tst_QWebSocketServer::init() |
133 | { |
134 | qRegisterMetaType<QWebSocketProtocol::Version>(typeName: "QWebSocketProtocol::Version" ); |
135 | qRegisterMetaType<QWebSocketProtocol::CloseCode>(typeName: "QWebSocketProtocol::CloseCode" ); |
136 | qRegisterMetaType<QWebSocketServer::SslMode>(typeName: "QWebSocketServer::SslMode" ); |
137 | qRegisterMetaType<QWebSocketCorsAuthenticator *>(typeName: "QWebSocketCorsAuthenticator *" ); |
138 | #ifndef QT_NO_SSL |
139 | qRegisterMetaType<QSslError>(typeName: "QSslError" ); |
140 | #ifndef QT_NO_OPENSSL |
141 | qRegisterMetaType<QSslPreSharedKeyAuthenticator *>(); |
142 | #endif |
143 | #endif |
144 | } |
145 | |
146 | #ifdef SHOULD_CHECK_SYSCALL_SUPPORT |
147 | #include <netinet/in.h> |
148 | #include <sys/socket.h> |
149 | #include <errno.h> |
150 | #include <unistd.h> |
151 | |
152 | bool tst_QWebSocketServer::ipv6GetsockoptionMissing(int level, int optname) |
153 | { |
154 | int testSocket; |
155 | |
156 | testSocket = socket(PF_INET6, SOCK_STREAM, 0); |
157 | |
158 | // If we can't test here, assume it's not missing |
159 | if (testSocket == -1) |
160 | return false; |
161 | |
162 | bool result = false; |
163 | if (getsockopt(testSocket, level, optname, nullptr, 0) == -1) { |
164 | if (errno == EOPNOTSUPP) { |
165 | result = true; |
166 | } |
167 | } |
168 | |
169 | close(testSocket); |
170 | return result; |
171 | } |
172 | |
173 | #endif //SHOULD_CHECK_SYSCALL_SUPPORT |
174 | |
175 | void tst_QWebSocketServer::initTestCase() |
176 | { |
177 | #ifdef SHOULD_CHECK_SYSCALL_SUPPORT |
178 | // Qemu does not have required support for IPV6 socket options. |
179 | // If this is detected, skip the test |
180 | m_shouldSkipUnsupportedIpv6Test = ipv6GetsockoptionMissing(SOL_IPV6, IPV6_V6ONLY); |
181 | #endif |
182 | } |
183 | |
184 | void tst_QWebSocketServer::cleanupTestCase() |
185 | { |
186 | } |
187 | |
188 | void tst_QWebSocketServer::tst_initialisation() |
189 | { |
190 | { |
191 | QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); |
192 | |
193 | QVERIFY(server.serverName().isEmpty()); |
194 | QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode); |
195 | QVERIFY(!server.isListening()); |
196 | QCOMPARE(server.maxPendingConnections(), 30); |
197 | QCOMPARE(server.serverPort(), quint16(0)); |
198 | QCOMPARE(server.serverAddress(), QHostAddress()); |
199 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) |
200 | QCOMPARE(server.socketDescriptor(), -1); |
201 | #else // ### Qt 6: Remove leftovers |
202 | QCOMPARE(server.nativeDescriptor(), -1); |
203 | #endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) |
204 | QVERIFY(!server.hasPendingConnections()); |
205 | QVERIFY(!server.nextPendingConnection()); |
206 | QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal); |
207 | QVERIFY(server.errorString().isEmpty()); |
208 | #ifndef QT_NO_NETWORKPROXY |
209 | QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy); |
210 | #endif |
211 | #ifndef QT_NO_SSL |
212 | QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); |
213 | #endif |
214 | QCOMPARE(server.supportedVersions().count(), 1); |
215 | QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest); |
216 | QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13); |
217 | |
218 | server.close(); |
219 | //closing a server should not affect any of the parameters |
220 | //certainly if the server was not opened before |
221 | |
222 | QVERIFY(server.serverName().isEmpty()); |
223 | QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode); |
224 | QVERIFY(!server.isListening()); |
225 | QCOMPARE(server.maxPendingConnections(), 30); |
226 | QCOMPARE(server.serverPort(), quint16(0)); |
227 | QCOMPARE(server.serverAddress(), QHostAddress()); |
228 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) |
229 | QCOMPARE(server.socketDescriptor(), -1); |
230 | #else // ### Qt 6: Remove leftovers |
231 | QCOMPARE(server.nativeDescriptor(), -1); |
232 | #endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) |
233 | QVERIFY(!server.hasPendingConnections()); |
234 | QVERIFY(!server.nextPendingConnection()); |
235 | QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal); |
236 | QVERIFY(server.errorString().isEmpty()); |
237 | #ifndef QT_NO_NETWORKPROXY |
238 | QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy); |
239 | #endif |
240 | #ifndef QT_NO_SSL |
241 | QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); |
242 | #endif |
243 | QCOMPARE(server.supportedVersions().count(), 1); |
244 | QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest); |
245 | QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13); |
246 | QCOMPARE(server.serverUrl(), QUrl()); |
247 | } |
248 | |
249 | { |
250 | #ifndef QT_NO_SSL |
251 | QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode); |
252 | QCOMPARE(sslServer.secureMode(), QWebSocketServer::SecureMode); |
253 | #endif |
254 | } |
255 | } |
256 | |
257 | void tst_QWebSocketServer::tst_settersAndGetters() |
258 | { |
259 | QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); |
260 | |
261 | server.setMaxPendingConnections(23); |
262 | QCOMPARE(server.maxPendingConnections(), 23); |
263 | server.setMaxPendingConnections(INT_MIN); |
264 | QCOMPARE(server.maxPendingConnections(), INT_MIN); |
265 | server.setMaxPendingConnections(INT_MAX); |
266 | QCOMPARE(server.maxPendingConnections(), INT_MAX); |
267 | |
268 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) |
269 | QVERIFY(!server.setSocketDescriptor(-2)); |
270 | QCOMPARE(server.socketDescriptor(), -1); |
271 | #else // ### Qt 6: Remove leftovers |
272 | QVERIFY(!server.setNativeDescriptor(-2)); |
273 | QCOMPARE(server.nativeDescriptor(), -1); |
274 | #endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) |
275 | |
276 | server.setServerName(QStringLiteral("Qt WebSocketServer" )); |
277 | QCOMPARE(server.serverName(), QStringLiteral("Qt WebSocketServer" )); |
278 | |
279 | #ifndef QT_NO_NETWORKPROXY |
280 | QNetworkProxy proxy(QNetworkProxy::Socks5Proxy); |
281 | server.setProxy(proxy); |
282 | QCOMPARE(server.proxy(), proxy); |
283 | #endif |
284 | #ifndef QT_NO_SSL |
285 | //cannot set an ssl configuration on a non secure server |
286 | QSslConfiguration sslConfiguration = QSslConfiguration::defaultConfiguration(); |
287 | sslConfiguration.setPeerVerifyDepth(sslConfiguration.peerVerifyDepth() + 1); |
288 | server.setSslConfiguration(sslConfiguration); |
289 | QVERIFY(server.sslConfiguration() != sslConfiguration); |
290 | QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); |
291 | |
292 | QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode); |
293 | sslServer.setSslConfiguration(sslConfiguration); |
294 | QCOMPARE(sslServer.sslConfiguration(), sslConfiguration); |
295 | QVERIFY(sslServer.sslConfiguration() != QSslConfiguration::defaultConfiguration()); |
296 | #endif |
297 | |
298 | server.setHandshakeTimeout(64); |
299 | QCOMPARE(server.handshakeTimeoutMS(), 64); |
300 | #if QT_HAS_INCLUDE(<chrono>) |
301 | auto expected = std::chrono::milliseconds(64); |
302 | QCOMPARE(server.handshakeTimeout(), expected); |
303 | |
304 | expected = std::chrono::milliseconds(242); |
305 | server.setHandshakeTimeout(expected); |
306 | QCOMPARE(server.handshakeTimeoutMS(), 242); |
307 | QCOMPARE(server.handshakeTimeout(), expected); |
308 | #endif |
309 | } |
310 | |
311 | void tst_QWebSocketServer::tst_listening() |
312 | { |
313 | //These listening tests are not too extensive, as the implementation of QWebSocketServer |
314 | //relies on QTcpServer |
315 | |
316 | QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); |
317 | |
318 | QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError))); |
319 | QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); |
320 | QSignalSpy serverErrorSpy(&server, |
321 | SIGNAL(serverError(QWebSocketProtocol::CloseCode))); |
322 | QSignalSpy corsAuthenticationSpy(&server, |
323 | SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); |
324 | QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); |
325 | #ifndef QT_NO_SSL |
326 | QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); |
327 | QSignalSpy (&server, SIGNAL(sslErrors(QList<QSslError>))); |
328 | #endif |
329 | |
330 | QVERIFY(server.listen()); //listen on all network interface, choose an appropriate port |
331 | QVERIFY(server.isListening()); |
332 | QCOMPARE(serverClosedSpy.count(), 0); |
333 | server.close(); |
334 | QTRY_COMPARE(serverClosedSpy.count(), 1); |
335 | QVERIFY(!server.isListening()); |
336 | QCOMPARE(serverErrorSpy.count(), 0); |
337 | |
338 | QVERIFY(!server.listen(QHostAddress(QStringLiteral("1.2.3.4" )), 0)); |
339 | QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeAbnormalDisconnection); |
340 | QCOMPARE(server.errorString().toLatin1().constData(), "The address is not available" ); |
341 | QVERIFY(!server.isListening()); |
342 | |
343 | QCOMPARE(serverAcceptErrorSpy.count(), 0); |
344 | QCOMPARE(serverConnectionSpy.count(), 0); |
345 | QCOMPARE(corsAuthenticationSpy.count(), 0); |
346 | #ifndef QT_NO_SSL |
347 | QCOMPARE(peerVerifyErrorSpy.count(), 0); |
348 | QCOMPARE(sslErrorsSpy.count(), 0); |
349 | #endif |
350 | QCOMPARE(serverErrorSpy.count(), 1); |
351 | QCOMPARE(serverClosedSpy.count(), 1); |
352 | } |
353 | |
354 | void tst_QWebSocketServer::tst_connectivity() |
355 | { |
356 | if (m_shouldSkipUnsupportedIpv6Test) |
357 | QSKIP("Syscalls needed for ipv6 sockoptions missing functionality" ); |
358 | |
359 | QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); |
360 | QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); |
361 | QSignalSpy serverErrorSpy(&server, |
362 | SIGNAL(serverError(QWebSocketProtocol::CloseCode))); |
363 | QSignalSpy corsAuthenticationSpy(&server, |
364 | SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); |
365 | QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); |
366 | #ifndef QT_NO_SSL |
367 | QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); |
368 | QSignalSpy (&server, SIGNAL(sslErrors(QList<QSslError>))); |
369 | #endif |
370 | QWebSocket socket; |
371 | QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); |
372 | |
373 | QVERIFY(server.listen()); |
374 | QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any)); |
375 | QCOMPARE(server.serverUrl(), QUrl(QStringLiteral("ws://" ) + QHostAddress(QHostAddress::LocalHost).toString() + |
376 | QStringLiteral(":" ).append(QString::number(server.serverPort())))); |
377 | |
378 | socket.open(url: server.serverUrl().toString()); |
379 | |
380 | QTRY_COMPARE(socketConnectedSpy.count(), 1); |
381 | QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); |
382 | QCOMPARE(serverConnectionSpy.count(), 1); |
383 | QCOMPARE(corsAuthenticationSpy.count(), 1); |
384 | |
385 | QCOMPARE(serverClosedSpy.count(), 0); |
386 | |
387 | server.close(); |
388 | |
389 | QTRY_COMPARE(serverClosedSpy.count(), 1); |
390 | #ifndef QT_NO_SSL |
391 | QCOMPARE(peerVerifyErrorSpy.count(), 0); |
392 | QCOMPARE(sslErrorsSpy.count(), 0); |
393 | #endif |
394 | QCOMPARE(serverErrorSpy.count(), 0); |
395 | } |
396 | |
397 | void tst_QWebSocketServer::tst_preSharedKey() |
398 | { |
399 | if (m_shouldSkipUnsupportedIpv6Test) |
400 | QSKIP("Syscalls needed for ipv6 sockoptions missing functionality" ); |
401 | |
402 | #ifndef QT_NO_OPENSSL |
403 | QWebSocketServer server(QString(), QWebSocketServer::SecureMode); |
404 | |
405 | bool cipherFound = false; |
406 | const QList<QSslCipher> supportedCiphers = QSslConfiguration::supportedCiphers(); |
407 | for (const QSslCipher &cipher : supportedCiphers) { |
408 | if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) { |
409 | cipherFound = true; |
410 | break; |
411 | } |
412 | } |
413 | |
414 | if (!cipherFound) |
415 | QSKIP("SSL implementation does not support the necessary cipher" ); |
416 | |
417 | QSslCipher cipher(PSK_CIPHER_WITHOUT_AUTH); |
418 | QList<QSslCipher> list; |
419 | list << cipher; |
420 | |
421 | QSslConfiguration config = QSslConfiguration::defaultConfiguration(); |
422 | if (QSslSocket::sslLibraryVersionNumber() >= 0x10101000L) |
423 | config.setProtocol(QSsl::TlsV1_2); // With TLS 1.3 there are some issues with PSK, force 1.2 |
424 | config.setCiphers(list); |
425 | config.setPeerVerifyMode(QSslSocket::VerifyNone); |
426 | config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT); |
427 | server.setSslConfiguration(config); |
428 | |
429 | PskProvider providerServer; |
430 | providerServer.m_server = true; |
431 | providerServer.m_identity = PSK_CLIENT_IDENTITY; |
432 | providerServer.m_psk = PSK_CLIENT_PRESHAREDKEY; |
433 | connect(sender: &server, signal: &QWebSocketServer::preSharedKeyAuthenticationRequired, receiver: &providerServer, slot: &PskProvider::providePsk); |
434 | |
435 | QSignalSpy serverPskRequiredSpy(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired); |
436 | QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection); |
437 | QSignalSpy serverErrorSpy(&server, |
438 | SIGNAL(serverError(QWebSocketProtocol::CloseCode))); |
439 | QSignalSpy serverClosedSpy(&server, &QWebSocketServer::closed); |
440 | QSignalSpy (&server, SIGNAL(sslErrors(QList<QSslError>))); |
441 | |
442 | QWebSocket socket; |
443 | QSslConfiguration socketConfig = QSslConfiguration::defaultConfiguration(); |
444 | if (QSslSocket::sslLibraryVersionNumber() >= 0x10101000L) |
445 | socketConfig.setProtocol(QSsl::TlsV1_2); // With TLS 1.3 there are some issues with PSK, force 1.2 |
446 | socketConfig.setPeerVerifyMode(QSslSocket::VerifyNone); |
447 | socketConfig.setCiphers(list); |
448 | socket.setSslConfiguration(socketConfig); |
449 | |
450 | PskProvider providerClient; |
451 | providerClient.m_identity = PSK_CLIENT_IDENTITY; |
452 | providerClient.m_psk = PSK_CLIENT_PRESHAREDKEY; |
453 | connect(sender: &socket, signal: &QWebSocket::preSharedKeyAuthenticationRequired, receiver: &providerClient, slot: &PskProvider::providePsk); |
454 | QSignalSpy socketPskRequiredSpy(&socket, &QWebSocket::preSharedKeyAuthenticationRequired); |
455 | QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); |
456 | |
457 | QVERIFY(server.listen()); |
458 | QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any)); |
459 | QCOMPARE(server.serverUrl(), QUrl(QString::asprintf("wss://%ls:%d" , |
460 | qUtf16Printable(QHostAddress(QHostAddress::LocalHost).toString()), server.serverPort()))); |
461 | |
462 | socket.open(url: server.serverUrl().toString()); |
463 | |
464 | QTRY_COMPARE(socketConnectedSpy.count(), 1); |
465 | QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); |
466 | QCOMPARE(serverConnectionSpy.count(), 1); |
467 | QCOMPARE(serverPskRequiredSpy.count(), 1); |
468 | QCOMPARE(socketPskRequiredSpy.count(), 1); |
469 | |
470 | QCOMPARE(serverClosedSpy.count(), 0); |
471 | |
472 | server.close(); |
473 | |
474 | QTRY_COMPARE(serverClosedSpy.count(), 1); |
475 | QCOMPARE(sslErrorsSpy.count(), 0); |
476 | QCOMPARE(serverErrorSpy.count(), 0); |
477 | #endif |
478 | } |
479 | |
480 | void tst_QWebSocketServer::tst_maxPendingConnections() |
481 | { |
482 | if (m_shouldSkipUnsupportedIpv6Test) |
483 | QSKIP("Syscalls needed for ipv6 sockoptions missing functionality" ); |
484 | |
485 | //tests if maximum connections are respected |
486 | //also checks if there are no side-effects like signals that are unexpectedly thrown |
487 | QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); |
488 | server.setMaxPendingConnections(2); |
489 | QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); |
490 | QSignalSpy serverErrorSpy(&server, |
491 | SIGNAL(serverError(QWebSocketProtocol::CloseCode))); |
492 | QSignalSpy corsAuthenticationSpy(&server, |
493 | SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); |
494 | QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); |
495 | #ifndef QT_NO_SSL |
496 | QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); |
497 | QSignalSpy (&server, SIGNAL(sslErrors(QList<QSslError>))); |
498 | #endif |
499 | QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError))); |
500 | |
501 | QWebSocket socket1; |
502 | QWebSocket socket2; |
503 | QWebSocket socket3; |
504 | |
505 | QSignalSpy socket1ConnectedSpy(&socket1, SIGNAL(connected())); |
506 | QSignalSpy socket2ConnectedSpy(&socket2, SIGNAL(connected())); |
507 | QSignalSpy socket3ConnectedSpy(&socket3, SIGNAL(connected())); |
508 | |
509 | QVERIFY(server.listen()); |
510 | |
511 | socket1.open(url: server.serverUrl().toString()); |
512 | |
513 | QTRY_COMPARE(socket1ConnectedSpy.count(), 1); |
514 | QCOMPARE(socket1.state(), QAbstractSocket::ConnectedState); |
515 | QCOMPARE(serverConnectionSpy.count(), 1); |
516 | QCOMPARE(corsAuthenticationSpy.count(), 1); |
517 | socket2.open(url: server.serverUrl().toString()); |
518 | QTRY_COMPARE(socket2ConnectedSpy.count(), 1); |
519 | QCOMPARE(socket2.state(), QAbstractSocket::ConnectedState); |
520 | QCOMPARE(serverConnectionSpy.count(), 2); |
521 | QCOMPARE(corsAuthenticationSpy.count(), 2); |
522 | socket3.open(url: server.serverUrl().toString()); |
523 | QVERIFY(!socket3ConnectedSpy.wait(250)); |
524 | QCOMPARE(socket3ConnectedSpy.count(), 0); |
525 | QCOMPARE(socket3.state(), QAbstractSocket::UnconnectedState); |
526 | QCOMPARE(serverConnectionSpy.count(), 2); |
527 | QCOMPARE(corsAuthenticationSpy.count(), 2); |
528 | |
529 | QVERIFY(server.hasPendingConnections()); |
530 | QWebSocket *pSocket = server.nextPendingConnection(); |
531 | QVERIFY(pSocket); |
532 | delete pSocket; |
533 | QVERIFY(server.hasPendingConnections()); |
534 | pSocket = server.nextPendingConnection(); |
535 | QVERIFY(pSocket); |
536 | delete pSocket; |
537 | QVERIFY(!server.hasPendingConnections()); |
538 | QVERIFY(!server.nextPendingConnection()); |
539 | |
540 | //will resolve in another commit |
541 | #ifndef Q_OS_WIN |
542 | QCOMPARE(serverErrorSpy.count(), 1); |
543 | QCOMPARE(serverErrorSpy.at(0).at(0).value<QWebSocketProtocol::CloseCode>(), |
544 | QWebSocketProtocol::CloseCodeAbnormalDisconnection); |
545 | #endif |
546 | QCOMPARE(serverClosedSpy.count(), 0); |
547 | |
548 | server.close(); |
549 | |
550 | QTRY_COMPARE(serverClosedSpy.count(), 1); |
551 | #ifndef QT_NO_SSL |
552 | QCOMPARE(peerVerifyErrorSpy.count(), 0); |
553 | QCOMPARE(sslErrorsSpy.count(), 0); |
554 | #endif |
555 | QCOMPARE(serverAcceptErrorSpy.count(), 0); |
556 | } |
557 | |
558 | void tst_QWebSocketServer::tst_serverDestroyedWhileSocketConnected() |
559 | { |
560 | if (m_shouldSkipUnsupportedIpv6Test) |
561 | QSKIP("Syscalls needed for ipv6 sockoptions missing functionality" ); |
562 | |
563 | QWebSocketServer * server = new QWebSocketServer(QString(), QWebSocketServer::NonSecureMode); |
564 | QSignalSpy serverConnectionSpy(server, SIGNAL(newConnection())); |
565 | QSignalSpy corsAuthenticationSpy(server, |
566 | SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); |
567 | QSignalSpy serverClosedSpy(server, SIGNAL(closed())); |
568 | |
569 | QWebSocket socket; |
570 | QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); |
571 | QSignalSpy socketDisconnectedSpy(&socket, SIGNAL(disconnected())); |
572 | |
573 | QVERIFY(server->listen()); |
574 | QCOMPARE(server->serverAddress(), QHostAddress(QHostAddress::Any)); |
575 | QCOMPARE(server->serverUrl(), QUrl(QStringLiteral("ws://" ) + QHostAddress(QHostAddress::LocalHost).toString() + |
576 | QStringLiteral(":" ).append(QString::number(server->serverPort())))); |
577 | |
578 | socket.open(url: server->serverUrl().toString()); |
579 | |
580 | QTRY_COMPARE(socketConnectedSpy.count(), 1); |
581 | QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); |
582 | QCOMPARE(serverConnectionSpy.count(), 1); |
583 | QCOMPARE(corsAuthenticationSpy.count(), 1); |
584 | |
585 | QCOMPARE(serverClosedSpy.count(), 0); |
586 | |
587 | delete server; |
588 | |
589 | QTRY_COMPARE(socketDisconnectedSpy.count(), 1); |
590 | } |
591 | |
592 | #ifndef QT_NO_SSL |
593 | static void setupSecureServer(QWebSocketServer *secureServer) |
594 | { |
595 | QSslConfiguration sslConfiguration; |
596 | QFile certFile(QStringLiteral(":/localhost.cert" )); |
597 | QFile keyFile(QStringLiteral(":/localhost.key" )); |
598 | QVERIFY(certFile.open(QIODevice::ReadOnly)); |
599 | QVERIFY(keyFile.open(QIODevice::ReadOnly)); |
600 | QSslCertificate certificate(&certFile, QSsl::Pem); |
601 | QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem); |
602 | certFile.close(); |
603 | keyFile.close(); |
604 | sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); |
605 | sslConfiguration.setLocalCertificate(certificate); |
606 | sslConfiguration.setPrivateKey(sslKey); |
607 | secureServer->setSslConfiguration(sslConfiguration); |
608 | } |
609 | #endif |
610 | |
611 | void tst_QWebSocketServer::tst_scheme() |
612 | { |
613 | if (m_shouldSkipUnsupportedIpv6Test) |
614 | QSKIP("Syscalls needed for ipv6 sockoptions missing functionality" ); |
615 | |
616 | QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode); |
617 | QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection())); |
618 | |
619 | QVERIFY(plainServer.listen()); |
620 | |
621 | QWebSocket plainSocket; |
622 | plainSocket.open(url: plainServer.serverUrl().toString()); |
623 | |
624 | QTRY_COMPARE(plainServerConnectionSpy.count(), 1); |
625 | QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection()); |
626 | QVERIFY(!plainServerSocket.isNull()); |
627 | QCOMPARE(plainServerSocket->requestUrl().scheme(), QStringLiteral("ws" )); |
628 | plainServer.close(); |
629 | |
630 | #ifndef QT_NO_SSL |
631 | QWebSocketServer secureServer(QString(), QWebSocketServer::SecureMode); |
632 | setupSecureServer(&secureServer); |
633 | if (QTest::currentTestFailed()) |
634 | return; |
635 | QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection())); |
636 | |
637 | QVERIFY(secureServer.listen()); |
638 | |
639 | QSslCipher sessionCipher; |
640 | QWebSocket secureSocket; |
641 | connect(sender: &secureSocket, signal: &QWebSocket::sslErrors, |
642 | context: &secureSocket, slot: [&] { |
643 | secureSocket.ignoreSslErrors(); |
644 | sessionCipher = secureSocket.sslConfiguration().sessionCipher(); |
645 | }); |
646 | secureSocket.open(url: secureServer.serverUrl().toString()); |
647 | |
648 | QTRY_COMPARE(secureServerConnectionSpy.count(), 1); |
649 | QScopedPointer<QWebSocket> secureServerSocket(secureServer.nextPendingConnection()); |
650 | QVERIFY(!secureServerSocket.isNull()); |
651 | QCOMPARE(secureServerSocket->requestUrl().scheme(), QStringLiteral("wss" )); |
652 | secureServer.close(); |
653 | QVERIFY(!sessionCipher.isNull()); |
654 | #endif |
655 | } |
656 | |
657 | void tst_QWebSocketServer::tst_handleConnection() |
658 | { |
659 | QWebSocketServer wsServer(QString(), QWebSocketServer::NonSecureMode); |
660 | QSignalSpy wsServerConnectionSpy(&wsServer, &QWebSocketServer::newConnection); |
661 | |
662 | QTcpServer tcpServer; |
663 | connect(sender: &tcpServer, signal: &QTcpServer::newConnection, |
664 | slot: [&tcpServer, &wsServer]() { |
665 | wsServer.handleConnection(socket: tcpServer.nextPendingConnection()); |
666 | }); |
667 | QVERIFY(tcpServer.listen()); |
668 | |
669 | QWebSocket webSocket; |
670 | QSignalSpy wsConnectedSpy(&webSocket, &QWebSocket::connected); |
671 | webSocket.open(QStringLiteral("ws://localhost:%1" ).arg(a: tcpServer.serverPort())); |
672 | QTRY_COMPARE(wsConnectedSpy.count(), 1); |
673 | |
674 | QTRY_COMPARE(wsServerConnectionSpy.count(), 1); |
675 | |
676 | QScopedPointer<QWebSocket> webServerSocket(wsServer.nextPendingConnection()); |
677 | QVERIFY(!webServerSocket.isNull()); |
678 | |
679 | QSignalSpy wsMessageReceivedSpy(webServerSocket.data(), &QWebSocket::textMessageReceived); |
680 | webSocket.sendTextMessage(message: "dummy" ); |
681 | |
682 | QTRY_COMPARE(wsMessageReceivedSpy.count(), 1); |
683 | QList<QVariant> arguments = wsMessageReceivedSpy.takeFirst(); |
684 | QCOMPARE(arguments.first().toString(), QString("dummy" )); |
685 | |
686 | QSignalSpy clientMessageReceivedSpy(&webSocket, &QWebSocket::textMessageReceived); |
687 | webServerSocket->sendTextMessage(message: "hello" ); |
688 | QVERIFY(webServerSocket->bytesToWrite() > 5); // 5 + a few extra bytes for header |
689 | QTRY_COMPARE(webServerSocket->bytesToWrite(), 0); |
690 | QTRY_COMPARE(clientMessageReceivedSpy.count(), 1); |
691 | arguments = clientMessageReceivedSpy.takeFirst(); |
692 | QCOMPARE(arguments.first().toString(), QString("hello" )); |
693 | } |
694 | |
695 | struct SocketSpy { |
696 | QTcpSocket *socket; |
697 | QSignalSpy *disconnectSpy; |
698 | ~SocketSpy() { |
699 | delete socket; |
700 | delete disconnectSpy; |
701 | } |
702 | }; |
703 | |
704 | static void openManyConnections(QList<SocketSpy *> *sockets, quint16 port, int numConnections) |
705 | { |
706 | for (int i = 0; i < numConnections; i++) { |
707 | QTcpSocket *c = new QTcpSocket; |
708 | QSignalSpy *spy = new QSignalSpy(c, &QTcpSocket::disconnected); |
709 | |
710 | c->connectToHost(hostName: "127.0.0.1" , port); |
711 | |
712 | sockets->append(t: new SocketSpy{.socket: c, .disconnectSpy: spy}); |
713 | } |
714 | } |
715 | |
716 | // Sum the counts together, for better output on failure (e.g. "FAIL: Actual: 49, Expected: 50") |
717 | static int sumSocketSpyCount(const QList<SocketSpy *> &sockets) |
718 | { |
719 | return std::accumulate(first: sockets.cbegin(), last: sockets.cend(), init: 0, binary_op: [](int c, SocketSpy *s) { |
720 | return c + s->disconnectSpy->count(); |
721 | }); |
722 | } |
723 | |
724 | void tst_QWebSocketServer::tst_handshakeTimeout() |
725 | { |
726 | { // No Timeout |
727 | QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode); |
728 | plainServer.setHandshakeTimeout(-1); |
729 | QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection())); |
730 | |
731 | QVERIFY(plainServer.listen()); |
732 | |
733 | QWebSocket socket; |
734 | socket.open(url: plainServer.serverUrl().toString()); |
735 | |
736 | QTRY_COMPARE(plainServerConnectionSpy.count(), 1); |
737 | QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection()); |
738 | QVERIFY(!plainServerSocket.isNull()); |
739 | |
740 | plainServer.close(); |
741 | } |
742 | |
743 | { // Unencrypted |
744 | QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode); |
745 | plainServer.setHandshakeTimeout(500); |
746 | QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection())); |
747 | |
748 | QVERIFY(plainServer.listen()); |
749 | |
750 | /* QTcpServer has a default of 30 pending connections. The test checks |
751 | * whether, when that list is full, the connections are dropped after |
752 | * a timeout and later pending connections are processed. */ |
753 | const int numConnections = 50; |
754 | QList<SocketSpy *> sockets; |
755 | auto cleaner = qScopeGuard(f: [&sockets]() { qDeleteAll(c: sockets); }); |
756 | openManyConnections(sockets: &sockets, port: plainServer.serverPort(), numConnections); |
757 | |
758 | QCoreApplication::processEvents(); |
759 | |
760 | /* We have 50 plain TCP connections open, that are not proper websockets. */ |
761 | QCOMPARE(plainServerConnectionSpy.count(), 0); |
762 | |
763 | QWebSocket socket; |
764 | socket.open(url: plainServer.serverUrl().toString()); |
765 | |
766 | /* Check that a real websocket will be processed after some non-websocket |
767 | * TCP connections timeout. */ |
768 | QTRY_COMPARE(plainServerConnectionSpy.count(), 1); |
769 | QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection()); |
770 | QVERIFY(!plainServerSocket.isNull()); |
771 | |
772 | /* Check that all non websocket connections eventually timeout. */ |
773 | QTRY_COMPARE(sumSocketSpyCount(sockets), numConnections); |
774 | |
775 | plainServer.close(); |
776 | } |
777 | |
778 | #if QT_CONFIG(ssl) |
779 | { // Encrypted |
780 | QWebSocketServer secureServer(QString(), QWebSocketServer::SecureMode); |
781 | setupSecureServer(&secureServer); |
782 | if (QTest::currentTestFailed()) |
783 | return; |
784 | secureServer.setHandshakeTimeout(2000); |
785 | |
786 | QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection())); |
787 | |
788 | QVERIFY(secureServer.listen()); |
789 | |
790 | const int numConnections = 50; |
791 | QList<SocketSpy *> sockets; |
792 | auto cleaner = qScopeGuard(f: [&sockets]() { qDeleteAll(c: sockets); }); |
793 | openManyConnections(sockets: &sockets, port: secureServer.serverPort(), numConnections); |
794 | |
795 | QCoreApplication::processEvents(); |
796 | QCOMPARE(secureServerConnectionSpy.count(), 0); |
797 | |
798 | QWebSocket secureSocket; |
799 | connect(sender: &secureSocket, signal: QOverload<QAbstractSocket::SocketError>::of(ptr: &QWebSocket::error), |
800 | slot: [](QAbstractSocket::SocketError error) { |
801 | // This shouldn't print but it's useful for debugging when/if it does. |
802 | qDebug() << "Error occurred in the client:" << error; |
803 | }); |
804 | QSslConfiguration config = secureSocket.sslConfiguration(); |
805 | config.setPeerVerifyMode(QSslSocket::VerifyNone); |
806 | secureSocket.setSslConfiguration(config); |
807 | |
808 | secureSocket.open(url: secureServer.serverUrl().toString()); |
809 | |
810 | QTRY_COMPARE(secureServerConnectionSpy.count(), 1); |
811 | QScopedPointer<QWebSocket> serverSocket(secureServer.nextPendingConnection()); |
812 | QVERIFY(!serverSocket.isNull()); |
813 | |
814 | QTRY_COMPARE(sumSocketSpyCount(sockets), numConnections); |
815 | |
816 | secureServer.close(); |
817 | } |
818 | #endif |
819 | |
820 | { // Ensure properly handshaked connections are not timed out |
821 | QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode); |
822 | plainServer.setHandshakeTimeout(250); |
823 | QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection())); |
824 | |
825 | QVERIFY(plainServer.listen()); |
826 | |
827 | QWebSocket socket; |
828 | QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); |
829 | QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected); |
830 | socket.open(url: plainServer.serverUrl().toString()); |
831 | |
832 | QTRY_COMPARE(plainServerConnectionSpy.count(), 1); |
833 | QTRY_COMPARE(socketConnectedSpy.count(), 1); |
834 | |
835 | QEventLoop loop; |
836 | QTimer::singleShot(interval: 500, receiver: &loop, slot: &QEventLoop::quit); |
837 | loop.exec(); |
838 | |
839 | QCOMPARE(socketDisconnectedSpy.count(), 0); |
840 | } |
841 | } |
842 | |
843 | void tst_QWebSocketServer::multipleFrames() |
844 | { |
845 | QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); |
846 | QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection); |
847 | QVERIFY(server.listen()); |
848 | |
849 | QWebSocket socket; |
850 | QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); |
851 | QSignalSpy messageReceivedSpy(&socket, &QWebSocket::binaryMessageReceived); |
852 | socket.open(url: server.serverUrl().toString()); |
853 | |
854 | QVERIFY(serverConnectionSpy.wait()); |
855 | QVERIFY(socketConnectedSpy.wait()); |
856 | |
857 | auto serverSocket = std::unique_ptr<QWebSocket>(server.nextPendingConnection()); |
858 | QVERIFY(serverSocket); |
859 | for (int i = 0; i < 10; i++) |
860 | serverSocket->sendBinaryMessage(data: QByteArray("abc" )); |
861 | if (serverSocket->bytesToWrite()) |
862 | QVERIFY(serverSocket->flush()); |
863 | |
864 | QVERIFY(messageReceivedSpy.wait()); |
865 | // Since there's no guarantee the operating system will fit all 10 websocket frames into 1 tcp |
866 | // frame, let's just assume it will do at least 2. EXCEPT_FAIL any which doesn't merge any. |
867 | QVERIFY2(messageReceivedSpy.count() > 1, "Received only 1 message in the TCP frame!" ); |
868 | } |
869 | |
870 | QTEST_MAIN(tst_QWebSocketServer) |
871 | |
872 | #include "tst_qwebsocketserver.moc" |
873 | |