5#include "protocolwebsocket.h"
7#include "protocolhttp.h"
11#include <Cutelyst/Context>
12#include <Cutelyst/Headers>
13#include <Cutelyst/Response>
15#include <QLoggingCategory>
17#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
20# include <QStringConverter>
23Q_LOGGING_CATEGORY(C_SERVER_WS,
"cutelyst.server.websocket", QtWarningMsg)
27ProtocolWebSocket::ProtocolWebSocket(
Server *wsgi)
29 , m_websockets_max_size(wsgi->websocketMaxSize() * 1024)
33ProtocolWebSocket::~ProtocolWebSocket()
37Protocol::Type ProtocolWebSocket::type()
const
39 return Protocol::Type::Http11Websocket;
42QByteArray ProtocolWebSocket::createWebsocketHeader(quint8 opcode, quint64 len)
45 ret.
append(
char(0x80 + opcode));
48 ret.
append(
static_cast<char>(len));
49 }
else if (len <=
static_cast<quint16
>(0xffff)) {
53 buf[1] = quint8(len & 0xff);
54 buf[0] = quint8((len >> 8) & 0xff);
55 ret.
append(
reinterpret_cast<char *
>(buf), 2);
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);
74QByteArray ProtocolWebSocket::createWebsocketCloseReply(
const QString &msg, quint16 closeCode)
80 payload = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodeClose,
81 quint64(data.
size() + 2));
84 buf[1] = quint8(closeCode & 0xff);
85 buf[0] = quint8((closeCode >> 8) & 0xff);
86 payload.
append(
reinterpret_cast<char *
>(buf), 2);
101 if (!bytesAvailable || !request->websocket_need ||
102 (bytesAvailable < request->websocket_need &&
103 request->websocket_phase != ProtoRequestHttp::WebSocketPhasePayload)) {
108 quint32 maxlen = qMin(request->websocket_need,
static_cast<quint32
>(m_postBufferSize));
109 qint64 len = io->
read(m_postBuffer, maxlen);
111 qCWarning(C_SERVER_WS) <<
"Failed to read from socket" << io->
errorString();
112 sock->connectionClose();
115 bytesAvailable -= len;
117 switch (request->websocket_phase) {
118 case ProtoRequestHttp::WebSocketPhaseHeaders:
119 if (!websocket_parse_header(sock, m_postBuffer, io)) {
123 case ProtoRequestHttp::WebSocketPhaseSize:
124 if (!websocket_parse_size(sock, m_postBuffer, m_websockets_max_size)) {
128 case ProtoRequestHttp::WebSocketPhaseMask:
129 websocket_parse_mask(sock, m_postBuffer, io);
131 case ProtoRequestHttp::WebSocketPhasePayload:
132 if (!websocket_parse_payload(sock, m_postBuffer,
int(len), io)) {
151 const int msg_size = protoRequest->websocket_message.
size();
152 protoRequest->websocket_message.append(protoRequest->websocket_payload);
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);
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;
165 const QString frame = toUtf16(payload);
166 const bool failed =
false;
168 if (singleFrame && (failed || (frame.
isEmpty() && payload.
size()))) {
169 sock->connectionClose();
171 }
else if (!failed) {
172 protoRequest->websocket_start_of_frame = protoRequest->websocket_message.size();
174 frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
177 if (protoRequest->websocket_finn_opcode & 0x80) {
178 protoRequest->websocket_continue_opcode = 0;
179 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
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(),
187 const bool failed = stateMsg.invalidChars || stateMsg.remainingChars;
190 const QString msg = toUtf16(protoRequest->websocket_message);
191 const bool failed =
false;
194 sock->connectionClose();
199 protoRequest->websocket_message =
QByteArray();
200 protoRequest->websocket_payload =
QByteArray();
211 protoRequest->websocket_message.
append(protoRequest->websocket_payload);
213 const QByteArray frame = protoRequest->websocket_payload;
215 frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
217 if (protoRequest->websocket_finn_opcode & 0x80) {
218 protoRequest->websocket_continue_opcode = 0;
219 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
223 protoRequest->context);
225 protoRequest->websocket_message =
QByteArray();
226 protoRequest->websocket_payload =
QByteArray();
232 io->
write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong,
233 quint64(data.
size())));
240 quint16 closeCode = Cutelyst::Response::CloseCodeMissingStatusCode;
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;
248 const bool failed =
false;
251 if (protoRequest->websocket_payload.size() >= 2) {
252 closeCode = net_be16(protoRequest->websocket_payload.data());
254 reason = toUtf16(protoRequest->websocket_payload.mid(2));
260 closeCode = Cutelyst::Response::CloseCodeProtocolError;
261 }
else if (closeCode < 3000 || closeCode > 4999) {
263 case Cutelyst::Response::CloseCodeNormal:
264 case Cutelyst::Response::CloseCodeGoingAway:
265 case Cutelyst::Response::CloseCodeProtocolError:
266 case Cutelyst::Response::CloseCodeDatatypeNotSupported:
269 case Cutelyst::Response::CloseCodeMissingStatusCode:
270 if (protoRequest->websocket_payload.isEmpty()) {
271 closeCode = Cutelyst::Response::CloseCodeNormal;
273 closeCode = Cutelyst::Response::CloseCodeProtocolError;
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:
286 closeCode = Cutelyst::Response::CloseCodeProtocolError;
291 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, closeCode);
294 sock->connectionClose();
297bool ProtocolWebSocket::websocket_parse_header(
Socket *sock,
const char *buf,
QIODevice *io)
const
299 const char byte1 = buf[0];
300 const char byte2 = buf[1];
303 protoRequest->websocket_finn_opcode = quint8(byte1);
304 protoRequest->websocket_payload_size = byte2 & 0x7f;
306 quint8 opcode = byte1 & 0xf;
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) ||
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))) {
330 io->
write(ProtocolWebSocket::createWebsocketCloseReply(
QString(), 1002));
331 sock->connectionClose();
335 if (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary) {
336 protoRequest->websocket_message =
QByteArray();
337 protoRequest->websocket_start_of_frame = 0;
338 if (!(byte1 & 0x80)) {
340 protoRequest->websocket_continue_opcode = opcode;
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;
351 protoRequest->websocket_need = 4;
352 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
358bool ProtocolWebSocket::websocket_parse_size(
Socket *sock,
360 int websockets_max_message_size)
const
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);
369 qCCritical(C_SERVER_WS) <<
"BUG error in websocket parser:"
370 << protoRequest->websocket_payload_size;
371 sock->connectionClose();
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();
381 protoRequest->websocket_payload_size = size;
383 protoRequest->websocket_need = 4;
384 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
389void ProtocolWebSocket::websocket_parse_mask(
Socket *sock,
char *buf,
QIODevice *io)
const
391 auto ptr =
reinterpret_cast<const quint32 *
>(buf);
393 protoRequest->websocket_mask = *ptr;
395 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhasePayload;
396 protoRequest->websocket_need = quint32(protoRequest->websocket_payload_size);
398 protoRequest->websocket_payload =
QByteArray();
399 if (protoRequest->websocket_payload_size == 0) {
400 websocket_parse_payload(sock, buf, 0, io);
402 protoRequest->websocket_payload.reserve(
int(protoRequest->websocket_payload_size));
406bool ProtocolWebSocket::websocket_parse_payload(
Socket *sock,
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];
417 protoRequest->websocket_payload.
append(buf, len);
418 if (quint64(protoRequest->websocket_payload.size()) < protoRequest->websocket_payload_size) {
420 protoRequest->websocket_need -= uint(len);
424 protoRequest->websocket_need = 2;
425 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
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)) {
437 case ProtoRequestHttp::OpCodeBinary:
438 send_binary(protoRequest->context, sock,
false);
441 qCCritical(C_SERVER_WS)
442 <<
"Invalid CONTINUE opcode:" << (protoRequest->websocket_finn_opcode & 0xf);
443 sock->connectionClose();
447 case ProtoRequestHttp::OpCodeText:
448 if (!send_text(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80)) {
452 case ProtoRequestHttp::OpCodeBinary:
453 send_binary(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80);
455 case ProtoRequestHttp::OpCodeClose:
456 send_closed(protoRequest->context, sock, io);
458 case ProtoRequestHttp::OpCodePing:
459 send_pong(io, protoRequest->websocket_payload.left(125));
462 case ProtoRequestHttp::OpCodePong:
463 Q_EMIT request->
webSocketPong(protoRequest->websocket_payload, protoRequest->context);
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...
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
QByteArray left(qsizetype len) const const
QByteArray mid(qsizetype pos, qsizetype len) const const
qsizetype size() const const
virtual qint64 bytesAvailable() const const
QString errorString() const const
QByteArray read(qint64 maxSize)
qint64 write(const QByteArray &data)
bool isEmpty() const const
QByteArray toUtf8() const const