From 34ef45478e645598ea17c69b2f760f16a3e65ff9 Mon Sep 17 00:00:00 2001 From: Sam Stevens Date: Sun, 3 Aug 2014 16:18:18 +0100 Subject: [PATCH] Server is basically working. Needs a re-design. --- Makefile | 1 + content/dirindex.html | 24 +++ nbproject/Makefile-Debug.mk | 10 +- nbproject/Makefile-Release.mk | 48 ++--- nbproject/configurations.xml | 41 +++-- nbproject/private/configurations.xml | 2 +- nbproject/private/private.xml | 8 +- src/config.c | 51 +++++- src/config.h | 3 + src/http-server.c | 262 +++++++++++++++++++++++---- src/http-server.h | 21 ++- src/http.h | 2 +- src/main.c | 180 ++++-------------- src/main.h | 16 +- src/mime.c | 11 +- src/socket.c | 47 ++--- src/util.c | 168 +++++++++++++++++ src/util.h | 39 ++++ testreq.txt | 2 +- 19 files changed, 671 insertions(+), 265 deletions(-) create mode 100644 content/dirindex.html create mode 100644 src/util.c create mode 100644 src/util.h diff --git a/Makefile b/Makefile index 812f445..15784b5 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,7 @@ build: .build-post # clean clean: .clean-post + -rm --recursive ${CND_ARTIFACT_DIR_${CONF}}/ .clean-pre: # Add your pre 'clean' code here... diff --git a/content/dirindex.html b/content/dirindex.html new file mode 100644 index 0000000..7859534 --- /dev/null +++ b/content/dirindex.html @@ -0,0 +1,24 @@ + + + + {{dirname}} + + + + +

{{dirname}}

