From 4dbb2da78f3bac25d50dfb94d8dc06dde59276c2 Mon Sep 17 00:00:00 2001 From: cpq Date: Wed, 17 Mar 2021 13:28:36 +0000 Subject: [PATCH] Add form-upload example --- docs/README.md | 22 +------------ examples/form-upload/Makefile | 10 ++++++ examples/form-upload/main.c | 42 ++++++++++++++++++++++++ examples/form-upload/web_root/index.html | 40 ++++++++++++++++++++++ mongoose.c | 13 +++++--- src/http.c | 13 +++++--- test/unit_test.c | 12 ++++--- 7 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 examples/form-upload/Makefile create mode 100644 examples/form-upload/main.c create mode 100644 examples/form-upload/web_root/index.html diff --git a/docs/README.md b/docs/README.md index 530d2c46..72895e22 100644 --- a/docs/README.md +++ b/docs/README.md @@ -846,28 +846,8 @@ 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: +See `form-upload` example for a 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/examples/form-upload/Makefile b/examples/form-upload/Makefile new file mode 100644 index 00000000..eaa5aa25 --- /dev/null +++ b/examples/form-upload/Makefile @@ -0,0 +1,10 @@ +PROG ?= example + +all: $(PROG) + $(DEBUGGER) ./$(PROG) + +$(PROG): + $(CC) ../../mongoose.c -I../.. $(CFLAGS) $(EXTRA) -o $(PROG) main.c + +clean: + rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt diff --git a/examples/form-upload/main.c b/examples/form-upload/main.c new file mode 100644 index 00000000..6872003c --- /dev/null +++ b/examples/form-upload/main.c @@ -0,0 +1,42 @@ +// Copyright (c) 2021 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" + +// HTTP request handler function. It implements the following endpoints: +// /upload - prints all submitted form elements +// all other URI - serves web_root/ directory +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; + LOG(LL_INFO, ("New request to: [%.*s]", (int) hm->uri.len, hm->uri.ptr)); + if (mg_http_match_uri(hm, "/upload")) { + struct mg_http_part part; + size_t ofs = 0; + while ((ofs = mg_http_next_multipart(hm->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)); + } + mg_http_reply(c, 200, "", "Thank you!"); + } else { + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); + } + } +} + +int main(void) { + struct mg_mgr mgr; + struct mg_timer t1; + + mg_mgr_init(&mgr); + mg_log_set("3"); + mg_http_listen(&mgr, "http://localhost:8000", cb, NULL); + + for (;;) mg_mgr_poll(&mgr, 50); + mg_mgr_free(&mgr); + + return 0; +} diff --git a/examples/form-upload/web_root/index.html b/examples/form-upload/web_root/index.html new file mode 100644 index 00000000..3b0a06c3 --- /dev/null +++ b/examples/form-upload/web_root/index.html @@ -0,0 +1,40 @@ + + + + example + + + + + + +
+
+ Mongoose always buffers a full HTTP message before invoking + MG_EV_HTTP_MSG event. Big POST request require of lot + of RAM to buffer everything. Therefore, standard form uploads + should be used only when Mongoose runs on a system with lots of RAM. + Otherwise, please see file-updload example, how + a big file could be uploaded to a device with little RAM. +

+ In this example, a standard HTML form upload is used, which uses + multipart-form-data encoding with + several variables and file upload. On a server side, a + mg_http_next_multipart() API is used to iterate over + all submitted form elements and print their payload. +
+
+
+
+
+ +
+
+
+ + diff --git a/mongoose.c b/mongoose.c index 6093b3d3..23ed3362 100644 --- a/mongoose.c +++ b/mongoose.c @@ -470,8 +470,8 @@ size_t mg_http_next_multipart(struct mg_str body, size_t ofs, 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) + 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; @@ -1224,9 +1224,12 @@ 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)); + const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; + int q = p < x && *p == '"' ? 1 : 0; + while (p < x && (q ? p == b || *p != '"' : *p != ';' && *p != ' ')) p++; + // LOG(LL_INFO, ("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, + // v.ptr, (int) (p - b), b)); + return stripquotes(mg_str_n(b, p - b + q)); } } return mg_str_n(NULL, 0); diff --git a/src/http.c b/src/http.c index 13f072fd..2a3f546c 100644 --- a/src/http.c +++ b/src/http.c @@ -51,8 +51,8 @@ size_t mg_http_next_multipart(struct mg_str body, size_t ofs, 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) + 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; @@ -805,9 +805,12 @@ 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)); + const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; + int q = p < x && *p == '"' ? 1 : 0; + while (p < x && (q ? p == b || *p != '"' : *p != ';' && *p != ' ')) p++; + // LOG(LL_INFO, ("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, + // v.ptr, (int) (p - b), b)); + return stripquotes(mg_str_n(b, p - b + q)); } } return mg_str_n(NULL, 0); diff --git a/test/unit_test.c b/test/unit_test.c index 4974ebb9..63ea4261 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1265,9 +1265,9 @@ static void test_multipart(void) { "--xyz\r\n" "Content-Disposition: form-data; name=\"val\"\r\n" "\r\n" - "abcdef\r\n" + "abc\r\ndef\r\n" "--xyz\r\n" - "Content-Disposition: form-data; name=\"foo\"; filename=\"a.txt\"\r\n" + "Content-Disposition: form-data; name=\"foo\"; filename=\"a b.txt\"\r\n" "Content-Type: text/plain\r\n" "\r\n" "hello world\r\n" @@ -1276,12 +1276,14 @@ static void test_multipart(void) { 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); + // LOG(LL_INFO, ("--> [%.*s]", (int) part.body.len, part.body.ptr)); + ASSERT(mg_strcmp(part.body, mg_str("abc\r\ndef")) == 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); + // LOG(LL_INFO, ("--> [%.*s]", (int) part.filename.len, part.filename.ptr)); + ASSERT(mg_strcmp(part.filename, mg_str("a b.txt")) == 0); + ASSERT(mg_strcmp(part.body, mg_str("hello world\r\n")) == 0); ASSERT(mg_http_next_multipart(mg_str(s), ofs, &part) == 0); }