diff --git a/webaccess/src/QtWebSockets/qwebsocketdataprocessor.cpp b/webaccess/src/QtWebSockets/qwebsocketdataprocessor.cpp new file mode 100644 index 0000000000..4110f2a2eb --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketdataprocessor.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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 +#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(nullptr), + m_pTextCodec(QTextCodec::codecForName("UTF-8")) +{ + clear(); + // initialize the internal timeout timer + waitTimer.setInterval(5000); + waitTimer.setSingleShot(true); + 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, + &waitTimer, &QTimer::stop, Qt::UniqueConnection); + 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(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; +} + +/*! + \internal + */ +void QWebSocketDataProcessor::timeout() +{ + clear(); + Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeGoingAway, + tr("Timeout when reading data from socket.")); +} + +QT_END_NAMESPACE diff --git a/webaccess/src/QtWebSockets/qwebsocketdataprocessor_p.h b/webaccess/src/QtWebSockets/qwebsocketdataprocessor_p.h new file mode 100644 index 0000000000..62a2dc09a5 --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketdataprocessor_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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$ +** +****************************************************************************/ + +#ifndef QWEBSOCKETDATAPROCESSOR_P_H +#define QWEBSOCKETDATAPROCESSOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include "qwebsocketframe_p.h" +#include "qwebsocketprotocol.h" +#include "qwebsocketprotocol_p.h" + +QT_BEGIN_NAMESPACE + +class QIODevice; +class QWebSocketFrame; + +const quint64 MAX_MESSAGE_SIZE_IN_BYTES = std::numeric_limits::max() - 1; + +class Q_AUTOTEST_EXPORT QWebSocketDataProcessor : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QWebSocketDataProcessor) + +public: + explicit QWebSocketDataProcessor(QObject *parent = nullptr); + ~QWebSocketDataProcessor() override; + + void setMaxAllowedFrameSize(quint64 maxAllowedFrameSize); + quint64 maxAllowedFrameSize() const; + void setMaxAllowedMessageSize(quint64 maxAllowedMessageSize); + quint64 maxAllowedMessageSize() const; + static quint64 maxMessageSize(); + static quint64 maxFrameSize(); + +Q_SIGNALS: + void pingReceived(const QByteArray &data); + void pongReceived(const QByteArray &data); + void closeReceived(QWebSocketProtocol::CloseCode closeCode, const QString &closeReason); + void textFrameReceived(const QString &frame, bool lastFrame); + void binaryFrameReceived(const QByteArray &frame, bool lastFrame); + void textMessageReceived(const QString &message); + void binaryMessageReceived(const QByteArray &message); + void errorEncountered(QWebSocketProtocol::CloseCode code, const QString &description); + +public Q_SLOTS: + bool process(QIODevice *pIoDevice); + void clear(); + +private: + enum + { + PS_READ_HEADER, + PS_READ_PAYLOAD_LENGTH, + PS_READ_BIG_PAYLOAD_LENGTH, + PS_READ_MASK, + PS_READ_PAYLOAD, + PS_DISPATCH_RESULT + } m_processingState; + + bool m_isFinalFrame; + bool m_isFragmented; + QWebSocketProtocol::OpCode m_opCode; + bool m_isControlFrame; + bool m_hasMask; + quint32 m_mask; + QByteArray m_binaryMessage; + QString m_textMessage; + quint64 m_payloadLength; + QTextCodec::ConverterState *m_pConverterState; + QTextCodec *m_pTextCodec; + QWebSocketFrame frame; + QTimer waitTimer; + quint64 m_maxAllowedMessageSize = MAX_MESSAGE_SIZE_IN_BYTES; + + bool processControlFrame(const QWebSocketFrame &frame); + void timeout(); +}; + +QT_END_NAMESPACE + +#endif // QWEBSOCKETDATAPROCESSOR_P_H diff --git a/webaccess/src/QtWebSockets/qwebsocketframe.cpp b/webaccess/src/QtWebSockets/qwebsocketframe.cpp new file mode 100644 index 0000000000..716aebd3af --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketframe.cpp @@ -0,0 +1,438 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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 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 + +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 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(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 http://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 diff --git a/webaccess/src/QtWebSockets/qwebsocketframe_p.h b/webaccess/src/QtWebSockets/qwebsocketframe_p.h new file mode 100644 index 0000000000..992379ab86 --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketframe_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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$ +** +****************************************************************************/ + +#ifndef QWEBSOCKETFRAME_P_H +#define QWEBSOCKETFRAME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +#include "qwebsockets_global.h" +#include "qwebsocketprotocol.h" +#include "qwebsocketprotocol_p.h" + +QT_BEGIN_NAMESPACE + +class QIODevice; + +const quint64 MAX_FRAME_SIZE_IN_BYTES = std::numeric_limits::max() - 1; + +class Q_AUTOTEST_EXPORT QWebSocketFrame +{ + Q_DECLARE_TR_FUNCTIONS(QWebSocketFrame) + +public: + QWebSocketFrame() = default; + + void setMaxAllowedFrameSize(quint64 maxAllowedFrameSize); + quint64 maxAllowedFrameSize() const; + static quint64 maxFrameSize(); + + QWebSocketProtocol::CloseCode closeCode() const; + QString closeReason() const; + bool isFinalFrame() const; + bool isControlFrame() const; + bool isDataFrame() const; + bool isContinuationFrame() const; + bool hasMask() const; + quint32 mask() const; //returns 0 if no mask + inline bool rsv1() const { return m_rsv1; } + inline bool rsv2() const { return m_rsv2; } + inline bool rsv3() const { return m_rsv3; } + QWebSocketProtocol::OpCode opCode() const; + QByteArray payload() const; + + void clear(); + + bool isValid() const; + bool isDone() const; + + void readFrame(QIODevice *pIoDevice); + +private: + QString m_closeReason; + QByteArray m_payload; + quint64 m_length = 0; + quint32 m_mask = 0; + QWebSocketProtocol::CloseCode m_closeCode = QWebSocketProtocol::CloseCodeNormal; + QWebSocketProtocol::OpCode m_opCode = QWebSocketProtocol::OpCodeReservedC; + + enum ProcessingState + { + PS_READ_HEADER, + PS_READ_PAYLOAD_LENGTH, + PS_READ_MASK, + PS_READ_PAYLOAD, + PS_DISPATCH_RESULT, + PS_WAIT_FOR_MORE_DATA + } m_processingState = PS_READ_HEADER; + + bool m_isFinalFrame = true; + bool m_rsv1 = false; + bool m_rsv2 = false; + bool m_rsv3 = false; + bool m_isValid = false; + quint64 m_maxAllowedFrameSize = MAX_FRAME_SIZE_IN_BYTES; + + 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(); +}; + +QT_END_NAMESPACE + +#endif // QWEBSOCKETFRAME_P_H diff --git a/webaccess/src/QtWebSockets/qwebsocketprotocol.cpp b/webaccess/src/QtWebSockets/qwebsocketprotocol.cpp new file mode 100644 index 0000000000..df87a93cda --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketprotocol.cpp @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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$ +** +****************************************************************************/ + +#include "qwebsocketprotocol_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \namespace QWebSocketProtocol + \inmodule QtWebSockets + \brief Contains constants related to the WebSocket standard. + \since 5.3 +*/ + +/*! + \enum QWebSocketProtocol::CloseCode + + \inmodule QtWebSockets + + The close codes supported by WebSockets V13 + + \value CloseCodeNormal Normal closure + \value CloseCodeGoingAway Going away + \value CloseCodeProtocolError Protocol error + \value CloseCodeDatatypeNotSupported Unsupported data + \value CloseCodeReserved1004 Reserved + \value CloseCodeMissingStatusCode No status received + \value CloseCodeAbnormalDisconnection Abnormal closure + \value CloseCodeWrongDatatype Invalid frame payload data + \value CloseCodePolicyViolated Policy violation + \value CloseCodeTooMuchData Message too big + \value CloseCodeMissingExtension Mandatory extension missing + \value CloseCodeBadOperation Internal server error + \value CloseCodeTlsHandshakeFailed TLS handshake failed + + \sa QWebSocket::close() +*/ +/*! + \enum QWebSocketProtocol::Version + + \inmodule QtWebSockets + + \brief The different defined versions of the WebSocket protocol. + + For an overview of the differences between the different protocols, see + \l {pywebsocket's WebSocketProtocolSpec}. + + \value VersionUnknown Unknown or unspecified version. + \value Version0 \l{hixie76} and \l{hybi-00}. + Works with key1, key2 and a key in the payload. + Attribute: Sec-WebSocket-Draft value 0. + Not supported by QtWebSockets. + \value Version4 \l{hybi-04}. + Changed handshake: key1, key2, key3 + ==> Sec-WebSocket-Key, Sec-WebSocket-Nonce, Sec-WebSocket-Accept + Sec-WebSocket-Draft renamed to Sec-WebSocket-Version + Sec-WebSocket-Version = 4. + Not supported by QtWebSockets. + \value Version5 \l{hybi-05}. + Sec-WebSocket-Version = 5 + Removed Sec-WebSocket-Nonce + Added Sec-WebSocket-Accept. + Not supported by QtWebSockets. + \value Version6 Sec-WebSocket-Version = 6. + Not supported by QtWebSockets. + \value Version7 \l{hybi-07}. + Sec-WebSocket-Version = 7. + Not supported by QtWebSockets. + \value Version8 hybi-8, hybi-9, hybi-10, hybi-11 and hybi-12. + Status codes 1005 and 1006 are added and all codes are now unsigned + Internal error results in 1006. + Not supported by QtWebSockets. + \value Version13 hybi-13, hybi14, hybi-15, hybi-16, hybi-17 and \l{RFC 6455}. + Sec-WebSocket-Version = 13 + Status code 1004 is now reserved + Added 1008, 1009 and 1010 + Must support TLS + Clarify multiple version support. + Supported by QtWebSockets. + \value VersionLatest Refers to the latest known version to QtWebSockets. +*/ + +/*! + \enum QWebSocketProtocol::OpCode + + \inmodule QtWebSockets + + The frame opcodes as defined by the WebSockets standard + + \value OpCodeContinue Continuation frame + \value OpCodeText Text frame + \value OpCodeBinary Binary frame + \value OpCodeReserved3 Reserved + \value OpCodeReserved4 Reserved + \value OpCodeReserved5 Reserved + \value OpCodeReserved6 Reserved + \value OpCodeReserved7 Reserved + \value OpCodeClose Close frame + \value OpCodePing Ping frame + \value OpCodePong Pong frame + \value OpCodeReservedB Reserved + \value OpCodeReservedC Reserved + \value OpCodeReservedD Reserved + \value OpCodeReservedE Reserved + \value OpCodeReservedF Reserved + + \internal +*/ + +/*! + \fn QWebSocketProtocol::isOpCodeReserved(OpCode code) + Checks if \a code is a valid OpCode + + \internal +*/ + +/*! + \fn QWebSocketProtocol::isCloseCodeValid(int closeCode) + Checks if \a closeCode is a valid WebSocket close code + + \internal +*/ + +/*! + \fn QWebSocketProtocol::currentVersion() + Returns the latest version that WebSocket is supporting + + \internal +*/ + +/*! + Parses the \a versionString and converts it to a Version value + + \internal +*/ +QWebSocketProtocol::Version QWebSocketProtocol::versionFromString(const QString &versionString) +{ + bool ok = false; + Version version = VersionUnknown; + const int ver = versionString.toInt(&ok); + QSet supportedVersions; + supportedVersions << Version0 << Version4 << Version5 << Version6 << Version7 << Version8 + << Version13; + if (Q_LIKELY(ok) && (supportedVersions.contains(static_cast(ver)))) + version = static_cast(ver); + return version; +} + +/*! + Mask the \a payload with the given \a maskingKey and stores the result back in \a payload. + + \internal +*/ +void QWebSocketProtocol::mask(QByteArray *payload, quint32 maskingKey) +{ + Q_ASSERT(payload); + mask(payload->data(), quint64(payload->size()), maskingKey); +} + +/*! + Masks the \a payload of length \a size with the given \a maskingKey and + stores the result back in \a payload. + + \internal +*/ +void QWebSocketProtocol::mask(char *payload, quint64 size, quint32 maskingKey) +{ + Q_ASSERT(payload); + const quint8 mask[] = { quint8((maskingKey & 0xFF000000u) >> 24), + quint8((maskingKey & 0x00FF0000u) >> 16), + quint8((maskingKey & 0x0000FF00u) >> 8), + quint8((maskingKey & 0x000000FFu)) + }; + int i = 0; + while (size-- > 0) + *payload++ ^= mask[i++ % 4]; +} + +QT_END_NAMESPACE diff --git a/webaccess/src/QtWebSockets/qwebsocketprotocol.h b/webaccess/src/QtWebSockets/qwebsocketprotocol.h new file mode 100644 index 0000000000..f3af51771b --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketprotocol.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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$ +** +****************************************************************************/ + +#ifndef QWEBSOCKETPROTOCOL_H +#define QWEBSOCKETPROTOCOL_H + +#include +#include "QtWebSockets/qwebsockets_global.h" + +QT_BEGIN_NAMESPACE + +class QString; + +namespace QWebSocketProtocol +{ +enum Version +{ + VersionUnknown = -1, + Version0 = 0, + //hybi-01, hybi-02 and hybi-03 not supported + Version4 = 4, + Version5 = 5, + Version6 = 6, + Version7 = 7, + Version8 = 8, + Version13 = 13, + VersionLatest = Version13 +}; + +enum CloseCode +{ + CloseCodeNormal = 1000, + CloseCodeGoingAway = 1001, + CloseCodeProtocolError = 1002, + CloseCodeDatatypeNotSupported = 1003, + CloseCodeReserved1004 = 1004, + CloseCodeMissingStatusCode = 1005, + CloseCodeAbnormalDisconnection = 1006, + CloseCodeWrongDatatype = 1007, + CloseCodePolicyViolated = 1008, + CloseCodeTooMuchData = 1009, + CloseCodeMissingExtension = 1010, + CloseCodeBadOperation = 1011, + CloseCodeTlsHandshakeFailed = 1015 +}; + +} //end namespace QWebSocketProtocol + +QT_END_NAMESPACE + +#endif // QWEBSOCKETPROTOCOL_H diff --git a/webaccess/src/QtWebSockets/qwebsocketprotocol_p.h b/webaccess/src/QtWebSockets/qwebsocketprotocol_p.h new file mode 100644 index 0000000000..693b07be00 --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsocketprotocol_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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$ +** +****************************************************************************/ + +#ifndef QWEBSOCKETPROTOCOL_P_H +#define QWEBSOCKETPROTOCOL_P_H + +// QLC modification: Work around bug where Qt forgot to include the correct header +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "QtWebSockets/qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +class QString; +class QByteArray; + +namespace QWebSocketProtocol +{ +enum OpCode +{ + OpCodeContinue = 0x0, + OpCodeText = 0x1, + OpCodeBinary = 0x2, + OpCodeReserved3 = 0x3, + OpCodeReserved4 = 0x4, + OpCodeReserved5 = 0x5, + OpCodeReserved6 = 0x6, + OpCodeReserved7 = 0x7, + OpCodeClose = 0x8, + OpCodePing = 0x9, + OpCodePong = 0xA, + OpCodeReservedB = 0xB, + OpCodeReservedC = 0xC, + OpCodeReservedD = 0xD, + OpCodeReservedE = 0xE, + OpCodeReservedF = 0xF +}; + +inline bool isOpCodeReserved(OpCode code) +{ + return ((code > OpCodeBinary) && (code < OpCodeClose)) || (code > OpCodePong); +} + +inline bool isCloseCodeValid(int closeCode) +{ + return (closeCode > 999) && (closeCode < 5000) && + (closeCode != CloseCodeReserved1004) && //see RFC6455 7.4.1 + (closeCode != CloseCodeMissingStatusCode) && + (closeCode != CloseCodeAbnormalDisconnection) && + ((closeCode >= 3000) || (closeCode < 1012)); +} + +inline Version currentVersion() { return VersionLatest; } +Version Q_AUTOTEST_EXPORT versionFromString(const QString &versionString); + +void Q_AUTOTEST_EXPORT mask(QByteArray *payload, quint32 maskingKey); +void Q_AUTOTEST_EXPORT mask(char *payload, quint64 size, quint32 maskingKey); +} //end namespace QWebSocketProtocol + +QT_END_NAMESPACE + +#endif // QWEBSOCKETPROTOCOL_P_H diff --git a/webaccess/src/QtWebSockets/qwebsockets_global.h b/webaccess/src/QtWebSockets/qwebsockets_global.h new file mode 100644 index 0000000000..d4b7acafe1 --- /dev/null +++ b/webaccess/src/QtWebSockets/qwebsockets_global.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Kurt Pattyn . +** 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$ +** +****************************************************************************/ + +#ifndef QWEBSOCKETSGLOBAL_H +#define QWEBSOCKETSGLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_WEBSOCKETS_LIB) +# define Q_WEBSOCKETS_EXPORT Q_DECL_EXPORT +# else +# define Q_WEBSOCKETS_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_WEBSOCKETS_EXPORT +#endif + +QT_END_NAMESPACE +#endif // QWEBSOCKETSGLOBAL_H diff --git a/webaccess/src/qhttpserver/qhttpconnection.cpp b/webaccess/src/qhttpserver/qhttpconnection.cpp index 8a45b50581..806a8f2190 100644 --- a/webaccess/src/qhttpserver/qhttpconnection.cpp +++ b/webaccess/src/qhttpserver/qhttpconnection.cpp @@ -42,7 +42,7 @@ QHttpConnection::QHttpConnection(QTcpSocket *socket, QObject *parent) m_transmitLen(0), m_transmitPos(0), m_postPending(false), - m_isWebSocket(false), + m_webSocketParser(0), m_pollTimer(NULL) { m_parser = (http_parser *)malloc(sizeof(http_parser)); @@ -77,8 +77,11 @@ QHttpConnection::~QHttpConnection() delete m_parserSettings; m_parserSettings = 0; - if (m_isWebSocket == true) + if (m_webSocketParser) + { Q_EMIT webSocketConnectionClose(this); + delete m_webSocketParser; + } qDebug() << "HTTP connection destroyed!"; } @@ -101,7 +104,7 @@ void QHttpConnection::invalidateRequest() void QHttpConnection::updateWriteCount(qint64 count) { - if (m_isWebSocket == false) + if (m_webSocketParser == 0) { //Q_ASSERT(m_transmitPos + count <= m_transmitLen); if (m_transmitPos + count > m_transmitLen) @@ -124,11 +127,16 @@ void QHttpConnection::parseRequest() while (m_socket->bytesAvailable()) { - QByteArray arr = m_socket->readAll(); - if (m_isWebSocket) - webSocketRead(arr); + if (m_webSocketParser) + { + if (m_webSocketParser->process(m_socket) == false) + return; + } else + { + QByteArray arr = m_socket->readAll(); http_parser_execute(m_parser, m_parserSettings, arr.constData(), arr.size()); + } } } @@ -151,7 +159,7 @@ void QHttpConnection::waitForBytesWritten() void QHttpConnection::responseDone() { QHttpResponse *response = qobject_cast(QObject::sender()); - if (response->m_last && m_isWebSocket == false) + if (response->m_last && m_webSocketParser == 0) m_socket->disconnectFromHost(); } @@ -333,16 +341,32 @@ int QHttpConnection::Body(http_parser *parser, const char *at, size_t length) * WebSocket methods *************************************************************************/ -QHttpConnection *QHttpConnection::enableWebSocket(bool enable) +QHttpConnection *QHttpConnection::enableWebSocket() { - m_isWebSocket = enable; m_pollTimer = new QTimer(this); m_pollTimer->setInterval(5000); + connect(m_pollTimer, SIGNAL(timeout()), this, SLOT(slotWebSocketPollTimeout())); m_pollTimer->start(); + + m_webSocketParser = new QWebSocketDataProcessor; + // Don't attempt to parse frames or messages larger than this many bytes + // Increase this number if it becomes necessary for some reason + m_webSocketParser->setMaxAllowedMessageSize(500); + m_isWebSocketCloseSent = false; + m_isWebSocketCloseReceived = false; + connect(m_webSocketParser, SIGNAL(pingReceived(const QByteArray&)), + this, SLOT(slot_pingReceived(const QByteArray&))); + connect(m_webSocketParser, SIGNAL(textMessageReceived(const QString&)), + this, SLOT(slot_textMessageReceived(const QString&))); + connect(m_webSocketParser, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode, const QString)), + this, SLOT(slot_closeReceived(QWebSocketProtocol::CloseCode))); + connect(m_webSocketParser, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode, const QString)), + this, SLOT(slot_closeReceived(QWebSocketProtocol::CloseCode))); + return this; } @@ -351,117 +375,51 @@ void QHttpConnection::slotWebSocketPollTimeout() webSocketWrite(Ping, QByteArray()); } -void QHttpConnection::webSocketWrite(WebSocketOpCode opCode, QByteArray data) +void QHttpConnection::webSocketWrite(WebSocketOpCode opCode, const QByteArray &data) { + QByteArray header; qDebug() << "[webSocketWrite] data size:" << data.size(); + + header.append(0x80 + quint8(opCode)); + if (data.size() < 126) - data.prepend(quint8(data.size())); + header.append(quint8(data.size())); else { - data.prepend(quint8(data.size() & 0x00FF)); - data.prepend(quint8(data.size() >> 8)); - data.prepend(0x7E); + header.append(0x7E); + header.append(quint8(data.size() >> 8)); + header.append(quint8(data.size() & 0x00FF)); } - data.prepend(0x80 + quint8(opCode)); - if (m_socket) + { + m_socket->write(header); m_socket->write(data); + } } -/** - Here's the RFC 6455 Framing specs. The table of the law - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ - */ - -void QHttpConnection::webSocketRead(QByteArray data) +void QHttpConnection::slot_pingReceived(const QByteArray &data) { - if (data.size() < 2) - return; - - int dataPos = 0; - - if (((data.at(dataPos) >> 4) & 0x07) != 0) - { - qWarning() << "Wrong WebSocket RSV bits. Discard."; - return; - } + webSocketWrite(Pong, data); +} - qDebug() << "[webSocketRead] total data length:" << data.size(); +void QHttpConnection::slot_textMessageReceived(const QString &data) +{ + Q_EMIT webSocketDataReady(this, data); +} - while (dataPos < data.size()) +void QHttpConnection::slot_closeReceived(QWebSocketProtocol::CloseCode closeCode) +{ + if(!m_isWebSocketCloseSent) { - int opCode = data.at(dataPos) & 0x0F; - dataPos++; - - bool masked = (data.at(dataPos) >> 7) ? true : false; - int dataLen = data.at(dataPos) & 0x7F; - dataPos++; - - if (dataLen == 126) - { - dataLen = (data.at(dataPos) << 8) + data.at(dataPos + 1); - dataPos+=2; - } - else if (dataLen == 127) - { - // TODO: 64bit length...really ? - dataPos+=8; - } - - quint8 mask[4]; - if (masked == true) - { - mask[0] = quint8(data.at(dataPos)); - mask[1] = quint8(data.at(dataPos + 1)); - mask[2] = quint8(data.at(dataPos + 2)); - mask[3] = quint8(data.at(dataPos + 3)); - dataPos+=4; - } - - qDebug() << "[webSocketRead] opCode:" << QString("0x%1").arg(opCode, 2, 16, QChar('0')); - - if (opCode == TextFrame) - { - int lengthCounter = dataLen; - qDebug() << "[webSocketRead] Text frame length:" << dataLen; - // if the payload is masked, then unmask - if (masked == true) - { - int i = 0; - char *cData = data.data() + dataPos; - while (lengthCounter-- > 0) - *cData++ ^= mask[i++ % 4]; - } - - Q_EMIT webSocketDataReady(this, QString(data.mid(dataPos, dataLen))); - } - else if (opCode == ConnectionClose) - { - qDebug() << "[webSocketRead] Connection closed by the client"; - Q_EMIT webSocketConnectionClose(this); - } - dataPos += dataLen; + QByteArray payload; + payload[0] = ((int)closeCode) >> 8; + payload[1] = ((int)closeCode) & 0xff; + webSocketWrite(ConnectionClose, payload); + m_isWebSocketCloseSent = true; } + m_socket->close(); + Q_EMIT webSocketConnectionClose(this); } - /// @endcond diff --git a/webaccess/src/qhttpserver/qhttpconnection.h b/webaccess/src/qhttpserver/qhttpconnection.h index 82bd51599b..cca596a591 100644 --- a/webaccess/src/qhttpserver/qhttpconnection.h +++ b/webaccess/src/qhttpserver/qhttpconnection.h @@ -25,6 +25,7 @@ //#include "qhttpserverapi.h" #include "qhttpserverfwd.h" +#include "qwebsocketdataprocessor_p.h" #include @@ -98,8 +99,8 @@ private Q_SLOTS: Pong = 0x0A }; - QHttpConnection *enableWebSocket(bool enable); - void webSocketWrite(WebSocketOpCode opCode, QByteArray data); + QHttpConnection *enableWebSocket(); + void webSocketWrite(WebSocketOpCode opCode, const QByteArray &data); Q_SIGNALS: void webSocketDataReady(QHttpConnection *conn, QString data); @@ -107,12 +108,17 @@ private Q_SLOTS: private Q_SLOTS: void slotWebSocketPollTimeout(); + void slot_pingReceived(const QByteArray &data); + void slot_textMessageReceived(const QString &data); + void slot_closeReceived(QWebSocketProtocol::CloseCode closeCode); private: - void webSocketRead(QByteArray data); + qint64 webSocketRead(QTcpSocket *io); private: - bool m_isWebSocket; + QWebSocketDataProcessor* m_webSocketParser; + bool m_isWebSocketCloseSent; + bool m_isWebSocketCloseReceived; QTimer *m_pollTimer; public: diff --git a/webaccess/src/qhttpserver/qhttpresponse.cpp b/webaccess/src/qhttpserver/qhttpresponse.cpp index 1c1f025c2d..117a2d8bb2 100644 --- a/webaccess/src/qhttpserver/qhttpresponse.cpp +++ b/webaccess/src/qhttpserver/qhttpresponse.cpp @@ -183,9 +183,9 @@ QByteArray QHttpResponse::getWebSocketHandshake(QString clientKey) return crypto.result().toBase64(); } -QHttpConnection *QHttpResponse::enableWebSocket(bool enable) +QHttpConnection *QHttpResponse::enableWebSocket() { - return m_connection->enableWebSocket(enable); + return m_connection->enableWebSocket(); } void QHttpResponse::end(const QByteArray &data) diff --git a/webaccess/src/qhttpserver/qhttpresponse.h b/webaccess/src/qhttpserver/qhttpresponse.h index 70a26d052d..b436509489 100644 --- a/webaccess/src/qhttpserver/qhttpresponse.h +++ b/webaccess/src/qhttpserver/qhttpresponse.h @@ -142,7 +142,7 @@ public Q_SLOTS: * @param enable boolean enable flag * @return the connection reference */ - QHttpConnection *enableWebSocket(bool enable); + QHttpConnection *enableWebSocket(); /// End/finish the response. /** Data will be flushed to the underlying socket diff --git a/webaccess/src/qhttpserver/qwebsockets_global.h b/webaccess/src/qhttpserver/qwebsockets_global.h new file mode 100644 index 0000000000..ccced9a610 --- /dev/null +++ b/webaccess/src/qhttpserver/qwebsockets_global.h @@ -0,0 +1 @@ +// qwebsockets_global.h defined to keep Qt original sources happy without modification \ No newline at end of file diff --git a/webaccess/src/src.pro b/webaccess/src/src.pro index f2dcd2fcff..26401d88d9 100644 --- a/webaccess/src/src.pro +++ b/webaccess/src/src.pro @@ -14,7 +14,7 @@ qmlui|greaterThan(QT_MAJOR_VERSION, 5) { QT += script } -INCLUDEPATH += qhttpserver +INCLUDEPATH += qhttpserver QtWebSockets INCLUDEPATH += ../../engine/src ../../engine/audio/src INCLUDEPATH += ../../ui/src ../../ui/src/virtualconsole DEPENDPATH += ../../engine/src ../../ui/src @@ -48,7 +48,17 @@ SOURCES = qhttpserver/http_parser.c \ qhttpserver/qhttpconnection.cpp \ qhttpserver/qhttprequest.cpp \ qhttpserver/qhttpresponse.cpp \ - qhttpserver/qhttpserver.cpp + qhttpserver/qhttpserver.cpp \ + +# qwebsocket files +HEADERS += QtWebSockets/qwebsocketdataprocessor_p.h \ + QtWebSockets/qwebsocketframe_p.h \ + QtWebSockets/qwebsocketprotocol.h \ + QtWebSockets/qwebsocketprotocol_p.h + +SOURCES += QtWebSockets/qwebsocketdataprocessor.cpp \ + QtWebSockets/qwebsocketframe.cpp \ + QtWebSockets/qwebsocketprotocol.cpp # QLC+ webaccess files HEADERS += commonjscss.h \ diff --git a/webaccess/src/webaccess.cpp b/webaccess/src/webaccess.cpp index e7ead62d65..23e1144ac5 100644 --- a/webaccess/src/webaccess.cpp +++ b/webaccess/src/webaccess.cpp @@ -128,13 +128,14 @@ void WebAccess::slotHandleRequest(QHttpRequest *req, QHttpResponse *resp) if (reqUrl == "/qlcplusWS") { + // resp->setHeader("Server", APPVERSION); resp->setHeader("Upgrade", "websocket"); resp->setHeader("Connection", "Upgrade"); QByteArray hash = resp->getWebSocketHandshake(req->header("sec-websocket-key")); //QByteArray hash = resp->getWebSocketHandshake("zTvHabaaTOEORzqK+d1yxw=="); qDebug() << "Websocket handshake:" << hash; resp->setHeader("Sec-WebSocket-Accept", hash); - QHttpConnection *conn = resp->enableWebSocket(true); + QHttpConnection *conn = resp->enableWebSocket(); if (conn != NULL) { // Allocate user for WS on heap so it doesn't go out of scope