mirror of
https://github.com/cesanta/mongoose.git
synced 2024-12-26 22:41:03 +08:00
Implement FS virtualization
This commit is contained in:
parent
9346122c29
commit
c2176f969b
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@ -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
|
||||
|
19
Makefile
19
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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 $< $@
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
585
mongoose.c
585
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,
|
||||
" <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
|
||||
"<td>%s</td><td>%s</td></tr>\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,
|
||||
" <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
|
||||
"<td>%s</td><td>%s</td></tr>\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 =
|
||||
"<script>function srt(tb, sc, so, d) {"
|
||||
"var tr = Array.prototype.slice.call(tb.rows, 0),"
|
||||
@ -1397,140 +1384,104 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
|
||||
"srt(tb, sc, so, true);"
|
||||
"}"
|
||||
"</script>";
|
||||
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,
|
||||
"<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
|
||||
"<style>th,td {text-align: left; padding-right: 1em; "
|
||||
"font-family: monospace; }</style></head>"
|
||||
"<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
|
||||
"<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
|
||||
"<a href=\"#\" rel=\"1\">Modified</a></th>"
|
||||
"<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
|
||||
"<tr><td colspan=\"3\"><hr></td></tr>"
|
||||
"</thead>"
|
||||
"<tbody id=\"tb\">\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,
|
||||
"<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
|
||||
"<style>th,td {text-align: left; padding-right: 1em; "
|
||||
"font-family: monospace; }</style></head>"
|
||||
"<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
|
||||
"<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
|
||||
"<a href=\"#\" rel=\"1\">Modified</a></th>"
|
||||
"<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
|
||||
"<tr><td colspan=\"3\"><hr></td></tr>"
|
||||
"</thead>"
|
||||
"<tbody id=\"tb\">\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,
|
||||
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
||||
"</table><address>Mongoose v.%s</address></body></html>\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,
|
||||
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
||||
"</table><address>Mongoose v.%s</address></body></html>\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
|
||||
|
39
mongoose.h
39
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);
|
||||
|
@ -22,5 +22,7 @@
|
||||
#ifndef MG_PATH_MAX
|
||||
#define MG_PATH_MAX 128
|
||||
#endif
|
||||
#undef MG_ENABLE_DIRLIST
|
||||
#define MG_ENABLE_DIRLIST 1
|
||||
|
||||
#endif
|
||||
|
@ -32,5 +32,7 @@
|
||||
|
||||
#define MG_DIRSEP '/'
|
||||
#define MG_INT64_FMT "%" PRId64
|
||||
#undef MG_ENABLE_DIRLIST
|
||||
#define MG_ENABLE_DIRLIST 1
|
||||
|
||||
#endif
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
2
src/fs.h
2
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);
|
||||
|
@ -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;
|
||||
|
148
src/fs_posix.c
148
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;
|
||||
}
|
||||
|
421
src/http.c
421
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,
|
||||
" <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
|
||||
"<td>%s</td><td>%s</td></tr>\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,
|
||||
" <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
|
||||
"<td>%s</td><td>%s</td></tr>\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 =
|
||||
"<script>function srt(tb, sc, so, d) {"
|
||||
"var tr = Array.prototype.slice.call(tb.rows, 0),"
|
||||
@ -773,140 +648,104 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
|
||||
"srt(tb, sc, so, true);"
|
||||
"}"
|
||||
"</script>";
|
||||
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,
|
||||
"<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
|
||||
"<style>th,td {text-align: left; padding-right: 1em; "
|
||||
"font-family: monospace; }</style></head>"
|
||||
"<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
|
||||
"<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
|
||||
"<a href=\"#\" rel=\"1\">Modified</a></th>"
|
||||
"<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
|
||||
"<tr><td colspan=\"3\"><hr></td></tr>"
|
||||
"</thead>"
|
||||
"<tbody id=\"tb\">\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,
|
||||
"<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
|
||||
"<style>th,td {text-align: left; padding-right: 1em; "
|
||||
"font-family: monospace; }</style></head>"
|
||||
"<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
|
||||
"<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
|
||||
"<a href=\"#\" rel=\"1\">Modified</a></th>"
|
||||
"<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
|
||||
"<tr><td colspan=\"3\"><hr></td></tr>"
|
||||
"</thead>"
|
||||
"<tbody id=\"tb\">\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,
|
||||
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
||||
"</table><address>Mongoose v.%s</address></body></html>\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,
|
||||
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
||||
"</table><address>Mongoose v.%s</address></body></html>\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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Required by the test ARM build
|
||||
#pragma once
|
||||
|
||||
#define _GNU_SOURCE // For fopencookie()
|
||||
#define _POSIX_TIMERS
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user