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