/**************************************************************************** ** ** Copyright (C) 2014 Kurt Pattyn . ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWebSockets module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /*! \class QWebSocketDataProcessor The class QWebSocketDataProcessor is responsible for reading, validating and interpreting data from a WebSocket. It reads data from a QIODevice, validates it against RFC 6455, and parses it into frames (data, control). It emits signals that correspond to the type of the frame: textFrameReceived(), binaryFrameReceived(), textMessageReceived(), binaryMessageReceived(), pingReceived(), pongReceived() and closeReceived(). Whenever an error is detected, the errorEncountered() signal is emitted. QWebSocketDataProcessor also checks if a frame is allowed in a sequence of frames (e.g. a continuation frame cannot follow a final frame). This class is an internal class used by QWebSocketInternal for data processing and validation. \sa Frame() \internal */ #include "qwebsocketdataprocessor_p.h" #include "qwebsocketprotocol.h" #include "qwebsocketprotocol_p.h" #include "qwebsocketframe_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE /*! \internal */ QWebSocketDataProcessor::QWebSocketDataProcessor(QObject *parent) : QObject(parent), m_processingState(PS_READ_HEADER), m_isFinalFrame(false), m_isFragmented(false), m_opCode(QWebSocketProtocol::OpCodeClose), m_isControlFrame(false), m_hasMask(false), m_mask(0), m_binaryMessage(), m_textMessage(), m_payloadLength(0), m_pConverterState(Q_NULLPTR), m_pTextCodec(QTextCodec::codecForName("UTF-8")) { clear(); } /*! \internal */ QWebSocketDataProcessor::~QWebSocketDataProcessor() { clear(); if (m_pConverterState) { delete m_pConverterState; m_pConverterState = Q_NULLPTR; } } /*! \internal */ quint64 QWebSocketDataProcessor::maxMessageSize() { return MAX_MESSAGE_SIZE_IN_BYTES; //COV_NF_LINE } /*! \internal */ quint64 QWebSocketDataProcessor::maxFrameSize() { return MAX_FRAME_SIZE_IN_BYTES; } /*! \internal */ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) { bool isDone = false; while (!isDone) { QWebSocketFrame frame = QWebSocketFrame::readFrame(pIoDevice); if (Q_LIKELY(frame.isValid())) { if (frame.isControlFrame()) { isDone = processControlFrame(frame); } else { //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY if (Q_UNLIKELY(!m_isFragmented && frame.isContinuationFrame())) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("Received Continuation frame, while there is " \ "nothing to continue.")); return; } if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame())) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("All data frames after the initial data frame " \ "must have opcode 0 (continuation).")); return; } if (!frame.isContinuationFrame()) { m_opCode = frame.opCode(); m_isFragmented = !frame.isFinalFrame(); } quint64 messageLength = (quint64)(m_opCode == QWebSocketProtocol::OpCodeText) ? m_textMessage.length() : m_binaryMessage.length(); if (Q_UNLIKELY((messageLength + quint64(frame.payload().length())) > MAX_MESSAGE_SIZE_IN_BYTES)) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData, tr("Received message is too big.")); return; } if (m_opCode == QWebSocketProtocol::OpCodeText) { QString frameTxt = m_pTextCodec->toUnicode(frame.payload().constData(), frame.payload().size(), m_pConverterState); bool failed = (m_pConverterState->invalidChars != 0) || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0)); if (Q_UNLIKELY(failed)) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeWrongDatatype, tr("Invalid UTF-8 code encountered.")); return; } else { m_textMessage.append(frameTxt); Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame()); } } else { m_binaryMessage.append(frame.payload()); Q_EMIT binaryFrameReceived(frame.payload(), frame.isFinalFrame()); } if (frame.isFinalFrame()) { if (m_opCode == QWebSocketProtocol::OpCodeText) Q_EMIT textMessageReceived(m_textMessage); else Q_EMIT binaryMessageReceived(m_binaryMessage); clear(); isDone = true; } } } else { Q_EMIT errorEncountered(frame.closeCode(), frame.closeReason()); clear(); isDone = true; } } } /*! \internal */ void QWebSocketDataProcessor::clear() { m_processingState = PS_READ_HEADER; m_isFinalFrame = false; m_isFragmented = false; m_opCode = QWebSocketProtocol::OpCodeClose; m_hasMask = false; m_mask = 0; m_binaryMessage.clear(); m_textMessage.clear(); m_payloadLength = 0; if (m_pConverterState) { if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) { delete m_pConverterState; m_pConverterState = Q_NULLPTR; } } if (!m_pConverterState) m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull | QTextCodec::IgnoreHeader); } /*! \internal */ bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame) { bool mustStopProcessing = true; //control frames never expect additional frames to be processed switch (frame.opCode()) { case QWebSocketProtocol::OpCodePing: Q_EMIT pingReceived(frame.payload()); break; case QWebSocketProtocol::OpCodePong: Q_EMIT pongReceived(frame.payload()); break; case QWebSocketProtocol::OpCodeClose: { quint16 closeCode = QWebSocketProtocol::CloseCodeNormal; QString closeReason; QByteArray payload = frame.payload(); if (Q_UNLIKELY(payload.size() == 1)) { //size is either 0 (no close code and no reason) //or >= 2 (at least a close code of 2 bytes) closeCode = QWebSocketProtocol::CloseCodeProtocolError; closeReason = tr("Payload of close frame is too small."); } else if (Q_LIKELY(payload.size() > 1)) { //close frame can have a close code and reason closeCode = qFromBigEndian(reinterpret_cast(payload.constData())); if (Q_UNLIKELY(!QWebSocketProtocol::isCloseCodeValid(closeCode))) { closeCode = QWebSocketProtocol::CloseCodeProtocolError; closeReason = tr("Invalid close code %1 detected.").arg(closeCode); } else { if (payload.size() > 2) { QTextCodec *tc = QTextCodec::codecForName(QByteArrayLiteral("UTF-8")); QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); closeReason = tc->toUnicode(payload.constData() + 2, payload.size() - 2, &state); const bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); if (Q_UNLIKELY(failed)) { closeCode = QWebSocketProtocol::CloseCodeWrongDatatype; closeReason = tr("Invalid UTF-8 code encountered."); } } } } Q_EMIT closeReceived(static_cast(closeCode), closeReason); break; } case QWebSocketProtocol::OpCodeContinue: case QWebSocketProtocol::OpCodeBinary: case QWebSocketProtocol::OpCodeText: case QWebSocketProtocol::OpCodeReserved3: case QWebSocketProtocol::OpCodeReserved4: case QWebSocketProtocol::OpCodeReserved5: case QWebSocketProtocol::OpCodeReserved6: case QWebSocketProtocol::OpCodeReserved7: case QWebSocketProtocol::OpCodeReservedC: case QWebSocketProtocol::OpCodeReservedB: case QWebSocketProtocol::OpCodeReservedD: case QWebSocketProtocol::OpCodeReservedE: case QWebSocketProtocol::OpCodeReservedF: //do nothing //case statements added to make C++ compiler happy break; default: Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("Invalid opcode detected: %1").arg(int(frame.opCode()))); //do nothing break; } return mustStopProcessing; } QT_END_NAMESPACE