summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--examples/websockets/echoserver/main.cpp2
-rw-r--r--src/imports/qmlwebsockets/plugins.qmltypes2
-rw-r--r--src/websockets/qsslserver.cpp15
-rw-r--r--src/websockets/qsslserver_p.h6
-rw-r--r--src/websockets/qwebsocket_p.cpp8
-rw-r--r--src/websockets/qwebsocket_p.h4
-rw-r--r--src/websockets/qwebsocketdataprocessor.cpp39
-rw-r--r--src/websockets/qwebsocketdataprocessor_p.h7
-rw-r--r--src/websockets/qwebsocketframe.cpp370
-rw-r--r--src/websockets/qwebsocketframe_p.h13
-rw-r--r--src/websockets/qwebsockethandshakeresponse.cpp27
-rw-r--r--src/websockets/qwebsocketserver.cpp50
-rw-r--r--src/websockets/qwebsocketserver.h17
-rw-r--r--src/websockets/qwebsocketserver_p.cpp50
-rw-r--r--src/websockets/qwebsocketserver_p.h11
-rw-r--r--tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp36
-rw-r--r--tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp36
-rw-r--r--tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp225
-rw-r--r--tests/auto/websockets/websocketframe/tst_websocketframe.cpp12
20 files changed, 685 insertions, 247 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 988629e..c6f1702 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -3,4 +3,4 @@ load(qt_build_config)
CONFIG += warning_clean
DEFINES += QT_NO_FOREACH
-MODULE_VERSION = 5.13.2
+MODULE_VERSION = 5.14.0
diff --git a/examples/websockets/echoserver/main.cpp b/examples/websockets/echoserver/main.cpp
index bf9e3c2..f857f0f 100644
--- a/examples/websockets/echoserver/main.cpp
+++ b/examples/websockets/echoserver/main.cpp
@@ -65,7 +65,7 @@ int main(int argc, char *argv[])
parser.addOption(dbgOption);
QCommandLineOption portOption(QStringList() << "p" << "port",
QCoreApplication::translate("main", "Port for echoserver [default: 1234]."),
- QCoreApplication::translate("main", "port"), QLatin1Literal("1234"));
+ QCoreApplication::translate("main", "port"), QLatin1String("1234"));
parser.addOption(portOption);
parser.process(a);
bool debug = parser.isSet(dbgOption);
diff --git a/src/imports/qmlwebsockets/plugins.qmltypes b/src/imports/qmlwebsockets/plugins.qmltypes
index 79e2f97..f1a5a2e 100644
--- a/src/imports/qmlwebsockets/plugins.qmltypes
+++ b/src/imports/qmlwebsockets/plugins.qmltypes
@@ -4,7 +4,7 @@ import QtQuick.tooling 1.2
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
-// 'qmlplugindump -nonrelocatable -dependencies dependencies.json QtWebSockets 1.13'
+// 'qmlplugindump -nonrelocatable -dependencies dependencies.json QtWebSockets 1.14'
Module {
dependencies: []
diff --git a/src/websockets/qsslserver.cpp b/src/websockets/qsslserver.cpp
index 586e520..ec645c9 100644
--- a/src/websockets/qsslserver.cpp
+++ b/src/websockets/qsslserver.cpp
@@ -118,11 +118,11 @@ void QSslServer::incomingConnection(qintptr socket)
connect(pSslSocket, QOverload<const QList<QSslError>&>::of(&QSslSocket::sslErrors),
this, &QSslServer::sslErrors);
connect(pSslSocket, &QSslSocket::encrypted,
- this, &QSslServer::newEncryptedConnection);
+ this, &QSslServer::socketEncrypted);
connect(pSslSocket, &QSslSocket::preSharedKeyAuthenticationRequired,
this, &QSslServer::preSharedKeyAuthenticationRequired);
- addPendingConnection(pSslSocket);
+ Q_EMIT startedEncryptionHandshake(pSslSocket);
pSslSocket->startServerEncryption();
} else {
@@ -131,4 +131,15 @@ void QSslServer::incomingConnection(qintptr socket)
}
}
+void QSslServer::socketEncrypted()
+{
+ QSslSocket *pSslSocket = qobject_cast<QSslSocket *>(sender());
+
+ // We do not add the connection until the encryption handshake is complete.
+ // In case the handshake is aborted, we would be left with a stale
+ // connection in the queue otherwise.
+ addPendingConnection(pSslSocket);
+ Q_EMIT newEncryptedConnection();
+}
+
QT_END_NAMESPACE
diff --git a/src/websockets/qsslserver_p.h b/src/websockets/qsslserver_p.h
index 10f8fea..6283058 100644
--- a/src/websockets/qsslserver_p.h
+++ b/src/websockets/qsslserver_p.h
@@ -59,6 +59,8 @@
QT_BEGIN_NAMESPACE
+class QSslSocket;
+
class QSslServer : public QTcpServer
{
Q_OBJECT
@@ -76,10 +78,14 @@ Q_SIGNALS:
void peerVerifyError(const QSslError &error);
void newEncryptedConnection();
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
+ void startedEncryptionHandshake(QSslSocket *socket);
protected:
void incomingConnection(qintptr socket) override;
+private slots:
+ void socketEncrypted();
+
private:
QSslConfiguration m_sslConfiguration;
};
diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp
index 6965731..36aefd9 100644
--- a/src/websockets/qwebsocket_p.cpp
+++ b/src/websockets/qwebsocket_p.cpp
@@ -113,6 +113,7 @@ QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::
m_defaultMaskGenerator(),
m_handshakeState(NothingDoneState)
{
+ m_pingTimer.start();
}
/*!
@@ -144,6 +145,7 @@ QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol:
m_defaultMaskGenerator(),
m_handshakeState(NothingDoneState)
{
+ m_pingTimer.start();
}
/*!
@@ -1176,10 +1178,10 @@ void QWebSocketPrivate::processData()
while (m_pSocket->bytesAvailable()) {
if (state() == QAbstractSocket::ConnectingState) {
if (!m_pSocket->canReadLine())
- break;
+ return;
processHandshake(m_pSocket);
- } else {
- m_dataProcessor.process(m_pSocket);
+ } else if (!m_dataProcessor.process(m_pSocket)) {
+ return;
}
}
}
diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h
index 4b39dfc..e72daa5 100644
--- a/src/websockets/qwebsocket_p.h
+++ b/src/websockets/qwebsocket_p.h
@@ -61,7 +61,7 @@
#include <QtNetwork/QSslError>
#include <QtNetwork/QSslSocket>
#endif
-#include <QtCore/QTime>
+#include <QtCore/QElapsedTimer>
#include <private/qobject_p.h>
#include "qwebsocket.h"
@@ -229,7 +229,7 @@ private:
QWebSocketProtocol::CloseCode m_closeCode;
QString m_closeReason;
- QTime m_pingTimer;
+ QElapsedTimer m_pingTimer;
QWebSocketDataProcessor m_dataProcessor;
QWebSocketConfiguration m_configuration;
diff --git a/src/websockets/qwebsocketdataprocessor.cpp b/src/websockets/qwebsocketdataprocessor.cpp
index 4f81222..191b992 100644
--- a/src/websockets/qwebsocketdataprocessor.cpp
+++ b/src/websockets/qwebsocketdataprocessor.cpp
@@ -87,6 +87,10 @@ QWebSocketDataProcessor::QWebSocketDataProcessor(QObject *parent) :
m_pTextCodec(QTextCodec::codecForName("UTF-8"))
{
clear();
+ // initialize the internal timeout timer
+ waitTimer.setInterval(5000);
+ waitTimer.setSingleShot(true);
+ waitTimer.callOnTimeout(this, &QWebSocketDataProcessor::timeout);
}
/*!
@@ -119,14 +123,23 @@ quint64 QWebSocketDataProcessor::maxFrameSize()
/*!
\internal
+
+ Returns \c true if a complete websocket frame has been processed;
+ otherwise returns \c false.
*/
-void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
+bool QWebSocketDataProcessor::process(QIODevice *pIoDevice)
{
bool isDone = false;
while (!isDone) {
- QWebSocketFrame frame = QWebSocketFrame::readFrame(pIoDevice);
- if (Q_LIKELY(frame.isValid())) {
+ frame.readFrame(pIoDevice);
+ if (!frame.isDone()) {
+ // waiting for more data available
+ QObject::connect(pIoDevice, &QIODevice::readyRead,
+ &waitTimer, &QTimer::stop, Qt::UniqueConnection);
+ waitTimer.start();
+ return false;
+ } else if (Q_LIKELY(frame.isValid())) {
if (frame.isControlFrame()) {
isDone = processControlFrame(frame);
} else {
@@ -136,7 +149,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
tr("Received Continuation frame, while there is " \
"nothing to continue."));
- return;
+ return true;
}
if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() &&
!frame.isContinuationFrame())) {
@@ -144,7 +157,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
tr("All data frames after the initial data frame " \
"must have opcode 0 (continuation)."));
- return;
+ return true;
}
if (!frame.isContinuationFrame()) {
m_opCode = frame.opCode();
@@ -158,7 +171,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
clear();
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData,
tr("Received message is too big."));
- return;
+ return true;
}
if (m_opCode == QWebSocketProtocol::OpCodeText) {
@@ -171,7 +184,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
clear();
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeWrongDatatype,
tr("Invalid UTF-8 code encountered."));
- return;
+ return true;
} else {
m_textMessage.append(frameTxt);
Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame());
@@ -199,7 +212,9 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
clear();
isDone = true;
}
+ frame.clear();
}
+ return true;
}
/*!
@@ -301,4 +316,14 @@ bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame)
return mustStopProcessing;
}
+/*!
+ \internal
+ */
+void QWebSocketDataProcessor::timeout()
+{
+ clear();
+ Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeGoingAway,
+ tr("Timeout when reading data from socket."));
+}
+
QT_END_NAMESPACE
diff --git a/src/websockets/qwebsocketdataprocessor_p.h b/src/websockets/qwebsocketdataprocessor_p.h
index e80a843..03635b1 100644
--- a/src/websockets/qwebsocketdataprocessor_p.h
+++ b/src/websockets/qwebsocketdataprocessor_p.h
@@ -55,6 +55,8 @@
#include <QtCore/QByteArray>
#include <QtCore/QString>
#include <QtCore/QTextCodec>
+#include <QTimer>
+#include "qwebsocketframe_p.h"
#include "qwebsocketprotocol.h"
#include "qwebsocketprotocol_p.h"
@@ -86,7 +88,7 @@ Q_SIGNALS:
void errorEncountered(QWebSocketProtocol::CloseCode code, const QString &description);
public Q_SLOTS:
- void process(QIODevice *pIoDevice);
+ bool process(QIODevice *pIoDevice);
void clear();
private:
@@ -111,8 +113,11 @@ private:
quint64 m_payloadLength;
QTextCodec::ConverterState *m_pConverterState;
QTextCodec *m_pTextCodec;
+ QWebSocketFrame frame;
+ QTimer waitTimer;
bool processControlFrame(const QWebSocketFrame &frame);
+ void timeout();
};
QT_END_NAMESPACE
diff --git a/src/websockets/qwebsocketframe.cpp b/src/websockets/qwebsocketframe.cpp
index 041302e..11373a7 100644
--- a/src/websockets/qwebsocketframe.cpp
+++ b/src/websockets/qwebsocketframe.cpp
@@ -93,7 +93,8 @@ QWebSocketFrame::QWebSocketFrame(const QWebSocketFrame &other) :
m_rsv1(other.m_rsv1),
m_rsv2(other.m_rsv2),
m_rsv3(other.m_rsv3),
- m_isValid(other.m_isValid)
+ m_isValid(other.m_isValid),
+ m_processingState(other.m_processingState)
{
}
@@ -113,6 +114,7 @@ QWebSocketFrame &QWebSocketFrame::operator =(const QWebSocketFrame &other)
m_length = other.m_length;
m_payload = other.m_payload;
m_isValid = other.m_isValid;
+ m_processingState = other.m_processingState;
return *this;
}
@@ -132,7 +134,8 @@ QWebSocketFrame::QWebSocketFrame(QWebSocketFrame &&other) :
m_rsv1(qMove(other.m_rsv1)),
m_rsv2(qMove(other.m_rsv2)),
m_rsv3(qMove(other.m_rsv3)),
- m_isValid(qMove(other.m_isValid))
+ m_isValid(qMove(other.m_isValid)),
+ m_processingState(qMove(other.m_processingState))
{}
@@ -152,6 +155,7 @@ QWebSocketFrame &QWebSocketFrame::operator =(QWebSocketFrame &&other)
qSwap(m_length, other.m_length);
qSwap(m_payload, other.m_payload);
qSwap(m_isValid, other.m_isValid);
+ qSwap(m_processingState, other.m_processingState);
return *this;
}
@@ -175,6 +179,7 @@ void QWebSocketFrame::swap(QWebSocketFrame &other)
qSwap(m_length, other.m_length);
qSwap(m_payload, other.m_payload);
qSwap(m_isValid, other.m_isValid);
+ qSwap(m_processingState, other.m_processingState);
}
}
@@ -183,7 +188,7 @@ void QWebSocketFrame::swap(QWebSocketFrame &other)
*/
QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const
{
- return m_closeCode;
+ return isDone() ? m_closeCode : QWebSocketProtocol::CloseCodeGoingAway;
}
/*!
@@ -191,7 +196,7 @@ QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const
*/
QString QWebSocketFrame::closeReason() const
{
- return m_closeReason;
+ return isDone() ? m_closeReason : tr("Waiting for more data from socket.");
}
/*!
@@ -276,6 +281,7 @@ void QWebSocketFrame::clear()
m_length = 0;
m_payload.clear();
m_isValid = false;
+ m_processingState = PS_READ_HEADER;
}
/*!
@@ -283,215 +289,211 @@ void QWebSocketFrame::clear()
*/
bool QWebSocketFrame::isValid() const
{
- return m_isValid;
+ return isDone() && m_isValid;
}
-#define WAIT_FOR_MORE_DATA(dataSizeInBytes) \
- { returnState = processingState; \
- processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; }
+/*!
+ \internal
+ */
+bool QWebSocketFrame::isDone() const
+{
+ return m_processingState == PS_DISPATCH_RESULT;
+}
/*!
\internal
*/
-QWebSocketFrame QWebSocketFrame::readFrame(QIODevice *pIoDevice)
+void QWebSocketFrame::readFrame(QIODevice *pIoDevice)
{
- bool isDone = false;
- qint64 bytesRead = 0;
- QWebSocketFrame frame;
- quint64 dataWaitSize = 0;
- Q_UNUSED(dataWaitSize); // value is used in MACRO, Q_UNUSED to avoid compiler warnings
- ProcessingState processingState = PS_READ_HEADER;
- ProcessingState returnState = PS_READ_HEADER;
- bool hasMask = false;
- quint64 payloadLength = 0;
-
- while (!isDone)
+ while (true)
{
- switch (processingState) {
- case PS_WAIT_FOR_MORE_DATA:
- //TODO: waitForReadyRead should really be changed
- //now, when a WebSocket is used in a GUI thread
- //the GUI will hang for at most 5 seconds
- //maybe, a QStateMachine should be used
- if (!pIoDevice->waitForReadyRead(5000)) {
- frame.setError(QWebSocketProtocol::CloseCodeGoingAway,
- tr("Timeout when reading data from socket."));
- processingState = PS_DISPATCH_RESULT;
- } else {
- processingState = returnState;
- }
- break;
-
+ switch (m_processingState) {
case PS_READ_HEADER:
- if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) {
- //FIN, RSV1-3, Opcode
- char header[2] = {0};
- bytesRead = pIoDevice->read(header, 2);
- frame.m_isFinalFrame = (header[0] & 0x80) != 0;
- frame.m_rsv1 = (header[0] & 0x40);
- frame.m_rsv2 = (header[0] & 0x20);
- frame.m_rsv3 = (header[0] & 0x10);
- frame.m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F);
-
- //Mask, PayloadLength
- hasMask = (header[1] & 0x80) != 0;
- frame.m_length = (header[1] & 0x7F);
-
- switch (frame.m_length)
- {
- case 126:
- {
- processingState = PS_READ_PAYLOAD_LENGTH;
- break;
- }
- case 127:
- {
- processingState = PS_READ_BIG_PAYLOAD_LENGTH;
- break;
- }
- default:
- {
- payloadLength = frame.m_length;
- processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
- break;
- }
- }
- if (!frame.checkValidity())
- processingState = PS_DISPATCH_RESULT;
- } else {
- WAIT_FOR_MORE_DATA(2);
+ m_processingState = readFrameHeader(pIoDevice);
+ if (m_processingState == PS_WAIT_FOR_MORE_DATA) {
+ m_processingState = PS_READ_HEADER;
+ return;
}
break;
case PS_READ_PAYLOAD_LENGTH:
- if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) {
- uchar length[2] = {0};
- bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 2);
- if (Q_UNLIKELY(bytesRead == -1)) {
- frame.setError(QWebSocketProtocol::CloseCodeGoingAway,
- tr("Error occurred while reading from the network: %1")
- .arg(pIoDevice->errorString()));
- processingState = PS_DISPATCH_RESULT;
- } else {
- payloadLength = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length));
- if (Q_UNLIKELY(payloadLength < 126)) {
- //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
- //"in all cases, the minimal number of bytes MUST be used to encode
- //the length, for example, the length of a 124-byte-long string
- //can't be encoded as the sequence 126, 0, 124"
- frame.setError(QWebSocketProtocol::CloseCodeProtocolError,
- tr("Lengths smaller than 126 " \
- "must be expressed as one byte."));
- processingState = PS_DISPATCH_RESULT;
- } else {
- processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
- }
- }
- } else {
- WAIT_FOR_MORE_DATA(2);
+ m_processingState = readFramePayloadLength(pIoDevice);
+ if (m_processingState == PS_WAIT_FOR_MORE_DATA) {
+ m_processingState = PS_READ_PAYLOAD_LENGTH;
+ return;
}
break;
- case PS_READ_BIG_PAYLOAD_LENGTH:
- if (Q_LIKELY(pIoDevice->bytesAvailable() >= 8)) {
- uchar length[8] = {0};
- bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 8);
- if (Q_UNLIKELY(bytesRead < 8)) {
- frame.setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
- tr("Something went wrong during "\
- "reading from the network."));
- processingState = PS_DISPATCH_RESULT;
- } else {
- //Most significant bit must be set to 0 as
- //per http://tools.ietf.org/html/rfc6455#section-5.2
- payloadLength = qFromBigEndian<quint64>(length);
- if (Q_UNLIKELY(payloadLength & (quint64(1) << 63))) {
- frame.setError(QWebSocketProtocol::CloseCodeProtocolError,
- tr("Highest bit of payload length is not 0."));
- processingState = PS_DISPATCH_RESULT;
- } else if (Q_UNLIKELY(payloadLength <= 0xFFFFu)) {
- //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
- //"in all cases, the minimal number of bytes MUST be used to encode
- //the length, for example, the length of a 124-byte-long string
- //can't be encoded as the sequence 126, 0, 124"
- frame.setError(QWebSocketProtocol::CloseCodeProtocolError,
- tr("Lengths smaller than 65536 (2^16) " \
- "must be expressed as 2 bytes."));
- processingState = PS_DISPATCH_RESULT;
- } else {
- processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
- }
- }
- } else {
- WAIT_FOR_MORE_DATA(8);
- }
-
- break;
-
case PS_READ_MASK:
- if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) {
- bytesRead = pIoDevice->read(reinterpret_cast<char *>(&frame.m_mask),
- sizeof(frame.m_mask));
- if (bytesRead == -1) {
- frame.setError(QWebSocketProtocol::CloseCodeGoingAway,
- tr("Error while reading from the network: %1.")
- .arg(pIoDevice->errorString()));
- processingState = PS_DISPATCH_RESULT;
- } else {
- frame.m_mask = qFromBigEndian(frame.m_mask);
- processingState = PS_READ_PAYLOAD;
- }
- } else {
- WAIT_FOR_MORE_DATA(4);
+ m_processingState = readFrameMask(pIoDevice);
+ if (m_processingState == PS_WAIT_FOR_MORE_DATA) {
+ m_processingState = PS_READ_MASK;
+ return;
}
break;
case PS_READ_PAYLOAD:
- if (!payloadLength) {
- processingState = PS_DISPATCH_RESULT;
- } else if (Q_UNLIKELY(payloadLength > MAX_FRAME_SIZE_IN_BYTES)) {
- frame.setError(QWebSocketProtocol::CloseCodeTooMuchData,
- tr("Maximum framesize exceeded."));
- processingState = PS_DISPATCH_RESULT;
- } else {
- quint64 bytesAvailable = quint64(pIoDevice->bytesAvailable());
- if (bytesAvailable >= payloadLength) {
- frame.m_payload = pIoDevice->read(int(payloadLength));
- //payloadLength can be safely cast to an integer,
- //because MAX_FRAME_SIZE_IN_BYTES = MAX_INT
- if (Q_UNLIKELY(frame.m_payload.length() != int(payloadLength))) {
- //some error occurred; refer to the Qt documentation of QIODevice::read()
- frame.setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
- tr("Some serious error occurred " \
- "while reading from the network."));
- processingState = PS_DISPATCH_RESULT;
- } else {
- if (hasMask)
- QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask);
- processingState = PS_DISPATCH_RESULT;
- }
- } else {
- //if payload is too big, then this will timeout
- WAIT_FOR_MORE_DATA(payloadLength);
- }
+ m_processingState = readFramePayload(pIoDevice);
+ if (m_processingState == PS_WAIT_FOR_MORE_DATA) {
+ m_processingState = PS_READ_PAYLOAD;
+ return;
}
break;
case PS_DISPATCH_RESULT:
- processingState = PS_READ_HEADER;
- isDone = true;
- break;
+ return;
default:
- //should not come here
- qWarning() << "DataProcessor::process: Found invalid state. This should not happen!";
- frame.clear();
- isDone = true;
- break;
- } //end switch
+ Q_UNREACHABLE();
+ return;
+ }
+ }
+}
+
+/*!
+ \internal
+ */
+QWebSocketFrame::ProcessingState QWebSocketFrame::readFrameHeader(QIODevice *pIoDevice)
+{
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) {
+ // FIN, RSV1-3, Opcode
+ char header[2] = {0};
+ if (Q_UNLIKELY(pIoDevice->read(header, 2) < 2)) {
+ setError(QWebSocketProtocol::CloseCodeGoingAway,
+ tr("Error occurred while reading header from the network: %1")
+ .arg(pIoDevice->errorString()));
+ return PS_DISPATCH_RESULT;
+ }
+ m_isFinalFrame = (header[0] & 0x80) != 0;
+ m_rsv1 = (header[0] & 0x40);
+ m_rsv2 = (header[0] & 0x20);
+ m_rsv3 = (header[0] & 0x10);
+ m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F);
+
+ // Mask
+ // Use zero as mask value to mean there's no mask to read.
+ // When the mask value is read, it over-writes this non-zero value.
+ m_mask = header[1] & 0x80;
+ // PayloadLength
+ m_length = (header[1] & 0x7F);
+
+ if (!checkValidity())
+ return PS_DISPATCH_RESULT;
+
+ switch (m_length) {
+ case 126:
+ case 127:
+ return PS_READ_PAYLOAD_LENGTH;
+ default:
+ return hasMask() ? PS_READ_MASK : PS_READ_PAYLOAD;
+ }
+ }
+ return PS_WAIT_FOR_MORE_DATA;
+}
+
+/*!
+ \internal
+ */
+QWebSocketFrame::ProcessingState QWebSocketFrame::readFramePayloadLength(QIODevice *pIoDevice)
+{
+ // see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
+ // in all cases, the minimal number of bytes MUST be used to encode the length,
+ // for example, the length of a 124-byte-long string can't be encoded as the
+ // sequence 126, 0, 124"
+ switch (m_length) {
+ case 126:
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) {
+ uchar length[2] = {0};
+ if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast<char *>(length), 2) < 2)) {
+ setError(QWebSocketProtocol::CloseCodeGoingAway,
+ tr("Error occurred while reading from the network: %1")
+ .arg(pIoDevice->errorString()));
+ return PS_DISPATCH_RESULT;
+ }
+ m_length = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length));
+ if (Q_UNLIKELY(m_length < 126)) {
+
+ setError(QWebSocketProtocol::CloseCodeProtocolError,
+ tr("Lengths smaller than 126 must be expressed as one byte."));
+ return PS_DISPATCH_RESULT;
+ }
+ return hasMask() ? PS_READ_MASK : PS_READ_PAYLOAD;
+ }
+ break;
+ case 127:
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 8)) {
+ uchar length[8] = {0};
+ if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast<char *>(length), 8) < 8)) {
+ setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
+ tr("Something went wrong during reading from the network."));
+ return PS_DISPATCH_RESULT;
+ }
+ // Most significant bit must be set to 0 as
+ // per http://tools.ietf.org/html/rfc6455#section-5.2
+ m_length = qFromBigEndian<quint64>(length);
+ if (Q_UNLIKELY(m_length & (quint64(1) << 63))) {
+ setError(QWebSocketProtocol::CloseCodeProtocolError,
+ tr("Highest bit of payload length is not 0."));
+ return PS_DISPATCH_RESULT;
+ }
+ if (Q_UNLIKELY(m_length <= 0xFFFFu)) {
+ setError(QWebSocketProtocol::CloseCodeProtocolError,
+ tr("Lengths smaller than 65536 (2^16) must be expressed as 2 bytes."));
+ return PS_DISPATCH_RESULT;
+ }
+ return hasMask() ? PS_READ_MASK : PS_READ_PAYLOAD;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
}
+ return PS_WAIT_FOR_MORE_DATA;
+}
- return frame;
+/*!
+ \internal
+ */
+QWebSocketFrame::ProcessingState QWebSocketFrame::readFrameMask(QIODevice *pIoDevice)
+{
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) {
+ if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast<char *>(&m_mask), sizeof(m_mask)) < 4)) {
+ setError(QWebSocketProtocol::CloseCodeGoingAway,
+ tr("Error while reading from the network: %1.").arg(pIoDevice->errorString()));
+ return PS_DISPATCH_RESULT;
+ }
+ m_mask = qFromBigEndian(m_mask);
+ return PS_READ_PAYLOAD;
+ }
+ return PS_WAIT_FOR_MORE_DATA;
+}
+
+/*!
+ \internal
+ */
+QWebSocketFrame::ProcessingState QWebSocketFrame::readFramePayload(QIODevice *pIoDevice)
+{
+ if (!m_length)
+ return PS_DISPATCH_RESULT;
+
+ if (Q_UNLIKELY(m_length > MAX_FRAME_SIZE_IN_BYTES)) {
+ setError(QWebSocketProtocol::CloseCodeTooMuchData, tr("Maximum framesize exceeded."));
+ return PS_DISPATCH_RESULT;
+ }
+ if (quint64(pIoDevice->bytesAvailable()) >= m_length) {
+ m_payload = pIoDevice->read(int(m_length));
+ // m_length can be safely cast to an integer,
+ // because MAX_FRAME_SIZE_IN_BYTES = MAX_INT
+ if (Q_UNLIKELY(m_payload.length() != int(m_length))) {
+ // some error occurred; refer to the Qt documentation of QIODevice::read()
+ setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
+ tr("Some serious error occurred while reading from the network."));
+ } else if (hasMask()) {
+ QWebSocketProtocol::mask(&m_payload, mask());
+ }
+ return PS_DISPATCH_RESULT;
+ }
+ return PS_WAIT_FOR_MORE_DATA;
}
/*!
diff --git a/src/websockets/qwebsocketframe_p.h b/src/websockets/qwebsocketframe_p.h
index 5c3a7df..e2b4e9f 100644
--- a/src/websockets/qwebsocketframe_p.h
+++ b/src/websockets/qwebsocketframe_p.h
@@ -101,15 +101,16 @@ public:
void clear();
bool isValid() const;
+ bool isDone() const;
- static QWebSocketFrame readFrame(QIODevice *pIoDevice);
+ void readFrame(QIODevice *pIoDevice);
private:
QWebSocketProtocol::CloseCode m_closeCode;
QString m_closeReason;
quint32 m_mask;
QWebSocketProtocol::OpCode m_opCode;
- quint8 m_length;
+ quint64 m_length;
QByteArray m_payload;
bool m_isFinalFrame;
@@ -122,12 +123,16 @@ private:
{
PS_READ_HEADER,
PS_READ_PAYLOAD_LENGTH,
- PS_READ_BIG_PAYLOAD_LENGTH,
PS_READ_MASK,
PS_READ_PAYLOAD,
PS_DISPATCH_RESULT,
PS_WAIT_FOR_MORE_DATA
- };
+ } m_processingState{PS_READ_HEADER};
+
+ ProcessingState readFrameHeader(QIODevice *pIoDevice);
+ ProcessingState readFramePayloadLength(QIODevice *pIoDevice);
+ ProcessingState readFrameMask(QIODevice *pIoDevice);
+ ProcessingState readFramePayload(QIODevice *pIoDevice);
void setError(QWebSocketProtocol::CloseCode code, const QString &closeReason);
bool checkValidity();
diff --git a/src/websockets/qwebsockethandshakeresponse.cpp b/src/websockets/qwebsockethandshakeresponse.cpp
index 2241ca2..d3ef609 100644
--- a/src/websockets/qwebsockethandshakeresponse.cpp
+++ b/src/websockets/qwebsockethandshakeresponse.cpp
@@ -53,7 +53,9 @@
#include <QtCore/QList>
#include <QtCore/QStringBuilder> //for more efficient string concatenation
+#include <algorithm>
#include <functional> //for std::greater
+#include <iterator>
QT_BEGIN_NAMESPACE
@@ -124,6 +126,18 @@ QString QWebSocketHandshakeResponse::calculateAcceptKey(const QString &key) cons
return QString::fromLatin1(hash.toBase64());
}
+template <class T, class Compare>
+static QList<T> listIntersection(QList<T> list1, QList<T> list2, Compare comp)
+{
+ QList<T> result;
+ std::sort(list1.begin(), list1.end(), comp);
+ std::sort(list2.begin(), list2.end(), comp);
+ std::set_intersection(list1.cbegin(), list1.cend(),
+ list2.cbegin(), list2.cend(),
+ std::back_inserter(result), comp);
+ return result;
+}
+
/*!
\internal
*/
@@ -148,15 +162,16 @@ QString QWebSocketHandshakeResponse::getHandshakeResponse(
if (request.isValid()) {
const QString acceptKey = calculateAcceptKey(request.key());
const QList<QString> matchingProtocols =
- supportedProtocols.toSet().intersect(request.protocols().toSet()).toList();
+ listIntersection(supportedProtocols, request.protocols(),
+ std::less<QString>());
//TODO: extensions must be kept in the order in which they arrive
//cannot use set.intersect() to get the supported extensions
const QList<QString> matchingExtensions =
- supportedExtensions.toSet().intersect(request.extensions().toSet()).toList();
- QList<QWebSocketProtocol::Version> matchingVersions =
- request.versions().toSet().intersect(supportedVersions.toSet()).toList();
- std::sort(matchingVersions.begin(), matchingVersions.end(),
- std::greater<QWebSocketProtocol::Version>()); //sort in descending order
+ listIntersection(supportedExtensions, request.extensions(),
+ std::less<QString>());
+ const QList<QWebSocketProtocol::Version> matchingVersions =
+ listIntersection(supportedVersions, request.versions(),
+ std::greater<QWebSocketProtocol::Version>()); //sort in descending order
if (Q_UNLIKELY(matchingVersions.isEmpty())) {
m_error = QWebSocketProtocol::CloseCodeProtocolError;
diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp
index 7790250..eafe3fd 100644
--- a/src/websockets/qwebsocketserver.cpp
+++ b/src/websockets/qwebsocketserver.cpp
@@ -77,6 +77,9 @@
QWebSocketServer only supports version 13 of the WebSocket protocol, as outlined in \l{RFC 6455}.
+ There is a default connection handshake timeout of 10 seconds to avoid denial of service,
+ which can be customized using setHandshakeTimeout().
+
\sa {WebSocket Server Example}, QWebSocket
*/
@@ -350,6 +353,32 @@ int QWebSocketServer::maxPendingConnections() const
}
/*!
+ \fn std::chrono::milliseconds QWebSocketServer::handshakeTimeout() const
+ Returns the handshake timeout for new connections in milliseconds.
+
+ The default is 10 seconds. If a peer uses more time to complete the
+ handshake their connection is closed.
+
+ \sa setHandshakeTimeout(), handshakeTimeoutMS()
+ \since 5.14
+ */
+
+/*!
+ Returns the handshake timeout for new connections in milliseconds.
+
+ The default is 10 seconds. If a peer uses more time to complete the
+ handshake their connection is closed.
+
+ \sa setHandshakeTimeout(), handshakeTimeout()
+ \since 5.14
+ */
+int QWebSocketServer::handshakeTimeoutMS() const
+{
+ Q_D(const QWebSocketServer);
+ return d->handshakeTimeout();
+}
+
+/*!
Returns the next pending connection as a connected QWebSocket object.
QWebSocketServer does not take ownership of the returned QWebSocket object.
It is up to the caller to delete the object explicitly when it will no longer be used,
@@ -574,6 +603,27 @@ void QWebSocketServer::setMaxPendingConnections(int numConnections)
d->setMaxPendingConnections(numConnections);
}
+/*!
+ \fn void QWebSocketServer::setHandshakeTimeout(std::chrono::milliseconds msec)
+ Sets the handshake timeout for new connections to \a msec milliseconds.
+
+ By default this is set to 10 seconds. If a peer uses more time to
+ complete the handshake, their connection is closed. You can pass a
+ negative value (e.g. -1) to disable the timeout.
+
+ \sa handshakeTimeout(), handshakeTimeoutMS()
+ \since 5.14
+ */
+
+/*!
+ \overload
+*/
+void QWebSocketServer::setHandshakeTimeout(int msec)
+{
+ Q_D(QWebSocketServer);
+ d->setHandshakeTimeout(msec);
+}
+
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
/*!
Sets the socket descriptor this server should use when listening for incoming connections to
diff --git a/src/websockets/qwebsocketserver.h b/src/websockets/qwebsocketserver.h
index 17d5376..ceb9106 100644
--- a/src/websockets/qwebsocketserver.h
+++ b/src/websockets/qwebsocketserver.h
@@ -52,6 +52,10 @@
#include <QtNetwork/QSslError>
#endif
+#if QT_HAS_INCLUDE(<chrono>)
+#include <chrono>
+#endif
+
QT_BEGIN_NAMESPACE
class QTcpSocket;
@@ -86,6 +90,19 @@ public:
void setMaxPendingConnections(int numConnections);
int maxPendingConnections() const;
+#if QT_HAS_INCLUDE(<chrono>) || defined(Q_CLANG_QDOC)
+ void setHandshakeTimeout(std::chrono::milliseconds msec)
+ {
+ setHandshakeTimeout(int(msec.count()));
+ }
+ std::chrono::milliseconds handshakeTimeout() const
+ {
+ return std::chrono::milliseconds(handshakeTimeoutMS());
+ }
+#endif
+ void setHandshakeTimeout(int msec);
+ int handshakeTimeoutMS() const;
+
quint16 serverPort() const;
QHostAddress serverAddress() const;
QUrl serverUrl() const;
diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp
index 3a38c4b..8225b59 100644
--- a/src/websockets/qwebsocketserver_p.cpp
+++ b/src/websockets/qwebsocketserver_p.cpp
@@ -49,6 +49,7 @@
#include "qwebsocket_p.h"
#include "qwebsocketcorsauthenticator.h"
+#include <QtCore/QTimer>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QNetworkProxy>
@@ -73,7 +74,8 @@ QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName,
m_pendingConnections(),
m_error(QWebSocketProtocol::CloseCodeNormal),
m_errorString(),
- m_maxPendingConnections(30)
+ m_maxPendingConnections(30),
+ m_handshakeTimeout(10000)
{}
/*!
@@ -97,6 +99,8 @@ void QWebSocketServerPrivate::init()
QObjectPrivate::connect(pSslServer, &QSslServer::newEncryptedConnection,
this, &QWebSocketServerPrivate::onNewConnection,
Qt::QueuedConnection);
+ QObjectPrivate::connect(pSslServer, &QSslServer::startedEncryptionHandshake,
+ this, &QWebSocketServerPrivate::startHandshakeTimeout);
QObject::connect(pSslServer, &QSslServer::peerVerifyError,
q, &QWebSocketServer::peerVerifyError);
QObject::connect(pSslServer, &QSslServer::sslErrors,
@@ -381,8 +385,12 @@ void QWebSocketServerPrivate::setError(QWebSocketProtocol::CloseCode code, const
*/
void QWebSocketServerPrivate::onNewConnection()
{
- while (m_pTcpServer->hasPendingConnections())
- handleConnection(m_pTcpServer->nextPendingConnection());
+ while (m_pTcpServer->hasPendingConnections()) {
+ QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection();
+ if (Q_LIKELY(pTcpSocket) && m_secureMode == NonSecureMode)
+ startHandshakeTimeout(pTcpSocket);
+ handleConnection(pTcpSocket);
+ }
}
/*!
@@ -390,8 +398,10 @@ void QWebSocketServerPrivate::onNewConnection()
*/
void QWebSocketServerPrivate::onSocketDisconnected()
{
- if (Q_LIKELY(currentSender)) {
- QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(currentSender->sender);
+ Q_Q(QWebSocketServer);
+ QObject *sender = q->sender();
+ if (Q_LIKELY(sender)) {
+ QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender);
if (Q_LIKELY(pTcpSocket))
pTcpSocket->deleteLater();
}
@@ -402,10 +412,12 @@ void QWebSocketServerPrivate::onSocketDisconnected()
*/
void QWebSocketServerPrivate::handshakeReceived()
{
- if (Q_UNLIKELY(!currentSender)) {
+ Q_Q(QWebSocketServer);
+ QObject *sender = q->sender();
+ if (Q_UNLIKELY(!sender)) {
return;
}
- QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(currentSender->sender);
+ QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender);
if (Q_UNLIKELY(!pTcpSocket)) {
return;
}
@@ -423,7 +435,6 @@ void QWebSocketServerPrivate::handshakeReceived()
}
disconnect(pTcpSocket, &QTcpSocket::readyRead,
this, &QWebSocketServerPrivate::handshakeReceived);
- Q_Q(QWebSocketServer);
bool success = false;
bool isSecure = (m_secureMode == SecureMode);
@@ -460,6 +471,7 @@ void QWebSocketServerPrivate::handshakeReceived()
request,
response);
if (pWebSocket) {
+ finishHandshakeTimeout(pTcpSocket);
addPendingConnection(pWebSocket);
Q_EMIT q->newConnection();
success = true;
@@ -499,4 +511,26 @@ void QWebSocketServerPrivate::handleConnection(QTcpSocket *pTcpSocket) const
}
}
+void QWebSocketServerPrivate::startHandshakeTimeout(QTcpSocket *pTcpSocket)
+{
+ if (m_handshakeTimeout < 0)
+ return;
+
+ QTimer *handshakeTimer = new QTimer(pTcpSocket);
+ handshakeTimer->setSingleShot(true);
+ handshakeTimer->setObjectName(QStringLiteral("handshakeTimer"));
+ QObject::connect(handshakeTimer, &QTimer::timeout, [=]() {
+ pTcpSocket->close();
+ });
+ handshakeTimer->start(m_handshakeTimeout);
+}
+
+void QWebSocketServerPrivate::finishHandshakeTimeout(QTcpSocket *pTcpSocket)
+{
+ if (QTimer *handshakeTimer = pTcpSocket->findChild<QTimer *>(QStringLiteral("handshakeTimer"))) {
+ handshakeTimer->stop();
+ delete handshakeTimer;
+ }
+}
+
QT_END_NAMESPACE
diff --git a/src/websockets/qwebsocketserver_p.h b/src/websockets/qwebsocketserver_p.h
index be7744f..1f1cbd9 100644
--- a/src/websockets/qwebsocketserver_p.h
+++ b/src/websockets/qwebsocketserver_p.h
@@ -90,6 +90,9 @@ public:
bool isListening() const;
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
int maxPendingConnections() const;
+ int handshakeTimeout() const {
+ return m_handshakeTimeout;
+ }
virtual QWebSocket *nextPendingConnection();
void pauseAccepting();
#ifndef QT_NO_NETWORKPROXY
@@ -101,6 +104,9 @@ public:
QWebSocketProtocol::CloseCode serverError() const;
quint16 serverPort() const;
void setMaxPendingConnections(int numConnections);
+ void setHandshakeTimeout(int msec) {
+ m_handshakeTimeout = msec;
+ }
bool setSocketDescriptor(qintptr socketDescriptor);
qintptr socketDescriptor() const;
@@ -122,6 +128,9 @@ public:
void handleConnection(QTcpSocket *pTcpSocket) const;
+private slots:
+ void startHandshakeTimeout(QTcpSocket *pTcpSocket);
+
private:
QTcpServer *m_pTcpServer;
QString m_serverName;
@@ -130,6 +139,7 @@ private:
QWebSocketProtocol::CloseCode m_error;
QString m_errorString;
int m_maxPendingConnections;
+ int m_handshakeTimeout;
void addPendingConnection(QWebSocket *pWebSocket);
void setErrorFromSocketError(QAbstractSocket::SocketError error,
@@ -138,6 +148,7 @@ private:
void onNewConnection();
void onSocketDisconnected();
void handshakeReceived();
+ void finishHandshakeTimeout(QTcpSocket *pTcpSocket);
};
QT_END_NAMESPACE
diff --git a/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp b/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp
index f9a91d5..5390ff0 100644
--- a/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp
+++ b/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp
@@ -193,7 +193,7 @@ private:
//sequences
void nonCharacterSequence(const char *sequence);
- void doTest();
+ void doTest(int timeout = 0);
void doCloseFrameTest();
QString opCodeToString(quint8 opCode);
@@ -744,6 +744,7 @@ void tst_DataProcessor::frameTooSmall()
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), 7000);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(closeSpy.count(), 0);
QCOMPARE(pingMessageSpy.count(), 0);
@@ -776,6 +777,7 @@ void tst_DataProcessor::frameTooSmall()
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), 7000);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(closeSpy.count(), 0);
QCOMPARE(pingMessageSpy.count(), 0);
@@ -808,6 +810,24 @@ void tst_DataProcessor::frameTooSmall()
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), 7000);
+ QCOMPARE(errorSpy.count(), 1);
+ QCOMPARE(closeSpy.count(), 0);
+ QCOMPARE(pingMessageSpy.count(), 0);
+ QCOMPARE(pongMessageSpy.count(), 0);
+ QCOMPARE(textMessageSpy.count(), 0);
+ QCOMPARE(binaryMessageSpy.count(), 0);
+ QCOMPARE(textFrameSpy.count(), 1);
+ QCOMPARE(binaryFrameSpy.count(), 0);
+
+ errorSpy.clear();
+ closeSpy.clear();
+ pingMessageSpy.clear();
+ pongMessageSpy.clear();
+ textMessageSpy.clear();
+ binaryMessageSpy.clear();
+ textFrameSpy.clear();
+ binaryFrameSpy.clear();
buffer.close();
data.clear();
@@ -816,17 +836,16 @@ void tst_DataProcessor::frameTooSmall()
//meaning the socket will be closed
buffer.setData(data);
buffer.open(QIODevice::ReadOnly);
- QSignalSpy errorSpy(&dataProcessor,
- SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), 7000);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(closeSpy.count(), 0);
QCOMPARE(pingMessageSpy.count(), 0);
QCOMPARE(pongMessageSpy.count(), 0);
QCOMPARE(textMessageSpy.count(), 0);
QCOMPARE(binaryMessageSpy.count(), 0);
- QCOMPARE(textFrameSpy.count(), 1);
+ QCOMPARE(textFrameSpy.count(), 0);
QCOMPARE(binaryFrameSpy.count(), 0);
QList<QVariant> arguments = errorSpy.takeFirst();
@@ -849,6 +868,7 @@ void tst_DataProcessor::frameTooSmall()
buffer.open(QIODevice::ReadOnly);
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), 7000);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(closeSpy.count(), 0);
QCOMPARE(pingMessageSpy.count(), 0);
@@ -877,6 +897,7 @@ void tst_DataProcessor::frameTooSmall()
buffer.open(QIODevice::ReadOnly);
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), 7000);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(closeSpy.count(), 0);
QCOMPARE(pingMessageSpy.count(), 0);
@@ -1400,7 +1421,7 @@ void tst_DataProcessor::incompletePayload_data()
void tst_DataProcessor::incompletePayload()
{
- doTest();
+ doTest(7000);
}
void tst_DataProcessor::incompleteSizeField_data()
@@ -1430,13 +1451,13 @@ void tst_DataProcessor::incompleteSizeField_data()
void tst_DataProcessor::incompleteSizeField()
{
- doTest();
+ doTest(7000);
}
//////////////////////////////////////////////////////////////////////////////////////////
/// HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////
-void tst_DataProcessor::doTest()
+void tst_DataProcessor::doTest(int timeout)
{
QFETCH(quint8, firstByte);
QFETCH(quint8, secondByte);
@@ -1465,6 +1486,7 @@ void tst_DataProcessor::doTest()
buffer.setData(data);
buffer.open(QIODevice::ReadOnly);
dataProcessor.process(&buffer);
+ QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count(), timeout);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(textMessageSpy.count(), 0);
QCOMPARE(binaryMessageSpy.count(), 0);
diff --git a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
index 2bb5d16..71e1262 100644
--- a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
+++ b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
@@ -457,12 +457,46 @@ void tst_QWebSocket::tst_sendTextMessage()
QVERIFY(isLastFrame);
socket.close();
+ socketConnectedSpy.clear();
+ textMessageReceived.clear();
+ textFrameReceived.clear();
- //QTBUG-36762: QWebSocket emits multiplied signals when socket was reopened
+ // QTBUG-74464 QWebsocket doesn't receive text (binary) message with size > 32 kb
+ socket.open(url);
+
+ QTRY_COMPARE(socketConnectedSpy.count(), 1);
+ QCOMPARE(socketError.count(), 0);
+ QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
+ arguments = serverConnectedSpy.takeFirst();
+ urlConnected = arguments.at(0).toUrl();
+ QCOMPARE(urlConnected, url);
+ QCOMPARE(socket.bytesToWrite(), 0);
+
+ // transmit a long text message with 1 MB
+ QString longString(0x100000, 'a');
+ socket.sendTextMessage(longString);
+ QVERIFY(socket.bytesToWrite() > longString.length());
+ QVERIFY(textMessageReceived.wait());
+ QCOMPARE(socket.bytesToWrite(), 0);
+
+ QCOMPARE(textMessageReceived.count(), 1);
+ QCOMPARE(binaryMessageReceived.count(), 0);
+ QCOMPARE(binaryFrameReceived.count(), 0);
+ arguments = textMessageReceived.takeFirst();
+ messageReceived = arguments.at(0).toString();
+ QCOMPARE(messageReceived.length(), longString.length());
+ QCOMPARE(messageReceived, longString);
+
+ arguments = textFrameReceived.takeLast();
+ isLastFrame = arguments.at(1).toBool();
+ QVERIFY(isLastFrame);
+
+ socket.close();
socketConnectedSpy.clear();
textMessageReceived.clear();
textFrameReceived.clear();
+ //QTBUG-36762: QWebSocket emits multiplied signals when socket was reopened
socket.open(QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() +
QStringLiteral(":") + QString::number(echoServer.port())));
diff --git a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp
index 64b2489..c40bb01 100644
--- a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp
+++ b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp
@@ -28,7 +28,9 @@
#include <QString>
#include <QtTest>
#include <QNetworkProxy>
+#include <QTcpSocket>
#include <QTcpServer>
+#include <QtCore/QScopedPointer>
#ifndef QT_NO_OPENSSL
#include <QtNetwork/qsslpresharedkeyauthenticator.h>
#endif
@@ -113,6 +115,8 @@ private Q_SLOTS:
void tst_serverDestroyedWhileSocketConnected();
void tst_scheme(); // qtbug-55927
void tst_handleConnection();
+ void tst_handshakeTimeout(); // qtbug-63312, qtbug-57026
+ void multipleFrames();
private:
bool m_shouldSkipUnsupportedIpv6Test;
@@ -290,6 +294,18 @@ void tst_QWebSocketServer::tst_settersAndGetters()
QCOMPARE(sslServer.sslConfiguration(), sslConfiguration);
QVERIFY(sslServer.sslConfiguration() != QSslConfiguration::defaultConfiguration());
#endif
+
+ server.setHandshakeTimeout(64);
+ QCOMPARE(server.handshakeTimeoutMS(), 64);
+#if QT_HAS_INCLUDE(<chrono>)
+ auto expected = std::chrono::milliseconds(64);
+ QCOMPARE(server.handshakeTimeout(), expected);
+
+ expected = std::chrono::milliseconds(242);
+ server.setHandshakeTimeout(expected);
+ QCOMPARE(server.handshakeTimeoutMS(), 242);
+ QCOMPARE(server.handshakeTimeout(), expected);
+#endif
}
void tst_QWebSocketServer::tst_listening()
@@ -387,7 +403,7 @@ void tst_QWebSocketServer::tst_preSharedKey()
QWebSocketServer server(QString(), QWebSocketServer::SecureMode);
bool cipherFound = false;
- const QList<QSslCipher> supportedCiphers = QSslSocket::supportedCiphers();
+ const QList<QSslCipher> supportedCiphers = QSslConfiguration::supportedCiphers();
for (const QSslCipher &cipher : supportedCiphers) {
if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) {
cipherFound = true;
@@ -573,6 +589,26 @@ void tst_QWebSocketServer::tst_serverDestroyedWhileSocketConnected()
QTRY_COMPARE(socketDisconnectedSpy.count(), 1);
}
+#ifndef QT_NO_SSL
+static void setupSecureServer(QWebSocketServer *secureServer)
+{
+ 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);
+}
+#endif
+
void tst_QWebSocketServer::tst_scheme()
{
if (m_shouldSkipUnsupportedIpv6Test)
@@ -594,20 +630,9 @@ void tst_QWebSocketServer::tst_scheme()
#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);
+ setupSecureServer(&secureServer);
+ if (QTest::currentTestFailed())
+ return;
QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection()));
QVERIFY(secureServer.listen());
@@ -668,6 +693,176 @@ void tst_QWebSocketServer::tst_handleConnection()
QCOMPARE(arguments.first().toString(), QString("hello"));
}
+struct SocketSpy {
+ QTcpSocket *socket;
+ QSignalSpy *disconnectSpy;
+ ~SocketSpy() {
+ delete socket;
+ delete disconnectSpy;
+ }
+};
+
+static void openManyConnections(QList<SocketSpy *> *sockets, quint16 port, int numConnections)
+{
+ for (int i = 0; i < numConnections; i++) {
+ QTcpSocket *c = new QTcpSocket;
+ QSignalSpy *spy = new QSignalSpy(c, &QTcpSocket::disconnected);
+
+ c->connectToHost("127.0.0.1", port);
+
+ sockets->append(new SocketSpy{c, spy});
+ }
+}
+
+// Sum the counts together, for better output on failure (e.g. "FAIL: Actual: 49, Expected: 50")
+static int sumSocketSpyCount(const QList<SocketSpy *> &sockets)
+{
+ return std::accumulate(sockets.cbegin(), sockets.cend(), 0, [](int c, SocketSpy *s) {
+ return c + s->disconnectSpy->count();
+ });
+}
+
+void tst_QWebSocketServer::tst_handshakeTimeout()
+{
+ { // No Timeout
+ QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
+ plainServer.setHandshakeTimeout(-1);
+ QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
+
+ QVERIFY(plainServer.listen());
+
+ QWebSocket socket;
+ socket.open(plainServer.serverUrl().toString());
+
+ QTRY_COMPARE(plainServerConnectionSpy.count(), 1);
+ QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection());
+ QVERIFY(!plainServerSocket.isNull());
+
+ plainServer.close();
+ }
+
+ { // Unencrypted
+ QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
+ plainServer.setHandshakeTimeout(500);
+ QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
+
+ QVERIFY(plainServer.listen());
+
+ /* QTcpServer has a default of 30 pending connections. The test checks
+ * whether, when that list is full, the connections are dropped after
+ * a timeout and later pending connections are processed. */
+ const int numConnections = 50;
+ QList<SocketSpy *> sockets;
+ auto cleaner = qScopeGuard([&sockets]() { qDeleteAll(sockets); });
+ openManyConnections(&sockets, plainServer.serverPort(), numConnections);
+
+ QCoreApplication::processEvents();
+
+ /* We have 50 plain TCP connections open, that are not proper websockets. */
+ QCOMPARE(plainServerConnectionSpy.count(), 0);
+
+ QWebSocket socket;
+ socket.open(plainServer.serverUrl().toString());
+
+ /* Check that a real websocket will be processed after some non-websocket
+ * TCP connections timeout. */
+ QTRY_COMPARE(plainServerConnectionSpy.count(), 1);
+ QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection());
+ QVERIFY(!plainServerSocket.isNull());
+
+ /* Check that all non websocket connections eventually timeout. */
+ QTRY_COMPARE(sumSocketSpyCount(sockets), numConnections);
+
+ plainServer.close();
+ }
+
+#if QT_CONFIG(ssl)
+ { // Encrypted
+ QWebSocketServer secureServer(QString(), QWebSocketServer::SecureMode);
+ setupSecureServer(&secureServer);
+ if (QTest::currentTestFailed())
+ return;
+ secureServer.setHandshakeTimeout(500);
+
+ QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection()));
+
+ QVERIFY(secureServer.listen());
+
+ const int numConnections = 50;
+ QList<SocketSpy *> sockets;
+ auto cleaner = qScopeGuard([&sockets]() { qDeleteAll(sockets); });
+ openManyConnections(&sockets, secureServer.serverPort(), numConnections);
+
+ QCoreApplication::processEvents();
+ QCOMPARE(secureServerConnectionSpy.count(), 0);
+
+ QWebSocket secureSocket;
+ QSslConfiguration config = secureSocket.sslConfiguration();
+ config.setPeerVerifyMode(QSslSocket::VerifyNone);
+ secureSocket.setSslConfiguration(config);
+
+ secureSocket.open(secureServer.serverUrl().toString());
+
+ QTRY_COMPARE(secureServerConnectionSpy.count(), 1);
+ QScopedPointer<QWebSocket> serverSocket(secureServer.nextPendingConnection());
+ QVERIFY(!serverSocket.isNull());
+
+ QTRY_COMPARE(sumSocketSpyCount(sockets), numConnections);
+
+ secureServer.close();
+ }
+#endif
+
+ { // Ensure properly handshaked connections are not timed out
+ QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
+ plainServer.setHandshakeTimeout(250);
+ QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
+
+ QVERIFY(plainServer.listen());
+
+ QWebSocket socket;
+ QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
+ QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected);
+ socket.open(plainServer.serverUrl().toString());
+
+ QTRY_COMPARE(plainServerConnectionSpy.count(), 1);
+ QTRY_COMPARE(socketConnectedSpy.count(), 1);
+
+ QEventLoop loop;
+ QTimer::singleShot(500, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ QCOMPARE(socketDisconnectedSpy.count(), 0);
+ }
+}
+
+void tst_QWebSocketServer::multipleFrames()
+{
+ QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
+ QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection);
+ QVERIFY(server.listen());
+
+ QWebSocket socket;
+ QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
+ QSignalSpy messageReceivedSpy(&socket, &QWebSocket::binaryMessageReceived);
+ socket.open(server.serverUrl().toString());
+
+ QVERIFY(serverConnectionSpy.wait());
+ QVERIFY(socketConnectedSpy.wait());
+
+ auto serverSocket = std::unique_ptr<QWebSocket>(server.nextPendingConnection());
+ QVERIFY(serverSocket);
+ for (int i = 0; i < 10; i++)
+ serverSocket->sendBinaryMessage(QByteArray("abc"));
+ if (serverSocket->bytesToWrite())
+ QVERIFY(serverSocket->flush());
+
+ QVERIFY(messageReceivedSpy.wait());
+ // Since there's no guarantee the operating system will fit all 10 websocket frames into 1 tcp
+ // frame, let's just assume it will do at least 2. EXCEPT_FAIL any which doesn't merge any.
+ QVERIFY2(messageReceivedSpy.count() > 1, "Received only 1 message in the TCP frame!");
+}
+
QTEST_MAIN(tst_QWebSocketServer)
#include "tst_qwebsocketserver.moc"
diff --git a/tests/auto/websockets/websocketframe/tst_websocketframe.cpp b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp
index 5614df8..6b9aaaf 100644
--- a/tests/auto/websockets/websocketframe/tst_websocketframe.cpp
+++ b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp
@@ -197,7 +197,8 @@ void tst_WebSocketFrame::tst_copyConstructorAndAssignment()
QBuffer buffer(&payload);
buffer.open(QIODevice::ReadOnly);
- QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer);
+ QWebSocketFrame frame;
+ frame.readFrame(&buffer);
buffer.close();
{
@@ -330,7 +331,8 @@ void tst_WebSocketFrame::tst_goodFrames()
QBuffer buffer;
buffer.setData(wireRepresentation);
buffer.open(QIODevice::ReadOnly);
- QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer);
+ QWebSocketFrame frame;
+ frame.readFrame(&buffer);
buffer.close();
QVERIFY(frame.isValid());
QCOMPARE(frame.rsv1(), rsv1);
@@ -495,7 +497,8 @@ void tst_WebSocketFrame::tst_invalidFrames()
QBuffer buffer;
buffer.setData(wireRepresentation);
buffer.open(QIODevice::ReadOnly);
- QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer);
+ QWebSocketFrame frame;
+ frame.readFrame(&buffer);
buffer.close();
QVERIFY(!frame.isValid());
@@ -606,7 +609,8 @@ void tst_WebSocketFrame::tst_malformedFrames()
QBuffer buffer;
buffer.setData(payload);
buffer.open(QIODevice::ReadOnly);
- QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer);
+ QWebSocketFrame frame;
+ frame.readFrame(&buffer);
buffer.close();
QVERIFY(!frame.isValid());