From 8a335a8ceae01a5ea14c1032f2923af5ea0c0cb4 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Wed, 10 Apr 2024 12:20:33 -0300 Subject: [PATCH] update to MG_EV_HTTP_HDRS --- examples/file-transfer/server.c | 146 +++++++++++++++++--------------- 1 file changed, 77 insertions(+), 69 deletions(-) diff --git a/examples/file-transfer/server.c b/examples/file-transfer/server.c index 69d625bc..28dc91af 100644 --- a/examples/file-transfer/server.c +++ b/examples/file-transfer/server.c @@ -25,79 +25,89 @@ static bool authuser(struct mg_http_message *hm) { return false; } -// Streaming upload example. Demonstrates how to use MG_EV_READ events -// to get large payload in smaller chunks. To test, use curl utility: -static void cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { - // Parse the incoming data ourselves. If we can parse the request, - // store two size_t variables in c->data: expected len and recv len. - size_t *data = (size_t *) c->data; - struct mg_fd *fd = (struct mg_fd *) c->fn_data; // get file descriptor - if (data[0]) { // Already parsed, receiving body - data[1] += c->recv.len; - MG_DEBUG(("Got chunk len %lu, %lu total", c->recv.len, data[1])); - fd->fs->wr(fd->fd, c->recv.buf, c->recv.len); - c->recv.len = 0; // And cleanup the receive buffer. Streaming! - if (data[1] >= data[0]) { - mg_fs_close(fd); - mg_http_reply(c, 200, "", "ok\n"); - } - } else if (c->is_resp == 0) { - struct mg_http_message hm; - int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); - if (n < 0) mg_error(c, "Bad response"); - if (n > 0) { - if (mg_http_match_uri(&hm, "/upload/#")) { - if (!authuser(&hm)) { - mg_http_reply(c, 403, "", "Denied\n"); - c->is_draining = 1; // Tell mongoose to close this connection - } else if (hm.body.len > (size_t) s_max_size) { - mg_http_reply(c, 400, "", "Too long\n"); - c->is_draining = 1; // Tell mongoose to close this connection - } else if (hm.uri.len == 8) { // 8: /upload/ - mg_http_reply(c, 400, "", "Name required\n"); - c->is_draining = 1; // Tell mongoose to close this connection - } else if (strlen(s_upld_dir) + (hm.uri.len - 8) + 2 > - MG_PATH_MAX) { // 2: MG_DIRSEP + NUL - mg_http_reply(c, 400, "", "Path is too long\n"); +struct upload_state { + size_t expected; // POST data length, bytes + size_t received; // Already received bytes + void *fp; // Opened uploaded file +}; + +static void handle_uploads(struct mg_connection *c, int ev, void *ev_data) { + struct upload_state *us = (struct upload_state *) c->data; + struct mg_fs *fs = &mg_fs_posix; + + // Catch /upload requests early, without buffering whole body + // When we receive MG_EV_HTTP_HDRS event, that means we've received all + // HTTP headers but not necessarily full HTTP body + if (ev == MG_EV_HTTP_HDRS) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_http_match_uri(hm, "/upload/#")) { + c->pfn = NULL; // Silence HTTP protocol handler, we'll take over + if (!authuser(hm)) { + mg_http_reply(c, 403, "", "Denied\n"); + c->is_draining = 1; // Tell mongoose to close this connection + } else if (hm->body.len > (size_t) s_max_size) { + mg_http_reply(c, 400, "", "Too long\n"); + c->is_draining = 1; // Tell mongoose to close this connection + } else if (hm->uri.len == 8) { // 8: /upload/ + mg_http_reply(c, 400, "", "Name required\n"); + c->is_draining = 1; // Tell mongoose to close this connection + } else if (strlen(s_upld_dir) + (hm->uri.len - 8) + 2 > + MG_PATH_MAX) { // 2: MG_DIRSEP + NUL + mg_http_reply(c, 400, "", "Path is too long\n"); + c->is_draining = 1; // Tell mongoose to close this connection + } else { + char fpath[MG_PATH_MAX]; + snprintf(fpath, MG_PATH_MAX, "%s%c", s_upld_dir, MG_DIRSEP); + strncat(fpath, hm->uri.ptr + 8, hm->uri.len - 8); + if (!mg_path_is_sane(fpath)) { + mg_http_reply(c, 400, "", "Invalid path\n"); + c->is_draining = 1; // Tell mongoose to close this connection + } else { + struct mg_fd *fd; + MG_DEBUG(("Got request")); + fs->rm(fpath); // Delete file if it exists + if ((fd = fs->op(fpath, MG_FS_WRITE)) == NULL) { + mg_http_reply(c, 400, "", "open failed: %d\n", errno); c->is_draining = 1; // Tell mongoose to close this connection } else { - char fpath[MG_PATH_MAX]; - snprintf(fpath, MG_PATH_MAX, "%s%c", s_upld_dir, MG_DIRSEP); - strncat(fpath, hm.uri.ptr + 8, hm.uri.len - 8); - if (!mg_path_is_sane(fpath)) { - mg_http_reply(c, 400, "", "Invalid path\n"); - c->is_draining = 1; // Tell mongoose to close this connection - } else { - MG_DEBUG(("Got request, chunk len %lu", c->recv.len - n)); - if ((fd = mg_fs_open(&mg_fs_posix, fpath, MG_FS_WRITE)) == NULL) { - mg_http_reply(c, 400, "", "open failed: %d\n", errno); - c->is_draining = 1; // Tell mongoose to close this connection - } else { - c->fn_data = fd; - c->recv.len -= n; // remove headers - data[0] = hm.body.len; - data[1] = c->recv.len; - if (c->recv.len) - fd->fs->wr(fd->fd, c->recv.buf + n, c->recv.len); - c->recv.len = 0; // consume data - if (data[1] >= data[0]) { - mg_fs_close(fd); - mg_http_reply(c, 200, "", "ok\n"); - } - } - } + us->fp = fd; + us->expected = hm->body.len; // Store number of bytes we expect + mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers } - c->is_resp = 1; // ignore the rest of the body - } else { - struct mg_http_serve_opts opts = {0}; - opts.root_dir = s_root_dir; - mg_http_serve_dir(c, &hm, &opts); } } } } - (void) ev_data; + + // Catch uploaded file data for both MG_EV_READ and MG_EV_HTTP_HDRS + if (us->expected > 0 && c->recv.len > 0) { + us->received += c->recv.len; + MG_DEBUG(("Got chunk: %lu bytes, %lu so far, %lu total", c->recv.len, + us->received, us->expected)); + if (us->fp) fs->wr(us->fp, c->recv.buf, c->recv.len); // Write to file + c->recv.len = 0; // Delete received data + if (us->received >= us->expected) { + // Uploaded everything. Send response back + MG_INFO(("Uploaded %lu bytes", us->received)); + mg_http_reply(c, 200, NULL, "%lu ok\n", us->received); + if (us->fp) fs->cl(us->fp); // Close file + memset(us, 0, sizeof(*us)); // Cleanup upload state + c->is_draining = 1; // Close connection when response gets sent + } + } +} + +static void cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ || ev == MG_EV_HTTP_HDRS) { + handle_uploads(c, ev, ev_data); + } else if (ev == MG_EV_HTTP_MSG && c->pfn != NULL) { + // Non-upload requests, we serve normally + // NOTE: handle_uploads() may delete request and reset c->pfn + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + struct mg_http_serve_opts opts = {0}; + opts.root_dir = s_root_dir; + mg_http_serve_dir(c, hm, &opts); + } } static void usage(const char *prog) { @@ -159,9 +169,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, signal_handler); mg_log_set(s_debug_level); mg_mgr_init(&mgr); - // use mg_listen instead of mg_http_listen to be able to override the parser - // and shape buffering - if (mg_listen(&mgr, s_listening_address, cb, NULL) == NULL) { + if (mg_http_listen(&mgr, s_listening_address, cb, NULL) == NULL) { MG_ERROR(("Cannot listen on %s.", s_listening_address)); exit(EXIT_FAILURE); }