83a1a268373b23054c3b79e409c097ab423b8937
[contrib/qtwebsockets.git] / src / websockets / qwebsocketdataprocessor.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>.
4 ** Contact: http://www.qt-project.org/legal
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 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.
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 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.
24 **
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.
28 **
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.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 /*!
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.
54
55     \sa Frame()
56
57     \internal
58 */
59 #include "qwebsocketdataprocessor_p.h"
60 #include "qwebsocketprotocol.h"
61 #include "qwebsocketprotocol_p.h"
62 #include "qwebsocketframe_p.h"
63
64 #include <QtCore/QtEndian>
65 #include <QtCore/QTextCodec>
66 #include <QtCore/QTextDecoder>
67 #include <QtCore/QDebug>
68
69 #include <limits.h>
70
71 QT_BEGIN_NAMESPACE
72
73 /*!
74     \internal
75  */
76 QWebSocketDataProcessor::QWebSocketDataProcessor(QObject *parent) :
77     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),
83     m_hasMask(false),
84     m_mask(0),
85     m_binaryMessage(),
86     m_textMessage(),
87     m_payloadLength(0),
88     m_pConverterState(Q_NULLPTR),
89     m_pTextCodec(QTextCodec::codecForName("UTF-8"))
90 {
91     clear();
92 }
93
94 /*!
95     \internal
96  */
97 QWebSocketDataProcessor::~QWebSocketDataProcessor()
98 {
99     clear();
100     if (m_pConverterState) {
101         delete m_pConverterState;
102         m_pConverterState = Q_NULLPTR;
103     }
104 }
105
106 /*!
107     \internal
108  */
109 quint64 QWebSocketDataProcessor::maxMessageSize()
110 {
111     return MAX_MESSAGE_SIZE_IN_BYTES;   //COV_NF_LINE
112 }
113
114 /*!
115     \internal
116  */
117 quint64 QWebSocketDataProcessor::maxFrameSize()
118 {
119     return MAX_FRAME_SIZE_IN_BYTES;
120 }
121
122 /*!
123     \internal
124  */
125 void QWebSocketDataProcessor::process(QIODevice *pIoDevice)
126 {
127     bool isDone = false;
128
129     while (!isDone) {
130         QWebSocketFrame frame = QWebSocketFrame::readFrame(pIoDevice);
131         if (Q_LIKELY(frame.isValid())) {
132             if (frame.isControlFrame()) {
133                 isDone = processControlFrame(frame);
134             } else {
135                 //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY
136                 if (Q_UNLIKELY(!m_isFragmented && frame.isContinuationFrame())) {
137                     clear();
138                     Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
139                                             tr("Received Continuation frame, while there is " \
140                                                "nothing to continue."));
141                     return;
142                 }
143                 if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() &&
144                                !frame.isContinuationFrame())) {
145                     clear();
146                     Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
147                                             tr("All data frames after the initial data frame " \
148                                                "must have opcode 0 (continuation)."));
149                     return;
150                 }
151                 if (!frame.isContinuationFrame()) {
152                     m_opCode = frame.opCode();
153                     m_isFragmented = !frame.isFinalFrame();
154                 }
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)) {
160                     clear();
161                     Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData,
162                                             tr("Received message is too big."));
163                     return;
164                 }
165
166                 if (m_opCode == QWebSocketProtocol::OpCodeText) {
167                     QString frameTxt = m_pTextCodec->toUnicode(frame.payload().constData(),
168                                                                frame.payload().size(),
169                                                                m_pConverterState);
170                     bool failed = (m_pConverterState->invalidChars != 0)
171                             || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0));
172                     if (Q_UNLIKELY(failed)) {
173                         clear();
174                         Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeWrongDatatype,
175                                                 tr("Invalid UTF-8 code encountered."));
176                         return;
177                     } else {
178                         m_textMessage.append(frameTxt);
179                         Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame());
180                     }
181                 } else {
182                     m_binaryMessage.append(frame.payload());
183                     Q_EMIT binaryFrameReceived(frame.payload(), frame.isFinalFrame());
184                 }
185
186                 if (frame.isFinalFrame()) {
187                     if (m_opCode == QWebSocketProtocol::OpCodeText)
188                         Q_EMIT textMessageReceived(m_textMessage);
189                     else
190                         Q_EMIT binaryMessageReceived(m_binaryMessage);
191                     clear();
192                     isDone = true;
193                 }
194             }
195         } else {
196             Q_EMIT errorEncountered(frame.closeCode(), frame.closeReason());
197             clear();
198             isDone = true;
199         }
200     }
201 }
202
203 /*!
204     \internal
205  */
206 void QWebSocketDataProcessor::clear()
207 {
208     m_processingState = PS_READ_HEADER;
209     m_isFinalFrame = false;
210     m_isFragmented = false;
211     m_opCode = QWebSocketProtocol::OpCodeClose;
212     m_hasMask = false;
213     m_mask = 0;
214     m_binaryMessage.clear();
215     m_textMessage.clear();
216     m_payloadLength = 0;
217     if (m_pConverterState) {
218         if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) {
219             delete m_pConverterState;
220             m_pConverterState = Q_NULLPTR;
221         }
222     }
223     if (!m_pConverterState)
224         m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull |
225                                                            QTextCodec::IgnoreHeader);
226 }
227
228 /*!
229     \internal
230  */
231 bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame)
232 {
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());
237         break;
238
239     case QWebSocketProtocol::OpCodePong:
240         Q_EMIT pongReceived(frame.payload());
241         break;
242
243     case QWebSocketProtocol::OpCodeClose:
244     {
245         quint16 closeCode = QWebSocketProtocol::CloseCodeNormal;
246         QString closeReason;
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);
259             } else {
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.");
268                     }
269                 }
270             }
271         }
272         Q_EMIT closeReceived(static_cast<QWebSocketProtocol::CloseCode>(closeCode), closeReason);
273         break;
274     }
275
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:
289         //do nothing
290         //case statements added to make C++ compiler happy
291         break;
292
293     default:
294         Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError,
295                                 tr("Invalid opcode detected: %1").arg(int(frame.opCode())));
296         //do nothing
297         break;
298     }
299     return mustStopProcessing;
300 }
301
302 QT_END_NAMESPACE