1 /*
2 * Copyright (C) 2017-2018 Daniel Nicoletti <dantti12@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18 #include "protocolwebsocket.h"
19
20 #include "socket.h"
21 #include "server.h"
22 #include "protocolhttp.h"
23
24 #include <Cutelyst/Headers>
25 #include <Cutelyst/Context>
26 #include <Cutelyst/Response>
27
28 #include <QLoggingCategory>
29
30 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
31 #include <QTextCodec>
32 #else
33 #include <QStringConverter>
34 #endif
35
36 Q_LOGGING_CATEGORY(CWSGI_WS, "cwsgi.websocket", QtWarningMsg)
37
38 using namespace Cutelyst;
39
ProtocolWebSocket(Server * wsgi)40 ProtocolWebSocket::ProtocolWebSocket(Server *wsgi) : Protocol(wsgi)
41 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
42 , m_codec(QTextCodec::codecForName(QByteArrayLiteral("UTF-8")))
43 #endif
44 , m_websockets_max_size(wsgi->websocketMaxSize() * 1024)
45 {
46 }
47
~ProtocolWebSocket()48 ProtocolWebSocket::~ProtocolWebSocket()
49 {
50 }
51
createWebsocketHeader(quint8 opcode,quint64 len)52 QByteArray ProtocolWebSocket::createWebsocketHeader(quint8 opcode, quint64 len)
53 {
54 QByteArray ret;
55 ret.append(char(0x80 + opcode));
56
57 if (len < 126) {
58 ret.append(static_cast<char>(len));
59 } else if (len <= static_cast<quint16>(0xffff)) {
60 ret.append(char(126));
61
62 quint8 buf[2];
63 buf[1] = quint8(len & 0xff);
64 buf[0] = quint8((len >> 8) & 0xff);
65 ret.append(reinterpret_cast<char*>(buf), 2);
66 } else {
67 ret.append(127);
68
69 quint8 buf[8];
70 buf[7] = quint8(len & 0xff);
71 buf[6] = quint8((len >> 8) & 0xff);
72 buf[5] = quint8((len >> 16) & 0xff);
73 buf[4] = quint8((len >> 24) & 0xff);
74 buf[3] = quint8((len >> 32) & 0xff);
75 buf[2] = quint8((len >> 40) & 0xff);
76 buf[1] = quint8((len >> 48) & 0xff);
77 buf[0] = quint8((len >> 56) & 0xff);
78 ret.append(reinterpret_cast<char*>(buf), 8);
79 }
80
81 return ret;
82 }
83
createWebsocketCloseReply(const QString & msg,quint16 closeCode)84 QByteArray ProtocolWebSocket::createWebsocketCloseReply(const QString &msg, quint16 closeCode)
85 {
86 QByteArray payload;
87
88 const QByteArray data = msg.toUtf8().left(123);
89
90 payload = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodeClose, quint64(data.size() + 2));
91
92 quint8 buf[2];
93 buf[1] = quint8(closeCode & 0xff);
94 buf[0] = quint8((closeCode >> 8) & 0xff);
95 payload.append(reinterpret_cast<char*>(buf), 2);
96
97 // 125 is max payload - 2 of the above bytes
98 payload.append(data);
99
100 return payload;
101 }
102
parse(Socket * sock,QIODevice * io) const103 void ProtocolWebSocket::parse(Socket *sock, QIODevice *io) const
104 {
105 qint64 bytesAvailable = io->bytesAvailable();
106 auto request = static_cast<ProtoRequestHttp *>(sock->protoData);
107
108 Q_FOREVER {
109 if (!bytesAvailable ||
110 !request->websocket_need ||
111 (bytesAvailable < request->websocket_need && request->websocket_phase != ProtoRequestHttp::WebSocketPhasePayload)) {
112 // Need more data
113 return;
114 }
115
116 quint32 maxlen = qMin(request->websocket_need, static_cast<quint32>(m_postBufferSize));
117 qint64 len = io->read(m_postBuffer, maxlen);
118 if (len == -1) {
119 qCWarning(CWSGI_WS) << "Failed to read from socket" << io->errorString();
120 sock->connectionClose();
121 return;
122 }
123 bytesAvailable -= len;
124
125 switch(request->websocket_phase) {
126 case ProtoRequestHttp::WebSocketPhaseHeaders:
127 if (!websocket_parse_header(sock, m_postBuffer, io)) {
128 return;
129 }
130 break;
131 case ProtoRequestHttp::WebSocketPhaseSize:
132 if (!websocket_parse_size(sock, m_postBuffer, m_websockets_max_size)) {
133 return;
134 }
135 break;
136 case ProtoRequestHttp::WebSocketPhaseMask:
137 websocket_parse_mask(sock, m_postBuffer, io);
138 break;
139 case ProtoRequestHttp::WebSocketPhasePayload:
140 if (!websocket_parse_payload(sock, m_postBuffer, int(len), io)) {
141 return;
142 }
143 break;
144 }
145 }
146 }
147
createData(Socket * sock) const148 ProtocolData *ProtocolWebSocket::createData(Socket *sock) const
149 {
150 Q_UNUSED(sock)
151 return nullptr;
152 }
153
send_text(Cutelyst::Context * c,Socket * sock,bool singleFrame) const154 bool ProtocolWebSocket::send_text(Cutelyst::Context *c, Socket *sock, bool singleFrame) const
155 {
156 Cutelyst::Request *request = c->request();
157 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
158
159 const int msg_size = protoRequest->websocket_message.size();
160 protoRequest->websocket_message.append(protoRequest->websocket_payload);
161
162 QByteArray payload = protoRequest->websocket_payload;
163 if (protoRequest->websocket_start_of_frame != msg_size) {
164 payload = protoRequest->websocket_message.mid(protoRequest->websocket_start_of_frame);
165 }
166
167 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
168 QTextCodec::ConverterState state;
169 const QString frame = m_codec->toUnicode(payload.data(), payload.size(), &state);
170 const bool failed = state.invalidChars || state.remainingChars;
171 #else
172 auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
173 const QString frame = toUtf16(payload);
174 const bool failed = false;//FIXME
175 #endif
176 if (singleFrame && (failed || (frame.isEmpty() && payload.size()))) {
177 sock->connectionClose();
178 return false;
179 } else if (!failed) {
180 protoRequest->websocket_start_of_frame = protoRequest->websocket_message.size();
181 Q_EMIT request->webSocketTextFrame(frame,
182 protoRequest->websocket_finn_opcode & 0x80,
183 protoRequest->context);
184 }
185
186 if (protoRequest->websocket_finn_opcode & 0x80) {
187 protoRequest->websocket_continue_opcode = 0;
188 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
189 Q_EMIT request->webSocketTextMessage(frame, protoRequest->context);
190 } else {
191 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
192 QTextCodec::ConverterState stateMsg;
193 const QString msg = m_codec->toUnicode(protoRequest->websocket_message.data(), protoRequest->websocket_message.size(), &stateMsg);
194 const bool failed = stateMsg.invalidChars || stateMsg.remainingChars;
195 #else
196 auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
197 const QString msg = toUtf16(protoRequest->websocket_message);
198 const bool failed = false;//FIXME
199 #endif
200 if (failed) {
201 sock->connectionClose();
202 return false;
203 }
204 Q_EMIT request->webSocketTextMessage(msg, protoRequest->context);
205 }
206 protoRequest->websocket_message = QByteArray();
207 protoRequest->websocket_payload = QByteArray();
208 }
209
210 return true;
211 }
212
send_binary(Cutelyst::Context * c,Socket * sock,bool singleFrame) const213 void ProtocolWebSocket::send_binary(Cutelyst::Context *c, Socket *sock, bool singleFrame) const
214 {
215 Cutelyst::Request *request = c->request();
216 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
217
218 protoRequest->websocket_message.append(protoRequest->websocket_payload);
219
220 const QByteArray frame = protoRequest->websocket_payload;
221 Q_EMIT request->webSocketBinaryFrame(frame,
222 protoRequest->websocket_finn_opcode & 0x80,
223 protoRequest->context);
224
225 if (protoRequest->websocket_finn_opcode & 0x80) {
226 protoRequest->websocket_continue_opcode = 0;
227 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
228 Q_EMIT request->webSocketBinaryMessage(frame, protoRequest->context);
229 } else {
230 Q_EMIT request->webSocketBinaryMessage(protoRequest->websocket_message,
231 protoRequest->context);
232 }
233 protoRequest->websocket_message = QByteArray();
234 protoRequest->websocket_payload = QByteArray();
235 }
236 }
237
send_pong(QIODevice * io,const QByteArray data) const238 void ProtocolWebSocket::send_pong(QIODevice *io, const QByteArray data) const
239 {
240 io->write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong, quint64(data.size())));
241 io->write(data);
242 }
243
send_closed(Cutelyst::Context * c,Socket * sock,QIODevice * io) const244 void ProtocolWebSocket::send_closed(Cutelyst::Context *c, Socket *sock, QIODevice *io) const
245 {
246 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
247 quint16 closeCode = Cutelyst::Response::CloseCodeMissingStatusCode;
248 QString reason;
249 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
250 QTextCodec::ConverterState state;
251 const QString msg = m_codec->toUnicode(protoRequest->websocket_message.data(), protoRequest->websocket_message.size(), &state);
252 const bool failed = state.invalidChars || state.remainingChars;
253 #else
254 const bool failed = false;//FIXME
255 #endif
256
257 if (protoRequest->websocket_payload.size() >= 2) {
258 closeCode = net_be16(protoRequest->websocket_payload.data());
259 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
260 reason = m_codec->toUnicode(protoRequest->websocket_payload.data() + 2, protoRequest->websocket_payload.size() - 2, &state);
261 #else
262 auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
263 reason = toUtf16(protoRequest->websocket_payload.mid(2));
264 #endif
265 }
266 Q_EMIT c->request()->webSocketClosed(closeCode, reason);
267
268 if (failed) {
269 reason = QString();
270 closeCode = Cutelyst::Response::CloseCodeProtocolError;
271 } else if (closeCode < 3000 || closeCode > 4999) {
272 switch (closeCode) {
273 case Cutelyst::Response::CloseCodeNormal:
274 case Cutelyst::Response::CloseCodeGoingAway:
275 case Cutelyst::Response::CloseCodeProtocolError:
276 case Cutelyst::Response::CloseCodeDatatypeNotSupported:
277 // case Cutelyst::Response::CloseCodeReserved1004:
278 break;
279 case Cutelyst::Response::CloseCodeMissingStatusCode:
280 if (protoRequest->websocket_payload.isEmpty()) {
281 closeCode = Cutelyst::Response::CloseCodeNormal;
282 } else {
283 closeCode = Cutelyst::Response::CloseCodeProtocolError;
284 }
285 break;
286 // case Cutelyst::Response::CloseCodeAbnormalDisconnection:
287 case Cutelyst::Response::CloseCodeWrongDatatype:
288 case Cutelyst::Response::CloseCodePolicyViolated:
289 case Cutelyst::Response::CloseCodeTooMuchData:
290 case Cutelyst::Response::CloseCodeMissingExtension:
291 case Cutelyst::Response::CloseCodeBadOperation:
292 // case Cutelyst::Response::CloseCodeTlsHandshakeFailed:
293 break;
294 default:
295 reason = QString();
296 closeCode = Cutelyst::Response::CloseCodeProtocolError;
297 break;
298 }
299 }
300
301 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, closeCode);
302 io->write(reply);
303
304 sock->connectionClose();
305 }
306
websocket_parse_header(Socket * sock,const char * buf,QIODevice * io) const307 bool ProtocolWebSocket::websocket_parse_header(Socket *sock, const char *buf, QIODevice *io) const
308 {
309 const char byte1 = buf[0];
310 const char byte2 = buf[1];
311
312 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
313 protoRequest->websocket_finn_opcode = quint8(byte1);
314 protoRequest->websocket_payload_size = byte2 & 0x7f;
315
316 quint8 opcode = byte1 & 0xf;
317
318 bool websocket_has_mask = byte2 >> 7;
319 if (!websocket_has_mask ||
320 ((opcode == ProtoRequestHttp::OpCodePing || opcode == ProtoRequestHttp::OpCodeClose) && protoRequest->websocket_payload_size > 125) ||
321 (byte1 & 0x70) ||
322 ((opcode >= ProtoRequestHttp::OpCodeReserved3 && opcode <= ProtoRequestHttp::OpCodeReserved7) ||
323 (opcode >= ProtoRequestHttp::OpCodeReservedB && opcode <= ProtoRequestHttp::OpCodeReservedF)) ||
324 (!(byte1 & 0x80) && opcode != ProtoRequestHttp::OpCodeText && opcode != ProtoRequestHttp::OpCodeBinary && opcode != ProtoRequestHttp::OpCodeContinue) ||
325 (protoRequest->websocket_continue_opcode && (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary))) {
326 // RFC errors
327 // client to server MUST have a mask
328 // Control opcode cannot have payload bigger than 125
329 // RSV bytes MUST not be set
330 // reserved opcodes must not be set 3-7
331 // reserved opcodes must not be set B-F
332 // Only Text/Bynary/Coninue opcodes can be fragmented
333 // Continue opcode was set but was NOT followed by CONTINUE
334
335 io->write(ProtocolWebSocket::createWebsocketCloseReply(QString(), 1002)); // Protocol error
336 sock->connectionClose();
337 return false;
338 }
339
340 if (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary) {
341 protoRequest->websocket_message = QByteArray();
342 protoRequest->websocket_start_of_frame = 0;
343 if (!(byte1 & 0x80)) {
344 // FINN byte not set, store opcode for continue
345 protoRequest->websocket_continue_opcode = opcode;
346 }
347 }
348
349 if (protoRequest->websocket_payload_size == 126) {
350 protoRequest->websocket_need = 2;
351 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseSize;
352 } else if (protoRequest->websocket_payload_size == 127) {
353 protoRequest->websocket_need = 8;
354 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseSize;
355 } else {
356 protoRequest->websocket_need = 4;
357 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
358 }
359
360 return true;
361 }
362
websocket_parse_size(Socket * sock,const char * buf,int websockets_max_message_size) const363 bool ProtocolWebSocket::websocket_parse_size(Socket *sock, const char *buf, int websockets_max_message_size) const
364 {
365 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
366 quint64 size;
367 if (protoRequest->websocket_payload_size == 126) {
368 size = net_be16(buf);
369 } else if (protoRequest->websocket_payload_size == 127) {
370 size = net_be64(buf);
371 } else {
372 qCCritical(CWSGI_WS) << "BUG error in websocket parser:" << protoRequest->websocket_payload_size;
373 sock->connectionClose();
374 return false;
375 }
376
377 if (size > static_cast<quint64>(websockets_max_message_size)) {
378 qCCritical(CWSGI_WS) << "Payload size too big" << size << "max allowed" << websockets_max_message_size;
379 sock->connectionClose();
380 return false;
381 }
382 protoRequest->websocket_payload_size = size;
383
384 protoRequest->websocket_need = 4;
385 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
386
387 return true;
388 }
389
websocket_parse_mask(Socket * sock,char * buf,QIODevice * io) const390 void ProtocolWebSocket::websocket_parse_mask(Socket *sock, char *buf, QIODevice *io) const
391 {
392 auto ptr = reinterpret_cast<const quint32 *>(buf);
393 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
394 protoRequest->websocket_mask = *ptr;
395
396 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhasePayload;
397 protoRequest->websocket_need = quint32(protoRequest->websocket_payload_size);
398
399 protoRequest->websocket_payload = QByteArray();
400 if (protoRequest->websocket_payload_size == 0) {
401 websocket_parse_payload(sock, buf, 0, io);
402 } else {
403 protoRequest->websocket_payload.reserve(int(protoRequest->websocket_payload_size));
404 }
405 }
406
websocket_parse_payload(Socket * sock,char * buf,int len,QIODevice * io) const407 bool ProtocolWebSocket::websocket_parse_payload(Socket *sock, char *buf, int len, QIODevice *io) const
408 {
409 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
410 auto mask = reinterpret_cast<quint8 *>(&protoRequest->websocket_mask);
411 for (int i = 0, maskIx = protoRequest->websocket_payload.size(); i < len; ++i, ++maskIx) {
412 buf[i] = buf[i] ^ mask[maskIx % 4];
413 }
414
415 protoRequest->websocket_payload.append(buf, len);
416 if (quint64(protoRequest->websocket_payload.size()) < protoRequest->websocket_payload_size) {
417 // need more data
418 protoRequest->websocket_need -= uint(len);
419 return true;
420 }
421
422 protoRequest->websocket_need = 2;
423 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
424
425 Cutelyst::Request *request = protoRequest->context->request();
426
427 switch (protoRequest->websocket_finn_opcode & 0xf) {
428 case ProtoRequestHttp::OpCodeContinue:
429 switch (protoRequest->websocket_continue_opcode) {
430 case ProtoRequestHttp::OpCodeText:
431 if (!send_text(protoRequest->context, sock, false)) {
432 return false;
433 }
434 break;
435 case ProtoRequestHttp::OpCodeBinary:
436 send_binary(protoRequest->context, sock, false);
437 break;
438 default:
439 qCCritical(CWSGI_WS) << "Invalid CONTINUE opcode:" << (protoRequest->websocket_finn_opcode & 0xf);
440 sock->connectionClose();
441 return false;
442 }
443 break;
444 case ProtoRequestHttp::OpCodeText:
445 if (!send_text(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80)) {
446 return false;
447 }
448 break;
449 case ProtoRequestHttp::OpCodeBinary:
450 send_binary(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80);
451 break;
452 case ProtoRequestHttp::OpCodeClose:
453 send_closed(protoRequest->context, sock, io);
454 return false;
455 case ProtoRequestHttp::OpCodePing:
456 send_pong(io, protoRequest->websocket_payload.left(125));
457 sock->flush();
458 break;
459 case ProtoRequestHttp::OpCodePong:
460 Q_EMIT request->webSocketPong(protoRequest->websocket_payload,
461 protoRequest->context);
462 break;
463 default:
464 break;
465 }
466
467 return true;
468 }
469