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 8529538..5480ecf 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::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) { } @@ -142,7 +144,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) { } @@ -565,7 +568,7 @@ void QWebSocketPrivate::enableMasking(bool enable) /*! * \internal */ -void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) +void QWebSocketPrivate::makeConnections(QTcpSocket *pTcpSocket) { Q_ASSERT(pTcpSocket); Q_Q(QWebSocket); @@ -632,6 +635,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(); } /*! @@ -768,11 +775,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; @@ -791,7 +798,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; @@ -1174,10 +1181,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; } } } @@ -1301,6 +1308,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 */ diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h index 4b39dfc..c3f170d 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, @@ -250,6 +261,8 @@ private: QString m_httpStatusMessage; QMap m_headers; + quint64 m_outgoingFrameSize; + friend class QWebSocketServerPrivate; #ifdef Q_OS_WASM emscripten::val socketContext = emscripten::val::null(); diff --git a/src/websockets/qwebsocketdataprocessor.cpp b/src/websockets/qwebsocketdataprocessor.cpp index 4f81222..4110f2a 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); } /*! @@ -101,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 */ @@ -114,19 +145,28 @@ quint64 QWebSocketDataProcessor::maxMessageSize() */ quint64 QWebSocketDataProcessor::maxFrameSize() { - return MAX_FRAME_SIZE_IN_BYTES; + return QWebSocketFrame::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 +176,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 +184,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(); @@ -154,11 +194,11 @@ void 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.")); - return; + return true; } if (m_opCode == QWebSocketProtocol::OpCodeText) { @@ -171,7 +211,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 +239,9 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) clear(); isDone = true; } + frame.clear(); } + return true; } /*! @@ -301,4 +343,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..62a2dc0 100644 --- a/src/websockets/qwebsocketdataprocessor_p.h +++ b/src/websockets/qwebsocketdataprocessor_p.h @@ -55,6 +55,8 @@ #include #include #include +#include +#include "qwebsocketframe_p.h" #include "qwebsocketprotocol.h" #include "qwebsocketprotocol_p.h" @@ -63,6 +65,8 @@ QT_BEGIN_NAMESPACE class QIODevice; class QWebSocketFrame; +const quint64 MAX_MESSAGE_SIZE_IN_BYTES = std::numeric_limits::max() - 1; + class Q_AUTOTEST_EXPORT QWebSocketDataProcessor : public QObject { Q_OBJECT @@ -72,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(); @@ -86,7 +94,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 +119,12 @@ private: quint64 m_payloadLength; QTextCodec::ConverterState *m_pConverterState; QTextCodec *m_pTextCodec; + QWebSocketFrame frame; + QTimer waitTimer; + quint64 m_maxAllowedMessageSize = MAX_MESSAGE_SIZE_IN_BYTES; bool processControlFrame(const QWebSocketFrame &frame); + void timeout(); }; QT_END_NAMESPACE diff --git a/src/websockets/qwebsocketframe.cpp b/src/websockets/qwebsocketframe.cpp index 041302e..716aebd 100644 --- a/src/websockets/qwebsocketframe.cpp +++ b/src/websockets/qwebsocketframe.cpp @@ -64,118 +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) +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; - - 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)) -{} - - -/*! - \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); - - 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); - } + return MAX_FRAME_SIZE_IN_BYTES; } /*! @@ -183,7 +91,7 @@ void QWebSocketFrame::swap(QWebSocketFrame &other) */ QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const { - return m_closeCode; + return isDone() ? m_closeCode : QWebSocketProtocol::CloseCodeGoingAway; } /*! @@ -191,7 +99,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 +184,7 @@ void QWebSocketFrame::clear() m_length = 0; m_payload.clear(); m_isValid = false; + m_processingState = PS_READ_HEADER; } /*! @@ -283,215 +192,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(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(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(reinterpret_cast(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(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(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(&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; + } } +} - return frame; +/*! + \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(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(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(reinterpret_cast(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(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(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; +} + +/*! + \internal + */ +QWebSocketFrame::ProcessingState QWebSocketFrame::readFrameMask(QIODevice *pIoDevice) +{ + if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) { + if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast(&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 > maxAllowedFrameSize())) { + 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..992379a 100644 --- a/src/websockets/qwebsocketframe_p.h +++ b/src/websockets/qwebsocketframe_p.h @@ -54,7 +54,7 @@ #include #include #include -#include +#include #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::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; @@ -101,33 +94,39 @@ 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; 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 { 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; + + 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); + ProcessingState readFrameMask(QIODevice *pIoDevice); + ProcessingState readFramePayload(QIODevice *pIoDevice); void setError(QWebSocketProtocol::CloseCode code, const QString &closeReason); bool checkValidity(); diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp index f3e7eac..d9d79d9 100644 --- a/src/websockets/qwebsocketserver_p.cpp +++ b/src/websockets/qwebsocketserver_p.cpp @@ -416,9 +416,25 @@ void QWebSocketServerPrivate::handshakeReceived() //For Safari, the handshake is delivered at once //FIXME: For FireFox, the readyRead signal is never emitted //This is a bug in FireFox (see https://bugzilla.mozilla.org/show_bug.cgi?id=594502) - if (!pTcpSocket->canReadLine()) { + // According to RFC822 the body is separated from the headers by a null line (CRLF) + 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 (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); Q_Q(QWebSocketServer); @@ -433,8 +449,20 @@ void QWebSocketServerPrivate::handshakeReceived() return; } + //don't read past the header + header.resize(headerSize); + //remove our header from the tcpSocket + qint64 skippedSize = pTcpSocket->skip(headerSize); + + if (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()) { @@ -487,11 +515,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/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 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..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 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() @@ -457,12 +469,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()))); @@ -761,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::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::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/websocketframe/tst_websocketframe.cpp b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp index 5614df8..7046657 100644 --- a/tests/auto/websockets/websocketframe/tst_websocketframe.cpp +++ b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp @@ -197,11 +197,12 @@ void tst_WebSocketFrame::tst_copyConstructorAndAssignment() QBuffer buffer(&payload); buffer.open(QIODevice::ReadOnly); - QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer); + QWebSocketFrame frame; + 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()); @@ -216,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); } } @@ -330,7 +327,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 +493,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()); @@ -542,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(payloadSize, swapped); QTest::newRow("Frame too big") @@ -606,7 +605,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());