git.strcat.st

/strcat/filebin.git/ - summarytreelogarchive

subject
add support for chunked uploads
commit
9c99059a450b120b61c11fa9774ba17f58796a2f
date
2026-05-28T17:28:15Z
message
diff
 filebin.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 183 insertions(+), 10 deletions(-)

diff --git a/filebin.c b/filebin.c
index f0a45d3..b2ac476 100644
--- a/filebin.c
+++ b/filebin.c
@@ -77,6 +77,7 @@ static const char *html_index =
 static int	has_suffix(const char *, const char *);
 static int	grow_reqbuf(struct client *);
 static int	hdr_get(const char *, const char *, char *, size_t);
+static int	hdr_has_chunked(const char *);
 static int	eqi_n(const char *, const char *, size_t);
 static int	isimg_ext(const char *);
 static int	req_done(const char *, size_t);
@@ -93,6 +94,8 @@ static off_t	used_bytes(void);
 static char	*find_bytes(const char *, size_t, const char *, size_t);
 
 static long	parse_clen(const char *, const char *);
+static int	parse_hex_size(const char *, size_t, size_t *);
+static int	decode_chunked_body(const char *, size_t, char **, size_t *);
 
 static void	cleanup_expired(void);
 static void	client_close(struct client *);
@@ -519,6 +522,122 @@ parse_clen(const char *hdr, const char *hdr_end)
 	return -1;
 }
 
+static int
+hdr_has_chunked(const char *hdr)
+{
+	char te[256];
+	char low[256];
+	char *tok;
+	size_t i;
+	size_t n;
+
+	if (hdr_get(hdr, "Transfer-Encoding", te, sizeof(te)) == 0)
+	    return 0;
+	n = strlen(te);
+	for (i = 0; i < n && i + 1 < sizeof(low); i++)
+	    low[i] = (char)tolower((unsigned char)te[i]);
+	low[i] = '\0';
+	tok = strtok(low, ",");
+	while (tok != NULL) {
+	    while (*tok == ' ' || *tok == '\t')
+	        tok++;
+	    if (strcmp(tok, "chunked") == 0)
+	        return 1;
+	    tok = strtok(NULL, ",");
+	}
+	return 0;
+}
+
+static int
+parse_hex_size(const char *p, size_t len, size_t *out)
+{
+	size_t i;
+	size_t v;
+	int digit;
+
+	v = 0;
+	if (len == 0)
+	    return 0;
+	for (i = 0; i < len; i++) {
+	    if (p[i] == ';')
+	        break;
+	    if (p[i] >= '0' && p[i] <= '9')
+	        digit = p[i] - '0';
+	    else if (p[i] >= 'a' && p[i] <= 'f')
+	        digit = 10 + (p[i] - 'a');
+	    else if (p[i] >= 'A' && p[i] <= 'F')
+	        digit = 10 + (p[i] - 'A');
+	    else
+	        return 0;
+	    if (v > (size_t)-1 / 16)
+	        return 0;
+	    v = v * 16 + (size_t)digit;
+	}
+	*out = v;
+	return 1;
+}
+
+static int
+decode_chunked_body(const char *in, size_t inlen, char **out, size_t *outlen)
+{
+	char *buf;
+	const char *line_end;
+	size_t chunk;
+	size_t off;
+	size_t line_len;
+	size_t woff;
+
+	buf = malloc(inlen + 1);
+	if (buf == NULL)
+	    return 0;
+	off = 0;
+	woff = 0;
+	while (off < inlen) {
+	    line_end = find_bytes(in + off, inlen - off, "\r\n", 2);
+	    if (line_end == NULL) {
+	        free(buf);
+	        return 0;
+	    }
+	    line_len = (size_t)(line_end - (in + off));
+	    if (parse_hex_size(in + off, line_len, &chunk) == 0) {
+	        free(buf);
+	        return 0;
+	    }
+	    off += line_len + 2;
+	    if (chunk == 0) {
+	        char *trail_end;
+
+	        trail_end = find_bytes(in + off, inlen - off, "\r\n\r\n", 4);
+	        if (trail_end == NULL && inlen - off >= 2 &&
+	            in[off] == '\r' && in[off + 1] == '\n')
+	            off += 2;
+	        else if (trail_end != NULL)
+	            off = (size_t)(trail_end - in) + 4;
+	        else {
+	            free(buf);
+	            return 0;
+	        }
+	        break;
+	    }
+	    if (chunk > inlen - off || woff > MAX_REQ - chunk) {
+	        free(buf);
+	        return 0;
+	    }
+	    memcpy(buf + woff, in + off, chunk);
+	    woff += chunk;
+	    off += chunk;
+	    if (inlen - off < 2 || in[off] != '\r' || in[off + 1] != '\n') {
+	        free(buf);
+	        return 0;
+	    }
+	    off += 2;
+	}
+	buf[woff] = '\0';
+	*out = buf;
+	*outlen = woff;
+	return 1;
+}
+
 static char *
 find_bytes(const char *hay, size_t hlen, const char *needle, size_t nlen)
 {
@@ -666,6 +785,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	char path[128];
 	char rnd[16];
 	char ttl_hdr[64];
+	char *dec_body;
 	char *body;
 	char *bpos;
 	char *curr;
@@ -690,12 +810,14 @@ serve_upload(int fd, char *req, size_t req_len)
 	long body_len;
 	long ttl_sec;
 	int have_file;
+	int is_chunked;
 	int i;
 	int urfd;
 	unsigned char r[12];
 	FILE *fp;
 
 	ttl_sec = DEFAULT_TTL_SEC;
+	dec_body = NULL;
 	filename[0] = '\0';
 	file_body = NULL;
 	file_bytes = 0;
@@ -706,8 +828,12 @@ serve_upload(int fd, char *req, size_t req_len)
 	    return;
 	}
 	*hdr_end = '\0';
