diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-10-06 23:01:42 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-10-08 01:57:42 +0200 |
| commit | e77bd93693a74ce872d4c13fb45537c34518d84f (patch) | |
| tree | 8fb265064ebf9debed2e797cbdef42187524d158 /http.c | |
| parent | db9cb051c4ee05c07ab32dfed5bae8b7dc0916bd (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.c | 138 |
1 files changed, 133 insertions, 5 deletions
@@ -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; } |
