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 <QtWebSockets/QWebSocket>
31#include <QtWebSockets/QWebSocketServer>
32#include <QtWebSockets/qwebsocketprotocol.h>
33
34QT_USE_NAMESPACE
35
36Q_DECLARE_METATYPE(QWebSocketProtocol::Version)
37
38class EchoServer : public QObject
39{
40 Q_OBJECT
41public:
42 explicit EchoServer(QObject *parent = nullptr,
43 quint64 maxAllowedIncomingMessageSize = QWebSocket::maxIncomingMessageSize(),
44 quint64 maxAllowedIncomingFrameSize = QWebSocket::maxIncomingFrameSize());
45 ~EchoServer();
46
47 QHostAddress hostAddress() const { return m_pWebSocketServer->serverAddress(); }
48 quint16 port() const { return m_pWebSocketServer->serverPort(); }
49
50Q_SIGNALS:
51 void newConnection(QUrl requestUrl);
52 void newConnection(QNetworkRequest request);
53
54private Q_SLOTS:
55 void onNewConnection();
56 void processTextMessage(QString message);
57 void processBinaryMessage(QByteArray message);
58 void socketDisconnected();
59
60private:
61 QWebSocketServer *m_pWebSocketServer;
62 quint64 m_maxAllowedIncomingMessageSize;
63 quint64 m_maxAllowedIncomingFrameSize;
64 QList<QWebSocket *> m_clients;
65};
66
67EchoServer::EchoServer(QObject *parent, quint64 maxAllowedIncomingMessageSize, quint64 maxAllowedIncomingFrameSize) :
68 QObject(parent),
69 m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Echo Server"),
70 QWebSocketServer::NonSecureMode, this)),
71 m_maxAllowedIncomingMessageSize(maxAllowedIncomingMessageSize),
72 m_maxAllowedIncomingFrameSize(maxAllowedIncomingFrameSize),
73 m_clients()
74{
75 if (m_pWebSocketServer->listen(address: QHostAddress(QStringLiteral("127.0.0.1")))) {
76 connect(sender: m_pWebSocketServer, SIGNAL(newConnection()),
77 receiver: this, SLOT(onNewConnection()));
78 }
79}
80
81EchoServer::~EchoServer()
82{
83 m_pWebSocketServer->close();
84 qDeleteAll(begin: m_clients.begin(), end: m_clients.end());
85}
86
87void EchoServer::onNewConnection()
88{
89 QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection();
90
91 pSocket->setMaxAllowedIncomingFrameSize(m_maxAllowedIncomingFrameSize);
92 pSocket->setMaxAllowedIncomingMessageSize(m_maxAllowedIncomingMessageSize);
93
94 Q_EMIT newConnection(requestUrl: pSocket->requestUrl());
95 Q_EMIT newConnection(request: pSocket->request());
96
97 connect(sender: pSocket, SIGNAL(textMessageReceived(QString)), receiver: this, SLOT(processTextMessage(QString)));
98 connect(sender: pSocket, SIGNAL(binaryMessageReceived(QByteArray)), receiver: this, SLOT(processBinaryMessage(QByteArray)));
99 connect(sender: pSocket, SIGNAL(disconnected()), receiver: this, SLOT(socketDisconnected()));
100
101 m_clients << pSocket;
102}
103
104void EchoServer::processTextMessage(QString message)
105{
106 QWebSocket *pClient = qobject_cast<QWebSocket *>(object: sender());
107 if (pClient) {
108 pClient->sendTextMessage(message);
109 }
110}
111
112void EchoServer::processBinaryMessage(QByteArray message)
113{
114 QWebSocket *pClient = qobject_cast<QWebSocket *>(object: sender());
115 if (pClient) {
116 pClient->sendBinaryMessage(data: message);
117 }
118}
119
120void EchoServer::socketDisconnected()
121{
122 QWebSocket *pClient = qobject_cast<QWebSocket *>(object: sender());
123 if (pClient) {
124 m_clients.removeAll(t: pClient);
125 pClient->deleteLater();
126 }
127}
128
129class tst_QWebSocket : public QObject
130{
131 Q_OBJECT
132
133public:
134 tst_QWebSocket();
135
136private Q_SLOTS:
137 void init();
138 void initTestCase();
139 void cleanupTestCase();
140 void tst_initialisation_data();
141 void tst_initialisation();
142 void tst_settersAndGetters();
143 void tst_invalidOpen_data();
144 void tst_invalidOpen();
145 void tst_invalidOrigin();
146 void tst_sendTextMessage();
147 void tst_sendBinaryMessage();
148 void tst_errorString();
149 void tst_openRequest();
150 void tst_moveToThread();
151 void tst_moveToThreadNoWarning();
152#ifndef QT_NO_NETWORKPROXY
153 void tst_setProxy();
154#endif
155 void overlongCloseReason();
156 void incomingMessageTooLong();
157 void incomingFrameTooLong();
158 void testingFrameAndMessageSizeApi();
159};
160
161tst_QWebSocket::tst_QWebSocket()
162{
163}
164
165void tst_QWebSocket::init()
166{
167 qRegisterMetaType<QWebSocketProtocol::Version>(typeName: "QWebSocketProtocol::Version");
168}
169
170void tst_QWebSocket::initTestCase()
171{
172}
173
174void tst_QWebSocket::cleanupTestCase()
175{
176}
177
178void tst_QWebSocket::tst_initialisation_data()
179{
180 QTest::addColumn<QString>(name: "origin");
181 QTest::addColumn<QString>(name: "expectedOrigin");
182 QTest::addColumn<QWebSocketProtocol::Version>(name: "version");
183 QTest::addColumn<QWebSocketProtocol::Version>(name: "expectedVersion");
184
185 QTest::newRow(dataTag: "Default origin and version")
186 << QString() << QString()
187 << QWebSocketProtocol::VersionUnknown << QWebSocketProtocol::VersionLatest;
188 QTest::newRow(dataTag: "Specific origin and default version")
189 << QStringLiteral("qt-project.org") << QStringLiteral("qt-project.org")
190 << QWebSocketProtocol::VersionUnknown << QWebSocketProtocol::VersionLatest;
191 QTest::newRow(dataTag: "Specific origin and specific version")
192 << QStringLiteral("qt-project.org") << QStringLiteral("qt-project.org")
193 << QWebSocketProtocol::Version7 << QWebSocketProtocol::Version7;
194}
195
196void tst_QWebSocket::tst_initialisation()
197{
198 QFETCH(QString, origin);
199 QFETCH(QString, expectedOrigin);
200 QFETCH(QWebSocketProtocol::Version, version);
201 QFETCH(QWebSocketProtocol::Version, expectedVersion);
202
203 QScopedPointer<QWebSocket> socket;
204
205 if (origin.isEmpty() && (version == QWebSocketProtocol::VersionUnknown))
206 socket.reset(other: new QWebSocket);
207 else if (!origin.isEmpty() && (version == QWebSocketProtocol::VersionUnknown))
208 socket.reset(other: new QWebSocket(origin));
209 else
210 socket.reset(other: new QWebSocket(origin, version));
211
212 QCOMPARE(socket->origin(), expectedOrigin);
213 QCOMPARE(socket->version(), expectedVersion);
214 QCOMPARE(socket->error(), QAbstractSocket::UnknownSocketError);
215 QVERIFY(socket->errorString().isEmpty());
216 QVERIFY(!socket->isValid());
217 QVERIFY(socket->localAddress().isNull());
218 QCOMPARE(socket->localPort(), quint16(0));
219 QCOMPARE(socket->pauseMode(), QAbstractSocket::PauseNever);
220 QVERIFY(socket->peerAddress().isNull());
221 QCOMPARE(socket->peerPort(), quint16(0));
222 QVERIFY(socket->peerName().isEmpty());
223 QCOMPARE(socket->state(), QAbstractSocket::UnconnectedState);
224 QCOMPARE(socket->readBufferSize(), 0);
225 QVERIFY(socket->resourceName().isEmpty());
226 QVERIFY(!socket->requestUrl().isValid());
227 QCOMPARE(socket->closeCode(), QWebSocketProtocol::CloseCodeNormal);
228 QVERIFY(socket->closeReason().isEmpty());
229 QVERIFY(socket->flush());
230 QCOMPARE(socket->sendTextMessage(QStringLiteral("A text message")), 0);
231 QCOMPARE(socket->sendBinaryMessage(QByteArrayLiteral("A binary message")), 0);
232}
233
234void tst_QWebSocket::tst_settersAndGetters()
235{
236 QWebSocket socket;
237
238 socket.setPauseMode(QAbstractSocket::PauseNever);
239 QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseNever);
240 socket.setPauseMode(QAbstractSocket::PauseOnSslErrors);
241 QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseOnSslErrors);
242
243 socket.setReadBufferSize(0);
244 QCOMPARE(socket.readBufferSize(), 0);
245 socket.setReadBufferSize(128);
246 QCOMPARE(socket.readBufferSize(), 128);
247 socket.setReadBufferSize(-1);
248 QCOMPARE(socket.readBufferSize(), -1);
249}
250
251void tst_QWebSocket::tst_invalidOpen_data()
252{
253 QTest::addColumn<QString>(name: "url");
254 QTest::addColumn<QString>(name: "expectedUrl");
255 QTest::addColumn<QString>(name: "expectedPeerName");
256 QTest::addColumn<QString>(name: "expectedResourceName");
257 QTest::addColumn<QAbstractSocket::SocketState>(name: "stateAfterOpenCall");
258 QTest::addColumn<int>(name: "disconnectedCount");
259 QTest::addColumn<int>(name: "stateChangedCount");
260
261 QTest::newRow(dataTag: "Illegal local address")
262 << QStringLiteral("ws://127.0.0.1:1/") << QStringLiteral("ws://127.0.0.1:1/")
263 << QStringLiteral("127.0.0.1")
264 << QStringLiteral("/") << QAbstractSocket::ConnectingState
265 << 1
266 << 2; //going from connecting to disconnected
267 QTest::newRow(dataTag: "URL containing new line in the hostname")
268 << QStringLiteral("ws://myhacky\r\nserver/") << QString()
269 << QString()
270 << QString() << QAbstractSocket::UnconnectedState
271 << 0 << 0;
272 QTest::newRow(dataTag: "URL containing new line in the resource name")
273 << QStringLiteral("ws://127.0.0.1:1/tricky\r\npath") << QString()
274 << QString()
275 << QString()
276 << QAbstractSocket::UnconnectedState
277 << 0 << 0;
278}
279
280void tst_QWebSocket::tst_invalidOpen()
281{
282 QFETCH(QString, url);
283 QFETCH(QString, expectedUrl);
284 QFETCH(QString, expectedPeerName);
285 QFETCH(QString, expectedResourceName);
286 QFETCH(QAbstractSocket::SocketState, stateAfterOpenCall);
287 QFETCH(int, disconnectedCount);
288 QFETCH(int, stateChangedCount);
289 QWebSocket socket;
290 QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError)));
291 QSignalSpy aboutToCloseSpy(&socket, SIGNAL(aboutToClose()));
292 QSignalSpy connectedSpy(&socket, SIGNAL(connected()));
293 QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected()));
294 QSignalSpy stateChangedSpy(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)));
295 QSignalSpy readChannelFinishedSpy(&socket, SIGNAL(readChannelFinished()));
296 QSignalSpy textFrameReceivedSpy(&socket, SIGNAL(textFrameReceived(QString,bool)));
297 QSignalSpy binaryFrameReceivedSpy(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool)));
298 QSignalSpy textMessageReceivedSpy(&socket, SIGNAL(textMessageReceived(QString)));
299 QSignalSpy binaryMessageReceivedSpy(&socket, SIGNAL(binaryMessageReceived(QByteArray)));
300 QSignalSpy pongSpy(&socket, SIGNAL(pong(quint64,QByteArray)));
301 QSignalSpy bytesWrittenSpy(&socket, SIGNAL(bytesWritten(qint64)));
302
303 socket.open(url: QUrl(url));
304
305 QVERIFY(socket.origin().isEmpty());
306 QCOMPARE(socket.version(), QWebSocketProtocol::VersionLatest);
307 //at this point the socket is in a connecting state
308 //so, there should no error at this point
309 QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError);
310 QVERIFY(!socket.errorString().isEmpty());
311 QVERIFY(!socket.isValid());
312 QVERIFY(socket.localAddress().isNull());
313 QCOMPARE(socket.localPort(), quint16(0));
314 QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseNever);
315 QVERIFY(socket.peerAddress().isNull());
316 QCOMPARE(socket.peerPort(), quint16(0));
317 QCOMPARE(socket.peerName(), expectedPeerName);
318 QCOMPARE(socket.state(), stateAfterOpenCall);
319 QCOMPARE(socket.readBufferSize(), 0);
320 QCOMPARE(socket.resourceName(), expectedResourceName);
321 QCOMPARE(socket.requestUrl().toString(), expectedUrl);
322 QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeNormal);
323 QVERIFY(socket.closeReason().isEmpty());
324 QCOMPARE(socket.sendTextMessage(QStringLiteral("A text message")), 0);
325 QCOMPARE(socket.sendBinaryMessage(QByteArrayLiteral("A text message")), 0);
326
327 if (errorSpy.count() == 0)
328 QVERIFY(errorSpy.wait());
329 QCOMPARE(errorSpy.count(), 1);
330 QList<QVariant> arguments = errorSpy.takeFirst();
331 QAbstractSocket::SocketError socketError =
332 qvariant_cast<QAbstractSocket::SocketError>(v: arguments.at(i: 0));
333 QCOMPARE(socketError, QAbstractSocket::ConnectionRefusedError);
334 QCOMPARE(aboutToCloseSpy.count(), 0);
335 QCOMPARE(connectedSpy.count(), 0);
336 QCOMPARE(disconnectedSpy.count(), disconnectedCount);
337 QCOMPARE(stateChangedSpy.count(), stateChangedCount);
338 if (stateChangedCount == 2) {
339 arguments = stateChangedSpy.takeFirst();
340 QAbstractSocket::SocketState socketState =
341 qvariant_cast<QAbstractSocket::SocketState>(v: arguments.at(i: 0));
342 arguments = stateChangedSpy.takeFirst();
343 socketState = qvariant_cast<QAbstractSocket::SocketState>(v: arguments.at(i: 0));
344 QCOMPARE(socketState, QAbstractSocket::UnconnectedState);
345 }
346 QCOMPARE(readChannelFinishedSpy.count(), 0);
347 QCOMPARE(textFrameReceivedSpy.count(), 0);
348 QCOMPARE(binaryFrameReceivedSpy.count(), 0);
349 QCOMPARE(textMessageReceivedSpy.count(), 0);
350 QCOMPARE(binaryMessageReceivedSpy.count(), 0);
351 QCOMPARE(pongSpy.count(), 0);
352 QCOMPARE(bytesWrittenSpy.count(), 0);
353}
354
355void tst_QWebSocket::tst_invalidOrigin()
356{
357 QWebSocket socket(QStringLiteral("My server\r\nin the wild."));
358
359 QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError)));
360 QSignalSpy aboutToCloseSpy(&socket, SIGNAL(aboutToClose()));
361 QSignalSpy connectedSpy(&socket, SIGNAL(connected()));
362 QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected()));
363 QSignalSpy stateChangedSpy(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)));
364 QSignalSpy readChannelFinishedSpy(&socket, SIGNAL(readChannelFinished()));
365 QSignalSpy textFrameReceivedSpy(&socket, SIGNAL(textFrameReceived(QString,bool)));
366 QSignalSpy binaryFrameReceivedSpy(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool)));
367 QSignalSpy textMessageReceivedSpy(&socket, SIGNAL(textMessageReceived(QString)));
368 QSignalSpy binaryMessageReceivedSpy(&socket, SIGNAL(binaryMessageReceived(QByteArray)));
369 QSignalSpy pongSpy(&socket, SIGNAL(pong(quint64,QByteArray)));
370 QSignalSpy bytesWrittenSpy(&socket, SIGNAL(bytesWritten(qint64)));
371
372 socket.open(url: QUrl(QStringLiteral("ws://127.0.0.1:1/")));
373
374 //at this point the socket is in a connecting state
375 //so, there should no error at this point
376 QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError);
377 QVERIFY(!socket.errorString().isEmpty());
378 QVERIFY(!socket.isValid());
379 QVERIFY(socket.localAddress().isNull());
380 QCOMPARE(socket.localPort(), quint16(0));
381 QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseNever);
382 QVERIFY(socket.peerAddress().isNull());
383 QCOMPARE(socket.peerPort(), quint16(0));
384 QCOMPARE(socket.peerName(), QStringLiteral("127.0.0.1"));
385 QCOMPARE(socket.state(), QAbstractSocket::ConnectingState);
386 QCOMPARE(socket.readBufferSize(), 0);
387 QCOMPARE(socket.resourceName(), QStringLiteral("/"));
388 QCOMPARE(socket.requestUrl(), QUrl(QStringLiteral("ws://127.0.0.1:1/")));
389 QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeNormal);
390
391 QVERIFY(errorSpy.wait());
392
393 QCOMPARE(errorSpy.count(), 1);
394 QList<QVariant> arguments = errorSpy.takeFirst();
395 QAbstractSocket::SocketError socketError =
396 qvariant_cast<QAbstractSocket::SocketError>(v: arguments.at(i: 0));
397 QCOMPARE(socketError, QAbstractSocket::ConnectionRefusedError);
398 QCOMPARE(aboutToCloseSpy.count(), 0);
399 QCOMPARE(connectedSpy.count(), 0);
400 QCOMPARE(disconnectedSpy.count(), 1);
401 QCOMPARE(stateChangedSpy.count(), 2); //connectingstate, unconnectedstate
402 arguments = stateChangedSpy.takeFirst();
403 QAbstractSocket::SocketState socketState =
404 qvariant_cast<QAbstractSocket::SocketState>(v: arguments.at(i: 0));
405 arguments = stateChangedSpy.takeFirst();
406 socketState = qvariant_cast<QAbstractSocket::SocketState>(v: arguments.at(i: 0));
407 QCOMPARE(socketState, QAbstractSocket::UnconnectedState);
408 QCOMPARE(readChannelFinishedSpy.count(), 0);
409 QCOMPARE(textFrameReceivedSpy.count(), 0);
410 QCOMPARE(binaryFrameReceivedSpy.count(), 0);
411 QCOMPARE(textMessageReceivedSpy.count(), 0);
412 QCOMPARE(binaryMessageReceivedSpy.count(), 0);
413 QCOMPARE(pongSpy.count(), 0);
414 QCOMPARE(bytesWrittenSpy.count(), 0);
415}
416
417void tst_QWebSocket::tst_sendTextMessage()
418{
419 EchoServer echoServer;
420
421 QWebSocket socket;
422
423 //should return 0 because socket is not open yet
424 QCOMPARE(socket.sendTextMessage(QStringLiteral("1234")), 0);
425
426 QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected()));
427 QSignalSpy serverConnectedSpy(&echoServer, SIGNAL(newConnection(QUrl)));
428 QSignalSpy textMessageReceived(&socket, SIGNAL(textMessageReceived(QString)));
429 QSignalSpy textFrameReceived(&socket, SIGNAL(textFrameReceived(QString,bool)));
430 QSignalSpy binaryMessageReceived(&socket, SIGNAL(binaryMessageReceived(QByteArray)));
431 QSignalSpy binaryFrameReceived(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool)));
432 QSignalSpy socketError(&socket, SIGNAL(error(QAbstractSocket::SocketError)));
433
434 QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
435 QStringLiteral(":") + QString::number(echoServer.port()));
436 url.setPath(path: "/segment/with spaces");
437 QUrlQuery query;
438 query.addQueryItem(key: "queryitem", value: "with encoded characters");
439 url.setQuery(query);
440
441 socket.open(url);
442
443 QTRY_COMPARE(socketConnectedSpy.count(), 1);
444 QCOMPARE(socketError.count(), 0);
445 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
446 QList<QVariant> arguments = serverConnectedSpy.takeFirst();
447 QUrl urlConnected = arguments.at(i: 0).toUrl();
448 QCOMPARE(urlConnected, url);
449
450 QCOMPARE(socket.bytesToWrite(), 0);
451 socket.sendTextMessage(QStringLiteral("Hello world!"));
452 QVERIFY(socket.bytesToWrite() > 12); // 12 + a few extra bytes for header
453
454 QVERIFY(textMessageReceived.wait(500));
455 QCOMPARE(socket.bytesToWrite(), 0);
456
457 QCOMPARE(textMessageReceived.count(), 1);
458 QCOMPARE(binaryMessageReceived.count(), 0);
459 QCOMPARE(binaryFrameReceived.count(), 0);
460 arguments = textMessageReceived.takeFirst();
461 QString messageReceived = arguments.at(i: 0).toString();
462 QCOMPARE(messageReceived, QStringLiteral("Hello world!"));
463
464 QCOMPARE(textFrameReceived.count(), 1);
465 arguments = textFrameReceived.takeFirst();
466 QString frameReceived = arguments.at(i: 0).toString();
467 bool isLastFrame = arguments.at(i: 1).toBool();
468 QCOMPARE(frameReceived, QStringLiteral("Hello world!"));
469 QVERIFY(isLastFrame);
470
471 socket.close();
472 socketConnectedSpy.clear();
473 textMessageReceived.clear();
474 textFrameReceived.clear();
475
476 // QTBUG-74464 QWebsocket doesn't receive text (binary) message with size > 32 kb
477 socket.open(url);
478
479 QTRY_COMPARE(socketConnectedSpy.count(), 1);
480 QCOMPARE(socketError.count(), 0);
481 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
482 arguments = serverConnectedSpy.takeFirst();
483 urlConnected = arguments.at(i: 0).toUrl();
484 QCOMPARE(urlConnected, url);
485 QCOMPARE(socket.bytesToWrite(), 0);
486
487 // transmit a long text message with 1 MB
488 QString longString(0x100000, 'a');
489 socket.sendTextMessage(message: longString);
490 QVERIFY(socket.bytesToWrite() > longString.length());
491 QVERIFY(textMessageReceived.wait());
492 QCOMPARE(socket.bytesToWrite(), 0);
493
494 QCOMPARE(textMessageReceived.count(), 1);
495 QCOMPARE(binaryMessageReceived.count(), 0);
496 QCOMPARE(binaryFrameReceived.count(), 0);
497 arguments = textMessageReceived.takeFirst();
498 messageReceived = arguments.at(i: 0).toString();
499 QCOMPARE(messageReceived.length(), longString.length());
500 QCOMPARE(messageReceived, longString);
501
502 arguments = textFrameReceived.takeLast();
503 isLastFrame = arguments.at(i: 1).toBool();
504 QVERIFY(isLastFrame);
505
506 socket.close();
507 socketConnectedSpy.clear();
508 textMessageReceived.clear();
509 textFrameReceived.clear();
510
511 //QTBUG-36762: QWebSocket emits multiplied signals when socket was reopened
512 socket.open(url: QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
513 QStringLiteral(":") + QString::number(echoServer.port())));
514
515 QTRY_COMPARE(socketConnectedSpy.count(), 1);
516 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
517
518 socket.sendTextMessage(QStringLiteral("Hello world!"));
519
520 QVERIFY(textMessageReceived.wait(500));
521 QCOMPARE(textMessageReceived.count(), 1);
522 QCOMPARE(binaryMessageReceived.count(), 0);
523 QCOMPARE(binaryFrameReceived.count(), 0);
524 arguments = textMessageReceived.takeFirst();
525 messageReceived = arguments.at(i: 0).toString();
526 QCOMPARE(messageReceived, QStringLiteral("Hello world!"));
527
528 QCOMPARE(textFrameReceived.count(), 1);
529 arguments = textFrameReceived.takeFirst();
530 frameReceived = arguments.at(i: 0).toString();
531 isLastFrame = arguments.at(i: 1).toBool();
532 QCOMPARE(frameReceived, QStringLiteral("Hello world!"));
533 QVERIFY(isLastFrame);
534
535 QString reason = QStringLiteral("going away");
536 socket.close(closeCode: QWebSocketProtocol::CloseCodeGoingAway, reason);
537 QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeGoingAway);
538 QCOMPARE(socket.closeReason(), reason);
539}
540
541void tst_QWebSocket::tst_sendBinaryMessage()
542{
543 EchoServer echoServer;
544
545 QWebSocket socket;
546
547 //should return 0 because socket is not open yet
548 QCOMPARE(socket.sendBinaryMessage(QByteArrayLiteral("1234")), 0);
549
550 QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected()));
551 QSignalSpy textMessageReceived(&socket, SIGNAL(textMessageReceived(QString)));
552 QSignalSpy textFrameReceived(&socket, SIGNAL(textFrameReceived(QString,bool)));
553 QSignalSpy binaryMessageReceived(&socket, SIGNAL(binaryMessageReceived(QByteArray)));
554 QSignalSpy binaryFrameReceived(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool)));
555
556 socket.open(url: QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
557 QStringLiteral(":") + QString::number(echoServer.port())));
558
559 QTRY_COMPARE(socketConnectedSpy.count(), 1);
560 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
561
562 QCOMPARE(socket.bytesToWrite(), 0);
563 socket.sendBinaryMessage(QByteArrayLiteral("Hello world!"));
564 QVERIFY(socket.bytesToWrite() > 12); // 12 + a few extra bytes for header
565
566 QVERIFY(binaryMessageReceived.wait(500));
567 QCOMPARE(socket.bytesToWrite(), 0);
568
569 QCOMPARE(textMessageReceived.count(), 0);
570 QCOMPARE(textFrameReceived.count(), 0);
571 QCOMPARE(binaryMessageReceived.count(), 1);
572 QList<QVariant> arguments = binaryMessageReceived.takeFirst();
573 QByteArray messageReceived = arguments.at(i: 0).toByteArray();
574 QCOMPARE(messageReceived, QByteArrayLiteral("Hello world!"));
575
576 QCOMPARE(binaryFrameReceived.count(), 1);
577 arguments = binaryFrameReceived.takeFirst();
578 QByteArray frameReceived = arguments.at(i: 0).toByteArray();
579 bool isLastFrame = arguments.at(i: 1).toBool();
580 QCOMPARE(frameReceived, QByteArrayLiteral("Hello world!"));
581 QVERIFY(isLastFrame);
582
583 socket.close();
584
585 //QTBUG-36762: QWebSocket emits multiple signals when socket is reopened
586 socketConnectedSpy.clear();
587 binaryMessageReceived.clear();
588 binaryFrameReceived.clear();
589
590 socket.open(url: QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
591 QStringLiteral(":") + QString::number(echoServer.port())));
592
593 QTRY_COMPARE(socketConnectedSpy.count(), 1);
594 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
595
596 socket.sendBinaryMessage(QByteArrayLiteral("Hello world!"));
597
598 QVERIFY(binaryMessageReceived.wait(500));
599 QCOMPARE(textMessageReceived.count(), 0);
600 QCOMPARE(textFrameReceived.count(), 0);
601 QCOMPARE(binaryMessageReceived.count(), 1);
602 arguments = binaryMessageReceived.takeFirst();
603 messageReceived = arguments.at(i: 0).toByteArray();
604 QCOMPARE(messageReceived, QByteArrayLiteral("Hello world!"));
605
606 QCOMPARE(binaryFrameReceived.count(), 1);
607 arguments = binaryFrameReceived.takeFirst();
608 frameReceived = arguments.at(i: 0).toByteArray();
609 isLastFrame = arguments.at(i: 1).toBool();
610 QCOMPARE(frameReceived, QByteArrayLiteral("Hello world!"));
611 QVERIFY(isLastFrame);
612}
613
614void tst_QWebSocket::tst_errorString()
615{
616 //Check for QTBUG-37228: QWebSocket returns "Unknown Error" for known errors
617 QWebSocket socket;
618
619 //check that the default error string is empty
620 QVERIFY(socket.errorString().isEmpty());
621
622 QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError)));
623
624 socket.open(url: QUrl(QStringLiteral("ws://someserver.on.mars:9999")));
625
626 QTRY_COMPARE(errorSpy.count(), 1);
627 QList<QVariant> arguments = errorSpy.takeFirst();
628 QAbstractSocket::SocketError socketError =
629 qvariant_cast<QAbstractSocket::SocketError>(v: arguments.at(i: 0));
630 QCOMPARE(socketError, QAbstractSocket::HostNotFoundError);
631 QCOMPARE(socket.errorString(), QStringLiteral("Host not found"));
632}
633
634void tst_QWebSocket::tst_openRequest()
635{
636 EchoServer echoServer;
637
638 QWebSocket socket;
639
640 QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected()));
641 QSignalSpy serverRequestSpy(&echoServer, SIGNAL(newConnection(QNetworkRequest)));
642
643 QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
644 QLatin1Char(':') + QString::number(echoServer.port()));
645 QUrlQuery query;
646 query.addQueryItem(key: "queryitem", value: "with encoded characters");
647 url.setQuery(query);
648 QNetworkRequest req(url);
649 req.setRawHeader(headerName: "X-Custom-Header", value: "A custom header");
650 socket.open(request: req);
651
652 QTRY_COMPARE(socketConnectedSpy.count(), 1);
653 QTRY_COMPARE(serverRequestSpy.count(), 1);
654 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
655 QList<QVariant> arguments = serverRequestSpy.takeFirst();
656 QNetworkRequest requestConnected = arguments.at(i: 0).value<QNetworkRequest>();
657 QCOMPARE(requestConnected.url(), req.url());
658 QCOMPARE(requestConnected.rawHeader("X-Custom-Header"), req.rawHeader("X-Custom-Header"));
659 socket.close();
660}
661
662class WebSocket : public QWebSocket
663{
664 Q_OBJECT
665
666public:
667 explicit WebSocket()
668 {
669 connect(asender: this, SIGNAL(triggerClose()), SLOT(onClose()), atype: Qt::QueuedConnection);
670 connect(asender: this, SIGNAL(triggerOpen(QUrl)), SLOT(onOpen(QUrl)), atype: Qt::QueuedConnection);
671 connect(asender: this, SIGNAL(triggerSendTextMessage(QString)), SLOT(onSendTextMessage(QString)), atype: Qt::QueuedConnection);
672 connect(sender: this, SIGNAL(textMessageReceived(QString)), receiver: this, SLOT(onTextMessageReceived(QString)), Qt::QueuedConnection);
673 }
674
675 void asyncClose() { triggerClose(); }
676 void asyncOpen(const QUrl &url) { triggerOpen(url); }
677 void asyncSendTextMessage(const QString &msg) { triggerSendTextMessage(msg); }
678
679 QString receivedMessage;
680
681Q_SIGNALS:
682 void triggerClose();
683 void triggerOpen(const QUrl &);
684 void triggerSendTextMessage(const QString &);
685 void done();
686
687private Q_SLOTS:
688 void onClose() { close(); }
689 void onOpen(const QUrl &url) { open(url); }
690 void onSendTextMessage(const QString &msg) { sendTextMessage(message: msg); }
691 void onTextMessageReceived(const QString &msg) { receivedMessage = msg; done(); }
692};
693
694struct Warned
695{
696 static QtMessageHandler origHandler;
697 static bool warned;
698 static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& str)
699 {
700 if (type == QtWarningMsg) {
701 warned = true;
702 }
703 if (origHandler)
704 origHandler(type, context, str);
705 }
706};
707QtMessageHandler Warned::origHandler = nullptr;
708bool Warned::warned = false;
709
710
711void tst_QWebSocket::tst_moveToThread()
712{
713 Warned::origHandler = qInstallMessageHandler(&Warned::messageHandler);
714
715 EchoServer echoServer;
716
717 QThread* thread = new QThread(this);
718 thread->start();
719
720 WebSocket* socket = new WebSocket;
721 socket->moveToThread(thread);
722
723 const QString textMessage = QStringLiteral("Hello world!");
724 QSignalSpy socketConnectedSpy(socket, SIGNAL(connected()));
725 QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
726 QStringLiteral(":") + QString::number(echoServer.port()));
727 url.setPath(path: "/segment/with spaces");
728 QUrlQuery query;
729 query.addQueryItem(key: "queryitem", value: "with encoded characters");
730 url.setQuery(query);
731
732 socket->asyncOpen(url);
733 if (socketConnectedSpy.count() == 0)
734 QVERIFY(socketConnectedSpy.wait(500));
735
736 socket->asyncSendTextMessage(msg: textMessage);
737
738 QTimer timer;
739 timer.setInterval(1000);
740 timer.start();
741 QEventLoop loop;
742 connect(sender: socket, SIGNAL(done()), receiver: &loop, SLOT(quit()));
743 connect(sender: socket, SIGNAL(done()), receiver: &timer, SLOT(stop()));
744 connect(sender: &timer, SIGNAL(timeout()), receiver: &loop, SLOT(quit()));
745 loop.exec();
746
747 socket->asyncClose();
748
749 QTRY_COMPARE_WITH_TIMEOUT(loop.isRunning(), false, 200);
750 QCOMPARE(socket->receivedMessage, textMessage);
751
752 socket->deleteLater();
753 thread->quit();
754 thread->wait();
755}
756
757void tst_QWebSocket::tst_moveToThreadNoWarning()
758{
759 // check for warnings in tst_moveToThread()
760 // couldn't done there because warnings are processed after the test run
761 QCOMPARE(Warned::warned, false);
762}
763
764
765#ifndef QT_NO_NETWORKPROXY
766void tst_QWebSocket::tst_setProxy()
767{
768 // check if property assignment works as expected.
769 QWebSocket socket;
770 QCOMPARE(socket.proxy(), QNetworkProxy(QNetworkProxy::DefaultProxy));
771
772 QNetworkProxy proxy;
773 proxy.setPort(123);
774 socket.setProxy(proxy);
775 QCOMPARE(socket.proxy(), proxy);
776
777 proxy.setPort(321);
778 QCOMPARE(socket.proxy().port(), quint16(123));
779 socket.setProxy(proxy);
780 QCOMPARE(socket.proxy(), proxy);
781}
782
783void tst_QWebSocket::overlongCloseReason()
784{
785 EchoServer echoServer;
786
787 QWebSocket socket;
788
789 //should return 0 because socket is not open yet
790 QCOMPARE(socket.sendTextMessage(QStringLiteral("1234")), 0);
791
792 QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected()));
793 QSignalSpy socketDisconnectedSpy(&socket, SIGNAL(disconnected()));
794 QSignalSpy serverConnectedSpy(&echoServer, SIGNAL(newConnection(QUrl)));
795
796 QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
797 QStringLiteral(":") + QString::number(echoServer.port()));
798 socket.open(url);
799 QTRY_COMPARE(socketConnectedSpy.count(), 1);
800 QTRY_COMPARE(serverConnectedSpy.count(), 1);
801
802 const QString reason(200, QChar::fromLatin1(c: 'a'));
803 socket.close(closeCode: QWebSocketProtocol::CloseCodeGoingAway, reason);
804 QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeGoingAway);
805 // Max length of a control frame is 125, but 2 bytes are used for the close code:
806 QCOMPARE(socket.closeReason().length(), 123);
807 QCOMPARE(socket.closeReason(), reason.leftRef(123));
808 QTRY_COMPARE(socketDisconnectedSpy.count(), 1);
809}
810
811void tst_QWebSocket::incomingMessageTooLong()
812{
813//QTBUG-70693
814 quint64 maxAllowedIncomingMessageSize = 1024;
815 quint64 maxAllowedIncomingFrameSize = QWebSocket::maxIncomingFrameSize();
816
817 EchoServer echoServer(nullptr, maxAllowedIncomingMessageSize, maxAllowedIncomingFrameSize);
818
819 QWebSocket socket;
820
821 QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
822 QSignalSpy serverConnectedSpy(&echoServer, QOverload<QUrl>::of(ptr: &EchoServer::newConnection));
823 QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected);
824
825 QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
826 QStringLiteral(":") + QString::number(echoServer.port()));
827 socket.open(url);
828 QTRY_COMPARE(socketConnectedSpy.count(), 1);
829 QTRY_COMPARE(serverConnectedSpy.count(), 1);
830
831 QString payload(maxAllowedIncomingMessageSize+1, 'a');
832 QCOMPARE(socket.sendTextMessage(payload), payload.size());
833
834 QTRY_COMPARE(socketDisconnectedSpy.count(), 1);
835 QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeTooMuchData);
836}
837
838void tst_QWebSocket::incomingFrameTooLong()
839{
840//QTBUG-70693
841 quint64 maxAllowedIncomingMessageSize = QWebSocket::maxIncomingMessageSize();
842 quint64 maxAllowedIncomingFrameSize = 1024;
843
844 EchoServer echoServer(nullptr, maxAllowedIncomingMessageSize, maxAllowedIncomingFrameSize);
845
846 QWebSocket socket;
847 socket.setOutgoingFrameSize(maxAllowedIncomingFrameSize+1);
848
849 QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
850 QSignalSpy serverConnectedSpy(&echoServer, QOverload<QUrl>::of(ptr: &EchoServer::newConnection));
851 QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected);
852
853 QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
854 QStringLiteral(":") + QString::number(echoServer.port()));
855 socket.open(url);
856 QTRY_COMPARE(socketConnectedSpy.count(), 1);
857 QTRY_COMPARE(serverConnectedSpy.count(), 1);
858
859 QString payload(maxAllowedIncomingFrameSize+1, 'a');
860 QCOMPARE(socket.sendTextMessage(payload), payload.size());
861
862 QTRY_COMPARE(socketDisconnectedSpy.count(), 1);
863 QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeTooMuchData);
864}
865
866void tst_QWebSocket::testingFrameAndMessageSizeApi()
867{
868//requested by André Hartmann, QTBUG-70693
869 QWebSocket socket;
870
871 const quint64 outgoingFrameSize = 5;
872 socket.setOutgoingFrameSize(outgoingFrameSize);
873 QTRY_COMPARE(outgoingFrameSize, socket.outgoingFrameSize());
874
875 const quint64 maxAllowedIncomingFrameSize = 9;
876 socket.setMaxAllowedIncomingFrameSize(maxAllowedIncomingFrameSize);
877 QTRY_COMPARE(maxAllowedIncomingFrameSize, socket.maxAllowedIncomingFrameSize());
878
879 const quint64 maxAllowedIncomingMessageSize = 889;
880 socket.setMaxAllowedIncomingMessageSize(maxAllowedIncomingMessageSize);
881 QTRY_COMPARE(maxAllowedIncomingMessageSize, socket.maxAllowedIncomingMessageSize());
882}
883
884#endif // QT_NO_NETWORKPROXY
885
886QTEST_MAIN(tst_QWebSocket)
887
888#include "tst_qwebsocket.moc"
889

source code of qtwebsockets/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp