1 /*
2  * Copyright (C) 2017-2018 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 #include "protocolwebsocket.h"
19 
20 #include "socket.h"
21 #include "server.h"
22 #include "protocolhttp.h"
23 
24 #include <Cutelyst/Headers>
25 #include <Cutelyst/Context>
26 #include <Cutelyst/Response>
27 
28 #include <QLoggingCategory>
29 
30 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
31 #include <QTextCodec>
32 #else
33 #include <QStringConverter>
34 #endif
35 
36 Q_LOGGING_CATEGORY(CWSGI_WS, "cwsgi.websocket", QtWarningMsg)
37 
38 using namespace Cutelyst;
39 
ProtocolWebSocket(Server * wsgi)40 ProtocolWebSocket::ProtocolWebSocket(Server *wsgi) : Protocol(wsgi)
41 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
42   , m_codec(QTextCodec::codecForName(QByteArrayLiteral("UTF-8")))
43 #endif
44   , m_websockets_max_size(wsgi->websocketMaxSize() * 1024)
45 {
46 }
47 
~ProtocolWebSocket()48 ProtocolWebSocket::~ProtocolWebSocket()
49 {
50 }
51 
createWebsocketHeader(quint8 opcode,quint64 len)52 QByteArray ProtocolWebSocket::createWebsocketHeader(quint8 opcode, quint64 len)
53 {
54     QByteArray ret;
55     ret.append(char(0x80 + opcode));
56 
57     if (len < 126) {
58         ret.append(static_cast<char>(len));
59     } else if (len <= static_cast<quint16>(0xffff)) {
60         ret.append(char(126));
61 
62         quint8 buf[2];
63         buf[1] = quint8(len & 0xff);
64         buf[0] = quint8((len >> 8) & 0xff);
65         ret.append(reinterpret_cast<char*>(buf), 2);
66     } else {
67         ret.append(127);
68 
69         quint8 buf[8];
70         buf[7] = quint8(len & 0xff);
71         buf[6] = quint8((len >> 8) & 0xff);
72         buf[5] = quint8((len >> 16) & 0xff);
73         buf[4] = quint8((len >> 24) & 0xff);
74         buf[3] = quint8((len >> 32) & 0xff);
75         buf[2] = quint8((len >> 40) & 0xff);
76         buf[1] = quint8((len >> 48) & 0xff);
77         buf[0] = quint8((len >> 56) & 0xff);
78         ret.append(reinterpret_cast<char*>(buf), 8);
79     }
80 
81     return ret;
82 }
83 
createWebsocketCloseReply(const QString & msg,quint16 closeCode)84 QByteArray ProtocolWebSocket::createWebsocketCloseReply(const QString &msg, quint16 closeCode)
85 {
86     QByteArray payload;
87 
88     const QByteArray data = msg.toUtf8().left(123);
89 
90     payload = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodeClose, quint64(data.size() + 2));
91 
92     quint8 buf[2];
93     buf[1] = quint8(closeCode & 0xff);
94     buf[0] = quint8((closeCode >> 8) & 0xff);
95     payload.append(reinterpret_cast<char*>(buf), 2);
96 
97     // 125 is max payload - 2 of the above bytes
98     payload.append(data);
99 
100     return payload;
101 }
102 
parse(Socket * sock,QIODevice * io) const103 void ProtocolWebSocket::parse(Socket *sock, QIODevice *io) const
104 {
105     qint64 bytesAvailable = io->bytesAvailable();
106     auto request = static_cast<ProtoRequestHttp *>(sock->protoData);
107 
108     Q_FOREVER {
109         if (!bytesAvailable ||
110                 !request->websocket_need ||
111                 (bytesAvailable < request->websocket_need && request->websocket_phase != ProtoRequestHttp::WebSocketPhasePayload)) {
112             // Need more data
113             return;
114         }
115 
116         quint32 maxlen = qMin(request->websocket_need, static_cast<quint32>(m_postBufferSize));
117         qint64 len = io->read(m_postBuffer, maxlen);
118         if (len == -1) {
119             qCWarning(CWSGI_WS) << "Failed to read from socket" << io->errorString();
120             sock->connectionClose();
121             return;
122         }
123         bytesAvailable -= len;
124 
125         switch(request->websocket_phase) {
126         case ProtoRequestHttp::WebSocketPhaseHeaders:
127             if (!websocket_parse_header(sock, m_postBuffer, io)) {
128                 return;
129             }
130             break;
131         case ProtoRequestHttp::WebSocketPhaseSize:
132             if (!websocket_parse_size(sock, m_postBuffer, m_websockets_max_size)) {
133                 return;
134             }
135             break;
136         case ProtoRequestHttp::WebSocketPhaseMask:
137             websocket_parse_mask(sock, m_postBuffer, io);
138             break;
139         case ProtoRequestHttp::WebSocketPhasePayload:
140             if (!websocket_parse_payload(sock, m_postBuffer, int(len), io)) {
141                 return;
142             }
143             break;
144         }
145     }
146 }
147 
createData(Socket * sock) const148 ProtocolData *ProtocolWebSocket::createData(Socket *sock) const
149 {
150     Q_UNUSED(sock)
151     return nullptr;
152 }
153 
send_text(Cutelyst::Context * c,Socket * sock,bool singleFrame) const154 bool ProtocolWebSocket::send_text(Cutelyst::Context *c, Socket *sock, bool singleFrame) const
155 {
156     Cutelyst::Request *request = c->request();
157     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
158 
159     const int msg_size = protoRequest->websocket_message.size();
160     protoRequest->websocket_message.append(protoRequest->websocket_payload);
161 
162     QByteArray payload = protoRequest->websocket_payload;
163     if (protoRequest->websocket_start_of_frame != msg_size) {
164         payload = protoRequest->websocket_message.mid(protoRequest->websocket_start_of_frame);
165     }
166 
167 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
168     QTextCodec::ConverterState state;
169     const QString frame = m_codec->toUnicode(payload.data(), payload.size(), &state);
170     const bool failed = state.invalidChars || state.remainingChars;
171 #else
172     auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
173     const QString frame = toUtf16(payload);
174     const bool failed = false;//FIXME
175 #endif
176     if (singleFrame && (failed || (frame.isEmpty() && payload.size()))) {
177         sock->connectionClose();
178         return false;
179     } else if (!failed) {
180         protoRequest->websocket_start_of_frame = protoRequest->websocket_message.size();
181         Q_EMIT request->webSocketTextFrame(frame,
182                                            protoRequest->websocket_finn_opcode & 0x80,
183                                            protoRequest->context);
184     }
185 
186     if (protoRequest->websocket_finn_opcode & 0x80) {
187         protoRequest->websocket_continue_opcode = 0;
188         if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
189             Q_EMIT request->webSocketTextMessage(frame, protoRequest->context);
190         } else {
191 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
192             QTextCodec::ConverterState stateMsg;
193             const QString msg = m_codec->toUnicode(protoRequest->websocket_message.data(), protoRequest->websocket_message.size(), &stateMsg);
194             const bool failed = stateMsg.invalidChars || stateMsg.remainingChars;
195 #else
196             auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
197             const QString msg = toUtf16(protoRequest->websocket_message);
198             const bool failed = false;//FIXME
199 #endif
200             if (failed) {
201                 sock->connectionClose();
202                 return false;
203             }
204             Q_EMIT request->webSocketTextMessage(msg, protoRequest->context);
205         }
206         protoRequest->websocket_message = QByteArray();
207         protoRequest->websocket_payload = QByteArray();
208     }
209 
210     return true;
211 }
212 
send_binary(Cutelyst::Context * c,Socket * sock,bool singleFrame) const213 void ProtocolWebSocket::send_binary(Cutelyst::Context *c, Socket *sock, bool singleFrame) const
214 {
215     Cutelyst::Request *request = c->request();
216     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
217 
218     protoRequest->websocket_message.append(protoRequest->websocket_payload);
219 
220     const QByteArray frame = protoRequest->websocket_payload;
221     Q_EMIT request->webSocketBinaryFrame(frame,
222                                          protoRequest->websocket_finn_opcode & 0x80,
223                                          protoRequest->context);
224 
225     if (protoRequest->websocket_finn_opcode & 0x80) {
226         protoRequest->websocket_continue_opcode = 0;
227         if (singleFrame || protoRequest->websocket_payload == protoRequest->websocket_message) {
228             Q_EMIT request->webSocketBinaryMessage(frame, protoRequest->context);
229         } else {
230             Q_EMIT request->webSocketBinaryMessage(protoRequest->websocket_message,
231                                                    protoRequest->context);
232         }
233         protoRequest->websocket_message = QByteArray();
234         protoRequest->websocket_payload = QByteArray();
235     }
236 }
237 
send_pong(QIODevice * io,const QByteArray data) const238 void ProtocolWebSocket::send_pong(QIODevice *io, const QByteArray data) const
239 {
240     io->write(ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePong, quint64(data.size())));
241     io->write(data);
242 }
243 
send_closed(Cutelyst::Context * c,Socket * sock,QIODevice * io) const244 void ProtocolWebSocket::send_closed(Cutelyst::Context *c, Socket *sock, QIODevice *io) const
245 {
246     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
247     quint16 closeCode = Cutelyst::Response::CloseCodeMissingStatusCode;
248     QString reason;
249 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
250     QTextCodec::ConverterState state;
251     const QString msg = m_codec->toUnicode(protoRequest->websocket_message.data(), protoRequest->websocket_message.size(), &state);
252     const bool failed = state.invalidChars || state.remainingChars;
253 #else
254     const bool failed = false;//FIXME
255 #endif
256 
257     if (protoRequest->websocket_payload.size() >= 2) {
258         closeCode = net_be16(protoRequest->websocket_payload.data());
259 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
260         reason = m_codec->toUnicode(protoRequest->websocket_payload.data() + 2, protoRequest->websocket_payload.size() - 2, &state);
261 #else
262         auto toUtf16 = QStringDecoder(QStringDecoder::Utf8);
263         reason = toUtf16(protoRequest->websocket_payload.mid(2));
264 #endif
265     }
266     Q_EMIT c->request()->webSocketClosed(closeCode, reason);
267 
268     if (failed) {
269         reason = QString();
270         closeCode = Cutelyst::Response::CloseCodeProtocolError;
271     } else if (closeCode < 3000 || closeCode > 4999) {
272         switch (closeCode) {
273         case Cutelyst::Response::CloseCodeNormal:
274         case Cutelyst::Response::CloseCodeGoingAway:
275         case Cutelyst::Response::CloseCodeProtocolError:
276         case Cutelyst::Response::CloseCodeDatatypeNotSupported:
277             //    case Cutelyst::Response::CloseCodeReserved1004:
278             break;
279         case Cutelyst::Response::CloseCodeMissingStatusCode:
280             if (protoRequest->websocket_payload.isEmpty()) {
281                 closeCode = Cutelyst::Response::CloseCodeNormal;
282             } else {
283                 closeCode = Cutelyst::Response::CloseCodeProtocolError;
284             }
285             break;
286             //    case Cutelyst::Response::CloseCodeAbnormalDisconnection:
287         case Cutelyst::Response::CloseCodeWrongDatatype:
288         case Cutelyst::Response::CloseCodePolicyViolated:
289         case Cutelyst::Response::CloseCodeTooMuchData:
290         case Cutelyst::Response::CloseCodeMissingExtension:
291         case Cutelyst::Response::CloseCodeBadOperation:
292             //    case Cutelyst::Response::CloseCodeTlsHandshakeFailed:
293             break;
294         default:
295             reason = QString();
296             closeCode = Cutelyst::Response::CloseCodeProtocolError;
297             break;
298         }
299     }
300 
301     const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, closeCode);
302     io->write(reply);
303 
304     sock->connectionClose();
305 }
306 
websocket_parse_header(Socket * sock,const char * buf,QIODevice * io) const307 bool ProtocolWebSocket::websocket_parse_header(Socket *sock, const char *buf, QIODevice *io) const
308 {
309     const char byte1 = buf[0];
310     const char byte2 = buf[1];
311 
312     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
313     protoRequest->websocket_finn_opcode = quint8(byte1);
314     protoRequest->websocket_payload_size = byte2 & 0x7f;
315 
316     quint8 opcode = byte1 & 0xf;
317 
318     bool websocket_has_mask = byte2 >> 7;
319     if (!websocket_has_mask ||
320             ((opcode == ProtoRequestHttp::OpCodePing || opcode == ProtoRequestHttp::OpCodeClose) && protoRequest->websocket_payload_size > 125) ||
321             (byte1 & 0x70) ||
322             ((opcode >= ProtoRequestHttp::OpCodeReserved3 && opcode <= ProtoRequestHttp::OpCodeReserved7) ||
323              (opcode >= ProtoRequestHttp::OpCodeReservedB && opcode <= ProtoRequestHttp::OpCodeReservedF)) ||
324             (!(byte1 & 0x80) && opcode != ProtoRequestHttp::OpCodeText && opcode != ProtoRequestHttp::OpCodeBinary && opcode != ProtoRequestHttp::OpCodeContinue) ||
325             (protoRequest->websocket_continue_opcode && (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary))) {
326         // RFC errors
327         // client to server MUST have a mask
328         // Control opcode cannot have payload bigger than 125
329         // RSV bytes MUST not be set
330         // reserved opcodes must not be set 3-7
331         // reserved opcodes must not be set B-F
332         // Only Text/Bynary/Coninue opcodes can be fragmented
333         // Continue opcode was set but was NOT followed by CONTINUE
334 
335         io->write(ProtocolWebSocket::createWebsocketCloseReply(QString(), 1002)); // Protocol error
336         sock->connectionClose();
337         return false;
338     }
339 
340     if (opcode == ProtoRequestHttp::OpCodeText || opcode == ProtoRequestHttp::OpCodeBinary) {
341         protoRequest->websocket_message = QByteArray();
342         protoRequest->websocket_start_of_frame = 0;
343         if (!(byte1 & 0x80)) {
344             // FINN byte not set, store opcode for continue
345             protoRequest->websocket_continue_opcode = opcode;
346         }
347     }
348 
349     if (protoRequest->websocket_payload_size == 126) {
350         protoRequest->websocket_need = 2;
351         protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseSize;
352     } else if (protoRequest->websocket_payload_size == 127) {
353         protoRequest->websocket_need = 8;
354         protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseSize;
355     } else {
356         protoRequest->websocket_need = 4;
357         protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
358     }
359 
360     return true;
361 }
362 
websocket_parse_size(Socket * sock,const char * buf,int websockets_max_message_size) const363 bool ProtocolWebSocket::websocket_parse_size(Socket *sock, const char *buf, int websockets_max_message_size) const
364 {
365     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
366     quint64 size;
367     if (protoRequest->websocket_payload_size == 126) {
368         size = net_be16(buf);
369     } else if (protoRequest->websocket_payload_size == 127) {
370         size = net_be64(buf);
371     } else {
372         qCCritical(CWSGI_WS) << "BUG error in websocket parser:" << protoRequest->websocket_payload_size;
373         sock->connectionClose();
374         return false;
375     }
376 
377     if (size > static_cast<quint64>(websockets_max_message_size)) {
378         qCCritical(CWSGI_WS) << "Payload size too big" << size << "max allowed" << websockets_max_message_size;
379         sock->connectionClose();
380         return false;
381     }
382     protoRequest->websocket_payload_size = size;
383 
384     protoRequest->websocket_need = 4;
385     protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseMask;
386 
387     return true;
388 }
389 
websocket_parse_mask(Socket * sock,char * buf,QIODevice * io) const390 void ProtocolWebSocket::websocket_parse_mask(Socket *sock, char *buf, QIODevice *io) const
391 {
392     auto ptr = reinterpret_cast<const quint32 *>(buf);
393     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
394     protoRequest->websocket_mask = *ptr;
395 
396     protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhasePayload;
397     protoRequest->websocket_need = quint32(protoRequest->websocket_payload_size);
398 
399     protoRequest->websocket_payload = QByteArray();
400     if (protoRequest->websocket_payload_size == 0) {
401         websocket_parse_payload(sock, buf, 0, io);
402     } else {
403         protoRequest->websocket_payload.reserve(int(protoRequest->websocket_payload_size));
404     }
405 }
406 
websocket_parse_payload(Socket * sock,char * buf,int len,QIODevice * io) const407 bool ProtocolWebSocket::websocket_parse_payload(Socket *sock, char *buf, int len, QIODevice *io) const
408 {
409     auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
410     auto mask = reinterpret_cast<quint8 *>(&protoRequest->websocket_mask);
411     for (int i = 0, maskIx = protoRequest->websocket_payload.size(); i < len; ++i, ++maskIx) {
412         buf[i] = buf[i] ^ mask[maskIx % 4];
413     }
414 
415     protoRequest->websocket_payload.append(buf, len);
416     if (quint64(protoRequest->websocket_payload.size()) < protoRequest->websocket_payload_size) {
417         // need more data
418         protoRequest->websocket_need -= uint(len);
419         return true;
420     }
421 
422     protoRequest->websocket_need = 2;
423     protoRequest->websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
424 
425     Cutelyst::Request *request = protoRequest->context->request();
426 
427     switch (protoRequest->websocket_finn_opcode & 0xf) {
428     case ProtoRequestHttp::OpCodeContinue:
429         switch (protoRequest->websocket_continue_opcode) {
430         case ProtoRequestHttp::OpCodeText:
431             if (!send_text(protoRequest->context, sock, false)) {
432                 return false;
433             }
434             break;
435         case ProtoRequestHttp::OpCodeBinary:
436             send_binary(protoRequest->context, sock, false);
437             break;
438         default:
439             qCCritical(CWSGI_WS) << "Invalid CONTINUE opcode:" << (protoRequest->websocket_finn_opcode & 0xf);
440             sock->connectionClose();
441             return false;
442         }
443         break;
444     case ProtoRequestHttp::OpCodeText:
445         if (!send_text(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80)) {
446             return false;
447         }
448         break;
449     case ProtoRequestHttp::OpCodeBinary:
450         send_binary(protoRequest->context, sock, protoRequest->websocket_finn_opcode & 0x80);
451         break;
452     case ProtoRequestHttp::OpCodeClose:
453         send_closed(protoRequest->context, sock, io);
454         return false;
455     case ProtoRequestHttp::OpCodePing:
456         send_pong(io, protoRequest->websocket_payload.left(125));
457         sock->flush();
458         break;
459     case ProtoRequestHttp::OpCodePong:
460         Q_EMIT request->webSocketPong(protoRequest->websocket_payload,
461                                       protoRequest->context);
462         break;
463     default:
464         break;
465     }
466 
467     return true;
468 }
469