diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-07-21 01:07:57 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-07-21 01:40:55 +0200 |
| commit | 7fe639b3ba7a253627d2cd34f3b97bd95b0a90b3 (patch) | |
| tree | ab50067686bfdc87b35ddfa29112af5ca0440574 | |
| parent | 1d4480c0f36f9d4e75c00e58c065b4c43e53b4f3 (diff) | |
| download | slcl-7fe639b3ba7a253627d2cd34f3b97bd95b0a90b3.tar.gz | |
Remove files now provided by slweb
| -rw-r--r-- | handler.c | 315 | ||||
| -rw-r--r-- | handler.h | 24 | ||||
| -rw-r--r-- | html.c | 301 | ||||
| -rw-r--r-- | html.h | 15 | ||||
| -rw-r--r-- | http.c | 1964 | ||||
| -rw-r--r-- | http.h | 106 | ||||
| -rw-r--r-- | server.c | 373 | ||||
| -rw-r--r-- | server.h | 15 |
8 files changed, 0 insertions, 3113 deletions
diff --git a/handler.c b/handler.c deleted file mode 100644 index 1d7e922..0000000 --- a/handler.c +++ /dev/null @@ -1,315 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "handler.h" -#include "http.h" -#include "server.h" -#include "wildcard_cmp.h" -#include <errno.h> -#include <stdbool.h> -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - -struct handler -{ - struct handler_cfg cfg; - struct elem - { - char *url; - enum http_op op; - handler_fn f; - void *user; - } *elem; - - struct server *server; - struct client - { - struct handler *h; - struct server_client *c; - struct http_ctx *http; - struct client *next; - } *clients; - - size_t n_cfg; -}; - -static int on_read(void *const buf, const size_t n, void *const user) -{ - struct client *const c = user; - - return server_read(buf, n, c->c); -} - -static int on_write(const void *const buf, const size_t n, void *const user) -{ - struct client *const c = user; - - return server_write(buf, n, c->c); -} - -static int on_payload(const struct http_payload *const p, - struct http_response *const r, void *const user) -{ - struct client *const c = user; - struct handler *const h = c->h; - - for (size_t i = 0; i < h->n_cfg; i++) - { - const struct elem *const e = &h->elem[i]; - - if (e->op == p->op && !wildcard_cmp(p->resource, e->url, true)) - return e->f(p, r, e->user); - } - - fprintf(stderr, "Not found: %s\n", p->resource); - - *r = (const struct http_response) - { - .status = HTTP_STATUS_NOT_FOUND - }; - - return 0; -} - -static int on_length(const unsigned long long len, - const struct http_cookie *const c, struct http_response *const r, - void *const user) -{ - struct client *const cl = user; - struct handler *const h = cl->h; - - if (h->cfg.length) - return h->cfg.length(len, c, r, h->cfg.user); - - return 0; -} - -static struct client *find_or_alloc_client(struct handler *const h, - struct server_client *const c) -{ - for (struct client *cl = h->clients; cl; cl = cl->next) - { - if (cl->c == c) - return cl; - } - - struct client *const ret = malloc(sizeof *ret); - - if (!ret) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - return NULL; - } - - const struct http_cfg cfg = - { - .read = on_read, - .write = on_write, - .payload = on_payload, - .length = on_length, - .user = ret, - .tmpdir = h->cfg.tmpdir - }; - - *ret = (const struct client) - { - .c = c, - .h = h, - .http = http_alloc(&cfg) - }; - - if (!ret->http) - { - fprintf(stderr, "%s: http_alloc failed\n", __func__); - return NULL; - } - - if (!h->clients) - h->clients = ret; - else - { - for (struct client *c = h->clients; c; c = c->next) - if (!c->next) - { - c->next = ret; - break; - } - } - - return ret; -} - -static void client_free(struct client *const c) -{ - if (c) - http_free(c->http); - - free(c); -} - -static int remove_client_from_list(struct handler *const h, - struct client *const c) -{ - int ret = -1; - - if (server_client_close(h->server, c->c)) - { - fprintf(stderr, "%s: server_client_close failed\n", - __func__); - goto end; - } - - for (struct client *cl = h->clients, *prev = NULL; cl; - prev = cl, cl = cl->next) - { - if (cl == c) - { - if (!prev) - h->clients = c->next; - else - prev->next = cl->next; - - break; - } - } - - ret = 0; - -end: - client_free(c); - return ret; -} - -int handler_listen(struct handler *const h, const short port) -{ - if (!(h->server = server_init(port))) - { - fprintf(stderr, "%s: server_init failed\n", __func__); - return -1; - } - - for (;;) - { - bool exit, io; - struct server_client *const c = server_poll(h->server, &io, &exit); - - if (exit) - { - printf("Exiting...\n"); - break; - } - else if (!c) - { - fprintf(stderr, "%s: server_poll failed\n", __func__); - return -1; - } - - struct client *const cl = find_or_alloc_client(h, c); - - if (!cl) - { - fprintf(stderr, "%s: find_or_alloc_client failed\n", __func__); - return -1; - } - else if (io) - { - bool write, close; - const int res = http_update(cl->http, &write, &close); - - if (res || close) - { - if (res < 0) - { - fprintf(stderr, "%s: http_update failed\n", __func__); - return -1; - } - else if (remove_client_from_list(h, cl)) - { - fprintf(stderr, "%s: remove_client_from_list failed\n", - __func__); - return -1; - } - } - else - server_client_write_pending(cl->c, write); - } - } - - return 0; -} - -static void free_clients(struct handler *const h) -{ - for (struct client *c = h->clients; c;) - { - struct client *const next = c->next; - - server_client_close(h->server, c->c); - client_free(c); - c = next; - } -} - -void handler_free(struct handler *const h) -{ - if (h) - { - for (size_t i = 0; i < h->n_cfg; i++) - free(h->elem[i].url); - - free(h->elem); - free_clients(h); - server_close(h->server); - } - - free(h); -} - -struct handler *handler_alloc(const struct handler_cfg *const cfg) -{ - struct handler *const h = malloc(sizeof *h); - - if (!h) - { - fprintf(stderr, "%s: malloc(3) handler: %s\n", - __func__, strerror(errno)); - return NULL; - } - - *h = (const struct handler){.cfg = *cfg}; - return h; -} - -int handler_add(struct handler *const h, const char *url, - const enum http_op op, const handler_fn f, void *const user) -{ - const size_t n = h->n_cfg + 1; - struct elem *const elem = realloc(h->elem, n * sizeof *h->elem); - - if (!elem) - { - fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); - return -1; - } - - struct elem *const e = &elem[h->n_cfg]; - - *e = (const struct elem) - { - .url = strdup(url), - .op = op, - .f = f, - .user = user - }; - - if (!e->url) - { - fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); - return -1; - } - - h->elem = elem; - h->n_cfg = n; - return 0; -} diff --git a/handler.h b/handler.h deleted file mode 100644 index 38de97b..0000000 --- a/handler.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef HANDLER_H -#define HANDLER_H - -#include "http.h" -#include <stddef.h> - -typedef int (*handler_fn)(const struct http_payload *p, - struct http_response *r, void *user); - -struct handler_cfg -{ - const char *tmpdir; - int (*length)(unsigned long long len, const struct http_cookie *c, - struct http_response *r, void *user); - void *user; -}; - -struct handler *handler_alloc(const struct handler_cfg *cfg); -void handler_free(struct handler *h); -int handler_add(struct handler *h, const char *url, enum http_op op, - handler_fn f, void *user); -int handler_listen(struct handler *h, short port); - -#endif /* HANDLER_H */ @@ -1,301 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "html.h" -#include <dynstr.h> -#include <errno.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -struct html_node -{ - struct html_attribute - { - char *attr, *value; - } *attrs; - - char *element, *value; - size_t n; - struct html_node *child, *sibling; -}; - -static char *html_encode(const char *s) -{ - struct dynstr d; - - dynstr_init(&d); - - if (!*s && dynstr_append(&d, "")) - { - fprintf(stderr, "%s: dynstr_append empty failed\n", __func__); - goto failure; - } - - while (*s) - { - static const struct esc - { - char c; - const char *str; - } esc[] = - { - {.c = '<', .str = ">"}, - {.c = '>', .str = "<"}, - {.c = '&', .str = "&"}, - {.c = '\"', .str = """}, - {.c = '\'', .str = "'"} - }; - - char buf[sizeof "a"] = {0}; - const char *str = NULL; - - for (size_t i = 0; i < sizeof esc / sizeof *esc; i++) - { - const struct esc *const e = &esc[i]; - - if (*s == e->c) - { - str = e->str; - break; - } - } - - if (!str) - { - *buf = *s; - str = buf; - } - - if (dynstr_append(&d, "%s", str)) - { - fprintf(stderr, "%s: dynstr_append failed\n", __func__); - goto failure; - } - - s++; - } - - return d.str; - -failure: - dynstr_free(&d); - return NULL; -} - -int html_node_set_value(struct html_node *const n, const char *const val) -{ - if (!(n->value = html_encode(val))) - { - fprintf(stderr, "%s: html_encode failed\n", __func__); - return -1; - } - - return 0; -} - -int html_node_set_value_unescaped(struct html_node *const n, - const char *const val) -{ - if (!(n->value = strdup(val))) - { - fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); - return -1; - } - - return 0; -} - -int html_node_add_attr(struct html_node *const n, const char *const attr, - const char *const val) -{ - const size_t el = n->n + 1; - struct html_attribute *const attrs = realloc(n->attrs, - el * sizeof *n->attrs), *a = NULL; - - if (!attrs) - { - fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); - return -1; - } - - a = &attrs[n->n]; - *a = (const struct html_attribute){0}; - - if (!(a->attr = strdup(attr)) - || (val && !(a->value = strdup(val)))) - { - fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); - free(a->attr); - free(a->value); - return -1; - } - - n->attrs = attrs; - n->n = el; - return 0; -} - -void html_node_add_sibling(struct html_node *const n, - struct html_node *const sibling) -{ - for (struct html_node *c = n; c; c = c->sibling) - if (!c->sibling) - { - c->sibling = sibling; - break; - } -} - -struct html_node *html_node_add_child(struct html_node *const n, - const char *const element) -{ - struct html_node *const child = html_node_alloc(element); - - if (!child) - return NULL; - else if (n->child) - html_node_add_sibling(n->child, child); - else - n->child = child; - - return child; -} - -int serialize_node(struct dynstr *const d, const struct html_node *const n, - const unsigned level) -{ - for (unsigned i = 0; i < level; i++) - dynstr_append(d, "\t"); - - dynstr_append_or_ret_nonzero(d, "<%s", n->element); - - if (n->n) - dynstr_append_or_ret_nonzero(d, " "); - - for (size_t i = 0; i < n->n; i++) - { - const struct html_attribute *const a = &n->attrs[i]; - - if (a->value) - dynstr_append_or_ret_nonzero(d, "%s=\"%s\"", a->attr, a->value); - else - dynstr_append_or_ret_nonzero(d, "%s", a->attr); - - if (i + 1 < n->n) - dynstr_append_or_ret_nonzero(d, " "); - } - - if (!n->value && !n->child) - dynstr_append_or_ret_nonzero(d, "/>"); - else - { - dynstr_append_or_ret_nonzero(d, ">"); - - if (n->value) - dynstr_append_or_ret_nonzero(d, "%s", n->value); - - if (n->child) - { - dynstr_append_or_ret_nonzero(d, "\n"); - - if (serialize_node(d, n->child, level + 1)) - { - fprintf(stderr, "%s: serialize_node failed\n", __func__); - return -1; - } - - for (unsigned i = 0; i < level; i++) - dynstr_append(d, "\t"); - } - - dynstr_append_or_ret_nonzero(d, "</%s>", n->element); - } - - /* TODO: print siblings */ - - dynstr_append_or_ret_nonzero(d, "\n"); - - if (n->sibling) - return serialize_node(d, n->sibling, level); - - return 0; -} - -int html_serialize(const struct html_node *const n, struct dynstr *const d) -{ - return serialize_node(d, n, 0); -} - -static void html_attribute_free(struct html_attribute *const a) -{ - if (a) - { - free(a->attr); - free(a->value); - } -} - -void html_node_free(struct html_node *const n) -{ - if (n) - { - struct html_node *s = n->sibling; - - html_node_free(n->child); - - while (s) - { - struct html_node *const next = s->sibling; - - html_node_free(s->child); - free(s->element); - free(s->value); - - for (size_t i = 0 ; i < s->n; i++) - html_attribute_free(&s->attrs[i]); - - free(s->attrs); - free(s); - s = next; - } - - free(n->element); - free(n->value); - - for (size_t i = 0 ; i < n->n; i++) - html_attribute_free(&n->attrs[i]); - - free(n->attrs); - } - - free(n); -} - -struct html_node *html_node_alloc(const char *const element) -{ - struct html_node *const n = malloc(sizeof *n); - - if (!n) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - goto failure; - } - - *n = (const struct html_node) - { - .element = strdup(element) - }; - - if (!n->element) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - goto failure; - } - - return n; - -failure: - html_node_free(n); - return NULL; -} @@ -1,15 +0,0 @@ -#ifndef HTML_H -#define HTML_H - -#include <dynstr.h> - -struct html_node *html_node_alloc(const char *element); -void html_node_free(struct html_node *n); -int html_node_set_value(struct html_node *n, const char *val); -int html_node_set_value_unescaped(struct html_node *n, const char *val); -int html_node_add_attr(struct html_node *n, const char *attr, const char *val); -struct html_node *html_node_add_child(struct html_node *n, const char *elem); -void html_node_add_sibling(struct html_node *n, struct html_node *sibling); -int html_serialize(const struct html_node *n, struct dynstr *d); - -#endif /* HTML_H */ @@ -1,1964 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "http.h" -#include <dynstr.h> -#include <sys/types.h> -#include <unistd.h> -#include <ctype.h> -#include <errno.h> -#include <inttypes.h> -#include <stdbool.h> -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <strings.h> -#include <time.h> - -#define HTTP_VERSION "HTTP/1.1" - -struct http_ctx -{ - struct ctx - { - enum state - { - START_LINE, - HEADER_CR_LINE, - BODY_LINE - } state; - - enum - { - LINE_CR, - LINE_LF - } lstate; - - enum http_op op; - char *resource, *field, *value, *boundary; - size_t len; - - struct post - { - char *path; - unsigned long long len, read; - } post; - - union - { - struct start_line - { - enum - { - START_LINE_OP, - START_LINE_RESOURCE, - START_LINE_PROTOCOL - } state; - } sl; - - struct multiform - { - enum mf_state - { - MF_START_BOUNDARY, - MF_HEADER_CR_LINE, - MF_BODY_BOUNDARY_LINE, - MF_END_BOUNDARY_CR_LINE - } state; - - enum - { - BODY_CR, - BODY_LF, - BODY_DATA - } bstate; - - off_t len, written; - char *boundary; - const char *dir; - size_t blen, nforms, nfiles; - int fd; - struct http_post_file *files; - - struct form - { - char *name, *filename, *tmpname, *value; - } *forms; - } mf; - } u; - - struct http_arg *args; - size_t n_args; - } ctx; - - struct write_ctx - { - bool pending, close; - enum state state; - struct http_response r; - off_t n; - struct dynstr d; - } wctx; - - /* From RFC9112, section 3 (Request line): - * It is RECOMMENDED that all HTTP senders and recipients support, - * at a minimum, request-line lengths of 8000 octets. */ - char line[8000]; - struct http_cfg cfg; -}; - -static void arg_free(struct http_arg *const a) -{ - if (a) - { - free(a->key); - free(a->value); - } -} - -static size_t chrcnt(const char *s, const int c) -{ - size_t ret = 0; - - while (*s++ == c) - ret++; - - return ret; -} - -static int parse_arg(struct ctx *const c, const char *const arg, - const size_t n) -{ - int ret = -1; - struct http_arg a = {0}, *args = NULL; - const char *sep = memchr(arg, '=', n); - char *enckey = NULL, *encvalue = NULL; - - if (!sep) - { - fprintf(stderr, "%s: expected '='\n", __func__); - ret = 1; - goto end; - } - else if (sep == arg) - { - fprintf(stderr, "%s: expected key\n", __func__); - ret = 1; - goto end; - } - - const char *const value = sep + 1; - - if (!*value) - { - fprintf(stderr, "%s: missing value: %.*s\n", __func__, (int)n, arg); - ret = 1; - goto end; - } - - const size_t keylen = sep - arg, valuelen = n - keylen - 1; - - if (!(enckey = strndup(arg, keylen))) - { - fprintf(stderr, "%s: strndup(3) key: %s\n", __func__, strerror(errno)); - goto end; - } - else if (!(encvalue = strndup(value, valuelen))) - { - fprintf(stderr, "%s: strndup(3) value: %s\n", - __func__, strerror(errno)); - goto end; - } - - /* URL parameters use '+' for whitespace, rather than %20. */ - a = (const struct http_arg) - { - .key = http_decode_url(enckey, true), - .value = http_decode_url(encvalue, true) - }; - - if (!a.key) - { - fprintf(stderr, "%s: http_decode_url key failed\n", __func__); - goto end; - } - else if (!a.value) - { - fprintf(stderr, "%s: http_decode_url value failed\n", __func__); - goto end; - } - else if (!(args = realloc(c->args, (c->n_args + 1) * sizeof *args))) - { - fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); - goto end; - } - - args[c->n_args++] = a; - c->args = args; - ret = 0; - -end: - if (ret) - arg_free(&a); - - free(enckey); - free(encvalue); - return ret; -} - -static int parse_first_arg(struct ctx *const c, const char *const arg, - const char *const ad_arg, const char *const res) -{ - int error; - const char *const next = arg + 1; - - if (chrcnt(next, '?')) - { - fprintf(stderr, "%s: more than one argument indicator '?' found: %s\n", - __func__, res); - return 1; - } - - const size_t n = ad_arg ? ad_arg - next : strlen(next); - - if (!n) - { - fprintf(stderr, "%s: unterminated argument: %s\n", __func__, res); - return 1; - } - else if ((error = parse_arg(c, next, n))) - { - fprintf(stderr, "%s: parse_arg failed: %s\n", __func__, res); - return error; - } - - return 0; -} - -static int parse_adargs(struct ctx *const c, const char *const start, - const char *const res) -{ - for (const char *arg = start, *next; arg; arg = next) - { - next = strchr(++arg, '&'); - - int error; - const size_t n = next ? next - arg : strlen(arg); - - if ((error = parse_arg(c, arg, n))) - { - fprintf(stderr, "%s: parse_arg failed: %s\n", __func__, res); - return error; - } - } - - return 0; -} - -static int parse_args(struct ctx *const c, const char *const res, - size_t *const reslen) -{ - int error; - const char *const tmp_arg_start = strrchr(res, '?'), - *const arg_start = tmp_arg_start && *(tmp_arg_start + 1) ? - tmp_arg_start : NULL, - *const ad_arg = arg_start ? strchr(arg_start, '&') : NULL; - - if (!arg_start) - { - if (!ad_arg) - { - *reslen = strlen(res); - return 0; - } - else - { - fprintf(stderr, "%s: expected argument indicator '?': %s\n", - __func__, res); - return 1; - } - } - else if (arg_start == res) - { - fprintf(stderr, "%s: expected resource: %s\n", __func__, res); - return 1; - } - else if (ad_arg && ad_arg <= arg_start) - { - fprintf(stderr, "%s: expected '?' before '&': %s\n", __func__, res); - return 1; - } - else if ((error = parse_first_arg(c, arg_start, ad_arg, res))) - { - fprintf(stderr, "%s: parse_first_arg failed\n", __func__); - return error; - } - else if ((error = parse_adargs(c, ad_arg, res))) - { - fprintf(stderr, "%s: parse_adargs failed\n", __func__); - return error; - } - - *reslen = arg_start - res; - return 0; -} - -static int parse_resource(struct ctx *const c, const char *const enc_res) -{ - int ret = -1, error; - size_t reslen; - char *trimmed_encres = NULL, *resource = NULL; - - if ((error = parse_args(c, enc_res, &reslen))) - { - fprintf(stderr, "%s: parse_args failed\n", __func__); - ret = error; - goto end; - } - else if (!(trimmed_encres = strndup(enc_res, reslen))) - { - fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); - goto end; - } - else if (!(resource = http_decode_url(trimmed_encres, false))) - { - fprintf(stderr, "%s: http_decode_url failed\n", __func__); - goto end; - } - - c->resource = resource; - ret = 0; - -end: - free(trimmed_encres); - return ret; -} - -static int start_line(struct http_ctx *const h) -{ - const char *const line = (const char *)h->line; - - if (!*line) - { - fprintf(stderr, "%s: expected non-empty line\n", __func__); - return 1; - } - - const char *const op = strchr(line, ' '); - - if (!op || op == line) - { - fprintf(stderr, "%s: expected resource\n", __func__); - return 1; - } - - struct ctx *const c = &h->ctx; - const size_t n = op - line; - - if (!strncmp(line, "GET", n)) - c->op = HTTP_OP_GET; - else if (!strncmp(line, "POST", n)) - c->op = HTTP_OP_POST; - else - { - fprintf(stderr, "%s: unsupported HTTP op %.*s\n", - __func__, (int)n, line); - return 1; - } - - const char *const resource = op + 1, - *const res_end = strchr(resource, ' '); - - if (!res_end) - { - fprintf(stderr, "%s: expected protocol version\n", __func__); - return 1; - } - - const size_t res_n = res_end - resource; - - if (memchr(resource, '*', res_n)) - { - fprintf(stderr, "%s: illegal character * in resource %.*s\n", - __func__, (int)res_n, resource); - return 1; - } - - const char *const protocol = res_end + 1; - - if (!*protocol) - { - fprintf(stderr, "%s: expected protocol version\n", __func__); - return 1; - } - else if (strchr(protocol, ' ')) - { - fprintf(stderr, "%s: unexpected field after protocol version\n", - __func__); - return 1; - } - - int ret = 1, error; - char *enc_res = NULL; - - if (strcmp(protocol, HTTP_VERSION)) - { - fprintf(stderr, "%s: unsupported protocol %s\n", __func__, protocol); - goto end; - } - else if (!(enc_res = strndup(resource, res_n))) - { - fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); - ret = -1; - goto end; - } - else if ((error = parse_resource(c, enc_res))) - { - fprintf(stderr, "%s: parse_resource failed\n", __func__); - ret = error; - goto end; - } - - printf("%.*s %s %s\n", (int)n, line, c->resource, protocol); - ret = 0; - c->state = HEADER_CR_LINE; - -end: - free(enc_res); - return ret; -} - -static void ctx_free(struct ctx *const c) -{ - if (c->boundary) - { - struct multiform *const m = &c->u.mf; - - free(m->files); - free(m->boundary); - - if (m->fd >= 0 && close(m->fd)) - fprintf(stderr, "%s: close(2) m->fd: %s\n", - __func__, strerror(errno)); - - for (size_t i = 0; i < m->nforms; i++) - { - struct form *const f = &m->forms[i]; - - free(f->name); - free(f->filename); - free(f->value); - - if (f->tmpname && remove(f->tmpname) && errno != ENOENT) - fprintf(stderr, "%s: remove(3) %s: %s\n", - __func__, f->tmpname, strerror(errno)); - - free(f->tmpname); - } - - free(m->forms); - } - - free(c->field); - free(c->value); - free(c->resource); - free(c->boundary); - - for (size_t i = 0; i < c->n_args; i++) - arg_free(&c->args[i]); - - free(c->args); - *c = (const struct ctx){0}; -} - -static int prepare_headers(struct http_ctx *const h) -{ - struct write_ctx *const w = &h->wctx; - struct dynstr *const d = &w->d; - - dynstr_init(d); - - for (size_t i = 0; i < w->r.n_headers; i++) - { - const struct http_header *const hdr = &w->r.headers[i]; - - dynstr_append_or_ret_nonzero(d, "%s: %s\r\n", hdr->header, hdr->value); - free(hdr->header); - free(hdr->value); - } - - free(w->r.headers); - dynstr_append_or_ret_nonzero(d, "\r\n"); - return 0; -} - -static int rw_error(const int r, bool *const close) -{ - if (r < 0) - { - switch (errno) - { - case EPIPE: - /* Fall through. */ - case ECONNRESET: - *close = true; - return 1; - - case EAGAIN: - return 0; - - default: - break; - } - - fprintf(stderr, "%s: %s\n", __func__, strerror(errno)); - return -1; - } - else if (!r) - { - *close = true; - return 0; - } - - fprintf(stderr, "%s: unexpected value %d\n", __func__, r); - return -1; -} - -static int write_start_line(struct http_ctx *const h, bool *const close) -{ - struct write_ctx *const w = &h->wctx; - struct dynstr *const d = &w->d; - const size_t rem = d->len - w->n; - const int res = h->cfg.write(d->str + w->n, rem, h->cfg.user); - - if (res <= 0) - return rw_error(res, close); - else if ((w->n += res) >= d->len) - { - char len[sizeof "18446744073709551615"]; - const int res = snprintf(len, sizeof len, "%llu", w->r.n); - - dynstr_free(d); - - if (res < 0 || res >= sizeof len) - { - fprintf(stderr, "%s: snprintf(3) failed\n", __func__); - return -1; - } - else if (http_response_add_header(&w->r, "Content-Length", len)) - { - fprintf(stderr, "%s: http_response_add_header failed\n", __func__); - return -1; - } - else if (prepare_headers(h)) - { - fprintf(stderr, "%s: prepare_headers failed\n", __func__); - return -1; - } - - w->state = HEADER_CR_LINE; - w->n = 0; - } - - return 0; -} - -static int write_ctx_free(struct write_ctx *const w) -{ - int ret = 0; - const struct http_response *const r = &w->r; - - if (r->free) - r->free(r->buf.rw); - - if (r->f && (ret = fclose(r->f))) - fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno)); - - dynstr_free(&w->d); - *w = (const struct write_ctx){0}; - return ret; -} - -static int write_header_cr_line(struct http_ctx *const h, bool *const close) -{ - struct write_ctx *const w = &h->wctx; - struct dynstr *const d = &w->d; - const size_t rem = d->len - w->n; - const int res = h->cfg.write(d->str + w->n, rem, h->cfg.user); - - if (res <= 0) - return rw_error(res, close); - else if ((w->n += res) >= d->len) - { - const bool close_pending = w->close; - - dynstr_free(d); - - if (w->r.n) - { - w->state = BODY_LINE; - w->n = 0; - } - else if (write_ctx_free(w)) - { - fprintf(stderr, "%s: write_ctx_free failed\n", __func__); - return -1; - } - else if (close_pending) - *close = true; - } - - return 0; -} - -static int write_body_mem(struct http_ctx *const h, bool *const close) -{ - struct write_ctx *const w = &h->wctx; - const struct http_response *const r = &w->r; - const size_t rem = r->n - w->n; - const int res = h->cfg.write((const char *)r->buf.ro + w->n, rem, - h->cfg.user); - - if (res <= 0) - return rw_error(res, close); - else if ((w->n += res) >= r->n) - { - const bool close_pending = w->close; - - if (write_ctx_free(w)) - { - fprintf(stderr, "%s: write_ctx_free failed\n", __func__); - return -1; - } - else if (close_pending) - *close = true; - - return res; - } - - return 0; -} - -static int write_body_file(struct http_ctx *const h, bool *const close) -{ - struct write_ctx *const w = &h->wctx; - const struct http_response *const r = &w->r; - const unsigned long long left = r->n - w->n; - char buf[1024]; - const size_t rem = left > sizeof buf ? sizeof buf : left; - - if (!fread(buf, 1, rem, r->f)) - { - fprintf(stderr, "%s: fread(3) failed, ferror=%d, feof=%d\n", - __func__, ferror(r->f), feof(r->f)); - return -1; - } - - const int res = h->cfg.write(buf, rem, h->cfg.user); - - if (res <= 0) - return rw_error(res, close); - else if ((w->n += res) >= r->n) - { - const bool close_pending = w->close; - - if (write_ctx_free(w)) - { - fprintf(stderr, "%s: write_ctx_free failed\n", __func__); - return -1; - } - else if (close_pending) - *close = true; - } - - return 0; -} - -static int write_body_line(struct http_ctx *const h, bool *const close) -{ - const struct http_response *const r = &h->wctx.r; - - if (r->buf.ro) - return write_body_mem(h, close); - else if (r->f) - return write_body_file(h, close); - - fprintf(stderr, "%s: expected either buffer or file path\n", __func__); - return -1; -} - -static int http_write(struct http_ctx *const h, bool *const close) -{ - static int (*const fn[])(struct http_ctx *, bool *) = - { - [START_LINE] = write_start_line, - [HEADER_CR_LINE] = write_header_cr_line, - [BODY_LINE] = write_body_line, - }; - - struct write_ctx *const w = &h->wctx; - - const int ret = fn[w->state](h, close); - - if (ret) - write_ctx_free(w); - - return ret; -} - -int http_response_add_header(struct http_response *const r, - const char *const header, const char *const value) -{ - const size_t n = r->n_headers + 1; - struct http_header *const headers = realloc(r->headers, - n * sizeof *r->headers), *h = NULL; - - if (!headers) - { - fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); - return -1; - } - - h = &headers[r->n_headers]; - - *h = (const struct http_header) - { - .header = strdup(header), - .value = strdup(value) - }; - - if (!h->header || !h->value) - { - fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); - free(h->header); - free(h->value); - return -1; - } - - r->headers = headers; - r->n_headers = n; - return 0; -} - -static int start_response(struct http_ctx *const h) -{ - static const struct code - { - const char *descr; - int code; - } codes[] = - { -#define X(x, y, z) [HTTP_STATUS_##x] = {.descr = y, .code = z}, - HTTP_STATUSES -#undef X - }; - - struct write_ctx *const w = &h->wctx; - const struct code *const c = &codes[w->r.status]; - - w->pending = true; - dynstr_init(&w->d); - dynstr_append_or_ret_nonzero(&w->d, HTTP_VERSION " %d %s\r\n", - c->code, c->descr); - return 0; -} - -static int set_cookie(struct http_ctx *const h, const char *const cookie) -{ - struct ctx *const c = &h->ctx; - const char *const value = strchr(cookie, '='); - - if (!value) - { - fprintf(stderr, "%s: expected field=value for cookie %s\n", - __func__, cookie); - goto failure; - } - else if (!*(value + 1)) - { - fprintf(stderr, "%s: expected non-empty value for cookie %s\n", - __func__, cookie); - goto failure; - } - else if (!(c->field = strndup(cookie, value - cookie))) - { - fprintf(stderr, "%s: malloc(3) field: %s\n", __func__, strerror(errno)); - goto failure; - } - else if (!(c->value = strdup(value + 1))) - { - fprintf(stderr, "%s: malloc(3) value: %s\n", __func__, strerror(errno)); - goto failure; - } - - return 0; - -failure: - free(c->field); - free(c->value); - return -1; -} - -static int set_length(struct http_ctx *const h, const char *const len) -{ - char *end; - - errno = 0; - h->ctx.post.len = strtoull(len, &end, 10); - - if (errno || *end != '\0') - { - fprintf(stderr, "%s: invalid length %s: %s\n", - __func__, len, strerror(errno)); - return 1; - } - - return 0; -} - -static int set_content_type(struct http_ctx *const h, const char *const type) -{ - const char *const sep = strchr(type, ';'); - - if (!sep) - /* No multipart/form-data expected. */ - return 0; - - const size_t n = sep - type; - - if (strncmp(type, "multipart/form-data", n)) - { - fprintf(stderr, "%s: unsupported Content-Type %.*s\n", - __func__, (int)n, type); - return 1; - } - - const char *boundary = sep + 1; - - while (*boundary == ' ') - boundary++; - - if (!*boundary) - { - fprintf(stderr, "%s: expected boundary\n", __func__); - return 1; - } - - const char *const eq = strchr(boundary, '='); - - if (!eq) - { - fprintf(stderr, "%s: expected = after boundary\n", __func__); - return 1; - } - - const size_t bn = eq - boundary; - - if (strncmp(boundary, "boundary", bn)) - { - fprintf(stderr, "%s: expected boundary, got %.*s\n", - __func__, (int)bn, boundary); - return 1; - } - - const char *val = eq + 1; - - while (*val == ' ') - val++; - - if (!*val) - { - fprintf(stderr, "%s: expected value after boundary\n", __func__); - return 1; - } - - struct ctx *const c = &h->ctx; - struct dynstr b; - - dynstr_init(&b); - - if (dynstr_append(&b, "\r\n--%s", val)) - { - fprintf(stderr, "%s: dynstr_append failed\n", __func__); - return -1; - } - - c->boundary = b.str; - c->u.mf = (const struct multiform){.fd = -1}; - return 0; -} - -static struct http_payload ctx_to_payload(const struct ctx *const c) -{ - return (const struct http_payload) - { - .cookie = - { - .field = c->field, - .value = c->value - }, - - .op = c->op, - .resource = c->resource, - .args = c->args, - .n_args = c->n_args - }; -} - -static int payload_get(struct http_ctx *const h, const char *const line) -{ - struct ctx *const c = &h->ctx; - const struct http_payload p = ctx_to_payload(c); - const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user); - - ctx_free(c); - - if (ret) - return ret; - - return start_response(h); -} - -static int payload_post(struct http_ctx *const h, const char *const line) -{ - struct ctx *const c = &h->ctx; - const struct http_payload pl = ctx_to_payload(c); - const int ret = h->cfg.payload(&pl, &h->wctx.r, h->cfg.user); - - ctx_free(c); - - if (ret) - return ret; - - return start_response(h); -} - -static int get_field_value(const char *const line, size_t *const n, - const char **const value) -{ - const char *const field = strchr(line, ':'); - - if (!field || line == field) - { - fprintf(stderr, "%s: expected field:value\n", __func__); - return 1; - } - - *n = field - line; - *value = field + 1; - - if (!**value) - { - fprintf(stderr, "%s: expected value\n", __func__); - return 1; - } - - while (**value == ' ') - (*value)++; - - return 0; -} - -static int expect(struct http_ctx *const h, const char *const value) -{ - if (!strcmp(value, "100-continue")) - { - struct ctx *const c = &h->ctx; - const struct http_payload p = - { - .u.post.expect_continue = true, - .cookie = - { - .field = c->field, - .value = c->value - }, - - .op = c->op, - .resource = c->resource - }; - - const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user); - - if (ret) - return ret; - - return start_response(h); - } - - return 0; -} - -static int process_header(struct http_ctx *const h, const char *const line, - const size_t n, const char *const value) -{ - static const struct header - { - const char *header; - int (*f)(struct http_ctx *, const char *); - } headers[] = - { - { - .header = "Cookie", - .f = set_cookie - }, - - { - .header = "Content-Length", - .f = set_length - }, - - { - .header = "Expect", - .f = expect - }, - - { - .header = "Content-Type", - .f = set_content_type - } - }; - - for (size_t i = 0; i < sizeof headers / sizeof *headers; i++) - { - const struct header *const hdr = &headers[i]; - int ret; - - if (!strncasecmp(line, hdr->header, n) && (ret = hdr->f(h, value))) - return ret; - } - - return 0; -} - -static int check_length(struct http_ctx *const h) -{ - struct ctx *const c = &h->ctx; - const struct http_cookie cookie = - { - .field = c->field, - .value = c->value - }; - - return h->cfg.length(c->post.len, &cookie, &h->wctx.r, h->cfg.user); -} - -static int header_cr_line(struct http_ctx *const h) -{ - const char *const line = (const char *)h->line; - struct ctx *const c = &h->ctx; - - if (!*line) - { - switch (c->op) - { - case HTTP_OP_GET: - return payload_get(h, line); - - case HTTP_OP_POST: - { - if (!c->post.len) - return payload_post(h, line); - else if (c->boundary) - { - const int res = check_length(h); - - if (res) - { - h->wctx.close = true; - return start_response(h); - } - } - - c->state = BODY_LINE; - return 0; - } - } - } - - const char *value; - size_t n; - const int ret = get_field_value(line, &n, &value); - - if (ret) - return ret; - - return process_header(h, line, n, value); -} - -static int send_payload(struct http_ctx *const h, - const struct http_payload *const p) -{ - struct ctx *const c = &h->ctx; - const int ret = h->cfg.payload(p, &h->wctx.r, h->cfg.user); - - ctx_free(c); - - if (ret) - return ret; - - return start_response(h); -} - -static int update_lstate(struct http_ctx *const h, bool *const close, - int (*const f)(struct http_ctx *), const char b) -{ - int ret = 1; - struct ctx *const c = &h->ctx; - - switch (c->lstate) - { - case LINE_CR: - if (b == '\r') - c->lstate = LINE_LF; - else if (c->len < sizeof h->line - 2) - h->line[c->len++] = b; - else - { - fprintf(stderr, "%s: line too long\n", __func__); - goto failure; - } - - break; - - case LINE_LF: - if (b == '\n') - { - h->line[c->len] = '\0'; - - if ((ret = f(h))) - goto failure; - - c->len = 0; - } - else if (c->len < sizeof h->line - 3) - { - h->line[c->len++] = '\r'; - h->line[c->len++] = b; - } - else - { - fprintf(stderr, "%s: line too long\n", __func__); - goto failure; - } - - c->lstate = LINE_CR; - break; - } - - return 0; - -failure: - ctx_free(c); - return ret; -} - -static int start_boundary_line(struct http_ctx *const h) -{ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - const char *const line = h->line; - - if (strcmp(line, c->boundary + strlen("\r\n"))) - { - fprintf(stderr, "%s: expected boundary %s, got %s\n", - __func__, c->boundary, line); - return 1; - } - - m->state = MF_HEADER_CR_LINE; - m->len = strlen(line) + strlen("\r\n"); - return 0; -} - -static int cd_fields(struct http_ctx *const h, struct form *const f, - const char *sep) -{ - do - { - while (*++sep == ' ') - ; - - if (!*sep) - break; - - const char *const op = strchr(sep, '='); - - if (!op) - { - fprintf(stderr, "%s: expected attr=value\n", __func__); - return 1; - } - - const char *const value = op + 1, *const end = strchr(value, ';'); - const size_t vlen = end ? end - value : strlen(value); - - if (*value != '\"' || value[vlen - 1] != '\"') - { - fprintf(stderr, "%s: expected \"-enclosed value, got %.*s\n", - __func__, (int)vlen, value); - return 1; - } - - const char *const evalue = value + 1; - const size_t evlen = vlen - 2; - - if (!strncmp(sep, "name", op - sep)) - { - if (!evlen) - { - fprintf(stderr, "%s: expected non-empty name\n", __func__); - return 1; - } - else if (!(f->name = strndup(evalue, evlen))) - { - fprintf(stderr, "%s: strndup(3): %s\n", - __func__, strerror(errno)); - return -1; - } - } - else if (!strncmp(sep, "filename", op - sep)) - { - if (!evlen) - { - fprintf(stderr, "%s: expected non-empty filename\n", __func__); - return 1; - } - else if (!(f->filename = strndup(evalue, evlen))) - { - fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); - return -1; - } - else if (!strcmp(f->filename, ".") - || !strcmp(f->filename, "..") - || strpbrk(f->filename, "/*")) - { - fprintf(stderr, "%s: invalid filename %s\n", - __func__, f->filename); - return 1; - } - } - } while ((sep = strchr(sep, ';'))); - - if (!f->name) - { - fprintf(stderr, "%s: expected name\n", __func__); - return 1; - } - - return 0; -} - -static int set_content_disposition(struct http_ctx *const h, - const char *const c) -{ - const char *const sep = strchr(c, ';'); - struct multiform *const m = &h->ctx.u.mf; - - if (!sep) - { - fprintf(stderr, "%s: no fields found\n", __func__); - return 1; - } - else if (strncmp(c, "form-data", sep - c)) - { - fprintf(stderr, "%s: expected form-data, got %.*s\n", - __func__, (int)(sep - c), c); - return 1; - } - - const size_t n = m->nforms + 1; - struct form *const forms = realloc(m->forms, n * sizeof *m->forms); - - if (!forms) - { - fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); - return -1; - } - - struct form *const f = &forms[m->nforms]; - - *f = (const struct form){0}; - m->nforms = n; - m->forms = forms; - return cd_fields(h, f, sep); -} - -static int mf_header_cr_line(struct http_ctx *const h) -{ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - const char *const line = h->line; - - m->len += strlen(line) + strlen("\r\n"); - - if (!*line) - { - const size_t n = strlen("\r\n") + strlen(c->boundary) + 1; - - if (!m->boundary && !(m->boundary = calloc(1, n))) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - return -1; - } - - m->state = MF_BODY_BOUNDARY_LINE; - return 0; - } - - const char *value; - size_t n; - int ret = get_field_value(line, &n, &value); - - if (ret) - return ret; - else if (!strncasecmp(line, "Content-Disposition", n) - && (ret = set_content_disposition(h, value))) - return ret; - - return 0; -} - -static int end_boundary_line(struct http_ctx *const h) -{ - const char *const line = h->line; - - if (!*line) - { - h->ctx.u.mf.state = MF_HEADER_CR_LINE; - return 0; - } - else if (!strcmp(line, "--")) - { - /* Found end boundary. */ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - - const struct http_payload p = - { - .cookie = - { - .field = c->field, - .value = c->value - }, - - .op = c->op, - .resource = c->resource, - .u.post = - { - .dir = m->dir, - .files = m->files, - .n = m->nfiles - } - }; - - return send_payload(h, &p); - } - - fprintf(stderr, "%s: unexpected line after boundary: %s\n", - __func__, line); - return 1; -} - -static int process_mf_line(struct http_ctx *const h) -{ - static int (*const state[])(struct http_ctx *) = - { - [MF_START_BOUNDARY] = start_boundary_line, - [MF_HEADER_CR_LINE] = mf_header_cr_line, - [MF_END_BOUNDARY_CR_LINE] = end_boundary_line - }; - - h->ctx.post.read += strlen(h->line) + strlen("\r\n"); - return state[h->ctx.u.mf.state](h); -} - -static char *get_tmp(const char *const tmpdir) -{ - struct dynstr d; - - dynstr_init(&d); - - if (dynstr_append(&d, "%s/tmp.XXXXXX", tmpdir)) - { - fprintf(stderr, "%s: dynstr_append failed\n", __func__); - return NULL; - } - - return d.str; -} - -static int generate_mf_file(struct http_ctx *const h) -{ - struct multiform *const m = &h->ctx.u.mf; - struct form *const f = &m->forms[m->nforms - 1]; - - if (!(f->tmpname = get_tmp(h->cfg.tmpdir))) - { - fprintf(stderr, "%s: get_tmp failed\n", __func__); - return -1; - } - else if ((m->fd = mkstemp(f->tmpname)) < 0) - { - fprintf(stderr, "%s: mkstemp(3): %s\n", __func__, strerror(errno)); - return -1; - } - - return 0; -} - -static int read_mf_body_to_mem(struct http_ctx *const h, const void *const buf, - const size_t n) -{ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - - if (m->written + n > sizeof h->line) - { - fprintf(stderr, "%s: maximum length exceeded\n", __func__); - return 1; - } - - memcpy(&h->line[m->written], buf, n); - m->written += n; - m->len += n; - c->post.read += n; - return 0; -} - -static int read_mf_body_to_file(struct http_ctx *const h, const void *const buf, - const size_t n) -{ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - ssize_t res; - - if (m->fd < 0 && generate_mf_file(h)) - { - fprintf(stderr, "%s: generate_mf_file failed\n", __func__); - return -1; - } - else if ((res = pwrite(m->fd, buf, n, m->written)) < 0) - { - fprintf(stderr, "%s: pwrite(2): %s\n", __func__, strerror(errno)); - return -1; - } - - m->written += res; - m->len += res; - c->post.read += res; - return 0; -} - -static int reset_boundary(struct http_ctx *const h, const void *const buf, - const size_t n) -{ - struct multiform *const m = &h->ctx.u.mf; - struct form *const f = &m->forms[m->nforms - 1]; - const size_t len = strlen(m->boundary); - int (*const read_mf)(struct http_ctx *, const void *, size_t) = - f->filename ? read_mf_body_to_file : read_mf_body_to_mem; - const int res = read_mf(h, m->boundary, len); - - if (res) - return res; - - memset(m->boundary, '\0', len); - m->blen = 0; - return read_mf(h, buf, n); -} - -static int apply_from_file(struct http_ctx *const h, struct form *const f) -{ - struct multiform *const m = &h->ctx.u.mf; - - if (close(m->fd)) - { - fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno)); - return -1; - } - - m->fd = -1; - - const size_t n = m->nfiles + 1; - struct http_post_file *const files = realloc(m->files, - n * sizeof *m->files); - - if (!files) - { - fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); - return -1; - } - - struct http_post_file *const pf = &files[m->nfiles]; - - *pf = (const struct http_post_file) - { - .tmpname = f->tmpname, - .filename = f->filename - }; - - m->files = files; - m->nfiles = n; - return 0; -} - -static int apply_from_mem(struct http_ctx *const h, struct form *const f) -{ - struct multiform *const m = &h->ctx.u.mf; - - if (!(f->value = strndup(h->line, m->written))) - { - fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); - return -1; - } - else if (!strcmp(f->name, "dir")) - { - if (m->dir) - { - fprintf(stderr, "%s: \"dir\" defined more than once\n", __func__); - return 1; - } - - m->dir = f->value; - } - - return 0; -} - -static int read_mf_body_boundary_byte(struct http_ctx *const h, const char b, - const size_t len) -{ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - - if (b == c->boundary[m->blen]) - { - m->boundary[len] = b; - - if (++m->blen >= strlen(c->boundary)) - { - /* Found intermediate boundary. */ - struct form *const f = &m->forms[m->nforms - 1]; - const int ret = f->filename ? apply_from_file(h, f) - : apply_from_mem(h, f); - - memset(m->boundary, '\0', len + 1); - m->blen = 0; - m->state = MF_END_BOUNDARY_CR_LINE; - m->written = 0; - return ret; - } - } - - return 0; -} - -/* Similar to memmem(3), provided here to avoid the use of GNU extensions. */ -static const char *http_memmem(const char *const a, const void *const b, - const size_t n) -{ - const size_t len = strlen(a); - - if (len > n) - return NULL; - - const char *s = a, *st = NULL; - - for (size_t i = 0; i < n; i++) - { - const char *bc = b; - const char c = bc[i]; - - if (*s == c) - { - if (!st) - st = &bc[i]; - - if (!*++s) - return st; - } - else if (*(s = a) != c) - st = NULL; - else - { - st = &bc[i]; - - if (!*++s) - return st; - } - } - - return NULL; -} - -static int read_mf_body_boundary(struct http_ctx *const h, - const char **const buf, size_t *const n) -{ - struct ctx *const c = &h->ctx; - struct multiform *const m = &c->u.mf; - const char *const boundary = http_memmem(&c->boundary[m->blen], *buf, *n); - int res; - - if (!boundary) - { - if ((res = reset_boundary(h, *buf, *n))) - return res; - - *n = 0; - return 0; - } - - const size_t prev = boundary - *buf; - - if ((res = reset_boundary(h, *buf, prev))) - return res; - - *buf += prev; - *n -= prev; - - const size_t len = strlen(m->boundary), - rem = strlen(c->boundary) - len, - r = rem > *n ? *n : rem; - - for (size_t i = 0; i < r; i++) - { - const char *const b = *buf; - - if ((res = read_mf_body_boundary_byte(h, b[i], len))) - return res; - } - - *buf += r; - *n -= r; - return 0; -} - -static int read_multiform_n(struct http_ctx *const h, bool *const close, - const char *buf, size_t n) -{ - struct multiform *const m = &h->ctx.u.mf; - - while (n) - { - int res; - - switch (m->state) - { - case MF_START_BOUNDARY: - /* Fall through. */ - case MF_HEADER_CR_LINE: - /* Fall through. */ - case MF_END_BOUNDARY_CR_LINE: - { - if ((res = update_lstate(h, close, process_mf_line, *buf))) - return res; - - buf++; - n--; - } - - break; - - case MF_BODY_BOUNDARY_LINE: - if ((res = read_mf_body_boundary(h, &buf, &n))) - return res; - } - } - - return 0; -} - -static int read_multiform(struct http_ctx *const h, bool *const close) -{ - /* Note: the larger the buffer below, the less CPU load. */ - char buf[sizeof h->line]; - struct post *const p = &h->ctx.post; - const unsigned long long left = p->len - p->read; - const size_t rem = left > sizeof buf ? sizeof buf : left; - const int r = h->cfg.read(buf, rem, h->cfg.user); - - if (r <= 0) - return rw_error(r, close); - - return read_multiform_n(h, close, buf, r); -} - -static int read_body_to_mem(struct http_ctx *const h, bool *const close) -{ - char b; - const int r = h->cfg.read(&b, sizeof b, h->cfg.user); - - if (r <= 0) - return rw_error(r, close); - - struct ctx *const c = &h->ctx; - struct post *const p = &c->post; - - if (p->read >= sizeof h->line) - { - fprintf(stderr, "%s: exceeded maximum length\n", __func__); - return 1; - } - - h->line[p->read++] = b; - - if (p->read >= p->len) - { - const struct http_payload pl = - { - .cookie = - { - .field = c->field, - .value = c->value - }, - - .op = c->op, - .resource = c->resource, - .u.post = - { - .data = h->line, - .n = p->len - } - }; - - return send_payload(h, &pl); - } - - return 0; -} - -static int read_body(struct http_ctx *const h, bool *const close) -{ - return h->ctx.boundary ? read_multiform(h, close) - : read_body_to_mem(h, close); -} - -static int process_line(struct http_ctx *const h) -{ - static int (*const state[])(struct http_ctx *) = - { - [START_LINE] = start_line, - [HEADER_CR_LINE] = header_cr_line - }; - - return state[h->ctx.state](h); -} - -static int http_read(struct http_ctx *const h, bool *const close) -{ - switch (h->ctx.state) - { - case START_LINE: - /* Fall through. */ - case HEADER_CR_LINE: - { - char b; - const int r = h->cfg.read(&b, sizeof b, h->cfg.user); - - if (r <= 0) - return rw_error(r, close); - - return update_lstate(h, close, process_line, b); - } - - case BODY_LINE: - return read_body(h, close); - } - - fprintf(stderr, "%s: unexpected state %d\n", __func__, h->ctx.state); - return -1; -} - -static int append_expire(struct dynstr *const d) -{ - time_t t = time(NULL); - - if (t == (time_t)-1) - { - fprintf(stderr, "%s: time(3): %s\n", __func__, strerror(errno)); - return -1; - } - - t += 365 * 24 * 60 * 60; - - struct tm tm; - - if (!localtime_r(&t, &tm)) - { - fprintf(stderr, "%s: localtime_r(3): %s\n", __func__, strerror(errno)); - return -1; - } - - char s[sizeof "Thu, 01 Jan 1970 00:00:00 GMT"]; - - if (!strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S GMT", &tm)) - { - fprintf(stderr, "%s: strftime(3) failed\n", __func__); - return -1; - } - - dynstr_append_or_ret_nonzero(d, "; Expires=%s", s); - return 0; -} - -char *http_cookie_create(const char *const key, const char *const value) -{ - struct dynstr d; - - dynstr_init(&d); - dynstr_append_or_ret_null(&d, "%s=%s; HttpOnly", key, value); - - if (append_expire(&d)) - { - dynstr_free(&d); - return NULL; - } - - return d.str; -} - -int http_update(struct http_ctx *const h, bool *const write, bool *const close) -{ - *close = false; - - struct write_ctx *const w = &h->wctx; - const int ret = w->pending ? http_write(h, close) : http_read(h, close); - - *write = w->pending; - return ret; -} - -void http_free(struct http_ctx *const h) -{ - if (h) - { - ctx_free(&h->ctx); - write_ctx_free(&h->wctx); - } - - free(h); -} - -struct http_ctx *http_alloc(const struct http_cfg *const cfg) -{ - struct http_ctx *const h = malloc(sizeof *h); - - if (!h) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - goto failure; - } - - *h = (const struct http_ctx) - { - .cfg = *cfg - }; - - return h; - -failure: - http_free(h); - return NULL; -} - -char *http_encode_url(const char *url) -{ - struct dynstr d; - char c; - - dynstr_init(&d); - - while ((c = *url++)) - { - /* Unreserved characters must not be percent-encoded. */ - if ((c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c == '-') - || (c == '_') - || (c == '.') - || (c == '/') - || (c == '~')) - { - if (dynstr_append(&d, "%c", c)) - { - fprintf(stderr, "%s: dynstr_append failed\n", __func__); - goto failure; - } - } - else if (dynstr_append(&d, "%%%hhx", c)) - { - fprintf(stderr, "%s: dynstr_append failed\n", __func__); - goto failure; - } - } - - return d.str; - -failure: - dynstr_free(&d); - return NULL; -} - -char *http_decode_url(const char *url, const bool spaces) -{ - char *ret = NULL; - size_t n = 0; - - while (*url) - { - char *const r = realloc(ret, n + 1); - - if (!r) - { - fprintf(stderr, "%s: realloc(3) loop: %s\n", - __func__, strerror(errno)); - goto failure; - } - - ret = r; - - if (spaces && *url == '+') - { - ret[n++] = ' '; - url++; - } - else if (*url != '%') - ret[n++] = *url++; - else if (*(url + 1) && *(url + 2)) - { - const char buf[sizeof "00"] = {*(url + 1), *(url + 2)}; - char *endptr; - const unsigned long res = strtoul(buf, &endptr, 16); - - if (*endptr) - { - fprintf(stderr, "%s: invalid number %s\n", __func__, buf); - goto failure; - } - - url += 3; - ret[n++] = res; - } - else - { - fprintf(stderr, "%s: unterminated %%\n", __func__); - goto failure; - } - } - - char *const r = realloc(ret, n + 1); - - if (!r) - { - fprintf(stderr, "%s: realloc(3) end: %s\n", __func__, strerror(errno)); - goto failure; - } - - ret = r; - ret[n] = '\0'; - return ret; - -failure: - free(ret); - return NULL; -} @@ -1,106 +0,0 @@ -#ifndef HTTP_H -#define HTTP_H - -#include <stdbool.h> -#include <stddef.h> -#include <stdio.h> - -struct http_payload -{ - enum http_op - { - HTTP_OP_GET, - HTTP_OP_POST - } op; - - const char *resource; - - struct http_cookie - { - const char *field, *value; - } cookie; - - union - { - struct http_post - { - bool expect_continue; - const void *data; - size_t n; - const char *dir; - - const struct http_post_file - { - const char *tmpname, *filename; - } *files; - } post; - } u; - - const struct http_arg - { - char *key, *value; - } *args; - - size_t n_args; -}; - -#define HTTP_STATUSES \ - X(CONTINUE, "Continue", 100) \ - X(OK, "OK", 200) \ - X(SEE_OTHER, "See other", 303) \ - X(BAD_REQUEST, "Bad Request", 400) \ - X(UNAUTHORIZED, "Unauthorized", 401) \ - X(FORBIDDEN, "Forbidden", 403) \ - X(NOT_FOUND, "Not found", 404) \ - X(PAYLOAD_TOO_LARGE, "Payload too large", 413) \ - X(INTERNAL_ERROR, "Internal Server Error", 500) - -struct http_response -{ - enum http_status - { -#define X(x, y, z) HTTP_STATUS_##x, - HTTP_STATUSES -#undef X - } status; - - struct http_header - { - char *header, *value; - } *headers; - - union - { - const void *ro; - void *rw; - } buf; - - FILE *f; - unsigned long long n; - size_t n_headers; - void (*free)(void *); -}; - -struct http_cfg -{ - int (*read)(void *buf , size_t n, void *user); - int (*write)(const void *buf, size_t n, void *user); - int (*payload)(const struct http_payload *p, struct http_response *r, - void *user); - int (*length)(unsigned long long len, const struct http_cookie *c, - struct http_response *r, void *user); - const char *tmpdir; - void *user; -}; - -struct http_ctx *http_alloc(const struct http_cfg *cfg); -void http_free(struct http_ctx *h); -/* Positive return value: user input error, negative: fatal error. */ -int http_update(struct http_ctx *h, bool *write, bool *close); -int http_response_add_header(struct http_response *r, const char *header, - const char *value); -char *http_cookie_create(const char *key, const char *value); -char *http_encode_url(const char *url); -char *http_decode_url(const char *url, bool spaces); - -#endif /* HTTP_H */ diff --git a/server.c b/server.c deleted file mode 100644 index 22615b2..0000000 --- a/server.c +++ /dev/null @@ -1,373 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "server.h" -#include <fcntl.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <poll.h> -#include <unistd.h> -#include <errno.h> -#include <signal.h> -#include <stdbool.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -struct server -{ - int fd; - - struct server_client - { - int fd; - bool write; - struct server_client *prev, *next; - } *c; -}; - -int server_close(struct server *const s) -{ - int ret = 0; - - if (!s) - return 0; - else if (s->fd >= 0) - ret = close(s->fd); - - free(s); - return ret; -} - -int server_client_close(struct server *const s, struct server_client *const c) -{ - int ret = 0; - - for (struct server_client *ref = s->c; ref; ref = ref->next) - { - if (c == ref) - { - struct server_client *const next = ref->next; - - if ((ret = close(c->fd))) - { - fprintf(stderr, "%s: close(2): %s\n", - __func__, strerror(errno)); - return -1; - } - else if (ref->prev) - ref->prev->next = next; - else - s->c = next; - - if (next) - next->prev = ref->prev; - - free(ref); - break; - } - } - - return ret; -} - -int server_read(void *const buf, const size_t n, struct server_client *const c) -{ - const ssize_t r = read(c->fd, buf, n); - - if (r < 0) - fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); - - return r; -} - -int server_write(const void *const buf, const size_t n, - struct server_client *const c) -{ - const ssize_t w = write(c->fd, buf, n); - - if (w < 0) - fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno)); - - return w; -} - -static struct server_client *alloc_client(struct server *const s) -{ - struct sockaddr_in addr; - socklen_t sz = sizeof addr; - const int fd = accept(s->fd, (struct sockaddr *)&addr, &sz); - - if (fd < 0) - { - fprintf(stderr, "%s: accept(2): %s\n", - __func__, strerror(errno)); - return NULL; - } - - const int flags = fcntl(fd, F_GETFL); - - if (flags < 0) - { - fprintf(stderr, "%s: fcntl(2) F_GETFL: %s\n", - __func__, strerror(errno)); - return NULL; - } - else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) - { - fprintf(stderr, "%s: fcntl(2) F_SETFL: %s\n", - __func__, strerror(errno)); - return NULL; - } - - struct server_client *const c = malloc(sizeof *c); - - if (!c) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - return NULL; - } - - *c = (const struct server_client) - { - .fd = fd - }; - - if (!s->c) - s->c = c; - else - for (struct server_client *ref = s->c; ref; ref = ref->next) - if (!ref->next) - { - ref->next = c; - c->prev = ref; - break; - } - - return c; -} - -void server_client_write_pending(struct server_client *const c, - const bool write) -{ - c->write = write; -} - -static volatile sig_atomic_t do_exit; - -static void handle_signal(const int signum) -{ - switch (signum) - { - case SIGINT: - /* Fall through. */ - case SIGTERM: - do_exit = 1; - break; - - default: - break; - } -} - -static size_t get_clients(const struct server *const s) -{ - size_t ret = 0; - - for (const struct server_client *c = s->c; c; c = c->next) - ret++; - - return ret; -} - -struct server_client *server_poll(struct server *const s, bool *const io, - bool *const exit) -{ - struct server_client *ret = NULL; - const size_t n_clients = get_clients(s); - const nfds_t n = n_clients + 1; - struct pollfd *const fds = malloc(n * sizeof *fds); - - if (!fds) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - goto end; - } - - struct pollfd *const sfd = &fds[0]; - - *io = *exit = false; - *sfd = (const struct pollfd) - { - .fd = s->fd, - .events = POLLIN - }; - - for (struct {const struct server_client *c; size_t j;} - _ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++) - { - struct pollfd *const p = &fds[_.j]; - const int fd = _.c->fd; - - *p = (const struct pollfd) - { - .fd = fd, - .events = POLLIN - }; - - if (_.c->write) - p->events |= POLLOUT; - } - - int res; - -again: - - res = poll(fds, n, -1); - - if (res < 0) - { - if (do_exit) - { - *exit = true; - goto end; - } - - switch (errno) - { - case EAGAIN: - /* Fall through. */ - case EINTR: - goto again; - - default: - fprintf(stderr, "%s: poll(2): %s\n", __func__, strerror(errno)); - break; - } - - goto end; - } - else if (!res) - { - fprintf(stderr, "%s: poll(2) returned zero\n", __func__); - goto end; - } - else if (sfd->revents) - { - ret = alloc_client(s); - goto end; - } - - for (struct {struct server_client *c; size_t j;} - _ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++) - { - const struct pollfd *const p = &fds[_.j]; - - if (p->revents) - { - *io = true; - ret = _.c; - goto end; - } - } - - fprintf(stderr, "%s: unlisted fd\n", __func__); - -end: - free(fds); - return ret; -} - -static int init_signals(void) -{ - struct sigaction sa = - { - .sa_handler = handle_signal, - .sa_flags = SA_RESTART - }; - - sigemptyset(&sa.sa_mask); - - if (sigaction(SIGINT, &sa, NULL)) - { - fprintf(stderr, "%s: sigaction(2) SIGINT: %s\n", - __func__, strerror(errno)); - return -1; - } - else if (sigaction(SIGTERM, &sa, NULL)) - { - fprintf(stderr, "%s: sigaction(2) SIGTERM: %s\n", - __func__, strerror(errno)); - return -1; - } - else if (sigaction(SIGPIPE, &sa, NULL)) - { - fprintf(stderr, "%s: sigaction(2) SIGPIPE: %s\n", - __func__, strerror(errno)); - return -1; - } - - return 0; -} - -struct server *server_init(const unsigned short port) -{ - struct server *const s = malloc(sizeof *s); - - if (!s) - { - fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); - goto failure; - } - - *s = (const struct server) - { - .fd = socket(AF_INET, SOCK_STREAM, 0) - }; - - if (s->fd < 0) - { - fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno)); - goto failure; - } - else if (init_signals()) - { - fprintf(stderr, "%s: init_signals failed\n", __func__); - goto failure; - } - - const struct sockaddr_in addr = - { - .sin_family = AF_INET, - .sin_port = htons(port) - }; - - enum {QUEUE_LEN = 10}; - - if (bind(s->fd, (const struct sockaddr *)&addr, sizeof addr)) - { - fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno)); - goto failure; - } - else if (listen(s->fd, QUEUE_LEN)) - { - fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno)); - goto failure; - } - - struct sockaddr_in in; - socklen_t sz = sizeof in; - - if (getsockname(s->fd, (struct sockaddr *)&in, &sz)) - { - fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno)); - goto failure; - } - - printf("Listening on port %hu\n", ntohs(in.sin_port)); - return s; - -failure: - server_close(s); - return NULL; -} diff --git a/server.h b/server.h deleted file mode 100644 index 74f06ae..0000000 --- a/server.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SERVER_H -#define SERVER_H - -#include <stdbool.h> -#include <stddef.h> - -struct server *server_init(unsigned short port); -struct server_client *server_poll(struct server *s, bool *io, bool *exit); -int server_read(void *buf, size_t n, struct server_client *c); -int server_write(const void *buf, size_t n, struct server_client *c); -int server_close(struct server *s); -int server_client_close(struct server *s, struct server_client *c); -void server_client_write_pending(struct server_client *c, bool write); - -#endif /* SERVER_H */ |
