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 
25 using namespace Cutelyst;
26 
27 Q_LOGGING_CATEGORY(C_SERVER_HTTP, "cutelyst.server.http", QtWarningMsg)
28 Q_DECLARE_LOGGING_CATEGORY(C_SERVER_SOCK)
29 
30 ProtocolHttp::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 
38 ProtocolHttp::~ProtocolHttp()
39 {
40  delete m_websocketProto;
41 }
42 
43 Protocol::Type ProtocolHttp::type() const
44 {
45  return Protocol::Type::Http11;
46 }
47 
48 inline 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 
69 void 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 
191 ProtocolData *ProtocolHttp::createData(Socket *sock) const
192 {
193  return new ProtoRequestHttp(sock, m_bufferSize);
194 }
195 
196 bool 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 
223 void 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 
268 void 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 
321 ProtoRequestHttp::ProtoRequestHttp(Socket *sock, int bufferSize)
322  : ProtocolData(sock, bufferSize)
323 {
324  isSecure = sock->isSecure;
325 }
326 
327 ProtoRequestHttp::~ProtoRequestHttp()
328 {
329 }
330 
331 void ProtoRequestHttp::setupNewConnection(Socket *sock)
332 {
333  serverAddress = sock->serverAddress;
334  remoteAddress = sock->remoteAddress;
335  remotePort = sock->remotePort;
336 }
337 
338 bool 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 
397 qint64 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 
437 bool 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 
452 bool 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 
466 bool 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 
480 bool 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 
495 void 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 
505 bool 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:72
virtual bool seek(qint64 pos)
Headers & headers() noexcept
QString errorString() const const
static const char * httpStatusMessage(quint16 status, int *len=nullptr)
Definition: engine.cpp:104
qsizetype size() const const
bool isEmpty() const const
Container for HTTP headers.
Definition: headers.h:23
Implements a web server.
Definition: server.h:59
void processRequest(EngineRequest *request)
Definition: engine.cpp:251
qsizetype length() const const
void processingFinished() override final
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
A Cutelyst response.
Definition: response.h:28
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&#39;s also emitted when the connection closes without the client sending the close frame.
Headers headers() const noexcept
Definition: request.cpp:312
qint64 doWrite(const char *data, qint64 len) override final
CaseInsensitive
qint64 read(char *data, qint64 maxSize)
QHostAddress remoteAddress
The Cutelyst namespace holds all public Cutelyst API.
QVector< HeaderKeyValue > data() const
Definition: headers.h:419
virtual qint64 bytesAvailable() const const
bool writeHeaders(quint16 status, const Cutelyst::Headers &headers) override final
QByteArray & append(char ch)
QByteArray header(QByteArrayView key) const noexcept
Definition: headers.cpp:392
QString fromLatin1(QByteArrayView str)
QByteArray left(qsizetype len) const const
QByteArray hash(const QByteArray &data, QCryptographicHash::Algorithm method)
qint64 write(const char *data, qint64 maxSize)
QByteArray toBase64(QByteArray::Base64Options options) const const
qsizetype size() const const
Response * response() const noexcept
Definition: context.cpp:97
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
void setHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:436
QByteArray toUtf8() const const