diff options
Diffstat (limited to 'http.c')
| -rw-r--r-- | http.c | 285 |
1 files changed, 259 insertions, 26 deletions
@@ -37,11 +37,16 @@ struct http_ctx char *resource, *field, *value, *boundary; size_t len; - struct payload + union { - char *path; - unsigned long long len, read; - } payload; + struct http_get get; + + struct payload + { + char *path; + unsigned long long len, read; + } payload; + } u2; union { @@ -104,6 +109,7 @@ struct http_ctx bool pending, close; enum state state; struct http_response r; + struct http_get_range gr; off_t n; struct dynstr d; enum http_op op; @@ -116,6 +122,134 @@ struct http_ctx struct http_cfg cfg; }; +static int adjust_partial(struct write_ctx *const w) +{ + int ret = -1; + struct http_response *const r = &w->r; + const struct http_get_range *const g = &w->gr; + struct dynstr d; + const unsigned long long total = r->n; + unsigned long long start = 0, end = total - 1; + + dynstr_init(&d); + + if (!r->f) + { + fprintf(stderr, "%s: expected non-NULL file\n", __func__); + goto end; + } + else if (g->state & HTTP_GET_RANGE_END) + { + if (g->end >= r->n) + { + fprintf(stderr, "%s: end offset larger than file size " + "(%llu, %llu)\n", __func__, g->start, r->n); + ret = 1; + goto end; + } + else if (dynstr_append(&d, "%llu")) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + r->n = g->end; + end = g->end; + } + + if (g->state & HTTP_GET_RANGE_START) + { + if (g->start >= r->n) + { + fprintf(stderr, "%s: start offset larger than file size " + "(%llu, %llu)\n", __func__, g->start, r->n); + ret = 1; + goto end; + } + else if (fseeko(r->f, g->start, SEEK_SET)) + { + fprintf(stderr, "%s: fseeko(3): %s\n", __func__, strerror(errno)); + goto end; + } + + r->n -= g->start; + start = g->start; + } + + char range[sizeof "18446744073709551615"]; + const int res = snprintf(range, sizeof range, "%llu", r->n); + + if (res < 0 || res >= sizeof range) + { + fprintf(stderr, "%s: snprintf(3) failed: %d\n", __func__, res); + goto end; + } + else if (dynstr_append(&d, "bytes %llu-%llu/%llu", start, end, total)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (http_response_add_header(r, "Content-Range", d.str)) + { + fprintf(stderr, "%s: http_response_add_header range failed\n", + __func__); + goto end; + } + else if (http_response_add_header(r, "Content-Length", range)) + { + fprintf(stderr, "%s: http_response_add_header length failed\n", + __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + return ret; +} + +static int send_length(struct write_ctx *const w) +{ + struct http_response *const r = &w->r; + + if (r->status == HTTP_STATUS_PARTIAL_CONTENT) + { + const int res = adjust_partial(w); + + if (res) + { + fprintf(stderr, "%s: adjust_file failed\n", __func__); + return res; + } + } + else + { + char len[sizeof "18446744073709551615"]; + const int res = snprintf(len, sizeof len, "%llu", r->n); + + if (res < 0 || res >= sizeof len) + { + fprintf(stderr, "%s: snprintf(3) failed\n", __func__); + return -1; + } + else if (http_response_add_header(r, "Content-Length", len)) + { + fprintf(stderr, "%s: http_response_add_header failed\n", __func__); + return -1; + } + } + + if (http_response_add_header(r, "Accept-Ranges", "bytes")) + { + fprintf(stderr, "%s: http_response_add_header length failed\n", + __func__); + return -1; + } + + return 0; +} + static void arg_free(struct http_arg *const a) { if (a) @@ -611,20 +745,14 @@ static int write_start_line(struct http_ctx *const h, bool *const close) 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); + int res; - dynstr_free(d); + dynstr_free(&w->d); - if (res < 0 || res >= sizeof len) + if ((res = send_length(w))) { - 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; + fprintf(stderr, "%s: send_length failed\n", __func__); + return res; } else if (prepare_headers(h)) { @@ -907,7 +1035,7 @@ static int set_length(struct http_ctx *const h, const char *const len) case HTTP_OP_POST: /* Fall through. */ case HTTP_OP_PUT: - c->payload.len = value; + c->u2.payload.len = value; c->u.put = (const struct put){0}; break; @@ -1152,6 +1280,84 @@ static int expect(struct http_ctx *const h, const char *const value) return 0; } +static int set_range(struct http_ctx *const h, const char *const range) +{ + const char *const sep = strchr(range, '='); + + if (!sep) + { + fprintf(stderr, "%s: missing separator\n", __func__); + return 1; + } + + const size_t tlen = sep - range; + + if (strncmp(range, "bytes", tlen)) + { + fprintf(stderr, "%s: unsupported range type %.*s\n", + __func__, (int)tlen, range); + return 1; + } + + const char *const ssep = strchr(sep, '-'); + + if (!ssep) + { + fprintf(stderr, "%s: missing range separator\n", __func__); + return 1; + } + + struct http_get_range *const r = &h->ctx.u2.get.range; + + *r = (const struct http_get_range){0}; + + if (ssep != sep + 1) + { + const char *const start = sep + 1; + char *endptr; + + errno = 0; + r->start = strtoull(start, &endptr, 10); + + if (errno || endptr != ssep) + { + fprintf(stderr, "%s: unexpected start range %.*s\n", + __func__, (int)(ssep - start), start); + return 1; + } + + r->state |= HTTP_GET_RANGE_START; + } + + const char *const end = ssep + 1; + + if (*end) + { + char *endptr; + + errno = 0; + r->end = strtoull(end, &endptr, 10); + + if (errno || *endptr) + { + fprintf(stderr, "%s: unexpected end range %s\n", __func__, end); + return 1; + } + + r->state |= HTTP_GET_RANGE_END; + + if (r->state & HTTP_GET_RANGE_START + && r->start > r->end) + { + fprintf(stderr, "%s: start offset larger than end (%llu, %llu)\n", + __func__, r->start, r->end); + return 1; + } + } + + return 0; +} + static int append_header(struct http_ctx *const h, const char *const line, const size_t n, const char *const value) { @@ -1229,6 +1435,11 @@ static int process_header(struct http_ctx *const h, const char *const line, { .header = "Content-Type", .f = set_content_type + }, + + { + .header = "Range", + .f = set_range } }; @@ -1253,7 +1464,7 @@ static int check_length(struct http_ctx *const h) .value = c->value }; - return h->cfg.length(c->payload.len, &cookie, &h->wctx.r, h->cfg.user); + return h->cfg.length(c->u2.payload.len, &cookie, &h->wctx.r, h->cfg.user); } static int process_expect(struct http_ctx *const h) @@ -1288,6 +1499,27 @@ static int process_expect(struct http_ctx *const h) return start_response(h); } +static int process_get(struct http_ctx *const h) +{ + struct ctx *const c = &h->ctx; + struct http_payload p = ctx_to_payload(c); + struct write_ctx *const w = &h->wctx; + + p.u.get.range = w->gr = c->u2.get.range; + + const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user); + + w->op = c->op; + ctx_free(c); + + if (ret) + return ret; + else if (w->gr.state) + w->r.status = HTTP_STATUS_PARTIAL_CONTENT; + + return start_response(h); +} + static int header_cr_line(struct http_ctx *const h) { const char *const line = (const char *)h->line; @@ -1298,13 +1530,14 @@ static int header_cr_line(struct http_ctx *const h) switch (c->op) { case HTTP_OP_GET: - /* Fall through. */ + return process_get(h); + case HTTP_OP_HEAD: return process_payload(h); case HTTP_OP_POST: { - if (!c->payload.len) + if (!c->u2.payload.len) return process_payload(h); else if (c->expect_continue) return process_expect(h); @@ -1337,7 +1570,7 @@ static int header_cr_line(struct http_ctx *const h) __func__); return 1; } - else if (!c->payload.len) + else if (!c->u2.payload.len) return process_payload(h); else if (c->expect_continue) return process_expect(h); @@ -1616,7 +1849,7 @@ static int process_mf_line(struct http_ctx *const h) [MF_HEADER_CR_LINE] = mf_header_cr_line }; - h->ctx.payload.read += strlen(h->line) + strlen("\r\n"); + h->ctx.u2.payload.read += strlen(h->line) + strlen("\r\n"); return state[h->ctx.u.mf.state](h); } @@ -1691,7 +1924,7 @@ static int read_mf_body_to_mem(struct http_ctx *const h, memcpy(h->line, buf, n); m->written += n; m->len += n; - c->payload.read += n; + c->u2.payload.read += n; return 0; } @@ -1715,7 +1948,7 @@ static int read_mf_body_to_file(struct http_ctx *const h, m->written += n; m->len += n; - c->payload.read += n; + c->u2.payload.read += n; return 0; } @@ -2055,7 +2288,7 @@ static int read_body_to_mem(struct http_ctx *const h, const char *const buf, size_t *const sz) { struct ctx *const c = &h->ctx; - struct payload *const p = &c->payload; + struct payload *const p = &c->u2.payload; const char b = *buf; if (p->read >= sizeof h->line - 1) @@ -2158,7 +2391,7 @@ static int read_put_body_to_file(struct http_ctx *const h, return -1; } - c->payload.read += *sz; + c->u2.payload.read += *sz; *sz = 0; return 0; } @@ -2167,7 +2400,7 @@ static int read_to_file(struct http_ctx *const h, const char *const buf, size_t *const sz) { struct ctx *const c = &h->ctx; - struct payload *const p = &c->payload; + struct payload *const p = &c->u2.payload; if (read_put_body_to_file(h, buf, sz)) return -1; |
