cutelyst  5.0.1
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 *server)
23  : Protocol(server)
24  , m_websockets_max_size(server->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  const 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::WebSocketPhase::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::WebSocketPhase::WebSocketPhaseHeaders:
114  if (!websocket_parse_header(sock, m_postBuffer, io)) {
115  return;
116  }
117  break;
118  case ProtoRequestHttp::WebSocketPhase::WebSocketPhaseSize:
119  if (!websocket_parse_size(sock, m_postBuffer, m_websockets_max_size)) {
120  return;
121  }
122  break;
123  case ProtoRequestHttp::WebSocketPhase::WebSocketPhaseMask:
124  websocket_parse_mask(sock, m_postBuffer, io);
125  break;
126  case ProtoRequestHttp::WebSocketPhase::WebSocketPhasePayload:
127  if (!websocket_parse_payload(sock, m_postBuffer, int(len), io)) {
128  return;
129  }
130  break;
131  default:
132  Q_UNREACHABLE();
133  break;
134  }
135  }
136 }
137 
138 ProtocolData *ProtocolWebSocket::createData(Socket *sock) const
139 {
140  Q_UNUSED(sock)
141  return nullptr;
142 }
143 
144 bool ProtocolWebSocket::send_text(Cutelyst::Context *c, Socket *sock, bool singleFrame) const
145 {
146  Cutelyst::Request *request = c->request();
147  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
148 
149  const int msg_size = protoRequest->websocket_message.size();
150  protoRequest->websocket_message.append(protoRequest->websocket_payload);
151 
152  QByteArray payload = protoRequest->websocket_payload;
153  if (protoRequest->websocket_start_of_frame != msg_size) {
154  payload = protoRequest->websocket_message.mid(protoRequest->websocket_start_of_frame);
155  }
156 
157  auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
158  const QString frame = toUtf16(payload);
159  const bool failed = toUtf16.hasError();
160 
161  if (singleFrame && (failed || (frame.isEmpty() && payload.size()))) {
162  sock->connectionClose();
163  return false;
164  } else if (!failed) {
165  protoRequest->websocket_start_of_frame = protoRequest->websocket_message.size();
166  Q_EMIT request->webSocketTextFrame(
167  frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
168  }
169 
170  if (protoRequest->websocket_finn_opcode & 0x80) {
171  protoRequest->websocket_continue_opcode = 0;
172  if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
173  Q_EMIT request->webSocketTextMessage(frame, protoRequest->context);
174  } else {
175  const QString msg = toUtf16(protoRequest->websocket_message);
176  if (toUtf16.hasError()) {
177  sock->connectionClose();
178  return false;
179  }
180  Q_EMIT request->webSocketTextMessage(msg, protoRequest->context);
181  }
182  protoRequest->websocket_message = QByteArray();
183  protoRequest->websocket_payload = QByteArray();
184  }
185 
186  return true;
187 }
188 
189 void ProtocolWebSocket::send_binary(Cutelyst::Context *c, Socket *sock, bool singleFrame) const
190 {
191  Cutelyst::Request *request = c->request();
192  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
193 
194  protoRequest->websocket_message.append(protoRequest->websocket_payload);
195 
196  const QByteArray frame = protoRequest->websocket_payload;
197  Q_EMIT request->webSocketBinaryFrame(
198  frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
199 
200  if (protoRequest->websocket_finn_opcode & 0x80) {
201  protoRequest->websocket_continue_opcode = 0;
202  if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
203  Q_EMIT request->webSocketBinaryMessage(frame, protoRequest->context);
204  } else {
205  Q_EMIT request->webSocketBinaryMessage(protoRequest->websocket_message,
206  protoRequest->context);
207  }
208  protoRequest->websocket_message = QByteArray();
209  protoRequest->websocket_payload = QByteArray();
210  }
211 }
212 
213 void ProtocolWebSocket::send_pong(QIODevice *io, const QByteArray &data) const
214 {
215  io->write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong,
216  quint64(data.size())));
217  io->write(data);
218 }
219 
220 void ProtocolWebSocket::send_closed(const Cutelyst::Context *c, Socket *sock, QIODevice *io) const
221 {
222  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
223  quint16 closeCode = Cutelyst::Response::CloseCodeMissingStatusCode;
224  QString reason;
225  const bool failed = false; // FIXME
226 
227  if (protoRequest->websocket_payload.size() >= 2) {
228  closeCode = net_be16(protoRequest->websocket_payload.data());
229  auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
230  reason = toUtf16(protoRequest->websocket_payload.mid(2));
231  }
232  Q_EMIT c->request()->webSocketClosed(closeCode, reason);
233 
234  if (failed) {
235  reason.clear();
236  closeCode = Cutelyst::Response::CloseCodeProtocolError;
237  } else if (closeCode < 3000 || closeCode > 4999) {
238  switch (closeCode) {
239  case Cutelyst::Response::CloseCodeNormal:
240  case Cutelyst::Response::CloseCodeGoingAway:
241  case Cutelyst::Response::CloseCodeProtocolError:
242  case Cutelyst::Response::CloseCodeDatatypeNotSupported:
243  // case Cutelyst::Response::CloseCodeReserved1004:
244  break;
245  case Cutelyst::Response::CloseCodeMissingStatusCode:
246  if (protoRequest->websocket_payload.isEmpty()) {
247  closeCode = Cutelyst::Response::CloseCodeNormal;
248  } else {
249  closeCode = Cutelyst::Response::CloseCodeProtocolError;
250  }
251  break;
252  // case Cutelyst::Response::CloseCodeAbnormalDisconnection:
253  case Cutelyst::Response::CloseCodeWrongDatatype:
254  case Cutelyst::Response::CloseCodePolicyViolated:
255  case Cutelyst::Response::CloseCodeTooMuchData:
256  case Cutelyst::Response::CloseCodeMissingExtension:
257  case Cutelyst::Response::CloseCodeBadOperation:
258  // case Cutelyst::Response::CloseCodeTlsHandshakeFailed:
259  break;
260  default:
261  reason.clear();
262  closeCode = Cutelyst::Response::CloseCodeProtocolError;
263  break;
264  }
265  }
266 
267  const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, closeCode);
268  io->write(reply);
269 
270  sock->connectionClose();
271 }
272 
273 bool ProtocolWebSocket::websocket_parse_header(Socket *sock, const char *buf, QIODevice *io) const
274 {
275  const char byte1 = buf[0];
276  const char byte2 = buf[1];
277 
278  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
279  protoRequest->websocket_finn_opcode = quint8(byte1);
280  protoRequest->websocket_payload_size = byte2 & 0x7f;
281 
282  quint8 opcode = byte1 & 0xf;
283 
284  bool websocket_has_mask = byte2 >> 7;
285  if (!websocket_has_mask ||
286  ((opcode == ProtoRequestHttp::OpCodePing || opcode == ProtoRequestHttp::OpCodeClose) &&
287  protoRequest->websocket_payload_size > 125) ||
288  (byte1 & 0x70) ||
289  ((opcode >= ProtoRequestHttp::OpCodeReserved3 &&
290  opcode <= ProtoRequestHttp::OpCodeReserved7) ||
291  (opcode >= ProtoRequestHttp::OpCodeReservedB &&
292  opcode <= ProtoRequestHttp::OpCodeReservedF)) ||
293  (!(byte1 & 0x80) && opcode != ProtoRequestHttp::OpCodeText &&
294  opcode != ProtoRequestHttp::OpCodeBinary && opcode != ProtoRequestHttp::OpCodeContinue) ||
295  (protoRequest->websocket_continue_opcode &&
296  (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary))) {
297  // RFC errors
298  // client to server MUST have a mask
299  // Control opcode cannot have payload bigger than 125
300  // RSV bytes MUST not be set
301  // reserved opcodes must not be set 3-7
302  // reserved opcodes must not be set B-F
303  // Only Text/Bynary/Coninue opcodes can be fragmented
304  // Continue opcode was set but was NOT followed by CONTINUE
305 
306  io->write(ProtocolWebSocket::createWebsocketCloseReply({}, 1002)); // Protocol error
307  sock->connectionClose();
308  return false;
309  }
310 
311  if (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary) {
312  protoRequest->websocket_message = QByteArray();
313  protoRequest->websocket_start_of_frame = 0;
314  if (!(byte1 & 0x80)) {
315  // FINN byte not set, store opcode for continue
316  protoRequest->websocket_continue_opcode = opcode;
317  }
318  }
319 
320  if (protoRequest->websocket_payload_size == 126) {
321  protoRequest->websocket_need = 2;
322  protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseSize;
323  } else if (protoRequest->websocket_payload_size == 127) {
324  protoRequest->websocket_need = 8;
325  protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseSize;
326  } else {
327  protoRequest->websocket_need = 4;
328  protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseMask;
329  }
330 
331  return true;
332 }
333 
334 bool ProtocolWebSocket::websocket_parse_size(Socket *sock,
335  const char *buf,
336  int websockets_max_message_size) const
337 {
338  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
339  quint64 size;
340  if (protoRequest->websocket_payload_size == 126) {
341  size = net_be16(buf);
342  } else if (protoRequest->websocket_payload_size == 127) {
343  size = net_be64(buf);
344  } else {
345  qCCritical(C_SERVER_WS) << "BUG error in websocket parser:"
346  << protoRequest->websocket_payload_size;
347  sock->connectionClose();
348  return false;
349  }
350 
351  if (size > static_cast<quint64>(websockets_max_message_size)) {
352  qCCritical(C_SERVER_WS) << "Payload size too big" << size << "max allowed"
353  << websockets_max_message_size;
354  sock->connectionClose();
355  return false;
356  }
357  protoRequest->websocket_payload_size = size;
358 
359  protoRequest->websocket_need = 4;
360  protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseMask;
361 
362  return true;
363 }
364 
365 void ProtocolWebSocket::websocket_parse_mask(Socket *sock, char *buf, QIODevice *io) const
366 {
367  auto ptr = reinterpret_cast<const quint32 *>(buf);
368  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
369  protoRequest->websocket_mask = *ptr;
370 
371  protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhasePayload;
372  protoRequest->websocket_need = quint32(protoRequest->websocket_payload_size);
373 
374  protoRequest->websocket_payload = QByteArray();
375  if (protoRequest->websocket_payload_size == 0) {
376  websocket_parse_payload(sock, buf, 0, io);
377  } else {
378  protoRequest->websocket_payload.reserve(int(protoRequest->websocket_payload_size));
379  }
380 }
381 
382 bool ProtocolWebSocket::websocket_parse_payload(Socket *sock,
383  char *buf,
384  int len,
385  QIODevice *io) const
386 {
387  auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
388  const auto *mask = reinterpret_cast<quint8 *>(&protoRequest->websocket_mask);
389  for (int i = 0, maskIx = protoRequest->websocket_payload.size(); i < len; ++i, ++maskIx) {
390  buf[i] = buf[i] ^ mask[maskIx % 4];
391  }
392 
393  protoRequest->websocket_payload.append(buf, len);
394  if (quint64(protoRequest->websocket_payload.size()) < protoRequest->websocket_payload_size) {
395  // need more data
396  protoRequest->websocket_need -= uint(len);
397  return true;
398  }
399 
400  protoRequest->websocket_need = 2;
401  protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseHeaders;
402 
403  Cutelyst::Request *request = protoRequest->context->request();
404 
405  switch (protoRequest->websocket_finn_opcode & 0xf) {
406  case ProtoRequestHttp::OpCodeContinue:
407  switch (protoRequest->websocket_continue_opcode) {
408  case ProtoRequestHttp::OpCodeText:
409  if (!send_text(protoRequest->context, sock, false)) {
410  return false;
411  }
412  break;
413  case ProtoRequestHttp::OpCodeBinary:
414  send_binary(protoRequest->context, sock, false);
415  break;
416  default:
417  qCCritical(C_SERVER_WS)
418  << "Invalid CONTINUE opcode:" << (protoRequest->websocket_finn_opcode & 0xf);
419  sock->connectionClose();
420  return false;
421  }
422  break;
423  case ProtoRequestHttp::OpCodeText:
424  if (!send_text(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80)) {
425  return false;
426  }
427  break;
428  case ProtoRequestHttp::OpCodeBinary:
429  send_binary(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80);
430  break;
431  case ProtoRequestHttp::OpCodeClose:
432  send_closed(protoRequest->context, sock, io);
433  return false;
434  case ProtoRequestHttp::OpCodePing:
435  send_pong(io, protoRequest->websocket_payload.left(125));
436  sock->flush();
437  break;
438  case ProtoRequestHttp::OpCodePong:
439  Q_EMIT request->webSocketPong(protoRequest->websocket_payload, protoRequest->context);
440  break;
441  default:
442  break;
443  }
444 
445  return true;
446 }
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 clear()
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