// Copyright (C) 2016 Kurt Pattyn . // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only /*! \class QWebSocketFrame The class QWebSocketFrame is responsible for reading, validating and interpreting frames from a WebSocket. It reads data from a QIODevice, validates it against RFC 6455, and parses it into a frame (data, control). Whenever an error is detected, isValid() returns false. \note The QWebSocketFrame class does not look at valid sequences of frames. It processes frames one at a time. \note It is the QWebSocketDataProcessor that takes the sequence into account. \sa QWebSocketDataProcessor \internal */ #include "qwebsocketframe_p.h" #include "qwebsocketprotocol_p.h" #include #include #include QT_BEGIN_NAMESPACE /*! \internal */ void QWebSocketFrame::setMaxAllowedFrameSize(quint64 maxAllowedFrameSize) { if (maxAllowedFrameSize <= maxFrameSize()) m_maxAllowedFrameSize = maxAllowedFrameSize; } /*! \internal */ quint64 QWebSocketFrame::maxAllowedFrameSize() const { return m_maxAllowedFrameSize; } /*! \internal */ quint64 QWebSocketFrame::maxFrameSize() { return MAX_FRAME_SIZE_IN_BYTES; } /*! \internal */ QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const { return isDone() ? m_closeCode : QWebSocketProtocol::CloseCodeGoingAway; } /*! \internal */ QString QWebSocketFrame::closeReason() const { return isDone() ? m_closeReason : tr("Waiting for more data from socket."); } /*! \internal */ bool QWebSocketFrame::isFinalFrame() const { return m_isFinalFrame; } /*! \internal */ bool QWebSocketFrame::isControlFrame() const { return (m_opCode & 0x08) == 0x08; } /*! \internal */ bool QWebSocketFrame::isDataFrame() const { return !isControlFrame(); } /*! \internal */ bool QWebSocketFrame::isContinuationFrame() const { return isDataFrame() && (m_opCode == QWebSocketProtocol::OpCodeContinue); } /*! \internal */ bool QWebSocketFrame::hasMask() const { return m_mask != 0; } /*! \internal */ quint32 QWebSocketFrame::mask() const { return m_mask; } /*! \internal */ QWebSocketProtocol::OpCode QWebSocketFrame::opCode() const { return m_opCode; } /*! \internal */ QByteArray QWebSocketFrame::payload() const { return m_payload; } /*! Resets all member variables, and invalidates the object. \internal */ void QWebSocketFrame::clear() { m_closeCode = QWebSocketProtocol::CloseCodeNormal; m_closeReason.clear(); m_isFinalFrame = true; m_mask = 0; m_rsv1 = false; m_rsv2 = false; m_rsv3 = false; m_opCode = QWebSocketProtocol::OpCodeReservedC; m_length = 0; m_payload.clear(); m_isValid = false; m_processingState = PS_READ_HEADER; } /*! \internal */ bool QWebSocketFrame::isValid() const { return isDone() && m_isValid; } /*! \internal */ bool QWebSocketFrame::isDone() const { return m_processingState == PS_DISPATCH_RESULT; } /*! \internal */ void QWebSocketFrame::readFrame(QIODevice *pIoDevice) { while (true) { switch (m_processingState) { case PS_READ_HEADER: m_processingState = readFrameHeader(pIoDevice); if (m_processingState == PS_WAIT_FOR_MORE_DATA) { m_processingState = PS_READ_HEADER; return; } break; case PS_READ_PAYLOAD_LENGTH: m_processingState = readFramePayloadLength(pIoDevice); if (m_processingState == PS_WAIT_FOR_MORE_DATA) { m_processingState = PS_READ_PAYLOAD_LENGTH; return; } break; case PS_READ_MASK: m_processingState = readFrameMask(pIoDevice); if (m_processingState == PS_WAIT_FOR_MORE_DATA) { m_processingState = PS_READ_MASK; return; } break; case PS_READ_PAYLOAD: m_processingState = readFramePayload(pIoDevice); if (m_processingState == PS_WAIT_FOR_MORE_DATA) { m_processingState = PS_READ_PAYLOAD; return; } break; case PS_DISPATCH_RESULT: return; default: 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(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 https://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 https://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; } /*! \internal */ void QWebSocketFrame::setError(QWebSocketProtocol::CloseCode code, const QString &closeReason) { clear(); m_closeCode = code; m_closeReason = closeReason; m_isValid = false; } /*! \internal */ bool QWebSocketFrame::checkValidity() { if (Q_UNLIKELY(m_rsv1 || m_rsv2 || m_rsv3)) { setError(QWebSocketProtocol::CloseCodeProtocolError, tr("Rsv field is non-zero")); } else if (Q_UNLIKELY(QWebSocketProtocol::isOpCodeReserved(m_opCode))) { setError(QWebSocketProtocol::CloseCodeProtocolError, tr("Used reserved opcode")); } else if (isControlFrame()) { if (Q_UNLIKELY(m_length > 125)) { setError(QWebSocketProtocol::CloseCodeProtocolError, tr("Control frame is larger than 125 bytes")); } else if (Q_UNLIKELY(!m_isFinalFrame)) { setError(QWebSocketProtocol::CloseCodeProtocolError, tr("Control frames cannot be fragmented")); } else { m_isValid = true; } } else { m_isValid = true; } return m_isValid; } QT_END_NAMESPACE