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)) {
 
   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)
 
   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();
 
  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.
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