diff --git a/Makefile b/Makefile index 46ad8404..d8cf17ec 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SRCS = $(wildcard src/*.c) HDRS = $(wildcard src/*.h) -DEFS ?= -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES -DMG_ENABLE_HTTP_DEBUG_ENDPOINT=1 -DMG_ENABLE_DIRECTORY_LISTING=1 +DEFS ?= -DMG_MAX_HTTP_HEADERS=5 -DMG_ENABLE_LINES -DMG_ENABLE_HTTP_DEBUG_ENDPOINT=1 -DMG_ENABLE_DIRECTORY_LISTING=1 -DMG_ENABLE_SSI=1 CFLAGS ?= -W -Wall -Werror -Isrc -I. -O0 -g $(DEFS) $(TFLAGS) $(EXTRA) SSL ?= MBEDTLS CDIR ?= $(realpath $(CURDIR)) @@ -99,7 +99,7 @@ mongoose.c: $(SRCS) Makefile (cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/private.h src/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - (cat src/license.h src/version.h ; cat src/arch.h src/arch_*.h src/config.h src/str.h src/log.h src/timer.h src/util.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/tls.h src/ws.h src/sntp.h src/mqtt.h src/dns.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,')> $@ + (cat src/license.h src/version.h ; cat src/arch.h src/arch_*.h src/config.h src/str.h src/log.h src/timer.h src/util.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,,')> $@ clean: EXAMPLE_TARGET = clean clean: ex diff --git a/docs/README.md b/docs/README.md index 0ec21058..e9b66025 100644 --- a/docs/README.md +++ b/docs/README.md @@ -223,6 +223,7 @@ Here is a list of build constants and their default values: |`MG_ENABLE_DIRECTORY_LISTING` | 0 | Enable directory listing for HTTP server | |`MG_ENABLE_HTTP_DEBUG_ENDPOINT` | 0 | Enable `/debug/info` debug URI | |`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_MAX_RECV_BUF_SIZE` | (3 * 1024 * 1024) | Maximum recv buffer size | |`MG_MAX_HTTP_HEADERS` | 40 | Maximum number of HTTP headers | @@ -242,7 +243,8 @@ static const char *s_web_root_dir = "."; static const char *s_listening_address = "http://localhost:8000"; static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { - if (ev == MG_EV_HTTP_MSG) mg_http_serve_dir(c, ev_data, s_web_root_dir); + struct mg_http_serve_opts opts = {.root_dir = s_web_root_dir}; + if (ev == MG_EV_HTTP_MSG) mg_http_serve_dir(c, ev_data, &opts); } int main(int argc, char *argv[]) { @@ -628,11 +630,16 @@ Write a chunk of data in chunked encoding format. ### mg\_serve\_dir() ```c +struct mg_http_serve_opts { + const char *root_dir; // Web root directory, must be non-NULL + const char *ssi_pattern; // SSI filename pattern, e.g. #.shtml +}; void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, - const char *path); + const struct mg_http_serve_opts *opts); ``` -Serve static files using `path` as a root directory. +Serve static files according to the given options. Note that in order to +enable SSI, set a `-DMG_ENABLE_SSI=1` build flag. ### mg\_serve\_file() diff --git a/examples/complete/main.c b/examples/complete/main.c index 93980b48..8ccfbb22 100644 --- a/examples/complete/main.c +++ b/examples/complete/main.c @@ -129,7 +129,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { u->token); mg_http_printf_chunk(c, ""); } else { - mg_http_serve_dir(c, ev_data, "web_root"); + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); } } } @@ -146,7 +147,7 @@ static void broadcast_mjpeg_frame(struct mg_mgr *mgr) { char *data = mg_file_read(path); // Read next file struct mg_connection *c; for (c = mgr->conns; c != NULL; c = c->next) { - if (c->label[0] != 'S') continue; // Skip non-stream connections + if (c->label[0] != 'S') continue; // Skip non-stream connections if (data == NULL || size == 0) continue; // Skip on file read error mg_printf(c, "--foo\r\nContent-Type: image/jpeg\r\n" diff --git a/examples/dashboard/main.c b/examples/dashboard/main.c index 922c7b74..b85ca07e 100644 --- a/examples/dashboard/main.c +++ b/examples/dashboard/main.c @@ -66,7 +66,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { c->label[0] = 'W'; // Mark ourselves as a config watcher mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); } else { - mg_http_serve_dir(c, ev_data, "web_root"); + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); } } } diff --git a/examples/desktop-server/Makefile b/examples/desktop-server/Makefile index 632710f2..baa9b5b5 100644 --- a/examples/desktop-server/Makefile +++ b/examples/desktop-server/Makefile @@ -1,6 +1,6 @@ PROG ?= example ROOT ?= $(realpath $(CURDIR)/../..) -DEFS ?= -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 +DEFS ?= -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 -DMG_ENABLE_SSI=1 CFLAGS ?= -I../.. -W -Wall -DMG_ENABLE_IPV6=1 $(DEFS) $(EXTRA) VCFLAGS = /nologo /W3 /O2 /I../.. $(DEFS) $(EXTRA) VC98 = docker run --rm -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR) docker.io/mdashnet/vc98 diff --git a/examples/desktop-server/main.c b/examples/desktop-server/main.c index 5b4569ca..23da36eb 100644 --- a/examples/desktop-server/main.c +++ b/examples/desktop-server/main.c @@ -8,11 +8,13 @@ static const char *s_web_root_dir = "."; static const char *s_listening_address = "http://localhost:8000"; static const char *s_enable_hexdump = "no"; static const char *s_rewrites = ""; +static const char *s_ssi_pattern = "#.shtml"; 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; - mg_http_serve_dir(c, ev_data, s_web_root_dir); + struct mg_http_serve_opts opts = {.root_dir = s_web_root_dir, + .ssi_pattern = s_ssi_pattern}; + mg_http_serve_dir(c, ev_data, &opts); } (void) fn_data; } @@ -23,11 +25,12 @@ static void usage(const char *prog) { "\nUsage: %s OPTIONS\n" " -D LEVEL - debug level, from 0 to 4, default: '%s'\n" " -H yes|no - enable traffic hexdump, default: '%s'\n" + " -S GLOB - glob pattern for SSI files, default: '%s'\n" " -d DIR - directory to serve, default: '%s'\n" " -l ADDR - listening address, default: '%s'\n" " -r LIST - list of URI=DIR,... URI rewrites, default: '%s'\n", - MG_VERSION, prog, s_debug_level, s_enable_hexdump, s_web_root_dir, - s_listening_address, s_rewrites); + MG_VERSION, prog, s_debug_level, s_enable_hexdump, s_ssi_pattern, + s_web_root_dir, s_listening_address, s_rewrites); exit(EXIT_FAILURE); } @@ -44,6 +47,8 @@ int main(int argc, char *argv[]) { s_debug_level = argv[++i]; } else if (strcmp(argv[i], "-H") == 0) { s_enable_hexdump = argv[++i]; + } else if (strcmp(argv[i], "-S") == 0) { + s_ssi_pattern = argv[++i]; } else if (strcmp(argv[i], "-l") == 0) { s_listening_address = argv[++i]; } else if (strcmp(argv[i], "-r") == 0) { diff --git a/examples/file-upload/main.c b/examples/file-upload/main.c index 21bc456b..38520846 100644 --- a/examples/file-upload/main.c +++ b/examples/file-upload/main.c @@ -12,7 +12,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (mg_http_match_uri(hm, "/upload")) { mg_http_upload(c, hm, "/tmp"); } else { - mg_http_serve_dir(c, ev_data, "web_root"); + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); } } } diff --git a/examples/http-restful-server/main.c b/examples/http-restful-server/main.c index cd5236f9..acd6e6b1 100644 --- a/examples/http-restful-server/main.c +++ b/examples/http-restful-server/main.c @@ -34,7 +34,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len, hm->uri.ptr); } else { - mg_http_serve_dir(c, ev_data, s_web_directory); // Serve static files + struct mg_http_serve_opts opts = {.root_dir = s_web_directory}; + mg_http_serve_dir(c, ev_data, &opts); } } (void) fn_data; diff --git a/examples/live-log/main.c b/examples/live-log/main.c index 17df9310..75679e29 100644 --- a/examples/live-log/main.c +++ b/examples/live-log/main.c @@ -16,7 +16,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { 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"); } else { - mg_http_serve_dir(c, ev_data, "web_root"); + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); } } } diff --git a/examples/multiple-logins/main.c b/examples/multiple-logins/main.c index 469861d0..e6c64171 100644 --- a/examples/multiple-logins/main.c +++ b/examples/multiple-logins/main.c @@ -71,7 +71,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { u->token); mg_http_printf_chunk(c, ""); } else { - mg_http_serve_dir(c, ev_data, "web_root"); + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); } } } diff --git a/examples/video-stream/main.c b/examples/video-stream/main.c index ab092afd..991c7f3b 100644 --- a/examples/video-stream/main.c +++ b/examples/video-stream/main.c @@ -18,7 +18,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 { - mg_http_serve_dir(c, ev_data, "web_root"); + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); } } } @@ -35,7 +36,7 @@ static void broadcast_mjpeg_frame(struct mg_mgr *mgr) { char *data = mg_file_read(path); // Read next file struct mg_connection *c; for (c = mgr->conns; c != NULL; c = c->next) { - if (c->label[0] != 'S') continue; // Skip non-stream connections + if (c->label[0] != 'S') continue; // Skip non-stream connections if (data == NULL || size == 0) continue; // Skip on file read error mg_printf(c, "--foo\r\nContent-Type: image/jpeg\r\n" diff --git a/examples/websocket-server/main.c b/examples/websocket-server/main.c index f9b76751..d9df7cc0 100644 --- a/examples/websocket-server/main.c +++ b/examples/websocket-server/main.c @@ -27,7 +27,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123); } else { // Serve static files - mg_http_serve_dir(c, ev_data, s_web_directory); + struct mg_http_serve_opts opts = {.root_dir = s_web_directory}; + mg_http_serve_dir(c, ev_data, &opts); } } else if (ev == MG_EV_WS_MSG) { // Got websocket frame. Received data is wm->data. Echo it back! diff --git a/mongoose.c b/mongoose.c index 57b5ed2b..a7dc9c10 100644 --- a/mongoose.c +++ b/mongoose.c @@ -422,6 +422,7 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) { + struct http_data { void *old_pfn_data; // Previous pfn_data FILE *fp; // For static file serving @@ -1009,7 +1010,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, #endif void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts) { + struct mg_http_serve_opts *opts) { char path[PATH_MAX + 2], root[sizeof(path) - 2], real[sizeof(path) - 2]; path[0] = root[0] = real[0] = '\0'; if (realpath(opts->root_dir, root) == NULL) @@ -1028,12 +1029,22 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, // LOG(LL_INFO, ("[%s] [%s] [%s] [%s]", dir, root, path, real)); if (mg_is_dir(real)) { strncat(real, "/index.html", sizeof(real) - strlen(real) - 1); + real[sizeof(real) - 1] = '\0'; is_index = true; } if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) { mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr); } else { FILE *fp = fopen(real, "r"); +#if MG_ENABLE_SSI + if (is_index && fp == NULL) { + char *p = real + strlen(real); + while (p > real && p[-1] != '/') p--; + strncpy(p, "index.shtml", &real[sizeof(real)] - p - 2); + real[sizeof(real) - 1] = '\0'; + fp = fopen(real, "r"); + } +#endif #if MG_ENABLE_HTTP_DEBUG_ENDPOINT snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real); #endif @@ -1042,6 +1053,12 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, listdir(c, hm, real); #else mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); +#endif +#if MG_ENABLE_SSI + } else if (opts->ssi_pattern != NULL && + mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), + real, strlen(real))) { + mg_http_serve_ssi(c, root, real); #endif } else { mg_http_serve_file(c, hm, real, guess_content_type(real)); @@ -2984,6 +3001,93 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { } #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/ssi.c" +#endif + + + +#ifndef MG_MAX_SSI_DEPTH +#define MG_MAX_SSI_DEPTH 5 +#endif + +#if MG_ENABLE_SSI +static char *mg_ssi(const char *path, const char *root, int depth) { + struct mg_iobuf b = {NULL, 0, 0}; + FILE *fp = fopen(path, "rb"); + if (fp != NULL) { + char buf[BUFSIZ], arg[sizeof(buf)]; + int ch, intag = 0; + size_t len = 0, align = MG_IO_SIZE; + while ((ch = fgetc(fp)) != EOF) { + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { + buf[len++] = ch & 0xff; + if (sscanf(buf, " + diff --git a/test/data/ssi/nested.shtml b/test/data/ssi/nested.shtml new file mode 100644 index 00000000..898c73b8 --- /dev/null +++ b/test/data/ssi/nested.shtml @@ -0,0 +1,3 @@ +this is nested + + diff --git a/test/data/ssi/recurse.shtml b/test/data/ssi/recurse.shtml new file mode 100644 index 00000000..87e7c3a9 --- /dev/null +++ b/test/data/ssi/recurse.shtml @@ -0,0 +1,2 @@ + +recurse diff --git a/test/unit_test.c b/test/unit_test.c index 7fa27640..4f2168a6 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -349,7 +349,7 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { struct mg_http_serve_opts opts = {".", NULL}; mg_http_serve_dir(c, hm, &opts); } else { - struct mg_http_serve_opts opts = {"./test/data", NULL}; + struct mg_http_serve_opts opts = {"./test/data", "#.shtml"}; mg_http_serve_dir(c, hm, &opts); } } else if (ev == MG_EV_WS_MSG) { @@ -477,6 +477,17 @@ static void test_http_server(void) { "Content-Length: 4\r\n\r\nkuku") == 200); ASSERT(cmpbody(buf, "kuku") == 0); + ASSERT(fetch(&mgr, buf, url, "GET /ssi HTTP/1.1\r\n\r\n") == 200); + ASSERT(cmpbody(buf, + "this is index\n" + "this is nested\n\n" + "this is f1\n\n\n\n" + "recurse\n\n" + "recurse\n\n" + "recurse\n\n" + "recurse\n\n" + "recurse\n\n") == 0); + ASSERT(fetch(&mgr, buf, url, "GET /badroot HTTP/1.0\r\n\n") == 400); #if MG_ARCH == MG_ARCH_WIN32 ASSERT(cmpbody(buf, "Bad web root [Z:\\BAAADDD!]\n") == 0);