From 621f893bbc790f4fdc92ed71557e5a38f553f7aa Mon Sep 17 00:00:00 2001 From: cpq Date: Thu, 28 May 2026 15:01:43 +0100 Subject: [PATCH] Unify upload API --- mongoose.c | 301 ++++++++++++++++++++--------------------------- mongoose.h | 6 + src/dash.c | 115 ++++-------------- src/http.c | 85 +++++++++++++ src/http.h | 6 + src/ota.c | 103 ++++------------ test/unit_test.c | 53 +++++++++ 7 files changed, 326 insertions(+), 343 deletions(-) diff --git a/mongoose.c b/mongoose.c index f8a732f2..e9108354 100644 --- a/mongoose.c +++ b/mongoose.c @@ -123,6 +123,7 @@ fail: + #define MG_NO_CACHE_HEADERS "Cache-Control: no-cache\r\n" #define MG_JSON_HEADERS "Content-Type: application/json\r\n" MG_NO_CACHE_HEADERS @@ -134,15 +135,6 @@ struct mg_dash_user { uint64_t expire; // Expiration timestamp }; -struct mg_upload_state { - char marker; // Tells that we're a file upload connection - size_t expected; // POST data length, bytes - size_t received; // Already received bytes - void *ctx; // OTA context -}; - -#define CONN_OTA 'O' -#define CONN_UPLOAD 'F' #define CONN_HANDLED 'Z' static struct mg_str trimq(struct mg_str s) { // Trim double quotes @@ -430,82 +422,24 @@ static struct mg_str mg_dash_file_name(struct mg_http_message *hm) { return name; } -static void upload_ev_handler(struct mg_connection *c, int ev, void *ev_data) { - // struct mg_dash *dash = (struct mg_dash *) c->fn_data; - struct mg_upload_state *us = (struct mg_upload_state *) c->data; - if (ev == MG_EV_HTTP_HDRS) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - struct mg_str name = mg_dash_file_name(hm); - if (sizeof(*us) > sizeof(c->data)) { - MG_ERROR( - ("FAIL: sizeof(c->data) == %lu, need %lu." - " Set #define MG_DATA_SIZE XXX", - sizeof(c->data), sizeof(*us))); - mg_http_reply(c, 500, NULL, ":(\n"); - c->is_draining = 1; - } else if (mg_path_is_sane(name) == false) { - // Bad file name, return error. Protect from traversal, etc - mg_http_reply(c, 400, NULL, "Bad name"); - c->is_draining = 1; - } else { - bool ok = false; - if (us->marker == CONN_UPLOAD) { - char path[128]; - mg_snprintf(path, sizeof(path), "/tmp/%.*s", (int) name.len, name.buf); - us->ctx = fopen(path, "wb+"); - if (us->ctx == NULL) MG_ERROR(("open(%s): %d", path, errno)); - if (us->ctx != NULL) ok = true; - } else { - ok = mg_ota_begin(hm->body.len); - } - if (ok == false) { - mg_http_reply(c, 500, NULL, "Upload failed to start\n"); - c->is_draining = 1; - } else { - if (us->marker == CONN_UPLOAD) mg_dash_file_add(name, hm->body.len); - us->expected = hm->body.len; - us->received = 0; - mg_iobuf_del(&c->recv, 0, hm->head.len); - mg_call(c, MG_EV_READ, &c->recv.len); - } - } - } else if (ev == MG_EV_READ && us->expected > 0 && c->recv.len > 0) { - // Write uploaded data +static void mg_dash_ota_cb(struct mg_connection *c, const char *errmsg) { + mg_http_reply(c, errmsg ? 500 : 200, NULL, errmsg ? errmsg : "ok\n"); + c->is_draining = 1; +} - size_t alignment = 512; // Maximum flash write granularity (iMXRT, Pico) - size_t left = us->expected > us->received ? us->expected - us->received : 0; - size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment) - : c->recv.len; - bool ok = true, is_ota = us->marker == CONN_OTA; - if (aligned > 0 && !is_ota) { - ok = (fwrite(c->recv.buf, 1, aligned, (FILE *) us->ctx) == aligned); - } else if (aligned > 0 && is_ota) { - ok = mg_ota_write(c->recv.buf, aligned); - } - - us->received += aligned; - // MG_DEBUG(("%lu chunk: %lu/%lu, %lu/%lu, ok: %d", c->id, aligned, - // c->recv.len, us->received, us->expected, ok)); - mg_iobuf_del(&c->recv, 0, aligned); // Delete received data - - if (ok == false) { - // Some sort of failure - send error response and cleanup - mg_http_reply(c, 400, "", "Upload error\n"); - c->is_draining = 1; // Close connection when response it sent - if (is_ota) mg_ota_end(); - if (!is_ota && us->ctx != NULL) (void) fclose((FILE *) us->ctx); - memset(us, 0, sizeof(*us)); - } else if (us->received >= us->expected) { - // Uploaded everything. Send success response and cleanup - mg_http_reply(c, 200, NULL, "%lu ok\n", us->received); - c->is_draining = 1; // Close connection when response it sent - MG_INFO(("%lu done, %lu bytes", c->id, us->received)); - if (is_ota) mg_ota_end(); - if (!is_ota && us->ctx != NULL) (void) fclose((FILE *) us->ctx); - if (!is_ota) mg_dash_send_change(c->mgr, &set_files); - memset(us, 0, sizeof(*us)); - } +static void mg_dash_upload_cb(struct mg_connection *c, const char *errmsg) { + if (errmsg) { + mg_http_reply(c, 500, NULL, "%s\n", errmsg); + } else { + char path[128]; + size_t size = 0; + mg_snprintf(path, sizeof(path), "/tmp/%s", c->data); + mg_fs_posix.st(path, &size, NULL); + mg_dash_file_add(mg_str(c->data), size); + mg_http_reply(c, 200, NULL, "ok\n"); + mg_dash_send_change(c->mgr, &set_files); } + c->is_draining = 1; } static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) { @@ -618,19 +552,14 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { mg_handle_login(c, u); c->data[0] = CONN_HANDLED; } else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) { - // Start OTA - in a separate handler function - c->data[0] = CONN_OTA; - c->fn = upload_ev_handler; - c->pfn = NULL; - mg_call(c, ev, ev_data); + mg_http_start_ota(c, hm, mg_dash_ota_cb); } else if (mg_match(hm->uri, mg_str("/fs/#"), NULL) && (mg_strcasecmp(hm->method, mg_str("POST")) == 0 || mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) { - // Start file upload - in a separate function - c->data[0] = CONN_UPLOAD; - c->fn = upload_ev_handler; - c->pfn = NULL; - mg_call(c, ev, ev_data); + struct mg_str name = mg_dash_file_name(hm); + mg_snprintf(c->data, sizeof(c->data), "%.*s", (int) name.len, name.buf); + mg_http_start_upload(c, hm, name, mg_str("/tmp"), &mg_fs_posix, + mg_dash_upload_cb); } if (c->data[0] != '\0') mg_log_http_req(c, hm); } else if (ev == MG_EV_HTTP_MSG && c->data[0] != '\0') { @@ -2439,6 +2368,7 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, + static int mg_ncasecmp(const char *s1, const char *s2, size_t len) { int diff = 0; if (len > 0) do { @@ -3425,6 +3355,90 @@ int mg_http_status(const struct mg_http_message *hm) { return atoi(hm->uri.buf); } +struct mg_upload_priv { + size_t expected; + size_t received; + struct mg_fd *fd; // non-NULL: file upload; NULL: OTA + void (*fn)(struct mg_connection *, const char *); +}; + +static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + if (p->fn == NULL) return; + if (ev == MG_EV_READ && c->recv.len > 0) { + size_t alignment = 512; + size_t left = p->expected > p->received ? p->expected - p->received : 0; + size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment) + : c->recv.len; + bool ok = true; + if (aligned > 0) { + if (p->fd != NULL) { + ok = p->fd->fs->wr(p->fd->fd, c->recv.buf, aligned) == aligned; + } else { + ok = mg_ota_write(c->recv.buf, aligned); + } + } + p->received += aligned; + mg_iobuf_del(&c->recv, 0, aligned); + if (!ok) { + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else mg_ota_end(); + p->fn(c, "write error"); + p->fn = NULL; + } else if (p->received >= p->expected) { + const char *errmsg = NULL; + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else if (!mg_ota_end()) errmsg = "OTA finalize failed"; + p->fn(c, errmsg); + p->fn = NULL; + } + } else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) { + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else mg_ota_end(); + p->fn(c, ev == MG_EV_ERROR ? (const char *) ev_data : "connection closed"); + p->fn = NULL; + } + (void) ev_data; +} + +void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_str name, struct mg_str dir, + struct mg_fs *fs, + void (*fn)(struct mg_connection *, const char *)) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + char path[MG_PATH_MAX]; + struct mg_fd *fd; + if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; } + if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; } + mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf, + MG_DIRSEP, (int) name.len, name.buf); + fd = mg_fs_open(fs, path, MG_FS_WRITE); + if (fd == NULL) { fn(c, "open failed"); return; } + p->expected = hm->body.len; + p->received = 0; + p->fd = fd; + p->fn = fn; + c->fn = mg_upload_handler; + c->pfn = NULL; + mg_iobuf_del(&c->recv, 0, hm->head.len); + mg_call(c, MG_EV_READ, &c->recv.len); +} + +void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm, + void (*fn)(struct mg_connection *, const char *)) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; } + if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; } + p->expected = hm->body.len; + p->received = 0; + p->fd = NULL; + p->fn = fn; + c->fn = mg_upload_handler; + c->pfn = NULL; + mg_iobuf_del(&c->recv, 0, hm->head.len); + mg_call(c, MG_EV_READ, &c->recv.len); +} + static bool is_hex_digit(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); @@ -9018,7 +9032,9 @@ void mg_tcpip_mapip(struct mg_connection *c, struct mg_addr *ip) { -#if MG_OTA != MG_OTA_NONE + + + enum { MG_OTA_STATUS_WAITING, MG_OTA_STATUS_SUCCESS, MG_OTA_STATUS_FAIL }; static int s_version_status; @@ -9087,95 +9103,40 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) { } } +static void s_ota_done(struct mg_connection *c, const char *errmsg) { + (void) c; + s_ota_status = errmsg ? MG_OTA_STATUS_FAIL : MG_OTA_STATUS_SUCCESS; + if (errmsg) MG_ERROR(("OTA failed: %s", errmsg)); +} + static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) { struct mg_str host = mg_url_host(s_ota_metadata.url); - struct mg_http_message hm; - static size_t ofs = 0; - static bool ota_begun = false; - size_t alignment = 512, drop, len; - int n, status; - char *buf; - (void) ev_data; - if (ev == MG_EV_POLL) { - if (s_start_time + 120 * 1000 < mg_millis()) { - mg_error(c, "Connection timeout"); - } + if (s_start_time + 120 * 1000 < mg_millis()) mg_error(c, "Timeout"); } else if (ev == MG_EV_CONNECT) { mg_printf(c, "GET %s HTTP/1.1\r\n" "Host: %.*s\r\n" "Connection: close\r\n\r\n", mg_url_uri(s_ota_metadata.url), (int) host.len, host.buf); - ofs = 0; - ota_begun = false; - } else if (ev == MG_EV_READ) { - if (s_ota_status == MG_OTA_STATUS_FAIL) return; - if (!ota_begun) { // First chunk, parse headers, OTA begin - n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); - if (n < 0) { - mg_error(c, "Bad HTTP response"); - return; - } - if (n == 0) return; - status = mg_http_status(&hm); - if (status != 200 || hm.body.len != s_ota_metadata.size) { - mg_error(c, "Bad HTTP response (%d), body length: %lu", status, - hm.body.len); - return; - } - MG_DEBUG(("Beginning OTA (%ld bytes)", s_ota_metadata.size)); - if (!mg_ota_begin(s_ota_metadata.size)) { - s_ota_status = MG_OTA_STATUS_FAIL; - mg_error(c, "mg_ota_begin(%lu) failed", - (unsigned long) s_ota_metadata.size); - return; - } - ota_begun = true; - len = c->recv.len - (size_t) n; - buf = (char *) c->recv.buf + n; - drop = (size_t) n; - } else { // Header parsed, incoming chunks - len = c->recv.len; - buf = (char *) c->recv.buf; - drop = 0; - } - // OTA write - size_t aligned = (ofs + len < s_ota_metadata.size) - ? aligned = MG_ROUND_DOWN(len, alignment) - : len; - if (aligned == 0) return; - if (!mg_ota_write(buf, aligned)) { - mg_error(c, "mg_ota_write(%lu) @%lu failed", (unsigned long) aligned, - (unsigned long) ofs); + } else if (ev == MG_EV_HTTP_HDRS) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + int status = mg_http_status(hm); + if (status != 200 || hm->body.len != s_ota_metadata.size) { + mg_error(c, "Bad HTTP response: status %d, size %lu vs %lu", status, + (unsigned long) hm->body.len, + (unsigned long) s_ota_metadata.size); + s_ota_status = MG_OTA_STATUS_FAIL; return; } - ofs += aligned; - mg_iobuf_del(&c->recv, 0, aligned + drop); - MG_DEBUG(("Wrote %lu/%lu bytes", (unsigned long) ofs, - (unsigned long) s_ota_metadata.size)); + MG_DEBUG( + ("Beginning OTA (%lu bytes)", (unsigned long) s_ota_metadata.size)); + mg_http_start_ota(c, hm, s_ota_done); } else if (ev == MG_EV_ERROR) { MG_ERROR(("%lu Connection error", c->id)); s_ota_status = MG_OTA_STATUS_FAIL; - } else if (ev == MG_EV_CLOSE) { // OTA end - MG_DEBUG(("Connection closing, downloaded %ld/%ld bytes", ofs, - s_ota_metadata.size)); - if (s_ota_status != MG_OTA_STATUS_FAIL && ota_begun) { - if (ofs != s_ota_metadata.size) { - s_ota_status = MG_OTA_STATUS_FAIL; - MG_ERROR(("Firmware size mismatch: got %lu expected %lu", - (unsigned long) ofs, (unsigned long) s_ota_metadata.size)); - } else if (!mg_ota_end()) { - s_ota_status = MG_OTA_STATUS_FAIL; - MG_ERROR(("mg_ota_end() failed")); - } else { - MG_DEBUG(("mg_ota_end() successful")); - s_ota_status = MG_OTA_STATUS_SUCCESS; - } - } - ota_begun = false; - ofs = 0; } + (void) ev_data; } void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, @@ -9205,7 +9166,7 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, if (fn) fn("Pulling firmware"); s_start_time = mg_millis(); MG_DEBUG(("Connecting to %s to download firmware", s_ota_metadata.url)); - if (!mg_connect(mgr, s_ota_metadata.url, s_firmware_fn, NULL)) { + if (!mg_http_connect(mgr, s_ota_metadata.url, s_firmware_fn, NULL)) { if (fn) fn("Failed to connect"); free_ota_metadata(); return; @@ -9215,8 +9176,6 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, free_ota_metadata(); } -#endif - #ifdef MG_ENABLE_LINES #line 1 "src/ota_ch32v307.c" #endif diff --git a/mongoose.h b/mongoose.h index 3b804e89..ffdd3fb2 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1861,6 +1861,12 @@ 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); long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, struct mg_fs *fs, const char *dir, size_t max_size); +void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_str name, struct mg_str dir, + struct mg_fs *fs, + void (*fn)(struct mg_connection *, const char *)); +void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm, + void (*fn)(struct mg_connection *, const char *)); 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/dash.c b/src/dash.c index cbcb3594..a5c2bab7 100644 --- a/src/dash.c +++ b/src/dash.c @@ -1,4 +1,5 @@ #include "dash.h" +#include "http.h" #include "ota.h" #include "util.h" @@ -13,15 +14,6 @@ struct mg_dash_user { uint64_t expire; // Expiration timestamp }; -struct mg_upload_state { - char marker; // Tells that we're a file upload connection - size_t expected; // POST data length, bytes - size_t received; // Already received bytes - void *ctx; // OTA context -}; - -#define CONN_OTA 'O' -#define CONN_UPLOAD 'F' #define CONN_HANDLED 'Z' static struct mg_str trimq(struct mg_str s) { // Trim double quotes @@ -309,82 +301,24 @@ static struct mg_str mg_dash_file_name(struct mg_http_message *hm) { return name; } -static void upload_ev_handler(struct mg_connection *c, int ev, void *ev_data) { - // struct mg_dash *dash = (struct mg_dash *) c->fn_data; - struct mg_upload_state *us = (struct mg_upload_state *) c->data; - if (ev == MG_EV_HTTP_HDRS) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - struct mg_str name = mg_dash_file_name(hm); - if (sizeof(*us) > sizeof(c->data)) { - MG_ERROR( - ("FAIL: sizeof(c->data) == %lu, need %lu." - " Set #define MG_DATA_SIZE XXX", - sizeof(c->data), sizeof(*us))); - mg_http_reply(c, 500, NULL, ":(\n"); - c->is_draining = 1; - } else if (mg_path_is_sane(name) == false) { - // Bad file name, return error. Protect from traversal, etc - mg_http_reply(c, 400, NULL, "Bad name"); - c->is_draining = 1; - } else { - bool ok = false; - if (us->marker == CONN_UPLOAD) { - char path[128]; - mg_snprintf(path, sizeof(path), "/tmp/%.*s", (int) name.len, name.buf); - us->ctx = fopen(path, "wb+"); - if (us->ctx == NULL) MG_ERROR(("open(%s): %d", path, errno)); - if (us->ctx != NULL) ok = true; - } else { - ok = mg_ota_begin(hm->body.len); - } - if (ok == false) { - mg_http_reply(c, 500, NULL, "Upload failed to start\n"); - c->is_draining = 1; - } else { - if (us->marker == CONN_UPLOAD) mg_dash_file_add(name, hm->body.len); - us->expected = hm->body.len; - us->received = 0; - mg_iobuf_del(&c->recv, 0, hm->head.len); - mg_call(c, MG_EV_READ, &c->recv.len); - } - } - } else if (ev == MG_EV_READ && us->expected > 0 && c->recv.len > 0) { - // Write uploaded data +static void mg_dash_ota_cb(struct mg_connection *c, const char *errmsg) { + mg_http_reply(c, errmsg ? 500 : 200, NULL, errmsg ? errmsg : "ok\n"); + c->is_draining = 1; +} - size_t alignment = 512; // Maximum flash write granularity (iMXRT, Pico) - size_t left = us->expected > us->received ? us->expected - us->received : 0; - size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment) - : c->recv.len; - bool ok = true, is_ota = us->marker == CONN_OTA; - if (aligned > 0 && !is_ota) { - ok = (fwrite(c->recv.buf, 1, aligned, (FILE *) us->ctx) == aligned); - } else if (aligned > 0 && is_ota) { - ok = mg_ota_write(c->recv.buf, aligned); - } - - us->received += aligned; - // MG_DEBUG(("%lu chunk: %lu/%lu, %lu/%lu, ok: %d", c->id, aligned, - // c->recv.len, us->received, us->expected, ok)); - mg_iobuf_del(&c->recv, 0, aligned); // Delete received data - - if (ok == false) { - // Some sort of failure - send error response and cleanup - mg_http_reply(c, 400, "", "Upload error\n"); - c->is_draining = 1; // Close connection when response it sent - if (is_ota) mg_ota_end(); - if (!is_ota && us->ctx != NULL) (void) fclose((FILE *) us->ctx); - memset(us, 0, sizeof(*us)); - } else if (us->received >= us->expected) { - // Uploaded everything. Send success response and cleanup - mg_http_reply(c, 200, NULL, "%lu ok\n", us->received); - c->is_draining = 1; // Close connection when response it sent - MG_INFO(("%lu done, %lu bytes", c->id, us->received)); - if (is_ota) mg_ota_end(); - if (!is_ota && us->ctx != NULL) (void) fclose((FILE *) us->ctx); - if (!is_ota) mg_dash_send_change(c->mgr, &set_files); - memset(us, 0, sizeof(*us)); - } +static void mg_dash_upload_cb(struct mg_connection *c, const char *errmsg) { + if (errmsg) { + mg_http_reply(c, 500, NULL, "%s\n", errmsg); + } else { + char path[128]; + size_t size = 0; + mg_snprintf(path, sizeof(path), "/tmp/%s", c->data); + mg_fs_posix.st(path, &size, NULL); + mg_dash_file_add(mg_str(c->data), size); + mg_http_reply(c, 200, NULL, "ok\n"); + mg_dash_send_change(c->mgr, &set_files); } + c->is_draining = 1; } static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) { @@ -497,19 +431,14 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { mg_handle_login(c, u); c->data[0] = CONN_HANDLED; } else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) { - // Start OTA - in a separate handler function - c->data[0] = CONN_OTA; - c->fn = upload_ev_handler; - c->pfn = NULL; - mg_call(c, ev, ev_data); + mg_http_start_ota(c, hm, mg_dash_ota_cb); } else if (mg_match(hm->uri, mg_str("/fs/#"), NULL) && (mg_strcasecmp(hm->method, mg_str("POST")) == 0 || mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) { - // Start file upload - in a separate function - c->data[0] = CONN_UPLOAD; - c->fn = upload_ev_handler; - c->pfn = NULL; - mg_call(c, ev, ev_data); + struct mg_str name = mg_dash_file_name(hm); + mg_snprintf(c->data, sizeof(c->data), "%.*s", (int) name.len, name.buf); + mg_http_start_upload(c, hm, name, mg_str("/tmp"), &mg_fs_posix, + mg_dash_upload_cb); } if (c->data[0] != '\0') mg_log_http_req(c, hm); } else if (ev == MG_EV_HTTP_MSG && c->data[0] != '\0') { diff --git a/src/http.c b/src/http.c index e2f6095b..ddacbe89 100644 --- a/src/http.c +++ b/src/http.c @@ -3,6 +3,7 @@ #include "fmt.h" #include "log.h" #include "net.h" +#include "ota.h" #include "printf.h" #include "ssi.h" #include "util.h" @@ -994,6 +995,90 @@ int mg_http_status(const struct mg_http_message *hm) { return atoi(hm->uri.buf); } +struct mg_upload_priv { + size_t expected; + size_t received; + struct mg_fd *fd; // non-NULL: file upload; NULL: OTA + void (*fn)(struct mg_connection *, const char *); +}; + +static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + if (p->fn == NULL) return; + if (ev == MG_EV_READ && c->recv.len > 0) { + size_t alignment = 512; + size_t left = p->expected > p->received ? p->expected - p->received : 0; + size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment) + : c->recv.len; + bool ok = true; + if (aligned > 0) { + if (p->fd != NULL) { + ok = p->fd->fs->wr(p->fd->fd, c->recv.buf, aligned) == aligned; + } else { + ok = mg_ota_write(c->recv.buf, aligned); + } + } + p->received += aligned; + mg_iobuf_del(&c->recv, 0, aligned); + if (!ok) { + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else mg_ota_end(); + p->fn(c, "write error"); + p->fn = NULL; + } else if (p->received >= p->expected) { + const char *errmsg = NULL; + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else if (!mg_ota_end()) errmsg = "OTA finalize failed"; + p->fn(c, errmsg); + p->fn = NULL; + } + } else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) { + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else mg_ota_end(); + p->fn(c, ev == MG_EV_ERROR ? (const char *) ev_data : "connection closed"); + p->fn = NULL; + } + (void) ev_data; +} + +void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_str name, struct mg_str dir, + struct mg_fs *fs, + void (*fn)(struct mg_connection *, const char *)) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + char path[MG_PATH_MAX]; + struct mg_fd *fd; + if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; } + if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; } + mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf, + MG_DIRSEP, (int) name.len, name.buf); + fd = mg_fs_open(fs, path, MG_FS_WRITE); + if (fd == NULL) { fn(c, "open failed"); return; } + p->expected = hm->body.len; + p->received = 0; + p->fd = fd; + p->fn = fn; + c->fn = mg_upload_handler; + c->pfn = NULL; + mg_iobuf_del(&c->recv, 0, hm->head.len); + mg_call(c, MG_EV_READ, &c->recv.len); +} + +void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm, + void (*fn)(struct mg_connection *, const char *)) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; } + if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; } + p->expected = hm->body.len; + p->received = 0; + p->fd = NULL; + p->fn = fn; + c->fn = mg_upload_handler; + c->pfn = NULL; + mg_iobuf_del(&c->recv, 0, hm->head.len); + mg_call(c, MG_EV_READ, &c->recv.len); +} + static bool is_hex_digit(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); diff --git a/src/http.h b/src/http.h index 34fdf957..ede85e7b 100644 --- a/src/http.h +++ b/src/http.h @@ -58,6 +58,12 @@ 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); long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, struct mg_fs *fs, const char *dir, size_t max_size); +void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_str name, struct mg_str dir, + struct mg_fs *fs, + void (*fn)(struct mg_connection *, const char *)); +void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm, + void (*fn)(struct mg_connection *, const char *)); 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/ota.c b/src/ota.c index 979eb53e..781d7a13 100644 --- a/src/ota.c +++ b/src/ota.c @@ -1,8 +1,10 @@ #include "ota.h" #include "http.h" +#include "json.h" #include "log.h" - -#if MG_OTA != MG_OTA_NONE +#include "timer.h" +#include "url.h" +#include "util.h" enum { MG_OTA_STATUS_WAITING, MG_OTA_STATUS_SUCCESS, MG_OTA_STATUS_FAIL }; static int s_version_status; @@ -71,95 +73,40 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) { } } +static void s_ota_done(struct mg_connection *c, const char *errmsg) { + (void) c; + s_ota_status = errmsg ? MG_OTA_STATUS_FAIL : MG_OTA_STATUS_SUCCESS; + if (errmsg) MG_ERROR(("OTA failed: %s", errmsg)); +} + static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) { struct mg_str host = mg_url_host(s_ota_metadata.url); - struct mg_http_message hm; - static size_t ofs = 0; - static bool ota_begun = false; - size_t alignment = 512, drop, len; - int n, status; - char *buf; - (void) ev_data; - if (ev == MG_EV_POLL) { - if (s_start_time + 120 * 1000 < mg_millis()) { - mg_error(c, "Connection timeout"); - } + if (s_start_time + 120 * 1000 < mg_millis()) mg_error(c, "Timeout"); } else if (ev == MG_EV_CONNECT) { mg_printf(c, "GET %s HTTP/1.1\r\n" "Host: %.*s\r\n" "Connection: close\r\n\r\n", mg_url_uri(s_ota_metadata.url), (int) host.len, host.buf); - ofs = 0; - ota_begun = false; - } else if (ev == MG_EV_READ) { - if (s_ota_status == MG_OTA_STATUS_FAIL) return; - if (!ota_begun) { // First chunk, parse headers, OTA begin - n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); - if (n < 0) { - mg_error(c, "Bad HTTP response"); - return; - } - if (n == 0) return; - status = mg_http_status(&hm); - if (status != 200 || hm.body.len != s_ota_metadata.size) { - mg_error(c, "Bad HTTP response (%d), body length: %lu", status, - hm.body.len); - return; - } - MG_DEBUG(("Beginning OTA (%ld bytes)", s_ota_metadata.size)); - if (!mg_ota_begin(s_ota_metadata.size)) { - s_ota_status = MG_OTA_STATUS_FAIL; - mg_error(c, "mg_ota_begin(%lu) failed", - (unsigned long) s_ota_metadata.size); - return; - } - ota_begun = true; - len = c->recv.len - (size_t) n; - buf = (char *) c->recv.buf + n; - drop = (size_t) n; - } else { // Header parsed, incoming chunks - len = c->recv.len; - buf = (char *) c->recv.buf; - drop = 0; - } - // OTA write - size_t aligned = (ofs + len < s_ota_metadata.size) - ? aligned = MG_ROUND_DOWN(len, alignment) - : len; - if (aligned == 0) return; - if (!mg_ota_write(buf, aligned)) { - mg_error(c, "mg_ota_write(%lu) @%lu failed", (unsigned long) aligned, - (unsigned long) ofs); + } else if (ev == MG_EV_HTTP_HDRS) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + int status = mg_http_status(hm); + if (status != 200 || hm->body.len != s_ota_metadata.size) { + mg_error(c, "Bad HTTP response: status %d, size %lu vs %lu", status, + (unsigned long) hm->body.len, + (unsigned long) s_ota_metadata.size); + s_ota_status = MG_OTA_STATUS_FAIL; return; } - ofs += aligned; - mg_iobuf_del(&c->recv, 0, aligned + drop); - MG_DEBUG(("Wrote %lu/%lu bytes", (unsigned long) ofs, - (unsigned long) s_ota_metadata.size)); + MG_DEBUG( + ("Beginning OTA (%lu bytes)", (unsigned long) s_ota_metadata.size)); + mg_http_start_ota(c, hm, s_ota_done); } else if (ev == MG_EV_ERROR) { MG_ERROR(("%lu Connection error", c->id)); s_ota_status = MG_OTA_STATUS_FAIL; - } else if (ev == MG_EV_CLOSE) { // OTA end - MG_DEBUG(("Connection closing, downloaded %ld/%ld bytes", ofs, - s_ota_metadata.size)); - if (s_ota_status != MG_OTA_STATUS_FAIL && ota_begun) { - if (ofs != s_ota_metadata.size) { - s_ota_status = MG_OTA_STATUS_FAIL; - MG_ERROR(("Firmware size mismatch: got %lu expected %lu", - (unsigned long) ofs, (unsigned long) s_ota_metadata.size)); - } else if (!mg_ota_end()) { - s_ota_status = MG_OTA_STATUS_FAIL; - MG_ERROR(("mg_ota_end() failed")); - } else { - MG_DEBUG(("mg_ota_end() successful")); - s_ota_status = MG_OTA_STATUS_SUCCESS; - } - } - ota_begun = false; - ofs = 0; } + (void) ev_data; } void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, @@ -189,7 +136,7 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, if (fn) fn("Pulling firmware"); s_start_time = mg_millis(); MG_DEBUG(("Connecting to %s to download firmware", s_ota_metadata.url)); - if (!mg_connect(mgr, s_ota_metadata.url, s_firmware_fn, NULL)) { + if (!mg_http_connect(mgr, s_ota_metadata.url, s_firmware_fn, NULL)) { if (fn) fn("Failed to connect"); free_ota_metadata(); return; @@ -198,5 +145,3 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, if (s_ota_status == MG_OTA_STATUS_FAIL && fn) fn("OTA fail"); free_ota_metadata(); } - -#endif diff --git a/test/unit_test.c b/test/unit_test.c index 96f84841..1ea28f3e 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -2983,6 +2983,29 @@ static void uc(struct mg_connection *c, int ev, void *ev_data) { } } +static int s_su_done; + +static void su_cb(struct mg_connection *c, const char *errmsg) { + s_su_done = errmsg ? -1 : 1; + mg_http_reply(c, errmsg ? 400 : 200, "", errmsg ? errmsg : "ok\n"); + c->is_draining = 1; +} + +static void su(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_HDRS) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + struct mg_str name = mg_str_n(hm->uri.buf + 1, hm->uri.len - 1); + mg_http_start_upload(c, hm, name, mg_str("."), &mg_fs_posix, su_cb); + } +} + +static void cu(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_CONNECT) + mg_printf(c, "POST %s HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n", + (char *) c->fn_data); + (void) ev_data; +} + static void test_http_upload(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12352"; @@ -2996,6 +3019,36 @@ static void test_http_upload(void) { ASSERT(s == NULL); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); + + // mg_http_start_upload: successful upload, file content verified + { + char buf[FETCH_BUF_SIZE]; + struct mg_str fc; + remove("su_ok.txt"); + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://127.0.0.1:12355", su, NULL); + ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12355", + "POST /su_ok.txt HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n") == + 200); + fc = mg_file_read(&mg_fs_posix, "su_ok.txt"); + ASSERT(mg_strcmp(fc, mg_str("foo\nbar\n")) == 0); + mg_free((void *) fc.buf); + remove("su_ok.txt"); + mg_mgr_free(&mgr); + ASSERT(mgr.conns == NULL); + } + + // mg_http_start_upload: path traversal rejected, callback gets error + { + s_su_done = 0; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://127.0.0.1:12356", su, NULL); + mg_http_connect(&mgr, "http://127.0.0.1:12356", cu, (void *) "/../evil.txt"); + for (i = 0; i < 50 && !s_su_done; i++) mg_mgr_poll(&mgr, 5); + ASSERT(s_su_done == -1); + mg_mgr_free(&mgr); + ASSERT(mgr.conns == NULL); + } } #define LONG_CHUNK "chunk with length taking up more than two hex digits"