1/****************************************************************************
2**
3** Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWebSockets module of the Qt Toolkit.
7**
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 The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
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 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39/*!
40 \class QWebSocketDataProcessor
41 The class QWebSocketDataProcessor is responsible for reading, validating and
42 interpreting data from a WebSocket.
43 It reads data from a QIODevice, validates it against \l{RFC 6455}, and parses it into
44 frames (data, control).
45 It emits signals that correspond to the type of the frame: textFrameReceived(),
46 binaryFrameReceived(), textMessageReceived(), binaryMessageReceived(), pingReceived(),
47 pongReceived() and closeReceived().
48 Whenever an error is detected, the errorEncountered() signal is emitted.
49 QWebSocketDataProcessor also checks if a frame is allowed in a sequence of frames
50 (e.g. a continuation frame cannot follow a final frame).
51 This class is an internal class used by QWebSocketInternal for data processing and validation.
52
53 \sa Frame()
54
55 \internal
56*/
57#include "qwebsocketdataprocessor_p.h"
58#include "qwebsocketprotocol.h"
59#include "qwebsocketprotocol_p.h"
60#include "qwebsocketframe_p.h"
61
62#include <QtCore/QtEndian>
63#include <QtCore/QTextCodec>
64#include <QtCore/QTextDecoder>
65#include <QtCore/QDebug>
66
67#include <limits.h>
68
69QT_BEGIN_NAMESPACE
70
71/*!
72 \internal
73 */
74QWebSocketDataProcessor::QWebSocketDataProcessor(QObject *parent) :
75 QObject(parent),
76 m_processingState(PS_READ_HEADER),
77 m_isFinalFrame(false),
78 m_isFragmented(false),
79 m_opCode(QWebSocketProtocol::OpCodeClose),
80 m_isControlFrame(false),
81 m_hasMask(false),
82 m_mask(0),
83 m_binaryMessage(),
84 m_textMessage(),
85 m_payloadLength(0),
86 m_pConverterState(nullptr),
87 m_pTextCodec(QTextCodec::codecForName(name: "UTF-8")),
88 m_waitTimer(new QTimer(this))
89{
90 clear();
91 // initialize the internal timeout timer
92 m_waitTimer->setInterval(5000);
93 m_waitTimer->setSingleShot(true);
94 m_waitTimer->callOnTimeout(args: this, args: &QWebSocketDataProcessor::timeout);
95}
96
97/*!
98 \internal
99 */
100QWebSocketDataProcessor::~QWebSocketDataProcessor()
101{
102 clear();
103 if (m_pConverterState) {
104 delete m_pConverterState;
105 m_pConverterState = nullptr;
106 }
107}
108
109void QWebSocketDataProcessor::setMaxAllowedFrameSize(quint64 maxAllowedFrameSize)
110{
111 frame.setMaxAllowedFrameSize(maxAllowedFrameSize);
112}
113
114quint64 QWebSocketDataProcessor::maxAllowedFrameSize() const
115{
116 return frame.maxAllowedFrameSize();
117}
118
119/*!
120 \internal
121 */
122void QWebSocketDataProcessor::setMaxAllowedMessageSize(quint64 maxAllowedMessageSize)
123{
124 if (maxAllowedMessageSize <= maxMessageSize())
125 m_maxAllowedMessageSize = maxAllowedMessageSize;
126}
127
128/*!
129 \internal
130 */
131quint64 QWebSocketDataProcessor::maxAllowedMessageSize() const
132{
133 return m_maxAllowedMessageSize;
134}
135
136/*!
137 \internal
138 */
139quint64 QWebSocketDataProcessor::maxMessageSize()
140{
141 return MAX_MESSAGE_SIZE_IN_BYTES; //COV_NF_LINE
142}
143
144/*!
145 \internal
146 */
147quint64 QWebSocketDataProcessor::maxFrameSize()
148{
149 return QWebSocketFrame::maxFrameSize();
150}
151
152/*!
153 \internal
154
155 Returns \c true if a complete websocket frame has been processed;
156 otherwise returns \c false.
157 */
158bool QWebSocketDataProcessor::process(QIODevice *pIoDevice)
159{
160 bool isDone = false;
161
162 while (!isDone) {
163 frame.readFrame(pIoDevice);
164 if (!frame.isDone()) {
165 // waiting for more data available
166 QObject::connect(sender: pIoDevice, signal: &QIODevice::readyRead,
167 receiver: m_waitTimer, slot: &QTimer::stop, type: Qt::UniqueConnection);
168 m_waitTimer->start();
169 return false;
170 } else if (Q_LIKELY(frame.isValid())) {
171 if (frame.isControlFrame()) {
172 isDone = processControlFrame(frame);
173 } else {
174 //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY
175 if (Q_UNLIKELY(!m_isFragmented && frame.isContinuationFrame())) {
176 clear();
177 Q_EMIT errorEncountered(code: QWebSocketProtocol::CloseCodeProtocolError,
178 description: tr(s: "Received Continuation frame, while there is " \
179 "nothing to continue."));
180 return true;
181 }
182 if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() &&
183 !frame.isContinuationFrame())) {
184 clear();
185 Q_EMIT errorEncountered(code: QWebSocketProtocol::CloseCodeProtocolError,
186 description: tr(s: "All data frames after the initial data frame " \
187 "must have opcode 0 (continuation)."));
188 return true;
189 }
190 if (!frame.isContinuationFrame()) {
191 m_opCode = frame.opCode();
192 m_isFragmented = !frame.isFinalFrame();
193 }
194 quint64 messageLength = m_opCode == QWebSocketProtocol::OpCodeText
195 ? quint64(m_textMessage.length())
196 : quint64(m_binaryMessage.length());
197 if (Q_UNLIKELY((messageLength + quint64(frame.payload().length())) >
198 maxAllowedMessageSize())) {
199 clear();
200 Q_EMIT errorEncountered(code: QWebSocketProtocol::CloseCodeTooMuchData,
201 description: tr(s: "Received message is too big."));
202 return true;
203 }
204
205 bool isFinalFrame = frame.isFinalFrame();
206 if (m_opCode == QWebSocketProtocol::OpCodeText) {
207 QString frameTxt = m_pTextCodec->toUnicode(in: frame.payload().constData(),
208 length: frame.payload().size(),
209 state: m_pConverterState);
210 bool failed = (m_pConverterState->invalidChars != 0)
211 || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0));
212 if (Q_UNLIKELY(failed)) {
213 clear();
214 Q_EMIT errorEncountered(code: QWebSocketProtocol::CloseCodeWrongDatatype,
215 description: tr(s: "Invalid UTF-8 code encountered."));
216 return true;
217 } else {
218 m_textMessage.append(s: frameTxt);
219 frame.clear();
220 Q_EMIT textFrameReceived(frame: frameTxt, lastFrame: isFinalFrame);
221 }
222 } else {
223 m_binaryMessage.append(a: frame.payload());
224 QByteArray payload = frame.payload();
225 frame.clear();
226 Q_EMIT binaryFrameReceived(frame: payload, lastFrame: isFinalFrame);
227 }
228
229 if (isFinalFrame) {
230 isDone = true;
231 if (m_opCode == QWebSocketProtocol::OpCodeText) {
232 const QString textMessage(m_textMessage);
233 clear();
234 Q_EMIT textMessageReceived(message: textMessage);
235 } else {
236 const QByteArray binaryMessage(m_binaryMessage);
237 clear();
238 Q_EMIT binaryMessageReceived(message: binaryMessage);
239 }
240 }
241 }
242 } else {
243 Q_EMIT errorEncountered(code: frame.closeCode(), description: frame.closeReason());
244 clear();
245 isDone = true;
246 }
247 frame.clear();
248 }
249 return true;
250}
251
252/*!
253 \internal
254 */
255void QWebSocketDataProcessor::clear()
256{
257 m_processingState = PS_READ_HEADER;
258 m_isFinalFrame = false;
259 m_isFragmented = false;
260 m_opCode = QWebSocketProtocol::OpCodeClose;
261 m_hasMask = false;
262 m_mask = 0;
263 m_binaryMessage.clear();
264 m_textMessage.clear();
265 m_payloadLength = 0;
266 frame.clear();
267 if (m_pConverterState) {
268 if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) {
269 delete m_pConverterState;
270 m_pConverterState = nullptr;
271 }
272 }
273 if (!m_pConverterState)
274 m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull |
275 QTextCodec::IgnoreHeader);
276}
277
278/*!
279 \internal
280 */
281bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame)
282{
283 bool mustStopProcessing = true; //control frames never expect additional frames to be processed
284 switch (frame.opCode()) {
285 case QWebSocketProtocol::OpCodePing:
286 Q_EMIT pingReceived(data: frame.payload());
287 break;
288
289 case QWebSocketProtocol::OpCodePong:
290 Q_EMIT pongReceived(data: frame.payload());
291 break;
292
293 case QWebSocketProtocol::OpCodeClose:
294 {
295 quint16 closeCode = QWebSocketProtocol::CloseCodeNormal;
296 QString closeReason;
297 QByteArray payload = frame.payload();
298 if (Q_UNLIKELY(payload.size() == 1)) {
299 //size is either 0 (no close code and no reason)
300 //or >= 2 (at least a close code of 2 bytes)
301 closeCode = QWebSocketProtocol::CloseCodeProtocolError;
302 closeReason = tr(s: "Payload of close frame is too small.");
303 } else if (Q_LIKELY(payload.size() > 1)) {
304 //close frame can have a close code and reason
305 closeCode = qFromBigEndian<quint16>(src: reinterpret_cast<const uchar *>(payload.constData()));
306 if (Q_UNLIKELY(!QWebSocketProtocol::isCloseCodeValid(closeCode))) {
307 closeCode = QWebSocketProtocol::CloseCodeProtocolError;
308 closeReason = tr(s: "Invalid close code %1 detected.").arg(a: closeCode);
309 } else {
310 if (payload.size() > 2) {
311 QTextCodec *tc = QTextCodec::codecForName(QByteArrayLiteral("UTF-8"));
312 QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull);
313 closeReason = tc->toUnicode(in: payload.constData() + 2, length: payload.size() - 2, state: &state);
314 const bool failed = (state.invalidChars != 0) || (state.remainingChars != 0);
315 if (Q_UNLIKELY(failed)) {
316 closeCode = QWebSocketProtocol::CloseCodeWrongDatatype;
317 closeReason = tr(s: "Invalid UTF-8 code encountered.");
318 }
319 }
320 }
321 }
322 Q_EMIT closeReceived(closeCode: static_cast<QWebSocketProtocol::CloseCode>(closeCode), closeReason);
323 break;
324 }
325
326 case QWebSocketProtocol::OpCodeContinue:
327 case QWebSocketProtocol::OpCodeBinary:
328 case QWebSocketProtocol::OpCodeText:
329 case QWebSocketProtocol::OpCodeReserved3:
330 case QWebSocketProtocol::OpCodeReserved4:
331 case QWebSocketProtocol::OpCodeReserved5:
332 case QWebSocketProtocol::OpCodeReserved6:
333 case QWebSocketProtocol::OpCodeReserved7:
334 case QWebSocketProtocol::OpCodeReservedC:
335 case QWebSocketProtocol::OpCodeReservedB:
336 case QWebSocketProtocol::OpCodeReservedD:
337 case QWebSocketProtocol::OpCodeReservedE:
338 case QWebSocketProtocol::OpCodeReservedF:
339 //do nothing
340 //case statements added to make C++ compiler happy
341 break;
342
343 default:
344 Q_EMIT errorEncountered(code: QWebSocketProtocol::CloseCodeProtocolError,
345 description: tr(s: "Invalid opcode detected: %1").arg(a: int(frame.opCode())));
346 //do nothing
347 break;
348 }
349 return mustStopProcessing;
350}
351
352/*!
353 \internal
354 */
355void QWebSocketDataProcessor::timeout()
356{
357 clear();
358 Q_EMIT errorEncountered(code: QWebSocketProtocol::CloseCodeGoingAway,
359 description: tr(s: "Timeout when reading data from socket."));
360}
361
362QT_END_NAMESPACE
363

source code of qtwebsockets/src/websockets/qwebsocketdataprocessor.cpp