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 *server)
24 , m_websockets_max_size(server->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)
73 const QByteArray data = msg.
toUtf8().
left(123);
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);
89void ProtocolWebSocket::parse(
Socket *sock, QIODevice *io)
const
92 const auto *request =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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)) {
144bool ProtocolWebSocket::send_text(Cutelyst::Context *c,
Socket *sock,
bool singleFrame)
const
146 Cutelyst::Request *request = c->
request();
147 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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();
189void ProtocolWebSocket::send_binary(Cutelyst::Context *c,
Socket *sock,
bool singleFrame)
const
191 Cutelyst::Request *request = c->
request();
192 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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();
213void ProtocolWebSocket::send_pong(QIODevice *io,
const QByteArray &data)
const
215 io->
write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong,
216 quint64(data.
size())));
220void ProtocolWebSocket::send_closed(
const Cutelyst::Context *c,
Socket *sock, QIODevice *io)
const
222 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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];
278 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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
338 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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);
368 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
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,
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];
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;
403 Cutelyst::Request *request = protoRequest->context->request();
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) &&
QByteArray mid(qsizetype pos, qsizetype len) &&
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