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 = server->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) {
81 if (protoRequest->chunkPhase != ProtoRequestHttp::ChunkPhase::NotChunked) {
82 if (parseChunkedBody(sock, io)) {
83 processRequest(sock, io);
94 remaining = protoRequest->contentLength - body->
size();
95 len = io->
read(m_postBuffer, qMin(m_postBufferSize, remaining));
97 qCWarning(C_SERVER_HTTP)
98 <<
"error while reading body" << len << protoRequest->headers;
99 sock->connectionClose();
102 bytesAvailable -= len;
105 body->
write(m_postBuffer, len);
106 }
while (bytesAvailable && remaining);
108 if (remaining == len) {
109 processRequest(sock, io);
115 qint64 len = io->
read(protoRequest->buffer + protoRequest->buf_size,
116 m_bufferSize - protoRequest->buf_size);
118 qCWarning(C_SERVER_HTTP) <<
"Failed to read from socket" << io->
errorString();
121 protoRequest->buf_size += len;
123 while (protoRequest->last < protoRequest->buf_size) {
126 int ix = CrLfIndexIn(protoRequest->buffer, protoRequest->buf_size, protoRequest->last);
128 len = ix - protoRequest->beginLine;
129 char *ptr = protoRequest->buffer + protoRequest->beginLine;
130 protoRequest->beginLine = ix + 2;
131 protoRequest->last = protoRequest->beginLine;
133 if (protoRequest->connState == ProtoRequestHttp::MethodLine) {
134 if (useStats && protoRequest->startOfRequest == TimePointSteady{}) {
135 protoRequest->startOfRequest = std::chrono::steady_clock::now();
138 parseMethod(ptr, ptr + len, sock);
139 protoRequest->connState = ProtoRequestHttp::HeaderLine;
140 protoRequest->contentLength = -1;
146 }
else if (protoRequest->connState == ProtoRequestHttp::HeaderLine) {
148 parseHeader(ptr, ptr + len, sock);
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();
161 const int leftover = protoRequest->buf_size - protoRequest->last;
163 memmove(protoRequest->buffer,
164 protoRequest->buffer + protoRequest->last,
167 protoRequest->buf_size = leftover;
168 protoRequest->last = 0;
169 protoRequest->beginLine = 0;
171 if (parseChunkedBody(sock, io)) {
172 if (!processRequest(sock, io)) {
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();
190 qMin(protoRequest->contentLength,
191 static_cast<qint64
>(protoRequest->buf_size - protoRequest->last));
195 protoRequest->body->write(ptr, len);
197 protoRequest->last += len;
199 if (protoRequest->contentLength > len) {
212 if (!processRequest(sock, io)) {
218 if (protoRequest->startOfRequest == TimePointSteady{}) {
219 protoRequest->startOfRequest = std::chrono::steady_clock::now();
223 if (protoRequest->buf_size > 0 &&
224 protoRequest->buffer[protoRequest->buf_size - 1] ==
'\r') {
225 protoRequest->last = protoRequest->buf_size - 1;
227 protoRequest->last = protoRequest->buf_size;
233 if (protoRequest->buf_size == m_bufferSize) {
249 const qint64 n = io->
read(req->buffer + req->buf_size, m_bufferSize - req->buf_size);
251 qCWarning(C_SERVER_HTTP) <<
"Failed to read chunked body" << io->
errorString();
252 sock->connectionClose();
255 req->buf_size += int(n);
258 switch (req->chunkPhase) {
259 case ProtoRequestHttp::ChunkPhase::Size:
261 const int ix = CrLfIndexIn(req->buffer, req->buf_size, 0);
263 if (req->buf_size == m_bufferSize) {
264 qCWarning(C_SERVER_HTTP) <<
"chunk size line too long";
265 sock->connectionClose();
272 while (sizeEnd < ix) {
273 const char c = req->buffer[sizeEnd];
274 if (c ==
';' || c ==
' ') {
281 const qint64 chunkSize =
283 if (!ok || chunkSize < 0) {
284 qCWarning(C_SERVER_HTTP) <<
"invalid chunk size";
285 sock->connectionClose();
289 const int consume = ix + 2;
290 memmove(req->buffer, req->buffer + consume,
size_t(req->buf_size - consume));
291 req->buf_size -= consume;
293 if (chunkSize == 0) {
294 req->chunkPhase = ProtoRequestHttp::ChunkPhase::Trailers;
298 req->chunkBytesLeft = chunkSize;
299 req->chunkPhase = ProtoRequestHttp::ChunkPhase::Data;
302 case ProtoRequestHttp::ChunkPhase::Data:
304 if (req->buf_size == 0) {
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();
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;
321 case ProtoRequestHttp::ChunkPhase::DataCrLf:
323 if (req->buf_size < 2) {
326 if (req->buffer[0] !=
'\r' || req->buffer[1] !=
'\n') {
327 qCWarning(C_SERVER_HTTP) <<
"missing CRLF after chunk data";
328 sock->connectionClose();
331 memmove(req->buffer, req->buffer + 2,
size_t(req->buf_size - 2));
333 req->chunkPhase = ProtoRequestHttp::ChunkPhase::Size;
336 case ProtoRequestHttp::ChunkPhase::Trailers:
338 const int ix = CrLfIndexIn(req->buffer, req->buf_size, 0);
344 memmove(req->buffer, req->buffer + 2,
size_t(req->buf_size - 2));
346 req->chunkPhase = ProtoRequestHttp::ChunkPhase::NotChunked;
352 memmove(req->buffer, req->buffer + ix + 2,
size_t(req->buf_size - (ix + 2)));
353 req->buf_size -= (ix + 2);
356 case ProtoRequestHttp::ChunkPhase::NotChunked:
371 if (m_upgradeH2c && m_upgradeH2c->upgradeH2C(sock, io, *request)) {
378 if (request->websocketUpgraded) {
382 if (request->status & Cutelyst::EngineRequest::Async) {
389void ProtocolHttp::parseMethod(
const char *ptr,
const char *end,
Socket *sock)
const
392 const char *word_boundary = ptr;
393 while (*word_boundary !=
' ' && word_boundary < end) {
399 while (*word_boundary ==
' ' && word_boundary < end) {
405 while (*word_boundary !=
' ' && *word_boundary !=
'?' && word_boundary < end) {
410 protoRequest->setPath(
const_cast<char *
>(ptr),
int(word_boundary - ptr));
412 if (*word_boundary ==
'?') {
413 ptr = word_boundary + 1;
414 while (*word_boundary !=
' ' && word_boundary < end) {
417 protoRequest->query =
QByteArray(ptr,
int(word_boundary - ptr));
423 while (*word_boundary ==
' ' && word_boundary < end) {
428 while (*word_boundary !=
' ' && word_boundary < end) {
431 protoRequest->protocol =
QByteArray(ptr,
int(word_boundary - ptr));
434void ProtocolHttp::parseHeader(
const char *ptr,
const char *end,
Socket *sock)
const
437 const char *word_boundary = ptr;
438 while (*word_boundary !=
':' && word_boundary < end) {
441 const auto key =
QByteArray(ptr,
int(word_boundary - ptr));
443 while ((*word_boundary ==
':' || *word_boundary ==
' ') && word_boundary < end) {
446 const auto value =
QByteArray(word_boundary,
int(end - word_boundary));
448 if (protoRequest->headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
451 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Close;
453 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
455 }
else if (protoRequest->contentLength < 0 &&
458 qint64 cl = value.toLongLong(&ok);
460 protoRequest->contentLength = cl;
462 }
else if (protoRequest->chunkPhase == ProtoRequestHttp::ChunkPhase::NotChunked &&
465 if (value.toLower().contains(
"chunked"_ba)) {
466 protoRequest->chunkPhase = ProtoRequestHttp::ChunkPhase::Size;
467 protoRequest->contentLength = -1;
470 protoRequest->serverAddress = value;
471 protoRequest->headerHost =
true;
472 }
else if (usingFrontendProxy) {
473 if (!protoRequest->X_Forwarded_For &&
478 protoRequest->remotePort = 0;
479 protoRequest->X_Forwarded_For =
true;
480 }
else if (!protoRequest->X_Forwarded_Host &&
482 protoRequest->serverAddress = value;
483 protoRequest->X_Forwarded_Host =
true;
484 protoRequest->headerHost =
true;
485 }
else if (!protoRequest->X_Forwarded_Proto &&
487 protoRequest->isSecure = (value.compare(
"https") == 0);
488 protoRequest->X_Forwarded_Proto =
true;
491 protoRequest->headers.pushHeader(key, value);
494ProtoRequestHttp::ProtoRequestHttp(
Socket *sock,
int bufferSize)
497 isSecure = sock->isSecure;
500ProtoRequestHttp::~ProtoRequestHttp()
504void ProtoRequestHttp::setupNewConnection(
Socket *sock)
513 if (websocketUpgraded &&
status != Cutelyst::Response::SwitchingProtocols) {
514 qCWarning(C_SERVER_SOCK) <<
"Trying to write header while on an Websocket context";
521 ProtoRequestHttp::HeaderConnection fallbackConnection = headerConnection;
522 headerConnection = ProtoRequestHttp::HeaderConnection::NotSet;
524 bool hasDate =
false;
525 for (
const auto &[key, value] : headersData) {
526 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
528 if (value.compare(
"close") == 0) {
529 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
530 }
else if (value.compare(
"Upgrade") == 0) {
531 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
533 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
545 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet) {
546 if (fallbackConnection == ProtoRequestHttp::HeaderConnection::Keep ||
547 (fallbackConnection != ProtoRequestHttp::HeaderConnection::Close &&
549 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
550 data.
append(
"\r\nConnection: keep-alive", 24);
552 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
553 data.
append(
"\r\nConnection: close", 19);
560 data.
append(
"\r\n\r\n", 4);
567 return io->
write(data, len);
572 if (websocketUpgraded) {
575 websocket_phase = ProtoRequestHttp::WebSocketPhase::WebSocketPhaseHeaders;
580 if (!sock->requestFinished()) {
585 if (headerConnection == ProtoRequestHttp::HeaderConnection::Close) {
586 sock->connectionClose();
590 if (last < buf_size) {
592 int remaining = buf_size - last;
593 memmove(buffer, buffer + last,
size_t(remaining));
595 buf_size = remaining;
597 if (
status & EngineRequest::Async) {
598 sock->proto->parse(sock, io);
605bool ProtoRequestHttp::webSocketSendTextMessage(
const QString &message)
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();
616 ProtoRequestHttp::OpCodeText, quint64(rawMessage.
size()));
620bool ProtoRequestHttp::webSocketSendBinaryMessage(
const QByteArray &message)
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();
630 ProtoRequestHttp::OpCodeBinary, quint64(message.
size()));
634bool ProtoRequestHttp::webSocketSendPing(
const QByteArray &payload)
636 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
637 qCWarning(C_SERVER_HTTP) <<
"Not sending websocket ping due connection header not upgraded"
638 << headerConnection << payload.
size();
644 ProtoRequestHttp::OpCodePing, quint64(rawMessage.
size()));
648bool ProtoRequestHttp::webSocketClose(quint16 code,
const QString &reason)
650 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
651 qCWarning(C_SERVER_HTTP) <<
"Not sending websocket close due connection header not upgraded"
652 << headerConnection << code << reason;
656 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, code);
658 sock->requestFinished();
659 sock->connectionClose();
663void ProtoRequestHttp::socketDisconnected()
665 if (websocketUpgraded) {
666 if (websocket_finn_opcode != 0x88) {
669 sock->requestFinished();
673bool ProtoRequestHttp::webSocketHandshakeDo(
const QByteArray &key,
677 if (headerConnection == ProtoRequestHttp::HeaderConnection::Upgrade) {
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();
692 response->
setStatus(Cutelyst::Response::SwitchingProtocols);
695 const auto localOrigin = origin.
isEmpty() ? requestHeaders.
header(
"Origin") : origin;
696 headers.
setHeader(
"Sec-Websocket-Origin"_ba, localOrigin.isEmpty() ?
"*"_ba : localOrigin);
700 }
else if (
const auto wsProtocol = requestHeaders.
header(
"Sec-Websocket-Protocol");
706 const QByteArray wsKey = localKey +
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
707 if (wsKey.
length() == 36) {
708 qCWarning(C_SERVER_SOCK) <<
"Missing websocket key";
716 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
717 websocketUpgraded =
true;
718 const auto *httpProto =
static_cast<ProtocolHttp *
>(sock->proto);
719 sock->proto = httpProto->m_websocketProto;
729 ret = QByteArrayLiteral(
"HTTP/1.1 200 OK");
731 case Response::Found:
732 ret = QByteArrayLiteral(
"HTTP/1.1 302 Found");
734 case Response::NotFound:
735 ret = QByteArrayLiteral(
"HTTP/1.1 404 Not Found");
737 case Response::InternalServerError:
738 ret = QByteArrayLiteral(
"HTTP/1.1 500 Internal Server Error");
740 case Response::MovedPermanently:
741 ret = QByteArrayLiteral(
"HTTP/1.1 301 Moved Permanently");
743 case Response::NotModified:
744 ret = QByteArrayLiteral(
"HTTP/1.1 304 Not Modified");
746 case Response::SeeOther:
747 ret = QByteArrayLiteral(
"HTTP/1.1 303 See Other");
749 case Response::Forbidden:
750 ret = QByteArrayLiteral(
"HTTP/1.1 403 Forbidden");
752 case Response::TemporaryRedirect:
753 ret = QByteArrayLiteral(
"HTTP/1.1 307 Temporary Redirect");
755 case Response::Unauthorized:
756 ret = QByteArrayLiteral(
"HTTP/1.1 401 Unauthorized");
758 case Response::BadRequest:
759 ret = QByteArrayLiteral(
"HTTP/1.1 400 Bad Request");
761 case Response::MethodNotAllowed:
762 ret = QByteArrayLiteral(
"HTTP/1.1 405 Method Not Allowed");
764 case Response::RequestTimeout:
765 ret = QByteArrayLiteral(
"HTTP/1.1 408 Request Timeout");
767 case Response::Continue:
768 ret = QByteArrayLiteral(
"HTTP/1.1 100 Continue");
770 case Response::SwitchingProtocols:
771 ret = QByteArrayLiteral(
"HTTP/1.1 101 Switching Protocols");
773 case Response::Created:
774 ret = QByteArrayLiteral(
"HTTP/1.1 201 Created");
776 case Response::Accepted:
777 ret = QByteArrayLiteral(
"HTTP/1.1 202 Accepted");
779 case Response::NonAuthoritativeInformation:
780 ret = QByteArrayLiteral(
"HTTP/1.1 203 Non-Authoritative Information");
782 case Response::NoContent:
783 ret = QByteArrayLiteral(
"HTTP/1.1 204 No Content");
785 case Response::ResetContent:
786 ret = QByteArrayLiteral(
"HTTP/1.1 205 Reset Content");
788 case Response::PartialContent:
789 ret = QByteArrayLiteral(
"HTTP/1.1 206 Partial Content");
791 case Response::MultipleChoices:
792 ret = QByteArrayLiteral(
"HTTP/1.1 300 Multiple Choices");
794 case Response::UseProxy:
795 ret = QByteArrayLiteral(
"HTTP/1.1 305 Use Proxy");
797 case Response::PaymentRequired:
798 ret = QByteArrayLiteral(
"HTTP/1.1 402 Payment Required");
800 case Response::NotAcceptable:
801 ret = QByteArrayLiteral(
"HTTP/1.1 406 Not Acceptable");
803 case Response::ProxyAuthenticationRequired:
804 ret = QByteArrayLiteral(
"HTTP/1.1 407 Proxy Authentication Required");
806 case Response::Conflict:
807 ret = QByteArrayLiteral(
"HTTP/1.1 409 Conflict");
810 ret = QByteArrayLiteral(
"HTTP/1.1 410 Gone");
812 case Response::LengthRequired:
813 ret = QByteArrayLiteral(
"HTTP/1.1 411 Length Required");
815 case Response::PreconditionFailed:
816 ret = QByteArrayLiteral(
"HTTP/1.1 412 Precondition Failed");
818 case Response::RequestEntityTooLarge:
819 ret = QByteArrayLiteral(
"HTTP/1.1 413 Request Entity Too Large");
821 case Response::RequestURITooLong:
822 ret = QByteArrayLiteral(
"HTTP/1.1 414 Request-URI Too Long");
824 case Response::UnsupportedMediaType:
825 ret = QByteArrayLiteral(
"HTTP/1.1 415 Unsupported Media Type");
827 case Response::RequestedRangeNotSatisfiable:
828 ret = QByteArrayLiteral(
"HTTP/1.1 416 Requested Range Not Satisfiable");
830 case Response::ExpectationFailed:
831 ret = QByteArrayLiteral(
"HTTP/1.1 417 Expectation Failed");
833 case Response::NotImplemented:
834 ret = QByteArrayLiteral(
"HTTP/1.1 501 Not Implemented");
836 case Response::BadGateway:
837 ret = QByteArrayLiteral(
"HTTP/1.1 502 Bad Gateway");
839 case Response::ServiceUnavailable:
840 ret = QByteArrayLiteral(
"HTTP/1.1 503 Service Unavailable");
842 case Response::MultiStatus:
843 ret = QByteArrayLiteral(
"HTTP/1.1 207 Multi-Status");
845 case Response::GatewayTimeout:
846 ret = QByteArrayLiteral(
"HTTP/1.1 504 Gateway Timeout");
848 case Response::HTTPVersionNotSupported:
849 ret = QByteArrayLiteral(
"HTTP/1.1 505 HTTP Version Not Supported");
851 case Response::BandwidthLimitExceeded:
852 ret = QByteArrayLiteral(
"HTTP/1.1 509 Bandwidth Limit Exceeded");
862#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
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