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 /http.c | |
| parent | 1d4480c0f36f9d4e75c00e58c065b4c43e53b4f3 (diff) | |
| download | slcl-7fe639b3ba7a253627d2cd34f3b97bd95b0a90b3.tar.gz | |
Remove files now provided by slweb
Diffstat (limited to 'http.c')
| -rw-r--r-- | http.c | 1964 |
1 files changed, 0 insertions, 1964 deletions
@@ -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; -} |