-	if (hdr_get(req, "Content-Type", ct, sizeof(ct)) == 0 ||
-	    hdr_get(req, "Content-Length", cl, sizeof(cl)) == 0) {
+	if (hdr_get(req, "Content-Type", ct, sizeof(ct)) == 0) {
+	    sendtxt(fd, "400 Bad Request", "missing headers\n");
+	    return;
+	}
+	is_chunked = hdr_has_chunked(req);
+	if (is_chunked == 0 && hdr_get(req, "Content-Length", cl, sizeof(cl)) == 0) {
 	    sendtxt(fd, "400 Bad Request", "missing headers\n");
 	    return;
 	}
@@ -717,15 +843,30 @@ serve_upload(int fd, char *req, size_t req_len)
 	        return;
 	    }
 	}
-	body_len = atol(cl);
-	if (body_len <= 0 || body_len > MAX_REQ) {
-	    sendtxt(fd, "413 Payload Too Large", "bad size\n");
-	    return;
-	}
 	body = hdr_end + 4;
-	if ((size_t)(body - req) + (size_t)body_len > req_len) {
-	    sendtxt(fd, "400 Bad Request", "truncated body\n");
-	    return;
+	if (is_chunked != 0) {
+	    size_t raw_len;
+	    size_t dec_len;
+
+	    raw_len = req_len - (size_t)(body - req);
+	    if (decode_chunked_body(body, raw_len, &dec_body, &dec_len) == 0 ||
+	        dec_len == 0 || dec_len > MAX_REQ) {
+	        free(dec_body);
+	        sendtxt(fd, "400 Bad Request", "bad chunked body\n");
+	        return;
+	    }
+	    body = dec_body;
+	    body_len = (long)dec_len;
+	} else {
+	    body_len = atol(cl);
+	    if (body_len <= 0 || body_len > MAX_REQ) {
+	        sendtxt(fd, "413 Payload Too Large", "bad size\n");
+	        return;
+	    }
+	    if ((size_t)(body - req) + (size_t)body_len > req_len) {
+	        sendtxt(fd, "400 Bad Request", "truncated body\n");
+	        return;
+	    }
 	}
 	bpos = strstr(ct, "boundary=");
 	if (bpos == NULL) {
@@ -759,6 +900,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	bterm_len = strlen(bterm);
 	if ((size_t)body_len < bmark_len ||
 	    memcmp(body, bmark, bmark_len) != 0) {
+	    free(dec_body);
 	    sendtxt(fd, "400 Bad Request", "bad multipart\n");
 	    return;
 	}
@@ -769,6 +911,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	    if (part_rem >= 2 && part[0] == '-' && part[1] == '-')
 	        break;
 	    if (part_rem < 2 || part[0] != '\r' || part[1] != '\n') {
+	        free(dec_body);
 	        sendtxt(fd, "400 Bad Request", "bad part\n");
 	        return;
 	    }
@@ -776,17 +919,20 @@ serve_upload(int fd, char *req, size_t req_len)
 	    part_rem = (size_t)(body + body_len - part);
 	    part_hdr_end = find_bytes(part, part_rem, "\r\n\r\n", 4);
 	    if (part_hdr_end == NULL) {
+	        free(dec_body);
 	        sendtxt(fd, "400 Bad Request", "bad part\n");
 	        return;
 	    }
 	    part_hdr_len = (size_t)(part_hdr_end - part);
 	    if (part_hdr_len >= sizeof(ct)) {
+	        free(dec_body);
 	        sendtxt(fd, "400 Bad Request", "part headers too large\n");
 	        return;
 	    }
 	    memcpy(ct, part, part_hdr_len);
 	    ct[part_hdr_len] = '\0';
 	    if (hdr_get(ct, "Content-Disposition", cd, sizeof(cd)) == 0) {
+	        free(dec_body);
 	        sendtxt(fd, "400 Bad Request", "no disposition\n");
 	        return;
 	    }
@@ -822,6 +968,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	    pbody_len = (size_t)(body + body_len - pbody);
 	    pend = find_bytes(pbody, pbody_len, bterm, bterm_len);
 	    if (pend == NULL) {
+	        free(dec_body);
 	        sendtxt(fd, "400 Bad Request", "part end missing\n");
 	        return;
 	    }
@@ -832,6 +979,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	        memcpy(ttl_hdr, pbody, field_len);
 	        ttl_hdr[field_len] = '\0';
 	        if (parse_ttl(ttl_hdr, &ttl_sec) == 0) {
+	            free(dec_body);
 	            sendtxt(fd, "400 Bad Request", "bad ttl (use 1m/1h/1d)\n");
 	            return;
 	        }
@@ -843,17 +991,20 @@ serve_upload(int fd, char *req, size_t req_len)
 	    curr = pend + 2;
 	    if ((size_t)(body + body_len - curr) < bmark_len ||
 	        memcmp(curr, bmark, bmark_len) != 0) {
+	        free(dec_body);
 	        sendtxt(fd, "400 Bad Request", "bad multipart\n");
 	        return;
 	    }
 	}
 	if (have_file == 0) {
+	    free(dec_body);
 	    sendtxt(fd, "400 Bad Request", "missing file\n");
 	    return;
 	}
 	used_sz = used_bytes();
 	cap_sz = cap_bytes();
 	if ((off_t)file_bytes > cap_sz - used_sz) {
+	    free(dec_body);
 	    sendtxt(fd, "507 Insufficient Storage", "storage cap reached\n");
 	    return;
 	}
@@ -878,6 +1029,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	snprintf(path, sizeof(path), "f/%s", final);
 	fp = fopen(path, "wb");
 	if (fp == NULL) {
+	    free(dec_body);
 	    sendtxt(fd, "500 Internal Server Error", "cannot write\n");
 	    return;
 	}
@@ -899,6 +1051,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	        "alt='upload'></p>",
 	        final, final, final);
 	    sendall(fd, resp, (size_t)n);
