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)
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 auto request =
static_cast<ProtoRequestHttp *
>(sock->protoData);
96 if (!bytesAvailable || !request->websocket_need ||
97 (bytesAvailable < request->websocket_need &&
98 request->websocket_phase != ProtoRequestHttp::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::WebSocketPhaseHeaders:
114 if (!websocket_parse_header(sock, m_postBuffer, io)) {
118 case ProtoRequestHttp::WebSocketPhaseSize:
119 if (!websocket_parse_size(sock, m_postBuffer, m_websockets_max_size)) {
123 case ProtoRequestHttp::WebSocketPhaseMask:
124 websocket_parse_mask(sock, m_postBuffer, io);
126 case ProtoRequestHttp::WebSocketPhasePayload:
127 if (!websocket_parse_payload(sock, m_postBuffer,
int(len), io)) {
141bool ProtocolWebSocket::send_text(Cutelyst::Context *c,
Socket *sock,
bool singleFrame)
const
143 Cutelyst::Request *request = c->
request();
144 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
146 const int msg_size = protoRequest->websocket_message.size();
147 protoRequest->websocket_message.append(protoRequest->websocket_payload);
149 QByteArray payload = protoRequest->websocket_payload;
150 if (protoRequest->websocket_start_of_frame != msg_size) {
151 payload = protoRequest->websocket_message.
mid(protoRequest->websocket_start_of_frame);
155 const QString frame = toUtf16(payload);
156 const bool failed =
false;
158 if (singleFrame && (failed || (frame.
isEmpty() && payload.
size()))) {
159 sock->connectionClose();
161 }
else if (!failed) {
162 protoRequest->websocket_start_of_frame = protoRequest->websocket_message.size();
164 frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
167 if (protoRequest->websocket_finn_opcode & 0x80) {
168 protoRequest->websocket_continue_opcode = 0;
169 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
173 const QString msg = toUtf16(protoRequest->websocket_message);
174 const bool failed =
false;
176 sock->connectionClose();
181 protoRequest->websocket_message = QByteArray();
182 protoRequest->websocket_payload = QByteArray();
188void ProtocolWebSocket::send_binary(Cutelyst::Context *c,
Socket *sock,
bool singleFrame)
const
190 Cutelyst::Request *request = c->
request();
191 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
193 protoRequest->websocket_message.append(protoRequest->websocket_payload);
195 const QByteArray frame = protoRequest->websocket_payload;
197 frame, protoRequest->websocket_finn_opcode & 0x80, protoRequest->context);
199 if (protoRequest->websocket_finn_opcode & 0x80) {
200 protoRequest->websocket_continue_opcode = 0;
201 if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
205 protoRequest->context);
207 protoRequest->websocket_message = QByteArray();
208 protoRequest->websocket_payload = QByteArray();
212void ProtocolWebSocket::send_pong(QIODevice *io,
const QByteArray data)
const
214 io->
write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong,
215 quint64(data.
size())));
219void ProtocolWebSocket::send_closed(Cutelyst::Context *c,
Socket *sock, QIODevice *io)
const
221 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
222 quint16 closeCode = Cutelyst::Response::CloseCodeMissingStatusCode;
224 const bool failed =
false;
226 if (protoRequest->websocket_payload.size() >= 2) {
227 closeCode = net_be16(protoRequest->websocket_payload.data());
229 reason = toUtf16(protoRequest->websocket_payload.mid(2));
235 closeCode = Cutelyst::Response::CloseCodeProtocolError;
236 }
else if (closeCode < 3000 || closeCode > 4999) {
238 case Cutelyst::Response::CloseCodeNormal:
239 case Cutelyst::Response::CloseCodeGoingAway:
240 case Cutelyst::Response::CloseCodeProtocolError:
241 case Cutelyst::Response::CloseCodeDatatypeNotSupported:
244 case Cutelyst::Response::CloseCodeMissingStatusCode:
245 if (protoRequest->websocket_payload.isEmpty()) {
246 closeCode = Cutelyst::Response::CloseCodeNormal;
248 closeCode = Cutelyst::Response::CloseCodeProtocolError;
252 case Cutelyst::Response::CloseCodeWrongDatatype:
253 case Cutelyst::Response::CloseCodePolicyViolated:
254 case Cutelyst::Response::CloseCodeTooMuchData:
255 case Cutelyst::Response::CloseCodeMissingExtension:
256 case Cutelyst::Response::CloseCodeBadOperation:
261 closeCode = Cutelyst::Response::CloseCodeProtocolError;
266 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, closeCode);
269 sock->connectionClose();
272bool ProtocolWebSocket::websocket_parse_header(
Socket *sock,
const char *buf, QIODevice *io)
const
274 const char byte1 = buf[0];
275 const char byte2 = buf[1];
277 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
278 protoRequest->websocket_finn_opcode = quint8(byte1);
279 protoRequest->websocket_payload_size = byte2 & 0x7f;
281 quint8 opcode = byte1 & 0xf;
283 bool websocket_has_mask = byte2 >> 7;
284 if (!websocket_has_mask ||
285 ((opcode == ProtoRequestHttp::OpCodePing || opcode == ProtoRequestHttp::OpCodeClose) &&
286 protoRequest->websocket_payload_size > 125) ||
288 ((opcode >= ProtoRequestHttp::OpCodeReserved3 &&
289 opcode <= ProtoRequestHttp::OpCodeReserved7) ||
290 (opcode >= ProtoRequestHttp::OpCodeReservedB &&
291 opcode <= ProtoRequestHttp::OpCodeReservedF)) ||
292 (!(byte1 & 0x80) && opcode != ProtoRequestHttp::OpCodeText &&
293 opcode != ProtoRequestHttp::OpCodeBinary && opcode != ProtoRequestHttp::OpCodeContinue) ||
294 (protoRequest->websocket_continue_opcode &&
295 (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary))) {
305 io->
write(ProtocolWebSocket::createWebsocketCloseReply(QString(), 1002));
306 sock->connectionClose();
310 if (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary) {
311 protoRequest->websocket_message = QByteArray();
312 protoRequest->websocket_start_of_frame = 0;
313 if (!(byte1 & 0x80)) {
315 protoRequest->websocket_continue_opcode = opcode;
319 if (protoRequest->websocket_payload_size == 126) {
320 protoRequest->websocket_need = 2;
321 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseSize;
322 }
else if (protoRequest->websocket_payload_size == 127) {
323 protoRequest->websocket_need = 8;
324 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseSize;
326 protoRequest->websocket_need = 4;
327 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
333bool ProtocolWebSocket::websocket_parse_size(
Socket *sock,
335 int websockets_max_message_size)
const
337 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
339 if (protoRequest->websocket_payload_size == 126) {
340 size = net_be16(buf);
341 }
else if (protoRequest->websocket_payload_size == 127) {
342 size = net_be64(buf);
344 qCCritical(C_SERVER_WS) <<
"BUG error in websocket parser:"
345 << protoRequest->websocket_payload_size;
346 sock->connectionClose();
350 if (size >
static_cast<quint64
>(websockets_max_message_size)) {
351 qCCritical(C_SERVER_WS) <<
"Payload size too big" << size <<
"max allowed"
352 << websockets_max_message_size;
353 sock->connectionClose();
356 protoRequest->websocket_payload_size = size;
358 protoRequest->websocket_need = 4;
359 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
364void ProtocolWebSocket::websocket_parse_mask(
Socket *sock,
char *buf, QIODevice *io)
const
366 auto ptr =
reinterpret_cast<const quint32 *
>(buf);
367 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
368 protoRequest->websocket_mask = *ptr;
370 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhasePayload;
371 protoRequest->websocket_need = quint32(protoRequest->websocket_payload_size);
373 protoRequest->websocket_payload = QByteArray();
374 if (protoRequest->websocket_payload_size == 0) {
375 websocket_parse_payload(sock, buf, 0, io);
377 protoRequest->websocket_payload.reserve(
int(protoRequest->websocket_payload_size));
381bool ProtocolWebSocket::websocket_parse_payload(
Socket *sock,
386 auto protoRequest =
static_cast<ProtoRequestHttp *
>(sock->protoData);
387 auto mask =
reinterpret_cast<quint8 *
>(&protoRequest->websocket_mask);
388 for (
int i = 0, maskIx = protoRequest->websocket_payload.size(); i < len; ++i, ++maskIx) {
389 buf[i] = buf[i] ^ mask[maskIx % 4];
392 protoRequest->websocket_payload.append(buf, len);
393 if (quint64(protoRequest->websocket_payload.size()) < protoRequest->websocket_payload_size) {
395 protoRequest->websocket_need -= uint(len);
399 protoRequest->websocket_need = 2;
400 protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
402 Cutelyst::Request *request = protoRequest->context->request();
404 switch (protoRequest->websocket_finn_opcode & 0xf) {
405 case ProtoRequestHttp::OpCodeContinue:
406 switch (protoRequest->websocket_continue_opcode) {
407 case ProtoRequestHttp::OpCodeText:
408 if (!send_text(protoRequest->context, sock,
false)) {
412 case ProtoRequestHttp::OpCodeBinary:
413 send_binary(protoRequest->context, sock,
false);
416 qCCritical(C_SERVER_WS)
417 <<
"Invalid CONTINUE opcode:" << (protoRequest->websocket_finn_opcode & 0xf);
418 sock->connectionClose();
422 case ProtoRequestHttp::OpCodeText:
423 if (!send_text(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80)) {
427 case ProtoRequestHttp::OpCodeBinary:
428 send_binary(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80);
430 case ProtoRequestHttp::OpCodeClose:
431 send_closed(protoRequest->context, sock, io);
433 case ProtoRequestHttp::OpCodePing:
434 send_pong(io, protoRequest->websocket_payload.left(125));
437 case ProtoRequestHttp::OpCodePong:
438 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