commit 6cd3e76

default  ·  2023-10-17 18:02:08 +0000 UTC
parent d17aa86
Added FastCGI support.
6 files changed,  +389, -8
M snac.c
+2, -2
 1@@ -43,13 +43,13 @@ html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \
 2 http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \
 3  snac.h
 4 httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \
 5- xs_time.h xs_openssl.h snac.h
 6+ xs_time.h xs_openssl.h xs_fcgi.h snac.h
 7 main.o: main.c xs.h xs_io.h xs_json.h snac.h
 8 mastoapi.o: mastoapi.c xs.h xs_openssl.h xs_json.h xs_io.h xs_time.h \
 9  xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h snac.h
10 snac.o: snac.c xs.h xs_io.h xs_unicode.h xs_json.h xs_curl.h xs_openssl.h \
11  xs_socket.h xs_url.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h \
12- xs_glob.h xs_random.h xs_match.h snac.h
13+ xs_glob.h xs_random.h xs_match.h xs_fcgi.h snac.h
14 upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h
15 utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
16  xs_random.h snac.h
+18, -4
 1@@ -9,6 +9,7 @@
 2 #include "xs_mime.h"
 3 #include "xs_time.h"
 4 #include "xs_openssl.h"
 5+#include "xs_fcgi.h"
 6 
 7 #include "snac.h"
 8 
 9@@ -24,6 +25,8 @@
10 #include <poll.h>
11 #endif
12 
13+int use_fcgi = 0;
14+
15 int srv_running = 0;
16 
17 /* nodeinfo 2.0 template */
18@@ -199,8 +202,12 @@ void httpd_connection(FILE *f)
19     xs *etag     = NULL;
20     int p_size   = 0;
21     char *p;
22+    int fcgi_id;
23 
24-    req = xs_httpd_request(f, &payload, &p_size);
25+    if (use_fcgi)
26+        req = xs_fcgi_request(f, &payload, &p_size, &fcgi_id);
27+    else
28+        req = xs_httpd_request(f, &payload, &p_size);
29 
30     if (req == NULL) {
31         /* probably because a timeout */
32@@ -330,7 +337,10 @@ void httpd_connection(FILE *f)
33     headers = xs_dict_append(headers, "access-control-allow-origin", "*");
34     headers = xs_dict_append(headers, "access-control-allow-headers", "*");
35 
36-    xs_httpd_response(f, status, headers, body, b_size);
37+    if (use_fcgi)
38+        xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
39+    else
40+        xs_httpd_response(f, status, headers, body, b_size);
41 
42     fclose(f);
43 
44@@ -550,6 +560,8 @@ void httpd(void)
45     char sem_name[24];
46     sem_t anon_job_sem;
47 
48+    use_fcgi = xs_type(xs_dict_get(srv_config, "fastcgi")) == XSTYPE_TRUE;
49+
50     address = xs_dict_get(srv_config, "address");
51     port    = xs_number_str(xs_dict_get(srv_config, "port"));
52 
53@@ -564,7 +576,8 @@ void httpd(void)
54     signal(SIGTERM, term_handler);
55     signal(SIGINT,  term_handler);
56 
57-    srv_log(xs_fmt("httpd start %s:%s %s", address, port, USER_AGENT));
58+    srv_log(xs_fmt("httpd%s start %s:%s %s", use_fcgi ? " (FastCGI)" : "",
59+                    address, port, USER_AGENT));
60 
61     /* show the number of usable file descriptors */
62     struct rlimit r;
63@@ -651,5 +664,6 @@ void httpd(void)
64 
65     xs *uptime = xs_str_time_diff(time(NULL) - start_time);
66 
67-    srv_log(xs_fmt("httpd stop %s:%s (run time: %s)", address, port, uptime));
68+    srv_log(xs_fmt("httpd%s stop %s:%s (run time: %s)", use_fcgi ? " (FastCGI)" : "",
69+                address, port, uptime));
70 }
M snac.c
+1, -0
1@@ -19,6 +19,7 @@
2 #include "xs_glob.h"
3 #include "xs_random.h"
4 #include "xs_match.h"
5+#include "xs_fcgi.h"
6 
7 #include "snac.h"
8 
+2, -1
 1@@ -29,7 +29,8 @@ static const char *default_srv_config = "{"
 2     "\"admin_email\":          \"\","
 3     "\"admin_account\":        \"\","
 4     "\"title\":                \"\","
 5-    "\"short_description\":    \"\""
 6+    "\"short_description\":    \"\","
 7+    "\"fastcgi\":              false"
 8     "}";
 9 
