5#include "protocolhttp.h"
7#include "protocolhttp2.h"
8#include "protocolwebsocket.h"
12#include <Cutelyst/Context>
13#include <Cutelyst/Headers>
14#include <Cutelyst/Response>
18#include <QCoreApplication>
19#include <QCryptographicHash>
22#include <QLoggingCategory>
30Q_LOGGING_CATEGORY(C_SERVER_HTTP,
"cutelyst.server.http", QtWarningMsg)
31Q_DECLARE_LOGGING_CATEGORY(C_SERVER_SOCK)
36 , m_upgradeH2c(upgradeH2c)
38 usingFrontendProxy = wsgi->usingFrontendProxy();
41ProtocolHttp::~ProtocolHttp()
43 delete m_websocketProto;
46Protocol::Type ProtocolHttp::type()
const
48 return Protocol::Type::Http11;
51inline int CrLfIndexIn(
const char *str,
int len,
int from)
54 const char *pch =
static_cast<const char *
>(memchr(str + from,
'\r',
size_t(len - from)));
56 int pos = int(pch - str);
57 if ((pos + 1) < len) {
76 if (protoRequest->status & Cutelyst::EngineRequest::Async) {
80 if (protoRequest->connState == ProtoRequestHttp::ContentBody) {
87 remaining = protoRequest->contentLength - body->
size();
88 len = io->
read(m_postBuffer, qMin(m_postBufferSize, remaining));
90 qCWarning(C_SERVER_HTTP)
91 <<
"error while reading body" << len << protoRequest->headers;
92 sock->connectionClose();
95 bytesAvailable -= len;
98 body->
write(m_postBuffer, len);
99 }
while (bytesAvailable && remaining);
101 if (remaining == len) {
102 processRequest(sock, io);
108 qint64 len = io->
read(protoRequest->buffer + protoRequest->buf_size,
109 m_bufferSize - protoRequest->buf_size);
111 qCWarning(C_SERVER_HTTP) <<
"Failed to read from socket" << io->
errorString();
114 protoRequest->buf_size += len;
116 while (protoRequest->last < protoRequest->buf_size) {
119 int ix = CrLfIndexIn(protoRequest->buffer, protoRequest->buf_size, protoRequest->last);
121 qint64 len = ix - protoRequest->beginLine;
122 char *ptr = protoRequest->buffer + protoRequest->beginLine;
123 protoRequest->beginLine = ix + 2;
124 protoRequest->last = protoRequest->beginLine;
126 if (protoRequest->connState == ProtoRequestHttp::MethodLine) {
127 if (useStats && protoRequest->startOfRequest == TimePointSteady{}) {
128 protoRequest->startOfRequest = std::chrono::steady_clock::now();
131 parseMethod(ptr, ptr + len, sock);
132 protoRequest->connState = ProtoRequestHttp::HeaderLine;
133 protoRequest->contentLength = -1;
139 }
else if (protoRequest->connState == ProtoRequestHttp::HeaderLine) {
141 parseHeader(ptr, ptr + len, sock);
143 if (protoRequest->contentLength > 0) {
144 protoRequest->connState = ProtoRequestHttp::ContentBody;
145 protoRequest->body = createBody(protoRequest->contentLength);
146 if (!protoRequest->body) {
147 qCWarning(C_SERVER_HTTP) <<
"error while creating body, closing socket";
148 sock->connectionClose();
154 qMin(protoRequest->contentLength,
155 static_cast<qint64
>(protoRequest->buf_size - protoRequest->last));
159 protoRequest->body->write(ptr, len);
161 protoRequest->last += len;
163 if (protoRequest->contentLength > len) {
176 if (!processRequest(sock, io)) {
182 if (protoRequest->startOfRequest == TimePointSteady{}) {
183 protoRequest->startOfRequest = std::chrono::steady_clock::now();
185 protoRequest->last = protoRequest->buf_size;
189 if (protoRequest->buf_size == m_bufferSize) {
208 if (m_upgradeH2c && m_upgradeH2c->upgradeH2C(sock, io, *request)) {
215 if (request->websocketUpgraded) {
219 if (request->status & Cutelyst::EngineRequest::Async) {
226void ProtocolHttp::parseMethod(
const char *ptr,
const char *end,
Socket *sock)
const
229 const char *word_boundary = ptr;
230 while (*word_boundary !=
' ' && word_boundary < end) {
236 while (*word_boundary ==
' ' && word_boundary < end) {
242 while (*word_boundary !=
' ' && *word_boundary !=
'?' && word_boundary < end) {
247 protoRequest->setPath(
const_cast<char *
>(ptr),
int(word_boundary - ptr));
249 if (*word_boundary ==
'?') {
250 ptr = word_boundary + 1;
251 while (*word_boundary !=
' ' && word_boundary < end) {
254 protoRequest->query =
QByteArray(ptr,
int(word_boundary - ptr));
260 while (*word_boundary ==
' ' && word_boundary < end) {
265 while (*word_boundary !=
' ' && word_boundary < end) {
268 protoRequest->protocol =
QByteArray(ptr,
int(word_boundary - ptr));
271void ProtocolHttp::parseHeader(
const char *ptr,
const char *end,
Socket *sock)
const
274 const char *word_boundary = ptr;
275 while (*word_boundary !=
':' && word_boundary < end) {
278 const auto key =
QByteArray(ptr,
int(word_boundary - ptr));
280 while ((*word_boundary ==
':' || *word_boundary ==
' ') && word_boundary < end) {
283 const auto value =
QByteArray(word_boundary,
int(end - word_boundary));
285 if (protoRequest->headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
288 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Close;
290 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
292 }
else if (protoRequest->contentLength < 0 &&
295 qint64 cl = value.toLongLong(&ok);
297 protoRequest->contentLength = cl;
300 protoRequest->serverAddress = value;
301 protoRequest->headerHost =
true;
302 }
else if (usingFrontendProxy) {
303 if (!protoRequest->X_Forwarded_For &&
308 protoRequest->remotePort = 0;
309 protoRequest->X_Forwarded_For =
true;
310 }
else if (!protoRequest->X_Forwarded_Host &&
312 protoRequest->serverAddress = value;
313 protoRequest->X_Forwarded_Host =
true;
314 protoRequest->headerHost =
true;
315 }
else if (!protoRequest->X_Forwarded_Proto &&
317 protoRequest->isSecure = (value.compare(
"https") == 0);
318 protoRequest->X_Forwarded_Proto =
true;
321 protoRequest->headers.pushHeader(key, value);
324ProtoRequestHttp::ProtoRequestHttp(
Socket *sock,
int bufferSize)
327 isSecure = sock->isSecure;
330ProtoRequestHttp::~ProtoRequestHttp()
334void ProtoRequestHttp::setupNewConnection(
Socket *sock)
343 if (websocketUpgraded &&
status != Cutelyst::Response::SwitchingProtocols) {
344 qCWarning(C_SERVER_SOCK) <<
"Trying to write header while on an Websocket context";
351 ProtoRequestHttp::HeaderConnection fallbackConnection = headerConnection;
352 headerConnection = ProtoRequestHttp::HeaderConnection::NotSet;
354 bool hasDate =
false;
355 for (
const auto &[key, value] : headersData) {
356 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
358 if (value.compare(
"close") == 0) {
359 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
360 }
else if (value.compare(
"Upgrade") == 0) {
361 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
363 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
375 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet) {
376 if (fallbackConnection == ProtoRequestHttp::HeaderConnection::Keep ||
377 (fallbackConnection != ProtoRequestHttp::HeaderConnection::Close &&
379 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
380 data.
append(
"\r\nConnection: keep-alive", 24);
382 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
383 data.
append(
"\r\nConnection: close", 19);
390 data.
append(
"\r\n\r\n", 4);
397 return io->
write(data, len);
402 if (websocketUpgraded) {
405 websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseHeaders;
410 if (!sock->requestFinished()) {
415 if (headerConnection == ProtoRequestHttp::HeaderConnection::Close) {
416 sock->connectionClose();
420 if (last < buf_size) {
422 int remaining = buf_size - last;
423 memmove(buffer, buffer + last,
size_t(remaining));
425 buf_size = remaining;
427 if (
status & EngineRequest::Async) {
428 sock->proto->parse(sock, io);
435bool ProtoRequestHttp::webSocketSendTextMessage(
const QString &message)
437 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
438 qCWarning(C_SERVER_HTTP)
439 <<
"Not sending websocket text message due connection header not upgraded"
440 << headerConnection << message.
size();
446 ProtoRequestHttp::OpCodeText, quint64(rawMessage.
size()));
450bool ProtoRequestHttp::webSocketSendBinaryMessage(
const QByteArray &message)
452 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
453 qCWarning(C_SERVER_HTTP)
454 <<
"Not sending websocket binary messagedue connection header not upgraded"
455 << headerConnection << message.
size();
460 ProtoRequestHttp::OpCodeBinary, quint64(message.
size()));
464bool ProtoRequestHttp::webSocketSendPing(
const QByteArray &payload)
466 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
467 qCWarning(C_SERVER_HTTP) <<
"Not sending websocket ping due connection header not upgraded"
468 << headerConnection << payload.
size();
474 ProtoRequestHttp::OpCodePing, quint64(rawMessage.
size()));
478bool ProtoRequestHttp::webSocketClose(quint16 code,
const QString &reason)
480 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
481 qCWarning(C_SERVER_HTTP) <<
"Not sending websocket close due connection header not upgraded"
482 << headerConnection << code << reason;
486 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, code);
488 sock->requestFinished();
489 sock->connectionClose();
493void ProtoRequestHttp::socketDisconnected()
495 if (websocketUpgraded) {
496 if (websocket_finn_opcode != 0x88) {
499 sock->requestFinished();
503bool ProtoRequestHttp::webSocketHandshakeDo(
const QByteArray &key,
507 if (headerConnection == ProtoRequestHttp::HeaderConnection::Upgrade) {
511 if (sock->proto->type() != Protocol::Type::Http11) {
512 qCWarning(C_SERVER_SOCK)
513 <<
"Upgrading a connection to websocket is only supported with the HTTP/1.1 protocol"
514 <<
typeid(sock->proto).name();
522 response->
setStatus(Cutelyst::Response::SwitchingProtocols);
525 const auto localOrigin = origin.
isEmpty() ? requestHeaders.
header(
"Origin") : origin;
526 headers.
setHeader(
"Sec-Websocket-Origin"_ba, localOrigin.isEmpty() ?
"*"_ba : localOrigin);
530 }
else if (
const auto wsProtocol = requestHeaders.
header(
"Sec-Websocket-Protocol");
536 const QByteArray wsKey = localKey +
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
537 if (wsKey.
length() == 36) {
538 qCWarning(C_SERVER_SOCK) <<
"Missing websocket key";
546 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
547 websocketUpgraded =
true;
548 auto httpProto =
static_cast<ProtocolHttp *
>(sock->proto);
549 sock->proto = httpProto->m_websocketProto;
559 ret = QByteArrayLiteral(
"HTTP/1.1 200 OK");
561 case Response::Found:
562 ret = QByteArrayLiteral(
"HTTP/1.1 302 Found");
564 case Response::NotFound:
565 ret = QByteArrayLiteral(
"HTTP/1.1 404 Not Found");
567 case Response::InternalServerError:
568 ret = QByteArrayLiteral(
"HTTP/1.1 500 Internal Server Error");
570 case Response::MovedPermanently:
571 ret = QByteArrayLiteral(
"HTTP/1.1 301 Moved Permanently");
573 case Response::NotModified:
574 ret = QByteArrayLiteral(
"HTTP/1.1 304 Not Modified");
576 case Response::SeeOther:
577 ret = QByteArrayLiteral(
"HTTP/1.1 303 See Other");
579 case Response::Forbidden:
580 ret = QByteArrayLiteral(
"HTTP/1.1 403 Forbidden");
582 case Response::TemporaryRedirect:
583 ret = QByteArrayLiteral(
"HTTP/1.1 307 Temporary Redirect");
585 case Response::Unauthorized:
586 ret = QByteArrayLiteral(
"HTTP/1.1 401 Unauthorized");
588 case Response::BadRequest:
589 ret = QByteArrayLiteral(
"HTTP/1.1 400 Bad Request");
591 case Response::MethodNotAllowed:
592 ret = QByteArrayLiteral(
"HTTP/1.1 405 Method Not Allowed");
594 case Response::RequestTimeout:
595 ret = QByteArrayLiteral(
"HTTP/1.1 408 Request Timeout");
597 case Response::Continue:
598 ret = QByteArrayLiteral(
"HTTP/1.1 100 Continue");
600 case Response::SwitchingProtocols:
601 ret = QByteArrayLiteral(
"HTTP/1.1 101 Switching Protocols");
603 case Response::Created:
604 ret = QByteArrayLiteral(
"HTTP/1.1 201 Created");
606 case Response::Accepted:
607 ret = QByteArrayLiteral(
"HTTP/1.1 202 Accepted");
609 case Response::NonAuthoritativeInformation:
610 ret = QByteArrayLiteral(
"HTTP/1.1 203 Non-Authoritative Information");
612 case Response::NoContent:
613 ret = QByteArrayLiteral(
"HTTP/1.1 204 No Content");
615 case Response::ResetContent:
616 ret = QByteArrayLiteral(
"HTTP/1.1 205 Reset Content");
618 case Response::PartialContent:
619 ret = QByteArrayLiteral(
"HTTP/1.1 206 Partial Content");
621 case Response::MultipleChoices:
622 ret = QByteArrayLiteral(
"HTTP/1.1 300 Multiple Choices");
624 case Response::UseProxy:
625 ret = QByteArrayLiteral(
"HTTP/1.1 305 Use Proxy");
627 case Response::PaymentRequired:
628 ret = QByteArrayLiteral(
"HTTP/1.1 402 Payment Required");
630 case Response::NotAcceptable:
631 ret = QByteArrayLiteral(
"HTTP/1.1 406 Not Acceptable");
633 case Response::ProxyAuthenticationRequired:
634 ret = QByteArrayLiteral(
"HTTP/1.1 407 Proxy Authentication Required");
636 case Response::Conflict:
637 ret = QByteArrayLiteral(
"HTTP/1.1 409 Conflict");
640 ret = QByteArrayLiteral(
"HTTP/1.1 410 Gone");
642 case Response::LengthRequired:
643 ret = QByteArrayLiteral(
"HTTP/1.1 411 Length Required");
645 case Response::PreconditionFailed:
646 ret = QByteArrayLiteral(
"HTTP/1.1 412 Precondition Failed");
648 case Response::RequestEntityTooLarge:
649 ret = QByteArrayLiteral(
"HTTP/1.1 413 Request Entity Too Large");
651 case Response::RequestURITooLong:
652 ret = QByteArrayLiteral(
"HTTP/1.1 414 Request-URI Too Long");
654 case Response::UnsupportedMediaType:
655 ret = QByteArrayLiteral(
"HTTP/1.1 415 Unsupported Media Type");
657 case Response::RequestedRangeNotSatisfiable:
658 ret = QByteArrayLiteral(
"HTTP/1.1 416 Requested Range Not Satisfiable");
660 case Response::ExpectationFailed:
661 ret = QByteArrayLiteral(
"HTTP/1.1 417 Expectation Failed");
663 case Response::NotImplemented:
664 ret = QByteArrayLiteral(
"HTTP/1.1 501 Not Implemented");
666 case Response::BadGateway:
667 ret = QByteArrayLiteral(
"HTTP/1.1 502 Bad Gateway");
669 case Response::ServiceUnavailable:
670 ret = QByteArrayLiteral(
"HTTP/1.1 503 Service Unavailable");
672 case Response::MultiStatus:
673 ret = QByteArrayLiteral(
"HTTP/1.1 207 Multi-Status");
675 case Response::GatewayTimeout:
676 ret = QByteArrayLiteral(
"HTTP/1.1 504 Gateway Timeout");
678 case Response::HTTPVersionNotSupported:
679 ret = QByteArrayLiteral(
"HTTP/1.1 505 HTTP Version Not Supported");
681 case Response::BandwidthLimitExceeded:
682 ret = QByteArrayLiteral(
"HTTP/1.1 509 Bandwidth Limit Exceeded");
692#include "moc_protocolhttp.cpp"
Response * response() const noexcept
QHostAddress remoteAddress
void processRequest(EngineRequest *request)
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
void setStatus(quint16 status) noexcept
Headers & headers() noexcept
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
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
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