cutelyst 5.0.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
19namespace {
20/*
21 * Value for version component of FCGI_Header
22 */
23constexpr auto FCGI_VERSION_1 = 1;
24
25/*
26 * Values for type component of FCGI_Header
27 */
28constexpr auto FCGI_BEGIN_REQUEST = 1;
29constexpr auto FCGI_PARAMS = 4;
30constexpr auto FCGI_STDIN = 5;
31constexpr auto FCGI_STDOUT = 6;
32
33/*
34 * Mask for flags component of FCGI_BeginRequestBody
35 */
36constexpr auto FCGI_KEEP_CONN = 1;
37
38constexpr auto WSGI_OK = 0;
39constexpr auto WSGI_AGAIN = 1;
40constexpr auto WSGI_BODY = 2;
41constexpr auto WSGI_ERROR = -1;
42
43#define FCGI_ALIGNMENT 8
44#define FCGI_ALIGN(n) (((n) + (FCGI_ALIGNMENT - 1)) & ~(FCGI_ALIGNMENT - 1))
45
46struct 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
61struct 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
74using namespace Cutelyst;
75
76ProtocolFastCGI::ProtocolFastCGI(Server *wsgi)
77 : Protocol(wsgi)
78{
79}
80
81ProtocolFastCGI::~ProtocolFastCGI()
82{
83}
84
85Protocol::Type ProtocolFastCGI::type() const
86{
87 return Protocol::Type::FastCGI1;
88}
89
90quint16 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 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 uwsgi var: %.*s = %.*s", keylen, key, vallen, val);
149 // #endif
150
151 return keylen + vallen + 2 + 2;
152}
153
154int 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
209int ProtocolFastCGI::processPacket(ProtoRequestFastCGI *request) const
210{
211 Q_FOREVER
212 {
213 if (request->buf_size >= int(sizeof(struct fcgi_record))) {
214 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 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
282bool 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
294qint64 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
328void 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
391ProtocolData *ProtocolFastCGI::createData(Socket *sock) const
392{
393 return new ProtoRequestFastCGI(sock, m_bufferSize);
394}
395
396ProtoRequestFastCGI::ProtoRequestFastCGI(Socket *sock, int bufferSize)
397 : ProtocolData(sock, bufferSize)
398{
399}
400
401ProtoRequestFastCGI::~ProtoRequestFastCGI()
402{
403}
404
405void ProtoRequestFastCGI::setupNewConnection(Socket *sock)
406{
407 serverAddress = sock->serverAddress;
408 remoteAddress = sock->remoteAddress;
409 remotePort = sock->remotePort;
410}
411
412bool 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
445qint64 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 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"
void setPath(char *rawPath, const int len)
TimePointSteady startOfRequest
void processRequest(EngineRequest *request)
Definition engine.cpp:110
Container for HTTP headers.
Definition headers.h:24
QVector< HeaderKeyValue > data() const
Definition headers.h:420
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:464
void setContentType(const QByteArray &contentType)
Definition headers.cpp:77
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