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)