cutelyst 5.1.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
protocolhttp.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2016-2018 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "protocolhttp.h"
6
7#include "protocolhttp2.h"
8#include "protocolwebsocket.h"
9#include "server.h"
10#include "socket.h"
11
12#include <Cutelyst/Context>
13#include <Cutelyst/Headers>
14#include <Cutelyst/Response>
15#include <typeinfo>
16
17#include <QBuffer>
18#include <QCoreApplication>
19#include <QCryptographicHash>
20#include <QEventLoop>
21#include <QIODevice>
22#include <QLoggingCategory>
23#include <QVariant>
24
25using namespace Cutelyst;
26using namespace Qt::Literals::StringLiterals;
27
28QByteArray http11StatusMessage(quint16 status);
29
30Q_LOGGING_CATEGORY(C_SERVER_HTTP, "cutelyst.server.http", QtWarningMsg)
31Q_DECLARE_LOGGING_CATEGORY(C_SERVER_SOCK)
32
33ProtocolHttp::ProtocolHttp(Server *server, ProtocolHttp2 *upgradeH2c)
34 : Protocol(server)
35 , m_websocketProto(new ProtocolWebSocket(server))
36 , m_upgradeH2c(upgradeH2c)
37{
38 usingFrontendProxy = server->usingFrontendProxy();
39}
40
41ProtocolHttp::~ProtocolHttp()
42{
43 delete m_websocketProto;
44}
45
46Protocol::Type ProtocolHttp::type() const
47{
48 return Protocol::Type::Http11;
49}
50
51inline int CrLfIndexIn(const char *str, int len, int from)
52{
53 do {
54 const char *pch = static_cast<const char *>(memchr(str + from, '\r', size_t(len - from)));
55 if (pch != nullptr) {
56 int pos = int(pch - str);
57 if ((pos + 1) < len) {
58 if (*++pch == '\n') {
59 return pos;
60 } else {
61 from = ++pos;
62 continue;
63 }
64 }
65 }
66 break;
67 } while (true);
68
69 return -1;
70}
71
72void ProtocolHttp::parse(Socket *sock, QIODevice *io) const
73{
74 // Post buffering
75 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
76 if (protoRequest->status & Cutelyst::EngineRequest::Async) {
77 return;
78 }
79
80 if (protoRequest->connState == ProtoRequestHttp::ContentBody) {
81 if (protoRequest->chunkPhase != ProtoRequestHttp::ChunkPhase::NotChunked) {
82 if (parseChunkedBody(sock, io)) {
83 processRequest(sock, io);
84 }
85 return;
86 }
87
88 qint64 bytesAvailable = io->bytesAvailable();
89 qint64 len;
90 qint64 remaining;
91
92 QIODevice *body = protoRequest->body;
93 do {
94 remaining = protoRequest->contentLength - body->size();
95 len = io->read(m_postBuffer, qMin(m_postBufferSize, remaining));
96 if (len == -1) {
97 qCWarning(C_SERVER_HTTP)
98 << "error while reading body" << len << protoRequest->headers;
99 sock->connectionClose();
100 return;
101 }
102 bytesAvailable -= len;
103 // qCDebug(C_SERVER_HTTP) << "WRITE body" << protoRequest->contentLength <<
104 // remaining << len << (remaining == len) << io->bytesAvailable();
105 body->write(m_postBuffer, len);
106 } while (bytesAvailable && remaining);
107
108 if (remaining == len) {
109 processRequest(sock, io);
110 }
111
112 return;
113 }
114
115 qint64 len = io->read(protoRequest->buffer + protoRequest->buf_size,
116 m_bufferSize - protoRequest->buf_size);
117 if (len == -1) {
118 qCWarning(C_SERVER_HTTP) << "Failed to read from socket" << io->errorString();
119 return;
120 }
121 protoRequest->buf_size += len;
122
123 while (protoRequest->last < protoRequest->buf_size) {
124 // qCDebug(C_SERVER_HTTP) << Q_FUNC_INFO << QByteArray(protoRequest->buffer,
125 // protoRequest->buf_size);
126 int ix = CrLfIndexIn(protoRequest->buffer, protoRequest->buf_size, protoRequest->last);
127 if (ix != -1) {
128 len = ix - protoRequest->beginLine;
129 char *ptr = protoRequest->buffer + protoRequest->beginLine;
130 protoRequest->beginLine = ix + 2;
131 protoRequest->last = protoRequest->beginLine;
132
133 if (protoRequest->connState == ProtoRequestHttp::MethodLine) {
134 if (useStats && protoRequest->startOfRequest == TimePointSteady{}) {
135 protoRequest->startOfRequest = std::chrono::steady_clock::now();
136 }
137
138 parseMethod(ptr, ptr + len, sock);
139 protoRequest->connState = ProtoRequestHttp::HeaderLine;
140 protoRequest->contentLength = -1;
141 protoRequest->headers = Cutelyst::Headers();
142 // qCDebug(C_SERVER_HTTP) << "--------" << protoRequest->method <<
143 // protoRequest->path << protoRequest->query <<
144 // protoRequest->protocol;
145
146 } else if (protoRequest->connState == ProtoRequestHttp::HeaderLine) {
147 if (len) {
148 parseHeader(ptr, ptr + len, sock);
149 } else {
150 if (protoRequest->chunkPhase == ProtoRequestHttp::ChunkPhase::Size) {
151 protoRequest->connState = ProtoRequestHttp::ContentBody;
152 protoRequest->body = createBody(0);
153 if (!protoRequest->body) {
154 qCWarning(C_SERVER_HTTP) << "error while creating body, closing socket";
155 sock->connectionClose();
156 return;
157 }
158
159 // Move any bytes already read past the header CRLF into the
160 // start of the buffer so the chunked parser can consume them.
161 const int leftover = protoRequest->buf_size - protoRequest->last;
162 if (leftover > 0) {
163 memmove(protoRequest->buffer,
164 protoRequest->buffer + protoRequest->last,
165 size_t(leftover));
166 }
167 protoRequest->buf_size = leftover;
168 protoRequest->last = 0;
169 protoRequest->beginLine = 0;
170
171 if (parseChunkedBody(sock, io)) {
172 if (!processRequest(sock, io)) {
173 break;
174 }
175 }
176 return;
177 }
178
179 if (protoRequest->contentLength > 0) {
180 protoRequest->connState = ProtoRequestHttp::ContentBody;
181 protoRequest->body = createBody(protoRequest->contentLength);
182 if (!protoRequest->body) {
183 qCWarning(C_SERVER_HTTP) << "error while creating body, closing socket";
184 sock->connectionClose();
185 return;
186 }
187
188 ptr += 2;
189 len =
190 qMin(protoRequest->contentLength,
191 static_cast<qint64>(protoRequest->buf_size - protoRequest->last));
192 // qCDebug(C_SERVER_HTTP) << "WRITE" <<
193 // protoRequest->contentLength << len;
194 if (len) {
195 protoRequest->body->write(ptr, len);
196 }
197 protoRequest->last += len;
198
199 if (protoRequest->contentLength > len) {
200 // qCDebug(C_SERVER_HTTP) << "WRITE more..."
201 // << protoRequest->contentLength << len;
202 // body is not completed yet
203 if (io->bytesAvailable()) {
204 // since we still have bytes available call this function
205 // so that the body parser reads the rest of available data
206 parse(sock, io);
207 }
208 return;
209 }
210 }
211
212 if (!processRequest(sock, io)) {
213 break;
214 }
215 }
216 }
217 } else {
218 if (protoRequest->startOfRequest == TimePointSteady{}) {
219 protoRequest->startOfRequest = std::chrono::steady_clock::now();
220 }
221 // Keep a trailing CR in the search window so a following LF still forms CRLF,
222 // then leave the loop to wait for more data (avoid spinning on the same CR).
223 if (protoRequest->buf_size > 0 &&
224 protoRequest->buffer[protoRequest->buf_size - 1] == '\r') {
225 protoRequest->last = protoRequest->buf_size - 1;
226 } else {
227 protoRequest->last = protoRequest->buf_size;
228 }
229 break;
230 }
231 }
232
233 if (protoRequest->buf_size == m_bufferSize) {
234 // 414 Request-URI Too Long
235 }
236}
237
238ProtocolData *ProtocolHttp::createData(Socket *sock) const
239{
240 return new ProtoRequestHttp(sock, m_bufferSize);
241}
242
243bool ProtocolHttp::parseChunkedBody(Socket *sock, QIODevice *io) const
244{
245 auto req = static_cast<ProtoRequestHttp *>(sock->protoData);
246
247 while (true) {
248 if (io->bytesAvailable() && req->buf_size < m_bufferSize) {
249 const qint64 n = io->read(req->buffer + req->buf_size, m_bufferSize - req->buf_size);
250 if (n == -1) {
251 qCWarning(C_SERVER_HTTP) << "Failed to read chunked body" << io->errorString();
252 sock->connectionClose();
253 return false;
254 }
255 req->buf_size += int(n);
256 }
257
258 switch (req->chunkPhase) {
259 case ProtoRequestHttp::ChunkPhase::Size:
260 {
261 const int ix = CrLfIndexIn(req->buffer, req->buf_size, 0);
262 if (ix < 0) {
263 if (req->buf_size == m_bufferSize) {
264 qCWarning(C_SERVER_HTTP) << "chunk size line too long";
265 sock->connectionClose();
266 }
267 return false;
268 }
269
270 // Allow chunk extensions: size[;ext...]
271 int sizeEnd = 0;
272 while (sizeEnd < ix) {
273 const char c = req->buffer[sizeEnd];
274 if (c == ';' || c == ' ') {
275 break;
276 }
277 ++sizeEnd;
278 }
279
280 bool ok = false;
281 const qint64 chunkSize =
282 QByteArray::fromRawData(req->buffer, sizeEnd).toLongLong(&ok, 16);
283 if (!ok || chunkSize < 0) {
284 qCWarning(C_SERVER_HTTP) << "invalid chunk size";
285 sock->connectionClose();
286 return false;
287 }
288
289 const int consume = ix + 2;
290 memmove(req->buffer, req->buffer + consume, size_t(req->buf_size - consume));
291 req->buf_size -= consume;
292
293 if (chunkSize == 0) {
294 req->chunkPhase = ProtoRequestHttp::ChunkPhase::Trailers;
295 break;
296 }
297
298 req->chunkBytesLeft = chunkSize;
299 req->chunkPhase = ProtoRequestHttp::ChunkPhase::Data;
300 break;
301 }
302 case ProtoRequestHttp::ChunkPhase::Data:
303 {
304 if (req->buf_size == 0) {
305 return false;
306 }
307 const qint64 take = qMin(req->chunkBytesLeft, static_cast<qint64>(req->buf_size));
308 if (req->body->write(req->buffer, take) != take) {
309 qCWarning(C_SERVER_HTTP) << "failed writing chunk data";
310 sock->connectionClose();
311 return false;
312 }
313 memmove(req->buffer, req->buffer + take, size_t(req->buf_size - take));
314 req->buf_size -= int(take);
315 req->chunkBytesLeft -= take;
316 if (req->chunkBytesLeft == 0) {
317 req->chunkPhase = ProtoRequestHttp::ChunkPhase::DataCrLf;
318 }
319 break;
320 }
321 case ProtoRequestHttp::ChunkPhase::DataCrLf:
322 {
323 if (req->buf_size < 2) {
324 return false;
325 }
326 if (req->buffer[0] != '\r' || req->buffer[1] != '\n') {
327 qCWarning(C_SERVER_HTTP) << "missing CRLF after chunk data";
328 sock->connectionClose();
329 return false;
330 }
331 memmove(req->buffer, req->buffer + 2, size_t(req->buf_size - 2));
332 req->buf_size -= 2;
333 req->chunkPhase = ProtoRequestHttp::ChunkPhase::Size;
334 break;
335 }
336 case ProtoRequestHttp::ChunkPhase::Trailers:
337 {
338 const int ix = CrLfIndexIn(req->buffer, req->buf_size, 0);
339 if (ix < 0) {
340 return false;
341 }
342 if (ix == 0) {
343 // Empty trailer line — body complete.
344 memmove(req->buffer, req->buffer + 2, size_t(req->buf_size - 2));
345 req->buf_size -= 2;
346 req->chunkPhase = ProtoRequestHttp::ChunkPhase::NotChunked;
347 req->last = 0;
348 req->beginLine = 0;
349 return true;
350 }
351 // Skip trailer field line
352 memmove(req->buffer, req->buffer + ix + 2, size_t(req->buf_size - (ix + 2)));
353 req->buf_size -= (ix + 2);
354 break;
355 }
356 case ProtoRequestHttp::ChunkPhase::NotChunked:
357 return true;
358 }
359 }
360}
361
362bool ProtocolHttp::processRequest(Socket *sock, QIODevice *io) const
363{
364 auto request = static_cast<ProtoRequestHttp *>(sock->protoData);
365 // qCDebug(C_SERVER_HTTP) << "processRequest" << sock->protoData->contentLength;
366 if (request->body) {
367 request->body->seek(0);
368 }
369
370 // When enabled try to upgrade to H2C
371 if (m_upgradeH2c && m_upgradeH2c->upgradeH2C(sock, io, *request)) {
372 return false;
373 }
374
375 ++sock->processing;
376 sock->engine->processRequest(request);
377
378 if (request->websocketUpgraded) {
379 return false; // Must read remaining data
380 }
381
382 if (request->status & Cutelyst::EngineRequest::Async) {
383 return false; // Need to break now
384 }
385
386 return true;
387}
388
389void ProtocolHttp::parseMethod(const char *ptr, const char *end, Socket *sock) const
390{
391 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
392 const char *word_boundary = ptr;
393 while (*word_boundary != ' ' && word_boundary < end) {
394 ++word_boundary;
395 }
396 protoRequest->method = QByteArray(ptr, int(word_boundary - ptr));
397
398 // skip spaces
399 while (*word_boundary == ' ' && word_boundary < end) {
400 ++word_boundary;
401 }
402 ptr = word_boundary;
403
404 // find path end
405 while (*word_boundary != ' ' && *word_boundary != '?' && word_boundary < end) {
406 ++word_boundary;
407 }
408
409 // This will change the ptr but will only change less than size
410 protoRequest->setPath(const_cast<char *>(ptr), int(word_boundary - ptr));
411
412 if (*word_boundary == '?') {
413 ptr = word_boundary + 1;
414 while (*word_boundary != ' ' && word_boundary < end) {
415 ++word_boundary;
416 }
417 protoRequest->query = QByteArray(ptr, int(word_boundary - ptr));
418 } else {
419 protoRequest->query = QByteArray();
420 }
421
422 // skip spaces
423 while (*word_boundary == ' ' && word_boundary < end) {
424 ++word_boundary;
425 }
426 ptr = word_boundary;
427
428 while (*word_boundary != ' ' && word_boundary < end) {
429 ++word_boundary;
430 }
431 protoRequest->protocol = QByteArray(ptr, int(word_boundary - ptr));
432}
433
434void ProtocolHttp::parseHeader(const char *ptr, const char *end, Socket *sock) const
435{
436 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
437 const char *word_boundary = ptr;
438 while (*word_boundary != ':' && word_boundary < end) {
439 ++word_boundary;
440 }
441 const auto key = QByteArray(ptr, int(word_boundary - ptr));
442
443 while ((*word_boundary == ':' || *word_boundary == ' ') && word_boundary < end) {
444 ++word_boundary;
445 }
446 const auto value = QByteArray(word_boundary, int(end - word_boundary));
447
448 if (protoRequest->headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
449 key.compare("Connection", Qt::CaseInsensitive) == 0) {
450 if (value.compare("close", Qt::CaseInsensitive) == 0) {
451 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Close;
452 } else {
453 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
454 }
455 } else if (protoRequest->contentLength < 0 &&
456 key.compare("Content-Length", Qt::CaseInsensitive) == 0) {
457 bool ok;
458 qint64 cl = value.toLongLong(&ok);
459 if (ok && cl >= 0) {
460 protoRequest->contentLength = cl;
461 }
462 } else if (protoRequest->chunkPhase == ProtoRequestHttp::ChunkPhase::NotChunked &&
463 key.compare("Transfer-Encoding", Qt::CaseInsensitive) == 0) {
464 // RFC 9112: if Transfer-Encoding includes chunked, Content-Length is ignored.
465 if (value.toLower().contains("chunked"_ba)) {
466 protoRequest->chunkPhase = ProtoRequestHttp::ChunkPhase::Size;
467 protoRequest->contentLength = -1;
468 }
469 } else if (!protoRequest->headerHost && key.compare("Host", Qt::CaseInsensitive) == 0) {
470 protoRequest->serverAddress = value;
471 protoRequest->headerHost = true;
472 } else if (usingFrontendProxy) {
473 if (!protoRequest->X_Forwarded_For &&
474 (key.compare("X-Forwarded-For", Qt::CaseInsensitive) == 0 ||
475 key.compare("X-Real-Ip", Qt::CaseInsensitive) == 0)) {
476 // configure your reverse-proxy to list only one IP address
477 protoRequest->remoteAddress.setAddress(QString::fromLatin1(value));
478 protoRequest->remotePort = 0; // unknown
479 protoRequest->X_Forwarded_For = true;
480 } else if (!protoRequest->X_Forwarded_Host &&
481 key.compare("X-Forwarded-Host", Qt::CaseInsensitive) == 0) {
482 protoRequest->serverAddress = value;
483 protoRequest->X_Forwarded_Host = true;
484 protoRequest->headerHost = true; // ignore a following Host: header (if any)
485 } else if (!protoRequest->X_Forwarded_Proto &&
486 key.compare("X-Forwarded-Proto", Qt::CaseInsensitive) == 0) {
487 protoRequest->isSecure = (value.compare("https") == 0);
488 protoRequest->X_Forwarded_Proto = true;
489 }
490 }
491 protoRequest->headers.pushHeader(key, value);
492}
493
494ProtoRequestHttp::ProtoRequestHttp(Socket *sock, int bufferSize)
495 : ProtocolData(sock, bufferSize)
496{
497 isSecure = sock->isSecure;
498}
499
500ProtoRequestHttp::~ProtoRequestHttp()
501{
502}
503
504void ProtoRequestHttp::setupNewConnection(Socket *sock)
505{
506 serverAddress = sock->serverAddress;
507 remoteAddress = sock->remoteAddress;
508 remotePort = sock->remotePort;
509}
510
511bool ProtoRequestHttp::writeHeaders(quint16 status, const Cutelyst::Headers &headers)
512{
513 if (websocketUpgraded && status != Cutelyst::Response::SwitchingProtocols) {
514 qCWarning(C_SERVER_SOCK) << "Trying to write header while on an Websocket context";
515 return false;
516 }
517
518 QByteArray data = http11StatusMessage(status);
519
520 const auto headersData = headers.data();
521 ProtoRequestHttp::HeaderConnection fallbackConnection = headerConnection;
522 headerConnection = ProtoRequestHttp::HeaderConnection::NotSet;
523
524 bool hasDate = false;
525 for (const auto &[key, value] : headersData) {
526 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
527 key.compare("Connection", Qt::CaseInsensitive) == 0) {
528 if (value.compare("close") == 0) {
529 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
530 } else if (value.compare("Upgrade") == 0) {
531 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
532 } else {
533 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
534 }
535 } else if (!hasDate && key.compare("Date", Qt::CaseInsensitive) == 0) {
536 hasDate = true;
537 }
538
539 data.append("\r\n");
540 data.append(key);
541 data.append(": ");
542 data.append(value);
543 }
544
545 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet) {
546 if (fallbackConnection == ProtoRequestHttp::HeaderConnection::Keep ||
547 (fallbackConnection != ProtoRequestHttp::HeaderConnection::Close &&
548 protocol.compare("HTTP/1.1") == 0)) {
549 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
550 data.append("\r\nConnection: keep-alive", 24);
551 } else {
552 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
553 data.append("\r\nConnection: close", 19);
554 }
555 }
556
557 if (!hasDate) {
558 data.append(static_cast<ServerEngine *>(sock->engine)->lastDate());
559 }
560 data.append("\r\n\r\n", 4);
561
562 return io->write(data) == data.size();
563}
564
565qint64 ProtoRequestHttp::doWrite(const char *data, qint64 len)
566{
567 return io->write(data, len);
568}
569
571{
572 if (websocketUpgraded) {
573 // need 2 byte header
574 websocket_need = 2;
575 websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseHeaders;
576 buf_size = 0;
577 return;
578 }
579
580 if (!sock->requestFinished()) {
581 // disconnected
582 return;
583 }
584
585 if (headerConnection == ProtoRequestHttp::HeaderConnection::Close) {
586 sock->connectionClose();
587 return;
588 }
589
590 if (last < buf_size) {
591 // move pipelined request to 0
592 int remaining = buf_size - last;
593 memmove(buffer, buffer + last, size_t(remaining));
594 resetData();
595 buf_size = remaining;
596
597 if (status & EngineRequest::Async) {
598 sock->proto->parse(sock, io);
599 }
600 } else {
601 resetData();
602 }
603}
604
605bool ProtoRequestHttp::webSocketSendTextMessage(const QString &message)
606{
607 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
608 qCWarning(C_SERVER_HTTP)
609 << "Not sending websocket text message due connection header not upgraded"
610 << headerConnection << message.size();
611 return false;
612 }
613
614 const QByteArray rawMessage = message.toUtf8();
615 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(
616 ProtoRequestHttp::OpCodeText, quint64(rawMessage.size()));
617 return doWrite(headers) == headers.size() && doWrite(rawMessage) == rawMessage.size();
618}
619
620bool ProtoRequestHttp::webSocketSendBinaryMessage(const QByteArray &message)
621{
622 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
623 qCWarning(C_SERVER_HTTP)
624 << "Not sending websocket binary messagedue connection header not upgraded"
625 << headerConnection << message.size();
626 return false;
627 }
628
629 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(
630 ProtoRequestHttp::OpCodeBinary, quint64(message.size()));
631 return doWrite(headers) == headers.size() && doWrite(message) == message.size();
632}
633
634bool ProtoRequestHttp::webSocketSendPing(const QByteArray &payload)
635{
636 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
637 qCWarning(C_SERVER_HTTP) << "Not sending websocket ping due connection header not upgraded"
638 << headerConnection << payload.size();
639 return false;
640 }
641
642 const QByteArray rawMessage = payload.left(125);
643 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(
644 ProtoRequestHttp::OpCodePing, quint64(rawMessage.size()));
645 return doWrite(headers) == headers.size() && doWrite(rawMessage) == rawMessage.size();
646}
647
648bool ProtoRequestHttp::webSocketClose(quint16 code, const QString &reason)
649{
650 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
651 qCWarning(C_SERVER_HTTP) << "Not sending websocket close due connection header not upgraded"
652 << headerConnection << code << reason;
653 return false;
654 }
655
656 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, code);
657 bool ret = doWrite(reply) == reply.size();
658 sock->requestFinished();
659 sock->connectionClose();
660 return ret;
661}
662
663void ProtoRequestHttp::socketDisconnected()
664{
665 if (websocketUpgraded) {
666 if (websocket_finn_opcode != 0x88) {
667 Q_EMIT context->request()->webSocketClosed(1005, QString{});
668 }
669 sock->requestFinished();
670 }
671}
672
673bool ProtoRequestHttp::webSocketHandshakeDo(const QByteArray &key,
674 const QByteArray &origin,
675 const QByteArray &protocol)
676{
677 if (headerConnection == ProtoRequestHttp::HeaderConnection::Upgrade) {
678 return true;
679 }
680
681 if (sock->proto->type() != Protocol::Type::Http11) {
682 qCWarning(C_SERVER_SOCK)
683 << "Upgrading a connection to websocket is only supported with the HTTP/1.1 protocol"
684 << typeid(sock->proto).name();
685 return false;
686 }
687
688 const Cutelyst::Headers requestHeaders = context->request()->headers();
689 Cutelyst::Response *response = context->response();
690 Cutelyst::Headers &headers = response->headers();
691
692 response->setStatus(Cutelyst::Response::SwitchingProtocols);
693 headers.setHeader("Upgrade"_ba, "WebSocket"_ba);
694 headers.setHeader("Connection"_ba, "Upgrade"_ba);
695 const auto localOrigin = origin.isEmpty() ? requestHeaders.header("Origin") : origin;
696 headers.setHeader("Sec-Websocket-Origin"_ba, localOrigin.isEmpty() ? "*"_ba : localOrigin);
697
698 if (!protocol.isEmpty()) {
699 headers.setHeader("Sec-Websocket-Protocol"_ba, protocol);
700 } else if (const auto wsProtocol = requestHeaders.header("Sec-Websocket-Protocol");
701 !wsProtocol.isEmpty()) {
702 headers.setHeader("Sec-Websocket-Protocol"_ba, wsProtocol);
703 }
704
705 const QByteArray localKey = key.isEmpty() ? requestHeaders.header("Sec-Websocket-Key") : key;
706 const QByteArray wsKey = localKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
707 if (wsKey.length() == 36) {
708 qCWarning(C_SERVER_SOCK) << "Missing websocket key";
709 return false;
710 }
711
712 const QByteArray wsAccept =
714 headers.setHeader("Sec-Websocket-Accept"_ba, wsAccept);
715
716 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
717 websocketUpgraded = true;
718 const auto *httpProto = static_cast<ProtocolHttp *>(sock->proto);
719 sock->proto = httpProto->m_websocketProto;
720
721 return writeHeaders(Cutelyst::Response::SwitchingProtocols, headers);
722}
723
724QByteArray http11StatusMessage(quint16 status)
725{
726 QByteArray ret;
727 switch (status) {
728 case Response::OK:
729 ret = QByteArrayLiteral("HTTP/1.1 200 OK");
730 break;
731 case Response::Found:
732 ret = QByteArrayLiteral("HTTP/1.1 302 Found");
733 break;
734 case Response::NotFound:
735 ret = QByteArrayLiteral("HTTP/1.1 404 Not Found");
736 break;
737 case Response::InternalServerError:
738 ret = QByteArrayLiteral("HTTP/1.1 500 Internal Server Error");
739 break;
740 case Response::MovedPermanently:
741 ret = QByteArrayLiteral("HTTP/1.1 301 Moved Permanently");
742 break;
743 case Response::NotModified:
744 ret = QByteArrayLiteral("HTTP/1.1 304 Not Modified");
745 break;
746 case Response::SeeOther:
747 ret = QByteArrayLiteral("HTTP/1.1 303 See Other");
748 break;
749 case Response::Forbidden:
750 ret = QByteArrayLiteral("HTTP/1.1 403 Forbidden");
751 break;
752 case Response::TemporaryRedirect:
753 ret = QByteArrayLiteral("HTTP/1.1 307 Temporary Redirect");
754 break;
755 case Response::Unauthorized:
756 ret = QByteArrayLiteral("HTTP/1.1 401 Unauthorized");
757 break;
758 case Response::BadRequest:
759 ret = QByteArrayLiteral("HTTP/1.1 400 Bad Request");
760 break;
761 case Response::MethodNotAllowed:
762 ret = QByteArrayLiteral("HTTP/1.1 405 Method Not Allowed");
763 break;
764 case Response::RequestTimeout:
765 ret = QByteArrayLiteral("HTTP/1.1 408 Request Timeout");
766 break;
767 case Response::Continue:
768 ret = QByteArrayLiteral("HTTP/1.1 100 Continue");
769 break;
770 case Response::SwitchingProtocols:
771 ret = QByteArrayLiteral("HTTP/1.1 101 Switching Protocols");
772 break;
773 case Response::Created:
774 ret = QByteArrayLiteral("HTTP/1.1 201 Created");
775 break;
776 case Response::Accepted:
777 ret = QByteArrayLiteral("HTTP/1.1 202 Accepted");
778 break;
779 case Response::NonAuthoritativeInformation:
780 ret = QByteArrayLiteral("HTTP/1.1 203 Non-Authoritative Information");
781 break;
782 case Response::NoContent:
783 ret = QByteArrayLiteral("HTTP/1.1 204 No Content");
784 break;
785 case Response::ResetContent:
786 ret = QByteArrayLiteral("HTTP/1.1 205 Reset Content");
787 break;
788 case Response::PartialContent:
789 ret = QByteArrayLiteral("HTTP/1.1 206 Partial Content");
790 break;
791 case Response::MultipleChoices:
792 ret = QByteArrayLiteral("HTTP/1.1 300 Multiple Choices");
793 break;
794 case Response::UseProxy:
795 ret = QByteArrayLiteral("HTTP/1.1 305 Use Proxy");
796 break;
797 case Response::PaymentRequired:
798 ret = QByteArrayLiteral("HTTP/1.1 402 Payment Required");
799 break;
800 case Response::NotAcceptable:
801 ret = QByteArrayLiteral("HTTP/1.1 406 Not Acceptable");
802 break;
803 case Response::ProxyAuthenticationRequired:
804 ret = QByteArrayLiteral("HTTP/1.1 407 Proxy Authentication Required");
805 break;
806 case Response::Conflict:
807 ret = QByteArrayLiteral("HTTP/1.1 409 Conflict");
808 break;
809 case Response::Gone:
810 ret = QByteArrayLiteral("HTTP/1.1 410 Gone");
811 break;
812 case Response::LengthRequired:
813 ret = QByteArrayLiteral("HTTP/1.1 411 Length Required");
814 break;
815 case Response::PreconditionFailed:
816 ret = QByteArrayLiteral("HTTP/1.1 412 Precondition Failed");
817 break;
818 case Response::RequestEntityTooLarge:
819 ret = QByteArrayLiteral("HTTP/1.1 413 Request Entity Too Large");
820 break;
821 case Response::RequestURITooLong:
822 ret = QByteArrayLiteral("HTTP/1.1 414 Request-URI Too Long");
823 break;
824 case Response::UnsupportedMediaType:
825 ret = QByteArrayLiteral("HTTP/1.1 415 Unsupported Media Type");
826 break;
827 case Response::RequestedRangeNotSatisfiable:
828 ret = QByteArrayLiteral("HTTP/1.1 416 Requested Range Not Satisfiable");
829 break;
830 case Response::ExpectationFailed:
831 ret = QByteArrayLiteral("HTTP/1.1 417 Expectation Failed");
832 break;
833 case Response::NotImplemented:
834 ret = QByteArrayLiteral("HTTP/1.1 501 Not Implemented");
835 break;
836 case Response::BadGateway:
837 ret = QByteArrayLiteral("HTTP/1.1 502 Bad Gateway");
838 break;
839 case Response::ServiceUnavailable:
840 ret = QByteArrayLiteral("HTTP/1.1 503 Service Unavailable");
841 break;
842 case Response::MultiStatus:
843 ret = QByteArrayLiteral("HTTP/1.1 207 Multi-Status");
844 break;
845 case Response::GatewayTimeout:
846 ret = QByteArrayLiteral("HTTP/1.1 504 Gateway Timeout");
847 break;
848 case Response::HTTPVersionNotSupported:
849 ret = QByteArrayLiteral("HTTP/1.1 505 HTTP Version Not Supported");
850 break;
851 case Response::BandwidthLimitExceeded:
852 ret = QByteArrayLiteral("HTTP/1.1 509 Bandwidth Limit Exceeded");
853 break;
854 default:
855 ret = QByteArrayLiteral("HTTP/1.1 ").append(QByteArray::number(status));
856 break;
857 }
858
859 return ret;
860}
861
862#include "moc_protocolhttp.cpp"
Request * request
Definition context.h:71
Response * response() const noexcept
Definition context.cpp:98
void processRequest(EngineRequest *request)
Definition engine.cpp:110
Container for HTTP headers.
Definition headers.h:24
QVector< HeaderKeyValue > data() const
Definition headers.h:420
QByteArray header(QAnyStringView key) const noexcept
Definition headers.cpp:416
void setHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:463
bool writeHeaders(quint16 status, const Cutelyst::Headers &headers) override final
qint64 doWrite(const char *data, qint64 len) override final
void processingFinished() override final
void webSocketClosed(quint16 closeCode, const QString &reason)
Emitted when the websocket receives a close frame, including a close code and a reason,...
Headers headers() const noexcept
Definition request.cpp:312
A Cutelyst response.
Definition response.h:29
void setStatus(quint16 status) noexcept
Definition response.cpp:74
Headers & headers() noexcept
Implements a web server.
Definition server.h:60
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
QByteArray number(double n, char format, int precision)
qsizetype size() const const
QByteArray toBase64(QByteArray::Base64Options options) const const
qlonglong toLongLong(bool *ok, int base) const const
QByteArray hash(QByteArrayView data, QCryptographicHash::Algorithm method)
virtual qint64 bytesAvailable() const const
QString errorString() const const
QByteArray read(qint64 maxSize)
virtual bool seek(qint64 pos)
virtual qint64 size() const const
qint64 write(const QByteArray &data)
QString fromLatin1(QByteArrayView str)
qsizetype size() const const
QByteArray toUtf8() const const
CaseInsensitive