diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-10-15 16:40:37 +0200 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-10-15 16:40:52 +0200 |
commit | 539d5597491893c939c7a05ff4d45261e949e9ce (patch) | |
tree | 7c3aaeec1f082b3269f54ef2dc436ac704b3fc8f /src | |
parent | 432b75517cba65e8ab633a2fe0d53f18abd59426 (diff) | |
parent | c2b7d3dc209fe6652571f1dcdd78fb92155e7b8b (diff) | |
download | qtwebsockets-539d5597491893c939c7a05ff4d45261e949e9ce.tar.gz |
Merge remote-tracking branch 'origin/dev' into wip/cmake
Removed dependencies.yaml.
Change-Id: Ie799709d6a94054ca937c70a73fd979efb9619f7
Diffstat (limited to 'src')
-rw-r--r-- | src/websockets/doc/qtwebsockets.qdocconf | 1 | ||||
-rw-r--r-- | src/websockets/qwebsocket_p.cpp | 8 | ||||
-rw-r--r-- | src/websockets/qwebsocket_p.h | 4 | ||||
-rw-r--r-- | src/websockets/qwebsocket_wasm_p.cpp | 16 | ||||
-rw-r--r-- | src/websockets/qwebsocketdataprocessor.cpp | 39 | ||||
-rw-r--r-- | src/websockets/qwebsocketdataprocessor_p.h | 7 | ||||
-rw-r--r-- | src/websockets/qwebsocketframe.cpp | 370 | ||||
-rw-r--r-- | src/websockets/qwebsocketframe_p.h | 13 | ||||
-rw-r--r-- | src/websockets/qwebsockethandshakeresponse.cpp | 9 | ||||
-rw-r--r-- | src/websockets/qwebsocketserver.cpp | 24 | ||||
-rw-r--r-- | src/websockets/qwebsocketserver.h | 16 |
11 files changed, 295 insertions, 212 deletions
diff --git a/src/websockets/doc/qtwebsockets.qdocconf b/src/websockets/doc/qtwebsockets.qdocconf index ba77c82..56d54a2 100644 --- a/src/websockets/doc/qtwebsockets.qdocconf +++ b/src/websockets/doc/qtwebsockets.qdocconf @@ -1,4 +1,5 @@ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtwebsockets.qdocconf) project = QtWebSockets description = Qt WebSockets Reference Documentation diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp index 6965731..36aefd9 100644 --- a/src/websockets/qwebsocket_p.cpp +++ b/src/websockets/qwebsocket_p.cpp @@ -113,6 +113,7 @@ QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol:: m_defaultMaskGenerator(), m_handshakeState(NothingDoneState) { + m_pingTimer.start(); } /*! @@ -144,6 +145,7 @@ QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol: m_defaultMaskGenerator(), m_handshakeState(NothingDoneState) { + m_pingTimer.start(); } /*! @@ -1176,10 +1178,10 @@ void QWebSocketPrivate::processData() while (m_pSocket->bytesAvailable()) { if (state() == QAbstractSocket::ConnectingState) { if (!m_pSocket->canReadLine()) - break; + return; processHandshake(m_pSocket); - } else { - m_dataProcessor.process(m_pSocket); + } else if (!m_dataProcessor.process(m_pSocket)) { + return; } } } diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h index 4b39dfc..e72daa5 100644 --- a/src/websockets/qwebsocket_p.h +++ b/src/websockets/qwebsocket_p.h @@ -61,7 +61,7 @@ #include <QtNetwork/QSslError> #include <QtNetwork/QSslSocket> #endif -#include <QtCore/QTime> +#include <QtCore/QElapsedTimer> #include <private/qobject_p.h> #include "qwebsocket.h" @@ -229,7 +229,7 @@ private: QWebSocketProtocol::CloseCode m_closeCode; QString m_closeReason; - QTime m_pingTimer; + QElapsedTimer m_pingTimer; QWebSocketDataProcessor m_dataProcessor; QWebSocketConfiguration m_configuration; diff --git a/src/websockets/qwebsocket_wasm_p.cpp b/src/websockets/qwebsocket_wasm_p.cpp index 199fe44..85fcab2 100644 --- a/src/websockets/qwebsocket_wasm_p.cpp +++ b/src/websockets/qwebsocket_wasm_p.cpp @@ -137,11 +137,19 @@ qint64 QWebSocketPrivate::sendTextMessage(const QString &message) qint64 QWebSocketPrivate::sendBinaryMessage(const QByteArray &data) { - socketContext.call<void>("send", - val(typed_memory_view(data.size(), - reinterpret_cast<const unsigned char *> - (data.constData())))); + // Make a copy of the payload data; we don't know how long WebSocket.send() will + // retain the memory view, while the QByteArray passed to this function may be + // destroyed as soon as this function returns. In addition, the WebSocket.send() + // API does not accept data from a view backet by a SharedArrayBuffer, which will + // be the case for the view produced by typed_memory_view() when threads are enabled. + val Uint8Array = val::global("Uint8Array"); + val dataCopy = Uint8Array.new_(data.size()); + val dataView = val(typed_memory_view(data.size(), + reinterpret_cast<const unsigned char *> + (data.constData()))); + dataCopy.call<void>("set", dataView); + socketContext.call<void>("send", dataCopy); return data.length(); } diff --git a/src/websockets/qwebsocketdataprocessor.cpp b/src/websockets/qwebsocketdataprocessor.cpp index 4f81222..191b992 100644 --- a/src/websockets/qwebsocketdataprocessor.cpp +++ b/src/websockets/qwebsocketdataprocessor.cpp @@ -87,6 +87,10 @@ QWebSocketDataProcessor::QWebSocketDataProcessor(QObject *parent) : m_pTextCodec(QTextCodec::codecForName("UTF-8")) { clear(); + // initialize the internal timeout timer + waitTimer.setInterval(5000); + waitTimer.setSingleShot(true); + waitTimer.callOnTimeout(this, &QWebSocketDataProcessor::timeout); } /*! @@ -119,14 +123,23 @@ quint64 QWebSocketDataProcessor::maxFrameSize() /*! \internal + + Returns \c true if a complete websocket frame has been processed; + otherwise returns \c false. */ -void QWebSocketDataProcessor::process(QIODevice *pIoDevice) +bool QWebSocketDataProcessor::process(QIODevice *pIoDevice) { bool isDone = false; while (!isDone) { - QWebSocketFrame frame = QWebSocketFrame::readFrame(pIoDevice); - if (Q_LIKELY(frame.isValid())) { + frame.readFrame(pIoDevice); + if (!frame.isDone()) { + // waiting for more data available + QObject::connect(pIoDevice, &QIODevice::readyRead, + &waitTimer, &QTimer::stop, Qt::UniqueConnection); + waitTimer.start(); + return false; + } else if (Q_LIKELY(frame.isValid())) { if (frame.isControlFrame()) { isDone = processControlFrame(frame); } else { @@ -136,7 +149,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("Received Continuation frame, while there is " \ "nothing to continue.")); - return; + return true; } if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame())) { @@ -144,7 +157,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("All data frames after the initial data frame " \ "must have opcode 0 (continuation).")); - return; + return true; } if (!frame.isContinuationFrame()) { m_opCode = frame.opCode(); @@ -158,7 +171,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData, tr("Received message is too big.")); - return; + return true; } if (m_opCode == QWebSocketProtocol::OpCodeText) { @@ -171,7 +184,7 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeWrongDatatype, tr("Invalid UTF-8 code encountered.")); - return; + return true; } else { m_textMessage.append(frameTxt); Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame()); @@ -199,7 +212,9 @@ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) clear(); isDone = true; } + frame.clear(); } + return true; } /*! @@ -301,4 +316,14 @@ bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame) return mustStopProcessing; } +/*! + \internal + */ +void QWebSocketDataProcessor::timeout() +{ + clear(); + Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeGoingAway, + tr("Timeout when reading data from socket.")); +} + QT_END_NAMESPACE diff --git a/src/websockets/qwebsocketdataprocessor_p.h b/src/websockets/qwebsocketdataprocessor_p.h index e80a843..03635b1 100644 --- a/src/websockets/qwebsocketdataprocessor_p.h +++ b/src/websockets/qwebsocketdataprocessor_p.h @@ -55,6 +55,8 @@ #include <QtCore/QByteArray> #include <QtCore/QString> #include <QtCore/QTextCodec> +#include <QTimer> +#include "qwebsocketframe_p.h" #include "qwebsocketprotocol.h" #include "qwebsocketprotocol_p.h" @@ -86,7 +88,7 @@ Q_SIGNALS: void errorEncountered(QWebSocketProtocol::CloseCode code, const QString &description); public Q_SLOTS: - void process(QIODevice *pIoDevice); + bool process(QIODevice *pIoDevice); void clear(); private: @@ -111,8 +113,11 @@ private: quint64 m_payloadLength; QTextCodec::ConverterState *m_pConverterState; QTextCodec *m_pTextCodec; + QWebSocketFrame frame; + QTimer waitTimer; bool processControlFrame(const QWebSocketFrame &frame); + void timeout(); }; QT_END_NAMESPACE diff --git a/src/websockets/qwebsocketframe.cpp b/src/websockets/qwebsocketframe.cpp index 041302e..11373a7 100644 --- a/src/websockets/qwebsocketframe.cpp +++ b/src/websockets/qwebsocketframe.cpp @@ -93,7 +93,8 @@ QWebSocketFrame::QWebSocketFrame(const QWebSocketFrame &other) : m_rsv1(other.m_rsv1), m_rsv2(other.m_rsv2), m_rsv3(other.m_rsv3), - m_isValid(other.m_isValid) + m_isValid(other.m_isValid), + m_processingState(other.m_processingState) { } @@ -113,6 +114,7 @@ QWebSocketFrame &QWebSocketFrame::operator =(const QWebSocketFrame &other) m_length = other.m_length; m_payload = other.m_payload; m_isValid = other.m_isValid; + m_processingState = other.m_processingState; return *this; } @@ -132,7 +134,8 @@ QWebSocketFrame::QWebSocketFrame(QWebSocketFrame &&other) : m_rsv1(qMove(other.m_rsv1)), m_rsv2(qMove(other.m_rsv2)), m_rsv3(qMove(other.m_rsv3)), - m_isValid(qMove(other.m_isValid)) + m_isValid(qMove(other.m_isValid)), + m_processingState(qMove(other.m_processingState)) {} @@ -152,6 +155,7 @@ QWebSocketFrame &QWebSocketFrame::operator =(QWebSocketFrame &&other) qSwap(m_length, other.m_length); qSwap(m_payload, other.m_payload); qSwap(m_isValid, other.m_isValid); + qSwap(m_processingState, other.m_processingState); return *this; } @@ -175,6 +179,7 @@ void QWebSocketFrame::swap(QWebSocketFrame &other) qSwap(m_length, other.m_length); qSwap(m_payload, other.m_payload); qSwap(m_isValid, other.m_isValid); + qSwap(m_processingState, other.m_processingState); } } @@ -183,7 +188,7 @@ void QWebSocketFrame::swap(QWebSocketFrame &other) */ QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const { - return m_closeCode; + return isDone() ? m_closeCode : QWebSocketProtocol::CloseCodeGoingAway; } /*! @@ -191,7 +196,7 @@ QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const */ QString QWebSocketFrame::closeReason() const { - return m_closeReason; + return isDone() ? m_closeReason : tr("Waiting for more data from socket."); } /*! @@ -276,6 +281,7 @@ void QWebSocketFrame::clear() m_length = 0; m_payload.clear(); m_isValid = false; + m_processingState = PS_READ_HEADER; } /*! @@ -283,215 +289,211 @@ void QWebSocketFrame::clear() */ bool QWebSocketFrame::isValid() const { - return m_isValid; + return isDone() && m_isValid; } -#define WAIT_FOR_MORE_DATA(dataSizeInBytes) \ - { returnState = processingState; \ - processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; } +/*! + \internal + */ +bool QWebSocketFrame::isDone() const +{ + return m_processingState == PS_DISPATCH_RESULT; +} /*! \internal */ -QWebSocketFrame QWebSocketFrame::readFrame(QIODevice *pIoDevice) +void QWebSocketFrame::readFrame(QIODevice *pIoDevice) { - bool isDone = false; - qint64 bytesRead = 0; - QWebSocketFrame frame; - quint64 dataWaitSize = 0; - Q_UNUSED(dataWaitSize); // value is used in MACRO, Q_UNUSED to avoid compiler warnings - ProcessingState processingState = PS_READ_HEADER; - ProcessingState returnState = PS_READ_HEADER; - bool hasMask = false; - quint64 payloadLength = 0; - - while (!isDone) + while (true) { - switch (processingState) { - case PS_WAIT_FOR_MORE_DATA: - //TODO: waitForReadyRead should really be changed - //now, when a WebSocket is used in a GUI thread - //the GUI will hang for at most 5 seconds - //maybe, a QStateMachine should be used - if (!pIoDevice->waitForReadyRead(5000)) { - frame.setError(QWebSocketProtocol::CloseCodeGoingAway, - tr("Timeout when reading data from socket.")); - processingState = PS_DISPATCH_RESULT; - } else { - processingState = returnState; - } - break; - + switch (m_processingState) { case PS_READ_HEADER: - if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) { - //FIN, RSV1-3, Opcode - char header[2] = {0}; - bytesRead = pIoDevice->read(header, 2); - frame.m_isFinalFrame = (header[0] & 0x80) != 0; - frame.m_rsv1 = (header[0] & 0x40); - frame.m_rsv2 = (header[0] & 0x20); - frame.m_rsv3 = (header[0] & 0x10); - frame.m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F); - - //Mask, PayloadLength - hasMask = (header[1] & 0x80) != 0; - frame.m_length = (header[1] & 0x7F); - - switch (frame.m_length) - { - case 126: - { - processingState = PS_READ_PAYLOAD_LENGTH; - break; - } - case 127: - { - processingState = PS_READ_BIG_PAYLOAD_LENGTH; - break; - } - default: - { - payloadLength = frame.m_length; - processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; - break; - } - } - if (!frame.checkValidity()) - processingState = PS_DISPATCH_RESULT; - } else { - WAIT_FOR_MORE_DATA(2); + m_processingState = readFrameHeader(pIoDevice); + if (m_processingState == PS_WAIT_FOR_MORE_DATA) { + m_processingState = PS_READ_HEADER; + return; } break; case PS_READ_PAYLOAD_LENGTH: - if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) { - uchar length[2] = {0}; - bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 2); - if (Q_UNLIKELY(bytesRead == -1)) { - frame.setError(QWebSocketProtocol::CloseCodeGoingAway, - tr("Error occurred while reading from the network: %1") - .arg(pIoDevice->errorString())); - processingState = PS_DISPATCH_RESULT; - } else { - payloadLength = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length)); - if (Q_UNLIKELY(payloadLength < 126)) { - //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 - //"in all cases, the minimal number of bytes MUST be used to encode - //the length, for example, the length of a 124-byte-long string - //can't be encoded as the sequence 126, 0, 124" - frame.setError(QWebSocketProtocol::CloseCodeProtocolError, - tr("Lengths smaller than 126 " \ - "must be expressed as one byte.")); - processingState = PS_DISPATCH_RESULT; - } else { - processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; - } - } - } else { - WAIT_FOR_MORE_DATA(2); + m_processingState = readFramePayloadLength(pIoDevice); + if (m_processingState == PS_WAIT_FOR_MORE_DATA) { + m_processingState = PS_READ_PAYLOAD_LENGTH; + return; } break; - case PS_READ_BIG_PAYLOAD_LENGTH: - if (Q_LIKELY(pIoDevice->bytesAvailable() >= 8)) { - uchar length[8] = {0}; - bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 8); - if (Q_UNLIKELY(bytesRead < 8)) { - frame.setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, - tr("Something went wrong during "\ - "reading from the network.")); - processingState = PS_DISPATCH_RESULT; - } else { - //Most significant bit must be set to 0 as - //per http://tools.ietf.org/html/rfc6455#section-5.2 - payloadLength = qFromBigEndian<quint64>(length); - if (Q_UNLIKELY(payloadLength & (quint64(1) << 63))) { - frame.setError(QWebSocketProtocol::CloseCodeProtocolError, - tr("Highest bit of payload length is not 0.")); - processingState = PS_DISPATCH_RESULT; - } else if (Q_UNLIKELY(payloadLength <= 0xFFFFu)) { - //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 - //"in all cases, the minimal number of bytes MUST be used to encode - //the length, for example, the length of a 124-byte-long string - //can't be encoded as the sequence 126, 0, 124" - frame.setError(QWebSocketProtocol::CloseCodeProtocolError, - tr("Lengths smaller than 65536 (2^16) " \ - "must be expressed as 2 bytes.")); - processingState = PS_DISPATCH_RESULT; - } else { - processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; - } - } - } else { - WAIT_FOR_MORE_DATA(8); - } - - break; - case PS_READ_MASK: - if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) { - bytesRead = pIoDevice->read(reinterpret_cast<char *>(&frame.m_mask), - sizeof(frame.m_mask)); - if (bytesRead == -1) { - frame.setError(QWebSocketProtocol::CloseCodeGoingAway, - tr("Error while reading from the network: %1.") - .arg(pIoDevice->errorString())); - processingState = PS_DISPATCH_RESULT; - } else { - frame.m_mask = qFromBigEndian(frame.m_mask); - processingState = PS_READ_PAYLOAD; - } - } else { - WAIT_FOR_MORE_DATA(4); + m_processingState = readFrameMask(pIoDevice); + if (m_processingState == PS_WAIT_FOR_MORE_DATA) { + m_processingState = PS_READ_MASK; + return; } break; case PS_READ_PAYLOAD: - if (!payloadLength) { - processingState = PS_DISPATCH_RESULT; - } else if (Q_UNLIKELY(payloadLength > MAX_FRAME_SIZE_IN_BYTES)) { - frame.setError(QWebSocketProtocol::CloseCodeTooMuchData, - tr("Maximum framesize exceeded.")); - processingState = PS_DISPATCH_RESULT; - } else { - quint64 bytesAvailable = quint64(pIoDevice->bytesAvailable()); - if (bytesAvailable >= payloadLength) { - frame.m_payload = pIoDevice->read(int(payloadLength)); - //payloadLength can be safely cast to an integer, - //because MAX_FRAME_SIZE_IN_BYTES = MAX_INT - if (Q_UNLIKELY(frame.m_payload.length() != int(payloadLength))) { - //some error occurred; refer to the Qt documentation of QIODevice::read() - frame.setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, - tr("Some serious error occurred " \ - "while reading from the network.")); - processingState = PS_DISPATCH_RESULT; - } else { - if (hasMask) - QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask); - processingState = PS_DISPATCH_RESULT; - } - } else { - //if payload is too big, then this will timeout - WAIT_FOR_MORE_DATA(payloadLength); - } + m_processingState = readFramePayload(pIoDevice); + if (m_processingState == PS_WAIT_FOR_MORE_DATA) { + m_processingState = PS_READ_PAYLOAD; + return; } break; case PS_DISPATCH_RESULT: - processingState = PS_READ_HEADER; - isDone = true; - break; + return; default: - //should not come here - qWarning() << "DataProcessor::process: Found invalid state. This should not happen!"; - frame.clear(); - isDone = true; - break; - } //end switch + Q_UNREACHABLE(); + return; + } + } +} + +/*! + \internal + */ +QWebSocketFrame::ProcessingState QWebSocketFrame::readFrameHeader(QIODevice *pIoDevice) +{ + if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) { + // FIN, RSV1-3, Opcode + char header[2] = {0}; + if (Q_UNLIKELY(pIoDevice->read(header, 2) < 2)) { + setError(QWebSocketProtocol::CloseCodeGoingAway, + tr("Error occurred while reading header from the network: %1") + .arg(pIoDevice->errorString())); + return PS_DISPATCH_RESULT; + } + m_isFinalFrame = (header[0] & 0x80) != 0; + m_rsv1 = (header[0] & 0x40); + m_rsv2 = (header[0] & 0x20); + m_rsv3 = (header[0] & 0x10); + m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F); + + // Mask + // Use zero as mask value to mean there's no mask to read. + // When the mask value is read, it over-writes this non-zero value. + m_mask = header[1] & 0x80; + // PayloadLength + m_length = (header[1] & 0x7F); + + if (!checkValidity()) + return PS_DISPATCH_RESULT; + + switch (m_length) { + case 126: + case 127: + return PS_READ_PAYLOAD_LENGTH; + default: + return hasMask() ? PS_READ_MASK : PS_READ_PAYLOAD; + } + } + return PS_WAIT_FOR_MORE_DATA; +} + +/*! + \internal + */ +QWebSocketFrame::ProcessingState QWebSocketFrame::readFramePayloadLength(QIODevice *pIoDevice) +{ + // see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 + // in all cases, the minimal number of bytes MUST be used to encode the length, + // for example, the length of a 124-byte-long string can't be encoded as the + // sequence 126, 0, 124" + switch (m_length) { + case 126: + if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) { + uchar length[2] = {0}; + if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast<char *>(length), 2) < 2)) { + setError(QWebSocketProtocol::CloseCodeGoingAway, + tr("Error occurred while reading from the network: %1") + .arg(pIoDevice->errorString())); + return PS_DISPATCH_RESULT; + } + m_length = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length)); + if (Q_UNLIKELY(m_length < 126)) { + + setError(QWebSocketProtocol::CloseCodeProtocolError, + tr("Lengths smaller than 126 must be expressed as one byte.")); + return PS_DISPATCH_RESULT; + } + return hasMask() ? PS_READ_MASK : PS_READ_PAYLOAD; + } + break; + case 127: + if (Q_LIKELY(pIoDevice->bytesAvailable() >= 8)) { + uchar length[8] = {0}; + if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast<char *>(length), 8) < 8)) { + setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, + tr("Something went wrong during reading from the network.")); + return PS_DISPATCH_RESULT; + } + // Most significant bit must be set to 0 as + // per http://tools.ietf.org/html/rfc6455#section-5.2 + m_length = qFromBigEndian<quint64>(length); + if (Q_UNLIKELY(m_length & (quint64(1) << 63))) { + setError(QWebSocketProtocol::CloseCodeProtocolError, + tr("Highest bit of payload length is not 0.")); + return PS_DISPATCH_RESULT; + } + if (Q_UNLIKELY(m_length <= 0xFFFFu)) { + setError(QWebSocketProtocol::CloseCodeProtocolError, + tr("Lengths smaller than 65536 (2^16) must be expressed as 2 bytes.")); + return PS_DISPATCH_RESULT; + } + return hasMask() ? PS_READ_MASK : PS_READ_PAYLOAD; + } + break; + default: + Q_UNREACHABLE(); + break; } + return PS_WAIT_FOR_MORE_DATA; +} - return frame; +/*! + \internal + */ +QWebSocketFrame::ProcessingState QWebSocketFrame::readFrameMask(QIODevice *pIoDevice) +{ + if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) { + if (Q_UNLIKELY(pIoDevice->read(reinterpret_cast<char *>(&m_mask), sizeof(m_mask)) < 4)) { + setError(QWebSocketProtocol::CloseCodeGoingAway, + tr("Error while reading from the network: %1.").arg(pIoDevice->errorString())); + return PS_DISPATCH_RESULT; + } + m_mask = qFromBigEndian(m_mask); + return PS_READ_PAYLOAD; + } + return PS_WAIT_FOR_MORE_DATA; +} + +/*! + \internal + */ +QWebSocketFrame::ProcessingState QWebSocketFrame::readFramePayload(QIODevice *pIoDevice) +{ + if (!m_length) + return PS_DISPATCH_RESULT; + + if (Q_UNLIKELY(m_length > MAX_FRAME_SIZE_IN_BYTES)) { + setError(QWebSocketProtocol::CloseCodeTooMuchData, tr("Maximum framesize exceeded.")); + return PS_DISPATCH_RESULT; + } + if (quint64(pIoDevice->bytesAvailable()) >= m_length) { + m_payload = pIoDevice->read(int(m_length)); + // m_length can be safely cast to an integer, + // because MAX_FRAME_SIZE_IN_BYTES = MAX_INT + if (Q_UNLIKELY(m_payload.length() != int(m_length))) { + // some error occurred; refer to the Qt documentation of QIODevice::read() + setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, + tr("Some serious error occurred while reading from the network.")); + } else if (hasMask()) { + QWebSocketProtocol::mask(&m_payload, mask()); + } + return PS_DISPATCH_RESULT; + } + return PS_WAIT_FOR_MORE_DATA; } /*! diff --git a/src/websockets/qwebsocketframe_p.h b/src/websockets/qwebsocketframe_p.h index 5c3a7df..e2b4e9f 100644 --- a/src/websockets/qwebsocketframe_p.h +++ b/src/websockets/qwebsocketframe_p.h @@ -101,15 +101,16 @@ public: void clear(); bool isValid() const; + bool isDone() const; - static QWebSocketFrame readFrame(QIODevice *pIoDevice); + void readFrame(QIODevice *pIoDevice); private: QWebSocketProtocol::CloseCode m_closeCode; QString m_closeReason; quint32 m_mask; QWebSocketProtocol::OpCode m_opCode; - quint8 m_length; + quint64 m_length; QByteArray m_payload; bool m_isFinalFrame; @@ -122,12 +123,16 @@ private: { PS_READ_HEADER, PS_READ_PAYLOAD_LENGTH, - PS_READ_BIG_PAYLOAD_LENGTH, PS_READ_MASK, PS_READ_PAYLOAD, PS_DISPATCH_RESULT, PS_WAIT_FOR_MORE_DATA - }; + } m_processingState{PS_READ_HEADER}; + + ProcessingState readFrameHeader(QIODevice *pIoDevice); + ProcessingState readFramePayloadLength(QIODevice *pIoDevice); + ProcessingState readFrameMask(QIODevice *pIoDevice); + ProcessingState readFramePayload(QIODevice *pIoDevice); void setError(QWebSocketProtocol::CloseCode code, const QString &closeReason); bool checkValidity(); diff --git a/src/websockets/qwebsockethandshakeresponse.cpp b/src/websockets/qwebsockethandshakeresponse.cpp index 383f1bf..d3ef609 100644 --- a/src/websockets/qwebsockethandshakeresponse.cpp +++ b/src/websockets/qwebsockethandshakeresponse.cpp @@ -162,13 +162,16 @@ QString QWebSocketHandshakeResponse::getHandshakeResponse( if (request.isValid()) { const QString acceptKey = calculateAcceptKey(request.key()); const QList<QString> matchingProtocols = - listIntersection(supportedProtocols, request.protocols(), std::less<>()); + listIntersection(supportedProtocols, request.protocols(), + std::less<QString>()); //TODO: extensions must be kept in the order in which they arrive //cannot use set.intersect() to get the supported extensions const QList<QString> matchingExtensions = - listIntersection(supportedExtensions, request.extensions(), std::less<>()); + listIntersection(supportedExtensions, request.extensions(), + std::less<QString>()); const QList<QWebSocketProtocol::Version> matchingVersions = - listIntersection(supportedVersions, request.versions(), std::greater<>()); //sort in descending order + listIntersection(supportedVersions, request.versions(), + std::greater<QWebSocketProtocol::Version>()); //sort in descending order if (Q_UNLIKELY(matchingVersions.isEmpty())) { m_error = QWebSocketProtocol::CloseCodeProtocolError; diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp index 9717401..eafe3fd 100644 --- a/src/websockets/qwebsocketserver.cpp +++ b/src/websockets/qwebsocketserver.cpp @@ -353,14 +353,26 @@ int QWebSocketServer::maxPendingConnections() const } /*! + \fn std::chrono::milliseconds QWebSocketServer::handshakeTimeout() const Returns the handshake timeout for new connections in milliseconds. The default is 10 seconds. If a peer uses more time to complete the handshake their connection is closed. - \sa setHandshakeTimeout() + \sa setHandshakeTimeout(), handshakeTimeoutMS() + \since 5.14 */ -int QWebSocketServer::handshakeTimeout() const + +/*! + Returns the handshake timeout for new connections in milliseconds. + + The default is 10 seconds. If a peer uses more time to complete the + handshake their connection is closed. + + \sa setHandshakeTimeout(), handshakeTimeout() + \since 5.14 + */ +int QWebSocketServer::handshakeTimeoutMS() const { Q_D(const QWebSocketServer); return d->handshakeTimeout(); @@ -592,14 +604,20 @@ void QWebSocketServer::setMaxPendingConnections(int numConnections) } /*! + \fn void QWebSocketServer::setHandshakeTimeout(std::chrono::milliseconds msec) Sets the handshake timeout for new connections to \a msec milliseconds. By default this is set to 10 seconds. If a peer uses more time to complete the handshake, their connection is closed. You can pass a negative value (e.g. -1) to disable the timeout. - \sa handshakeTimeout() + \sa handshakeTimeout(), handshakeTimeoutMS() + \since 5.14 */ + +/*! + \overload +*/ void QWebSocketServer::setHandshakeTimeout(int msec) { Q_D(QWebSocketServer); diff --git a/src/websockets/qwebsocketserver.h b/src/websockets/qwebsocketserver.h index dd06448..ceb9106 100644 --- a/src/websockets/qwebsocketserver.h +++ b/src/websockets/qwebsocketserver.h @@ -52,6 +52,10 @@ #include <QtNetwork/QSslError> #endif +#if QT_HAS_INCLUDE(<chrono>) +#include <chrono> +#endif + QT_BEGIN_NAMESPACE class QTcpSocket; @@ -86,8 +90,18 @@ public: void setMaxPendingConnections(int numConnections); int maxPendingConnections() const; +#if QT_HAS_INCLUDE(<chrono>) || defined(Q_CLANG_QDOC) + void setHandshakeTimeout(std::chrono::milliseconds msec) + { + setHandshakeTimeout(int(msec.count())); + } + std::chrono::milliseconds handshakeTimeout() const + { + return std::chrono::milliseconds(handshakeTimeoutMS()); + } +#endif void setHandshakeTimeout(int msec); - int handshakeTimeout() const; + int handshakeTimeoutMS() const; quint16 serverPort() const; QHostAddress serverAddress() const; |