From 31e1ff4f972f34c9e67ebb6887dda859ba9518bb Mon Sep 17 00:00:00 2001 From: Sam Stevens Date: Tue, 29 Jul 2014 21:00:19 +0100 Subject: [PATCH] Done the basics of http server Added mime type detection Lots still todo, directory index files auto indexing(?) --- nbproject/Makefile-Debug.mk | 8 +- nbproject/Makefile-Release.mk | 6 ++ nbproject/configurations.xml | 15 ++++ nbproject/private/private.xml | 4 + src/config.c | 12 ++- src/config.h | 5 +- src/http-server.c | 139 ++++++++++++++++++++++++++++++++++ src/http-server.h | 5 +- src/http.c | 18 +++++ src/http.h | 12 +++ src/main.c | 42 +++++++++- src/main.h | 2 + src/mime.c | 96 +++++++++++++++++++++++ src/mime.h | 39 ++++++++++ testreq.txt | 2 +- 15 files changed, 391 insertions(+), 14 deletions(-) create mode 100644 src/mime.c create mode 100644 src/mime.h diff --git a/nbproject/Makefile-Debug.mk b/nbproject/Makefile-Debug.mk index f8565e5..581ae77 100644 --- a/nbproject/Makefile-Debug.mk +++ b/nbproject/Makefile-Debug.mk @@ -42,6 +42,7 @@ OBJECTFILES= \ ${OBJECTDIR}/src/http-server.o \ ${OBJECTDIR}/src/http.o \ ${OBJECTDIR}/src/main.o \ + ${OBJECTDIR}/src/mime.o \ ${OBJECTDIR}/src/socket.o @@ -59,7 +60,7 @@ FFLAGS= ASFLAGS= # Link Libraries and Options -LDLIBSOPTIONS= +LDLIBSOPTIONS=-lmagic # Build Targets .build-conf: ${BUILD_SUBPROJECTS} @@ -104,6 +105,11 @@ ${OBJECTDIR}/src/main.o: nbproject/Makefile-${CND_CONF}.mk src/main.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/main.o src/main.c +${OBJECTDIR}/src/mime.o: nbproject/Makefile-${CND_CONF}.mk src/mime.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/mime.o src/mime.c + ${OBJECTDIR}/src/socket.o: nbproject/Makefile-${CND_CONF}.mk src/socket.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" diff --git a/nbproject/Makefile-Release.mk b/nbproject/Makefile-Release.mk index 05d17da..567d3b9 100644 --- a/nbproject/Makefile-Release.mk +++ b/nbproject/Makefile-Release.mk @@ -42,6 +42,7 @@ OBJECTFILES= \ ${OBJECTDIR}/src/http-server.o \ ${OBJECTDIR}/src/http.o \ ${OBJECTDIR}/src/main.o \ + ${OBJECTDIR}/src/mime.o \ ${OBJECTDIR}/src/socket.o @@ -104,6 +105,11 @@ ${OBJECTDIR}/src/main.o: src/main.c ${RM} "$@.d" $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c +${OBJECTDIR}/src/mime.o: src/mime.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/mime.o src/mime.c + ${OBJECTDIR}/src/socket.o: src/socket.c ${MKDIR} -p ${OBJECTDIR}/src ${RM} "$@.d" diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 35b6d1f..ffa3e5e 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -11,6 +11,7 @@ lib/http_parser.h lib/ini.h src/main.h + src/mime.h src/socket.h lib/http_parser.c lib/ini.c src/main.c + src/mime.c src/socket.c 3 + + + magic + + @@ -104,6 +111,10 @@ + + + + @@ -165,6 +176,10 @@ + + + + diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml index 358024c..f3ee2df 100644 --- a/nbproject/private/private.xml +++ b/nbproject/private/private.xml @@ -8,6 +8,8 @@ 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/src/http-server.h file:/home/sam/NetBeansProjects/KHttp/src/http.c file:/home/sam/NetBeansProjects/KHttp/src/http-reader.h @@ -16,8 +18,10 @@ file:/home/sam/NetBeansProjects/KHttp/content/error.html file:/home/sam/NetBeansProjects/KHttp/src/main.c 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 5d2da86..ca3c62a 100644 --- a/src/config.c +++ b/src/config.c @@ -43,7 +43,7 @@ config_host* config_server_gethost(config_server *config, char *name) { config_host *defaulthost=NULL; config_host *host=NULL; CONFIG_SERVER_FOREACH_HOST(config, host) { - if (strcasecmp(name, host->hostname) == 0) { + if (name != NULL && strcasecmp(name, host->hostname) == 0) { return host; } if (host->default_host == true) { @@ -123,13 +123,19 @@ static int config_read_ini_cb(void* _config, const char* section, const char* na host->default_host = false; } } else if (MATCH("Host", "serve")) { - DIR *dir = opendir(value); + char* serve_dir = realpath(value, NULL); + if (serve_dir == NULL) { + warning("Config: host serve directory is invalid", true); + return -1; + } + DIR* dir = opendir(serve_dir); if (dir == NULL) { + free(serve_dir); warning("Config: host serve directory is invalid", true); return -1; } closedir(dir); - host->serve_dir = strdup(value); + host->serve_dir = serve_dir; } } #undef MATCH diff --git a/src/config.h b/src/config.h index ee76bcd..4cd9388 100644 --- a/src/config.h +++ b/src/config.h @@ -11,19 +11,18 @@ #ifdef __cplusplus extern "C" { #endif - #include #include "main.h" #define CONFIG_SERVER_FOREACH_HOST(config, elem) \ - elem = config->hosts[0]; \ + elem = config->hosts != NULL ? config->hosts[0] : NULL; \ for(int i=0; i < config->host_count; elem=config->hosts[i++]) typedef struct config_host { char *hostname; bool default_host; bool enabled; - char* serve_dir; + char *serve_dir; } config_host; typedef struct config_server { diff --git a/src/http-server.c b/src/http-server.c index ce35a83..614e870 100644 --- a/src/http-server.c +++ b/src/http-server.c @@ -1,6 +1,145 @@ #include #include +#include +#include +#include +#include +#include +#include "http_parser.h" #include "http.h" #include "main.h" +#include "config.h" +#include "http-server.h" +#include "mime.h" +http_response* server_process_request(config_server* config, http_request *request) { + http_response* response = NULL; + //Determin host + char* hostname=NULL; + if (request->req->version == HTTP11) { + http_header *hostheader = http_header_list_get(request->headers, HEADER_HOST); + if (hostheader != NULL) { + hostname = strdup(hostheader->content); + } else { + response = http_response_create_builtin(400, "Host header required"); + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + return response; + } + } + //hostname may be null, this indicates that the default host should be used + config_host* host_config = config_server_gethost(config, hostname); + if (hostname != NULL) { + free(hostname); + hostname = NULL; + } + if (host_config == NULL) { + //host not found and default not found + response = http_response_create_builtin(500, "Server configuration error (host/default not found)"); + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + return response; + } + + //Validate request method + http_request_method acceptable_methods[] = {METHOD_GET, METHOD_HEAD, METHOD_POST}; + bool valid_method = false; + for(int i=0; ireq->method == acceptable_methods[i]) { + valid_method = true; + break; + } + } + if (valid_method == false) { + response = http_response_create_builtin(411, "Method is not valid"); + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + return response; + } + + //Parse the request uri + struct http_parser_url *url = calloc(1, sizeof(struct http_parser_url)); + 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); + return response; + } + + //Get the uri/path + char* uri = NULL; + if ((url->field_set & (1 << UF_PATH)) != 0) { + uri = calloc(url->field_data[UF_PATH].len+1, sizeof(char)); + strncpy(uri, request->req->uri+url->field_data[UF_PATH].off, url->field_data[UF_PATH].len); + } else { + //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); + return response; + } + + //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); + + //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"); + 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; + } + + 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"); + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + 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; + } + + //Open file + FILE *file = fopen(filepath_actual, "r"); + if (file == NULL) { + warning("failed to open file for reading", true); + response = http_response_create_builtin(404, "File not found"); + return response; + } + + //File is ok and can be served to the client + fseek(file, 0, SEEK_END); + size_t filesize = ftell(file); + rewind(file); + + //Read file into response + //TODO: send file directly from the write loop + char* buffer = calloc(filesize, sizeof(char)); + if (fread(buffer, sizeof(char), filesize, file) != filesize) { + warning("failed to read file into memory", true); + 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); + http_response_append_body(response, buffer); + } + fclose(file); + free(buffer); + free(filepath_requested); + free(filepath_actual); + + return response; +} \ No newline at end of file diff --git a/src/http-server.h b/src/http-server.h index a2c7f21..b0e3f59 100644 --- a/src/http-server.h +++ b/src/http-server.h @@ -14,10 +14,11 @@ extern "C" { #include "http.h" #include "main.h" +#include "config.h" + + http_response* server_process_request(config_server* config, http_request *request); - - #ifdef __cplusplus } #endif diff --git a/src/http.c b/src/http.c index 719b384..5e8aa83 100644 --- a/src/http.c +++ b/src/http.c @@ -428,4 +428,22 @@ char* http_chunks_terminate(http_header_list *footers) { char* outputstr = utstring_body(output); free(output); return outputstr; +} + +http_response_list* http_response_list_new() { + http_response_list *list = calloc(1, sizeof(http_response_list)); + list->responses = NULL; + list->count = 0; + return list; +} +void http_response_list_append(http_response_list *list, http_response* response) { + list->responses = realloc(list->responses, (++list->count)*sizeof(http_response*)); + list->responses[list->count-1] = response; +} +void http_response_list_delete(http_response_list *list) { + http_response *elem; + HTTP_RESPONSE_LIST_FOREACH(list, elem) { + http_response_delete(elem); + } + free(list); } \ No newline at end of file diff --git a/src/http.h b/src/http.h index cdb39e1..be5d838 100644 --- a/src/http.h +++ b/src/http.h @@ -89,6 +89,14 @@ extern "C" { char* body; } http_response; +#define HTTP_RESPONSE_LIST_FOREACH(list, elem) \ + elem = list->count == 0 ? NULL : list->responses[0]; \ + for(int i=0; icount; elem=list->responses[++i]) + + typedef struct http_response_list { + http_response **responses; + size_t count; + } http_response_list; char* http_method_getstring(http_request_method method, char* method_other); http_request_method http_method_fromstring(const char* method); @@ -124,6 +132,10 @@ extern "C" { char* http_chunks_write(char* source); char* http_chunks_terminate(http_header_list *footers); + + http_response_list* http_response_list_new(); + void http_response_list_append(http_response_list *list, http_response* response); + void http_response_list_delete(http_response_list *list); #ifdef __cplusplus } diff --git a/src/main.c b/src/main.c index 13d0135..a8b2575 100644 --- a/src/main.c +++ b/src/main.c @@ -24,10 +24,13 @@ #include "http.h" #include "http-reader.h" #include "config.h" +#include "http-server.h" +#include "mime.h" int serverfd = 0; 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; @@ -78,17 +81,23 @@ int main(int argc, char** argv) { warning(warningmsg, false); //send 400 back and close connection http_response *resp400 = http_response_create_builtin(400, "Request was invalid or could not be read"); - char *resp400str = http_response_write(resp400); - utstring_printf(elem->info->write, "%s", resp400str); + http_header_list_add(resp400->headers, http_header_new(HEADER_CONNECTION, "close"), false); + skt_elem_write_response(elem, resp400, false); http_response_delete(resp400); - free(resp400str); - elem->info->close_afterwrite = true; + skt_elem_reset(elem); } //Clear read data now that we have processed it utstring_clear(elem->info->read); //Process request if received if (elem->request_complete == true) { + http_response *response = server_process_request(config, elem->current_request); + if (response == NULL) { + response = http_response_create_builtin(500, "Request could not be processed"); + http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false); + } + skt_elem_write_response(elem, response, true); + skt_elem_reset(elem); } } } @@ -129,6 +138,7 @@ int main(int argc, char** argv) { } } + mime_free(); svr_release(serverfd); serverfd = 0; @@ -145,6 +155,30 @@ skt_elem* skt_elem_new(skt_info *info) { elem->request_complete = false; return elem; } +void skt_elem_reset(skt_elem *elem) { + if (elem->current_request != NULL) { + http_request_delete(elem->current_request); + elem->current_request = NULL; + } + if (elem->parser_current_header != NULL) { + http_header_delete(elem->parser_current_header); + } + elem->parser_current_header = NULL; + elem->parser_header_state = HSTATE_NONE; + 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); + if (dispose == true) { + http_response_delete(response); + } + 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; + } +} 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); diff --git a/src/main.h b/src/main.h index 683530c..afb1f40 100644 --- a/src/main.h +++ b/src/main.h @@ -34,6 +34,8 @@ extern "C" { } skt_elem; skt_elem* skt_elem_new(skt_info *info); + void skt_elem_reset(skt_elem *elem); + void skt_elem_write_response(skt_elem *skt, http_response *response, bool dispose); void skt_elem_delete(skt_elem* elem); int main(int argc, char** argv); diff --git a/src/mime.c b/src/mime.c new file mode 100644 index 0000000..81584d9 --- /dev/null +++ b/src/mime.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include + +#include "ut/utlist.h" +#include "mime.h" +#include "main.h" + +mime_type *mime_list; + +int mime_load(const char* file) { + FILE *mimetypes = fopen(file == NULL ? MIME_DEFAULT_FILE : file, "r"); + if (mimetypes == NULL) { + return -1; + } + size_t count = 1024; + char* buffer = calloc(count, sizeof(char)); + ssize_t linelength; + while((linelength = getline(&buffer, &count, mimetypes)) >= 0) { + + if (linelength == 0) { + continue; + } + if (buffer[0] == '#' || buffer[0] == '\n') { + continue; + } + char* line = strndup(buffer, linelength); + char mime[512], extstr[512]; + if (sscanf(line, "%511s%*[ \t]%511[a-z0-9 \t]", mime, extstr) == 2) { + char* saveptr=NULL; + char* ext = strtok_r(extstr, " \t", &saveptr); + while(ext != NULL) { + mime_type *new = calloc(1, sizeof(mime_type)); + new->extension = strdup(ext); + new->mime = strdup(mime); + LL_APPEND(mime_list, new); + ext = strtok_r(NULL, " \t", &saveptr); + } + } + free(line); + } + free(buffer); + fclose(mimetypes); + count = 0; + mime_type *elem; + LL_COUNT(mime_list, elem, count); + return count; +} +void mime_free() { + mime_type *elem, *tmp; + LL_FOREACH_SAFE(mime_list, elem, tmp) { + free(elem->extension); + free(elem->mime); + free(elem); + } + mime_list = NULL; +} +void mime_print_all() { + mime_type *elem; + LL_FOREACH(mime_list, elem) { + printf("%s\t\t%s\n", elem->mime, elem->extension); + } +} + +const char* mime_get_type(const char* filename, const char* fallback) { + char *ext = strrchr(filename, '.'); + if (ext != NULL && strlen(ext) > 1) { + ext++; //Skip . + mime_type *elem; + LL_FOREACH(mime_list, elem) { + if (strcmp(ext, elem->extension) == 0) { + return elem->mime; + } + } + } + return mime_get_type_magic(filename, fallback); +} + +const char* mime_get_type_magic(const char* filename, const char* fallback) { + magic_t magic; + + magic = magic_open(MAGIC_MIME_TYPE); + magic_load(magic, NULL); + magic_compile(magic, NULL); + + const char* mime = magic_file(magic, filename); + magic_close(magic); + + if (mime != NULL) { + return mime; + } + + return fallback; +} \ No newline at end of file diff --git a/src/mime.h b/src/mime.h new file mode 100644 index 0000000..ada68dd --- /dev/null +++ b/src/mime.h @@ -0,0 +1,39 @@ +/* + * File: mime.h + * Author: sam + * + * Created on 29 July 2014, 19:38 + */ + +#ifndef MIME_H +#define MIME_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ut/utlist.h" + +#define MIME_DEFAULT_FILE "/etc/mime.types" + + typedef struct mime_type { + char* mime; + char* extension; + struct mime_type *next; + } mime_type; + + extern mime_type *mime_list; + + int mime_load(const char* file); + void mime_free(); + void mime_print_all(); + + const char* mime_get_type(const char* filename, const char* fallback); + const char* mime_get_type_magic(const char* filename, const char* fallback); + +#ifdef __cplusplus +} +#endif + +#endif /* MIME_H */ + diff --git a/testreq.txt b/testreq.txt index d6d2969..6338ec5 100644 --- a/testreq.txt +++ b/testreq.txt @@ -1,4 +1,4 @@ -GET /testing123 HTTP/1.1 +GET /index.html 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