5#include "protocolwebsocket.h"
7#include "protocolhttp.h"
11#include <Cutelyst/Context>
12#include <Cutelyst/Headers>
13#include <Cutelyst/Response>
15#include <QLoggingCategory>
16#include <QStringConverter>
18Q_LOGGING_CATEGORY(C_SERVER_WS,
"cutelyst.server.websocket", QtWarningMsg)
22ProtocolWebSocket::ProtocolWebSocket(
Server *wsgi)
24 , m_websockets_max_size(wsgi->websocketMaxSize() * 1024)
28ProtocolWebSocket::~ProtocolWebSocket()
32Protocol::Type ProtocolWebSocket::type()
const
34 return Protocol::Type::Http11Websocket;
37QByteArray ProtocolWebSocket::createWebsocketHeader(quint8 opcode, quint64 len)
40 ret.
append(
char(0x80 + opcode));
43 ret.
append(
static_cast<char>(len));
44 }
else if (len <=
static_cast<quint16
>(0xffff)) {
48 buf[1] = quint8(len & 0xff);
49 buf[0] = quint8((len >> 8) & 0xff);
50 ret.
append(
reinterpret_cast<char *
>(buf), 2);
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);
69QByteArray ProtocolWebSocket::createWebsocketCloseReply(
const QString &msg, quint16 closeCode)
75 payload = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodeClose,
76 quint64(data.
size() + 2));
79 buf[1] = quint8(closeCode & 0xff);
80 buf[0] = quint8((closeCode >> 8) & 0xff);
81 payload.
append(
reinterpret_cast<char *
>(buf), 2);
96 if (!bytesAvailable || !request->websocket_need ||
97 (bytesAvailable < request->websocket_need &&
98 request->websocket_phase != ProtoRequestHttp::WebSocketPhase::WebSocketPhasePayload)) {
103 quint32 maxlen = qMin(request->websocket_need,
static_cast<quint32
>(m_postBufferSize));
104 qint64 len = io->
read(m_postBuffer, maxlen);
106 qCWarning(C_SERVER_WS) <<
"Failed to read from socket" << io->
errorString();
107 sock->connectionClose();
110 bytesAvailable -= len;
112 switch (request->websocket_phase) {
113 case ProtoRequestHttp::WebSocketPhase::WebSocketPhaseHeaders:
114 if (!websocket_parse_header(sock, m_postBuffer, io)) {
118 case ProtoRequestHttp::WebSocketPhase::WebSocketPhaseSize:
119 if (!websocket_parse_size(sock, m_postBuffer, m_websockets_max_size)) {
123 case ProtoRequestHttp::WebSocketPhase::WebSocketPhaseMask:
124 websocket_parse_mask(sock, m_postBuffer, io);
126 case ProtoRequestHttp::WebSocketPhase::WebSocketPhasePayload:
127 if (!websocket_parse_payload(sock, m_postBuffer,
int(len), io)) {
149 const int msg_size = protoRequest->websocket_message.
size();
150 protoRequest->websocket_message.append(protoRequest->websocket_payload);
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);
158 const QString frame = toUtf16(payload);
159 const bool failed = toUtf16.hasError();
161 if (singleFrame && (failed || (frame.
isEmpty() && payload.
size()))) {
162 sock->connectionClose();
164 }
else if (!failed) {
165 protoRequest->websocket_start_of_frame = protoRequest->websocket_message.size();
167 frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
170 if (protoRequest->websocket_finn_opcode & 0x80) {
171 protoRequest->websocket_continue_opcode = 0;
172 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
175 const QString msg = toUtf16(protoRequest->websocket_message);
176 if (toUtf16.hasError()) {
177 sock->connectionClose();
182 protoRequest->websocket_message =
QByteArray();
183 protoRequest->websocket_payload =
QByteArray();
194 protoRequest->websocket_message.
append(protoRequest->websocket_payload);
196 const QByteArray frame = protoRequest->websocket_payload;
198 frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
200 if (protoRequest->websocket_finn_opcode & 0x80) {
201 protoRequest->websocket_continue_opcode = 0;
202 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
206 protoRequest->context);
208 protoRequest->websocket_message =
QByteArray();
209 protoRequest->websocket_payload =
QByteArray();
215 io->
write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong,
216 quint64(data.
size())));
223 quint16 closeCode = Cutelyst::Response::CloseCodeMissingStatusCode;
225 const bool failed =
false;
227 if (protoRequest->websocket_payload.size() >= 2) {
228 closeCode = net_be16(protoRequest->websocket_payload.data());
230 reason = toUtf16(protoRequest->websocket_payload.mid(2));
236 closeCode = Cutelyst::Response::CloseCodeProtocolError;
237 }
else if (closeCode < 3000 || closeCode > 4999) {
239 case Cutelyst::Response::CloseCodeNormal:
240 case Cutelyst::Response::CloseCodeGoingAway:
241 case Cutelyst::Response::CloseCodeProtocolError:
242 case Cutelyst::Response::CloseCodeDatatypeNotSupported:
245 case Cutelyst::Response::CloseCodeMissingStatusCode:
246 if (protoRequest->websocket_payload.isEmpty()) {
247 closeCode = Cutelyst::Response::CloseCodeNormal;
249 closeCode = Cutelyst::Response::CloseCodeProtocolError;
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:
262 closeCode = Cutelyst::Response::CloseCodeProtocolError;
267 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, closeCode);
270 sock->connectionClose();
273bool ProtocolWebSocket::websocket_parse_header(
Socket *sock,
const char *buf,
QIODevice *io)
const
275 const char byte1 = buf[0];
276 const char byte2 = buf[1];
279 protoRequest->websocket_finn_opcode = quint8(byte1);
280 protoRequest->websocket_payload_size = byte2 & 0x7f;
282 quint8 opcode = byte1 & 0xf;
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) ||
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))) {
306 io->
write(ProtocolWebSocket::createWebsocketCloseReply({}, 1002));
307 sock->connectionClose();
311 if (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary) {
312 protoRequest->websocket_message =
QByteArray();
313 protoRequest->websocket_start_of_frame = 0;
314 if (!(byte1 & 0x80)) {
316 protoRequest->websocket_continue_opcode = opcode;
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;
327 protoRequest->websocket_need = 4;
328 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseMask;
334bool ProtocolWebSocket::websocket_parse_size(
Socket *sock,
336 int websockets_max_message_size)
const
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);
345 qCCritical(C_SERVER_WS) <<
"BUG error in websocket parser:"
346 << protoRequest->websocket_payload_size;
347 sock->connectionClose();
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();
357 protoRequest->websocket_payload_size = size;
359 protoRequest->websocket_need = 4;
360 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseMask;
365void ProtocolWebSocket::websocket_parse_mask(
Socket *sock,
char *buf,
QIODevice *io)
const
367 auto ptr =
reinterpret_cast<const quint32 *
>(buf);
369 protoRequest->websocket_mask = *ptr;
371 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhasePayload;
372 protoRequest->websocket_need = quint32(protoRequest->websocket_payload_size);
374 protoRequest->websocket_payload =
QByteArray();
375 if (protoRequest->websocket_payload_size == 0) {
376 websocket_parse_payload(sock, buf, 0, io);
378 protoRequest->websocket_payload.reserve(
int(protoRequest->websocket_payload_size));
382bool ProtocolWebSocket::websocket_parse_payload(
Socket *sock,
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];
393 protoRequest->websocket_payload.
append(buf, len);
394 if (quint64(protoRequest->websocket_payload.size()) < protoRequest->websocket_payload_size) {
396 protoRequest->websocket_need -= uint(len);
400 protoRequest->websocket_need = 2;
401 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseHeaders;
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)) {
413 case ProtoRequestHttp::OpCodeBinary:
414 send_binary(protoRequest->context, sock,
false);
417 qCCritical(C_SERVER_WS)
418 <<
"Invalid CONTINUE opcode:" << (protoRequest->websocket_finn_opcode & 0xf);
419 sock->connectionClose();
423 case ProtoRequestHttp::OpCodeText:
424 if (!send_text(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80)) {
428 case ProtoRequestHttp::OpCodeBinary:
429 send_binary(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80);
431 case ProtoRequestHttp::OpCodeClose:
432 send_closed(protoRequest->context, sock, io);
434 case ProtoRequestHttp::OpCodePing:
435 send_pong(io, protoRequest->websocket_payload.left(125));
438 case ProtoRequestHttp::OpCodePong:
439 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