Skip to content

Commit

Permalink
Pull in QT WebSocket library for parsing
Browse files Browse the repository at this point in the history
This avoids crashes when frames arrive in separate TCP packets.
  • Loading branch information
awwright committed May 27, 2022
1 parent 5565c3a commit 3da3065
Show file tree
Hide file tree
Showing 15 changed files with 1,571 additions and 114 deletions.
357 changes: 357 additions & 0 deletions webaccess/src/QtWebSockets/qwebsocketdataprocessor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
/****************************************************************************
**
** Copyright (C) 2016 Kurt Pattyn <[email protected]>.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.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 \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/QTextCodec>
#include <QtCore/QTextDecoder>
#include <QtCore/QDebug>

#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_binaryMessage(),
m_textMessage(),
m_payloadLength(0),
m_pConverterState(nullptr),
m_pTextCodec(QTextCodec::codecForName("UTF-8")),
m_waitTimer(new QTimer(this))
{
clear();
// initialize the internal timeout timer
m_waitTimer->setInterval(5000);
m_waitTimer->setSingleShot(true);
m_waitTimer->callOnTimeout(this, &QWebSocketDataProcessor::timeout);
}

/*!
\internal
*/
QWebSocketDataProcessor::~QWebSocketDataProcessor()
{
clear();
if (m_pConverterState) {
delete m_pConverterState;
m_pConverterState = nullptr;
}
}

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
Returns \c true if a complete websocket frame has been processed;
otherwise returns \c false.
*/
bool QWebSocketDataProcessor::process(QIODevice *pIoDevice)
{
bool isDone = false;

while (!isDone) {
frame.readFrame(pIoDevice);
if (!frame.isDone()) {
// waiting for more data available
QObject::connect(pIoDevice, &QIODevice::readyRead,
m_waitTimer, &QTimer::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())) {
clear();
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
tr("Received Continuation frame, while there is " \
"nothing to continue."));
return true;
}
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 true;
}
if (!frame.isContinuationFrame()) {
m_opCode = frame.opCode();
m_isFragmented = !frame.isFinalFrame();
}
quint64 messageLength = m_opCode == QWebSocketProtocol::OpCodeText
? quint64(m_textMessage.length())
: quint64(m_binaryMessage.length());
if (Q_UNLIKELY((messageLength + quint64(frame.payload().length())) >
maxAllowedMessageSize())) {
clear();
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData,
tr("Received message is too big."));
return true;
}

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 true;
} 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()) {
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 {
Q_EMIT errorEncountered(frame.closeCode(), frame.closeReason());
clear();
isDone = true;
}
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;
if (m_pConverterState) {
if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) {
delete m_pConverterState;
m_pConverterState = 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<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) {
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<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()
{
clear();
Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeGoingAway,
tr("Timeout when reading data from socket."));
}

QT_END_NAMESPACE
Loading

0 comments on commit 3da3065

Please sign in to comment.