Browse Source

Added FastCGI support.

default 1 year ago
parent
commit
6cd3e76890
6 changed files with 389 additions and 8 deletions
  1. 2 2
      Makefile
  2. 18 4
      httpd.c
  3. 1 0
      snac.c
  4. 2 1
      utils.c
  5. 365 0
      xs_fcgi.h
  6. 1 1
      xs_version.h

+ 2 - 2
Makefile

@@ -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 \
 http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \
  snac.h
 httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \
- xs_time.h xs_openssl.h snac.h
+ xs_time.h xs_openssl.h xs_fcgi.h snac.h
 main.o: main.c xs.h xs_io.h xs_json.h snac.h
 mastoapi.o: mastoapi.c xs.h xs_openssl.h xs_json.h xs_io.h xs_time.h \
  xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h snac.h
 snac.o: snac.c xs.h xs_io.h xs_unicode.h xs_json.h xs_curl.h xs_openssl.h \
  xs_socket.h xs_url.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h \
- xs_glob.h xs_random.h xs_match.h snac.h
+ xs_glob.h xs_random.h xs_match.h xs_fcgi.h snac.h
 upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h
 utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
  xs_random.h snac.h

+ 18 - 4
httpd.c

@@ -9,6 +9,7 @@
 #include "xs_mime.h"
 #include "xs_time.h"
 #include "xs_openssl.h"
+#include "xs_fcgi.h"
 
 #include "snac.h"
 
@@ -24,6 +25,8 @@
 #include <poll.h>
 #endif
 
+int use_fcgi = 0;
+
 int srv_running = 0;
 
 /* nodeinfo 2.0 template */
@@ -199,8 +202,12 @@ void httpd_connection(FILE *f)
     xs *etag     = NULL;
     int p_size   = 0;
     char *p;
+    int fcgi_id;
 
-    req = xs_httpd_request(f, &payload, &p_size);
+    if (use_fcgi)
+        req = xs_fcgi_request(f, &payload, &p_size, &fcgi_id);
+    else
+        req = xs_httpd_request(f, &payload, &p_size);
 
     if (req == NULL) {
         /* probably because a timeout */
@@ -330,7 +337,10 @@ void httpd_connection(FILE *f)
     headers = xs_dict_append(headers, "access-control-allow-origin", "*");
     headers = xs_dict_append(headers, "access-control-allow-headers", "*");
 
-    xs_httpd_response(f, status, headers, body, b_size);
+    if (use_fcgi)
+        xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
+    else
+        xs_httpd_response(f, status, headers, body, b_size);
 
     fclose(f);
 
@@ -550,6 +560,8 @@ void httpd(void)
     char sem_name[24];
     sem_t anon_job_sem;
 
+    use_fcgi = xs_type(xs_dict_get(srv_config, "fastcgi")) == XSTYPE_TRUE;
+
     address = xs_dict_get(srv_config, "address");
     port    = xs_number_str(xs_dict_get(srv_config, "port"));
 
@@ -564,7 +576,8 @@ void httpd(void)
     signal(SIGTERM, term_handler);
     signal(SIGINT,  term_handler);
 
-    srv_log(xs_fmt("httpd start %s:%s %s", address, port, USER_AGENT));
+    srv_log(xs_fmt("httpd%s start %s:%s %s", use_fcgi ? " (FastCGI)" : "",
+                    address, port, USER_AGENT));
 
     /* show the number of usable file descriptors */
     struct rlimit r;
@@ -651,5 +664,6 @@ void httpd(void)
 
     xs *uptime = xs_str_time_diff(time(NULL) - start_time);
 
-    srv_log(xs_fmt("httpd stop %s:%s (run time: %s)", address, port, uptime));
+    srv_log(xs_fmt("httpd%s stop %s:%s (run time: %s)", use_fcgi ? " (FastCGI)" : "",
+                address, port, uptime));
 }

+ 1 - 0
snac.c

@@ -19,6 +19,7 @@
 #include "xs_glob.h"
 #include "xs_random.h"
 #include "xs_match.h"
