cutelyst  5.0.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
protocolfastcgi.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2017-2018 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "protocolfastcgi.h"
6 
7 #include "server.h"
8 #include "socket.h"
9 
10 #include <Cutelyst/Context>
11 
12 #include <QBuffer>
13 #include <QCoreApplication>
14 #include <QLoggingCategory>
15 #include <QTemporaryFile>
16 
17 Q_LOGGING_CATEGORY(C_SERVER_FCGI, "cutelyst.server.fcgi", QtWarningMsg)
18 
19 namespace {
20 /*
21  * Value for version component of FCGI_Header
22  */
23 constexpr auto FCGI_VERSION_1 = 1;
24 
25 /*
26  * Values for type component of FCGI_Header
27  */
28 constexpr auto FCGI_BEGIN_REQUEST = 1;
29 constexpr auto FCGI_PARAMS = 4;
30 constexpr auto FCGI_STDIN = 5;
31 constexpr auto FCGI_STDOUT = 6;
32 
33 /*
34  * Mask for flags component of FCGI_BeginRequestBody
35  */
36 constexpr auto FCGI_KEEP_CONN = 1;
37 
38 constexpr auto WSGI_OK = 0;
39 constexpr auto WSGI_AGAIN = 1;
40 constexpr auto WSGI_BODY = 2;
41 constexpr auto WSGI_ERROR = -1;
42 
43 #define FCGI_ALIGNMENT 8
44 #define FCGI_ALIGN(n) (((n) + (FCGI_ALIGNMENT - 1)) & ~(FCGI_ALIGNMENT - 1))
45 
46 struct fcgi_record {
47  quint8 version;
48  quint8 type;
49  quint8 req1;
50  quint8 req0;
51  quint8 cl1;
52  quint8 cl0;
53  quint8 pad;
54  quint8 reserved;
55 };
56 
57 #ifdef Q_CC_MSVC
58 # pragma pack(push)
59 # pragma pack(1)
60 #endif
61 struct fcgi_begin_request_body {
62  quint16 role;
63  quint8 flags;
64  quint8 reserved[5];
65 }
66 #ifdef Q_CC_MSVC
67 ;
68 # pragma pack(pop)
69 #else
70 __attribute__((__packed__));
71 #endif
72 } // namespace
73 
74 using namespace Cutelyst;
75 
76 ProtocolFastCGI::ProtocolFastCGI(Server *server)
77  : Protocol(server)
78 {
79 }
80 
81 ProtocolFastCGI::~ProtocolFastCGI()
82 {
83 }
84 
85 Protocol::Type ProtocolFastCGI::type() const
86 {
87  return Protocol::Type::FastCGI1;
88 }
89 
90 quint16 ProtocolFastCGI::addHeader(ProtoRequestFastCGI *request,
91  const char *key,
92  quint16 keylen,
93  const char *val,
94  quint16 vallen) const
95 {
96  char *buffer = request->buffer + request->pktsize;
97  const char *watermark = request->buffer + m_bufferSize;
98 
99  if (buffer + keylen + vallen + 2 + 2 >= watermark) {
100  qCWarning(C_SERVER_FCGI,
101  "unable to add %.*s=%.*s to wsgi packet, consider increasing buffer size",
102  keylen,
103  key,
104  vallen,
105  val);
106  return 0;
107  }
108 
109  if (keylen > 5 && memcmp(key, "HTTP_", 5) == 0) {
110  const auto value = QByteArray(val, vallen);
111  if (!request->headerHost && memcmp(key + 5, "HOST", 4) == 0) {
112  request->serverAddress = value;
113  request->headerHost = true;
114  request->headers.pushHeader(QByteArrayLiteral("Host"), value);
115  } else {
116  const auto keyStr = QByteArray(key + 5, keylen - 5).replace('_', '-');
117  request->headers.pushHeader(keyStr, value);
118  }
119  } else if (memcmp(key, "REQUEST_METHOD", 14) == 0) {
120  request->method = QByteArray(val, vallen);
121  } else if (memcmp(key, "REQUEST_URI", 11) == 0) {
122  const char *pch = static_cast<const char *>(memchr(val, '?', vallen));
123  if (pch) {
124  int pos = int(pch - val);
125  request->setPath(const_cast<char *>(val), pos);
126  request->query = QByteArray(pch + 1, vallen - pos - 1);
127  } else {
128  request->setPath(const_cast<char *>(val), vallen);
129  request->query = QByteArray();
130  }
131  } else if (memcmp(key, "SERVER_PROTOCOL", 15) == 0) {
132  request->protocol = QByteArray(val, vallen);
133  } else if (memcmp(key, "REMOTE_ADDR", 11) == 0) {
134  request->remoteAddress.setAddress(QString::fromLatin1(val, vallen));
135  } else if (memcmp(key, "REMOTE_PORT", 11) == 0) {
136  request->remotePort = quint16(QByteArray(val, vallen).toUInt());
137  } else if (memcmp(key, "CONTENT_TYPE", 12) == 0) {
138  if (vallen) {
139  request->headers.setContentType(QByteArray{val, vallen});
140  }
141  } else if (memcmp(key, "CONTENT_LENGTH", 14) == 0) {
142  request->contentLength = QByteArray(val, vallen).toInt();
143  } else if (memcmp(key, "REQUEST_SCHEME", 14) == 0) {
144  request->isSecure = QByteArray(val, vallen) == "https" ? true : false;
145  }
146 
147  // #ifdef DEBUG
148  // qCDebug(C_SERVER_FCGI, "add server var: %.*s = %.*s", keylen, key, vallen, val);
149  // #endif
150 
151  return keylen + vallen + 2 + 2;
152 }
153 
154 int ProtocolFastCGI::parseHeaders(ProtoRequestFastCGI *request, const char *buf, quint16 len) const
155 {
156  quint32 j = 0;
157  while (j < len) {
158  quint32 keylen;
159  auto octet = static_cast<quint8>(buf[j]);
160  if (octet > 127) {
161  if (j + 4 >= len) {
162  return -1;
163  }
164 
165  // Ignore first bit
166  keylen = net_be32(&buf[j]) ^ 0x80000000;
167  j += 4;
168  } else {
169  if (++j >= len) {
170  return -1;
171  }
172  keylen = octet;
173  }
174 
175  quint32 vallen;
176  octet = static_cast<quint8>(buf[j]);
177  if (octet > 127) {
178  if (j + 4 >= len) {
179  return -1;
180  }
181 
182  // Ignore first bit
183  vallen = net_be32(&buf[j]) ^ 0x80000000;
184  j += 4;
185  } else {
186  if (++j >= len) {
187  return -1;
188  }
189  vallen = octet;
190  }
191 
192  if (j + (keylen + vallen) > len || keylen > 0xffff || vallen > 0xffff) {
193  return -1;
194  }
195 
196  quint16 pktsize =
197  addHeader(request, buf + j, quint16(keylen), buf + j + keylen, quint16(vallen));
198  if (pktsize == 0) {
199  return -1;
200  }
201  request->pktsize += pktsize;
202 
203  j += keylen + vallen;
204  }
205 
206  return 0;
207 }
208 
209 int ProtocolFastCGI::processPacket(ProtoRequestFastCGI *request) const
210 {
211  Q_FOREVER
212  {
213  if (request->buf_size >= int(sizeof(struct fcgi_record))) {
214  const auto *fr = reinterpret_cast<struct fcgi_record *>(request->buffer);
215 
216  quint8 fcgi_type = fr->type;
217  auto fcgi_len = quint16(fr->cl0 | (fr->cl1 << 8));
218  qint32 fcgi_all_len = sizeof(struct fcgi_record) + fcgi_len + fr->pad;
219  request->stream_id = quint16(fr->req0 | (fr->req1 << 8));
220 
221  // if STDIN, end of the loop
222  if (fcgi_type == FCGI_STDIN) {
223  if (fcgi_len == 0) {
224  memmove(request->buffer,
225  request->buffer + fcgi_all_len,
226  size_t(request->buf_size - fcgi_all_len));
227  request->buf_size -= fcgi_all_len;
228  return WSGI_OK;
229  }
230 
231  int content_size = request->buf_size - int(sizeof(struct fcgi_record));
232  if (!writeBody(request,
233  request->buffer + sizeof(struct fcgi_record),
234  qMin(content_size, int(fcgi_len)))) {
235  return WSGI_ERROR;
236  }
237 
238  if (content_size < fcgi_len) {
239  // we still need the rest of the pkt body
240  request->connState = ProtoRequestFastCGI::ContentBody;
241  request->pktsize = quint16(fcgi_len - content_size);
242  request->buf_size = fr->pad;
243  return WSGI_BODY;
244  }
245 
246  memmove(request->buffer,
247  request->buffer + fcgi_all_len,
248  size_t(request->buf_size - fcgi_all_len));
249  request->buf_size -= fcgi_all_len;
250  } else if (request->buf_size >= fcgi_all_len) {
251  // PARAMS ? (ignore other types)
252  if (fcgi_type == FCGI_PARAMS) {
253  if (parseHeaders(
254  request, request->buffer + sizeof(struct fcgi_record), fcgi_len)) {
255  return WSGI_ERROR;
256  }
257  } else if (fcgi_type == FCGI_BEGIN_REQUEST) {
258  const auto *brb = reinterpret_cast<struct fcgi_begin_request_body *>(
259  request->buffer + sizeof(struct fcgi_begin_request_body));
260  request->headerConnection = (brb->flags & FCGI_KEEP_CONN)
261  ? ProtoRequestFastCGI::HeaderConnection::Keep
262  : ProtoRequestFastCGI::HeaderConnection::Close;
263  request->contentLength = -1;
264  request->headers = Cutelyst::Headers();
265  request->connState = ProtoRequestFastCGI::MethodLine;
266  }
267 
268  memmove(request->buffer,
269  request->buffer + fcgi_all_len,
270  size_t(request->buf_size - fcgi_all_len));
271  request->buf_size -= fcgi_all_len;
272  } else {
273  break;
274  }
275  } else {
276  break;
277  }
278  }
279  return WSGI_AGAIN; // read again
280 }
281 
282 bool ProtocolFastCGI::writeBody(ProtoRequestFastCGI *request, char *buf, qint64 len) const
283 {
284  if (!request->body) {
285  request->body = createBody(request->contentLength);
286  if (!request->body) {
287  return false;
288  }
289  }
290 
291  return request->body->write(buf, len) == len;
292 }
293 
294 qint64 ProtocolFastCGI::readBody(Socket *sock, QIODevice *io, qint64 bytesAvailable) const
295 {
296  auto request = static_cast<ProtoRequestFastCGI *>(sock->protoData);
297  QIODevice *body = request->body;
298  int &pad = request->buf_size;
299  while (bytesAvailable && request->pktsize + pad) {
300  // We need to read and ignore ending PAD data
301  qint64 len = io->read(m_postBuffer,
302  qMin(m_postBufferSize, static_cast<qint64>(request->pktsize + pad)));
303  if (len == -1) {
304  sock->connectionClose();
305  return -1;
306  }
307  bytesAvailable -= len;
308 
309  if (len > request->pktsize) {
310  // We read past pktsize, so possibly PAD data was read too.
311  pad -= len - request->pktsize;
312  len = request->pktsize;
313  request->pktsize = 0;
314  } else {
315  request->pktsize -= len;
316  }
317 
318  body->write(m_postBuffer, len);
319  }
320 
321  if (request->pktsize + pad == 0) {
322  request->connState = ProtoRequestFastCGI::MethodLine;
323  }
324 
325  return bytesAvailable;
326 }
327 
328 void ProtocolFastCGI::parse(Socket *sock, QIODevice *io) const
329 {
330  // Post buffering
331  auto request = static_cast<ProtoRequestFastCGI *>(sock->protoData);
332  if (request->status & Cutelyst::EngineRequest::Async) {
333  return;
334  }
335 
336  qint64 bytesAvailable = io->bytesAvailable();
337  if (request->connState == ProtoRequestFastCGI::ContentBody) {
338  bytesAvailable = readBody(sock, io, bytesAvailable);
339  if (bytesAvailable == -1) {
340  return;
341  }
342  }
343 
344  do {
345  qint64 len =
346  io->read(request->buffer + request->buf_size, m_bufferSize - request->buf_size);
347  bytesAvailable -= len;
348 
349  if (len > 0) {
350  request->buf_size += len;
351 
352  if (useStats && request->startOfRequest == TimePointSteady{}) {
353  request->startOfRequest = std::chrono::steady_clock::now();
354  }
355 
356  if (request->buf_size < int(sizeof(struct fcgi_record))) {
357  // not enough data
358  continue;
359  }
360 
361  int ret = processPacket(request);
362  if (ret == WSGI_AGAIN) {
363  continue;
364  } else if (ret == WSGI_OK) {
365  sock->processing++;
366  if (request->body) {
367  request->body->seek(0);
368  }
369  sock->engine->processRequest(request);
370  if (request->status & Cutelyst::EngineRequest::Async) {
371  return; // We are in async mode
372  }
373  } else if (ret == WSGI_BODY) {
374  bytesAvailable = readBody(sock, io, bytesAvailable);
375  if (bytesAvailable == -1) {
376  return;
377  }
378  } else {
379  qCWarning(C_SERVER_FCGI) << "Failed to parse packet from"
380  << sock->remoteAddress.toString() << sock->remotePort;
381  // On error disconnect immediately
382  io->close();
383  }
384  } else {
385  qCWarning(C_SERVER_FCGI) << "Failed to read from socket" << io->errorString();
386  break;
387  }
388  } while (bytesAvailable);
389 }
390 
391 ProtocolData *ProtocolFastCGI::createData(Socket *sock) const
392 {
393  return new ProtoRequestFastCGI(sock, m_bufferSize);
394 }
395 
396 ProtoRequestFastCGI::ProtoRequestFastCGI(Socket *sock, int bufferSize)
397  : ProtocolData(sock, bufferSize)
398 {
399 }
400 
401 ProtoRequestFastCGI::~ProtoRequestFastCGI()
402 {
403 }
404 
405 void ProtoRequestFastCGI::setupNewConnection(Socket *sock)
406 {
407  serverAddress = sock->serverAddress;
408  remoteAddress = sock->remoteAddress;
409  remotePort = sock->remotePort;
410 }
411 
412 bool ProtoRequestFastCGI::writeHeaders(quint16 status, const Cutelyst::Headers &headers)
413 {
414  static thread_local QByteArray headerBuffer = ([]() -> QByteArray {
415  QByteArray ret;
416  ret.reserve(1024);
417  return ret;
418  }());
419 
420  headerBuffer.resize(0);
421  headerBuffer.append(QByteArrayLiteral("Status: ") + QByteArray::number(status));
422 
423  const auto headersData = headers.data();
424 
425  bool hasDate = false;
426  for (const auto &[key, value] : headersData) {
427  if (!hasDate && key.compare("Date", Qt::CaseInsensitive) == 0) {
428  hasDate = true;
429  }
430 
431  headerBuffer.append("\r\n");
432  headerBuffer.append(key);
433  headerBuffer.append(": ");
434  headerBuffer.append(value);
435  }
436 
437  if (!hasDate) {
438  headerBuffer.append(static_cast<ServerEngine *>(sock->engine)->lastDate());
439  }
440  headerBuffer.append("\r\n\r\n", 4);
441 
442  return doWrite(headerBuffer.constData(), headerBuffer.size()) != -1;
443 }
444 
445 qint64 ProtoRequestFastCGI::doWrite(const char *data, qint64 len)
446 {
447  // reset for next write
448  qint64 write_pos = 0;
449  quint32 proto_parser_status = 0;
450 
451  Q_FOREVER
452  {
453  // fastcgi packets are limited to 64k
454  quint8 padding = 0;
455 
456  if (proto_parser_status == 0) {
457  quint16 fcgi_len;
458  if (len - write_pos < 0xffff) {
459  fcgi_len = quint16(len - write_pos);
460  } else {
461  fcgi_len = 0xffff;
462  }
463  proto_parser_status = fcgi_len;
464 
465  struct fcgi_record fr;
466  fr.version = FCGI_VERSION_1;
467  fr.type = FCGI_STDOUT;
468 
469  fr.req1 = quint8(stream_id >> 8);
470  fr.req0 = quint8(stream_id);
471 
472  quint16 padded_len = FCGI_ALIGN(fcgi_len);
473  if (padded_len > fcgi_len) {
474  padding = quint8(padded_len - fcgi_len);
475  }
476  fr.pad = padding;
477 
478  fr.reserved = 0;
479  fr.cl1 = quint8(fcgi_len >> 8);
480  fr.cl0 = quint8(fcgi_len);
481  if (io->write(reinterpret_cast<const char *>(&fr), sizeof(struct fcgi_record)) !=
482  sizeof(struct fcgi_record)) {
483  return -1;
484  }
485  }
486 
487  qint64 wlen = io->write(data + write_pos, proto_parser_status);
488  if (padding) {
489  io->write("\0\0\0\0\0\0\0\0\0", padding);
490  }
491 
492  if (wlen > 0) {
493  write_pos += wlen;
494  proto_parser_status -= wlen;
495  if (write_pos == len) {
496  return write_pos;
497  }
498  continue;
499  }
500  if (wlen < 0) {
501  qCWarning(C_SERVER_FCGI) << "Writing socket error" << io->errorString();
502  }
503  return -1;
504  }
505 }
506 
507 #define FCGI_END_REQUEST_DATA "\1\x06\0\1\0\0\0\0\1\3\0\1\0\x08\0\0\0\0\0\0\0\0\0\0"
508 
510 {
511  char end_request[] = FCGI_END_REQUEST_DATA;
512  const char *sid = reinterpret_cast<char *>(&stream_id);
513  // update with request id
514  end_request[2] = sid[1];
515  end_request[3] = sid[0];
516  end_request[10] = sid[1];
517  end_request[11] = sid[0];
518  io->write(end_request, 24);
519 
520  if (!sock->requestFinished()) {
521  // disconnected
522  return;
523  }
524 
525  if (headerConnection == ProtoRequestFastCGI::HeaderConnection::Close) {
526  // Web server did not set FCGI_KEEP_CONN
527  sock->connectionClose();
528  return;
529  }
530 
531  const auto size = buf_size;
532  resetData();
533  buf_size = size;
534 
535  if (status & EngineRequest::Async && buf_size) {
536  sock->proto->parse(sock, io);
537  }
538 }
539 
540 #include "moc_protocolfastcgi.cpp"
int toInt(bool *ok, int base) const const
void reserve(qsizetype size)
virtual bool seek(qint64 pos)
bool writeHeaders(quint16 status, const Cutelyst::Headers &headers) override final
QString errorString() const const
void setPath(char *rawPath, int len)
Container for HTTP headers.
Definition: headers.h:23
Implements a web server.
Definition: server.h:59
void processRequest(EngineRequest *request)
Definition: engine.cpp:110
QString toString() const const
virtual void close()
bool setAddress(const QString &address)
qint64 doWrite(const char *data, qint64 len) override final
void processingFinished() override final
QByteArray read(qint64 maxSize)
TimePointSteady startOfRequest
QByteArray number(double n, char format, int precision)
void setContentType(const QByteArray &contentType)
Definition: headers.cpp:103
CaseInsensitive
QByteArray & replace(QByteArrayView before, QByteArrayView after)
qint64 write(const QByteArray &data)
QHostAddress remoteAddress
The Cutelyst namespace holds all public Cutelyst API.
QVector< HeaderKeyValue > data() const
Definition: headers.h:420
virtual qint64 bytesAvailable() const const
void resize(qsizetype newSize, char c)
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:489
QString fromLatin1(QByteArrayView str)