blob: 716aebd3afee95f29ba8aea074dd6eac26f26d2e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
** 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 <QtCore/QtEndian>
#include <QtCore/QDebug>
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<QWebSocketProtocol::OpCode>(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<char *>(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<quint16>(reinterpret_cast<const uchar *>(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<char *>(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<quint64>(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<char *>(&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