From 7fe639b3ba7a253627d2cd34f3b97bd95b0a90b3 Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Fri, 21 Jul 2023 01:07:57 +0200 Subject: Remove files now provided by slweb --- handler.c | 315 ---------- handler.h | 24 - html.c | 301 ---------- html.h | 15 - http.c | 1964 ------------------------------------------------------------- http.h | 106 ---- server.c | 373 ------------ server.h | 15 - 8 files changed, 3113 deletions(-) delete mode 100644 handler.c delete mode 100644 handler.h delete mode 100644 html.c delete mode 100644 html.h delete mode 100644 http.c delete mode 100644 http.h delete mode 100644 server.c delete mode 100644 server.h 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 -#include -#include -#include -#include -#include - -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 - -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 */ diff --git a/html.c b/html.c deleted file mode 100644 index 41ce387..0000000 --- a/html.c +++ /dev/null @@ -1,301 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "html.h" -#include -#include -#include -#include -#include -#include - -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, "", 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; -} diff --git a/html.h b/html.h deleted file mode 100644 index e62575e..0000000 --- a/html.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef HTML_H -#define HTML_H - -#include - -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 */ diff --git a/http.c b/http.c deleted file mode 100644 index cb28ee5..0000000 --- a/http.c +++ /dev/null @@ -1,1964 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "http.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; -} diff --git a/http.h b/http.h deleted file mode 100644 index ac1f51a..0000000 --- a/http.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef HTTP_H -#define HTTP_H - -#include -#include -#include - -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 -#include - -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 */ -- cgit v1.2.3