cutelyst 4.3.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
17Q_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
91using namespace Cutelyst;
92
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
120ProtocolFastCGI::ProtocolFastCGI(Server *wsgi)
121 : Protocol(wsgi)
122{
123}
124
125ProtocolFastCGI::~ProtocolFastCGI()
126{
127}
128
129Protocol::Type ProtocolFastCGI::type() const
130{
131 return Protocol::Type::FastCGI1;
132}
133
134quint16 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
198int 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
247int 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
320bool 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
332qint64 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
366void 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
429ProtocolData *ProtocolFastCGI::createData(Socket *sock) const
430{
431 return new ProtoRequestFastCGI(sock, m_bufferSize);
432}
433
434ProtoRequestFastCGI::ProtoRequestFastCGI(Socket *sock, int bufferSize)
435 : ProtocolData(sock, bufferSize)
436{
437}
438
439ProtoRequestFastCGI::~ProtoRequestFastCGI()
440{
441}
442
443void ProtoRequestFastCGI::setupNewConnection(Socket *sock)
444{
445 serverAddress = sock->serverAddress;
446 remoteAddress = sock->remoteAddress;
447 remotePort = sock->remotePort;
448}
449
450bool 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
486qint64 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)
TimePointSteady startOfRequest
void processRequest(EngineRequest *request)
Definition engine.cpp:251
Container for HTTP headers.
Definition headers.h:24
QVector< HeaderKeyValue > data() const
Definition headers.h:419
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:458
void setContentType(const QByteArray &contentType)
Definition headers.cpp:76
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 & append(QByteArrayView data)
const char * constData() const const
QByteArray number(double n, char format, int precision)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
void reserve(qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
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