From 99f0688377814b76847ff4fc621bb0aa28863075 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Thu, 30 Jun 2022 21:54:50 +0100 Subject: [PATCH] Change mg_http_upload API --- docs/README.md | 45 +++++++++++++++++ examples/file-upload-multiple-posts/main.c | 9 +++- .../web_root/app.js | 17 +++++-- .../web_root/index.html | 2 +- mongoose.c | 50 +++++++++++-------- mongoose.h | 4 +- src/http.c | 46 +++++++++-------- src/http.h | 4 +- src/str.c | 4 +- test/unit_test.c | 10 +++- 10 files changed, 135 insertions(+), 56 deletions(-) diff --git a/docs/README.md b/docs/README.md index e967020d..72fdfcb4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1337,6 +1337,51 @@ A diagram below shows how `mg_http_next_multipart()` in action: Function mg_http_next_multipart() +### mg\_http\_upload() + +```c + +int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *path, size_t max_size); +``` + +This is a helper utility function that is used to upload large files by small +chunks. + +Append HTTP POST data to a file in a specified directory. A file name and +file offset are specified by the query string parameters: +`POST /upload?name=firmware.bin&offset=2048 HTTP/1.1`. If the offset is 0, then the +file is truncated. It is a client's responsibility to divide a file into a +smaller chunks and send a sequence of POST requests that will be handled by +this function. + +Parameters: +- `c`- a connection +- `hm` - a parsed HTTP message +- `fs` - a filesystem where to write a file, e.g. `&mg_fs_posix` +- `path` - a filename +- `max_size` - a maximum allowed file size + +Return value: file size after write, or negative number on error + +Usage example: + +```c +static void fn(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")) { + mg_http_upload(c, hm, &mg_fs_posix, "/tmp/myfile.bin", 99999); + } else { + struct mg_http_serve_opts opts = {.root_dir = "."}; // Serve + mg_http_serve_dir(c, ev_data, &opts); // static content + } + } +} + +``` + + ## Websocket ### struct mg\_ws\_message diff --git a/examples/file-upload-multiple-posts/main.c b/examples/file-upload-multiple-posts/main.c index 4389e851..8dc36338 100644 --- a/examples/file-upload-multiple-posts/main.c +++ b/examples/file-upload-multiple-posts/main.c @@ -10,7 +10,14 @@ 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")) { - mg_http_upload(c, hm, &mg_fs_posix, "/tmp"); + char path[80], name[64]; + mg_http_get_var(&hm->query, "name", name, sizeof(name)); + if (name[0] == '\0') { + mg_http_reply(c, 400, "", "%s", "name required"); + } else { + mg_snprintf(path, sizeof(path), "/tmp/%s", name); + mg_http_upload(c, hm, &mg_fs_posix, mg_remove_double_dots(path), 99999); + } } else { struct mg_http_serve_opts opts = {.root_dir = "web_root"}; mg_http_serve_dir(c, ev_data, &opts); diff --git a/examples/file-upload-multiple-posts/web_root/app.js b/examples/file-upload-multiple-posts/web_root/app.js index f90c01d6..e62f1d89 100644 --- a/examples/file-upload-multiple-posts/web_root/app.js +++ b/examples/file-upload-multiple-posts/web_root/app.js @@ -18,12 +18,19 @@ var sendFileData = function(name, data, chunkSize) { var chunk = data.subarray(offset, offset + chunkSize) || ''; var opts = {method: 'POST', body: chunk}; var url = '/upload?offset=' + offset + '&name=' + encodeURIComponent(name); + var ok; setStatus( - 'sending bytes ' + offset + '..' + (offset + chunk.length) + ' of ' + - data.length); - fetch(url, opts).then(function(res) { - if (chunk.length > 0) sendChunk(offset + chunk.length); - }); + 'Upoading ' + name + ', bytes ' + offset + '..' + + (offset + chunk.length) + ' of ' + data.length); + fetch(url, opts) + .then(function(res) { + if (res.ok && chunk.length > 0) sendChunk(offset + chunk.length); + ok = res.ok; + return res.text(); + }) + .then(function(text) { + if (!ok) setStatus('Error: ' + text); + }); }; sendChunk(0); }; diff --git a/examples/file-upload-multiple-posts/web_root/index.html b/examples/file-upload-multiple-posts/web_root/index.html index 3d55a26e..595d0b1d 100644 --- a/examples/file-upload-multiple-posts/web_root/index.html +++ b/examples/file-upload-multiple-posts/web_root/index.html @@ -21,7 +21,7 @@ in small chunks.