+	    free(dec_body);
 	    return;
 	}
 	{
@@ -913,6 +1066,7 @@ serve_upload(int fd, char *req, size_t req_len)
 	        final);
 	    sendall(fd, resp, (size_t)n);
 	}
+	free(dec_body);
 }
 
 static int
@@ -968,12 +1122,31 @@ static int
 req_done(const char *req, size_t off)
 {
 	char *hdr_end;
+	char *trail_end;
 	long need;
 	size_t have;
 
 	hdr_end = strstr((char *)req, "\r\n\r\n");
 	if (hdr_end == NULL)
 	    return 0;
+	if (hdr_has_chunked(req) != 0) {
+	    trail_end = find_bytes(hdr_end + 4, off - (size_t)(hdr_end + 4 - req),
+	        "\r\n0\r\n\r\n", 7);
+	    if (trail_end != NULL)
+	        return 1;
+	    trail_end = find_bytes(hdr_end + 4, off - (size_t)(hdr_end + 4 - req),
+	        "\r\n0\r\n", 5);
+	    if (trail_end != NULL) {
+	        trail_end = find_bytes(trail_end + 5,
+	            off - (size_t)(trail_end + 5 - req), "\r\n\r\n", 4);
+	        if (trail_end != NULL)
+	            return 1;
+	    }
+	    if (off >= (size_t)(hdr_end + 6 - req) &&
+	        memcmp(hdr_end + 4, "0\r\n\r\n", 5) == 0)
+	        return 1;
+	    return 0;
+	}
 	need = parse_clen(req, hdr_end);
 	have = off - (size_t)(hdr_end + 4 - req);
 	if (need < 0)