diff options
-rw-r--r-- | .qmake.conf | 2 | ||||
-rw-r--r-- | examples/websockets/sslechoserver/sslechoserver.cpp | 1 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/plugins.qmltypes | 2 | ||||
-rw-r--r-- | src/websockets/qwebsocket.cpp | 111 | ||||
-rw-r--r-- | src/websockets/qwebsocket.h | 11 | ||||
-rw-r--r-- | src/websockets/qwebsocket_p.cpp | 105 | ||||
-rw-r--r-- | src/websockets/qwebsocket_p.h | 17 | ||||
-rw-r--r-- | src/websockets/qwebsocketdataprocessor.cpp | 31 | ||||
-rw-r--r-- | src/websockets/qwebsocketdataprocessor_p.h | 7 | ||||
-rw-r--r-- | src/websockets/qwebsocketframe.cpp | 113 | ||||
-rw-r--r-- | src/websockets/qwebsocketframe_p.h | 42 | ||||
-rw-r--r-- | src/websockets/qwebsockethandshakerequest.cpp | 15 | ||||
-rw-r--r-- | src/websockets/qwebsockethandshakerequest_p.h | 2 | ||||
-rw-r--r-- | src/websockets/qwebsocketserver_p.cpp | 49 | ||||
-rw-r--r-- | tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp | 90 | ||||
-rw-r--r-- | tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp | 8 | ||||
-rw-r--r-- | tests/auto/websockets/websocketframe/tst_websocketframe.cpp | 28 |
17 files changed, 446 insertions, 188 deletions
diff --git a/.qmake.conf b/.qmake.conf index 5c26162..1121008 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.14.2 +MODULE_VERSION = 5.15.0 diff --git a/examples/websockets/sslechoserver/sslechoserver.cpp b/examples/websockets/sslechoserver/sslechoserver.cpp index 00bc6c1..5dc41e0 100644 --- a/examples/websockets/sslechoserver/sslechoserver.cpp +++ b/examples/websockets/sslechoserver/sslechoserver.cpp @@ -77,7 +77,6 @@ SslEchoServer::SslEchoServer(quint16 port, QObject *parent) : sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); sslConfiguration.setLocalCertificate(certificate); sslConfiguration.setPrivateKey(sslKey); - sslConfiguration.setProtocol(QSsl::TlsV1SslV3); m_pWebSocketServer->setSslConfiguration(sslConfiguration); if (m_pWebSocketServer->listen(QHostAddress::Any, port)) diff --git a/src/imports/qmlwebsockets/plugins.qmltypes b/src/imports/qmlwebsockets/plugins.qmltypes index f1a5a2e..cabe5a2 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.14' +// 'qmlplugindump -nonrelocatable -dependencies dependencies.json QtWebSockets 1.15' Module { dependencies: [] diff --git a/src/websockets/qwebsocket.cpp b/src/websockets/qwebsocket.cpp index ade1eb4..144268f 100644 --- a/src/websockets/qwebsocket.cpp +++ b/src/websockets/qwebsocket.cpp @@ -788,4 +788,115 @@ qint64 QWebSocket::bytesToWrite() const return d->m_pSocket ? d->m_pSocket->bytesToWrite() : 0; } +/*! + \since 5.15 + Sets the maximum allowed size of an incoming websocket frame to \a maxAllowedIncomingFrameSize. + If an incoming frame exceeds this limit, the peer gets disconnected. + The accepted range is between 0 and maxIncomingFrameSize(), default is maxIncomingFrameSize(). + The purpose of this function is to avoid exhausting virtual memory. + + \sa maxAllowedIncomingFrameSize() + */ +void QWebSocket::setMaxAllowedIncomingFrameSize(quint64 maxAllowedIncomingFrameSize) +{ + Q_D(QWebSocket); + d->setMaxAllowedIncomingFrameSize(maxAllowedIncomingFrameSize); +} + +/*! + \since 5.15 + Returns the maximum allowed size of an incoming websocket frame. + + \sa setMaxAllowedIncomingFrameSize() + */ +quint64 QWebSocket::maxAllowedIncomingFrameSize() const +{ + Q_D(const QWebSocket); + return d->maxAllowedIncomingFrameSize(); +} + +/*! + \since 5.15 + Sets the maximum allowed size of an incoming websocket message to \a maxAllowedIncomingMessageSize. + If an incoming message exceeds this limit, the peer gets disconnected. + The accepted range is between 0 and maxIncomingMessageSize(), default is maxIncomingMessageSize(). + The purpose of this function is to avoid exhausting virtual memory. + + \sa maxAllowedIncomingMessageSize() + */ +void QWebSocket::setMaxAllowedIncomingMessageSize(quint64 maxAllowedIncomingMessageSize) +{ + Q_D(QWebSocket); + d->setMaxAllowedIncomingMessageSize(maxAllowedIncomingMessageSize); +} + +/*! + \since 5.15 + Returns the maximum allowed size of an incoming websocket message. + + \sa setMaxAllowedIncomingMessageSize() + */ +quint64 QWebSocket::maxAllowedIncomingMessageSize() const +{ + Q_D(const QWebSocket); + return d->maxAllowedIncomingMessageSize(); +} + +/*! + \since 5.15 + Returns the maximum supported size of an incoming websocket message for this websocket + implementation. + */ +quint64 QWebSocket::maxIncomingMessageSize() +{ + return QWebSocketPrivate::maxIncomingMessageSize(); +} + +/*! + \since 5.15 + Returns the maximum supported size of an incoming websocket frame for this websocket + implementation. + */ +quint64 QWebSocket::maxIncomingFrameSize() +{ + return QWebSocketPrivate::maxIncomingFrameSize(); +} + +/*! + \since 5.15 + Sets the maximum size of an outgoing websocket frame to \a outgoingFrameSize. + The accepted range is between 0 and maxOutgoingFrameSize(), default is 512kB. + The purpose of this function is to adapt to the maximum allowed frame size + of the receiver. + + \sa outgoingFrameSize() + */ +void QWebSocket::setOutgoingFrameSize(quint64 outgoingFrameSize) +{ + Q_D(QWebSocket); + d->setOutgoingFrameSize(outgoingFrameSize); +} + +/*! + \since 5.15 + Returns the maximum size of an outgoing websocket frame. + + \sa setOutgoingFrameSize() + */ +quint64 QWebSocket::outgoingFrameSize() const +{ + Q_D(const QWebSocket); + return d->outgoingFrameSize(); +} + +/*! + \since 5.15 + Returns the maximum supported size of an outgoing websocket frame for this websocket + implementation. + */ +quint64 QWebSocket::maxOutgoingFrameSize() +{ + return QWebSocketPrivate::maxOutgoingFrameSize(); +} + QT_END_NAMESPACE diff --git a/src/websockets/qwebsocket.h b/src/websockets/qwebsocket.h index 4944689..fad565c 100644 --- a/src/websockets/qwebsocket.h +++ b/src/websockets/qwebsocket.h @@ -115,6 +115,17 @@ public: qint64 bytesToWrite() const; + void setMaxAllowedIncomingFrameSize(quint64 maxAllowedIncomingFrameSize); + quint64 maxAllowedIncomingFrameSize() const; + void setMaxAllowedIncomingMessageSize(quint64 maxAllowedIncomingMessageSize); + quint64 maxAllowedIncomingMessageSize() const; + static quint64 maxIncomingMessageSize(); + static quint64 maxIncomingFrameSize(); + + void setOutgoingFrameSize(quint64 outgoingFrameSize); + quint64 outgoingFrameSize() const; + static quint64 maxOutgoingFrameSize(); + public Q_SLOTS: void close(QWebSocketProtocol::CloseCode closeCode = QWebSocketProtocol::CloseCodeNormal, const QString &reason = QString()); diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp index bca4ccd..659e283 100644 --- a/src/websockets/qwebsocket_p.cpp +++ b/src/websockets/qwebsocket_p.cpp @@ -69,7 +69,8 @@ QT_BEGIN_NAMESPACE -const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message +const quint64 MAX_OUTGOING_FRAME_SIZE_IN_BYTES = std::numeric_limits<int>::max() - 1; +const quint64 DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //default size of a frame when sending a message QWebSocketConfiguration::QWebSocketConfiguration() : #ifndef QT_NO_SSL @@ -111,7 +112,8 @@ QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol:: m_configuration(), m_pMaskGenerator(&m_defaultMaskGenerator), m_defaultMaskGenerator(), - m_handshakeState(NothingDoneState) + m_handshakeState(NothingDoneState), + m_outgoingFrameSize(DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES) { m_pingTimer.start(); } @@ -143,7 +145,8 @@ QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol: m_configuration(), m_pMaskGenerator(&m_defaultMaskGenerator), m_defaultMaskGenerator(), - m_handshakeState(NothingDoneState) + m_handshakeState(NothingDoneState), + m_outgoingFrameSize(DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES) { m_pingTimer.start(); } @@ -569,15 +572,14 @@ void QWebSocketPrivate::enableMasking(bool enable) /*! * \internal */ -void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) +void QWebSocketPrivate::makeConnections(QTcpSocket *pTcpSocket) { Q_ASSERT(pTcpSocket); Q_Q(QWebSocket); if (Q_LIKELY(pTcpSocket)) { //pass through signals - QObject::connect(pTcpSocket, - QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), + QObject::connect(pTcpSocket, &QAbstractSocket::errorOccurred, q, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error)); #ifndef QT_NO_NETWORKPROXY QObject::connect(pTcpSocket, &QAbstractSocket::proxyAuthenticationRequired, q, @@ -636,6 +638,10 @@ void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) &QWebSocketPrivate::processPong); QObjectPrivate::connect(&m_dataProcessor, &QWebSocketDataProcessor::closeReceived, this, &QWebSocketPrivate::processClose); + + //fire readyread, in case we already have data inside the tcpSocket + if (pTcpSocket->bytesAvailable()) + Q_EMIT pTcpSocket->readyRead(); } /*! @@ -772,11 +778,11 @@ qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) const QWebSocketProtocol::OpCode firstOpCode = isBinary ? QWebSocketProtocol::OpCodeBinary : QWebSocketProtocol::OpCodeText; - int numFrames = data.size() / int(FRAME_SIZE_IN_BYTES); + int numFrames = data.size() / int(outgoingFrameSize()); QByteArray tmpData(data); tmpData.detach(); char *payload = tmpData.data(); - quint64 sizeLeft = quint64(data.size()) % FRAME_SIZE_IN_BYTES; + quint64 sizeLeft = quint64(data.size()) % outgoingFrameSize(); if (Q_LIKELY(sizeLeft)) ++numFrames; @@ -795,7 +801,7 @@ qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) const bool isLastFrame = (i == (numFrames - 1)); const bool isFirstFrame = (i == 0); - const quint64 size = qMin(bytesLeft, FRAME_SIZE_IN_BYTES); + const quint64 size = qMin(bytesLeft, outgoingFrameSize()); const QWebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode : QWebSocketProtocol::OpCodeContinue; @@ -1000,8 +1006,8 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) errorDescription = QWebSocket::tr("Malformed header in response: %1.").arg(headerLine); break; } - lastHeader = m_headers.insertMulti(headerLine.left(colonPos).trimmed().toLower(), - headerLine.mid(colonPos + 1).trimmed()); + lastHeader = m_headers.insert(headerLine.left(colonPos).trimmed().toLower(), + headerLine.mid(colonPos + 1).trimmed()); } } @@ -1048,8 +1054,7 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) } else if (m_httpStatusCode == 400) { //HTTP/1.1 400 Bad Request if (!version.isEmpty()) { - const QStringList versions = version.split(QStringLiteral(", "), - QString::SkipEmptyParts); + const QStringList versions = version.split(QStringLiteral(", "), Qt::SkipEmptyParts); if (!versions.contains(QString::number(QWebSocketProtocol::currentVersion()))) { //if needed to switch protocol version, then we are finished here //because we cannot handle other protocols than the RFC one (v13) @@ -1304,6 +1309,80 @@ void QWebSocketPrivate::setSocketState(QAbstractSocket::SocketState state) /*! \internal */ +void QWebSocketPrivate::setMaxAllowedIncomingFrameSize(quint64 maxAllowedIncomingFrameSize) +{ + m_dataProcessor.setMaxAllowedFrameSize(maxAllowedIncomingFrameSize); +} + +/*! + \internal + */ +quint64 QWebSocketPrivate::maxAllowedIncomingFrameSize() const +{ + return m_dataProcessor.maxAllowedFrameSize(); +} + +/*! + \internal + */ +void QWebSocketPrivate::setMaxAllowedIncomingMessageSize(quint64 maxAllowedIncomingMessageSize) +{ + m_dataProcessor.setMaxAllowedMessageSize(maxAllowedIncomingMessageSize); +} + +/*! + \internal + */ +quint64 QWebSocketPrivate::maxAllowedIncomingMessageSize() const +{ + return m_dataProcessor.maxAllowedMessageSize(); +} + +/*! + \internal + */ +quint64 QWebSocketPrivate::maxIncomingMessageSize() +{ + return QWebSocketDataProcessor::maxMessageSize(); +} + +/*! + \internal + */ +quint64 QWebSocketPrivate::maxIncomingFrameSize() +{ + return QWebSocketDataProcessor::maxFrameSize(); +} + +/*! + \internal + */ +void QWebSocketPrivate::setOutgoingFrameSize(quint64 outgoingFrameSize) +{ + if (outgoingFrameSize <= maxOutgoingFrameSize()) + m_outgoingFrameSize = outgoingFrameSize; +} + +/*! + \internal + */ +quint64 QWebSocketPrivate::outgoingFrameSize() const +{ + return m_outgoingFrameSize; +} + +/*! + \internal + */ +quint64 QWebSocketPrivate::maxOutgoingFrameSize() +{ + return MAX_OUTGOING_FRAME_SIZE_IN_BYTES; +} + + +/*! + \internal + */ void QWebSocketPrivate::setErrorString(const QString &errorString) { if (m_errorString != errorString) diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h index e72daa5..640e7b2 100644 --- a/src/websockets/qwebsocket_p.h +++ b/src/websockets/qwebsocket_p.h @@ -160,6 +160,17 @@ public: void ping(const QByteArray &payload); void setSocketState(QAbstractSocket::SocketState state); + void setMaxAllowedIncomingFrameSize(quint64 maxAllowedIncomingFrameSize); + quint64 maxAllowedIncomingFrameSize() const; + void setMaxAllowedIncomingMessageSize(quint64 maxAllowedIncomingMessageSize); + quint64 maxAllowedIncomingMessageSize() const; + static quint64 maxIncomingMessageSize(); + static quint64 maxIncomingFrameSize(); + + void setOutgoingFrameSize(quint64 outgoingFrameSize); + quint64 outgoingFrameSize() const; + static quint64 maxOutgoingFrameSize(); + private: QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version); void setVersion(QWebSocketProtocol::Version version); @@ -182,7 +193,7 @@ private: Q_REQUIRED_RESULT qint64 doWriteFrames(const QByteArray &data, bool isBinary); - void makeConnections(const QTcpSocket *pTcpSocket); + void makeConnections(QTcpSocket *pTcpSocket); void releaseConnections(const QTcpSocket *pTcpSocket); QByteArray getFrameHeader(QWebSocketProtocol::OpCode opCode, quint64 payloadLength, @@ -248,7 +259,9 @@ private: int m_httpStatusCode; int m_httpMajorVersion, m_httpMinorVersion; QString m_httpStatusMessage; - QMap<QString, QString> m_headers; + QMultiMap<QString, QString> m_headers; + + quint64 m_outgoingFrameSize; friend class QWebSocketServerPrivate; #ifdef Q_OS_WASM diff --git a/src/websockets/qwebsocketdataprocessor.cpp b/src/websockets/qwebsocketdataprocessor.cpp index 191b992..4110f2a 100644 --- a/src/websockets/qwebsocketdataprocessor.cpp +++ b/src/websockets/qwebsocketdataprocessor.cpp @@ -105,6 +105,33 @@ QWebSocketDataProcessor::~QWebSocketDataProcessor() } } +void QWebSocketDataProcessor::setMaxAllowedFrameSize(quint64 maxAllowedFrameSize) +{ + frame.setMaxAllowedFrameSize(maxAllowedFrameSize); +} + +quint64 QWebSocketDataProcessor::maxAllowedFrameSize() const +{ + return frame.maxAllowedFrameSize(); +} + +/*! + \internal + */ +void QWebSocketDataProcessor::setMaxAllowedMessageSize(quint64 maxAllowedMessageSize) +{ + if (maxAllowedMessageSize <= maxMessageSize()) + m_maxAllowedMessageSize = maxAllowedMessageSize; +} + +/*! + \internal + */ +quint64 QWebSocketDataProcessor::maxAllowedMessageSize() const +{ + return m_maxAllowedMessageSize; +} + /*! \internal */ @@ -118,7 +145,7 @@ quint64 QWebSocketDataProcessor::maxMessageSize() */ quint64 QWebSocketDataProcessor::maxFrameSize() { - return MAX_FRAME_SIZE_IN_BYTES; + return QWebSocketFrame::maxFrameSize(); } /*! @@ -167,7 +194,7 @@ bool QWebSocketDataProcessor::process(QIODevice *pIoDevice) ? quint64(m_textMessage.length()) : quint64(m_binaryMessage.length()); if (Q_UNLIKELY((messageLength + quint64(frame.payload().length())) > - MAX_MESSAGE_SIZE_IN_BYTES)) { + maxAllowedMessageSize())) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData, tr("Received message is too big.")); diff --git a/src/websockets/qwebsocketdataprocessor_p.h b/src/websockets/qwebsocketdataprocessor_p.h index 03635b1..62a2dc0 100644 --- a/src/websockets/qwebsocketdataprocessor_p.h +++ b/src/websockets/qwebsocketdataprocessor_p.h @@ -65,6 +65,8 @@ QT_BEGIN_NAMESPACE class QIODevice; class QWebSocketFrame; +const quint64 MAX_MESSAGE_SIZE_IN_BYTES = std::numeric_limits<int>::max() - 1; + class Q_AUTOTEST_EXPORT QWebSocketDataProcessor : public QObject { Q_OBJECT @@ -74,6 +76,10 @@ public: explicit QWebSocketDataProcessor(QObject *parent = nullptr); ~QWebSocketDataProcessor() override; + void setMaxAllowedFrameSize(quint64 maxAllowedFrameSize); + quint64 maxAllowedFrameSize() const; + void setMaxAllowedMessageSize(quint64 maxAllowedMessageSize); + quint64 maxAllowedMessageSize() const; static quint64 maxMessageSize(); static quint64 maxFrameSize(); @@ -115,6 +121,7 @@ private: QTextCodec *m_pTextCodec; QWebSocketFrame frame; QTimer waitTimer; + quint64 m_maxAllowedMessageSize = MAX_MESSAGE_SIZE_IN_BYTES; bool processControlFrame(const QWebSocketFrame &frame); void timeout(); diff --git a/src/websockets/qwebsocketframe.cpp b/src/websockets/qwebsocketframe.cpp index 11373a7..716aebd 100644 --- a/src/websockets/qwebsocketframe.cpp +++ b/src/websockets/qwebsocketframe.cpp @@ -64,123 +64,26 @@ QT_BEGIN_NAMESPACE /*! \internal */ -QWebSocketFrame::QWebSocketFrame() : - m_closeCode(QWebSocketProtocol::CloseCodeNormal), - m_closeReason(), - m_mask(0), - m_opCode(QWebSocketProtocol::OpCodeReservedC), - m_length(0), - m_payload(), - m_isFinalFrame(true), - m_rsv1(false), - m_rsv2(false), - m_rsv3(false), - m_isValid(false) +void QWebSocketFrame::setMaxAllowedFrameSize(quint64 maxAllowedFrameSize) { + if (maxAllowedFrameSize <= maxFrameSize()) + m_maxAllowedFrameSize = maxAllowedFrameSize; } /*! \internal */ -QWebSocketFrame::QWebSocketFrame(const QWebSocketFrame &other) : - m_closeCode(other.m_closeCode), - m_closeReason(other.m_closeReason), - m_mask(other.m_mask), - m_opCode(other.m_opCode), - m_length(other.m_length), - m_payload(other.m_payload), - m_isFinalFrame(other.m_isFinalFrame), - m_rsv1(other.m_rsv1), - m_rsv2(other.m_rsv2), - m_rsv3(other.m_rsv3), - m_isValid(other.m_isValid), - m_processingState(other.m_processingState) +quint64 QWebSocketFrame::maxAllowedFrameSize() const { + return m_maxAllowedFrameSize; } /*! \internal */ -QWebSocketFrame &QWebSocketFrame::operator =(const QWebSocketFrame &other) +quint64 QWebSocketFrame::maxFrameSize() { - m_closeCode = other.m_closeCode; - m_closeReason = other.m_closeReason; - m_isFinalFrame = other.m_isFinalFrame; - m_mask = other.m_mask; - m_rsv1 = other.m_rsv1; - m_rsv2 = other.m_rsv2; - m_rsv3 = other.m_rsv3; - m_opCode = other.m_opCode; - m_length = other.m_length; - m_payload = other.m_payload; - m_isValid = other.m_isValid; - m_processingState = other.m_processingState; - - return *this; -} - -#ifdef Q_COMPILER_RVALUE_REFS -/*! - \internal - */ -QWebSocketFrame::QWebSocketFrame(QWebSocketFrame &&other) : - m_closeCode(qMove(other.m_closeCode)), - m_closeReason(qMove(other.m_closeReason)), - m_mask(qMove(other.m_mask)), - m_opCode(qMove(other.m_opCode)), - m_length(qMove(other.m_length)), - m_payload(qMove(other.m_payload)), - m_isFinalFrame(qMove(other.m_isFinalFrame)), - 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_processingState(qMove(other.m_processingState)) -{} - - -/*! - \internal - */ -QWebSocketFrame &QWebSocketFrame::operator =(QWebSocketFrame &&other) -{ - qSwap(m_closeCode, other.m_closeCode); - qSwap(m_closeReason, other.m_closeReason); - qSwap(m_isFinalFrame, other.m_isFinalFrame); - qSwap(m_mask, other.m_mask); - qSwap(m_rsv1, other.m_rsv1); - qSwap(m_rsv2, other.m_rsv2); - qSwap(m_rsv3, other.m_rsv3); - qSwap(m_opCode, other.m_opCode); - 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; -} - -#endif - -/*! - \internal - */ -void QWebSocketFrame::swap(QWebSocketFrame &other) -{ - if (&other != this) { - qSwap(m_closeCode, other.m_closeCode); - qSwap(m_closeReason, other.m_closeReason); - qSwap(m_isFinalFrame, other.m_isFinalFrame); - qSwap(m_mask, other.m_mask); - qSwap(m_rsv1, other.m_rsv1); - qSwap(m_rsv2, other.m_rsv2); - qSwap(m_rsv3, other.m_rsv3); - qSwap(m_opCode, other.m_opCode); - 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 MAX_FRAME_SIZE_IN_BYTES; } /*! @@ -476,7 +379,7 @@ QWebSocketFrame::ProcessingState QWebSocketFrame::readFramePayload(QIODevice *pI if (!m_length) return PS_DISPATCH_RESULT; - if (Q_UNLIKELY(m_length > MAX_FRAME_SIZE_IN_BYTES)) { + if (Q_UNLIKELY(m_length > maxAllowedFrameSize())) { setError(QWebSocketProtocol::CloseCodeTooMuchData, tr("Maximum framesize exceeded.")); return PS_DISPATCH_RESULT; } diff --git a/src/websockets/qwebsocketframe_p.h b/src/websockets/qwebsocketframe_p.h index e2b4e9f..992379a 100644 --- a/src/websockets/qwebsocketframe_p.h +++ b/src/websockets/qwebsocketframe_p.h @@ -54,7 +54,7 @@ #include <QtCore/QString> #include <QtCore/QByteArray> #include <QtCore/QCoreApplication> -#include <limits.h> +#include <limits> #include "qwebsockets_global.h" #include "qwebsocketprotocol.h" @@ -64,25 +64,18 @@ QT_BEGIN_NAMESPACE class QIODevice; -const quint64 MAX_FRAME_SIZE_IN_BYTES = INT_MAX - 1; -const quint64 MAX_MESSAGE_SIZE_IN_BYTES = INT_MAX - 1; +const quint64 MAX_FRAME_SIZE_IN_BYTES = std::numeric_limits<int>::max() - 1; class Q_AUTOTEST_EXPORT QWebSocketFrame { Q_DECLARE_TR_FUNCTIONS(QWebSocketFrame) public: - QWebSocketFrame(); - QWebSocketFrame(const QWebSocketFrame &other); + QWebSocketFrame() = default; - QWebSocketFrame &operator =(const QWebSocketFrame &other); - -#ifdef Q_COMPILER_RVALUE_REFS - QWebSocketFrame(QWebSocketFrame &&other); - QWebSocketFrame &operator =(QWebSocketFrame &&other); -#endif - - void swap(QWebSocketFrame &other); + void setMaxAllowedFrameSize(quint64 maxAllowedFrameSize); + quint64 maxAllowedFrameSize() const; + static quint64 maxFrameSize(); QWebSocketProtocol::CloseCode closeCode() const; QString closeReason() const; @@ -106,18 +99,12 @@ public: void readFrame(QIODevice *pIoDevice); private: - QWebSocketProtocol::CloseCode m_closeCode; QString m_closeReason; - quint32 m_mask; - QWebSocketProtocol::OpCode m_opCode; - quint64 m_length; QByteArray m_payload; - - bool m_isFinalFrame; - bool m_rsv1; - bool m_rsv2; - bool m_rsv3; - bool m_isValid; + quint64 m_length = 0; + quint32 m_mask = 0; + QWebSocketProtocol::CloseCode m_closeCode = QWebSocketProtocol::CloseCodeNormal; + QWebSocketProtocol::OpCode m_opCode = QWebSocketProtocol::OpCodeReservedC; enum ProcessingState { @@ -127,7 +114,14 @@ private: PS_READ_PAYLOAD, PS_DISPATCH_RESULT, PS_WAIT_FOR_MORE_DATA - } m_processingState{PS_READ_HEADER}; + } m_processingState = PS_READ_HEADER; + + bool m_isFinalFrame = true; + bool m_rsv1 = false; + bool m_rsv2 = false; + bool m_rsv3 = false; + bool m_isValid = false; + quint64 m_maxAllowedFrameSize = MAX_FRAME_SIZE_IN_BYTES; ProcessingState readFrameHeader(QIODevice *pIoDevice); ProcessingState readFramePayloadLength(QIODevice *pIoDevice); diff --git a/src/websockets/qwebsockethandshakerequest.cpp b/src/websockets/qwebsockethandshakerequest.cpp index bfc8a3d..17275eb 100644 --- a/src/websockets/qwebsockethandshakerequest.cpp +++ b/src/websockets/qwebsockethandshakerequest.cpp @@ -230,7 +230,7 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH clear(); return; } - const QStringList tokens = requestLine.split(' ', QString::SkipEmptyParts); + const QStringList tokens = requestLine.split(' ', Qt::SkipEmptyParts); if (Q_UNLIKELY(tokens.length() < 3)) { clear(); return; @@ -268,8 +268,8 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH clear(); return; } - lastHeader = m_headers.insertMulti(headerLine.left(colonPos).trimmed().toLower(), - headerLine.mid(colonPos + 1).trimmed()); + lastHeader = m_headers.insert(headerLine.left(colonPos).trimmed().toLower(), + headerLine.mid(colonPos + 1).trimmed()); } if (m_headers.size() > maxHeaders) { clear(); @@ -301,7 +301,7 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH const QStringList versionLines = m_headers.values(QStringLiteral("sec-websocket-version")); for (QStringList::const_iterator v = versionLines.begin(); v != versionLines.end(); ++v) { - const QStringList versions = (*v).split(QStringLiteral(","), QString::SkipEmptyParts); + const QStringList versions = (*v).split(QStringLiteral(","), Qt::SkipEmptyParts); for (QStringList::const_iterator i = versions.begin(); i != versions.end(); ++i) { bool ok = false; (void)(*i).toUInt(&ok); @@ -321,8 +321,7 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH const QString upgrade = m_headers.value(QStringLiteral("upgrade"), QString()); //must be equal to "websocket", case-insensitive const QString connection = m_headers.value(QStringLiteral("connection"), QString()); - const QStringList connectionLine = connection.split(QStringLiteral(","), - QString::SkipEmptyParts); + const QStringList connectionLine = connection.split(QStringLiteral(","), Qt::SkipEmptyParts); QStringList connectionValues; for (QStringList::const_iterator c = connectionLine.begin(); c != connectionLine.end(); ++c) connectionValues << (*c).trimmed(); @@ -331,14 +330,14 @@ void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxH m_origin = m_headers.value(QStringLiteral("origin"), QString()); const QStringList protocolLines = m_headers.values(QStringLiteral("sec-websocket-protocol")); for (const QString& pl : protocolLines) { - const QStringList protocols = pl.split(QStringLiteral(","), QString::SkipEmptyParts); + const QStringList protocols = pl.split(QStringLiteral(","), Qt::SkipEmptyParts); for (const QString& p : protocols) m_protocols << p.trimmed(); } const QStringList extensionLines = m_headers.values(QStringLiteral("sec-websocket-extensions")); for (const QString& el : extensionLines) { - const QStringList extensions = el.split(QStringLiteral(","), QString::SkipEmptyParts); + const QStringList extensions = el.split(QStringLiteral(","), Qt::SkipEmptyParts); for (const QString& e : extensions) m_extensions << e.trimmed(); } diff --git a/src/websockets/qwebsockethandshakerequest_p.h b/src/websockets/qwebsockethandshakerequest_p.h index e5762df..e14c05d 100644 --- a/src/websockets/qwebsockethandshakerequest_p.h +++ b/src/websockets/qwebsockethandshakerequest_p.h @@ -91,7 +91,7 @@ private: int m_port; bool m_isSecure; bool m_isValid; - QMap<QString, QString> m_headers; + QMultiMap<QString, QString> m_headers; QList<QWebSocketProtocol::Version> m_versions; QString m_key; QString m_origin; diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp index 966126d..3196ca7 100644 --- a/src/websockets/qwebsocketserver_p.cpp +++ b/src/websockets/qwebsocketserver_p.cpp @@ -430,23 +430,50 @@ void QWebSocketServerPrivate::handshakeReceived() //This is a bug in FireFox (see https://bugzilla.mozilla.org/show_bug.cgi?id=594502) // According to RFC822 the body is separated from the headers by a null line (CRLF) - if (!pTcpSocket->peek(pTcpSocket->bytesAvailable()).endsWith(QByteArrayLiteral("\r\n\r\n"))) { + const QByteArray& endOfHeaderMarker = QByteArrayLiteral("\r\n\r\n"); + + const qint64 byteAvailable = pTcpSocket->bytesAvailable(); + QByteArray header = pTcpSocket->peek(byteAvailable); + const int endOfHeaderIndex = header.indexOf(endOfHeaderMarker); + if (endOfHeaderIndex < 0) { + //then we don't have our header complete yet + //check that no one is trying to exhaust our virtual memory + const qint64 maxHeaderLength = MAX_HEADERLINE_LENGTH * MAX_HEADERLINES + endOfHeaderMarker.size(); + if (Q_UNLIKELY(byteAvailable > maxHeaderLength)) { + pTcpSocket->close(); + setError(QWebSocketProtocol::CloseCodeTooMuchData, + QWebSocketServer::tr("Header is too large.")); + } return; } + const int headerSize = endOfHeaderIndex + endOfHeaderMarker.size(); + disconnect(pTcpSocket, &QTcpSocket::readyRead, this, &QWebSocketServerPrivate::handshakeReceived); bool success = false; bool isSecure = (m_secureMode == SecureMode); - if (m_pendingConnections.length() >= maxPendingConnections()) { + if (Q_UNLIKELY(m_pendingConnections.length() >= maxPendingConnections())) { pTcpSocket->close(); setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, QWebSocketServer::tr("Too many pending connections.")); return; } + //don't read past the header + header.resize(headerSize); + //remove our header from the tcpSocket + qint64 skippedSize = pTcpSocket->skip(headerSize); + + if (Q_UNLIKELY(skippedSize != headerSize)) { + pTcpSocket->close(); + setError(QWebSocketProtocol::CloseCodeProtocolError, + QWebSocketServer::tr("Read handshake request header failed.")); + return; + } + QWebSocketHandshakeRequest request(pTcpSocket->peerPort(), isSecure); - QTextStream textStream(pTcpSocket); + QTextStream textStream(header, QIODevice::ReadOnly); request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERLINES); if (request.isValid()) { @@ -460,16 +487,16 @@ void QWebSocketServerPrivate::handshakeReceived() supportedProtocols(), supportedExtensions()); - if (response.isValid()) { + if (Q_LIKELY(response.isValid())) { QTextStream httpStream(pTcpSocket); httpStream << response; httpStream.flush(); - if (response.canUpgrade()) { + if (Q_LIKELY(response.canUpgrade())) { QWebSocket *pWebSocket = QWebSocketPrivate::upgradeFrom(pTcpSocket, request, response); - if (pWebSocket) { + if (Q_LIKELY(pWebSocket)) { finishHandshakeTimeout(pTcpSocket); addPendingConnection(pWebSocket); Q_EMIT q->newConnection(); @@ -500,11 +527,13 @@ void QWebSocketServerPrivate::handleConnection(QTcpSocket *pTcpSocket) const QObjectPrivate::connect(pTcpSocket, &QTcpSocket::readyRead, this, &QWebSocketServerPrivate::handshakeReceived, Qt::QueuedConnection); - if (pTcpSocket->canReadLine()) { - // We received some data! We must emit now to be sure that handshakeReceived is called - // since the data could have been received before the signal and slot was connected. - emit pTcpSocket->readyRead(); + + // We received some data! We must emit now to be sure that handshakeReceived is called + // since the data could have been received before the signal and slot was connected. + if (pTcpSocket->bytesAvailable()) { + Q_EMIT pTcpSocket->readyRead(); } + QObjectPrivate::connect(pTcpSocket, &QTcpSocket::disconnected, this, &QWebSocketServerPrivate::onSocketDisconnected); } diff --git a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp index 71e1262..aca3f40 100644 --- a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp +++ b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp @@ -39,7 +39,9 @@ class EchoServer : public QObject { Q_OBJECT public: - explicit EchoServer(QObject *parent = nullptr); + explicit EchoServer(QObject *parent = nullptr, + quint64 maxAllowedIncomingMessageSize = QWebSocket::maxIncomingMessageSize(), + quint64 maxAllowedIncomingFrameSize = QWebSocket::maxIncomingFrameSize()); ~EchoServer(); QHostAddress hostAddress() const { return m_pWebSocketServer->serverAddress(); } @@ -57,13 +59,17 @@ private Q_SLOTS: private: QWebSocketServer *m_pWebSocketServer; + quint64 m_maxAllowedIncomingMessageSize; + quint64 m_maxAllowedIncomingFrameSize; QList<QWebSocket *> m_clients; }; -EchoServer::EchoServer(QObject *parent) : +EchoServer::EchoServer(QObject *parent, quint64 maxAllowedIncomingMessageSize, quint64 maxAllowedIncomingFrameSize) : QObject(parent), m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Echo Server"), QWebSocketServer::NonSecureMode, this)), + m_maxAllowedIncomingMessageSize(maxAllowedIncomingMessageSize), + m_maxAllowedIncomingFrameSize(maxAllowedIncomingFrameSize), m_clients() { if (m_pWebSocketServer->listen(QHostAddress(QStringLiteral("127.0.0.1")))) { @@ -82,6 +88,9 @@ void EchoServer::onNewConnection() { QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection(); + pSocket->setMaxAllowedIncomingFrameSize(m_maxAllowedIncomingFrameSize); + pSocket->setMaxAllowedIncomingMessageSize(m_maxAllowedIncomingMessageSize); + Q_EMIT newConnection(pSocket->requestUrl()); Q_EMIT newConnection(pSocket->request()); @@ -144,6 +153,9 @@ private Q_SLOTS: void tst_setProxy(); #endif void overlongCloseReason(); + void incomingMessageTooLong(); + void incomingFrameTooLong(); + void testingFrameAndMessageSizeApi(); }; tst_QWebSocket::tst_QWebSocket() @@ -795,6 +807,80 @@ void tst_QWebSocket::overlongCloseReason() QCOMPARE(socket.closeReason(), reason.leftRef(123)); QTRY_COMPARE(socketDisconnectedSpy.count(), 1); } + +void tst_QWebSocket::incomingMessageTooLong() +{ +//QTBUG-70693 + quint64 maxAllowedIncomingMessageSize = 1024; + quint64 maxAllowedIncomingFrameSize = QWebSocket::maxIncomingFrameSize(); + + EchoServer echoServer(nullptr, maxAllowedIncomingMessageSize, maxAllowedIncomingFrameSize); + + QWebSocket socket; + + QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); + QSignalSpy serverConnectedSpy(&echoServer, QOverload<QUrl>::of(&EchoServer::newConnection)); + QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected); + + QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QStringLiteral(":") + QString::number(echoServer.port())); + socket.open(url); + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QTRY_COMPARE(serverConnectedSpy.count(), 1); + + QString payload(maxAllowedIncomingMessageSize+1, 'a'); + QCOMPARE(socket.sendTextMessage(payload), payload.size()); + + QTRY_COMPARE(socketDisconnectedSpy.count(), 1); + QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeTooMuchData); +} + +void tst_QWebSocket::incomingFrameTooLong() +{ +//QTBUG-70693 + quint64 maxAllowedIncomingMessageSize = QWebSocket::maxIncomingMessageSize(); + quint64 maxAllowedIncomingFrameSize = 1024; + + EchoServer echoServer(nullptr, maxAllowedIncomingMessageSize, maxAllowedIncomingFrameSize); + + QWebSocket socket; + socket.setOutgoingFrameSize(maxAllowedIncomingFrameSize+1); + + QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); + QSignalSpy serverConnectedSpy(&echoServer, QOverload<QUrl>::of(&EchoServer::newConnection)); + QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected); + + QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QStringLiteral(":") + QString::number(echoServer.port())); + socket.open(url); + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QTRY_COMPARE(serverConnectedSpy.count(), 1); + + QString payload(maxAllowedIncomingFrameSize+1, 'a'); + QCOMPARE(socket.sendTextMessage(payload), payload.size()); + + QTRY_COMPARE(socketDisconnectedSpy.count(), 1); + QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeTooMuchData); +} + +void tst_QWebSocket::testingFrameAndMessageSizeApi() +{ +//requested by André Hartmann, QTBUG-70693 + QWebSocket socket; + + const quint64 outgoingFrameSize = 5; + socket.setOutgoingFrameSize(outgoingFrameSize); + QTRY_COMPARE(outgoingFrameSize, socket.outgoingFrameSize()); + + const quint64 maxAllowedIncomingFrameSize = 9; + socket.setMaxAllowedIncomingFrameSize(maxAllowedIncomingFrameSize); + QTRY_COMPARE(maxAllowedIncomingFrameSize, socket.maxAllowedIncomingFrameSize()); + + const quint64 maxAllowedIncomingMessageSize = 889; + socket.setMaxAllowedIncomingMessageSize(maxAllowedIncomingMessageSize); + QTRY_COMPARE(maxAllowedIncomingMessageSize, socket.maxAllowedIncomingMessageSize()); +} + #endif // QT_NO_NETWORKPROXY QTEST_MAIN(tst_QWebSocket) diff --git a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp index c40bb01..62d8d43 100644 --- a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp +++ b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp @@ -604,7 +604,6 @@ static void setupSecureServer(QWebSocketServer *secureServer) sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); sslConfiguration.setLocalCertificate(certificate); sslConfiguration.setPrivateKey(sslKey); - sslConfiguration.setProtocol(QSsl::TlsV1SslV3); secureServer->setSslConfiguration(sslConfiguration); } #endif @@ -782,7 +781,7 @@ void tst_QWebSocketServer::tst_handshakeTimeout() setupSecureServer(&secureServer); if (QTest::currentTestFailed()) return; - secureServer.setHandshakeTimeout(500); + secureServer.setHandshakeTimeout(2000); QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection())); @@ -797,6 +796,11 @@ void tst_QWebSocketServer::tst_handshakeTimeout() QCOMPARE(secureServerConnectionSpy.count(), 0); QWebSocket secureSocket; + connect(&secureSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), + [](QAbstractSocket::SocketError error) { + // This shouldn't print but it's useful for debugging when/if it does. + qDebug() << "Error occurred in the client:" << error; + }); QSslConfiguration config = secureSocket.sslConfiguration(); config.setPeerVerifyMode(QSslSocket::VerifyNone); secureSocket.setSslConfiguration(config); diff --git a/tests/auto/websockets/websocketframe/tst_websocketframe.cpp b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp index 6b9aaaf..7046657 100644 --- a/tests/auto/websockets/websocketframe/tst_websocketframe.cpp +++ b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp @@ -201,8 +201,8 @@ void tst_WebSocketFrame::tst_copyConstructorAndAssignment() frame.readFrame(&buffer); buffer.close(); + auto compareFrames = [](const QWebSocketFrame &other, const QWebSocketFrame &frame) { - QWebSocketFrame other(frame); QCOMPARE(other.closeCode(), frame.closeCode()); QCOMPARE(other.closeReason(), frame.closeReason()); QCOMPARE(other.hasMask(), frame.hasMask()); @@ -217,24 +217,20 @@ void tst_WebSocketFrame::tst_copyConstructorAndAssignment() QCOMPARE(other.rsv1(), frame.rsv1()); QCOMPARE(other.rsv2(), frame.rsv2()); QCOMPARE(other.rsv3(), frame.rsv3()); + }; + + { + QWebSocketFrame other(frame); + compareFrames(other, frame); } { QWebSocketFrame other; other = frame; - QCOMPARE(other.closeCode(), frame.closeCode()); - QCOMPARE(other.closeReason(), frame.closeReason()); - QCOMPARE(other.hasMask(), frame.hasMask()); - QCOMPARE(other.isContinuationFrame(), frame.isContinuationFrame()); - QCOMPARE(other.isControlFrame(), frame.isControlFrame()); - QCOMPARE(other.isDataFrame(), frame.isDataFrame()); - QCOMPARE(other.isFinalFrame(), frame.isFinalFrame()); - QCOMPARE(other.isValid(), frame.isValid()); - QCOMPARE(other.mask(), frame.mask()); - QCOMPARE(other.opCode(), frame.opCode()); - QCOMPARE(other.payload(), frame.payload()); - QCOMPARE(other.rsv1(), frame.rsv1()); - QCOMPARE(other.rsv2(), frame.rsv2()); - QCOMPARE(other.rsv3(), frame.rsv3()); + compareFrames(other, frame); + QWebSocketFrame other2 = std::move(other); + compareFrames(other2, frame); + QWebSocketFrame other3(std::move(other2)); + compareFrames(other3, frame); } } @@ -545,7 +541,7 @@ void tst_WebSocketFrame::tst_malformedFrames_data() //too much data { const char bigpayloadIndicator = char(127); - const quint64 payloadSize = MAX_FRAME_SIZE_IN_BYTES + 1; + const quint64 payloadSize = QWebSocketFrame::maxFrameSize() + 1; uchar swapped[8] = {0}; qToBigEndian<quint64>(payloadSize, swapped); QTest::newRow("Frame too big") |