commit 6cd3e76
default
·
2023-10-17 18:02:08 +0000 UTC
parent d17aa86
Added FastCGI support.
M
Makefile
+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
M
httpd.c
+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
M
utils.c
+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 */