cutelyst 4.3.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;
26
27Q_LOGGING_CATEGORY(C_SERVER_HTTP, "cutelyst.server.http", QtWarningMsg)
28Q_DECLARE_LOGGING_CATEGORY(C_SERVER_SOCK)
29
30ProtocolHttp::ProtocolHttp(Server *wsgi, ProtocolHttp2 *upgradeH2c)
31 : Protocol(wsgi)
32 , m_websocketProto(new ProtocolWebSocket(wsgi))
33 , m_upgradeH2c(upgradeH2c)
34{
35 usingFrontendProxy = wsgi->usingFrontendProxy();
36}
37
38ProtocolHttp::~ProtocolHttp()
39{
40 delete m_websocketProto;
41}
42
43Protocol::Type ProtocolHttp::type() const
44{
45 return Protocol::Type::Http11;
46}
47
48inline int CrLfIndexIn(const char *str, int len, int from)
49{
50 do {
51 const char *pch = static_cast<const char *>(memchr(str + from, '\r', size_t(len - from)));
52 if (pch != nullptr) {
53 int pos = int(pch - str);
54 if ((pos + 1) < len) {
55 if (*++pch == '\n') {
56 return pos;
57 } else {
58 from = ++pos;
59 continue;
60 }
61 }
62 }
63 break;
64 } while (true);
65
66 return -1;
67}
68
69void ProtocolHttp::parse(Socket *sock, QIODevice *io) const
70{
71 // Post buffering
72 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
73 if (protoRequest->status & Cutelyst::EngineRequest::Async) {
74 return;
75 }
76
77 if (protoRequest->connState == ProtoRequestHttp::ContentBody) {
78 qint64 bytesAvailable = io->bytesAvailable();
79 qint64 len;
80 qint64 remaining;
81
82 QIODevice *body = protoRequest->body;
83 do {
84 remaining = protoRequest->contentLength - body->size();
85 len = io->read(m_postBuffer, qMin(m_postBufferSize, remaining));
86 if (len == -1) {
87 qCWarning(C_SERVER_HTTP)
88 << "error while reading body" << len << protoRequest->headers;
89 sock->connectionClose();
90 return;
91 }
92 bytesAvailable -= len;
93 // qCDebug(C_SERVER_HTTP) << "WRITE body" << protoRequest->contentLength <<
94 // remaining << len << (remaining == len) << io->bytesAvailable();
95 body->write(m_postBuffer, len);
96 } while (bytesAvailable && remaining);
97
98 if (remaining == len) {
99 processRequest(sock, io);
100 }
101
102 return;
103 }
104
105 qint64 len = io->read(protoRequest->buffer + protoRequest->buf_size,
106 m_bufferSize - protoRequest->buf_size);
107 if (len == -1) {
108 qCWarning(C_SERVER_HTTP) << "Failed to read from socket" << io->errorString();
109 return;
110 }
111 protoRequest->buf_size += len;
112
113 while (protoRequest->last < protoRequest->buf_size) {
114 // qCDebug(C_SERVER_HTTP) << Q_FUNC_INFO << QByteArray(protoRequest->buffer,
115 // protoRequest->buf_size);
116 int ix = CrLfIndexIn(protoRequest->buffer, protoRequest->buf_size, protoRequest->last);
117 if (ix != -1) {
118 qint64 len = ix - protoRequest->beginLine;
119 char *ptr = protoRequest->buffer + protoRequest->beginLine;
120 protoRequest->beginLine = ix + 2;
121 protoRequest->last = protoRequest->beginLine;
122
123 if (protoRequest->connState == ProtoRequestHttp::MethodLine) {
124 if (useStats && protoRequest->startOfRequest == TimePointSteady{}) {
125 protoRequest->startOfRequest = std::chrono::steady_clock::now();
126 }
127
128 parseMethod(ptr, ptr + len, sock);
129 protoRequest->connState = ProtoRequestHttp::HeaderLine;
130 protoRequest->contentLength = -1;
131 protoRequest->headers = Cutelyst::Headers();
132 // qCDebug(C_SERVER_HTTP) << "--------" << protoRequest->method <<
133 // protoRequest->path << protoRequest->query <<
134 // protoRequest->protocol;
135
136 } else if (protoRequest->connState == ProtoRequestHttp::HeaderLine) {
137 if (len) {
138 parseHeader(ptr, ptr + len, sock);
139 } else {
140 if (protoRequest->contentLength > 0) {
141 protoRequest->connState = ProtoRequestHttp::ContentBody;
142 protoRequest->body = createBody(protoRequest->contentLength);
143 if (!protoRequest->body) {
144 qCWarning(C_SERVER_HTTP) << "error while creating body, closing socket";
145 sock->connectionClose();
146 return;
147 }
148
149 ptr += 2;
150 len =
151 qMin(protoRequest->contentLength,
152 static_cast<qint64>(protoRequest->buf_size - protoRequest->last));
153 // qCDebug(C_SERVER_HTTP) << "WRITE" <<
154 // protoRequest->contentLength << len;
155 if (len) {
156 protoRequest->body->write(ptr, len);
157 }
158 protoRequest->last += len;
159
160 if (protoRequest->contentLength > len) {
161 // qCDebug(C_SERVER_HTTP) << "WRITE more..."
162 // << protoRequest->contentLength << len;
163 // body is not completed yet
164 if (io->bytesAvailable()) {
165 // since we still have bytes available call this function
166 // so that the body parser reads the rest of available data
167 parse(sock, io);
168 }
169 return;
170 }
171 }
172
173 if (!processRequest(sock, io)) {
174 break;
175 }
176 }
177 }
178 } else {
179 if (protoRequest->startOfRequest == TimePointSteady{}) {
180 protoRequest->startOfRequest = std::chrono::steady_clock::now();
181 }
182 protoRequest->last = protoRequest->buf_size;
183 }
184 }
185
186 if (protoRequest->buf_size == m_bufferSize) {
187 // 414 Request-URI Too Long
188 }
189}
190
191ProtocolData *ProtocolHttp::createData(Socket *sock) const
192{
193 return new ProtoRequestHttp(sock, m_bufferSize);
194}
195
196bool ProtocolHttp::processRequest(Socket *sock, QIODevice *io) const
197{
198 auto request = static_cast<ProtoRequestHttp *>(sock->protoData);
199 // qCDebug(C_SERVER_HTTP) << "processRequest" << sock->protoData->contentLength;
200 if (request->body) {
201 request->body->seek(0);
202 }
203
204 // When enabled try to upgrade to H2C
205 if (m_upgradeH2c && m_upgradeH2c->upgradeH2C(sock, io, *request)) {
206 return false;
207 }
208
209 ++sock->processing;
210 sock->engine->processRequest(request);
211
212 if (request->websocketUpgraded) {
213 return false; // Must read remaining data
214 }
215
216 if (request->status & Cutelyst::EngineRequest::Async) {
217 return false; // Need to break now
218 }
219
220 return true;
221}
222
223void ProtocolHttp::parseMethod(const char *ptr, const char *end, Socket *sock) const
224{
225 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
226 const char *word_boundary = ptr;
227 while (*word_boundary != ' ' && word_boundary < end) {
228 ++word_boundary;
229 }
230 protoRequest->method = QByteArray(ptr, int(word_boundary - ptr));
231
232 // skip spaces
233 while (*word_boundary == ' ' && word_boundary < end) {
234 ++word_boundary;
235 }
236 ptr = word_boundary;
237
238 // find path end
239 while (*word_boundary != ' ' && *word_boundary != '?' && word_boundary < end) {
240 ++word_boundary;
241 }
242
243 // This will change the ptr but will only change less than size
244 protoRequest->setPath(const_cast<char *>(ptr), int(word_boundary - ptr));
245
246 if (*word_boundary == '?') {
247 ptr = word_boundary + 1;
248 while (*word_boundary != ' ' && word_boundary < end) {
249 ++word_boundary;
250 }
251 protoRequest->query = QByteArray(ptr, int(word_boundary - ptr));
252 } else {
253 protoRequest->query = QByteArray();
254 }
255
256 // skip spaces
257 while (*word_boundary == ' ' && word_boundary < end) {
258 ++word_boundary;
259 }
260 ptr = word_boundary;
261
262 while (*word_boundary != ' ' && word_boundary < end) {
263 ++word_boundary;
264 }
265 protoRequest->protocol = QByteArray(ptr, int(word_boundary - ptr));
266}
267
268void ProtocolHttp::parseHeader(const char *ptr, const char *end, Socket *sock) const
269{
270 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
271 const char *word_boundary = ptr;
272 while (*word_boundary != ':' && word_boundary < end) {
273 ++word_boundary;
274 }
275 const auto key = QByteArray(ptr, int(word_boundary - ptr));
276
277 while ((*word_boundary == ':' || *word_boundary == ' ') && word_boundary < end) {
278 ++word_boundary;
279 }
280 const auto value = QByteArray(word_boundary, int(end - word_boundary));
281
282 if (protoRequest->headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
283 key.compare("Connection", Qt::CaseInsensitive) == 0) {
284 if (value.compare("close", Qt::CaseInsensitive) == 0) {
285 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Close;
286 } else {
287 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
288 }
289 } else if (protoRequest->contentLength < 0 &&
290 key.compare("Content-Length", Qt::CaseInsensitive) == 0) {
291 bool ok;
292 qint64 cl = value.toLongLong(&ok);
293 if (ok && cl >= 0) {
294 protoRequest->contentLength = cl;
295 }
296 } else if (!protoRequest->headerHost && key.compare("Host", Qt::CaseInsensitive) == 0) {
297 protoRequest->serverAddress = value;
298 protoRequest->headerHost = true;
299 } else if (usingFrontendProxy) {
300 if (!protoRequest->X_Forwarded_For &&
301 (key.compare("X-Forwarded-For", Qt::CaseInsensitive) == 0 ||
302 key.compare("X-Real-Ip", Qt::CaseInsensitive) == 0)) {
303 // configure your reverse-proxy to list only one IP address
304 protoRequest->remoteAddress.setAddress(QString::fromLatin1(value));
305 protoRequest->remotePort = 0; // unknown
306 protoRequest->X_Forwarded_For = true;
307 } else if (!protoRequest->X_Forwarded_Host &&
308 key.compare("X-Forwarded-Host", Qt::CaseInsensitive) == 0) {
309 protoRequest->serverAddress = value;
310 protoRequest->X_Forwarded_Host = true;
311 protoRequest->headerHost = true; // ignore a following Host: header (if any)
312 } else if (!protoRequest->X_Forwarded_Proto &&
313 key.compare("X-Forwarded-Proto", Qt::CaseInsensitive) == 0) {
314 protoRequest->isSecure = (value.compare("https") == 0);
315 protoRequest->X_Forwarded_Proto = true;
316 }
317 }
318 protoRequest->headers.pushHeader(key, value);
319}
320
321ProtoRequestHttp::ProtoRequestHttp(Socket *sock, int bufferSize)
322 : ProtocolData(sock, bufferSize)
323{
324 isSecure = sock->isSecure;
325}
326
327ProtoRequestHttp::~ProtoRequestHttp()
328{
329}
330
331void ProtoRequestHttp::setupNewConnection(Socket *sock)
332{
333 serverAddress = sock->serverAddress;
334 remoteAddress = sock->remoteAddress;
335 remotePort = sock->remotePort;
336}
337
338bool ProtoRequestHttp::writeHeaders(quint16 status, const Cutelyst::Headers &headers)
339{
340 if (websocketUpgraded && status != Cutelyst::Response::SwitchingProtocols) {
341 qCWarning(C_SERVER_SOCK) << "Trying to write header while on an Websocket context";
342 return false;
343 }
344
345 int msgLen;
346 const char *msg = ServerEngine::httpStatusMessage(status, &msgLen);
347 QByteArray data(msg, msgLen);
348
349 const auto headersData = headers.data();
350 ProtoRequestHttp::HeaderConnection fallbackConnection = headerConnection;
351 headerConnection = ProtoRequestHttp::HeaderConnection::NotSet;
352
353 bool hasDate = false;
354 auto it = headersData.begin();
355 while (it != headersData.end()) {
356 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet &&
357 it->key.compare("Connection", Qt::CaseInsensitive) == 0) {
358 if (it->value.compare("close") == 0) {
359 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
360 } else if (it->value.compare("Upgrade") == 0) {
361 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
362 } else {
363 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
364 }
365 } else if (!hasDate && it->key.compare("Date", Qt::CaseInsensitive) == 0) {
366 hasDate = true;
367 }
368
369 data.append("\r\n");
370 data.append(it->key);
371 data.append(": ");
372 data.append(it->value);
373
374 ++it;
375 }
376
377 if (headerConnection == ProtoRequestHttp::HeaderConnection::NotSet) {
378 if (fallbackConnection == ProtoRequestHttp::HeaderConnection::Keep ||
379 (fallbackConnection != ProtoRequestHttp::HeaderConnection::Close &&
380 protocol.compare("HTTP/1.1") == 0)) {
381 headerConnection = ProtoRequestHttp::HeaderConnection::Keep;
382 data.append("\r\nConnection: keep-alive", 24);
383 } else {
384 headerConnection = ProtoRequestHttp::HeaderConnection::Close;
385 data.append("\r\nConnection: close", 19);
386 }
387 }
388
389 if (!hasDate) {
390 data.append(static_cast<ServerEngine *>(sock->engine)->lastDate());
391 }
392 data.append("\r\n\r\n", 4);
393
394 return io->write(data) == data.size();
395}
396
397qint64 ProtoRequestHttp::doWrite(const char *data, qint64 len)
398{
399 return io->write(data, len);
400}
401
403{
404 if (websocketUpgraded) {
405 // need 2 byte header
406 websocket_need = 2;
407 websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
408 buf_size = 0;
409 return;
410 }
411
412 if (!sock->requestFinished()) {
413 // disconnected
414 return;
415 }
416
417 if (headerConnection == ProtoRequestHttp::HeaderConnection::Close) {
418 sock->connectionClose();
419 return;
420 }
421
422 if (last < buf_size) {
423 // move pipelined request to 0
424 int remaining = buf_size - last;
425 memmove(buffer, buffer + last, size_t(remaining));
426 resetData();
427 buf_size = remaining;
428
429 if (status & EngineRequest::Async) {
430 sock->proto->parse(sock, io);
431 }
432 } else {
433 resetData();
434 }
435}
436
437bool ProtoRequestHttp::webSocketSendTextMessage(const QString &message)
438{
439 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
440 qCWarning(C_SERVER_HTTP)
441 << "Not sending websocket text message due connection header not upgraded"
442 << headerConnection << message.size();
443 return false;
444 }
445
446 const QByteArray rawMessage = message.toUtf8();
447 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(
448 ProtoRequestHttp::OpCodeText, quint64(rawMessage.size()));
449 return doWrite(headers) == headers.size() && doWrite(rawMessage) == rawMessage.size();
450}
451
452bool ProtoRequestHttp::webSocketSendBinaryMessage(const QByteArray &message)
453{
454 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
455 qCWarning(C_SERVER_HTTP)
456 << "Not sending websocket binary messagedue connection header not upgraded"
457 << headerConnection << message.size();
458 return false;
459 }
460
461 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(
462 ProtoRequestHttp::OpCodeBinary, quint64(message.size()));
463 return doWrite(headers) == headers.size() && doWrite(message) == message.size();
464}
465
466bool ProtoRequestHttp::webSocketSendPing(const QByteArray &payload)
467{
468 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
469 qCWarning(C_SERVER_HTTP) << "Not sending websocket ping due connection header not upgraded"
470 << headerConnection << payload.size();
471 return false;
472 }
473
474 const QByteArray rawMessage = payload.left(125);
475 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(
476 ProtoRequestHttp::OpCodePing, quint64(rawMessage.size()));
477 return doWrite(headers) == headers.size() && doWrite(rawMessage) == rawMessage.size();
478}
479
480bool ProtoRequestHttp::webSocketClose(quint16 code, const QString &reason)
481{
482 if (headerConnection != ProtoRequestHttp::HeaderConnection::Upgrade) {
483 qCWarning(C_SERVER_HTTP) << "Not sending websocket close due connection header not upgraded"
484 << headerConnection << code << reason;
485 return false;
486 }
487
488 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, code);
489 bool ret = doWrite(reply) == reply.size();
490 sock->requestFinished();
491 sock->connectionClose();
492 return ret;
493}
494
495void ProtoRequestHttp::socketDisconnected()
496{
497 if (websocketUpgraded) {
498 if (websocket_finn_opcode != 0x88) {
499 Q_EMIT context->request()->webSocketClosed(1005, QString{});
500 }
501 sock->requestFinished();
502 }
503}
504
505bool ProtoRequestHttp::webSocketHandshakeDo(const QByteArray &key,
506 const QByteArray &origin,
507 const QByteArray &protocol)
508{
509 if (headerConnection == ProtoRequestHttp::HeaderConnection::Upgrade) {
510 return true;
511 }
512
513 if (sock->proto->type() != Protocol::Type::Http11) {
514 qCWarning(C_SERVER_SOCK)
515 << "Upgrading a connection to websocket is only supported with the HTTP/1.1 protocol"
516 << typeid(sock->proto).name();
517 return false;
518 }
519
520 const Cutelyst::Headers requestHeaders = context->request()->headers();
521 Cutelyst::Response *response = context->response();
522 Cutelyst::Headers &headers = response->headers();
523
524 response->setStatus(Cutelyst::Response::SwitchingProtocols);
525 headers.setHeader("Upgrade"_qba, "WebSocket"_qba);
526 headers.setHeader("Connection"_qba, "Upgrade"_qba);
527 const auto localOrigin = origin.isEmpty() ? requestHeaders.header("Origin") : origin;
528 headers.setHeader("Sec-Websocket-Origin"_qba, localOrigin.isEmpty() ? "*"_qba : localOrigin);
529
530 if (!protocol.isEmpty()) {
531 headers.setHeader("Sec-Websocket-Protocol"_qba, protocol);
532 } else if (const auto wsProtocol = requestHeaders.header("Sec-Websocket-Protocol");
533 !wsProtocol.isEmpty()) {
534 headers.setHeader("Sec-Websocket-Protocol"_qba, wsProtocol);
535 }
536
537 const QByteArray localKey = key.isEmpty() ? requestHeaders.header("Sec-Websocket-Key") : key;
538 const QByteArray wsKey = localKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
539 if (wsKey.length() == 36) {
540 qCWarning(C_SERVER_SOCK) << "Missing websocket key";
541 return false;
542 }
543
544 const QByteArray wsAccept =
546 headers.setHeader("Sec-Websocket-Accept"_qba, wsAccept);
547
548 headerConnection = ProtoRequestHttp::HeaderConnection::Upgrade;
549 websocketUpgraded = true;
550 auto httpProto = static_cast<ProtocolHttp *>(sock->proto);
551 sock->proto = httpProto->m_websocketProto;
552
553 return writeHeaders(Cutelyst::Response::SwitchingProtocols, headers);
554}
555
556#include "moc_protocolhttp.cpp"
Request * request
Definition context.h:71
Response * response() const noexcept
Definition context.cpp:97
static const char * httpStatusMessage(quint16 status, int *len=nullptr)
Definition engine.cpp:104
void processRequest(EngineRequest *request)
Definition engine.cpp:251
Container for HTTP headers.
Definition headers.h:24
QVector< HeaderKeyValue > data() const
Definition headers.h:419
QByteArray header(QByteArrayView key) const noexcept
Definition headers.cpp:392
void setHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:436
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:72
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
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
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
CaseInsensitive