summaryrefslogtreecommitdiff
path: root/src/websockets/qwebsocketframe.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/websockets/qwebsocketframe.cpp')
-rw-r--r--src/websockets/qwebsocketframe.cpp561
1 files changed, 561 insertions, 0 deletions
diff --git a/src/websockets/qwebsocketframe.cpp b/src/websockets/qwebsocketframe.cpp
new file mode 100644
index 0000000..9765027
--- /dev/null
+++ b/src/websockets/qwebsocketframe.cpp
@@ -0,0 +1,561 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** 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 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 DataProcessor()
+ \internal
+ */
+
+#include "qwebsocketframe_p.h"
+#include "qwebsocketprotocol_p.h"
+
+#include <QtCore/QtEndian>
+#include <QtCore/QDebug>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+ */
+QWebSocketFrame::QWebSocketFrame() :
+ m_closeCode(QWebSocketProtocol::CC_NORMAL),
+ m_closeReason(),
+ m_isFinalFrame(true),
+ m_mask(0),
+ m_rsv1(0),
+ m_rsv2(0),
+ m_rsv3(0),
+ m_opCode(QWebSocketProtocol::OC_RESERVED_C),
+ m_length(0),
+ m_payload(),
+ m_isValid(false)
+{
+}
+
+/*!
+ \internal
+ */
+QWebSocketFrame::QWebSocketFrame(const QWebSocketFrame &other) :
+ m_closeCode(other.m_closeCode),
+ m_closeReason(other.m_closeReason),
+ m_isFinalFrame(other.m_isFinalFrame),
+ m_mask(other.m_mask),
+ m_rsv1(other.m_rsv1),
+ m_rsv2(other.m_rsv2),
+ m_rsv3(other.m_rsv3),
+ m_opCode(other.m_opCode),
+ m_length(other.m_length),
+ m_payload(other.m_payload),
+ m_isValid(other.m_isValid)
+{
+}
+
+/*!
+ \internal
+ */
+QWebSocketFrame &QWebSocketFrame::operator =(const QWebSocketFrame &other)
+{
+ m_closeCode = other.m_closeCode;
+ m_closeReason = other.m_closeReason;
+ m_isFinalFrame = other.m_isFinalFrame;
+ m_mask = other.m_mask;
+ m_rsv1 = other.m_rsv1;
+ m_rsv2 = other.m_rsv2;
+ m_rsv3 = other.m_rsv2;
+ m_opCode = other.m_opCode;
+ m_length = other.m_length;
+ m_payload = other.m_payload;
+ m_isValid = other.m_isValid;
+
+ return *this;
+}
+
+#ifdef Q_COMPILER_RVALUE_REFS
+/*!
+ \internal
+ */
+QWebSocketFrame::QWebSocketFrame(QWebSocketFrame &&other) :
+ m_closeCode(qMove(other.m_closeCode)),
+ m_closeReason(qMove(other.m_closeReason)),
+ m_isFinalFrame(qMove(other.m_isFinalFrame)),
+ m_mask(qMove(other.m_mask)),
+ m_rsv1(qMove(other.m_rsv1)),
+ m_rsv2(qMove(other.m_rsv2)),
+ m_rsv3(qMove(other.m_rsv3)),
+ m_opCode(qMove(other.m_opCode)),
+ m_length(qMove(other.m_length)),
+ m_payload(qMove(other.m_payload)),
+ m_isValid(qMove(other.m_isValid))
+{}
+
+
+/*!
+ \internal
+ */
+QWebSocketFrame &QWebSocketFrame::operator =(QWebSocketFrame &&other)
+{
+ qSwap(m_closeCode, other.m_closeCode);
+ qSwap(m_closeReason, other.m_closeReason);
+ qSwap(m_isFinalFrame, other.m_isFinalFrame);
+ qSwap(m_mask, other.m_mask);
+ qSwap(m_rsv1, other.m_rsv1);
+ qSwap(m_rsv2, other.m_rsv2);
+ qSwap(m_rsv3, other.m_rsv3);
+ qSwap(m_opCode, other.m_opCode);
+ qSwap(m_length, other.m_length);
+ qSwap(m_payload, other.m_payload);
+ qSwap(m_isValid, other.m_isValid);
+
+ return *this;
+}
+
+#endif
+
+/*!
+ \internal
+ */
+void QWebSocketFrame::swap(QWebSocketFrame &other)
+{
+ if (&other != this) {
+ qSwap(m_closeCode, other.m_closeCode);
+ qSwap(m_closeReason, other.m_closeReason);
+ qSwap(m_isFinalFrame, other.m_isFinalFrame);
+ qSwap(m_mask, other.m_mask);
+ qSwap(m_rsv1, other.m_rsv1);
+ qSwap(m_rsv2, other.m_rsv2);
+ qSwap(m_rsv3, other.m_rsv3);
+ qSwap(m_opCode, other.m_opCode);
+ qSwap(m_length, other.m_length);
+ qSwap(m_payload, other.m_payload);
+ qSwap(m_isValid, other.m_isValid);
+ }
+}
+
+/*!
+ \internal
+ */
+QWebSocketProtocol::CloseCode QWebSocketFrame::closeCode() const
+{
+ return m_closeCode;
+}
+
+/*!
+ \internal
+ */
+QString QWebSocketFrame::closeReason() const
+{
+ return m_closeReason;
+}
+
+/*!
+ \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::OC_CONTINUE);
+}
+
+/*!
+ \internal
+ */
+bool QWebSocketFrame::hasMask() const
+{
+ return m_mask != 0;
+}
+
+/*!
+ \internal
+ */
+quint32 QWebSocketFrame::mask() const
+{
+ return m_mask;
+}
+
+/*!
+ \internal
+ */
+int QWebSocketFrame::rsv1() const
+{
+ return m_rsv1;
+}
+
+/*!
+ \internal
+ */
+int QWebSocketFrame::rsv2() const
+{
+ return m_rsv2;
+}
+
+/*!
+ \internal
+ */
+int QWebSocketFrame::rsv3() const
+{
+ return m_rsv3;
+}
+
+/*!
+ \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::CC_NORMAL;
+ m_closeReason.clear();
+ m_isFinalFrame = true;
+ m_mask = 0;
+ m_rsv1 = 0;
+ m_rsv2 = 0;
+ m_rsv3 = 0;
+ m_opCode = QWebSocketProtocol::OC_RESERVED_C;
+ m_length = 0;
+ m_payload.clear();
+ m_isValid = false;
+}
+
+/*!
+ \internal
+ */
+bool QWebSocketFrame::isValid() const
+{
+ return m_isValid;
+}
+
+#define WAIT_FOR_MORE_DATA(dataSizeInBytes) \
+ { returnState = processingState; \
+ processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; }
+
+/*!
+ \internal
+ */
+QWebSocketFrame QWebSocketFrame::readFrame(QIODevice *pIoDevice)
+{
+ bool isDone = false;
+ qint64 bytesRead = 0;
+ QWebSocketFrame frame;
+ quint64 dataWaitSize = 0;
+ Q_UNUSED(dataWaitSize); // value is used in MACRO, Q_UNUSED to avoid compiler warnings
+ ProcessingState processingState = PS_READ_HEADER;
+ ProcessingState returnState = PS_READ_HEADER;
+ bool hasMask = false;
+ quint64 payloadLength = 0;
+
+ while (!isDone)
+ {
+ switch (processingState) {
+ case PS_WAIT_FOR_MORE_DATA:
+ //TODO: waitForReadyRead should really be changed
+ //now, when a websocket is used in a GUI thread
+ //the GUI will hang for at most 5 seconds
+ //maybe, a QStateMachine should be used
+ if (!pIoDevice->waitForReadyRead(5000)) {
+ frame.setError(QWebSocketProtocol::CC_GOING_AWAY,
+ QObject::tr("Timeout when reading data from socket."));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ processingState = returnState;
+ }
+ break;
+
+ case PS_READ_HEADER:
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) {
+ //FIN, RSV1-3, Opcode
+ char header[2] = {0};
+ bytesRead = pIoDevice->read(header, 2);
+ frame.m_isFinalFrame = (header[0] & 0x80) != 0;
+ frame.m_rsv1 = (header[0] & 0x40);
+ frame.m_rsv2 = (header[0] & 0x20);
+ frame.m_rsv3 = (header[0] & 0x10);
+ frame.m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F);
+
+ //Mask, PayloadLength
+ hasMask = (header[1] & 0x80) != 0;
+ frame.m_length = (header[1] & 0x7F);
+
+ switch (frame.m_length)
+ {
+ case 126:
+ {
+ processingState = PS_READ_PAYLOAD_LENGTH;
+ break;
+ }
+ case 127:
+ {
+ processingState = PS_READ_BIG_PAYLOAD_LENGTH;
+ break;
+ }
+ default:
+ {
+ payloadLength = frame.m_length;
+ processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
+ break;
+ }
+ }
+ if (!frame.checkValidity())
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ WAIT_FOR_MORE_DATA(2);
+ }
+ break;
+
+ case PS_READ_PAYLOAD_LENGTH:
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) {
+ uchar length[2] = {0};
+ bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 2);
+ if (Q_UNLIKELY(bytesRead == -1)) {
+ frame.setError(QWebSocketProtocol::CC_GOING_AWAY,
+ QObject::tr("Error occurred while reading from the network: %1")
+ .arg(pIoDevice->errorString()));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ payloadLength = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length));
+ if (Q_UNLIKELY(payloadLength < 126)) {
+ //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"
+ frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR,
+ QObject::tr("Lengths smaller than 126 " \
+ "must be expressed as one byte."));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
+ }
+ }
+ } else {
+ WAIT_FOR_MORE_DATA(2);
+ }
+ break;
+
+ case PS_READ_BIG_PAYLOAD_LENGTH:
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 8)) {
+ uchar length[8] = {0};
+ bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 8);
+ if (Q_UNLIKELY(bytesRead < 8)) {
+ frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION,
+ QObject::tr("Something went wrong during "\
+ "reading from the network."));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ //Most significant bit must be set to 0 as
+ //per http://tools.ietf.org/html/rfc6455#section-5.2
+ payloadLength = qFromBigEndian<quint64>(length);
+ if (Q_UNLIKELY(payloadLength & (quint64(1) << 63))) {
+ frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR,
+ QObject::tr("Highest bit of payload length is not 0."));
+ processingState = PS_DISPATCH_RESULT;
+ } else if (Q_UNLIKELY(payloadLength <= 0xFFFFu)) {
+ //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"
+ frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR,
+ QObject::tr("Lengths smaller than 65536 (2^16) " \
+ "must be expressed as 2 bytes."));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
+ }
+ }
+ } else {
+ WAIT_FOR_MORE_DATA(8);
+ }
+
+ break;
+
+ case PS_READ_MASK:
+ if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) {
+ bytesRead = pIoDevice->read(reinterpret_cast<char *>(&frame.m_mask),
+ sizeof(frame.m_mask));
+ if (bytesRead == -1) {
+ frame.setError(QWebSocketProtocol::CC_GOING_AWAY,
+ QObject::tr("Error while reading from the network: %1.")
+ .arg(pIoDevice->errorString()));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ frame.m_mask = qFromBigEndian(frame.m_mask);
+ processingState = PS_READ_PAYLOAD;
+ }
+ } else {
+ WAIT_FOR_MORE_DATA(4);
+ }
+ break;
+
+ case PS_READ_PAYLOAD:
+ if (!payloadLength) {
+ processingState = PS_DISPATCH_RESULT;
+ } else if (Q_UNLIKELY(payloadLength > MAX_FRAME_SIZE_IN_BYTES)) {
+ frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA,
+ QObject::tr("Maximum framesize exceeded."));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ quint64 bytesAvailable = quint64(pIoDevice->bytesAvailable());
+ if (bytesAvailable >= payloadLength) {
+ frame.m_payload = pIoDevice->read(payloadLength);
+ //payloadLength can be safely cast to an integer,
+ //because MAX_FRAME_SIZE_IN_BYTES = MAX_INT
+ if (Q_UNLIKELY(frame.m_payload.length() != int(payloadLength))) {
+ //some error occurred; refer to the Qt documentation of QIODevice::read()
+ frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION,
+ QObject::tr("Some serious error occurred " \
+ "while reading from the network."));
+ processingState = PS_DISPATCH_RESULT;
+ } else {
+ if (hasMask)
+ QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask);
+ processingState = PS_DISPATCH_RESULT;
+ }
+ } else {
+ //if payload is too big, then this will timeout
+ WAIT_FOR_MORE_DATA(payloadLength);
+ }
+ }
+ break;
+
+ case PS_DISPATCH_RESULT:
+ processingState = PS_READ_HEADER;
+ isDone = true;
+ break;
+
+ default:
+ //should not come here
+ qWarning() << "DataProcessor::process: Found invalid state. This should not happen!";
+ frame.clear();
+ isDone = true;
+ break;
+ } //end switch
+ }
+
+ return frame;
+}
+
+/*!
+ \internal
+ */
+void QWebSocketFrame::setError(QWebSocketProtocol::CloseCode code, QString closeReason)
+{
+ clear();
+ m_closeCode = code;
+ m_closeReason = closeReason;
+ m_isValid = false;
+}
+
+/*!
+ \internal
+ */
+bool QWebSocketFrame::checkValidity()
+{
+ if (isValid())
+ return true;
+ if (Q_UNLIKELY(m_rsv1 || m_rsv2 || m_rsv3)) {
+ setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Rsv field is non-zero"));
+ } else if (Q_UNLIKELY(QWebSocketProtocol::isOpCodeReserved(m_opCode))) {
+ setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Used reserved opcode"));
+ } else if (isControlFrame()) {
+ if (Q_UNLIKELY(m_length > 125)) {
+ setError(QWebSocketProtocol::CC_PROTOCOL_ERROR,
+ QObject::tr("Controle frame is larger than 125 bytes"));
+ } else if (Q_UNLIKELY(!m_isFinalFrame)) {
+ setError(QWebSocketProtocol::CC_PROTOCOL_ERROR,
+ QObject::tr("Controle frames cannot be fragmented"));
+ } else {
+ m_isValid = true;
+ }
+ } else {
+ m_isValid = true;
+ }
+ return m_isValid;
+}
+
+QT_END_NAMESPACE