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)) 18 # include <QTextCodec> 20 # include <QStringConverter> 23 Q_LOGGING_CATEGORY(C_SERVER_WS,
"cutelyst.server.websocket", QtWarningMsg)
27 ProtocolWebSocket::ProtocolWebSocket(
Server *wsgi)
29 , m_websockets_max_size(wsgi->websocketMaxSize() * 1024)
33 ProtocolWebSocket::~ProtocolWebSocket()
37 Protocol::Type ProtocolWebSocket::type()
const 39 return Protocol::Type::Http11Websocket;
42 QByteArray 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);
74 QByteArray 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();
297 bool 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;
358 bool 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;
389 void 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));
406 bool 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 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...
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...
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 webSocketClosed(quint16 closeCode, const QString &reason)
Emitted when the websocket receives a close frame, including a close code and a reason, it's also emitted when the connection closes without the client sending the close frame.
bool isEmpty() const const
qint64 read(char *data, qint64 maxSize)
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(char ch)
QByteArray left(qsizetype len) const const
qint64 write(const char *data, qint64 maxSize)
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