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:
+### 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));