diff --git a/examples/esp32/Makefile b/examples/esp32/Makefile index bdf03ca6..ee2290a8 100644 --- a/examples/esp32/Makefile +++ b/examples/esp32/Makefile @@ -9,7 +9,7 @@ example: main/main.c Makefile COMPORT ?= /dev/cu.SLAB_USBtoUART ESPTOOL ?= esptool.py flash: - cd build && $(ESPTOOL) --chip esp32 -p $(COMPORT) -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x8000 partition_table/partition-table.bin 0x1000 bootloader/bootloader.bin 0x10000 mongoose-esp32-example.bin + cd build && $(ESPTOOL) --chip esp32 -p $(COMPORT) -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x8000 partition_table/partition-table.bin 0x1000 bootloader/bootloader.bin 0x100000 mongoose-esp32-example.bin clean: rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb mongoose mongoose_* mongoose.* build sdkconfig diff --git a/examples/esp32/main/CMakeLists.txt b/examples/esp32/main/CMakeLists.txt index 5ca8e861..2dcf0bed 100644 --- a/examples/esp32/main/CMakeLists.txt +++ b/examples/esp32/main/CMakeLists.txt @@ -2,3 +2,4 @@ idf_component_register(SRCS "main.c" "wifi.c" "../../../mongoose.c" INCLUDE_DIRS "../../..") +component_compile_options(-DMG_ENABLE_DIRECTORY_LISTING=1 -DMG_ENABLE_LINES) diff --git a/examples/esp32/main/main.c b/examples/esp32/main/main.c index 1b92198a..4d508f4f 100644 --- a/examples/esp32/main/main.c +++ b/examples/esp32/main/main.c @@ -1,54 +1,53 @@ // Copyright (c) 2020 Cesanta Software Limited // All rights reserved +#include "esp_spiffs.h" +#include "freertos/FreeRTOS.h" #include "mongoose.h" #define WIFI_SSID "WIFI_NETWORK" // SET THIS! #define WIFI_PASS "WIFI_PASSWORD" // SET THIS! +#define FS_ROOT "/spiffs" -#define SERVER_URL "http://0.0.0.0:80" -#define CLIENT_URL "http://info.cern.ch" - -// Event handler for an server (accepted) connection +// Event handler for an server (accepted) connection. Implemented endpoints: +// /api/stats - return JSON object with ESP32 stats (free RAM) +// any other - serve files from the filesystem static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_HTTP_MSG) { - mg_http_reply(c, 200, "", "Hello from ESP!\n"); + struct mg_http_message *hm = ev_data; + if (mg_http_match_uri(hm, "/api/stats")) { + mg_http_reply(c, 200, "", "{\"ram\": %lu}\n", xPortGetFreeHeapSize()); + } else { + struct mg_http_serve_opts opts = {.root_dir = FS_ROOT}; + mg_http_serve_dir(c, hm, &opts); + } } } -// Event handler for a client connection - fetch the first web page in history -// To enable TLS for HTTP, -// 1. Copy "ca.pem" file to the ESP32 flash FS -// 2. Add TLS init snippet for the connection, see examples/http-client -static void cb2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { - if (ev == MG_EV_CONNECT) { - struct mg_str s = mg_url_host(CLIENT_URL); - mg_printf(c, "GET / HTTP/1.0\r\nHost: %.*s\r\n\r\n", (int) s.len, s.ptr); - } else if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = ev_data; // Print HTTP response - LOG(LL_INFO, ("Fetched:\n%.*s", (int) hm->message.len, hm->message.ptr)); - c->is_closing = 1; - } -} - -// Called after we're connected to WiFi network -static void run_mongoose(void) { - struct mg_mgr mgr; - mg_log_set("3"); - mg_mgr_init(&mgr); - mg_http_listen(&mgr, SERVER_URL, cb, &mgr); // Listening server - mg_http_connect(&mgr, CLIENT_URL, cb2, &mgr); // Example client - LOG(LL_INFO, ("Starting Mongoose web server v%s", MG_VERSION)); - for (;;) mg_mgr_poll(&mgr, 1000); - mg_mgr_free(&mgr); +// SPIFFS is flat, so tell Mongoose that the FS root is a directory +// This cludge is not required for filesystems with directory support +bool mg_is_dir(const char *path) { + return strcmp(path, FS_ROOT) == 0; } void app_main(void) { + // Mount filesystem + esp_vfs_spiffs_conf_t conf = { + .base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true}; + int res = esp_vfs_spiffs_register(&conf); + LOG(res == ESP_OK ? LL_INFO : LL_ERROR, ("FS %s, %d", conf.base_path, res)); + mg_file_printf(FS_ROOT "/hello.txt", "%s", "hello from ESP"); + // Setup wifi. This function is implemented in wifi.c // It blocks until connected to the configured WiFi network void wifi_init(const char *ssid, const char *pass); wifi_init(WIFI_SSID, WIFI_PASS); - // Done connecting to WiFi, now start HTTP server - run_mongoose(); + // Connected to WiFi, now start HTTP server + struct mg_mgr mgr; + mg_log_set("3"); + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0:80", cb, &mgr); // Listening server + LOG(LL_INFO, ("Starting Mongoose web server v%s", MG_VERSION)); + for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop } diff --git a/examples/esp32/partitions.csv b/examples/esp32/partitions.csv new file mode 100644 index 00000000..8ab9ab77 --- /dev/null +++ b/examples/esp32/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +storage, data, spiffs, 0x10000, 0x10000, +factory, app, factory, 0x100000, 1M, diff --git a/examples/esp32/sdkconfig.defaults b/examples/esp32/sdkconfig.defaults new file mode 100644 index 00000000..8ff365b1 --- /dev/null +++ b/examples/esp32/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/mongoose.c b/mongoose.c index 0c1df381..ada38118 100644 --- a/mongoose.c +++ b/mongoose.c @@ -483,7 +483,7 @@ int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, s = (const char *) memchr(p, '&', (size_t)(e - p)); if (s == NULL) s = e; len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1); - if (len == -1) len = -3; // Failed to decode + if (len < 0) len = -3; // Failed to decode break; } } @@ -511,7 +511,7 @@ int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, } } if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination - return i >= src_len ? (int) j : -1; + return i >= src_len && j < dst_len ? (int) j : -1; } int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { @@ -819,21 +819,23 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, MG_ARCH == MG_ARCH_FREERTOS char *realpath(const char *src, char *dst) { int len = strlen(src); - if (len > PATH_MAX - 1) len = PATH_MAX - 1; + if (len > MG_PATH_MAX - 1) len = MG_PATH_MAX - 1; strncpy(dst, src, len); dst[len] = '\0'; + LOG(LL_DEBUG, ("[%s] -> [%s]", src, dst)); return dst; } #endif -// Try to avoid dirent API -static int mg_is_dir(const char *path) { +// Allow user to override this function +bool mg_is_dir(const char *path) WEAK; +bool mg_is_dir(const char *path) { #if MG_ARCH == MG_ARCH_FREERTOS struct FF_STAT st; return (ff_stat(path, &st) == 0) && (st.st_mode & FF_IFDIR); #else - struct stat st; - return (stat(path, &st) == 0) && (st.st_mode & S_IFDIR); + mg_stat_t st; + return mg_stat(path, &st) == 0 && S_ISDIR(st.st_mode); #endif } @@ -978,7 +980,7 @@ static void printdirentry(struct mg_connection *c, struct mg_http_message *hm, static void listdir(struct mg_connection *c, struct mg_http_message *hm, char *dir) { - char path[PATH_MAX + 1], *p = &dir[strlen(dir) - 1], tmp[10]; + char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; struct dirent *dp; DIR *dirp; @@ -997,14 +999,17 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, (int) hm->uri.len, hm->uri.ptr, (int) hm->uri.len, hm->uri.ptr); while ((dp = readdir(dirp)) != NULL) { mg_stat_t st; + const char *sep = dp->d_name[0] == MG_DIRSEP ? "/" : ""; // Do not show current dir and hidden files if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; - snprintf(path, sizeof(path), "%s/%s", dir, dp->d_name); - if (mg_stat(path, &st) != 0) { + // SPIFFS can report "/foo.txt" in the dp->d_name + if (snprintf(path, sizeof(path), "%s%s%s", dir, sep, dp->d_name) < 0) { + LOG(LL_ERROR, ("%s truncated", dp->d_name)); + } else if (mg_stat(path, &st) != 0) { LOG(LL_ERROR, ("%lu stat(%s): %d", c->id, path, errno)); - continue; + } else { + printdirentry(c, hm, dp->d_name, &st); } - printdirentry(c, hm, dp->d_name, &st); } closedir(dirp); mg_printf(c, @@ -1022,59 +1027,70 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts) { - char path[PATH_MAX + 2], root[sizeof(path) - 2], real[sizeof(path) - 2]; - path[0] = root[0] = real[0] = '\0'; - if (realpath(opts->root_dir, root) == NULL) - LOG(LL_DEBUG, ("realpath(%s): %d", opts->root_dir, errno)); - if (!mg_is_dir(root)) { - mg_http_reply(c, 400, "", "Bad web root [%s]\n", root); + char t1[MG_PATH_MAX], t2[sizeof(t1)]; + t1[0] = t2[0] = '\0'; + + if (realpath(opts->root_dir, t1) == NULL) + LOG(LL_ERROR, ("realpath(%s): %d", opts->root_dir, errno)); + + LOG(LL_DEBUG, ("realpath(%s) -> [%s] %zu", opts->root_dir, t1, sizeof(t1))); + + if (!mg_is_dir(t1)) { + mg_http_reply(c, 400, "", "Bad web root [%s]\n", t1); } else { - char dec[PATH_MAX]; // NOTE(lsm): Xilinx snprintf does not 0-terminate the detination for // the %.*s specifier, if the length is zero. Make sure hm->uri.len > 0 bool is_index = false; - int ndec = mg_url_decode(hm->uri.ptr, hm->uri.len, dec, sizeof(dec), 0); - size_t n = - snprintf(path, sizeof(path), "%s%.*s", root, ndec < 0 ? 0 : ndec, dec); - while (n > 0 && n < sizeof(path) && path[n - 1] == '/') path[--n] = 0; - if (realpath(path, real) == NULL) - LOG(LL_DEBUG, ("realpath(%s): %d", path, errno)); - // LOG(LL_INFO, ("[%s] [%s] [%s] [%s]", dir, root, path, real)); - if (mg_is_dir(real)) { - strncat(real, "/index.html", sizeof(real) - strlen(real) - 1); - real[sizeof(real) - 1] = '\0'; + size_t n1 = strlen(t1), n2; + + mg_url_decode(hm->uri.ptr, hm->uri.len, t1 + n1, sizeof(t1) - n1, 0); + t1[sizeof(t1) - 1] = '\0'; + n2 = strlen(t1); + while (n2 > 0 && t1[n2 - 1] == '/') t1[--n2] = 0; + + if (realpath(t1, t2) == NULL) + LOG(LL_ERROR, ("realpath(%s): %d", t1, errno)); + + if (mg_is_dir(t2)) { + strncat(t2, "/index.html", sizeof(t2) - strlen(t2) - 1); + t2[sizeof(t2) - 1] = '\0'; is_index = true; } - if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) { + + LOG(LL_DEBUG, (" --> [%s] [%s] %zu", t1, t2, sizeof(t1))); + + if (strlen(t2) < n1 || memcmp(t1, t2, n1) != 0) { + // Requested file is located outside root directory, fail mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr); } else { - FILE *fp = fopen(real, "r"); + FILE *fp = fopen(t2, "r"); #if MG_ENABLE_SSI if (is_index && fp == NULL) { - char *p = real + strlen(real); - while (p > real && p[-1] != '/') p--; - strncpy(p, "index.shtml", &real[sizeof(real)] - p - 2); - real[sizeof(real) - 1] = '\0'; - fp = fopen(real, "r"); + char *p = t2 + strlen(t2); + while (p > t2 && p[-1] != '/') p--; + strncpy(p, "index.shtml", &t2[sizeof(t2)] - p - 2); + t2[sizeof(t2) - 1] = '\0'; + fp = fopen(t2, "r"); } #endif #if MG_ENABLE_HTTP_DEBUG_ENDPOINT - snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real); + snprintf(c->label, sizeof(c->label) - 1, "<-F %s", t2); #endif if (is_index && fp == NULL) { #if MG_ENABLE_DIRECTORY_LISTING - listdir(c, hm, real); + listdir(c, hm, t2); #else mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); #endif #if MG_ENABLE_SSI } else if (opts->ssi_pattern != NULL && - mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), - real, strlen(real))) { - mg_http_serve_ssi(c, root, real); + mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), t2, + strlen(t2))) { + t1[n1] = '\0'; + mg_http_serve_ssi(c, t1, t2); #endif } else { - mg_http_serve_file(c, hm, real, guess_content_type(real), NULL); + mg_http_serve_file(c, hm, t2, guess_content_type(t2), NULL); } if (fp != NULL) fclose(fp); } @@ -3058,7 +3074,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) { if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { buf[len++] = ch & 0xff; if (sscanf(buf, " [%s] [%s] %zu", t1, t2, sizeof(t1))); + + if (strlen(t2) < n1 || memcmp(t1, t2, n1) != 0) { + // Requested file is located outside root directory, fail mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr); } else { - FILE *fp = fopen(real, "r"); + FILE *fp = fopen(t2, "r"); #if MG_ENABLE_SSI if (is_index && fp == NULL) { - char *p = real + strlen(real); - while (p > real && p[-1] != '/') p--; - strncpy(p, "index.shtml", &real[sizeof(real)] - p - 2); - real[sizeof(real) - 1] = '\0'; - fp = fopen(real, "r"); + char *p = t2 + strlen(t2); + while (p > t2 && p[-1] != '/') p--; + strncpy(p, "index.shtml", &t2[sizeof(t2)] - p - 2); + t2[sizeof(t2) - 1] = '\0'; + fp = fopen(t2, "r"); } #endif #if MG_ENABLE_HTTP_DEBUG_ENDPOINT - snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real); + snprintf(c->label, sizeof(c->label) - 1, "<-F %s", t2); #endif if (is_index && fp == NULL) { #if MG_ENABLE_DIRECTORY_LISTING - listdir(c, hm, real); + listdir(c, hm, t2); #else mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); #endif #if MG_ENABLE_SSI } else if (opts->ssi_pattern != NULL && - mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), - real, strlen(real))) { - mg_http_serve_ssi(c, root, real); + mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), t2, + strlen(t2))) { + t1[n1] = '\0'; + mg_http_serve_ssi(c, t1, t2); #endif } else { - mg_http_serve_file(c, hm, real, guess_content_type(real), NULL); + mg_http_serve_file(c, hm, t2, guess_content_type(t2), NULL); } if (fp != NULL) fclose(fp); } diff --git a/src/ssi.c b/src/ssi.c index b2b822e2..0417d51c 100644 --- a/src/ssi.c +++ b/src/ssi.c @@ -17,7 +17,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) { if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { buf[len++] = ch & 0xff; if (sscanf(buf, "