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