Add SSI include file=, include virtual=

This commit is contained in:
cpq 2021-01-02 17:57:51 +00:00
parent 86824795a0
commit c7e09bd7b4
24 changed files with 289 additions and 29 deletions

View File

@ -1,6 +1,6 @@
SRCS = $(wildcard src/*.c) SRCS = $(wildcard src/*.c)
HDRS = $(wildcard src/*.h) 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) CFLAGS ?= -W -Wall -Werror -Isrc -I. -O0 -g $(DEFS) $(TFLAGS) $(EXTRA)
SSL ?= MBEDTLS SSL ?= MBEDTLS
CDIR ?= $(realpath $(CURDIR)) 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))> $@ (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 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: EXAMPLE_TARGET = clean
clean: ex clean: ex

View File

@ -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_DIRECTORY_LISTING` | 0 | Enable directory listing for HTTP server |
|`MG_ENABLE_HTTP_DEBUG_ENDPOINT` | 0 | Enable `/debug/info` debug URI | |`MG_ENABLE_HTTP_DEBUG_ENDPOINT` | 0 | Enable `/debug/info` debug URI |
|`MG_ENABLE_SOCKETPAIR` | 0 | Enable `mg_socketpair()` for multi-threading | |`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_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_RECV_BUF_SIZE` | (3 * 1024 * 1024) | Maximum recv buffer size |
|`MG_MAX_HTTP_HEADERS` | 40 | Maximum number of HTTP headers | |`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 const char *s_listening_address = "http://localhost:8000";
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { 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[]) { int main(int argc, char *argv[]) {
@ -628,11 +630,16 @@ Write a chunk of data in chunked encoding format.
### mg\_serve\_dir() ### mg\_serve\_dir()
```c ```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, 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() ### mg\_serve\_file()

View File

@ -129,7 +129,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
u->token); u->token);
mg_http_printf_chunk(c, ""); mg_http_printf_chunk(c, "");
} else { } 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 char *data = mg_file_read(path); // Read next file
struct mg_connection *c; struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) { 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 if (data == NULL || size == 0) continue; // Skip on file read error
mg_printf(c, mg_printf(c,
"--foo\r\nContent-Type: image/jpeg\r\n" "--foo\r\nContent-Type: image/jpeg\r\n"

View File

@ -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 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"); mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else { } 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);
} }
} }
} }

View File

@ -1,6 +1,6 @@
PROG ?= example PROG ?= example
ROOT ?= $(realpath $(CURDIR)/../..) 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) CFLAGS ?= -I../.. -W -Wall -DMG_ENABLE_IPV6=1 $(DEFS) $(EXTRA)
VCFLAGS = /nologo /W3 /O2 /I../.. $(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 VC98 = docker run --rm -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR) docker.io/mdashnet/vc98

View File

@ -8,11 +8,13 @@ static const char *s_web_root_dir = ".";
static const char *s_listening_address = "http://localhost:8000"; static const char *s_listening_address = "http://localhost:8000";
static const char *s_enable_hexdump = "no"; static const char *s_enable_hexdump = "no";
static const char *s_rewrites = ""; 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) { static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) { if (ev == MG_EV_HTTP_MSG) {
// struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_serve_opts opts = {.root_dir = s_web_root_dir,
mg_http_serve_dir(c, ev_data, s_web_root_dir); .ssi_pattern = s_ssi_pattern};
mg_http_serve_dir(c, ev_data, &opts);
} }
(void) fn_data; (void) fn_data;
} }
@ -23,11 +25,12 @@ static void usage(const char *prog) {
"\nUsage: %s OPTIONS\n" "\nUsage: %s OPTIONS\n"
" -D LEVEL - debug level, from 0 to 4, default: '%s'\n" " -D LEVEL - debug level, from 0 to 4, default: '%s'\n"
" -H yes|no - enable traffic hexdump, 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" " -d DIR - directory to serve, default: '%s'\n"
" -l ADDR - listening address, default: '%s'\n" " -l ADDR - listening address, default: '%s'\n"
" -r LIST - list of URI=DIR,... URI rewrites, 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, MG_VERSION, prog, s_debug_level, s_enable_hexdump, s_ssi_pattern,
s_listening_address, s_rewrites); s_web_root_dir, s_listening_address, s_rewrites);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -44,6 +47,8 @@ int main(int argc, char *argv[]) {
s_debug_level = argv[++i]; s_debug_level = argv[++i];
} else if (strcmp(argv[i], "-H") == 0) { } else if (strcmp(argv[i], "-H") == 0) {
s_enable_hexdump = argv[++i]; s_enable_hexdump = argv[++i];
} else if (strcmp(argv[i], "-S") == 0) {
s_ssi_pattern = argv[++i];
} else if (strcmp(argv[i], "-l") == 0) { } else if (strcmp(argv[i], "-l") == 0) {
s_listening_address = argv[++i]; s_listening_address = argv[++i];
} else if (strcmp(argv[i], "-r") == 0) { } else if (strcmp(argv[i], "-r") == 0) {

View File

@ -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")) { if (mg_http_match_uri(hm, "/upload")) {
mg_http_upload(c, hm, "/tmp"); mg_http_upload(c, hm, "/tmp");
} else { } 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);
} }
} }
} }

View File

@ -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, mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len,
hm->uri.ptr); hm->uri.ptr);
} else { } 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; (void) fn_data;

View File

@ -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 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"); mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else { } 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);
} }
} }
} }

View File

@ -71,7 +71,8 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
u->token); u->token);
mg_http_printf_chunk(c, ""); mg_http_printf_chunk(c, "");
} else { } 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);
} }
} }
} }

View File

@ -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" "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"); "Content-Type: multipart/x-mixed-replace; boundary=--foo\r\n\r\n");
} else { } 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 char *data = mg_file_read(path); // Read next file
struct mg_connection *c; struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) { 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 if (data == NULL || size == 0) continue; // Skip on file read error
mg_printf(c, mg_printf(c,
"--foo\r\nContent-Type: image/jpeg\r\n" "--foo\r\nContent-Type: image/jpeg\r\n"

View File

@ -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); mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123);
} else { } else {
// Serve static files // 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) { } else if (ev == MG_EV_WS_MSG) {
// Got websocket frame. Received data is wm->data. Echo it back! // Got websocket frame. Received data is wm->data. Echo it back!

View File

@ -422,6 +422,7 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) {
struct http_data { struct http_data {
void *old_pfn_data; // Previous pfn_data void *old_pfn_data; // Previous pfn_data
FILE *fp; // For static file serving FILE *fp; // For static file serving
@ -1009,7 +1010,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
#endif #endif
void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, 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]; char path[PATH_MAX + 2], root[sizeof(path) - 2], real[sizeof(path) - 2];
path[0] = root[0] = real[0] = '\0'; path[0] = root[0] = real[0] = '\0';
if (realpath(opts->root_dir, root) == NULL) 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)); // LOG(LL_INFO, ("[%s] [%s] [%s] [%s]", dir, root, path, real));
if (mg_is_dir(real)) { if (mg_is_dir(real)) {
strncat(real, "/index.html", sizeof(real) - strlen(real) - 1); strncat(real, "/index.html", sizeof(real) - strlen(real) - 1);
real[sizeof(real) - 1] = '\0';
is_index = true; is_index = true;
} }
if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) { 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); mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr);
} else { } else {
FILE *fp = fopen(real, "r"); 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 #if MG_ENABLE_HTTP_DEBUG_ENDPOINT
snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real); snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real);
#endif #endif
@ -1042,6 +1053,12 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
listdir(c, hm, real); listdir(c, hm, real);
#else #else
mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); 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 #endif
} else { } else {
mg_http_serve_file(c, hm, real, guess_content_type(real)); 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 #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, "<!--#include file=\"%[^\"]", arg)) {
char tmp[PATH_MAX], *p = (char *) path + strlen(path), *data;
while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
if (depth < MG_MAX_SSI_DEPTH &&
(data = mg_ssi(tmp, root, depth + 1)) != NULL) {
mg_iobuf_append(&b, data, strlen(data), align);
free(data);
} else {
LOG(LL_ERROR, ("%s: file=%s error or too deep", path, arg));
}
} else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
char tmp[PATH_MAX], *data;
snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
if (depth < MG_MAX_SSI_DEPTH &&
(data = mg_ssi(tmp, root, depth + 1)) != NULL) {
mg_iobuf_append(&b, data, strlen(data), align);
free(data);
} else {
LOG(LL_ERROR, ("%s: virtual=%s error or too deep", path, arg));
}
} else {
// Unknown SSI tag
LOG(LL_INFO, ("Unknown SSI tag: %.*s", (int) len, buf));
mg_iobuf_append(&b, buf, len, align);
}
intag = 0;
len = 0;
} else if (ch == '<') {
intag = 1;
if (len > 0) mg_iobuf_append(&b, buf, len, align);
len = 0;
buf[len++] = ch & 0xff;
} else if (intag) {
if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
intag = 0;
} else if (len >= sizeof(buf) - 2) {
LOG(LL_ERROR, ("%s: SSI tag is too large", path));
len = 0;
}
buf[len++] = ch & 0xff;
} else {
buf[len++] = ch & 0xff;
if (len >= sizeof(buf)) {
mg_iobuf_append(&b, buf, len, align);
len = 0;
}
}
}
if (len > 0) mg_iobuf_append(&b, buf, len, align);
if (b.len > 0) mg_iobuf_append(&b, "", 1, align); // nul-terminate
fclose(fp);
}
(void) depth;
(void) root;
return (char *) b.buf;
}
void mg_http_serve_ssi(struct mg_connection *c, const char *root,
const char *fullpath) {
char *data = mg_ssi(fullpath, root, 0);
mg_http_reply(c, 200, "", "%s", data == NULL ? "" : data);
free(data);
}
#endif
#ifdef MG_ENABLE_LINES #ifdef MG_ENABLE_LINES
#line 1 "src/str.c" #line 1 "src/str.c"
#endif #endif

View File

@ -296,6 +296,10 @@ typedef int socklen_t;
#define MG_ENABLE_FS 1 #define MG_ENABLE_FS 1
#endif #endif
#ifndef MG_ENABLE_SSI
#define MG_ENABLE_SSI 0
#endif
#ifndef MG_ENABLE_IPV6 #ifndef MG_ENABLE_IPV6
#define MG_ENABLE_IPV6 0 #define MG_ENABLE_IPV6 0
#endif #endif
@ -667,8 +671,8 @@ struct mg_http_message {
// Parameter for mg_http_serve_dir() // Parameter for mg_http_serve_dir()
struct mg_http_serve_opts { struct mg_http_serve_opts {
const char *root_dir; const char *root_dir; // Web root directory, must be non-NULL
const char *ssi_pattern; const char *ssi_pattern; // SSI filename pattern, e.g. #.shtml
}; };
int mg_http_parse(const char *s, size_t len, struct mg_http_message *); int mg_http_parse(const char *s, size_t len, struct mg_http_message *);
@ -680,7 +684,7 @@ struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url,
struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url,
mg_event_handler_t fn, void *fn_data); mg_event_handler_t fn, void *fn_data);
void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm,
const struct mg_http_serve_opts *); struct mg_http_serve_opts *);
void mg_http_serve_file(struct mg_connection *, struct mg_http_message *, void mg_http_serve_file(struct mg_connection *, struct mg_http_message *,
const char *, const char *mime); const char *, const char *mime);
void mg_http_reply(struct mg_connection *, int status_code, const char *headers, void mg_http_reply(struct mg_connection *, int status_code, const char *headers,
@ -696,6 +700,10 @@ int mg_http_upload(struct mg_connection *, struct mg_http_message *hm,
void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
void mg_http_serve_ssi(struct mg_connection *c, const char *root,
const char *fullpath);
struct mg_tls_opts { struct mg_tls_opts {
const char *ca; // CA certificate file. For both listeners and clients const char *ca; // CA certificate file. For both listeners and clients

View File

@ -22,6 +22,10 @@
#define MG_ENABLE_FS 1 #define MG_ENABLE_FS 1
#endif #endif
#ifndef MG_ENABLE_SSI
#define MG_ENABLE_SSI 0
#endif
#ifndef MG_ENABLE_IPV6 #ifndef MG_ENABLE_IPV6
#define MG_ENABLE_IPV6 0 #define MG_ENABLE_IPV6 0
#endif #endif

View File

@ -4,6 +4,7 @@
#include "log.h" #include "log.h"
#include "net.h" #include "net.h"
#include "private.h" #include "private.h"
#include "ssi.h"
#include "util.h" #include "util.h"
#include "version.h" #include "version.h"
#include "ws.h" #include "ws.h"
@ -595,7 +596,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
#endif #endif
void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, 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]; char path[PATH_MAX + 2], root[sizeof(path) - 2], real[sizeof(path) - 2];
path[0] = root[0] = real[0] = '\0'; path[0] = root[0] = real[0] = '\0';
if (realpath(opts->root_dir, root) == NULL) if (realpath(opts->root_dir, root) == NULL)
@ -614,12 +615,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)); // LOG(LL_INFO, ("[%s] [%s] [%s] [%s]", dir, root, path, real));
if (mg_is_dir(real)) { if (mg_is_dir(real)) {
strncat(real, "/index.html", sizeof(real) - strlen(real) - 1); strncat(real, "/index.html", sizeof(real) - strlen(real) - 1);
real[sizeof(real) - 1] = '\0';
is_index = true; is_index = true;
} }
if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) { 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); mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr);
} else { } else {
FILE *fp = fopen(real, "r"); 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 #if MG_ENABLE_HTTP_DEBUG_ENDPOINT
snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real); snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real);
#endif #endif
@ -628,6 +639,12 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
listdir(c, hm, real); listdir(c, hm, real);
#else #else
mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); 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 #endif
} else { } else {
mg_http_serve_file(c, hm, real, guess_content_type(real)); mg_http_serve_file(c, hm, real, guess_content_type(real));

View File

@ -21,8 +21,8 @@ struct mg_http_message {
// Parameter for mg_http_serve_dir() // Parameter for mg_http_serve_dir()
struct mg_http_serve_opts { struct mg_http_serve_opts {
const char *root_dir; const char *root_dir; // Web root directory, must be non-NULL
const char *ssi_pattern; const char *ssi_pattern; // SSI filename pattern, e.g. #.shtml
}; };
int mg_http_parse(const char *s, size_t len, struct mg_http_message *); int mg_http_parse(const char *s, size_t len, struct mg_http_message *);
@ -34,7 +34,7 @@ struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url,
struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url,
mg_event_handler_t fn, void *fn_data); mg_event_handler_t fn, void *fn_data);
void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm,
const struct mg_http_serve_opts *); struct mg_http_serve_opts *);
void mg_http_serve_file(struct mg_connection *, struct mg_http_message *, void mg_http_serve_file(struct mg_connection *, struct mg_http_message *,
const char *, const char *mime); const char *, const char *mime);
void mg_http_reply(struct mg_connection *, int status_code, const char *headers, void mg_http_reply(struct mg_connection *, int status_code, const char *headers,

83
src/ssi.c Normal file
View File

@ -0,0 +1,83 @@
#include "log.h"
#include "ssi.h"
#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, "<!--#include file=\"%[^\"]", arg)) {
char tmp[PATH_MAX], *p = (char *) path + strlen(path), *data;
while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
if (depth < MG_MAX_SSI_DEPTH &&
(data = mg_ssi(tmp, root, depth + 1)) != NULL) {
mg_iobuf_append(&b, data, strlen(data), align);
free(data);
} else {
LOG(LL_ERROR, ("%s: file=%s error or too deep", path, arg));
}
} else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
char tmp[PATH_MAX], *data;
snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
if (depth < MG_MAX_SSI_DEPTH &&
(data = mg_ssi(tmp, root, depth + 1)) != NULL) {
mg_iobuf_append(&b, data, strlen(data), align);
free(data);
} else {
LOG(LL_ERROR, ("%s: virtual=%s error or too deep", path, arg));
}
} else {
// Unknown SSI tag
LOG(LL_INFO, ("Unknown SSI tag: %.*s", (int) len, buf));
mg_iobuf_append(&b, buf, len, align);
}
intag = 0;
len = 0;
} else if (ch == '<') {
intag = 1;
if (len > 0) mg_iobuf_append(&b, buf, len, align);
len = 0;
buf[len++] = ch & 0xff;
} else if (intag) {
if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
intag = 0;
} else if (len >= sizeof(buf) - 2) {
LOG(LL_ERROR, ("%s: SSI tag is too large", path));
len = 0;
}
buf[len++] = ch & 0xff;
} else {
buf[len++] = ch & 0xff;
if (len >= sizeof(buf)) {
mg_iobuf_append(&b, buf, len, align);
len = 0;
}
}
}
if (len > 0) mg_iobuf_append(&b, buf, len, align);
if (b.len > 0) mg_iobuf_append(&b, "", 1, align); // nul-terminate
fclose(fp);
}
(void) depth;
(void) root;
return (char *) b.buf;
}
void mg_http_serve_ssi(struct mg_connection *c, const char *root,
const char *fullpath) {
char *data = mg_ssi(fullpath, root, 0);
mg_http_reply(c, 200, "", "%s", data == NULL ? "" : data);
free(data);
}
#endif

4
src/ssi.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "http.h"
void mg_http_serve_ssi(struct mg_connection *c, const char *root,
const char *fullpath);

1
test/data/ssi/f1.txt Normal file
View File

@ -0,0 +1 @@
this is f1

View File

@ -0,0 +1,3 @@
this is index
<!--#include file="nested.shtml" -->
<!--#include file="recurse.shtml" -->

View File

@ -0,0 +1,3 @@
this is nested
<!--#include virtual="/notexist.txt"-->
<!--#include virtual="/ssi/f1.txt"-->

View File

@ -0,0 +1,2 @@
<!--#include file="recurse.shtml"-->
recurse

View File

@ -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}; struct mg_http_serve_opts opts = {".", NULL};
mg_http_serve_dir(c, hm, &opts); mg_http_serve_dir(c, hm, &opts);
} else { } 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); mg_http_serve_dir(c, hm, &opts);
} }
} else if (ev == MG_EV_WS_MSG) { } 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); "Content-Length: 4\r\n\r\nkuku") == 200);
ASSERT(cmpbody(buf, "kuku") == 0); 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); ASSERT(fetch(&mgr, buf, url, "GET /badroot HTTP/1.0\r\n\n") == 400);
#if MG_ARCH == MG_ARCH_WIN32 #if MG_ARCH == MG_ARCH_WIN32
ASSERT(cmpbody(buf, "Bad web root [Z:\\BAAADDD!]\n") == 0); ASSERT(cmpbody(buf, "Bad web root [Z:\\BAAADDD!]\n") == 0);