aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http.c285
-rw-r--r--include/libweb/http.h16
2 files changed, 275 insertions, 26 deletions
diff --git a/http.c b/http.c
index 2360c41..fbdba54 100644
--- a/http.c
+++ b/http.c
@@ -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;
diff --git a/include/libweb/http.h b/include/libweb/http.h
index 4e80570..736a561 100644
--- a/include/libweb/http.h
+++ b/include/libweb/http.h
@@ -49,6 +49,21 @@ struct http_payload
{
const char *tmpname;
} put;
+
+ struct http_get
+ {
+ struct http_get_range
+ {
+ enum
+ {
+ HTTP_GET_RANGE_NONE,
+ HTTP_GET_RANGE_START = 1 << 0,
+ HTTP_GET_RANGE_END = 1 << 1
+ } state;
+
+ unsigned long long start, end;
+ } range;
+ } get;
} u;
const struct http_arg
@@ -64,6 +79,7 @@ struct http_payload
#define HTTP_STATUSES \
X(CONTINUE, "Continue", 100) \
X(OK, "OK", 200) \
+ X(PARTIAL_CONTENT, "Partial Content", 206) \
X(SEE_OTHER, "See other", 303) \
X(BAD_REQUEST, "Bad Request", 400) \
X(UNAUTHORIZED, "Unauthorized", 401) \