+ + + + + + + + + + {{index}} + +
NameFilesizeLast modified
+ + + diff --git a/nbproject/Makefile-Debug.mk b/nbproject/Makefile-Debug.mk index 581ae77..1d674ab 100644 --- a/nbproject/Makefile-Debug.mk +++ b/nbproject/Makefile-Debug.mk @@ -43,11 +43,12 @@ OBJECTFILES= \ ${OBJECTDIR}/src/http.o \ ${OBJECTDIR}/src/main.o \ ${OBJECTDIR}/src/mime.o \ - ${OBJECTDIR}/src/socket.o + ${OBJECTDIR}/src/socket.o \ + ${OBJECTDIR}/src/util.o # C Compiler Flags -CFLAGS=-O0 +CFLAGS=-m64 -O0 -march=native # CC Compiler Flags CCFLAGS= @@ -115,6 +116,11 @@ ${OBJECTDIR}/src/socket.o: nbproject/Makefile-${CND_CONF}.mk src/socket.c ${RM} "$@.d" $(COMPILE.c) -g -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/socket.o src/socket.c +${OBJECTDIR}/src/util.o: nbproject/Makefile-${CND_CONF}.mk src/util.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/util.o src/util.c + # Subprojects .build-subprojects: diff --git a/nbproject/Makefile-Release.mk b/nbproject/Makefile-Release.mk index 567d3b9..4a7c552 100644 --- a/nbproject/Makefile-Release.mk +++ b/nbproject/Makefile-Release.mk @@ -43,11 +43,12 @@ OBJECTFILES= \ ${OBJECTDIR}/src/http.o \ ${OBJECTDIR}/src/main.o \ ${OBJECTDIR}/src/mime.o \ - ${OBJECTDIR}/src/socket.o + ${OBJECTDIR}/src/socket.o \ + ${OBJECTDIR}/src/util.o # C Compiler Flags -CFLAGS= +CFLAGS=-m64 -march=native # CC Compiler Flags CCFLAGS= @@ -60,7 +61,7 @@ FFLAGS= ASFLAGS= # Link Libraries and Options -LDLIBSOPTIONS= +LDLIBSOPTIONS=-lmagic # Build Targets .build-conf: ${BUILD_SUBPROJECTS} @@ -70,50 +71,55 @@ ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/khttp: ${OBJECTFILES} ${MKDIR} -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM} ${LINK.c} -o ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/khttp ${OBJECTFILES} ${LDLIBSOPTIONS} -${OBJECTDIR}/lib/http_parser.o: lib/http_parser.c +${OBJECTDIR}/lib/http_parser.o: nbproject/Makefile-${CND_CONF}.mk lib/http_parser.c ${MKDIR} -p ${OBJECTDIR}/lib ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/http_parser.o lib/http_parser.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/http_parser.o lib/http_parser.c -${OBJECTDIR}/lib/ini.o: lib/ini.c +${OBJECTDIR}/lib/ini.o: nbproject/Makefile-${CND_CONF}.mk lib/ini.c ${MKDIR} -p ${OBJECTDIR}/lib ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/ini.o lib/ini.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/ini.o lib/ini.c -${OBJECTDIR}/src/config.o: src/config.c +${OBJECTDIR}/src/config.o: nbproject/Makefile-${CND_CONF}.mk src/config.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/config.o src/config.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/config.o src/config.c -${OBJECTDIR}/src/http-reader.o: src/http-reader.c +${OBJECTDIR}/src/http-reader.o: nbproject/Makefile-${CND_CONF}.mk src/http-reader.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-reader.o src/http-reader.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-reader.o src/http-reader.c -${OBJECTDIR}/src/http-server.o: src/http-server.c +${OBJECTDIR}/src/http-server.o: nbproject/Makefile-${CND_CONF}.mk src/http-server.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-server.o src/http-server.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-server.o src/http-server.c -${OBJECTDIR}/src/http.o: src/http.c +${OBJECTDIR}/src/http.o: nbproject/Makefile-${CND_CONF}.mk src/http.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http.o src/http.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http.o src/http.c -${OBJECTDIR}/src/main.o: src/main.c +${OBJECTDIR}/src/main.o: nbproject/Makefile-${CND_CONF}.mk src/main.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c -${OBJECTDIR}/src/mime.o: src/mime.c +${OBJECTDIR}/src/mime.o: nbproject/Makefile-${CND_CONF}.mk src/mime.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/mime.o src/mime.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/mime.o src/mime.c -${OBJECTDIR}/src/socket.o: src/socket.c +${OBJECTDIR}/src/socket.o: nbproject/Makefile-${CND_CONF}.mk src/socket.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" - $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/socket.o src/socket.c + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/socket.o src/socket.c + +${OBJECTDIR}/src/util.o: nbproject/Makefile-${CND_CONF}.mk src/util.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/util.o src/util.c # Subprojects .build-subprojects: diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index ffa3e5e..a6563b1 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -13,6 +13,7 @@ src/main.h src/mime.h src/socket.h + src/util.h src/main.c src/mime.c src/socket.c + src/util.c + 2 3 lib - -O0 + -O0 -march=native INI_ALLOW_BOM=0 INI_ALLOW_MULTILINE=0 @@ -119,26 +122,38 @@ + + + + default true - false + true 5 + 2 + 3 + + lib + + -march=native + + INI_ALLOW_BOM=0 + INI_ALLOW_MULTILINE=0 + _GNU_SOURCE + + 3 - - 5 - - - 5 - - - 5 - + + + magic + + @@ -184,6 +199,10 @@ + + + + diff --git a/nbproject/private/configurations.xml b/nbproject/private/configurations.xml index cb9d4e5..3897824 100644 --- a/nbproject/private/configurations.xml +++ b/nbproject/private/configurations.xml @@ -62,7 +62,7 @@ "${OUTPUT_PATH}" "${OUTPUT_PATH}" - + /home/sam/NetBeansProjects/KHttp/./dist/Debug/GNU-Linux-x86 true 0 0 diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml index f3ee2df..d97b1b3 100644 --- a/nbproject/private/private.xml +++ b/nbproject/private/private.xml @@ -8,19 +8,19 @@ file:/home/sam/NetBeansProjects/KHttp/src/socket.c - file:/home/sam/NetBeansProjects/KHttp/content/khttpd.ini file:/home/sam/NetBeansProjects/KHttp/src/config.c + file:/home/sam/NetBeansProjects/KHttp/content/dirindex.html file:/home/sam/NetBeansProjects/KHttp/src/http-server.h file:/home/sam/NetBeansProjects/KHttp/src/http.c - file:/home/sam/NetBeansProjects/KHttp/src/http-reader.h + file:/home/sam/NetBeansProjects/KHttp/lib/ut/utstring.h file:/home/sam/NetBeansProjects/KHttp/src/main.h + file:/home/sam/NetBeansProjects/KHttp/lib/http_parser.h file:/home/sam/NetBeansProjects/KHttp/Makefile - file:/home/sam/NetBeansProjects/KHttp/content/error.html file:/home/sam/NetBeansProjects/KHttp/src/main.c + file:/home/sam/NetBeansProjects/KHttp/src/socket.h file:/home/sam/NetBeansProjects/KHttp/src/http-server.c file:/home/sam/NetBeansProjects/KHttp/lib/ini.c file:/home/sam/NetBeansProjects/KHttp/src/http.h - file:/home/sam/NetBeansProjects/KHttp/src/http-reader.c file:/home/sam/NetBeansProjects/KHttp/src/config.h diff --git a/src/config.c b/src/config.c index ca3c62a..8235acb 100644 --- a/src/config.c +++ b/src/config.c @@ -20,7 +20,7 @@ config_server* config_server_new() { config->servername = calloc(128, sizeof(char)); if (gethostname(config->servername, 128) < 0) { - warning("failed to get server hostname", true); + warning(true, "failed to get server hostname"); free(config->servername); config->servername = strdup(default_servername); } @@ -69,12 +69,20 @@ config_host* config_host_new() { host->enabled = true; host->hostname = NULL; host->serve_dir = NULL; + host->dir_listings = true; + host->index_files = calloc(1, sizeof(char*)); + host->index_files[0] = strdup("index.html"); + host->index_files_count = 1; return host; } void config_host_delete(config_host *host) { if (host->hostname != NULL) free(host->hostname); if (host->serve_dir != NULL) free(host->serve_dir); + for(int i=0; iindex_files_count; i++) { + free(host->index_files[i]); + } + free(host->index_files); free(host); } @@ -92,18 +100,21 @@ static int config_read_ini_cb(void* _config, const char* section, const char* na return -1; } else if (name != NULL) { if (MATCH("Server", "name")) { - config->servername = strdup(value); + config->servername = realloc(config->servername, strlen(value)+1); + strcpy(config->servername, value); } else if (MATCH("Server", "admin")) { - config->administrator = strdup(value); + config->administrator = realloc(config->administrator, strlen(value)+1); + strcpy(config->administrator, value); } else if (MATCH("Server", "listen")) { errno = 0; config->listen_port = (uint16_t)strtol(value, NULL, 10); if (errno != 0) { - warning("Config: Invalid port number for [Server]listen", true); + warning(true, "Config: Invalid port number for [Server]listen"); } return -1; } else if (MATCH("Host", "name")) { - host->hostname = strdup(value); + host->hostname = realloc(host->hostname, strlen(value)+1); + strcpy(host->hostname, value); } else if (MATCH("Host", "enabled")) { if (strcasecmp(value, "yes") == 0) { host->enabled = true; @@ -125,17 +136,43 @@ static int config_read_ini_cb(void* _config, const char* section, const char* na } else if (MATCH("Host", "serve")) { char* serve_dir = realpath(value, NULL); if (serve_dir == NULL) { - warning("Config: host serve directory is invalid", true); + warning(true, "Config: host serve directory is invalid"); return -1; } DIR* dir = opendir(serve_dir); if (dir == NULL) { free(serve_dir); - warning("Config: host serve directory is invalid", true); + warning(true, "Config: host serve directory is invalid"); return -1; } closedir(dir); + if (host->serve_dir != NULL) free(host->serve_dir); host->serve_dir = serve_dir; + } else if (MATCH("Host", "index_files")) { + for(int i=0;iindex_files_count;i++) { + free(host->index_files[i]); + } + free(host->index_files); + host->index_files = NULL; + host->index_files_count = 0; + + char* savepos=NULL; + char* value_cpy = strdup(value); + char* file = strtok_r(value_cpy, ",", &savepos); + while(file != NULL) { + host->index_files = realloc(host->index_files, sizeof(char*)*(host->index_files_count+1)); + host->index_files[host->index_files_count++] = strdup(str_trimwhitespace(file)); + file = strtok_r(NULL, ",", &savepos); + } + free(value_cpy); + } else if (MATCH("Host", "dir_listings")) { + if (strcasecmp(value, "yes") == 0) { + host->dir_listings = true; + } else if (strcasecmp(value, "no") == 0) { + host->dir_listings = false; + } else { + warning(false, "Unexpected value for [Host]dir_listings"); + } } } #undef MATCH diff --git a/src/config.h b/src/config.h index 4cd9388..034f224 100644 --- a/src/config.h +++ b/src/config.h @@ -23,6 +23,9 @@ extern "C" { bool default_host; bool enabled; char *serve_dir; + char**index_files; + size_t index_files_count; + bool dir_listings; } config_host; typedef struct config_server { diff --git a/src/http-server.c b/src/http-server.c index fa12f02..fc4f6a6 100644 --- a/src/http-server.c +++ b/src/http-server.c @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include "http_parser.h" #include "http.h" @@ -60,6 +63,7 @@ http_response* server_process_request(config_server* config, http_request *reque if (http_parser_parse_url(request->req->uri, strlen(request->req->uri), false, url) < 0) { response = http_response_create_builtin(400, "Invalid request"); http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + free(url); return response; } @@ -72,52 +76,52 @@ http_response* server_process_request(config_server* config, http_request *reque //Not found (for some reason) response = http_response_create_builtin(400, "URI is required or was invalid"); http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + free(url); return response; } + free(url); //Build actual path to file requested - char* filepath_requested = NULL; - size_t totallen = strlen(host_config->serve_dir) + strlen(uri) + 2; - filepath_requested = calloc(totallen, sizeof(char)); - strcat(filepath_requested, host_config->serve_dir); - strcat(filepath_requested, "/"); - strcat(filepath_requested, uri); + char* filepath = NULL; + size_t totallen = strlen(host_config->serve_dir) + strlen(uri) + 1; + filepath = calloc(totallen, sizeof(char)); + strcat(filepath, host_config->serve_dir); + strcat(filepath, uri); + free(uri); - //Check that the file exists - char* filepath_actual = realpath(filepath_requested, NULL); - if (filepath_actual == NULL) { - warning("realpath: not found/error", true); - response = http_response_create_builtin(404, "File not found"); - http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); - return response; - } - //Check that the file is within the server directory of the host - if (strncmp(host_config->serve_dir, filepath_actual, strlen(host_config->serve_dir)) != 0) { - //file is outside the server directory :S - response = http_response_create_builtin(400, "URI is required or was invalid"); - http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); - return response; - } + server_file_result *file_result = server_determine_file(host_config, filepath); + free(filepath); + filepath=NULL; - struct stat *pathstat = calloc(1, sizeof(struct stat)); - if (stat(filepath_actual, pathstat) < 0) { - warning("stat failed", true); - response = http_response_create_builtin(400, "URI is required or was invalid"); + if (file_result->error != false) { + response = http_response_create_builtin(file_result->error_code, file_result->error_text); http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + server_file_result_delete(file_result); return response; } - //Is is a directory? - if (S_ISDIR(pathstat->st_mode) != 0) { - response = http_response_create_builtin(200, "Directory listing not implemented"); - return response; + FILE *file = NULL; + if (file_result->dir == true) { + file = server_generate_directory_index(host_config, file_result->path); + if (file == NULL) { + response = http_response_create_builtin(500, "Failed to generate directory index"); + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + server_file_result_delete(file_result); + return response; + } + } else { + filepath = strdup(file_result->path); } + server_file_result_delete(file_result); //Open file - FILE *file = fopen(filepath_actual, "r"); if (file == NULL) { - warning("failed to open file for reading", true); + file = fopen(filepath, "rb"); + } + if (file == NULL) { + warning(true, "failed to open file for reading"); response = http_response_create_builtin(404, "File not found"); http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + free(filepath); return response; } @@ -128,20 +132,23 @@ http_response* server_process_request(config_server* config, http_request *reque //Read file into response //TODO: send file directly from the write loop - char* buffer = calloc(filesize, sizeof(char)); + char* buffer = calloc(filesize+1, sizeof(char)); if (fread(buffer, sizeof(char), filesize, file) != filesize) { - warning("failed to read file into memory", true); + warning(true, "failed to read file into memory"); response = http_response_create_builtin(500, "Could not read file"); } else { response = http_response_new(http_response_line_new(200)); response->resp->version = request->req->version; - http_header_list_add(response->headers, http_header_new(HEADER_CONTENT_TYPE, mime_get_type(filepath_actual, DEFAULT_CONTENT_TYPE)), false); + const char* mime_type = "text/html"; + if (filepath != NULL) { + mime_type = mime_get_type(filepath, DEFAULT_CONTENT_TYPE); + } + http_header_list_add(response->headers, http_header_new(HEADER_CONTENT_TYPE, mime_type), false); http_response_append_body(response, buffer); } fclose(file); free(buffer); - free(filepath_requested); - free(filepath_actual); + free(filepath); //Check to see if client requested the connection be closed http_header* request_connection = http_header_list_get(request->headers, HEADER_CONNECTION); @@ -150,4 +157,187 @@ http_response* server_process_request(config_server* config, http_request *reque } return response; +} + +server_file_result* server_determine_file(config_host* hconfig, const char* requested_path) { + server_file_result *result = server_file_result_new(); + //Check that the file exists + char* filepath_actual = realpath(requested_path, NULL); + if (filepath_actual == NULL) { + server_file_result_seterror(result, 404, "File not found"); + return result; + } + //Check that the file is within the server directory of the host + if (strncmp(hconfig->serve_dir, filepath_actual, strlen(hconfig->serve_dir)) != 0) { + //file is outside the server directory :S + server_file_result_seterror(result, 404, "File not found"); + free(filepath_actual); + return result; + } + + struct stat *pathstat = calloc(1, sizeof(struct stat)); + if (stat(filepath_actual, pathstat) < 0) { + server_file_result_seterror(result, 404, "File not found"); + free(filepath_actual); + free(pathstat); + return result; + } + //If directory + if (S_ISDIR(pathstat->st_mode) != 0) { + char* dir_index = server_find_directory_index(hconfig, filepath_actual); + if (hconfig->index_files_count > 0 && dir_index != NULL) { + //Use the directory index + result->path = dir_index; + } else if (hconfig->dir_listings == true) { + //Show directory listing + result->dir = true; + result->path = strdup(filepath_actual); + } else { + server_file_result_seterror(result, 403, "No index file was found and directory indexes are disabled"); + } + } else if (S_ISREG(pathstat->st_mode) != 0) { + result->path = strdup(filepath_actual); + } + free(pathstat); + free(filepath_actual); + return result; +} +char* server_find_directory_index(config_host *hconfig, char* path) { + DIR *dir = opendir(path); + if (dir == NULL) { + return NULL; + } + struct dirent *entry; + while((entry = readdir(dir)) != NULL) { + for(int i=0; iindex_files_count; i++) { + if (strcasecmp(entry->d_name, hconfig->index_files[i]) == 0) { + char* dirindex = calloc(strlen(path)+strlen(entry->d_name)+2, sizeof(char)); + strcat(dirindex, path); + strcat(dirindex, "/"); + strcat(dirindex, entry->d_name); + closedir(dir); + return dirindex; + } + } + } + closedir(dir); + return NULL; +} +FILE * server_generate_directory_index(config_host *hconfig, const char* dirpath) { + DIR *dir = opendir(dirpath); + if (dir == NULL) { + return NULL; + } + + UT_string *index = NULL; + utstring_new(index); + + struct dirent *entry; + struct stat *filestat = calloc(1, sizeof(struct stat)); + uint32_t count=0; + while((entry = readdir(dir)) != NULL) { + if (count++ > 1024*8) break; + char *filepath = calloc(strlen(dirpath)+strlen(entry->d_name)+2, sizeof(char)); + strcat(filepath, dirpath); + strcat(filepath, "/"); + strcat(filepath, entry->d_name); + + if (stat(filepath, filestat) < 0) { + continue; + } + + char* filesize = NULL; + if (S_ISDIR(filestat->st_mode)) { + filesize = strdup("[DIR]"); + } else if (S_ISREG(filestat->st_mode)) { + filesize = server_get_filesize(filepath); + } else { + continue; + } + char* uri = strdup(filepath); + uri = str_replace(uri, hconfig->serve_dir, ""); + if (uri[0] = '/') { + memmove(uri, uri+1, strlen(uri+1)+1); + uri = realloc(uri, strlen(uri)+1); + } + + time_t file_mtime = (time_t)filestat->st_mtim.tv_sec; + char* file_mod_time = ctime(&file_mtime); + + utstring_printf(index, "%s%s%s\r\n", uri, + (filesize!=NULL)?filesize:"N/A", + (file_mod_time!=NULL)?file_mod_time:"N/A"); + free(filepath); + free(filesize); + free(uri); + } + closedir(dir); + free(filestat); + char *dirname = strdup(dirpath); + dirname = str_replace(dirname, hconfig->serve_dir, "/"); + + file_map *dirindex_map = file_map_new("dirindex.html"); + if (dirindex_map == NULL) { + utstring_free(index); + free(dirname); + return NULL; + } + char* dirindex = strdup(dirindex_map->map); + file_map_delete(dirindex_map); + + dirindex = str_replace(dirindex, "{{dirname}}", dirname); + free(dirname); + dirindex = str_replace(dirindex, "{{index}}", utstring_body(index)); + utstring_free(index); + + FILE *file = tmpfile(); + fwrite(dirindex, sizeof(char), strlen(dirindex), file); + free(dirindex); + + return file; +} +char* server_get_filesize(const char* filename) { + FILE *file = fopen(filename, "rb"); + if (file == NULL) { + return NULL; + } + fseek(file, 0L, SEEK_END); + long int size = ftell(file); + fclose(file); + + char* sizenames[] = {"b", "kb", "mb", "gb", "pb"}; + int size_i = 0; + + while(size > 1024) { + if (size_i > (sizeof(sizenames) / sizeof(char*))) { + break; + } + size_i++; + size /= 1024; + } + char* sizestr = calloc(100, sizeof(char)); + snprintf(sizestr, 100, "%li%s", size, sizenames[size_i]); + sizestr = realloc(sizestr, sizeof(char)*(strlen(sizestr)+1)); + return sizestr; +} + +server_file_result* server_file_result_new() { + server_file_result *result = calloc(1, sizeof(server_file_result)); + result->error = false; + result->error_code = 0; + result->error_text = NULL; + result->dir = false; + result->path = NULL; + + return result; +} +void server_file_result_seterror(server_file_result *result, uint16_t code, const char* message) { + result->error = true; + result->error_code = code; + result->error_text = strdup(message); +} +void server_file_result_delete(server_file_result *result) { + free(result->error_text); + free(result->path); + free(result); } \ No newline at end of file diff --git a/src/http-server.h b/src/http-server.h index b0e3f59..6ba5d1f 100644 --- a/src/http-server.h +++ b/src/http-server.h @@ -12,12 +12,31 @@ extern "C" { #endif +#include +#include +#include #include "http.h" #include "main.h" #include "config.h" - http_response* server_process_request(config_server* config, http_request *request); + typedef struct server_file_result { + bool error; + uint16_t error_code; + char* error_text; + bool dir; + DIR *dirent; + char* path; + } server_file_result; + http_response* server_process_request(config_server* config, http_request *request); + server_file_result* server_determine_file(config_host* hconfig, const char* requested_path); + char* server_find_directory_index(config_host *hconfig, char* path); + FILE * server_generate_directory_index(config_host *hconfig, const char* dirpath); + char* server_get_filesize(const char* filename); + + server_file_result* server_file_result_new(); + void server_file_result_seterror(server_file_result *result, uint16_t code, const char* message); + void server_file_result_delete(server_file_result *result); #ifdef __cplusplus } diff --git a/src/http.h b/src/http.h index 70aea64..8d862fd 100644 --- a/src/http.h +++ b/src/http.h @@ -29,7 +29,7 @@ extern "C" { #define HEADER_IF_MODIFIED_SINCE "If-Modified-Since" #define HEADER_IF_UNMODIFIED_SINCE "If-Unmodified-Since" -#define FORMAT_HEADER_DATE "%a, %e %h %Y %T %Z" +#define FORMAT_HEADER_DATE "%a, %d %h %Y %T %Z" #define DEFAULT_CONTENT_TYPE "text/plain" #define HTTP_CHUNK_MAXSIZE 1024*16 diff --git a/src/main.c b/src/main.c index be33ceb..3129636 100644 --- a/src/main.c +++ b/src/main.c @@ -9,11 +9,12 @@ #include #include #include -#include #include #include #include #include +#include +#include #include "http_parser.h" @@ -28,29 +29,39 @@ #include "mime.h" int serverfd = 0; +volatile static bool stop = false; + +static void signal_int(int signum) { + fprintf(stderr, "Terminating...\n"); + stop = true; +} int main(int argc, char** argv) { + mime_load(NULL); config_server *config = config_server_new(); if (config_read_ini("khttpd.ini", config) < 0) { - return 1; + fatal("Could not read config"); } + signal(SIGINT, signal_int); + skt_elem *connections = NULL; serverfd = svr_create(); svr_listen(serverfd, config->listen_port); while(1) { - uint32_t counter; + uint32_t connections_open; skt_elem *elem, *tmp; //Accept new connections - LL_COUNT(connections, elem, counter); - while(counter < 100 && svr_canaccept(serverfd)) { + LL_COUNT(connections, elem, connections_open); + while(connections_open < 100 && svr_canaccept(serverfd)) { skt_info *info = svr_accept(serverfd); if (info != NULL) { - LL_APPEND(connections, skt_elem_new(info)); + skt_elem *elem = skt_elem_new(info); + LL_APPEND(connections, elem); } } @@ -78,7 +89,7 @@ int main(int argc, char** argv) { "error parsing request (%s: %s). closing connection", http_errno_name(elem->parser->http_errno), http_errno_description(elem->parser->http_errno)); - warning(warningmsg, false); + warning(false, warningmsg); //send 400 back and close connection http_response *resp400 = http_response_create_builtin(400, "Request was invalid or could not be read"); http_header_list_add(resp400->headers, http_header_new(HEADER_CONNECTION, "close"), false); @@ -125,7 +136,7 @@ int main(int argc, char** argv) { if (elem->info->close_afterwrite && utstring_len(elem->info->write) == 0) { elem->info->close = true; } - if (elem->info->close == true) { + if (elem->info->close == true || stop == true) { skt_close(elem->info); } } @@ -136,9 +147,13 @@ int main(int argc, char** argv) { skt_elem_delete(elem); } } + if (stop == true) { + break; + } } mime_free(); + config_server_delete(config); svr_release(serverfd); serverfd = 0; @@ -168,13 +183,20 @@ void skt_elem_reset(skt_elem *elem) { elem->request_complete = false; } void skt_elem_write_response(skt_elem *elem, http_response *response, bool dispose) { - char *response_str = http_response_write(response); - utstring_printf(elem->info->write, "%s", response_str); - free(response_str); http_header* connection_header = http_header_list_get(response->headers, HEADER_CONNECTION); if (connection_header != NULL && strcasecmp(connection_header->content, "close") == 0) { elem->info->close_afterwrite = true; } + if (connection_header == NULL) { + if (response->resp->version == HTTP11) { + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "Keep-Alive"), true); + } else if (response->resp->version == HTTP10) { + elem->info->close_afterwrite = true; + } + } + char *response_str = http_response_write(response); + utstring_printf(elem->info->write, "%s", response_str); + free(response_str); if (dispose == true) { http_response_delete(response); } @@ -182,136 +204,10 @@ void skt_elem_write_response(skt_elem *elem, http_response *response, bool dispo void skt_elem_delete(skt_elem* elem) { if (elem->info!=NULL) skt_delete(elem->info); if (elem->current_request!=NULL) http_request_delete(elem->current_request); + if (elem->parser!= NULL) { + elem->parser->data = NULL; + free(elem->parser); + } + free(elem); } - -void fatal(char* msg) { - fprintf(stderr, "\n"); - perror(msg); - exit(EXIT_FAILURE); -} -void warning(char* msg, bool showPError) { - char warning[1024]; - memset(&warning, 0, 1024*sizeof(char)); - snprintf(warning, 1024, "Warning: %s", msg); - - if (showPError == true) { - perror(warning); - } else { - fprintf(stderr, "%s\n", warning); - } -} -void info(char* msg, ...) { - va_list va; - va_start(va, msg); - vfprintf(stdout, msg, va); - fputc('\n', stdout); - va_end(va); -} - -char** str_splitlines(char *str, size_t *line_count) { - char **result; - *line_count = 0; - char *tmp = str; - - while(*tmp) { - if (*tmp == '\n') { - (*line_count)++; - } - tmp++; - } - if (*line_count == 0) { - result = calloc(1, sizeof(char*)); - result[0] = calloc(strlen(str), sizeof(char)); - strcpy(result[0], str); - return result; - } - result = calloc(*line_count, sizeof(char*)); - if (result == NULL) { - fatal("calloc failed"); - } - - size_t i=0, linelen = 0; - char *line = strtok(str, "\n"); - while(line) { - linelen = strlen(line); - result[i] = calloc(linelen+1, sizeof(char)); - if (result[i] == NULL) { - fatal("calloc failed"); - } - strcpy(result[i], line); - if (result[i][linelen-1] == '\r') { - result[i][linelen-1] = '\0'; - result[i] = realloc(result[i], linelen); - } - line = strtok(NULL, "\n"); - i++; - } - - return result; -} - -file_map* file_map_new(const char* filename) { - - int fd = open(filename, O_RDONLY); - if (fd < 0) { - warning("Failed to open file for memory mapping", true); - return NULL; - } - size_t size = lseek(fd, 0L, SEEK_END); - void* map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (map == MAP_FAILED) { - warning("Failed to mmap file", true); - close(fd); - return NULL; - } - close(fd); - - file_map* filemap = calloc(1, sizeof(file_map)); - filemap->map = (char*)map; - filemap->size = size; - return filemap; -} -void file_map_delete(file_map* file) { - if (munmap((void*)file->map, file->size) < 0) { - warning("failed to unmap file", true); - } - free(file); -} - -char* str_replace(char *haystack, const char *search, const char *replacement) { - - size_t haystacklen = strlen(haystack); - size_t searchlen = strlen(search); - size_t replacementlen = strlen(replacement); - - char* result = haystack; - - if (searchlen > haystacklen || searchlen == 0) { - return result; - } - if (strstr(replacement, search) != NULL) { - warning("str_replace: replacement should not contain the search criteria", false); - } - int count = 0; - while(count++ < 1000) { - char* pos = strstr(result, search); - if (pos == NULL) { - break; - } - uint32_t start = (pos - result) / sizeof(char); - uint32_t end = start + searchlen; - - size_t resultlen = strlen(result); - size_t newlen = resultlen + replacementlen - searchlen; - - char* newstr = calloc(newlen+1, sizeof(char)); - strncpy(newstr, result, start); - strcat(newstr, replacement); - strcat(newstr, pos+(searchlen*sizeof(char))); - - free(result); - result = newstr; - } - return result; -} \ No newline at end of file diff --git a/src/main.h b/src/main.h index 7608e24..3dca40e 100644 --- a/src/main.h +++ b/src/main.h @@ -18,11 +18,7 @@ extern "C" { #include "http_parser.h" #include "socket.h" #include "http.h" - - typedef struct file_map { - char* map; - size_t size; - } file_map; +#include "util.h" typedef enum skt_elem_hstate {HSTATE_NONE, HSTATE_VALUE, HSTATE_FIELD} skt_elem_hstate; typedef struct skt_elem { @@ -42,15 +38,7 @@ extern "C" { int main(int argc, char** argv); - void fatal(char* msg); - void warning(char* msg, bool showPError); - void info(char* msg, ...); - - char** str_splitlines(char *str, size_t *line_count); - char* str_replace(char *str, const char *search, const char *replacement); - - file_map* file_map_new(const char* filename); - void file_map_delete(file_map* map); + #ifdef __cplusplus diff --git a/src/mime.c b/src/mime.c index 81584d9..5c80a83 100644 --- a/src/mime.c +++ b/src/mime.c @@ -8,13 +8,14 @@ #include "mime.h" #include "main.h" -mime_type *mime_list; +mime_type *mime_list = NULL; int mime_load(const char* file) { FILE *mimetypes = fopen(file == NULL ? MIME_DEFAULT_FILE : file, "r"); if (mimetypes == NULL) { return -1; } + mime_type *new_list = NULL; size_t count = 1024; char* buffer = calloc(count, sizeof(char)); ssize_t linelength; @@ -35,7 +36,7 @@ int mime_load(const char* file) { mime_type *new = calloc(1, sizeof(mime_type)); new->extension = strdup(ext); new->mime = strdup(mime); - LL_APPEND(mime_list, new); + LL_APPEND(new_list, new); ext = strtok_r(NULL, " \t", &saveptr); } } @@ -43,6 +44,12 @@ int mime_load(const char* file) { } free(buffer); fclose(mimetypes); + + if (mime_list != NULL) { + mime_free(); + } + mime_list = new_list; + count = 0; mime_type *elem; LL_COUNT(mime_list, elem, count); diff --git a/src/socket.c b/src/socket.c index 0939802..f167950 100644 --- a/src/socket.c +++ b/src/socket.c @@ -17,7 +17,7 @@ u_int64_t skt_nextid() { static u_int64_t id = 0; - return id++; + return __atomic_fetch_add(&id, 1, __ATOMIC_SEQ_CST); } skt_info* skt_new(int fd) { skt_info* skt = calloc(1, sizeof(skt_info)); @@ -29,7 +29,6 @@ skt_info* skt_new(int fd) { skt->close = false; skt->close_afterwrite = false; skt->closed = false; - return skt; } void skt_delete(skt_info* skt) { @@ -42,7 +41,7 @@ void skt_delete(skt_info* skt) { bool skt_canread(skt_info* skt) { int len = 0; if (ioctl(skt->fd, FIONREAD, &len) < 0) { - warning("ioctl failed", true); + warning(true, "ioctl failed"); return false; } return len > 0; @@ -52,9 +51,11 @@ uint32_t skt_read(skt_info* skt) { memset(buffer, 0, 1024); int result = read(skt->fd, &buffer,1023); - if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - warning("read error", true); - skt->close = true; + if (result < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + warning(true, "read error"); + skt->close = true; + } return 0; } skt->last_act = time(NULL); @@ -67,17 +68,19 @@ uint32_t skt_write(skt_info* skt) { } int result = write(skt->fd, utstring_body(skt->write), utstring_len(skt->write)); - if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - warning("write error", true); - skt->close = true; + if (result < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + warning(true, "write error"); + skt->close = true; + } return 0; } + skt->last_act = time(NULL); if (result == utstring_len(skt->write)) { - utstring_free(skt->write); - utstring_new(skt->write); - return 0; + utstring_clear(skt->write); + return result; } //remove first x chars char* newstr = calloc(utstring_len(skt->write) - result + 1, sizeof(char)); @@ -85,8 +88,7 @@ uint32_t skt_write(skt_info* skt) { char* writeBody = utstring_body(skt->write); strcpy(newstr, writeBody + (sizeof(char) * result)); - utstring_free(skt->write); - utstring_new(skt->write); + utstring_clear(skt->write); utstring_printf(skt->write, "%s", newstr); free(newstr); return result; //bytes written @@ -97,7 +99,7 @@ void skt_close(skt_info* skt) { } info("[#%lu %s] Closed", skt->id, skt_clientaddr(skt)); if (close(skt->fd) < 0) { - warning("error closing socket", true); + warning(true, "error closing socket"); } skt->closed = true; } @@ -122,20 +124,18 @@ void svr_listen(int fd, uint16_t port) { server_address.sin_port = htons(port); if (bind(fd, (struct sockaddr*)&server_address, sizeof server_address) < 0) { - warning("failed to bind", true); close(fd); - exit(EXIT_FAILURE); + fatal("Failed to bind to socket"); } if (listen(fd, 16) < 0) { - warning("failed to listen", true); close(fd); - exit(EXIT_FAILURE); + fatal("Could not set socket to listen mode"); } info("Listening on port %u", port); } void svr_release(int fd) { if (close(fd) < 0) { - warning("could not close socket", true); + warning(true, "could not close socket"); } } bool svr_canaccept(int fd) { @@ -145,12 +145,15 @@ bool svr_canaccept(int fd) { pfd[0].events = POLLIN; if (poll(pfd, 1, 50/*ms*/) < 0) { - warning("poll failed", true); + warning(true, "poll failed"); + free(pfd); return false; } if ((pfd[0].revents & POLLIN) == POLLIN) { + free(pfd); return true; } + free(pfd); return false; } skt_info* svr_accept(int fd) { @@ -160,7 +163,7 @@ skt_info* svr_accept(int fd) { socklen_t clientaddr_len = (socklen_t)sizeof(struct sockaddr_in); clientfd = accept(fd, (struct sockaddr*)clientaddr, &clientaddr_len); if (clientfd < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - warning("error accepting connection", true); + warning(true, "error accepting connection"); return NULL; } diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..b468558 --- /dev/null +++ b/src/util.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +void fatal(char* msg) { + fprintf(stderr, "\n"); + perror(msg); + exit(EXIT_FAILURE); +} +void warning(bool showPError, char* msg, ...) { + char warning[128] = {0}; + va_list va; + va_start(va, msg); + vsnprintf(warning, 128, msg, va); + va_end(va); + + if (showPError == true) { + perror(warning); + } else { + fprintf(stderr, "%s\n", warning); + } +} +void info(char* msg, ...) { + va_list va; + va_start(va, msg); + vfprintf(stdout, msg, va); + fputc('\n', stdout); + va_end(va); +} + +char** str_splitlines(char *str, size_t *line_count) { + char **result; + *line_count = 0; + char *tmp = str; + + while(*tmp) { + if (*tmp == '\n') { + (*line_count)++; + } + tmp++; + } + if (*line_count == 0) { + result = calloc(1, sizeof(char*)); + result[0] = calloc(strlen(str), sizeof(char)); + strcpy(result[0], str); + return result; + } + result = calloc(*line_count, sizeof(char*)); + if (result == NULL) { + fatal("calloc failed"); + } + + size_t i=0, linelen = 0; + char *line = strtok(str, "\n"); + while(line) { + linelen = strlen(line); + result[i] = calloc(linelen+1, sizeof(char)); + if (result[i] == NULL) { + fatal("calloc failed"); + } + strcpy(result[i], line); + if (result[i][linelen-1] == '\r') { + result[i][linelen-1] = '\0'; + result[i] = realloc(result[i], linelen); + } + line = strtok(NULL, "\n"); + i++; + } + + return result; +} +/* str_trimwhitespace + * Credit to https://stackoverflow.com/a/122721/428 + */ +char* str_trimwhitespace(char *str) +{ + char *end; + + // Trim leading space + while(isspace(*str)) str++; + + if(*str == 0) // All spaces? + return str; + + // Trim trailing space + end = str + strlen(str) - 1; + while(end > str && isspace(*end)) end--; + + // Write new null terminator + *(end+1) = 0; + + return str; +} + +file_map* file_map_new(const char* filename) { + + int fd = open(filename, O_RDONLY); + if (fd < 0) { + warning(true, "Failed to open file for memory mapping"); + return NULL; + } + size_t size = lseek(fd, 0L, SEEK_END); + void* map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + warning(true, "Failed to mmap file"); + close(fd); + return NULL; + } + close(fd); + + file_map* filemap = calloc(1, sizeof(file_map)); + filemap->map = (char*)map; + filemap->size = size; + return filemap; +} +void file_map_delete(file_map* file) { + if (munmap((void*)file->map, file->size) < 0) { + warning(true, "failed to unmap file"); + } + free(file); +} + +char* str_replace(char *haystack, const char *search, const char *replacement) { + + size_t haystacklen = strlen(haystack); + size_t searchlen = strlen(search); + size_t replacementlen = strlen(replacement); + + char* result = haystack; + + if (searchlen > haystacklen || searchlen == 0) { + return result; + } + if (strstr(replacement, search) != NULL) { + warning(false, "str_replace: replacement should not contain the search criteria"); + } + int count = 0; + while(count++ < 1000) { + char* pos = strstr(result, search); + if (pos == NULL) { + break; + } + uint32_t start = (pos - result) / sizeof(char); + uint32_t end = start + searchlen; + + size_t resultlen = strlen(result); + size_t newlen = resultlen + replacementlen - searchlen; + + char* newstr = calloc(newlen+1, sizeof(char)); + strncpy(newstr, result, start); + strcat(newstr, replacement); + strcat(newstr, pos+(searchlen*sizeof(char))); + + free(result); + result = newstr; + } + return result; +} \ No newline at end of file diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..135c31e --- /dev/null +++ b/src/util.h @@ -0,0 +1,39 @@ +/* + * File: util.h + * Author: sam + * + * Created on 03 August 2014, 13:52 + */ + +#ifndef UTIL_H +#define UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + + typedef struct file_map { + char* map; + size_t size; + } file_map; + + void fatal(char* msg); + void warning(bool showPError, char* msg, ...); + void info(char* msg, ...); + + char* str_trimwhitespace(char *str); + char** str_splitlines(char *str, size_t *line_count); + char* str_replace(char *str, const char *search, const char *replacement); + + file_map* file_map_new(const char* filename); + void file_map_delete(file_map* map); + + +#ifdef __cplusplus +} +#endif + +#endif /* UTIL_H */ + diff --git a/testreq.txt b/testreq.txt index 663ef9c..7b7f32c 100644 --- a/testreq.txt +++ b/testreq.txt @@ -1,4 +1,4 @@ -GET /index.html HTTP/1.1 +GET / HTTP/1.1 Host: example.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36