diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb4e9abc..9e10e807 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - name: Install packages run: sudo apt-get install libmbedtls-dev - name: examples - run: make ex + run: make clean examples macos: runs-on: macos-latest steps: @@ -49,3 +49,21 @@ jobs: - uses: actions/checkout@v2 - name: arm run: make arm + esp32: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: esp32 + run: make -C examples/esp32 build + esp8266: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: esp8266 + run: make -C examples/esp8266 build + stm32-freertos-tcp: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: stm32-freertos-tcp + run: make -C examples/stm32-freertos-tcp build diff --git a/Makefile b/Makefile index 7c7c712d..69c781e2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SRCS = mongoose.c test/unit_test.c test/packed_fs.c HDRS = $(wildcard src/*.h) -DEFS ?= -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES -DMG_ENABLE_DIRECTORY_LISTING=1 -DMG_ENABLE_SSI=1 +DEFS ?= -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES WARN ?= -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion OPTS ?= -O3 -g3 INCS ?= -Isrc -I. @@ -12,10 +12,9 @@ VCFLAGS = /nologo /W3 /O2 /I. $(DEFS) $(TFLAGS) IPV6 ?= 1 ASAN_OPTIONS ?= EXAMPLES := $(wildcard examples/*) -EXAMPLE_TARGET ?= example PREFIX ?= /usr/local SOVERSION = 7.4 -.PHONY: ex test +.PHONY: examples test ifeq "$(SSL)" "MBEDTLS" MBEDTLS ?= /usr @@ -28,10 +27,10 @@ CFLAGS += -DMG_ENABLE_OPENSSL=1 -I$(OPENSSL)/include LDFLAGS ?= -L$(OPENSSL)/lib -lssl -lcrypto endif -all: mg_prefix test test++ arm ex vc98 vc2017 mingw mingw++ linux linux++ fuzz +all: mg_prefix test test++ arm examples vc98 vc2017 mingw mingw++ linux linux++ fuzz -ex: - @for X in $(EXAMPLES); do $(MAKE) -C $$X $(EXAMPLE_TARGET) || break; done +examples: + @for X in $(EXAMPLES); do $(MAKE) -C $$X example || break; done test/packed_fs.c: $(CC) $(CFLAGS) examples/complete/pack.c -o pack @@ -73,10 +72,10 @@ infer: infer run -- cc test/unit_test.c -c -W -Wall -Werror -Isrc -I. -O2 -DMG_ENABLE_MBEDTLS=1 -DMG_ENABLE_LINES -I/usr/local/Cellar/mbedtls/2.23.0/include -DMG_ENABLE_IPV6=1 -g -o /dev/null arm: mongoose.h $(SRCS) - $(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES -DMG_ENABLE_DIRECTORY_LISTING=0 -DMG_ENABLE_SSI=1 -o unit_test -nostartfiles --specs nosys.specs -e 0 + $(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES=1 -o unit_test -nostartfiles --specs nosys.specs -e 0 riscv: mongoose.h $(SRCS) - $(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES -DMG_ENABLE_DIRECTORY_LISTING=0 -DMG_ENABLE_SSI=1 -o unit_test + $(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES=1 -o unit_test #vc98: VCFLAGS += -DMG_ENABLE_IPV6=1 vc98: Makefile mongoose.c mongoose.h test/unit_test.c @@ -125,6 +124,6 @@ mongoose.c: Makefile $(wildcard src/*) mongoose.h: $(HDRS) Makefile (cat src/license.h src/version.h ; cat src/config.h src/arch.h src/arch_*.h src/str.h src/log.h src/timer.h src/util.h src/fs.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/ws.h src/sntp.h src/mqtt.h src/dns.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ -clean: EXAMPLE_TARGET = clean -clean: ex +clean: rm -rf $(PROG) *.o *.dSYM unit_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack + @for X in $(EXAMPLES); do $(MAKE) -C $$X clean; done diff --git a/docs/README.md b/docs/README.md index 02541f62..4a28d501 100644 --- a/docs/README.md +++ b/docs/README.md @@ -204,7 +204,7 @@ option during build time, use the `-D OPTION` compiler flag: $ cc app0.c mongoose.c # Use defaults! $ cc app1.c mongoose.c -D MG_ENABLE_IPV6=1 # Build with IPv6 enabled $ cc app2.c mongoose.c -D MG_ARCH=MG_ARCH_FREERTOS_LWIP # Set architecture -$ cc app3.c mongoose.c -D MG_ENABLE_SSI=1 -D MG_ENABLE_LOG=0 # Multiple options +$ cc app3.c mongoose.c -D MG_ENABLE_SSI=0 -D MG_ENABLE_LOG=0 # Multiple options ``` The list of supported @@ -240,12 +240,13 @@ Here is a list of build constants and their default values: |MG_ENABLE_IPV6 | 0 | Enable IPv6 | |MG_ENABLE_LOG | 1 | Enable `LOG()` macro | |MG_ENABLE_MD5 | 0 | Use native MD5 implementation | -|MG_ENABLE_DIRECTORY_LISTING | 0 | Enable directory listing for HTTP server | |MG_ENABLE_SOCKETPAIR | 0 | Enable `mg_socketpair()` for multi-threading | -|MG_ENABLE_SSI | 0 | Enable serving SSI files by `mg_http_serve_dir()` | -|MG_IO_SIZE | 512 | Granularity of the send/recv IO buffer growth | +|MG_ENABLE_SSI | 1 | Enable serving SSI files by `mg_http_serve_dir()` | +|MG_ENABLE_DIRLIST | 0 | Enable directory listing | +|MG_IO_SIZE | 2048 | Granularity of the send/recv IO buffer growth | |MG_MAX_RECV_BUF_SIZE | (3 * 1024 * 1024) | Maximum recv buffer size | |MG_MAX_HTTP_HEADERS | 40 | Maximum number of HTTP headers | +|MG_ENABLE_LINES | undefined | If defined, show source file names in logs | NOTE: `MG_IO_SIZE` controls the maximum UDP message size, see @@ -724,18 +725,20 @@ enable SSI, set a `-DMG_ENABLE_SSI=1` build flag. ```c void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, - const char *path, const char *mimetype, - const char *extra_headers); + const char *path, struct mg_http_serve_opts *opts); ``` Serve static file. Note that the `extra_headers` must end with `\r\n`. Here is an example call: ```c -mg_http_serve_file(c, hm, "a.png", "image/png", "AA: bb\r\nCC: dd\r\n"); +struct mg_http_serve_opts opts = {.mime_types = "png=image/png", + .extra_headers = "AA: bb\r\nCC: dd\r\n"}; +mg_http_serve_file(c, hm, "a.png", &opts); ``` + ### mg\_http\_reply() ```c diff --git a/examples/complete/Makefile b/examples/complete/Makefile index 87defa1c..c55f8a58 100644 --- a/examples/complete/Makefile +++ b/examples/complete/Makefile @@ -20,4 +20,4 @@ windows: $(DOCKER) $(PROG).exe clean: - rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt fs.c pack + rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt packed_fs.c pack diff --git a/examples/complete/main.c b/examples/complete/main.c index 67d2f780..23f76ea8 100644 --- a/examples/complete/main.c +++ b/examples/complete/main.c @@ -119,7 +119,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { "Pragma: no-cache\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\n" "Content-Type: multipart/x-mixed-replace; boundary=--foo\r\n\r\n"); } else if (mg_http_match_uri(hm, "/api/log/static")) { - mg_http_serve_file(c, hm, "log.txt", "text/plain", ""); + struct mg_http_serve_opts opts = {.root_dir = NULL}; + mg_http_serve_file(c, hm, "log.txt", &opts); } else if (mg_http_match_uri(hm, "/api/log/live")) { c->label[0] = 'L'; // Mark that connection as live log listener mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); diff --git a/examples/esp32/Makefile b/examples/esp32/Makefile index ee2290a8..d4fc6aa2 100644 --- a/examples/esp32/Makefile +++ b/examples/esp32/Makefile @@ -3,7 +3,10 @@ ROOTDIR = $(realpath $(CURDIR)/../..) all: example -example: main/main.c Makefile +example: + true + +build: main/main.c Makefile docker run --rm -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf idf.py build COMPORT ?= /dev/cu.SLAB_USBtoUART @@ -12,4 +15,4 @@ 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 0x100000 mongoose-esp32-example.bin clean: - rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb mongoose mongoose_* mongoose.* build sdkconfig + rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb mongoose mongoose_* mongoose.* build sdkconfig build diff --git a/examples/esp32/main/CMakeLists.txt b/examples/esp32/main/CMakeLists.txt index 2dcf0bed..7c3e71dd 100644 --- a/examples/esp32/main/CMakeLists.txt +++ b/examples/esp32/main/CMakeLists.txt @@ -2,4 +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) +component_compile_options(-DMG_ENABLE_LINES) diff --git a/examples/esp8266/Makefile b/examples/esp8266/Makefile index 8fb09615..e838935d 100644 --- a/examples/esp8266/Makefile +++ b/examples/esp8266/Makefile @@ -3,8 +3,11 @@ ROOTDIR = $(realpath $(CURDIR)/../..) all: example -example: src/main/main.c src/main/wifi.c Makefile +example: + true + +build: src/main/main.c src/main/wifi.c Makefile docker run --rm -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) docker.io/mdashnet/8266 make -C src defconfig app clean: - rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb mongoose mongoose_* mongoose.* src/build src/sdkconfig + rm -rf build diff --git a/examples/http-restful-server/Makefile b/examples/http-restful-server/Makefile index 9302488d..d92c128d 100644 --- a/examples/http-restful-server/Makefile +++ b/examples/http-restful-server/Makefile @@ -12,7 +12,7 @@ all: $(PROG) $(DEBUGGER) ./$(PROG) $(ARGS) $(PROG): main.c - $(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 $(CFLAGS) -o $(PROG) main.c + $(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 $(CFLAGS) -o $(PROG) main.c clean: rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb diff --git a/examples/http-server/Makefile b/examples/http-server/Makefile index 0e6bae63..b371a54c 100644 --- a/examples/http-server/Makefile +++ b/examples/http-server/Makefile @@ -1,6 +1,6 @@ PROG ?= example ROOT ?= $(realpath $(CURDIR)/../..) -DEFS ?= -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 -DMG_ENABLE_SSI=1 +DEFS ?= -DMG_ENABLE_LINES=1 CFLAGS ?= -I../.. -W -Wall -DMG_ENABLE_IPV6=1 $(DEFS) $(EXTRA) VCFLAGS = /nologo /W3 /O2 /I../.. $(DEFS) $(EXTRA) /link /incremental:no /machine:IX86 VC98 = docker run -it --rm -e Tmp=. -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/vc98 diff --git a/examples/live-log/main.c b/examples/live-log/main.c index 073fab8e..b8ab2aaf 100644 --- a/examples/live-log/main.c +++ b/examples/live-log/main.c @@ -11,7 +11,8 @@ 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, "/api/log/static")) { - mg_http_serve_file(c, hm, "log.txt", "text/plain", ""); + struct mg_http_serve_opts opts = {.root_dir = NULL}; + mg_http_serve_file(c, hm, "log.txt", &opts); } else if (mg_http_match_uri(hm, "/api/log/live")) { c->label[0] = 'L'; // Mark that connection as live log listener mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); diff --git a/examples/stm32-freertos-tcp/Makefile b/examples/stm32-freertos-tcp/Makefile index 096c65b1..250ae716 100644 --- a/examples/stm32-freertos-tcp/Makefile +++ b/examples/stm32-freertos-tcp/Makefile @@ -52,7 +52,10 @@ SOURCES += $(FREERTOS_PLUS_TCP_PATH)/portable/NetworkInterface/STM32Fxx/stm32fxx OBJECTS = obj/boot.o $(SOURCES:%.c=obj/%.o) -example: $(PROG).hex +example: + true + +build: $(PROG).hex $(PROG).bin: $(PROG).elf $(DOCKER) arm-none-eabi-objcopy -O binary $< $@ diff --git a/examples/timers/Makefile b/examples/timers/Makefile index 09ac5f8c..3d179d14 100644 --- a/examples/timers/Makefile +++ b/examples/timers/Makefile @@ -4,7 +4,7 @@ all: $(PROG) $(DEBUGGER) ./$(PROG) $(ARGS) $(PROG): main.c - $(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 $(CFLAGS) -o $(PROG) main.c + $(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 $(CFLAGS) -o $(PROG) main.c clean: rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb diff --git a/examples/websocket-server/Makefile b/examples/websocket-server/Makefile index 09ac5f8c..3d179d14 100644 --- a/examples/websocket-server/Makefile +++ b/examples/websocket-server/Makefile @@ -4,7 +4,7 @@ all: $(PROG) $(DEBUGGER) ./$(PROG) $(ARGS) $(PROG): main.c - $(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 $(CFLAGS) -o $(PROG) main.c + $(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 $(CFLAGS) -o $(PROG) main.c clean: rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb diff --git a/mongoose.c b/mongoose.c index ae528bea..9d0f75f3 100644 --- a/mongoose.c +++ b/mongoose.c @@ -424,15 +424,15 @@ const char *mg_unpack(const char *path, size_t *size) { } static char *packed_realpath(const char *path, char *resolved_path) { - if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + if (resolved_path == NULL) resolved_path = (char *) malloc(strlen(path) + 1); strcpy(resolved_path, path); return resolved_path; } -static int packed_stat(const char *path, size_t *size, unsigned *mtime) { +static int packed_stat(const char *path, size_t *size, time_t *mtime) { const char *data = mg_unpack(path, size); if (mtime) *mtime = 0; - return data == NULL ? 0 : MG_FS_READ; + return data == NULL ? MG_FS_DIR : MG_FS_READ; } static void packed_list(const char *path, void (*fn)(const char *, void *), @@ -447,8 +447,8 @@ static struct mg_fd *packed_open(const char *path, int flags) { struct mg_fd *fd = NULL; if (data == NULL) return NULL; if (flags & MG_FS_WRITE) return NULL; - fp = calloc(1, sizeof(*fp)); - fd = calloc(1, sizeof(*fd)); + fp = (struct packed_file *) calloc(1, sizeof(*fp)); + fd = (struct mg_fd *) calloc(1, sizeof(*fd)); fp->size = size; fp->data = data; fd->fd = fp; @@ -489,16 +489,21 @@ struct mg_fs mg_fs_packed = {packed_realpath, packed_stat, packed_list, #endif -#if defined(O_READ) +#if defined(FOPEN_MAX) static char *posix_realpath(const char *path, char *resolved_path) { #ifdef _WIN32 - return _fullpath(path, resolved_path, PATH_MAX); + return _fullpath(resolved_path, path, _MAX_PATH); +#elif MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \ + MG_ARCH == MG_ARCH_FREERTOS_TCP + if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + strcpy(resolved_path, path); + return resolved_path; #else return realpath(path, resolved_path); #endif } -static int posix_stat(const char *path, size_t *size, unsigned *mtime) { +static int posix_stat(const char *path, size_t *size, time_t *mtime) { #ifdef _WIN32 struct _stati64 st; wchar_t tmp[PATH_MAX]; @@ -509,26 +514,133 @@ static int posix_stat(const char *path, size_t *size, unsigned *mtime) { if (stat(path, &st) != 0) return 0; #endif if (size) *size = (size_t) st.st_size; - if (mtime) *mtime = (unsigned) st.st_mtime; + if (mtime) *mtime = st.st_mtime; return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); } +#ifdef _WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; + +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +int gettimeofday(struct timeval *tv, void *tz) { + FILETIME ft; + unsigned __int64 tmpres = 0; + + if (tv != NULL) { + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + tmpres /= 10; // convert into microseconds + tmpres -= (int64_t) 11644473600000000; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + (void) tz; + return 0; +} + +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} + +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); + attrs = GetFileAttributesW(wpath); + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; + } else { + free(d); + d = NULL; + } + } + return d; +} + +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + free(d); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} + +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} +#endif + static void posix_list(const char *dir, void (*fn)(const char *, void *), void *userdata) { - // char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; +#if MG_ENABLE_DIRLIST struct dirent *dp; DIR *dirp; - - // while (p > dir && *p != '/') *p-- = '\0'; - if ((dirp = (opendir(dir))) != NULL) { - size_t off, n; - while ((dp = readdir(dirp)) != NULL) { - // Do not show current dir and hidden files - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; - fn(dp->d_name, &st); - } - closedir(dirp); + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif } static struct mg_fd *posix_open(const char *path, int flags) { @@ -547,14 +659,14 @@ static struct mg_fd *posix_open(const char *path, int flags) { fp = (void *) fopen(path, mode); #endif if (fp == NULL) return NULL; - fd = calloc(1, sizeof(*fd)); + fd = (struct mg_fd *) calloc(1, sizeof(*fd)); fd->fd = fp; fd->fs = &mg_fs_posix; return fd; } static void posix_close(struct mg_fd *fd) { - if (fd) fclose((FILE *) fd->fd), free(fd); + if (fd != NULL) fclose((FILE *) fd->fd), free(fd); } static size_t posix_read(void *fp, void *buf, size_t len) { @@ -580,7 +692,7 @@ static char *posix_realpath(const char *path, char *resolved_path) { return NULL; } -static int posix_stat(const char *path, size_t *size, unsigned *mtime) { +static int posix_stat(const char *path, size_t *size, time_t *mtime) { (void) path, (void) size, (void) mtime; return 0; } @@ -990,14 +1102,15 @@ void mg_http_reply(struct mg_connection *c, int code, const char *headers, static void http_cb(struct mg_connection *, int, void *, void *); static void restore_http_cb(struct mg_connection *c) { - if (c->pfn_data != NULL) fclose((FILE *) c->pfn_data); + struct mg_fd *fd = (struct mg_fd *) c->pfn_data; + if (fd != NULL) fd->fs->close(fd); c->pfn_data = NULL; c->pfn = http_cb; } -char *mg_http_etag(char *buf, size_t len, struct stat *st) { - snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) st->st_mtime, - (int64_t) st->st_size); +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { + snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) mtime, + (int64_t) size); return buf; } @@ -1030,12 +1143,13 @@ int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, static void static_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { - FILE *fp = (FILE *) fn_data; + struct mg_fd *fd = (struct mg_fd *) fn_data; // Read to send IO buffer directly, avoid extra on-stack buffer size_t n, max = 2 * MG_IO_SIZE; if (c->send.size < max) mg_iobuf_resize(&c->send, max); if (c->send.len >= c->send.size) return; // Rate limit - n = fread(c->send.buf + c->send.len, 1, c->send.size - c->send.len, fp); + n = fd->fs->read(fd->fd, c->send.buf + c->send.len, + c->send.size - c->send.len); if (n > 0) c->send.len += n; if (c->send.len < c->send.size) restore_http_cb(c); } else if (ev == MG_EV_CLOSE) { @@ -1145,23 +1259,27 @@ static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, const char *path, struct mg_http_serve_opts *opts) { - struct mg_str *inm = mg_http_get_header(hm, "If-None-Match"); - struct stat st; char etag[64]; - FILE *fp = fopen(path, "rb"); - if (fp == NULL || stat(path, &st) != 0 || - mg_http_etag(etag, sizeof(etag), &st) != etag) { - LOG(LL_DEBUG, ("404 [%.*s] [%s] %p", (int) hm->uri.len, hm->uri.ptr, path, - (void *) fp)); + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_fd *fd = fs->open(path, MG_FS_READ); + size_t size = 0; + time_t mtime = 0; + struct mg_str *inm = NULL; + + if (fd == NULL || fs->stat(path, &size, &mtime) == 0) { + LOG(LL_DEBUG, ("404 [%.*s] %p", (int) hm->uri.len, hm->uri.ptr, fd)); mg_http_reply(c, 404, "", "%s", "Not found\n"); - if (fp != NULL) fclose(fp); - } else if (inm != NULL && mg_vcasecmp(inm, etag) == 0) { - fclose(fp); + fs->close(fd); + // NOTE: mg_http_etag() call should go first! + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && + mg_vcasecmp(inm, etag) == 0) { + fs->close(fd); mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { int n, status = 200; char range[100] = ""; - int64_t r1 = 0, r2 = 0, cl = st.st_size; + int64_t r1 = 0, r2 = 0, cl = (int64_t) size; struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); // Handle Range header @@ -1173,22 +1291,15 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, status = 416; cl = 0; snprintf(range, sizeof(range), - "Content-Range: bytes */" MG_INT64_FMT "\r\n", - (int64_t) st.st_size); + "Content-Range: bytes */" MG_INT64_FMT "\r\n", (int64_t) size); } else { status = 206; cl = r2 - r1 + 1; snprintf(range, sizeof(range), "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT "/" MG_INT64_FMT "\r\n", - r1, r1 + cl - 1, (int64_t) st.st_size); -#if defined(_FILE_OFFSET_BITS) && \ - (_FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ - _XOPEN_SOURCE >= 600) - fseeko(fp, (off_t) r1, SEEK_SET); -#else - fseek(fp, (long) r1, SEEK_SET); -#endif + r1, r1 + cl - 1, (int64_t) size); + fs->seek(fd->fd, (size_t) r1); } } @@ -1198,182 +1309,58 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { - fclose(fp); + fs->close(fd); } else { c->pfn = static_cb; - c->pfn_data = fp; + c->pfn_data = fd; } } } -#if MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \ - MG_ARCH == MG_ARCH_FREERTOS_TCP -char *realpath(const char *src, char *dst) { - int len = strlen(src); - 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 - -// 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_TCP && defined(MG_ENABLE_FF) - struct FF_STAT st; - return (ff_stat(path, &st) == 0) && (st.st_mode & FF_IFDIR); -#else - struct stat st; - return stat(path, &st) == 0 && S_ISDIR(st.st_mode); -#endif -} - -#if MG_ENABLE_DIRECTORY_LISTING - -#ifdef _WIN32 -struct dirent { - char d_name[MAX_PATH]; +struct printdirentrydata { + struct mg_connection *c; + struct mg_http_message *hm; + struct mg_http_serve_opts *opts; + const char *dir; }; -typedef struct win32_dir { - HANDLE handle; - WIN32_FIND_DATAW info; - struct dirent result; -} DIR; +static void printdirentry(const char *name, void *userdata) { + struct printdirentrydata *d = (struct printdirentrydata *) userdata; + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; + size_t size = 0; + time_t mtime = 0; + char path[MG_PATH_MAX], sz[64], mod[64]; + int flags, n = 0; -int gettimeofday(struct timeval *tv, void *tz) { - FILETIME ft; - unsigned __int64 tmpres = 0; - - if (tv != NULL) { - GetSystemTimeAsFileTime(&ft); - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - tmpres /= 10; // convert into microseconds - tmpres -= (int64_t) 11644473600000000; - tv->tv_sec = (long) (tmpres / 1000000UL); - tv->tv_usec = (long) (tmpres % 1000000UL); - } - (void) tz; - return 0; -} - -static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { - int ret; - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; - strncpy(buf, path, sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - // Trim trailing slashes. Leave backslash for paths like "X:\" - p = buf + strlen(buf) - 1; - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; - memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); - // Convert back to Unicode. If doubly-converted string does not match the - // original, something is fishy, reject. - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), - NULL, NULL); - if (strcmp(buf, buf2) != 0) { - wbuf[0] = L'\0'; - ret = 0; - } - return ret; -} - -DIR *opendir(const char *name) { - DIR *d = NULL; - wchar_t wpath[MAX_PATH]; - DWORD attrs; - - if (name == NULL) { - SetLastError(ERROR_BAD_ARGUMENTS); - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); + if (snprintf(path, sizeof(path), "%s%c%s", d->dir, MG_DIRSEP, name) < 0) { + LOG(LL_ERROR, ("%s truncated", name)); + } else if ((flags = fs->stat(path, &size, &mtime)) == 0) { + LOG(LL_ERROR, ("%lu stat(%s): %d", d->c->id, path, errno)); } else { - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); - attrs = GetFileAttributesW(wpath); - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { - (void) wcscat(wpath, L"\\*"); - d->handle = FindFirstFileW(wpath, &d->info); - d->result.d_name[0] = '\0'; + const char *slash = flags & MG_FS_DIR ? "/" : ""; + struct tm t; + if (flags & MG_FS_DIR) { + snprintf(sz, sizeof(sz), "%s", "[DIR]"); + } else if (size < 1024) { + snprintf(sz, sizeof(sz), "%d", (int) size); + } else if (size < 0x100000) { + snprintf(sz, sizeof(sz), "%.1fk", (double) size / 1024.0); + } else if (size < 0x40000000) { + snprintf(sz, sizeof(sz), "%.1fM", (double) size / 1048576); } else { - free(d); - d = NULL; + snprintf(sz, sizeof(sz), "%.1fG", (double) size / 1073741824); } + strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&mtime, &t)); + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); + mg_printf(d->c, + " %s%s" + "%s%s\n", + n, path, slash, name, slash, mod, sz); } - return d; -} - -int closedir(DIR *d) { - int result = 0; - if (d != NULL) { - if (d->handle != INVALID_HANDLE_VALUE) - result = FindClose(d->handle) ? 0 : -1; - free(d); - } else { - result = -1; - SetLastError(ERROR_BAD_ARGUMENTS); - } - return result; -} - -struct dirent *readdir(DIR *d) { - struct dirent *result = NULL; - if (d != NULL) { - memset(&d->result, 0, sizeof(d->result)); - if (d->handle != INVALID_HANDLE_VALUE) { - result = &d->result; - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, - sizeof(result->d_name), NULL, NULL); - if (!FindNextFileW(d->handle, &d->info)) { - FindClose(d->handle); - d->handle = INVALID_HANDLE_VALUE; - } - } else { - SetLastError(ERROR_FILE_NOT_FOUND); - } - } else { - SetLastError(ERROR_BAD_ARGUMENTS); - } - return result; -} -#endif - -static void printdirentry(struct mg_connection *c, const char *name, - struct stat *stp) { - char size[64], mod[64], path[MG_PATH_MAX]; - int is_dir = S_ISDIR(stp->st_mode), n = 0; - const char *slash = is_dir ? "/" : ""; - struct tm t; - - if (is_dir) { - snprintf(size, sizeof(size), "%s", "[DIR]"); - } else { - if (stp->st_size < 1024) { - snprintf(size, sizeof(size), "%d", (int) stp->st_size); - } else if (stp->st_size < 0x100000) { - snprintf(size, sizeof(size), "%.1fk", (double) stp->st_size / 1024.0); - } else if (stp->st_size < 0x40000000) { - snprintf(size, sizeof(size), "%.1fM", (double) stp->st_size / 1048576); - } else { - snprintf(size, sizeof(size), "%.1fG", (double) stp->st_size / 1073741824); - } - } - strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&stp->st_mtime, &t)); - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); - mg_printf(c, - " %s%s" - "%s%s\n", - n, path, slash, name, slash, mod, size); } static void listdir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts, char *dir) { - char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; - struct dirent *dp; - DIR *dirp; static const char *sort_js_code = ""; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct printdirentrydata d = {c, hm, opts, dir}; + char tmp[10]; + size_t off, n; - while (p > dir && *p != '/') *p-- = '\0'; - if ((dirp = (opendir(dir))) != NULL) { - size_t off, n; - mg_printf(c, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=utf-8\r\n" - "%s" - "Content-Length: \r\n\r\n", - opts->extra_headers == NULL ? "" : opts->extra_headers); - off = c->send.len; // Start of body - mg_printf(c, - "Index of %.*s%s%s" - "" - "

