Implement FS virtualization

This commit is contained in:
Sergey Lyubka 2021-07-29 14:21:20 +01:00
parent 9346122c29
commit c2176f969b
28 changed files with 659 additions and 732 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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 $< $@

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -22,5 +22,7 @@
#ifndef MG_PATH_MAX
#define MG_PATH_MAX 128
#endif
#undef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 1
#endif

View File

@ -32,5 +32,7 @@
#define MG_DIRSEP '/'
#define MG_INT64_FMT "%" PRId64
#undef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 1
#endif

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;