1 /****************************************************************************
3 ** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>.
4 ** Contact: http://www.qt-project.org/legal
6 ** This file is part of the QtWebSockets module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
40 ****************************************************************************/
42 \class QWebSocketDataProcessor
43 The class QWebSocketDataProcessor is responsible for reading, validating and
44 interpreting data from a WebSocket.
45 It reads data from a QIODevice, validates it against RFC 6455, and parses it into
46 frames (data, control).
47 It emits signals that correspond to the type of the frame: textFrameReceived(),
48 binaryFrameReceived(), textMessageReceived(), binaryMessageReceived(), pingReceived(),
49 pongReceived() and closeReceived().
50 Whenever an error is detected, the errorEncountered() signal is emitted.
51 QWebSocketDataProcessor also checks if a frame is allowed in a sequence of frames
52 (e.g. a continuation frame cannot follow a final frame).
53 This class is an internal class used by QWebSocketInternal for data processing and validation.
59 #include "qwebsocketdataprocessor_p.h"
60 #include "qwebsocketprotocol.h"
61 #include "qwebsocketprotocol_p.h"
62 #include "qwebsocketframe_p.h"
64 #include <QtCore/QtEndian>
65 #include <QtCore/QTextCodec>
66 #include <QtCore/QTextDecoder>
67 #include <QtCore/QDebug>
76 QWebSocketDataProcessor::QWebSocketDataProcessor(QObject *parent) :
78 m_processingState(PS_READ_HEADER),
79 m_isFinalFrame(false),
80 m_isFragmented(false),
81 m_opCode(QWebSocketProtocol::OpCodeClose),
82 m_isControlFrame(false),
88 m_pConverterState(Q_NULLPTR),
89 m_pTextCodec(QTextCodec::codecForName("UTF-8"))
97 QWebSocketDataProcessor::~QWebSocketDataProcessor()
100 if (m_pConverterState) {
101 delete m_pConverterState;
102 m_pConverterState = Q_NULLPTR;
109 quint64 QWebSocketDataProcessor::maxMessageSize()
111 return MAX_MESSAGE_SIZE_IN_BYTES; //COV_NF_LINE
117 quint64 QWebSocketDataProcessor::maxFrameSize()
119 return MAX_FRAME_SIZE_IN_BYTES;
125 void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
130 QWebSocketFrame frame = QWebSocketFrame::readFrame(pIoDevice);
131 if (Q_LIKELY(frame.isValid())) {
132 if (frame.isControlFrame()) {
133 isDone = processControlFrame(frame);
135 //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY
136 if (Q_UNLIKELY(!m_isFragmented && frame.isContinuationFrame())) {
138 Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
139 tr("Received Continuation frame, while there is " \
140 "nothing to continue."));
143 if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() &&
144 !frame.isContinuationFrame())) {
146 Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
147 tr("All data frames after the initial data frame " \
148 "must have opcode 0 (continuation)."));
151 if (!frame.isContinuationFrame()) {
152 m_opCode = frame.opCode();
153 m_isFragmented = !frame.isFinalFrame();
155 quint64 messageLength = (quint64)(m_opCode == QWebSocketProtocol::OpCodeText)
156 ? m_textMessage.length()
157 : m_binaryMessage.length();
158 if (Q_UNLIKELY((messageLength + quint64(frame.payload().length())) >
159 MAX_MESSAGE_SIZE_IN_BYTES)) {
161 Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData,
162 tr("Received message is too big."));
166 if (m_opCode == QWebSocketProtocol::OpCodeText) {
167 QString frameTxt = m_pTextCodec->toUnicode(frame.payload().constData(),
168 frame.payload().size(),
170 bool failed = (m_pConverterState->invalidChars != 0)
171 || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0));
172 if (Q_UNLIKELY(failed)) {
174 Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeWrongDatatype,
175 tr("Invalid UTF-8 code encountered."));
178 m_textMessage.append(frameTxt);
179 Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame());
182 m_binaryMessage.append(frame.payload());
183 Q_EMIT binaryFrameReceived(frame.payload(), frame.isFinalFrame());
186 if (frame.isFinalFrame()) {
187 if (m_opCode == QWebSocketProtocol::OpCodeText)
188 Q_EMIT textMessageReceived(m_textMessage);
190 Q_EMIT binaryMessageReceived(m_binaryMessage);
196 Q_EMIT errorEncountered(frame.closeCode(), frame.closeReason());
206 void QWebSocketDataProcessor::clear()
208 m_processingState = PS_READ_HEADER;
209 m_isFinalFrame = false;
210 m_isFragmented = false;
211 m_opCode = QWebSocketProtocol::OpCodeClose;
214 m_binaryMessage.clear();
215 m_textMessage.clear();
217 if (m_pConverterState) {
218 if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) {
219 delete m_pConverterState;
220 m_pConverterState = Q_NULLPTR;
223 if (!m_pConverterState)
224 m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull |
225 QTextCodec::IgnoreHeader);
231 bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame)
233 bool mustStopProcessing = true; //control frames never expect additional frames to be processed
234 switch (frame.opCode()) {
235 case QWebSocketProtocol::OpCodePing:
236 Q_EMIT pingReceived(frame.payload());
239 case QWebSocketProtocol::OpCodePong:
240 Q_EMIT pongReceived(frame.payload());
243 case QWebSocketProtocol::OpCodeClose:
245 quint16 closeCode = QWebSocketProtocol::CloseCodeNormal;
247 QByteArray payload = frame.payload();
248 if (Q_UNLIKELY(payload.size() == 1)) {
249 //size is either 0 (no close code and no reason)
250 //or >= 2 (at least a close code of 2 bytes)
251 closeCode = QWebSocketProtocol::CloseCodeProtocolError;
252 closeReason = tr("Payload of close frame is too small.");
253 } else if (Q_LIKELY(payload.size() > 1)) {
254 //close frame can have a close code and reason
255 closeCode = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(payload.constData()));
256 if (Q_UNLIKELY(!QWebSocketProtocol::isCloseCodeValid(closeCode))) {
257 closeCode = QWebSocketProtocol::CloseCodeProtocolError;
258 closeReason = tr("Invalid close code %1 detected.").arg(closeCode);
260 if (payload.size() > 2) {
261 QTextCodec *tc = QTextCodec::codecForName(QByteArrayLiteral("UTF-8"));
262 QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull);
263 closeReason = tc->toUnicode(payload.constData() + 2, payload.size() - 2, &state);
264 const bool failed = (state.invalidChars != 0) || (state.remainingChars != 0);
265 if (Q_UNLIKELY(failed)) {
266 closeCode = QWebSocketProtocol::CloseCodeWrongDatatype;
267 closeReason = tr("Invalid UTF-8 code encountered.");
272 Q_EMIT closeReceived(static_cast<QWebSocketProtocol::CloseCode>(closeCode), closeReason);
276 case QWebSocketProtocol::OpCodeContinue:
277 case QWebSocketProtocol::OpCodeBinary:
278 case QWebSocketProtocol::OpCodeText:
279 case QWebSocketProtocol::OpCodeReserved3:
280 case QWebSocketProtocol::OpCodeReserved4:
281 case QWebSocketProtocol::OpCodeReserved5:
282 case QWebSocketProtocol::OpCodeReserved6:
283 case QWebSocketProtocol::OpCodeReserved7:
284 case QWebSocketProtocol::OpCodeReservedC:
285 case QWebSocketProtocol::OpCodeReservedB:
286 case QWebSocketProtocol::OpCodeReservedD:
287 case QWebSocketProtocol::OpCodeReservedE:
288 case QWebSocketProtocol::OpCodeReservedF:
290 //case statements added to make C++ compiler happy
294 Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
295 tr("Invalid opcode detected: %1").arg(int(frame.opCode())));
299 return mustStopProcessing;