+#include "xs_fcgi.h"
 
 #include "snac.h"
 

+ 2 - 1
utils.c

@@ -29,7 +29,8 @@ static const char *default_srv_config = "{"
     "\"admin_email\":          \"\","
     "\"admin_account\":        \"\","
     "\"title\":                \"\","
-    "\"short_description\":    \"\""
+    "\"short_description\":    \"\","
+    "\"fastcgi\":              false"
     "}";
 
 static const char *default_css =

+ 365 - 0
xs_fcgi.h

@@ -0,0 +1,365 @@
+/* copyright (c) 2022 - 2023 grunfink et al. / MIT license */
+
+/*
+    This is an intentionally-dead-simple FastCGI implementation;
+    only FCGI_RESPONDER type with *no* FCGI_KEEP_CON flag is supported.
+    This means only one simultaneous connection and no multiplexing.
+    It seems it's enough for nginx and OpenBSD's httpd, so here it goes.
+    Almost fully compatible with xs_httpd.h
+*/
+
+#ifndef _XS_FCGI_H
+
+#define _XS_FCGI_H
+
+ xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *id);
+ void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int id);
+
+
+#ifdef XS_IMPLEMENTATION
+
+struct fcgi_record_header {
+    unsigned char  version;
+    unsigned char  type;
+    unsigned short id;
+    unsigned short content_len;
+    unsigned char  padding_len;
+    unsigned char  reserved;
+} __attribute__((packed));
+
+/* version */
+
+#define FCGI_VERSION_1           1
+
+/* types */
+
+#define FCGI_BEGIN_REQUEST       1
+#define FCGI_ABORT_REQUEST       2
+#define FCGI_END_REQUEST         3
+#define FCGI_PARAMS              4
+#define FCGI_STDIN               5
+#define FCGI_STDOUT              6
+#define FCGI_STDERR              7
+#define FCGI_DATA                8
+#define FCGI_GET_VALUES          9
+#define FCGI_GET_VALUES_RESULT  10
+#define FCGI_UNKNOWN_TYPE       11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+struct fcgi_begin_request {
+    unsigned short role;
+    unsigned char  flags;
+    unsigned char  reserved[5];
+} __attribute__((packed));
+
+/* roles */
+
+#define FCGI_RESPONDER  1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER     3
+
+/* flags */
+
+#define FCGI_KEEP_CONN  1
+
+struct fcgi_end_request {
+    unsigned int app_status;
+    unsigned char protocol_status;
+    unsigned char reserved[3];
+} __attribute__((packed));
+
+/* protocol statuses */
+
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN    1
+#define FCGI_OVERLOADED       2
+#define FCGI_UNKNOWN_ROLE     3
+
+
+xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *fcgi_id)
+/* keeps receiving FCGI packets until a complete request is finished */
+{
+    unsigned char p_buf[100000];
+    struct fcgi_record_header hdr;
+    struct fcgi_begin_request *breq = (struct fcgi_begin_request *)&p_buf;
+    unsigned char *buf = NULL;
+    int b_size = 0;
+    xs_dict *req = NULL;
+    unsigned char p_status = FCGI_REQUEST_COMPLETE;
+    xs *q_vars = NULL;
+    xs *p_vars = NULL;
+
+    *fcgi_id = -1;
+
+    for (;;) {
+        int sz, psz;
+
+        /* read the packet header */
+        if (fread(&hdr, sizeof(hdr), 1, f) != 1)
+            break;
+
+        /* read the packet body */
+        if ((psz = ntohs(hdr.content_len)) > 0) {
+            if ((sz = fread(p_buf, 1, psz, f)) != psz)
+                break;
+        }
+
+        /* read (and drop) the padding */
+        if (hdr.padding_len > 0)
+            fread(p_buf + sz, 1, hdr.padding_len, f);
+
+        switch (hdr.type) {
+        case FCGI_BEGIN_REQUEST:
+            /* fail on unsupported roles */
+            if (ntohs(breq->role) != FCGI_RESPONDER) {
+                p_status = FCGI_UNKNOWN_ROLE;
+                goto end;
+            }
+
+            /* fail on unsupported flags */
+            if (breq->flags & FCGI_KEEP_CONN) {
+                p_status = FCGI_CANT_MPX_CONN;
+                goto end;
+            }
+
+            /* store the id for later */
+            *fcgi_id = (int) hdr.id;
+
+            break;
+
+        case FCGI_PARAMS:
+            /* unknown id? fail */
+            if (hdr.id != *fcgi_id) {
+                p_status = FCGI_CANT_MPX_CONN;
+                goto end;
+            }
+
+            if (psz) {
+                /* add to the buffer */
+                buf = xs_realloc(buf, b_size + psz);
+                memcpy(buf + b_size, p_buf, psz);
+                b_size += psz;
+            }
+            else {
+                /* no size, so the packet is complete; process it */
+                xs *cgi_vars = xs_dict_new();
+
+                req = xs_dict_new();
+
+                int offset = 0;
+                while (offset < b_size) {
+                    unsigned int ksz = buf[offset++];
+
+                    if (ksz & 0x80) {
+                        ksz &= 0x7f;
+                        ksz = (ksz << 8) | buf[offset++];
+                        ksz = (ksz << 8) | buf[offset++];
+                        ksz = (ksz << 8) | buf[offset++];
+                    }
+
+                    unsigned int vsz = buf[offset++];
+                    if (vsz & 0x80) {
+                        vsz &= 0x7f;
+                        vsz = (vsz << 8) | buf[offset++];
+                        vsz = (vsz << 8) | buf[offset++];
+                        vsz = (vsz << 8) | buf[offset++];
+                    }
+
+                    /* get the key */
+                    xs *k = xs_str_new_sz((char *)&buf[offset], ksz);
+                    offset += ksz;
+
+                    /* get the value */
+                    xs *v = xs_str_new_sz((char *)&buf[offset], vsz);
+                    offset += vsz;
+
+                    cgi_vars = xs_dict_append(cgi_vars, k, v);
+
+                    if (strcmp(k, "REQUEST_METHOD") == 0)
+                        req = xs_dict_append(req, "method", v);
+                    else
+                    if (strcmp(k, "REQUEST_URI") == 0) {
+                        xs *udp = xs_url_dec(v);
+                        xs *pnv = xs_split_n(udp, "?", 1);
+
+                        /* store the path */
+                        req = xs_dict_append(req, "path", xs_list_get(pnv, 0));
+
+                        /* get the variables */
+                        q_vars = xs_url_vars(xs_list_get(pnv, 1));
+                    }
+                    else
+                    if (xs_match(k, "CONTENT_TYPE|CONTENT_LENGTH|HTTP_*")) {
+                        if (xs_startswith(k, "HTTP_"))
+                            k = xs_crop_i(k, 5, 0);
+
+                        k = xs_tolower_i(k);
+                        k = xs_replace_i(k, "_", "-");
+
+                        req = xs_dict_append(req, k, v);
+                    }
+                }
+
+                req = xs_dict_append(req, "cgi_vars", cgi_vars);
+
+                buf    = xs_free(buf);
+                b_size = 0;
+            }
+
+            break;
+
+        case FCGI_STDIN:
+            /* unknown id? fail */
+            if (hdr.id != *fcgi_id) {
+                p_status = FCGI_CANT_MPX_CONN;
+                goto end;
+            }
+
+            if (psz) {
+                /* add to the buffer */
+                buf = xs_realloc(buf, b_size + psz);
+                memcpy(buf + b_size, p_buf, psz);
+                b_size += psz;
+            }
+            else {
+                /* the packet is complete; fill the payload info and finish */
+                *payload = (xs_str *)buf;
+                *p_size  = b_size;
+
+                const char *ct = xs_dict_get(req, "content-type");
+
+                if (*payload && ct && strcmp(ct, "application/x-www-form-urlencoded") == 0) {
+                    xs *upl = xs_url_dec(*payload);
+                    p_vars  = xs_url_vars(upl);
+                }
+                else
+                if (*payload && ct && xs_startswith(ct, "multipart/form-data")) {
+                    p_vars = xs_multipart_form_data(*payload, *p_size, ct);
+                }
+                else
+                    p_vars = xs_dict_new();
+
+                if (q_vars == NULL)
+                    q_vars = xs_dict_new();
+
+                req = xs_dict_append(req, "q_vars", q_vars);
+                req = xs_dict_append(req, "p_vars", p_vars);
+
+                /* disconnect the payload from the buf variable */
+                buf = NULL;
+
+                goto end;
+            }
+
+            break;
+        }
+    }
+
+end:
+    /* any kind of error? notify and cleanup */
+    if (p_status != FCGI_REQUEST_COMPLETE) {
+        struct fcgi_end_request ereq = {0};
+
+        /* complete the connection */
+        ereq.app_status      = 0;
+        ereq.protocol_status = p_status;
+
+        /* reuse header */
+        hdr.type        = FCGI_ABORT_REQUEST;
+        hdr.content_len = htons(sizeof(ereq));
+
+        fwrite(&hdr, sizeof(hdr), 1, f);
+        fwrite(&ereq, sizeof(ereq), 1, f);
+
+        /* session closed */
+        *fcgi_id = -1;
+
+        /* request dict is not valid */
+        req = xs_free(req);
+    }
+
+    xs_free(buf);
+    return req;
+}
+
+
+void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int fcgi_id)
+/* writes an FCGI response */
+{
+    struct fcgi_record_header hdr = {0};
+    struct fcgi_end_request ereq = {0};
+    xs *out = xs_str_new(NULL);
+    xs_dict *p;
+    xs_str *k;
+    xs_str *v;
+
+    /* no previous id? it's an error */
+    if (fcgi_id == -1)
+        return;
+
+    /* create the headers */
+    {
+        xs *s1 = xs_fmt("status: %d\r\n", status);
+        out = xs_str_cat(out, s1);
+    }
+
+    p = headers;
+    while (xs_dict_iter(&p, &k, &v)) {
+        xs *s1 = xs_fmt("%s: %s\r\n", k, v);
+        out = xs_str_cat(out, s1);
+    }
+
+    if (b_size > 0) {
+        xs *s1 = xs_fmt("content-length: %d\r\n", b_size);
+        out = xs_str_cat(out, s1);
+    }
+
+    out = xs_str_cat(out, "\r\n");
+
+    /* everything is text by now */
+    int size = strlen(out);
+
+    /* add the body */
+    if (body != NULL && b_size > 0)
+        out = xs_append_m(out, body, b_size);
+
+    /* now send all the STDOUT in packets */
+    hdr.version = FCGI_VERSION_1;
+    hdr.type    = FCGI_STDOUT;
+    hdr.id      = fcgi_id;
+
+    size += b_size;
+    int offset = 0;
+
+    while (offset < size) {
+        int sz = size - offset;
+        if (sz > 0xffff)
+            sz = 0xffff;
+
+        hdr.content_len = htons(sz);
+
+        fwrite(&hdr, sizeof(hdr), 1, f);
+        fwrite(out + offset, 1, sz, f);
+
+        offset += sz;
+    }
+
+    /* final STDOUT packet with 0 size */
+    hdr.content_len = 0;
+    fwrite(&hdr, sizeof(hdr), 1, f);
+
+    /* complete the connection */
+    ereq.app_status      = 0;
+    ereq.protocol_status = FCGI_REQUEST_COMPLETE;
+
+    hdr.type        = FCGI_END_REQUEST;
+    hdr.content_len = htons(sizeof(ereq));
+
+    fwrite(&hdr, sizeof(hdr), 1, f);
+    fwrite(&ereq, sizeof(ereq), 1, f);
+}
+
+
+#endif /* XS_IMPLEMENTATION */
+
+#endif /* XS_URL_H */

+ 1 - 1
xs_version.h

@@ -1 +1 @@
-/* a9cd3893c427bbcc478c5680245d435e415fd58a */
+/* b109eea00ddc0765929e36ed1ca6f3f697262bb2 */