10 static const char *default_css =
+365, -0
  1@@ -0,0 +1,365 @@
  2+/* copyright (c) 2022 - 2023 grunfink et al. / MIT license */
  3+
  4+/*
  5+    This is an intentionally-dead-simple FastCGI implementation;
  6+    only FCGI_RESPONDER type with *no* FCGI_KEEP_CON flag is supported.
  7+    This means only one simultaneous connection and no multiplexing.
  8+    It seems it's enough for nginx and OpenBSD's httpd, so here it goes.
  9+    Almost fully compatible with xs_httpd.h
 10+*/
 11+
 12+#ifndef _XS_FCGI_H
 13+
 14+#define _XS_FCGI_H
 15+
 16+ xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *id);
 17+ void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int id);
 18+
 19+
 20+#ifdef XS_IMPLEMENTATION
 21+
 22+struct fcgi_record_header {
 23+    unsigned char  version;
 24+    unsigned char  type;
 25+    unsigned short id;
 26+    unsigned short content_len;
 27+    unsigned char  padding_len;
 28+    unsigned char  reserved;
 29+} __attribute__((packed));
 30+
 31+/* version */
 32+
 33+#define FCGI_VERSION_1           1
 34+
 35+/* types */
 36+
 37+#define FCGI_BEGIN_REQUEST       1
 38+#define FCGI_ABORT_REQUEST       2
 39+#define FCGI_END_REQUEST         3
 40+#define FCGI_PARAMS              4
 41+#define FCGI_STDIN               5
 42+#define FCGI_STDOUT              6
 43+#define FCGI_STDERR              7
 44+#define FCGI_DATA                8
 45+#define FCGI_GET_VALUES          9
 46+#define FCGI_GET_VALUES_RESULT  10
 47+#define FCGI_UNKNOWN_TYPE       11
 48+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
 49+
 50+struct fcgi_begin_request {
 51+    unsigned short role;
 52+    unsigned char  flags;
 53+    unsigned char  reserved[5];
 54+} __attribute__((packed));
 55+
 56+/* roles */
 57+
 58+#define FCGI_RESPONDER  1
 59+#define FCGI_AUTHORIZER 2
 60+#define FCGI_FILTER     3
 61+
 62+/* flags */
 63+
 64+#define FCGI_KEEP_CONN  1
 65+
 66+struct fcgi_end_request {
 67+    unsigned int app_status;
 68+    unsigned char protocol_status;
 69+    unsigned char reserved[3];
 70+} __attribute__((packed));
 71+
 72+/* protocol statuses */
 73+
 74+#define FCGI_REQUEST_COMPLETE 0
 75+#define FCGI_CANT_MPX_CONN    1
 76+#define FCGI_OVERLOADED       2
 77+#define FCGI_UNKNOWN_ROLE     3
 78+
 79+
 80+xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *fcgi_id)
 81+/* keeps receiving FCGI packets until a complete request is finished */
 82+{
 83+    unsigned char p_buf[100000];
 84+    struct fcgi_record_header hdr;
 85+    struct fcgi_begin_request *breq = (struct fcgi_begin_request *)&p_buf;
 86+    unsigned char *buf = NULL;
 87+    int b_size = 0;
 88+    xs_dict *req = NULL;
 89+    unsigned char p_status = FCGI_REQUEST_COMPLETE;
 90+    xs *q_vars = NULL;
 91+    xs *p_vars = NULL;
 92+
 93+    *fcgi_id = -1;
 94+
 95+    for (;;) {
 96+        int sz, psz;
 97+
 98+        /* read the packet header */
 99+        if (fread(&hdr, sizeof(hdr), 1, f) != 1)
100+            break;
101+
102+        /* read the packet body */
103+        if ((psz = ntohs(hdr.content_len)) > 0) {
104+            if ((sz = fread(p_buf, 1, psz, f)) != psz)
105+                break;
106+        }
107+
108+        /* read (and drop) the padding */
109+        if (hdr.padding_len > 0)
110+            fread(p_buf + sz, 1, hdr.padding_len, f);
111+
112+        switch (hdr.type) {
113+        case FCGI_BEGIN_REQUEST:
114+            /* fail on unsupported roles */
115+            if (ntohs(breq->role) != FCGI_RESPONDER) {
116+                p_status = FCGI_UNKNOWN_ROLE;
117+                goto end;
118+            }
119+
120+            /* fail on unsupported flags */
121+            if (breq->flags & FCGI_KEEP_CONN) {
122+                p_status = FCGI_CANT_MPX_CONN;
123+                goto end;
124+            }
125+
126+            /* store the id for later */
127+            *fcgi_id = (int) hdr.id;
128+
129+            break;
130+
131+        case FCGI_PARAMS:
132+            /* unknown id? fail */
133+            if (hdr.id != *fcgi_id) {
134+                p_status = FCGI_CANT_MPX_CONN;
135+                goto end;
136+            }
137+
138+            if (psz) {
139+                /* add to the buffer */
140+                buf = xs_realloc(buf, b_size + psz);
141+                memcpy(buf + b_size, p_buf, psz);
142+                b_size += psz;
143+            }
144+            else {
145+                /* no size, so the packet is complete; process it */
146+                xs *cgi_vars = xs_dict_new();
147+
148+                req = xs_dict_new();
149+
150+                int offset = 0;
151+                while (offset < b_size) {
152+                    unsigned int ksz = buf[offset++];
153+
154+                    if (ksz & 0x80) {
155+                        ksz &= 0x7f;
156+                        ksz = (ksz << 8) | buf[offset++];
157+                        ksz = (ksz << 8) | buf[offset++];
158+                        ksz = (ksz << 8) | buf[offset++];
159+                    }
160+
161+                    unsigned int vsz = buf[offset++];
162+                    if (vsz & 0x80) {
163+                        vsz &= 0x7f;
164+                        vsz = (vsz << 8) | buf[offset++];
165+                        vsz = (vsz << 8) | buf[offset++];
166+                        vsz = (vsz << 8) | buf[offset++];
167+                    }
168+
169+                    /* get the key */
170+                    xs *k = xs_str_new_sz((char *)&buf[offset], ksz);
171+                    offset += ksz;
172+
173+                    /* get the value */
174+                    xs *v = xs_str_new_sz((char *)&buf[offset], vsz);
175+                    offset += vsz;
176+
177+                    cgi_vars = xs_dict_append(cgi_vars, k, v);
178+
179+                    if (strcmp(k, "REQUEST_METHOD") == 0)
180+                        req = xs_dict_append(req, "method", v);
181+                    else
182+                    if (strcmp(k, "REQUEST_URI") == 0) {
183+                        xs *udp = xs_url_dec(v);
184+                        xs *pnv = xs_split_n(udp, "?", 1);
185+
186+                        /* store the path */
187+                        req = xs_dict_append(req, "path", xs_list_get(pnv, 0));
188+
189+                        /* get the variables */
190+                        q_vars = xs_url_vars(xs_list_get(pnv, 1));
191+                    }
192+                    else
193+                    if (xs_match(k, "CONTENT_TYPE|CONTENT_LENGTH|HTTP_*")) {
194+                        if (xs_startswith(k, "HTTP_"))
195+                            k = xs_crop_i(k, 5, 0);
196+
197+                        k = xs_tolower_i(k);
198+                        k = xs_replace_i(k, "_", "-");
199+
200+                        req = xs_dict_append(req, k, v);
201+                    }
202+                }
203+
204+                req = xs_dict_append(req, "cgi_vars", cgi_vars);
205+
206+                buf    = xs_free(buf);
207+                b_size = 0;
208+            }
209+
210+            break;
211+
212+        case FCGI_STDIN:
213+            /* unknown id? fail */
214+            if (hdr.id != *fcgi_id) {
215+                p_status = FCGI_CANT_MPX_CONN;
216+                goto end;
217+            }
218+
219+            if (psz) {
220+                /* add to the buffer */
221+                buf = xs_realloc(buf, b_size + psz);
222+                memcpy(buf + b_size, p_buf, psz);
223+                b_size += psz;
224+            }
225+            else {
226+                /* the packet is complete; fill the payload info and finish */
227+                *payload = (xs_str *)buf;
228+                *p_size  = b_size;
229+
230+                const char *ct = xs_dict_get(req, "content-type");
231+
232+                if (*payload && ct && strcmp(ct, "application/x-www-form-urlencoded") == 0) {
233+                    xs *upl = xs_url_dec(*payload);
234+                    p_vars  = xs_url_vars(upl);
235+                }
236+                else
237+                if (*payload && ct && xs_startswith(ct, "multipart/form-data")) {
238+                    p_vars = xs_multipart_form_data(*payload, *p_size, ct);
239+                }
240+                else
241+                    p_vars = xs_dict_new();
242+
243+                if (q_vars == NULL)
244+                    q_vars = xs_dict_new();
245+
246+                req = xs_dict_append(req, "q_vars", q_vars);
247+                req = xs_dict_append(req, "p_vars", p_vars);
248+
249+                /* disconnect the payload from the buf variable */
250+                buf = NULL;
251+
252+                goto end;
253+            }
254+
255+            break;
256+        }
257+    }
258+
259+end:
260+    /* any kind of error? notify and cleanup */
261+    if (p_status != FCGI_REQUEST_COMPLETE) {
262+        struct fcgi_end_request ereq = {0};
263+
264+        /* complete the connection */
265+        ereq.app_status      = 0;
266+        ereq.protocol_status = p_status;
267+
268+        /* reuse header */
269+        hdr.type        = FCGI_ABORT_REQUEST;
270+        hdr.content_len = htons(sizeof(ereq));
271+
272+        fwrite(&hdr, sizeof(hdr), 1, f);
273+        fwrite(&ereq, sizeof(ereq), 1, f);
274+
275+        /* session closed */
276+        *fcgi_id = -1;
277+
278+        /* request dict is not valid */
279+        req = xs_free(req);
280+    }
281+
282+    xs_free(buf);
283+    return req;
284+}
285+
286+
287+void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int fcgi_id)
288+/* writes an FCGI response */
289+{
290+    struct fcgi_record_header hdr = {0};
291+    struct fcgi_end_request ereq = {0};
292+    xs *out = xs_str_new(NULL);
293+    xs_dict *p;
294+    xs_str *k;
295+    xs_str *v;
296+
297+    /* no previous id? it's an error */
298+    if (fcgi_id == -1)
299+        return;
300+
301+    /* create the headers */
302+    {
303+        xs *s1 = xs_fmt("status: %d\r\n", status);
304+        out = xs_str_cat(out, s1);
305+    }
306+
307+    p = headers;
308+    while (xs_dict_iter(&p, &k, &v)) {
309+        xs *s1 = xs_fmt("%s: %s\r\n", k, v);
310+        out = xs_str_cat(out, s1);
311+    }
312+
313+    if (b_size > 0) {
314+        xs *s1 = xs_fmt("content-length: %d\r\n", b_size);
315+        out = xs_str_cat(out, s1);
316+    }
317+
318+    out = xs_str_cat(out, "\r\n");
319+
320+    /* everything is text by now */
321+    int size = strlen(out);
322+
323+    /* add the body */
324+    if (body != NULL && b_size > 0)
325+        out = xs_append_m(out, body, b_size);
326+
327+    /* now send all the STDOUT in packets */
328+    hdr.version = FCGI_VERSION_1;
329+    hdr.type    = FCGI_STDOUT;
330+    hdr.id      = fcgi_id;
331+
332+    size += b_size;
333+    int offset = 0;
334+
335+    while (offset < size) {
336+        int sz = size - offset;
337+        if (sz > 0xffff)
338+            sz = 0xffff;
339+
340+        hdr.content_len = htons(sz);
341+
342+        fwrite(&hdr, sizeof(hdr), 1, f);
343+        fwrite(out + offset, 1, sz, f);
344+
345+        offset += sz;
346+    }
347+
348+    /* final STDOUT packet with 0 size */
349+    hdr.content_len = 0;
350+    fwrite(&hdr, sizeof(hdr), 1, f);
351+
352+    /* complete the connection */
353+    ereq.app_status      = 0;
354+    ereq.protocol_status = FCGI_REQUEST_COMPLETE;
355+
356+    hdr.type        = FCGI_END_REQUEST;
357+    hdr.content_len = htons(sizeof(ereq));
358+
359+    fwrite(&hdr, sizeof(hdr), 1, f);
360+    fwrite(&ereq, sizeof(ereq), 1, f);
361+}
362+
363+
364+#endif /* XS_IMPLEMENTATION */
365+
366+#endif /* XS_URL_H */
+1, -1
1@@ -1 +1 @@
2-/* a9cd3893c427bbcc478c5680245d435e415fd58a */
3+/* b109eea00ddc0765929e36ed1ca6f3f697262bb2 */