From 3a25e79f269aa171f4e5646d52eb2f90d275cb3c Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Wed, 21 Aug 2024 23:33:00 +0200 Subject: http.c: Accept double quotes on boundaries "multipart/form-data"-encoded POST requests might use double quotes for their boundaries. While this is required when invalid characters are otherwise used (e.g.: ':'), some web clients always insert double quotes. Additionally, according to RFC 2046 section 5.1.1, the boundary parameter consists of 1 to 70 characters, but libweb was not imposing such restrictions. --- http.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/http.c b/http.c index f6c5d5f..bc269a7 100644 --- a/http.c +++ b/http.c @@ -923,6 +923,64 @@ static int set_length(struct http_ctx *const h, const char *const len) return 0; } +static int get_boundary(const char *bnd, char *const buf, const size_t n) +{ + size_t len = strlen(bnd); + bool quotes = false; + + if (*bnd == '"') + { + if (bnd[len - 1] != '"') + { + fprintf(stderr, "%s: boundary starts with, but does not end with, " + "double quotes\n", __func__); + return 1; + } + + len -= strlen("\"\""); + quotes = true; + bnd++; + } + + int res = snprintf(buf, n, "%.*s", (int)len, bnd); + + if (res < 0) + { + fprintf(stderr, "%s: snprintf(3) failed with %d\n", __func__, res); + return -1; + } + else if (res >= n) + { + fprintf(stderr, "%s: boundary exceeds maximum length (%d/%zu)\n", + __func__, res, n); + return 1; + } + else if (isspace(buf[res - 1])) + { + fprintf(stderr, "%s: boundary ends with whitespace character: %s\n", + __func__, buf); + return 1; + } + +#define BSET "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "0123456789'()+_,-./=?" + + static const char bset[] = BSET, extset[] = BSET ": "; +#undef BSET + + const char *const set = quotes ? extset : bset; + + if (strspn(buf, set) != res) + { + fprintf(stderr, "%s: boundary contains invalid characters: %s\n", + __func__, buf); + return 1; + } + + return 0; +} + static int set_content_type(struct http_ctx *const h, const char *const type) { const char *const sep = strchr(type, ';'); @@ -975,14 +1033,15 @@ static int set_content_type(struct http_ctx *const h, const char *const type) } const char *val = eq + 1; + /* According to RFC 2046, section 5.1.1, null character included. */ + enum {MAXLEN = 71}; + char buf[MAXLEN]; + const int ret = get_boundary(val, buf, sizeof buf); - while (*val == ' ') - val++; - - if (!*val) + if (ret) { - fprintf(stderr, "%s: expected value after boundary\n", __func__); - return 1; + fprintf(stderr, "%s: get_boundary failed\n", __func__); + return ret; } struct ctx *const c = &h->ctx; @@ -990,7 +1049,7 @@ static int set_content_type(struct http_ctx *const h, const char *const type) dynstr_init(&b); - if (dynstr_append(&b, "\r\n--%s", val)) + if (dynstr_append(&b, "\r\n--%s", buf)) { fprintf(stderr, "%s: dynstr_append failed\n", __func__); return -1; -- cgit v1.2.3