In this example, the JavaScript code on this page sends the chosen - file in 4K chunks using the /upload endpoint. + file in 2Kb chunks using the /upload endpoint. The uploaded file is stored in the /tmp directory by the helper API function mg_http_upload() diff --git a/mongoose.c b/mongoose.c index 56767eec..c032b670 100644 --- a/mongoose.c +++ b/mongoose.c @@ -2253,31 +2253,37 @@ void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) { if (c->pfn_data != NULL) c->pfn_data = (char *) c->pfn_data - ch.len; } -int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, const char *dir) { - char offset[40] = "", name[200] = "", path[256]; - int res = 0; - mg_http_get_var(&hm->query, "offset", offset, sizeof(offset)); - mg_http_get_var(&hm->query, "name", name, sizeof(name)); - if (name[0] == '\0') { - mg_http_reply(c, 400, "", "%s", "name required"); - res = -1; - } else if (hm->body.len == 0) { - mg_http_reply(c, 200, "", "%d", res); // Nothing to write +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *path, size_t max_size) { + char buf[20] = ""; + long res = 0, offset; + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); + offset = strtol(buf, NULL, 0); + if (hm->body.len == 0) { + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write } else { struct mg_fd *fd; - long oft = strtol(offset, NULL, 0); - mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name); - mg_remove_double_dots(path); - MG_DEBUG(("%d bytes @ %ld [%s]", (int) hm->body.len, oft, path)); - if (oft == 0) fs->rm(path); - if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { - mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + size_t current_size = 0; + MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset)); + if (offset == 0) fs->rm(path); // If offset if 0, truncate file + fs->st(path, ¤t_size, NULL); + if (offset < 0) { + mg_http_reply(c, 400, "", "offset required"); + res = -1; + } else if (offset > 0 && current_size != (size_t) offset) { + mg_http_reply(c, 400, "", "%s: offset mismatch", path); res = -2; + } else if ((size_t) offset + hm->body.len > max_size) { + mg_http_reply(c, 400, "", "%s: over max size of %lu", path, + (unsigned long) max_size); + res = -3; + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { + mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + res = -4; } else { - res = (int) fs->wr(fd->fd, hm->body.ptr, hm->body.len); + res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len); mg_fs_close(fd); - mg_http_reply(c, 200, "", "%d", res); + mg_http_reply(c, 200, "", "%ld", res); } } return res; @@ -4795,7 +4801,7 @@ int64_t mg_to64(struct mg_str str) { } char *mg_remove_double_dots(char *s) { - char *p = s; + char *saved = s, *p = s; while (*s != '\0') { *p++ = *s++; if (s[-1] == '/' || s[-1] == '\\') { @@ -4812,7 +4818,7 @@ char *mg_remove_double_dots(char *s) { } } *p = '\0'; - return s; + return saved; } #ifdef MG_ENABLE_LINES diff --git a/mongoose.h b/mongoose.h index 12d23fd4..a041b2a9 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1107,8 +1107,8 @@ int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); 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, - struct mg_fs *fs, const char *dir); +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *path, size_t max_size); void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); 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/src/http.c b/src/http.c index 17f6f802..28ab3da1 100644 --- a/src/http.c +++ b/src/http.c @@ -838,31 +838,37 @@ void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) { if (c->pfn_data != NULL) c->pfn_data = (char *) c->pfn_data - ch.len; } -int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, const char *dir) { - char offset[40] = "", name[200] = "", path[256]; - int res = 0; - mg_http_get_var(&hm->query, "offset", offset, sizeof(offset)); - mg_http_get_var(&hm->query, "name", name, sizeof(name)); - if (name[0] == '\0') { - mg_http_reply(c, 400, "", "%s", "name required"); - res = -1; - } else if (hm->body.len == 0) { - mg_http_reply(c, 200, "", "%d", res); // Nothing to write +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *path, size_t max_size) { + char buf[20] = ""; + long res = 0, offset; + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); + offset = strtol(buf, NULL, 0); + if (hm->body.len == 0) { + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write } else { struct mg_fd *fd; - long oft = strtol(offset, NULL, 0); - mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name); - mg_remove_double_dots(path); - MG_DEBUG(("%d bytes @ %ld [%s]", (int) hm->body.len, oft, path)); - if (oft == 0) fs->rm(path); - if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { - mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + size_t current_size = 0; + MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset)); + if (offset == 0) fs->rm(path); // If offset if 0, truncate file + fs->st(path, ¤t_size, NULL); + if (offset < 0) { + mg_http_reply(c, 400, "", "offset required"); + res = -1; + } else if (offset > 0 && current_size != (size_t) offset) { + mg_http_reply(c, 400, "", "%s: offset mismatch", path); res = -2; + } else if ((size_t) offset + hm->body.len > max_size) { + mg_http_reply(c, 400, "", "%s: over max size of %lu", path, + (unsigned long) max_size); + res = -3; + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { + mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + res = -4; } else { - res = (int) fs->wr(fd->fd, hm->body.ptr, hm->body.len); + res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len); mg_fs_close(fd); - mg_http_reply(c, 200, "", "%d", res); + mg_http_reply(c, 200, "", "%ld", res); } } return res; diff --git a/src/http.h b/src/http.h index ab22c4e1..61acc4be 100644 --- a/src/http.h +++ b/src/http.h @@ -59,8 +59,8 @@ int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); 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, - struct mg_fs *fs, const char *dir); +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *path, size_t max_size); void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); 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/src/str.c b/src/str.c index 80bcec2f..e3990676 100644 --- a/src/str.c +++ b/src/str.c @@ -223,7 +223,7 @@ int64_t mg_to64(struct mg_str str) { } char *mg_remove_double_dots(char *s) { - char *p = s; + char *saved = s, *p = s; while (*s != '\0') { *p++ = *s++; if (s[-1] == '/' || s[-1] == '\\') { @@ -240,5 +240,5 @@ char *mg_remove_double_dots(char *s) { } } *p = '\0'; - return s; + return saved; } diff --git a/test/unit_test.c b/test/unit_test.c index 9a4b2b03..6f5c8cee 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -465,7 +465,15 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass)); mg_http_reply(c, 200, "", "[%s]:[%s]", user, pass); } else if (mg_http_match_uri(hm, "/upload")) { - mg_http_upload(c, hm, &mg_fs_posix, "."); + char path[80], name[64]; + mg_http_get_var(&hm->query, "name", name, sizeof(name)); + if (name[0] == '\0') { + mg_http_reply(c, 400, "", "%s", "name required"); + } else { + mg_snprintf(path, sizeof(path), "./%s", name); + mg_http_upload(c, hm, &mg_fs_posix, mg_remove_double_dots(path), 99999); + c->is_hexdumping = 1; + } } else if (mg_http_match_uri(hm, "/test/")) { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts));