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