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> 28 QByteArray http11StatusMessage(quint16 status);
30 Q_LOGGING_CATEGORY(C_SERVER_HTTP,
"cutelyst.server.http", QtWarningMsg)
31 Q_DECLARE_LOGGING_CATEGORY(C_SERVER_SOCK)
36 , m_upgradeH2c(upgradeH2c)
38 usingFrontendProxy = wsgi->usingFrontendProxy();
41 ProtocolHttp::~ProtocolHttp()
43 delete m_websocketProto;
46 Protocol::Type ProtocolHttp::type()
const 48 return Protocol::Type::Http11;
51 inline 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) {
226 void 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));
271 void 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);
324 ProtoRequestHttp::ProtoRequestHttp(
Socket *sock,
int bufferSize)
327 isSecure = sock->isSecure;
330 ProtoRequestHttp::~ProtoRequestHttp()
334 void 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 auto it = headersData.begin();
356 while (it != headersData.end()) {
357 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
359 if (it->value.compare(
"close") == 0) {
360 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
361 }
else if (it->value.compare(
"Upgrade") == 0) {
362 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
364 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
378 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet) {
379 if (fallbackConnection == ProtoRequestHttp::HeaderConnection::Keep ||
380 (fallbackConnection != ProtoRequestHttp::HeaderConnection::Close &&
382 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
383 data.
append(
"\r\nConnection: keep-alive", 24);
385 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
386 data.
append(
"\r\nConnection: close", 19);
391 data.
append(static_cast<ServerEngine *>(sock->engine)->lastDate());
393 data.
append(
"\r\n\r\n", 4);
400 return io->
write(data, len);
405 if (websocketUpgraded) {
408 websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
413 if (!sock->requestFinished()) {
418 if (headerConnection == ProtoRequestHttp::HeaderConnection::Close) {
419 sock->connectionClose();
423 if (last < buf_size) {
425 int remaining = buf_size - last;
426 memmove(buffer, buffer + last,
size_t(remaining));
428 buf_size = remaining;
430 if (
status & EngineRequest::Async) {
431 sock->proto->parse(sock, io);
438 bool ProtoRequestHttp::webSocketSendTextMessage(
const QString &message)
440 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
441 qCWarning(C_SERVER_HTTP)
442 <<
"Not sending websocket text message due connection header not upgraded" 443 << headerConnection << message.
size();
449 ProtoRequestHttp::OpCodeText, quint64(rawMessage.
size()));
453 bool ProtoRequestHttp::webSocketSendBinaryMessage(
const QByteArray &message)
455 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
456 qCWarning(C_SERVER_HTTP)
457 <<
"Not sending websocket binary messagedue connection header not upgraded" 458 << headerConnection << message.
size();
463 ProtoRequestHttp::OpCodeBinary, quint64(message.
size()));
467 bool ProtoRequestHttp::webSocketSendPing(
const QByteArray &payload)
469 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
470 qCWarning(C_SERVER_HTTP) <<
"Not sending websocket ping due connection header not upgraded" 471 << headerConnection << payload.
size();
477 ProtoRequestHttp::OpCodePing, quint64(rawMessage.
size()));
481 bool ProtoRequestHttp::webSocketClose(quint16 code,
const QString &reason)
483 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
484 qCWarning(C_SERVER_HTTP) <<
"Not sending websocket close due connection header not upgraded" 485 << headerConnection << code << reason;
489 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, code);
491 sock->requestFinished();
492 sock->connectionClose();
496 void ProtoRequestHttp::socketDisconnected()
498 if (websocketUpgraded) {
499 if (websocket_finn_opcode != 0x88) {
502 sock->requestFinished();
506 bool ProtoRequestHttp::webSocketHandshakeDo(
const QByteArray &key,
510 if (headerConnection == ProtoRequestHttp::HeaderConnection::Upgrade) {
514 if (sock->proto->type() != Protocol::Type::Http11) {
515 qCWarning(C_SERVER_SOCK)
516 <<
"Upgrading a connection to websocket is only supported with the HTTP/1.1 protocol" 517 <<
typeid(sock->proto).name();
525 response->
setStatus(Cutelyst::Response::SwitchingProtocols);
528 const auto localOrigin = origin.
isEmpty() ? requestHeaders.
header(
"Origin") : origin;
529 headers.
setHeader(
"Sec-Websocket-Origin"_ba, localOrigin.isEmpty() ?
"*"_ba : localOrigin);
533 }
else if (
const auto wsProtocol = requestHeaders.
header(
"Sec-Websocket-Protocol");
539 const QByteArray wsKey = localKey +
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
540 if (wsKey.
length() == 36) {
541 qCWarning(C_SERVER_SOCK) <<
"Missing websocket key";
549 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
550 websocketUpgraded =
true;
551 auto httpProto =
static_cast<ProtocolHttp *
>(sock->proto);
552 sock->proto = httpProto->m_websocketProto;
557 QByteArray http11StatusMessage(quint16 status)
562 ret = QByteArrayLiteral(
"HTTP/1.1 200 OK");
564 case Response::Found:
565 ret = QByteArrayLiteral(
"HTTP/1.1 302 Found");
567 case Response::NotFound:
568 ret = QByteArrayLiteral(
"HTTP/1.1 404 Not Found");
570 case Response::InternalServerError:
571 ret = QByteArrayLiteral(
"HTTP/1.1 500 Internal Server Error");
573 case Response::MovedPermanently:
574 ret = QByteArrayLiteral(
"HTTP/1.1 301 Moved Permanently");
576 case Response::NotModified:
577 ret = QByteArrayLiteral(
"HTTP/1.1 304 Not Modified");
579 case Response::SeeOther:
580 ret = QByteArrayLiteral(
"HTTP/1.1 303 See Other");
582 case Response::Forbidden:
583 ret = QByteArrayLiteral(
"HTTP/1.1 403 Forbidden");
585 case Response::TemporaryRedirect:
586 ret = QByteArrayLiteral(
"HTTP/1.1 307 Temporary Redirect");
588 case Response::Unauthorized:
589 ret = QByteArrayLiteral(
"HTTP/1.1 401 Unauthorized");
591 case Response::BadRequest:
592 ret = QByteArrayLiteral(
"HTTP/1.1 400 Bad Request");
594 case Response::MethodNotAllowed:
595 ret = QByteArrayLiteral(
"HTTP/1.1 405 Method Not Allowed");
597 case Response::RequestTimeout:
598 ret = QByteArrayLiteral(
"HTTP/1.1 408 Request Timeout");
600 case Response::Continue:
601 ret = QByteArrayLiteral(
"HTTP/1.1 100 Continue");
603 case Response::SwitchingProtocols:
604 ret = QByteArrayLiteral(
"HTTP/1.1 101 Switching Protocols");
606 case Response::Created:
607 ret = QByteArrayLiteral(
"HTTP/1.1 201 Created");
609 case Response::Accepted:
610 ret = QByteArrayLiteral(
"HTTP/1.1 202 Accepted");
612 case Response::NonAuthoritativeInformation:
613 ret = QByteArrayLiteral(
"HTTP/1.1 203 Non-Authoritative Information");
615 case Response::NoContent:
616 ret = QByteArrayLiteral(
"HTTP/1.1 204 No Content");
618 case Response::ResetContent:
619 ret = QByteArrayLiteral(
"HTTP/1.1 205 Reset Content");
621 case Response::PartialContent:
622 ret = QByteArrayLiteral(
"HTTP/1.1 206 Partial Content");
624 case Response::MultipleChoices:
625 ret = QByteArrayLiteral(
"HTTP/1.1 300 Multiple Choices");
627 case Response::UseProxy:
628 ret = QByteArrayLiteral(
"HTTP/1.1 305 Use Proxy");
630 case Response::PaymentRequired:
631 ret = QByteArrayLiteral(
"HTTP/1.1 402 Payment Required");
633 case Response::NotAcceptable:
634 ret = QByteArrayLiteral(
"HTTP/1.1 406 Not Acceptable");
636 case Response::ProxyAuthenticationRequired:
637 ret = QByteArrayLiteral(
"HTTP/1.1 407 Proxy Authentication Required");
639 case Response::Conflict:
640 ret = QByteArrayLiteral(
"HTTP/1.1 409 Conflict");
643 ret = QByteArrayLiteral(
"HTTP/1.1 410 Gone");
645 case Response::LengthRequired:
646 ret = QByteArrayLiteral(
"HTTP/1.1 411 Length Required");
648 case Response::PreconditionFailed:
649 ret = QByteArrayLiteral(
"HTTP/1.1 412 Precondition Failed");
651 case Response::RequestEntityTooLarge:
652 ret = QByteArrayLiteral(
"HTTP/1.1 413 Request Entity Too Large");
654 case Response::RequestURITooLong:
655 ret = QByteArrayLiteral(
"HTTP/1.1 414 Request-URI Too Long");
657 case Response::UnsupportedMediaType:
658 ret = QByteArrayLiteral(
"HTTP/1.1 415 Unsupported Media Type");
660 case Response::RequestedRangeNotSatisfiable:
661 ret = QByteArrayLiteral(
"HTTP/1.1 416 Requested Range Not Satisfiable");
663 case Response::ExpectationFailed:
664 ret = QByteArrayLiteral(
"HTTP/1.1 417 Expectation Failed");
666 case Response::NotImplemented:
667 ret = QByteArrayLiteral(
"HTTP/1.1 501 Not Implemented");
669 case Response::BadGateway:
670 ret = QByteArrayLiteral(
"HTTP/1.1 502 Bad Gateway");
672 case Response::ServiceUnavailable:
673 ret = QByteArrayLiteral(
"HTTP/1.1 503 Service Unavailable");
675 case Response::MultiStatus:
676 ret = QByteArrayLiteral(
"HTTP/1.1 207 Multi-Status");
678 case Response::GatewayTimeout:
679 ret = QByteArrayLiteral(
"HTTP/1.1 504 Gateway Timeout");
681 case Response::HTTPVersionNotSupported:
682 ret = QByteArrayLiteral(
"HTTP/1.1 505 HTTP Version Not Supported");
684 case Response::BandwidthLimitExceeded:
685 ret = QByteArrayLiteral(
"HTTP/1.1 509 Bandwidth Limit Exceeded");
695 #include "moc_protocolhttp.cpp"
virtual bool seek(qint64 pos)
Headers & headers() noexcept
QString errorString() const const
qsizetype size() const const
bool isEmpty() const const
void processRequest(EngineRequest *request)
qsizetype length() const const
void processingFinished() override final
QByteArray read(qint64 maxSize)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
QByteArray number(double n, char format, int precision)
virtual qint64 size() const const
void webSocketClosed(quint16 closeCode, const QString &reason)
Emitted when the websocket receives a close frame, including a close code and a reason, it's also emitted when the connection closes without the client sending the close frame.
Headers headers() const noexcept
qint64 doWrite(const char *data, qint64 len) override final
qint64 write(const QByteArray &data)
QHostAddress remoteAddress
The Cutelyst namespace holds all public Cutelyst API.
virtual qint64 bytesAvailable() const const
bool writeHeaders(quint16 status, const Cutelyst::Headers &headers) override final
QByteArray & append(QByteArrayView data)
QString fromLatin1(QByteArrayView str)
QByteArray left(qsizetype len) const const
QByteArray hash(QByteArrayView data, Algorithm method)
QByteArray toBase64(Base64Options options) const const
qsizetype size() const const
Response * response() const noexcept
void setStatus(quint16 status) noexcept
QByteArray toUtf8() const const