Index of %.*s

" - "" - "" - "" - "" - "\n", - (int) hm->uri.len, hm->uri.ptr, sort_js_code, sort_js_code2, - (int) hm->uri.len, hm->uri.ptr); + mg_printf(c, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "%s" + "Content-Length: \r\n\r\n", + opts->extra_headers == NULL ? "" : opts->extra_headers); + off = c->send.len; // Start of body + mg_printf(c, + "Index of %.*s%s%s" + "" + "

Index of %.*s

Name" - "ModifiedSize

" + "" + "" + "" + "" + "\n", + (int) hm->uri.len, hm->uri.ptr, sort_js_code, sort_js_code2, + (int) hm->uri.len, hm->uri.ptr); - while ((dp = readdir(dirp)) != NULL) { - struct stat 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; - // 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 (stat(path, &st) != 0) { - LOG(LL_ERROR, ("%lu stat(%s): %d", c->id, path, errno)); - } else { - printdirentry(c, dp->d_name, &st); - } - } - closedir(dirp); - mg_printf(c, - "" - "
Name" + "ModifiedSize


Mongoose v.%s
\n", - MG_VERSION); - n = (size_t) snprintf(tmp, sizeof(tmp), "%lu", - (unsigned long) (c->send.len - off)); - if (n > sizeof(tmp)) n = 0; - memcpy(c->send.buf + off - 10, tmp, n); // Set content length - } else { - mg_http_reply(c, 400, "", "Cannot open dir"); - LOG(LL_ERROR, ("%lu opendir(%s) -> %d", c->id, dir, errno)); - } + fs->list(dir, printdirentry, &d); + mg_printf(c, + "
" + "
Mongoose v.%s
\n", + MG_VERSION); + n = (size_t) snprintf(tmp, sizeof(tmp), "%lu", + (unsigned long) (c->send.len - off)); + if (n > sizeof(tmp)) n = 0; + memcpy(c->send.buf + off - 10, tmp, n); // Set content length } -#endif -static bool uri_to_local_path(struct mg_connection *c, - struct mg_http_message *hm, - struct mg_http_serve_opts *opts, char *root_dir, - size_t rlen, char *path, size_t path_len, - bool *is_index) { - bool success = false; - if (realpath(opts->root_dir, root_dir) == NULL) { +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + struct mg_http_serve_opts *opts, char *root_dir, + size_t rlen, char *path, size_t plen) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + int flags = 0, tmp; + if (fs->realpath(opts->root_dir, root_dir) == NULL) { LOG(LL_ERROR, ("realpath(%s): %d", opts->root_dir, errno)); mg_http_reply(c, 400, "", "Bad web root [%s]\n", opts->root_dir); - } else if (!mg_is_dir(root_dir)) { + } else if (!(fs->stat(root_dir, NULL, NULL) & MG_FS_DIR)) { mg_http_reply(c, 400, "", "Invalid web root [%s]\n", root_dir); } else { // 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 size_t n1 = strlen(root_dir), n2; - *is_index = false; - + // Temporarily append URI to the root_dir: that is the unresolved path mg_url_decode(hm->uri.ptr, hm->uri.len, root_dir + n1, rlen - n1, 0); root_dir[rlen - 1] = '\0'; n2 = strlen(root_dir); while (n2 > 0 && root_dir[n2 - 1] == '/') root_dir[--n2] = 0; - - if (realpath(root_dir, path) == NULL) { - LOG(LL_ERROR, ("realpath(%s): %d", root_dir, errno)); - mg_http_reply(c, 404, "", "Not found [%.*s]\n", (int) hm->uri.len, - hm->uri.ptr); + // Try to resolve it... + if (fs->realpath(root_dir, path) == NULL || + (flags = fs->stat(path, NULL, NULL)) == 0) { + mg_http_reply(c, 404, "", "Not found\n"); } else { - if (mg_is_dir(path)) { - strncat(path, "/index.html", path_len - strlen(path) - 1); - path[path_len - 1] = '\0'; - *is_index = true; - } - - if (strlen(path) < n1 || memcmp(root_dir, path, n1) != 0) { - // Requested file is located outside root directory, fail - mg_http_reply(c, 404, "", "Invalid URI [%.*s]\n", (int) hm->uri.len, - hm->uri.ptr); + // Path is resolved successfully. It it is a directory, try to + // serve index.html in it + root_dir[n1] = '\0'; // Restore root_dir - remove appended URI + n2 = strlen(path); // Memorise path length + if ((flags & MG_FS_DIR) && + ((snprintf(path + n2, plen - n2, "/index.html") > 0 && + (tmp = fs->stat(path, NULL, NULL)) != 0) || + (snprintf(path + n2, plen - n2, "/index.shtml") > 0 && + (tmp = fs->stat(path, NULL, NULL)) != 0))) { + flags = tmp; } else { - root_dir[n1] = '\0'; - success = true; + path[n2] = '\0'; // Remove appended index file name } } + // Check that the resolved file is located inside root directory + if (strlen(path) < n1 || memcmp(root_dir, path, n1) != 0) { + mg_http_reply(c, 404, "", "Invalid URI [%.*s]\n", (int) hm->uri.len, + hm->uri.ptr); + flags = 0; + } } - return success; + return flags; } void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts) { - char root_dir[MG_PATH_MAX], path[sizeof(root_dir)]; - bool is_index = false, exists; - struct stat st; - root_dir[0] = path[0] = '\0'; + char root[MG_PATH_MAX] = "", path[sizeof(root)] = ""; + int flags = uri_to_path(c, hm, opts, root, sizeof(root), path, sizeof(path)); - if (!uri_to_local_path(c, hm, opts, root_dir, sizeof(root_dir), path, - sizeof(path), &is_index)) - return; - - exists = stat(path, &st) == 0; -#if MG_ENABLE_SSI - if (is_index && !exists) { - char *p = path + strlen(path); - while (p > path && p[-1] != '/') p--; - strncpy(p, "index.shtml", (size_t)(&path[sizeof(path)] - p - 2)); - path[sizeof(path) - 1] = '\0'; - exists = stat(path, &st) == 0; - } -#endif - if (is_index && !exists) { -#if MG_ENABLE_DIRECTORY_LISTING + if (flags == 0) return; + LOG(LL_DEBUG, ("root [%s], path [%s] %d", root, path, flags)); + if (flags & MG_FS_DIR) { listdir(c, hm, opts, path); -#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), path, strlen(path))) { - mg_http_serve_ssi(c, root_dir, path); -#endif + mg_http_serve_ssi(c, root, path); } else { mg_http_serve_file(c, hm, path, opts); } @@ -3516,6 +3467,12 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root, mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data); free(data); } +#else +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath) { + mg_http_reply(c, 501, NULL, "SSI not enabled"); + (void) root, (void) fullpath; +} #endif #ifdef MG_ENABLE_LINES diff --git a/mongoose.h b/mongoose.h index 24e35ed7..eb4d3607 100644 --- a/mongoose.h +++ b/mongoose.h @@ -38,7 +38,7 @@ extern "C" { #endif #ifndef MG_ENABLE_SSI -#define MG_ENABLE_SSI 0 +#define MG_ENABLE_SSI 1 #endif #ifndef MG_ENABLE_IPV6 @@ -58,8 +58,8 @@ extern "C" { #define MG_ENABLE_WINSOCK 1 #endif -#ifndef MG_ENABLE_DIRECTORY_LISTING -#define MG_ENABLE_DIRECTORY_LISTING 0 +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 #endif #ifndef MG_ENABLE_SOCKETPAIR @@ -68,7 +68,7 @@ extern "C" { // Granularity of the send/recv IO buffer growth #ifndef MG_IO_SIZE -#define MG_IO_SIZE 512 +#define MG_IO_SIZE 2048 #endif // Maximum size of the recv IO buffer @@ -153,6 +153,8 @@ extern "C" { #ifndef MG_PATH_MAX #define MG_PATH_MAX 128 #endif +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 #endif @@ -344,6 +346,8 @@ struct timeval { #define MG_DIRSEP '/' #define MG_INT64_FMT "%" PRId64 +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 #endif @@ -414,17 +418,6 @@ typedef int socklen_t; #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif -#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) -#define fopen(a, b) mg_fopen((a), (b)) - -// Later windows define _stati64 macro, older do not -#ifdef _stati64 -#define stat _stat64 -#define _stat64(a, b) mg_stat((a), (b)) -#else -#define stat _stati64 -#define _stati64(a, b) mg_stat((a), (b)) -#endif #ifndef va_copy #ifdef __va_copy @@ -439,18 +432,8 @@ typedef int socklen_t; #define MG_INT64_FMT "%I64d" -static __inline FILE *mg_fopen(const char *path, const char *mode) { - wchar_t b1[MAX_PATH], b2[10]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); - return _wfopen(b1, b2); -} - -static __inline int mg_stat(const char *path, struct stat *st) { - wchar_t tmp[MAX_PATH]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); - return _wstati64(tmp, st); -} +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 // https://lgtm.com/rules/2154840805/ -gmtime, localtime, ctime and asctime static __inline struct tm *gmtime_r(time_t *t, struct tm *tm) { @@ -611,7 +594,7 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; // Filesystem API functions struct mg_fs { char *(*realpath)(const char *path, char *resolved_path); - int (*stat)(const char *path, size_t *size, unsigned *mtime); + int (*stat)(const char *path, size_t *size, time_t *mtime); void (*list)(const char *path, void (*fn)(const char *, void *), void *); struct mg_fd *(*open)(const char *path, int flags); void (*close)(struct mg_fd *fd); diff --git a/src/arch_esp32.h b/src/arch_esp32.h index c85c5b78..92f5a103 100644 --- a/src/arch_esp32.h +++ b/src/arch_esp32.h @@ -22,5 +22,7 @@ #ifndef MG_PATH_MAX #define MG_PATH_MAX 128 #endif +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 #endif diff --git a/src/arch_unix.h b/src/arch_unix.h index a65892ff..cf76dd39 100644 --- a/src/arch_unix.h +++ b/src/arch_unix.h @@ -32,5 +32,7 @@ #define MG_DIRSEP '/' #define MG_INT64_FMT "%" PRId64 +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 #endif diff --git a/src/arch_win32.h b/src/arch_win32.h index 1926f2ca..a14343f0 100644 --- a/src/arch_win32.h +++ b/src/arch_win32.h @@ -66,17 +66,6 @@ typedef int socklen_t; #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif -#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) -#define fopen(a, b) mg_fopen((a), (b)) - -// Later windows define _stati64 macro, older do not -#ifdef _stati64 -#define stat _stat64 -#define _stat64(a, b) mg_stat((a), (b)) -#else -#define stat _stati64 -#define _stati64(a, b) mg_stat((a), (b)) -#endif #ifndef va_copy #ifdef __va_copy @@ -91,18 +80,8 @@ typedef int socklen_t; #define MG_INT64_FMT "%I64d" -static __inline FILE *mg_fopen(const char *path, const char *mode) { - wchar_t b1[MAX_PATH], b2[10]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); - return _wfopen(b1, b2); -} - -static __inline int mg_stat(const char *path, struct stat *st) { - wchar_t tmp[MAX_PATH]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); - return _wstati64(tmp, st); -} +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 // https://lgtm.com/rules/2154840805/ -gmtime, localtime, ctime and asctime static __inline struct tm *gmtime_r(time_t *t, struct tm *tm) { diff --git a/src/config.h b/src/config.h index 0aeed8e3..fdb418fd 100644 --- a/src/config.h +++ b/src/config.h @@ -13,7 +13,7 @@ #endif #ifndef MG_ENABLE_SSI -#define MG_ENABLE_SSI 0 +#define MG_ENABLE_SSI 1 #endif #ifndef MG_ENABLE_IPV6 @@ -33,8 +33,8 @@ #define MG_ENABLE_WINSOCK 1 #endif -#ifndef MG_ENABLE_DIRECTORY_LISTING -#define MG_ENABLE_DIRECTORY_LISTING 0 +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 #endif #ifndef MG_ENABLE_SOCKETPAIR @@ -43,7 +43,7 @@ // Granularity of the send/recv IO buffer growth #ifndef MG_IO_SIZE -#define MG_IO_SIZE 512 +#define MG_IO_SIZE 2048 #endif // Maximum size of the recv IO buffer diff --git a/src/fs.h b/src/fs.h index c7f862e6..8afb2f56 100644 --- a/src/fs.h +++ b/src/fs.h @@ -7,7 +7,7 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; // Filesystem API functions struct mg_fs { char *(*realpath)(const char *path, char *resolved_path); - int (*stat)(const char *path, size_t *size, unsigned *mtime); + int (*stat)(const char *path, size_t *size, time_t *mtime); void (*list)(const char *path, void (*fn)(const char *, void *), void *); struct mg_fd *(*open)(const char *path, int flags); void (*close)(struct mg_fd *fd); diff --git a/src/fs_packed.c b/src/fs_packed.c index 75add662..638ef45e 100644 --- a/src/fs_packed.c +++ b/src/fs_packed.c @@ -13,15 +13,15 @@ const char *mg_unpack(const char *path, size_t *size) { } static char *packed_realpath(const char *path, char *resolved_path) { - if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + if (resolved_path == NULL) resolved_path = (char *) malloc(strlen(path) + 1); strcpy(resolved_path, path); return resolved_path; } -static int packed_stat(const char *path, size_t *size, unsigned *mtime) { +static int packed_stat(const char *path, size_t *size, time_t *mtime) { const char *data = mg_unpack(path, size); if (mtime) *mtime = 0; - return data == NULL ? 0 : MG_FS_READ; + return data == NULL ? MG_FS_DIR : MG_FS_READ; } static void packed_list(const char *path, void (*fn)(const char *, void *), @@ -36,8 +36,8 @@ static struct mg_fd *packed_open(const char *path, int flags) { struct mg_fd *fd = NULL; if (data == NULL) return NULL; if (flags & MG_FS_WRITE) return NULL; - fp = calloc(1, sizeof(*fp)); - fd = calloc(1, sizeof(*fd)); + fp = (struct packed_file *) calloc(1, sizeof(*fp)); + fd = (struct mg_fd *) calloc(1, sizeof(*fd)); fp->size = size; fp->data = data; fd->fd = fp; diff --git a/src/fs_posix.c b/src/fs_posix.c index d93f2c3b..15e4d2dd 100644 --- a/src/fs_posix.c +++ b/src/fs_posix.c @@ -1,15 +1,20 @@ #include "fs.h" -#if defined(O_READ) +#if defined(FOPEN_MAX) static char *posix_realpath(const char *path, char *resolved_path) { #ifdef _WIN32 - return _fullpath(path, resolved_path, PATH_MAX); + return _fullpath(resolved_path, path, _MAX_PATH); +#elif MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \ + MG_ARCH == MG_ARCH_FREERTOS_TCP + if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + strcpy(resolved_path, path); + return resolved_path; #else return realpath(path, resolved_path); #endif } -static int posix_stat(const char *path, size_t *size, unsigned *mtime) { +static int posix_stat(const char *path, size_t *size, time_t *mtime) { #ifdef _WIN32 struct _stati64 st; wchar_t tmp[PATH_MAX]; @@ -20,26 +25,133 @@ static int posix_stat(const char *path, size_t *size, unsigned *mtime) { if (stat(path, &st) != 0) return 0; #endif if (size) *size = (size_t) st.st_size; - if (mtime) *mtime = (unsigned) st.st_mtime; + if (mtime) *mtime = st.st_mtime; return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); } +#ifdef _WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; + +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +int gettimeofday(struct timeval *tv, void *tz) { + FILETIME ft; + unsigned __int64 tmpres = 0; + + if (tv != NULL) { + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + tmpres /= 10; // convert into microseconds + tmpres -= (int64_t) 11644473600000000; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + (void) tz; + return 0; +} + +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} + +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); + attrs = GetFileAttributesW(wpath); + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; + } else { + free(d); + d = NULL; + } + } + return d; +} + +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + free(d); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} + +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} +#endif + static void posix_list(const char *dir, void (*fn)(const char *, void *), void *userdata) { - // char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; +#if MG_ENABLE_DIRLIST struct dirent *dp; DIR *dirp; - - // while (p > dir && *p != '/') *p-- = '\0'; - if ((dirp = (opendir(dir))) != NULL) { - size_t off, n; - while ((dp = readdir(dirp)) != NULL) { - // Do not show current dir and hidden files - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; - fn(dp->d_name, &st); - } - closedir(dirp); + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif } static struct mg_fd *posix_open(const char *path, int flags) { @@ -58,14 +170,14 @@ static struct mg_fd *posix_open(const char *path, int flags) { fp = (void *) fopen(path, mode); #endif if (fp == NULL) return NULL; - fd = calloc(1, sizeof(*fd)); + fd = (struct mg_fd *) calloc(1, sizeof(*fd)); fd->fd = fp; fd->fs = &mg_fs_posix; return fd; } static void posix_close(struct mg_fd *fd) { - if (fd) fclose((FILE *) fd->fd), free(fd); + if (fd != NULL) fclose((FILE *) fd->fd), free(fd); } static size_t posix_read(void *fp, void *buf, size_t len) { @@ -91,7 +203,7 @@ static char *posix_realpath(const char *path, char *resolved_path) { return NULL; } -static int posix_stat(const char *path, size_t *size, unsigned *mtime) { +static int posix_stat(const char *path, size_t *size, time_t *mtime) { (void) path, (void) size, (void) mtime; return 0; } diff --git a/src/http.c b/src/http.c index 3447f88a..19097fb2 100644 --- a/src/http.c +++ b/src/http.c @@ -366,14 +366,15 @@ void mg_http_reply(struct mg_connection *c, int code, const char *headers, static void http_cb(struct mg_connection *, int, void *, void *); static void restore_http_cb(struct mg_connection *c) { - if (c->pfn_data != NULL) fclose((FILE *) c->pfn_data); + struct mg_fd *fd = (struct mg_fd *) c->pfn_data; + if (fd != NULL) fd->fs->close(fd); c->pfn_data = NULL; c->pfn = http_cb; } -char *mg_http_etag(char *buf, size_t len, struct stat *st) { - snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) st->st_mtime, - (int64_t) st->st_size); +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { + snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) mtime, + (int64_t) size); return buf; } @@ -406,12 +407,13 @@ int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, static void static_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { - FILE *fp = (FILE *) fn_data; + struct mg_fd *fd = (struct mg_fd *) fn_data; // Read to send IO buffer directly, avoid extra on-stack buffer size_t n, max = 2 * MG_IO_SIZE; if (c->send.size < max) mg_iobuf_resize(&c->send, max); if (c->send.len >= c->send.size) return; // Rate limit - n = fread(c->send.buf + c->send.len, 1, c->send.size - c->send.len, fp); + n = fd->fs->read(fd->fd, c->send.buf + c->send.len, + c->send.size - c->send.len); if (n > 0) c->send.len += n; if (c->send.len < c->send.size) restore_http_cb(c); } else if (ev == MG_EV_CLOSE) { @@ -521,23 +523,27 @@ static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, const char *path, struct mg_http_serve_opts *opts) { - struct mg_str *inm = mg_http_get_header(hm, "If-None-Match"); - struct stat st; char etag[64]; - FILE *fp = fopen(path, "rb"); - if (fp == NULL || stat(path, &st) != 0 || - mg_http_etag(etag, sizeof(etag), &st) != etag) { - LOG(LL_DEBUG, ("404 [%.*s] [%s] %p", (int) hm->uri.len, hm->uri.ptr, path, - (void *) fp)); + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_fd *fd = fs->open(path, MG_FS_READ); + size_t size = 0; + time_t mtime = 0; + struct mg_str *inm = NULL; + + if (fd == NULL || fs->stat(path, &size, &mtime) == 0) { + LOG(LL_DEBUG, ("404 [%.*s] %p", (int) hm->uri.len, hm->uri.ptr, fd)); mg_http_reply(c, 404, "", "%s", "Not found\n"); - if (fp != NULL) fclose(fp); - } else if (inm != NULL && mg_vcasecmp(inm, etag) == 0) { - fclose(fp); + fs->close(fd); + // NOTE: mg_http_etag() call should go first! + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && + mg_vcasecmp(inm, etag) == 0) { + fs->close(fd); mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { int n, status = 200; char range[100] = ""; - int64_t r1 = 0, r2 = 0, cl = st.st_size; + int64_t r1 = 0, r2 = 0, cl = (int64_t) size; struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); // Handle Range header @@ -549,22 +555,15 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, status = 416; cl = 0; snprintf(range, sizeof(range), - "Content-Range: bytes */" MG_INT64_FMT "\r\n", - (int64_t) st.st_size); + "Content-Range: bytes */" MG_INT64_FMT "\r\n", (int64_t) size); } else { status = 206; cl = r2 - r1 + 1; snprintf(range, sizeof(range), "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT "/" MG_INT64_FMT "\r\n", - r1, r1 + cl - 1, (int64_t) st.st_size); -#if defined(_FILE_OFFSET_BITS) && \ - (_FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ - _XOPEN_SOURCE >= 600) - fseeko(fp, (off_t) r1, SEEK_SET); -#else - fseek(fp, (long) r1, SEEK_SET); -#endif + r1, r1 + cl - 1, (int64_t) size); + fs->seek(fd->fd, (size_t) r1); } } @@ -574,182 +573,58 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { - fclose(fp); + fs->close(fd); } else { c->pfn = static_cb; - c->pfn_data = fp; + c->pfn_data = fd; } } } -#if MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \ - MG_ARCH == MG_ARCH_FREERTOS_TCP -char *realpath(const char *src, char *dst) { - int len = strlen(src); - 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 - -// 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_TCP && defined(MG_ENABLE_FF) - struct FF_STAT st; - return (ff_stat(path, &st) == 0) && (st.st_mode & FF_IFDIR); -#else - struct stat st; - return stat(path, &st) == 0 && S_ISDIR(st.st_mode); -#endif -} - -#if MG_ENABLE_DIRECTORY_LISTING - -#ifdef _WIN32 -struct dirent { - char d_name[MAX_PATH]; +struct printdirentrydata { + struct mg_connection *c; + struct mg_http_message *hm; + struct mg_http_serve_opts *opts; + const char *dir; }; -typedef struct win32_dir { - HANDLE handle; - WIN32_FIND_DATAW info; - struct dirent result; -} DIR; +static void printdirentry(const char *name, void *userdata) { + struct printdirentrydata *d = (struct printdirentrydata *) userdata; + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; + size_t size = 0; + time_t mtime = 0; + char path[MG_PATH_MAX], sz[64], mod[64]; + int flags, n = 0; -int gettimeofday(struct timeval *tv, void *tz) { - FILETIME ft; - unsigned __int64 tmpres = 0; - - if (tv != NULL) { - GetSystemTimeAsFileTime(&ft); - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - tmpres /= 10; // convert into microseconds - tmpres -= (int64_t) 11644473600000000; - tv->tv_sec = (long) (tmpres / 1000000UL); - tv->tv_usec = (long) (tmpres % 1000000UL); - } - (void) tz; - return 0; -} - -static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { - int ret; - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; - strncpy(buf, path, sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - // Trim trailing slashes. Leave backslash for paths like "X:\" - p = buf + strlen(buf) - 1; - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; - memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); - // Convert back to Unicode. If doubly-converted string does not match the - // original, something is fishy, reject. - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), - NULL, NULL); - if (strcmp(buf, buf2) != 0) { - wbuf[0] = L'\0'; - ret = 0; - } - return ret; -} - -DIR *opendir(const char *name) { - DIR *d = NULL; - wchar_t wpath[MAX_PATH]; - DWORD attrs; - - if (name == NULL) { - SetLastError(ERROR_BAD_ARGUMENTS); - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); + if (snprintf(path, sizeof(path), "%s%c%s", d->dir, MG_DIRSEP, name) < 0) { + LOG(LL_ERROR, ("%s truncated", name)); + } else if ((flags = fs->stat(path, &size, &mtime)) == 0) { + LOG(LL_ERROR, ("%lu stat(%s): %d", d->c->id, path, errno)); } else { - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); - attrs = GetFileAttributesW(wpath); - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { - (void) wcscat(wpath, L"\\*"); - d->handle = FindFirstFileW(wpath, &d->info); - d->result.d_name[0] = '\0'; + const char *slash = flags & MG_FS_DIR ? "/" : ""; + struct tm t; + if (flags & MG_FS_DIR) { + snprintf(sz, sizeof(sz), "%s", "[DIR]"); + } else if (size < 1024) { + snprintf(sz, sizeof(sz), "%d", (int) size); + } else if (size < 0x100000) { + snprintf(sz, sizeof(sz), "%.1fk", (double) size / 1024.0); + } else if (size < 0x40000000) { + snprintf(sz, sizeof(sz), "%.1fM", (double) size / 1048576); } else { - free(d); - d = NULL; + snprintf(sz, sizeof(sz), "%.1fG", (double) size / 1073741824); } + strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&mtime, &t)); + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); + mg_printf(d->c, + " %s%s" + "%s%s\n", + n, path, slash, name, slash, mod, sz); } - return d; -} - -int closedir(DIR *d) { - int result = 0; - if (d != NULL) { - if (d->handle != INVALID_HANDLE_VALUE) - result = FindClose(d->handle) ? 0 : -1; - free(d); - } else { - result = -1; - SetLastError(ERROR_BAD_ARGUMENTS); - } - return result; -} - -struct dirent *readdir(DIR *d) { - struct dirent *result = NULL; - if (d != NULL) { - memset(&d->result, 0, sizeof(d->result)); - if (d->handle != INVALID_HANDLE_VALUE) { - result = &d->result; - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, - sizeof(result->d_name), NULL, NULL); - if (!FindNextFileW(d->handle, &d->info)) { - FindClose(d->handle); - d->handle = INVALID_HANDLE_VALUE; - } - } else { - SetLastError(ERROR_FILE_NOT_FOUND); - } - } else { - SetLastError(ERROR_BAD_ARGUMENTS); - } - return result; -} -#endif - -static void printdirentry(struct mg_connection *c, const char *name, - struct stat *stp) { - char size[64], mod[64], path[MG_PATH_MAX]; - int is_dir = S_ISDIR(stp->st_mode), n = 0; - const char *slash = is_dir ? "/" : ""; - struct tm t; - - if (is_dir) { - snprintf(size, sizeof(size), "%s", "[DIR]"); - } else { - if (stp->st_size < 1024) { - snprintf(size, sizeof(size), "%d", (int) stp->st_size); - } else if (stp->st_size < 0x100000) { - snprintf(size, sizeof(size), "%.1fk", (double) stp->st_size / 1024.0); - } else if (stp->st_size < 0x40000000) { - snprintf(size, sizeof(size), "%.1fM", (double) stp->st_size / 1048576); - } else { - snprintf(size, sizeof(size), "%.1fG", (double) stp->st_size / 1073741824); - } - } - strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&stp->st_mtime, &t)); - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); - mg_printf(c, - " %s%s" - "%s%s\n", - n, path, slash, name, slash, mod, size); } static void listdir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts, char *dir) { - char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; - struct dirent *dp; - DIR *dirp; static const char *sort_js_code = ""; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct printdirentrydata d = {c, hm, opts, dir}; + char tmp[10]; + size_t off, n; - while (p > dir && *p != '/') *p-- = '\0'; - if ((dirp = (opendir(dir))) != NULL) { - size_t off, n; - mg_printf(c, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=utf-8\r\n" - "%s" - "Content-Length: \r\n\r\n", - opts->extra_headers == NULL ? "" : opts->extra_headers); - off = c->send.len; // Start of body - mg_printf(c, - "Index of %.*s%s%s" - "" - "

