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)) {
50 ret.append(
char(126));
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)
78 const QByteArray data = msg.toUtf8().left(123);
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);
94void ProtocolWebSocket::parse(
Socket *sock, QIODevice *io)
const
96 qint64 bytesAvailable = io->bytesAvailable();
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;
164 auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
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;
189 auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
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();
230void ProtocolWebSocket::send_pong(QIODevice *io,
const QByteArray data)
const
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());
253 auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
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.