From ce15bedcde7044b693c952a2861c73b58789c0cd Mon Sep 17 00:00:00 2001 From: Andrew Pamment Date: Wed, 17 Aug 2016 10:55:03 +1000 Subject: [PATCH] More work on WWW Server --- bbs.h | 2 + main.c | 1 + www.c | 223 +++++++++++++++++++++++++++++++++++++++++- www/403.tpl | 2 + www/404.tpl | 2 + www/footer.tpl | 2 + www/header.tpl | 7 ++ www/index.tpl | 2 +- www/mime.types | 1 + www/static/header.png | Bin 0 -> 12217 bytes www/static/style.css | 3 + 11 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 www/403.tpl create mode 100644 www/404.tpl create mode 100644 www/mime.types create mode 100644 www/static/header.png create mode 100644 www/static/style.css diff --git a/bbs.h b/bbs.h index 86471ac..fccaf38 100644 --- a/bbs.h +++ b/bbs.h @@ -200,8 +200,10 @@ extern void lua_push_cfunctions(lua_State *L); extern void load_strings(); extern char *get_string(int offset); +extern void chomp(char *string); #if defined(ENABLE_WWW) +extern void www_init(); extern int www_handler(void * cls, struct MHD_Connection * connection, const char * url, const char * method, const char * version, const char * upload_data, size_t * upload_data_size, void ** ptr); #endif diff --git a/main.c b/main.c index 699cc46..210f7f5 100644 --- a/main.c +++ b/main.c @@ -696,6 +696,7 @@ void server(int port) { #if defined(ENABLE_WWW) if (conf.www_server && conf.www_path != NULL) { + www_init(); www_daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, conf.www_port, NULL, NULL, &www_handler, NULL, MHD_OPTION_END); } #endif diff --git a/www.c b/www.c index c468ba7..7cd2869 100644 --- a/www.c +++ b/www.c @@ -4,10 +4,176 @@ #include #include #include +#include #include "bbs.h" extern struct bbs_config conf; +struct mime_type { + char *ext; + char *mime; +}; + +static struct mime_type **mime_types; +static int mime_types_count; + +void www_init() { + FILE *fptr; + char buffer[4096]; + int i; + + mime_types_count = 0; + + sprintf(buffer, "%s/mime.types", conf.www_path); + + fptr = fopen(buffer, "r"); + if (!fptr) { + return; + } + fgets(buffer, 4096, fptr); + while (!feof(fptr)) { + chomp(buffer); + + for (i=0;imime = strdup(buffer); + mime_types[mime_types_count]->ext = strdup(&buffer[i+1]); + + mime_types_count++; + break; + } + } + + fgets(buffer, 4096, fptr); + } + + fclose(fptr); +} + +char *www_get_mime_type(const char *extension) { + int i; + static char default_mime_type[] = "application/octet-stream"; + + + for (i=0;iext) == 0) { + return mime_types[i]->mime; + } + } + return default_mime_type; +} + +int www_404(char *header, char *footer, struct MHD_Connection * connection) { + char buffer[4096]; + char *page; + struct stat s; + char *whole_page; + struct MHD_Response *response; + int ret; + FILE *fptr; + + snprintf(buffer, 4096, "%s/404.tpl", conf.www_path); + + page = NULL; + + if (stat(buffer, &s) == 0) { + page = (char *)malloc(s.st_size + 1); + if (page == NULL) { + return -1; + } + memset(page, 0, s.st_size + 1); + fptr = fopen(buffer, "r"); + if (fptr) { + fread(page, s.st_size, 1, fptr); + fclose(fptr); + } else { + free(page); + page = NULL; + } + } + + if (page == NULL) { + page = (char *)malloc(16); + if (page == NULL) { + return -1; + } + sprintf(page, "Missing Content"); + } + + whole_page = (char *)malloc(strlen(header) + strlen(page) + strlen(footer) + 1); + + sprintf(whole_page, "%s%s%s", header, page, footer); + + response = MHD_create_response_from_buffer (strlen(whole_page), (void*)whole_page, MHD_RESPMEM_PERSISTENT); + + ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response); + MHD_destroy_response (response); + free(whole_page); + free(page); + + return 0; +} + +int www_403(char *header, char *footer, struct MHD_Connection * connection) { + char buffer[4096]; + char *page; + struct stat s; + char *whole_page; + struct MHD_Response *response; + int ret; + FILE *fptr; + + snprintf(buffer, 4096, "%s/403.tpl", conf.www_path); + + page = NULL; + + if (stat(buffer, &s) == 0) { + page = (char *)malloc(s.st_size + 1); + if (page == NULL) { + return -1; + } + memset(page, 0, s.st_size + 1); + fptr = fopen(buffer, "r"); + if (fptr) { + fread(page, s.st_size, 1, fptr); + fclose(fptr); + } else { + free(page); + page = NULL; + } + } + + if (page == NULL) { + page = (char *)malloc(16); + if (page == NULL) { + return -1; + } + sprintf(page, "Missing Content"); + } + + whole_page = (char *)malloc(strlen(header) + strlen(page) + strlen(footer) + 1); + + sprintf(whole_page, "%s%s%s", header, page, footer); + + response = MHD_create_response_from_buffer (strlen(whole_page), (void*)whole_page, MHD_RESPMEM_PERSISTENT); + + ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response); + MHD_destroy_response (response); + free(whole_page); + free(page); + + return 0; +} + int www_handler(void * cls, struct MHD_Connection * connection, const char * url, const char * method, const char * version, const char * upload_data, size_t * upload_data_size, void ** ptr) { struct MHD_Response *response; @@ -19,6 +185,9 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url char *footer; char *whole_page; FILE *fptr; + char *mime; + int i; + int fno; snprintf(buffer, 4096, "%s/header.tpl", conf.www_path); @@ -29,6 +198,7 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url if (header == NULL) { return MHD_NO; } + memset(header, 0, s.st_size + 1); fptr = fopen(buffer, "r"); if (fptr) { fread(header, s.st_size, 1, fptr); @@ -57,6 +227,7 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url free(header); return MHD_NO; } + memset(footer, 0, s.st_size + 1); fptr = fopen(buffer, "r"); if (fptr) { fread(footer, s.st_size, 1, fptr); @@ -90,6 +261,7 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url free(footer); return MHD_NO; } + memset(page, 0, s.st_size + 1); fptr = fopen(buffer, "r"); if (fptr) { fread(page, s.st_size, 1, fptr); @@ -115,12 +287,59 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url sprintf(whole_page, "%s%s%s", header, page, footer); } else if (strncasecmp(url, "/static/", 8) == 0) { // sanatize path - + if (strstr(url, "/..") != NULL) { + return MHD_NO; + } + // get mimetype + for (i=strlen(url);i>0;--i) { + if (url[i] == '.') { + mime = www_get_mime_type(&url[i+1]); + break; + } + } // load file + + sprintf(buffer, "%s%s", conf.www_path, url); + if (stat(buffer, &s) == 0 && S_ISREG(s.st_mode)) { + fno = open(buffer, O_RDONLY); + if (fno != -1) { + response = MHD_create_response_from_fd(s.st_size, fno); + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mime); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + free(header); + free(footer); + return MHD_YES; + } else { + if (www_403(header, footer, connection) != 0) { + free(header); + free(footer); + return MHD_NO; + } + free(header); + free(footer); + return MHD_YES; + } + } else { + if (www_404(header, footer, connection) != 0) { + free(header); + free(footer); + return MHD_NO; + } + free(header); + free(footer); + return MHD_YES; + } + } else { + if (www_404(header, footer, connection) != 0) { + free(header); + free(footer); + return MHD_NO; + } free(header); free(footer); - return MHD_NO; + return MHD_YES; } } else if (strcmp(method, "POST") == 0) { free(header); diff --git a/www/403.tpl b/www/403.tpl new file mode 100644 index 0000000..9bd457e --- /dev/null +++ b/www/403.tpl @@ -0,0 +1,2 @@ +