Index of %.*s

" - "" - "" - "" - "" - "\n", - (int) hm->uri.len, hm->uri.ptr, sort_js_code, sort_js_code2, - (int) hm->uri.len, hm->uri.ptr); + mg_printf(c, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "%s" + "Content-Length: \r\n\r\n", + opts->extra_headers == NULL ? "" : opts->extra_headers); + off = c->send.len; // Start of body + mg_printf(c, + "Index of %.*s%s%s" + "" + "

Index of %.*s

Name" - "ModifiedSize

" + "" + "" + "" + "" + "\n", + (int) hm->uri.len, hm->uri.ptr, sort_js_code, sort_js_code2, + (int) hm->uri.len, hm->uri.ptr); - while ((dp = readdir(dirp)) != NULL) { - struct stat 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; - // 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 (stat(path, &st) != 0) { - LOG(LL_ERROR, ("%lu stat(%s): %d", c->id, path, errno)); - } else { - printdirentry(c, dp->d_name, &st); - } - } - closedir(dirp); - mg_printf(c, - "" - "
Name" + "ModifiedSize


Mongoose v.%s
\n", - MG_VERSION); - n = (size_t) snprintf(tmp, sizeof(tmp), "%lu", - (unsigned long) (c->send.len - off)); - if (n > sizeof(tmp)) n = 0; - memcpy(c->send.buf + off - 10, tmp, n); // Set content length - } else { - mg_http_reply(c, 400, "", "Cannot open dir"); - LOG(LL_ERROR, ("%lu opendir(%s) -> %d", c->id, dir, errno)); - } + fs->list(dir, printdirentry, &d); + mg_printf(c, + "
" + "
Mongoose v.%s
\n", + MG_VERSION); + n = (size_t) snprintf(tmp, sizeof(tmp), "%lu", + (unsigned long) (c->send.len - off)); + if (n > sizeof(tmp)) n = 0; + memcpy(c->send.buf + off - 10, tmp, n); // Set content length } -#endif -static bool uri_to_local_path(struct mg_connection *c, - struct mg_http_message *hm, - struct mg_http_serve_opts *opts, char *root_dir, - size_t rlen, char *path, size_t path_len, - bool *is_index) { - bool success = false; - if (realpath(opts->root_dir, root_dir) == NULL) { +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + struct mg_http_serve_opts *opts, char *root_dir, + size_t rlen, char *path, size_t plen) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + int flags = 0, tmp; + if (fs->realpath(opts->root_dir, root_dir) == NULL) { LOG(LL_ERROR, ("realpath(%s): %d", opts->root_dir, errno)); mg_http_reply(c, 400, "", "Bad web root [%s]\n", opts->root_dir); - } else if (!mg_is_dir(root_dir)) { + } else if (!(fs->stat(root_dir, NULL, NULL) & MG_FS_DIR)) { mg_http_reply(c, 400, "", "Invalid web root [%s]\n", root_dir); } else { // 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 size_t n1 = strlen(root_dir), n2; - *is_index = false; - + // Temporarily append URI to the root_dir: that is the unresolved path mg_url_decode(hm->uri.ptr, hm->uri.len, root_dir + n1, rlen - n1, 0); root_dir[rlen - 1] = '\0'; n2 = strlen(root_dir); while (n2 > 0 && root_dir[n2 - 1] == '/') root_dir[--n2] = 0; - - if (realpath(root_dir, path) == NULL) { - LOG(LL_ERROR, ("realpath(%s): %d", root_dir, errno)); - mg_http_reply(c, 404, "", "Not found [%.*s]\n", (int) hm->uri.len, - hm->uri.ptr); + // Try to resolve it... + if (fs->realpath(root_dir, path) == NULL || + (flags = fs->stat(path, NULL, NULL)) == 0) { + mg_http_reply(c, 404, "", "Not found\n"); } else { - if (mg_is_dir(path)) { - strncat(path, "/index.html", path_len - strlen(path) - 1); - path[path_len - 1] = '\0'; - *is_index = true; - } - - if (strlen(path) < n1 || memcmp(root_dir, path, n1) != 0) { - // Requested file is located outside root directory, fail - mg_http_reply(c, 404, "", "Invalid URI [%.*s]\n", (int) hm->uri.len, - hm->uri.ptr); + // Path is resolved successfully. It it is a directory, try to + // serve index.html in it + root_dir[n1] = '\0'; // Restore root_dir - remove appended URI + n2 = strlen(path); // Memorise path length + if ((flags & MG_FS_DIR) && + ((snprintf(path + n2, plen - n2, "/index.html") > 0 && + (tmp = fs->stat(path, NULL, NULL)) != 0) || + (snprintf(path + n2, plen - n2, "/index.shtml") > 0 && + (tmp = fs->stat(path, NULL, NULL)) != 0))) { + flags = tmp; } else { - root_dir[n1] = '\0'; - success = true; + path[n2] = '\0'; // Remove appended index file name } } + // Check that the resolved file is located inside root directory + if (strlen(path) < n1 || memcmp(root_dir, path, n1) != 0) { + mg_http_reply(c, 404, "", "Invalid URI [%.*s]\n", (int) hm->uri.len, + hm->uri.ptr); + flags = 0; + } } - return success; + return flags; } void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts) { - char root_dir[MG_PATH_MAX], path[sizeof(root_dir)]; - bool is_index = false, exists; - struct stat st; - root_dir[0] = path[0] = '\0'; + char root[MG_PATH_MAX] = "", path[sizeof(root)] = ""; + int flags = uri_to_path(c, hm, opts, root, sizeof(root), path, sizeof(path)); - if (!uri_to_local_path(c, hm, opts, root_dir, sizeof(root_dir), path, - sizeof(path), &is_index)) - return; - - exists = stat(path, &st) == 0; -#if MG_ENABLE_SSI - if (is_index && !exists) { - char *p = path + strlen(path); - while (p > path && p[-1] != '/') p--; - strncpy(p, "index.shtml", (size_t)(&path[sizeof(path)] - p - 2)); - path[sizeof(path) - 1] = '\0'; - exists = stat(path, &st) == 0; - } -#endif - if (is_index && !exists) { -#if MG_ENABLE_DIRECTORY_LISTING + if (flags == 0) return; + LOG(LL_DEBUG, ("root [%s], path [%s] %d", root, path, flags)); + if (flags & MG_FS_DIR) { listdir(c, hm, opts, path); -#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), path, strlen(path))) { - mg_http_serve_ssi(c, root_dir, path); -#endif + mg_http_serve_ssi(c, root, path); } else { mg_http_serve_file(c, hm, path, opts); } diff --git a/src/ssi.c b/src/ssi.c index 7fe3505d..93182829 100644 --- a/src/ssi.c +++ b/src/ssi.c @@ -83,4 +83,10 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root, mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data); free(data); } +#else +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath) { + mg_http_reply(c, 501, NULL, "SSI not enabled"); + (void) root, (void) fullpath; +} #endif diff --git a/test/mongoose_custom.c b/test/mongoose_custom.c index eb564d02..326f9b74 100644 --- a/test/mongoose_custom.c +++ b/test/mongoose_custom.c @@ -11,6 +11,12 @@ int clock_gettime(clockid_t clock_id, struct timespec *tp) { return 0; } +char *realpath(const char *path, char *resolved_path) { + if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + strcpy(resolved_path, path); + return resolved_path; +} + struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { (void) mgr, (void) url, (void) fn, (void) fn_data; diff --git a/test/mongoose_custom.h b/test/mongoose_custom.h index 8176b245..a01c66b1 100644 --- a/test/mongoose_custom.h +++ b/test/mongoose_custom.h @@ -1,7 +1,7 @@ // Required by the test ARM build #pragma once -#define _GNU_SOURCE // For fopencookie() +#define _POSIX_TIMERS #include #include @@ -21,5 +21,3 @@ #define MG_PATH_MAX 100 #undef MG_ENABLE_SOCKET #define MG_ENABLE_SOCKET 0 - -int clock_gettime(clockid_t clock_id, struct timespec *tp); diff --git a/test/unit_test.c b/test/unit_test.c index 316912d2..75f3d93d 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -363,11 +363,6 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { sopts.root_dir = "."; sopts.extra_headers = "A: B\r\nC: D\r\n"; mg_http_serve_dir(c, hm, &sopts); - } else if (mg_http_match_uri(hm, "/packed/#")) { - struct mg_http_serve_opts sopts; - memset(&sopts, 0, sizeof(sopts)); - sopts.root_dir = "."; - mg_http_serve_dir(c, hm, &sopts); } else if (mg_http_match_uri(hm, "/servefile")) { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); @@ -502,11 +497,12 @@ static void test_http_server(void) { ASSERT(cmpbody(buf, "є\n") == 0); { - extern char *mg_http_etag(char *, size_t, struct stat *); - struct stat st; + extern char *mg_http_etag(char *, size_t, size_t, time_t); char etag[100]; - ASSERT(stat("./test/data/a.txt", &st) == 0); - ASSERT(mg_http_etag(etag, sizeof(etag), &st) == etag); + size_t size = 0; + time_t mtime = 0; + ASSERT(mg_fs_posix.stat("./test/data/a.txt", &size, &mtime) != 0); + ASSERT(mg_http_etag(etag, sizeof(etag), size, mtime) == etag); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\nIf-None-Match: %s\n\n", etag) == 304); } @@ -586,6 +582,7 @@ static void test_http_server(void) { // Directory listing fetch(&mgr, buf, url, "GET /test/ HTTP/1.0\n\n"); ASSERT(fetch(&mgr, buf, url, "GET /test/ HTTP/1.0\n\n") == 200); + printf("-------\n%s\n", buf); ASSERT(mg_strstr(mg_str(buf), mg_str(">Index of /test/<")) != NULL); ASSERT(mg_strstr(mg_str(buf), mg_str(">fuzz.c<")) != NULL); @@ -1356,20 +1353,34 @@ static void test_multipart(void) { ASSERT(mg_http_next_multipart(mg_str(s), ofs, &part) == 0); } +static void eh7(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; + struct mg_http_serve_opts sopts; + memset(&sopts, 0, sizeof(sopts)); + sopts.root_dir = "."; + sopts.fs = &mg_fs_packed; + mg_http_serve_dir(c, hm, &sopts); + } + (void) ev_data, (void) fn_data; +} + static void test_packed(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12351"; - char buf[FETCH_BUF_SIZE]; + char buf[FETCH_BUF_SIZE] = ""; mg_mgr_init(&mgr); - mg_http_listen(&mgr, url, eh1, NULL); - ASSERT(fetch(&mgr, buf, url, "GET /packed/ HTTP/1.0\n\n") == 404); + mg_http_listen(&mgr, url, eh7, NULL); + // ASSERT(fetch(&mgr, buf, url, "GET /packed/ HTTP/1.0\n\n") == 404); + // fetch(&mgr, buf, url, "GET / HTTP/1.0\n\n"); + fetch(&mgr, buf, url, "GET /Makefile HTTP/1.0\n\n"); + printf("--------\n%s\n", buf); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } int main(void) { mg_log_set("3"); - test_packed(); test_crc32(); test_multipart(); test_http_chunked(); @@ -1379,7 +1390,6 @@ int main(void) { test_dns(); test_str(); test_timer(); - test_http_range(); test_url(); test_iobuf(); test_commalist(); @@ -1392,6 +1402,8 @@ int main(void) { test_http_client(); test_http_no_content_length(); test_http_pipeline(); + test_http_range(); + test_packed(); test_mqtt(); printf("SUCCESS. Total tests: %d\n", s_num_tests); return EXIT_SUCCESS;