diff --git a/SOURCES/qtwebsockets-add-public-api-to-set-max-frame-and-message-size.patch b/SOURCES/qtwebsockets-add-public-api-to-set-max-frame-and-message-size.patch new file mode 100644 index 0000000..5056c5a --- /dev/null +++ b/SOURCES/qtwebsockets-add-public-api-to-set-max-frame-and-message-size.patch @@ -0,0 +1,1672 @@ +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()); diff --git a/SPECS/qt5-qtwebsockets.spec b/SPECS/qt5-qtwebsockets.spec index cf174dd..81b0005 100644 --- a/SPECS/qt5-qtwebsockets.spec +++ b/SPECS/qt5-qtwebsockets.spec @@ -5,7 +5,7 @@ Summary: Qt5 - WebSockets component Name: qt5-%{qt_module} Version: 5.12.5 -Release: 1%{?dist} +Release: 2%{?dist} # See LICENSE.GPL LICENSE.LGPL LGPL_EXCEPTION.txt, for details License: LGPLv2 with exceptions or GPLv3 with exceptions @@ -13,6 +13,9 @@ Url: http://qt-project.org/ %global majmin %(echo %{version} | cut -d. -f1-2) Source0: https://download.qt.io/official_releases/qt/%{majmin}/%{version}/submodules/%{qt_module}-everywhere-src-%{version}.tar.xz +# Security fixes +Patch100: qtwebsockets-add-public-api-to-set-max-frame-and-message-size.patch + # filter qml provides %global __provides_exclude_from ^%{_qt5_archdatadir}/qml/.*\\.so$ @@ -52,6 +55,7 @@ Requires: %{name}%{?_isa} = %{version}-%{release} %prep %setup -q -n %{qt_module}-everywhere-src-%{version} +%patch100 -p1 -b .add-public-api-to-set-max-frame-and-message-size %build %{qmake_qt5} @@ -120,6 +124,10 @@ popd %endif %changelog +* Mon May 11 2020 Jan Grulich - 5.12.5-2 +- Add a public api to set max frame and message size + Resolves: bz#1815187 + * Mon Nov 18 2019 Jan Grulich - 5.12.5-1 - 5.12.5 Resolves: bz#1733156