403 - Forbidden

+The page you are looking for can not be accessed. diff --git a/www/404.tpl b/www/404.tpl new file mode 100644 index 0000000..0c731a1 --- /dev/null +++ b/www/404.tpl @@ -0,0 +1,2 @@ +

404 - Page not found

+The page you are looking for can not be found. diff --git a/www/footer.tpl b/www/footer.tpl index e04310f..45d0706 100644 --- a/www/footer.tpl +++ b/www/footer.tpl @@ -1,2 +1,4 @@ +
+ diff --git a/www/header.tpl b/www/header.tpl index ef464c5..ec7efc2 100644 --- a/www/header.tpl +++ b/www/header.tpl @@ -1,4 +1,11 @@ + Magicka BBS + +
+ Magicka BBS +
+
+ diff --git a/www/index.tpl b/www/index.tpl index 6128ddd..0de5d06 100644 --- a/www/index.tpl +++ b/www/index.tpl @@ -1,3 +1,3 @@

Welcome to another Magicka BBS!

-The sysop should customize this file with what he wants on the front page! +The sysop should customize this file with what he/she wants on the front page! diff --git a/www/mime.types b/www/mime.types new file mode 100644 index 0000000..b46d302 --- /dev/null +++ b/www/mime.types @@ -0,0 +1 @@ +image/png png diff --git a/www/static/header.png b/www/static/header.png new file mode 100644 index 0000000000000000000000000000000000000000..64ccadf04806d22ebfb3f7f2183116bbb4e2dd8f GIT binary patch literal 12217 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