aboutsummaryrefslogtreecommitdiff
path: root/http.c
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-10-06 23:01:42 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-10-08 01:57:42 +0200
commite77bd93693a74ce872d4c13fb45537c34518d84f (patch)
tree8fb265064ebf9debed2e797cbdef42187524d158 /http.c
parentdb9cb051c4ee05c07ab32dfed5bae8b7dc0916bd (diff)
Implement HTTP chunk encoding
A new function pointer, namely chunk, has been added to struct http_response so that library users can generate their message bodies dynamically.
Diffstat (limited to 'http.c')
-rw-r--r--http.c138
1 files changed, 133 insertions, 5 deletions
diff --git a/http.c b/http.c
index c173c3d..21966bc 100644
--- a/http.c
+++ b/http.c
@@ -109,13 +109,16 @@ struct http_ctx
struct write_ctx
{
- bool pending, close;
+ bool pending, close, chunk_done;
enum state state;
struct http_response r;
struct http_get_range gr;
off_t n;
struct dynstr d;
enum http_op op;
+ void *chunk;
+ int (*next)(struct http_ctx *, bool *close);
+ size_t written, chunklen;
} wctx;
/* From RFC9112, section 3 (Request line):
@@ -125,6 +128,8 @@ struct http_ctx
struct http_cfg cfg;
};
+static int read_chunk(struct http_ctx *, bool *);
+
static int adjust_partial(struct write_ctx *const w)
{
int ret = -1;
@@ -226,6 +231,16 @@ static int send_length(struct write_ctx *const w)
return res;
}
}
+ else if (r->chunk)
+ {
+ if (http_response_add_header(r, "Transfer-Encoding", "chunked"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+
+ w->next = read_chunk;
+ }
else
{
char len[sizeof "18446744073709551615"];
@@ -243,7 +258,7 @@ static int send_length(struct write_ctx *const w)
}
}
- if (http_response_add_header(r, "Accept-Ranges", "bytes"))
+ if (!r->chunk && http_response_add_header(r, "Accept-Ranges", "bytes"))
{
fprintf(stderr, "%s: http_response_add_header length failed\n",
__func__);
@@ -782,6 +797,7 @@ static int write_ctx_free(struct write_ctx *const w)
if (r->f && (ret = fclose(r->f)))
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
+ free(w->chunk);
dynstr_free(&w->d);
free_response_headers(&w->r);
*w = (const struct write_ctx){0};
@@ -808,7 +824,7 @@ static int write_header_cr_line(struct http_ctx *const h, bool *const close)
dynstr_free(d);
- if (w->r.n && must_write_body(w))
+ if (w->r.chunk || (w->r.n && must_write_body(w)))
{
w->state = BODY_LINE;
w->n = 0;
@@ -825,6 +841,116 @@ static int write_header_cr_line(struct http_ctx *const h, bool *const close)
return 0;
}
+static int write_chunkend(struct http_ctx *const h, bool *const close)
+{
+ static const char end[] = "\r\n";
+ struct write_ctx *const w = &h->wctx;
+ const void *const src = end + w->written;
+ const int r = h->cfg.write(src, strlen(end) - w->written, h->cfg.user);
+
+ if (r < 0)
+ return rw_error(r, close);
+ else if ((w->written += r) >= strlen(end))
+ {
+ if (w->chunk_done)
+ {
+ 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;
+ }
+ else
+ w->next = read_chunk;
+ }
+
+ return 0;
+}
+
+static int write_chunkdata(struct http_ctx *const h, bool *const close)
+{
+ struct write_ctx *const w = &h->wctx;
+ const void *const src = (const char *)w->chunk + w->written;
+ const int r = h->cfg.write(src, w->chunklen - w->written, h->cfg.user);
+
+ if (r < 0)
+ return rw_error(r, close);
+ else if ((w->written += r) >= w->chunklen)
+ {
+ w->written = 0;
+ w->next = write_chunkend;
+ }
+
+ return 0;
+}
+
+static int write_chunklen(struct http_ctx *const h, bool *const close)
+{
+ struct write_ctx *const w = &h->wctx;
+ struct dynstr *const d = &w->d;
+ const void *const src = d->str + w->written;
+ const int r = h->cfg.write(src, d->len - w->written, h->cfg.user);
+
+ if (r < 0)
+ return rw_error(r, close);
+ else if ((w->written += r) >= d->len)
+ {
+ dynstr_free(d);
+ w->written = 0;
+ w->next = write_chunkdata;
+ }
+
+ return 0;
+}
+
+static int read_chunk(struct http_ctx *const h, bool *const close)
+{
+ bool done = false;
+ struct write_ctx *const w = &h->wctx;
+ struct dynstr *const d = &w->d;
+ const struct http_response *const r = &w->r;
+ const int n = r->chunk(w->chunk, BUFSIZ, &done, h->cfg.user, r->args);
+
+ if (n < 0)
+ {
+ fprintf(stderr, "%s: user callback failed\n", __func__);
+ return -1;
+ }
+ else if (!n && !done)
+ return 0;
+
+ dynstr_init(d);
+
+ if (dynstr_append(d, "%X\r\n", n))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ return -1;
+ }
+
+ w->written = 0;
+ w->chunklen = n;
+ w->chunk_done = done;
+ w->next = write_chunklen;
+ return 0;
+}
+
+static int write_chunk(struct http_ctx *const h, bool *const close)
+{
+ struct write_ctx *const w = &h->wctx;
+
+ if (!w->chunk && !(w->chunk = malloc(BUFSIZ)))
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ return w->next(h, close);
+}
+
static int write_body_mem(struct http_ctx *const h, bool *const close)
{
struct write_ctx *const w = &h->wctx;
@@ -892,12 +1018,14 @@ 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)
+ if (r->chunk)
+ return write_chunk(h, close);
+ else 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__);
+ fprintf(stderr, "%s: expected chunk, buffer or file path\n", __func__);
return -1;
}