Revision control

Copy as Markdown

// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:network-protocol
/*!
\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 \l{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 <QtCore/QtEndian>
#include <QtCore/QDebug>
#include <QtCore/QIODevice>
#include <QtCore/QStringDecoder>
#include <limits.h>
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_payloadLength(0),
m_decoder(QStringDecoder(QStringDecoder::Utf8, QStringDecoder::Flag::ConvertInvalidToNull)),
m_waitTimer(new QChronoTimer(this))
{
clear();
// initialize the internal timeout timer
using namespace std::chrono_literals;
m_waitTimer->setInterval(5s);
m_waitTimer->setSingleShot(true);
m_waitTimer->callOnTimeout(this, &QWebSocketDataProcessor::timeout);
}
/*!
\internal
*/
QWebSocketDataProcessor::~QWebSocketDataProcessor()
{
clear();
}
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
*/
quint64 QWebSocketDataProcessor::maxMessageSize()
{
return MAX_MESSAGE_SIZE_IN_BYTES; //COV_NF_LINE
}
/*!
\internal
*/
quint64 QWebSocketDataProcessor::maxFrameSize()
{
return QWebSocketFrame::maxFrameSize();
}
/*!
\internal
*/
void QWebSocketDataProcessor::setIdleTimeout(std::chrono::milliseconds timeout)
{
Q_ASSERT(!m_waitTimer->isActive());
m_waitTimer->setInterval(timeout);
}
/*!
\internal
*/
std::chrono::milliseconds QWebSocketDataProcessor::idleTimeout() const
{
using namespace std::chrono;
return duration_cast<milliseconds>(m_waitTimer->interval());
}
/*!
\internal
Returns \c true if a complete websocket frame has been processed;
otherwise returns \c false.
*/
bool QWebSocketDataProcessor::process(QIODevice *pIoDevice)
{
if (Q_UNLIKELY(m_hasFailed))
return false;
bool isDone = false;
while (!isDone) {
frame.readFrame(pIoDevice);
if (!frame.isDone()) {
// waiting for more data available
QObject::connect(pIoDevice, &QIODevice::readyRead,
m_waitTimer, &QChronoTimer::stop, Qt::UniqueConnection);
m_waitTimer->start();
return false;
} else 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())) {
reportError(QWebSocketProtocol::CloseCodeProtocolError,
tr("Received Continuation frame, while there is " \
"nothing to continue."));
return false;
}
if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() &&
!frame.isContinuationFrame())) {
reportError(QWebSocketProtocol::CloseCodeProtocolError,
tr("All data frames after the initial data frame " \
"must have opcode 0 (continuation)."));
return false;
}
if (!frame.isContinuationFrame()) {
m_opCode = frame.opCode();
m_isFragmented = !frame.isFinalFrame();
}
quint64 messageLength = m_opCode == QWebSocketProtocol::OpCodeText
? quint64(m_textMessage.size())
: quint64(m_binaryMessage.size());
if (Q_UNLIKELY((messageLength + quint64(frame.payload().size())) >
maxAllowedMessageSize())) {
reportError(QWebSocketProtocol::CloseCodeTooMuchData,
tr("Received message is too big."));
return false;
}
bool isFinalFrame = frame.isFinalFrame();
if (m_opCode == QWebSocketProtocol::OpCodeText) {
QString frameTxt = m_decoder(frame.payload());
bool decoderHadError = m_decoder.hasError();
if (!decoderHadError && isFinalFrame) {
auto r = m_decoder.finalize();
decoderHadError = r.error != QStringDecoder::FinalizeResult::Error::NoError;
}
if (Q_UNLIKELY(decoderHadError)) {
reportError(QWebSocketProtocol::CloseCodeWrongDatatype,
tr("Invalid UTF-8 code encountered."));
return false;
} else {
m_textMessage.append(frameTxt);
frame.clear();
Q_EMIT textFrameReceived(frameTxt, isFinalFrame);
}
} else {
m_binaryMessage.append(frame.payload());
QByteArray payload = frame.payload();
frame.clear();
Q_EMIT binaryFrameReceived(payload, isFinalFrame);
}
if (isFinalFrame) {
isDone = true;
if (m_opCode == QWebSocketProtocol::OpCodeText) {
const QString textMessage(m_textMessage);
clear();
Q_EMIT textMessageReceived(textMessage);
} else {
const QByteArray binaryMessage(m_binaryMessage);
clear();
Q_EMIT binaryMessageReceived(binaryMessage);
}
}
}
} else {
reportError(frame.closeCode(), frame.closeReason());
return false;
}
frame.clear();
}
return 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;
m_decoder.resetState();
m_hasFailed = false;
frame.clear();
}
/*!
\internal
Reports a protocol violation and marks the connection as failed, so that no
further data is parsed after the error.
*/
void QWebSocketDataProcessor::reportError(QWebSocketProtocol::CloseCode code,
const QString &description)
{
clear();
m_hasFailed = true;
Q_EMIT errorEncountered(code, description);
}
/*!
\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<quint16>(reinterpret_cast<const uchar *>(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) {
auto toUtf16 = QStringDecoder(QStringDecoder::Utf8,
QStringDecoder::Flag::Stateless | QStringDecoder::Flag::ConvertInvalidToNull);
closeReason = toUtf16(QByteArrayView(payload).sliced(2));
if (Q_UNLIKELY(toUtf16.hasError())) {
closeCode = QWebSocketProtocol::CloseCodeWrongDatatype;
closeReason = tr("Invalid UTF-8 code encountered.");
}
}
}
}
Q_EMIT closeReceived(static_cast<QWebSocketProtocol::CloseCode>(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;
}
/*!
\internal
*/
void QWebSocketDataProcessor::timeout()
{
reportError(QWebSocketProtocol::CloseCodeGoingAway,
tr("Timeout when reading data from socket."));
}
QT_END_NAMESPACE