From 5bd22cb5e4290f5dad3bd15f9a9067075c5e92d4 Mon Sep 17 00:00:00 2001 From: cpq Date: Wed, 17 Mar 2021 07:43:29 +0000 Subject: [PATCH] Add mg_http_next_multipart() --- docs/README.md | 39 ++++++++++++++++++++++ mongoose.c | 84 ++++++++++++++++++++++++++++++++++++++--------- mongoose.h | 5 +-- src/http.c | 85 +++++++++++++++++++++++++++++++++++++++--------- src/http.h | 5 +-- test/unit_test.c | 28 ++++++++++++++++ 6 files changed, 211 insertions(+), 35 deletions(-) diff --git a/docs/README.md b/docs/README.md index fa27c55e..530d2c46 100644 --- a/docs/README.md +++ b/docs/README.md @@ -829,6 +829,45 @@ void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); Write a Basic `Authorization` header to the output buffer. +### mg\_http\_next_\multipart() + +```c +// Parameter for mg_http_next_multipart +struct mg_http_part { + struct mg_str name; // Form field name + struct mg_str filename; // Filename for file uploads + struct mg_str body; // Part contents +}; + +size_t mg_http_next_multipart(struct mg_str body, size_t offset, struct mg_http_part *part); +``` + +Parse the multipart chunk in the `body` at a given `offset`. An initial +`offset` should be 0. Fill up parameters in the provided `part`, which could be +NULL. Return offset to the next chunk, or 0 if there are no more chunks. + +Usage example: + +```c +static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_http_match_uri(hm, "/upload")) { + struct mg_http_part part; + size_t ofs = 0; + while ((ofs = mg_http_next_multipart(ev->body, ofs, &part)) > 0) { + LOG(LL_INFO, ("Name: [%.*s] Filename: [%.*s] Body: [%.*s]", + (int) part.name.len, part.name.ptr, + (int) part.filename.len, part.filename.ptr, + (int) part.body.len, part.body.ptr)); + } + } else { + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); + } + } +} +``` ## Websocket diff --git a/mongoose.c b/mongoose.c index 9ebe2862..76d14b10 100644 --- a/mongoose.c +++ b/mongoose.c @@ -429,12 +429,55 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) { // Multipart POST example: -// https://gist.github.com/cpq/b8dd247571e6ee9c54ef7e8dfcfecf48 -int mg_http_next_multipart(struct mg_str body, int offset, - struct mg_http_part *part) { - (void) body; - (void) part; - return offset; +// --xyz +// Content-Disposition: form-data; name="val" +// +// abcdef +// --xyz +// Content-Disposition: form-data; name="foo"; filename="a.txt" +// Content-Type: text/plain +// +// hello world +// +// --xyz-- +size_t mg_http_next_multipart(struct mg_str body, size_t ofs, + struct mg_http_part *part) { + struct mg_str cd = mg_str_n("Content-Disposition", 19); + const char *s = body.ptr; + size_t b = ofs, h1, h2, b1, b2, max = body.len; + + // Init part params + if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); + + // Skip boundary + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; + if (b <= ofs || b + 2 >= max) return 0; + // LOG(LL_INFO, ("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); + + // Skip headers + h1 = h2 = b + 2; + for (;;) { + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; + if (h2 == h1) break; + if (h2 + 2 >= max) return 0; + // LOG(LL_INFO, ("Header: [%.*s]", (int) (h2 - h1), &s[h1])); + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && + mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); + part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); + } + h1 = h2 = h2 + 2; + } + b1 = b2 = h2 + 2; + while (b2 + 2 + (b - ofs) + 2 < max && s[b2] != '\r' && s[b2 + 1] != '\n' && + memcmp(&s[b2 + 2], s, b - ofs) != 0) + b2++; + + if (b2 + 2 >= max) return 0; + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); + // LOG(LL_INFO, ("Body: [%.*s]", (int) (b2 - b1), &s[b1])); + return b2 + 2; } void mg_http_bauth(struct mg_connection *c, const char *user, @@ -1164,20 +1207,31 @@ void mg_http_creds(struct mg_http_message *hm, char *user, int userlen, } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7); } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { - size_t i; - for (i = 0; i < v->len - 13; i++) { - if (memcmp(&v->ptr[i], "access_token=", 13) == 0) { - const char *p2 = v->ptr + i + 13, *p3 = p2; - while (p2 < &v->ptr[v->len] && p2[0] != ';' && p2[0] != ' ') p2++; - snprintf(pass, passlen, "%.*s", (int) (p2 - p3), p3); - break; - } - } + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); + if (t.len > 0) snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr); } else { mg_http_get_var(&hm->query, "access_token", pass, passlen); } } +static struct mg_str stripquotes(struct mg_str s) { + return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' + ? mg_str_n(s.ptr + 1, s.len - 2) + : s; +} + +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { + size_t i; + for (i = 0; i + v.len + 2 < s.len; i++) { + if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { + const char *p2 = &s.ptr[i + v.len + 1], *p3 = p2; + while (p2 < &s.ptr[s.len] && p2[0] != ';' && p2[0] != ' ') p2++; + return stripquotes(mg_str_n(p3, p2 - p3)); + } + } + return mg_str_n(NULL, 0); +} + bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len); } diff --git a/mongoose.h b/mongoose.h index 9cd9ed19..39386542 100644 --- a/mongoose.h +++ b/mongoose.h @@ -752,7 +752,7 @@ struct mg_http_serve_opts { struct mg_http_part { struct mg_str name; // Form field name struct mg_str filename; // Filename for file uploads - struct mg_str part; // Part contents + struct mg_str body; // Part contents }; int mg_http_parse(const char *s, size_t len, struct mg_http_message *); @@ -780,7 +780,8 @@ bool mg_http_match_uri(const struct mg_http_message *, const char *glob); int mg_http_upload(struct mg_connection *, struct mg_http_message *hm, const char *dir); void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); -int mg_http_next_multipart(struct mg_str, int, struct mg_http_part *); +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); +size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); void mg_http_serve_ssi(struct mg_connection *c, const char *root, diff --git a/src/http.c b/src/http.c index 38fef6d9..13f072fd 100644 --- a/src/http.c +++ b/src/http.c @@ -10,13 +10,55 @@ #include "ws.h" // Multipart POST example: -// https://gist.github.com/cpq/b8dd247571e6ee9c54ef7e8dfcfecf48 -int mg_http_next_multipart(struct mg_str body, int offset, - struct mg_http_part *part) { - if (o - (void) body; - (void) part; - return 0; +// --xyz +// Content-Disposition: form-data; name="val" +// +// abcdef +// --xyz +// Content-Disposition: form-data; name="foo"; filename="a.txt" +// Content-Type: text/plain +// +// hello world +// +// --xyz-- +size_t mg_http_next_multipart(struct mg_str body, size_t ofs, + struct mg_http_part *part) { + struct mg_str cd = mg_str_n("Content-Disposition", 19); + const char *s = body.ptr; + size_t b = ofs, h1, h2, b1, b2, max = body.len; + + // Init part params + if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); + + // Skip boundary + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; + if (b <= ofs || b + 2 >= max) return 0; + // LOG(LL_INFO, ("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); + + // Skip headers + h1 = h2 = b + 2; + for (;;) { + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; + if (h2 == h1) break; + if (h2 + 2 >= max) return 0; + // LOG(LL_INFO, ("Header: [%.*s]", (int) (h2 - h1), &s[h1])); + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && + mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); + part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); + } + h1 = h2 = h2 + 2; + } + b1 = b2 = h2 + 2; + while (b2 + 2 + (b - ofs) + 2 < max && s[b2] != '\r' && s[b2 + 1] != '\n' && + memcmp(&s[b2 + 2], s, b - ofs) != 0) + b2++; + + if (b2 + 2 >= max) return 0; + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); + // LOG(LL_INFO, ("Body: [%.*s]", (int) (b2 - b1), &s[b1])); + return b2 + 2; } void mg_http_bauth(struct mg_connection *c, const char *user, @@ -746,20 +788,31 @@ void mg_http_creds(struct mg_http_message *hm, char *user, int userlen, } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7); } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { - size_t i; - for (i = 0; i < v->len - 13; i++) { - if (memcmp(&v->ptr[i], "access_token=", 13) == 0) { - const char *p2 = v->ptr + i + 13, *p3 = p2; - while (p2 < &v->ptr[v->len] && p2[0] != ';' && p2[0] != ' ') p2++; - snprintf(pass, passlen, "%.*s", (int) (p2 - p3), p3); - break; - } - } + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); + if (t.len > 0) snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr); } else { mg_http_get_var(&hm->query, "access_token", pass, passlen); } } +static struct mg_str stripquotes(struct mg_str s) { + return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' + ? mg_str_n(s.ptr + 1, s.len - 2) + : s; +} + +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { + size_t i; + for (i = 0; i + v.len + 2 < s.len; i++) { + if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { + const char *p2 = &s.ptr[i + v.len + 1], *p3 = p2; + while (p2 < &s.ptr[s.len] && p2[0] != ';' && p2[0] != ' ') p2++; + return stripquotes(mg_str_n(p3, p2 - p3)); + } + } + return mg_str_n(NULL, 0); +} + bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len); } diff --git a/src/http.h b/src/http.h index b74595a9..185fb48d 100644 --- a/src/http.h +++ b/src/http.h @@ -32,7 +32,7 @@ struct mg_http_serve_opts { struct mg_http_part { struct mg_str name; // Form field name struct mg_str filename; // Filename for file uploads - struct mg_str part; // Part contents + struct mg_str body; // Part contents }; int mg_http_parse(const char *s, size_t len, struct mg_http_message *); @@ -60,4 +60,5 @@ bool mg_http_match_uri(const struct mg_http_message *, const char *glob); int mg_http_upload(struct mg_connection *, struct mg_http_message *hm, const char *dir); void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); -int mg_http_next_multipart(struct mg_str, int, struct mg_http_part *); +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); +size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); diff --git a/test/unit_test.c b/test/unit_test.c index b5e88d3f..559a0a01 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1258,9 +1258,37 @@ static void test_http_chunked(void) { ASSERT(mgr.conns == NULL); } +static void test_multipart(void) { + struct mg_http_part part; + size_t ofs; + const char *s = + "--xyz\r\n" + "Content-Disposition: form-data; name=\"val\"\r\n" + "\r\n" + "abcdef\r\n" + "--xyz\r\n" + "Content-Disposition: form-data; name=\"foo\"; filename=\"a.txt\"\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "hello world\r\n" + "\r\n" + "--xyz--\r\n"; + ASSERT(mg_http_next_multipart(mg_str(""), 0, NULL) == 0); + ASSERT((ofs = mg_http_next_multipart(mg_str(s), 0, &part)) >= 0); + ASSERT(mg_strcmp(part.name, mg_str("val")) == 0); + ASSERT(mg_strcmp(part.body, mg_str("abcdef")) == 0); + ASSERT(part.filename.len == 0); + ASSERT((ofs = mg_http_next_multipart(mg_str(s), ofs, &part)) >= 0); + ASSERT(mg_strcmp(part.name, mg_str("foo")) == 0); + ASSERT(mg_strcmp(part.filename, mg_str("a.txt")) == 0); + ASSERT(mg_strcmp(part.body, mg_str("hello world")) == 0); + ASSERT(mg_http_next_multipart(mg_str(s), ofs, &part) == 0); +} + int main(void) { mg_log_set("3"); test_crc32(); + test_multipart(); test_http_chunked(); test_http_parse(); test_util();