From 0293b0f5c0b0a92e63cdcaff0c97f6fb99e9e169 Mon Sep 17 00:00:00 2001 From: Francesco Date: Sun, 26 Oct 2025 17:51:41 +0100 Subject: [PATCH] refactor: change project structure --- include/core/epoll_utils.h | 10 + include/{server => core}/server.h | 4 +- .../{utils/socket.h => core/socket_utils.h} | 0 include/{server => core}/worker.h | 4 +- include/http/mime.h | 13 + include/http/{http.h => request.h} | 9 +- include/http/response.h | 14 + include/server/epoll_utils.h | 7 - include/utils/debug.h | 2 - include/utils/hash_utils.h | 16 + include/utils/{utils.h => net_utils.h} | 9 - src/core/epoll_utils.c | 32 ++ src/{server => core}/server.c | 8 +- src/{utils/socket.c => core/socket_utils.c} | 2 +- src/{server => core}/worker.c | 10 +- src/http/http.c | 384 ------------------ src/http/mime.c | 28 ++ src/http/request.c | 190 +++++++++ src/http/response.c | 174 ++++++++ src/main.c | 2 +- src/meson.build | 15 +- src/server/epoll_utils.c | 43 -- src/utils/hash_utils.c | 51 +++ src/utils/net_utils.c | 31 ++ src/utils/utils.c | 82 ---- 25 files changed, 585 insertions(+), 555 deletions(-) create mode 100644 include/core/epoll_utils.h rename include/{server => core}/server.h (94%) rename include/{utils/socket.h => core/socket_utils.h} (100%) rename include/{server => core}/worker.h (91%) create mode 100644 include/http/mime.h rename include/http/{http.h => request.h} (87%) create mode 100644 include/http/response.h delete mode 100644 include/server/epoll_utils.h create mode 100644 include/utils/hash_utils.h rename include/utils/{utils.h => net_utils.h} (74%) create mode 100644 src/core/epoll_utils.c rename src/{server => core}/server.c (97%) rename src/{utils/socket.c => core/socket_utils.c} (96%) rename src/{server => core}/worker.c (95%) delete mode 100644 src/http/http.c create mode 100644 src/http/mime.c create mode 100644 src/http/request.c create mode 100644 src/http/response.c delete mode 100644 src/server/epoll_utils.c create mode 100644 src/utils/hash_utils.c create mode 100644 src/utils/net_utils.c delete mode 100644 src/utils/utils.c diff --git a/include/core/epoll_utils.h b/include/core/epoll_utils.h new file mode 100644 index 0000000..4a6360c --- /dev/null +++ b/include/core/epoll_utils.h @@ -0,0 +1,10 @@ +#ifndef CWS_EPOLL_UTILS_H +#define CWS_EPOLL_UTILS_H + +int cws_epoll_add(int epfd, int sockfd); + +int cws_epoll_del(int epfd, int sockfd); + +int cws_epoll_create_with_fd(int fd); + +#endif diff --git a/include/server/server.h b/include/core/server.h similarity index 94% rename from include/server/server.h rename to include/core/server.h index ea3008d..7dbc0ef 100644 --- a/include/server/server.h +++ b/include/core/server.h @@ -6,8 +6,8 @@ #include #include "config/config.h" -#include "utils/utils.h" -#include "worker.h" +#include "core/worker.h" +#include "utils/net_utils.h" /* Clients max queue */ #define CWS_SERVER_BACKLOG 128 diff --git a/include/utils/socket.h b/include/core/socket_utils.h similarity index 100% rename from include/utils/socket.h rename to include/core/socket_utils.h diff --git a/include/server/worker.h b/include/core/worker.h similarity index 91% rename from include/server/worker.h rename to include/core/worker.h index 02a8716..ea78ac2 100644 --- a/include/server/worker.h +++ b/include/core/worker.h @@ -5,8 +5,8 @@ #include #include -#include "../config/config.h" -#include "../utils/utils.h" +#include "config/config.h" +#include "utils/net_utils.h" extern volatile sig_atomic_t cws_server_run; diff --git a/include/http/mime.h b/include/http/mime.h new file mode 100644 index 0000000..799686c --- /dev/null +++ b/include/http/mime.h @@ -0,0 +1,13 @@ +#ifndef CWS_MIME_H +#define CWS_MIME_H + +#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +typedef struct mimetype { + const char *ext; + const char *type; +} mimetype; + +int http_get_content_type(char *location_path, char *content_type); + +#endif diff --git a/include/http/http.h b/include/http/request.h similarity index 87% rename from include/http/http.h rename to include/http/request.h index 456ab7e..1f88929 100644 --- a/include/http/http.h +++ b/include/http/request.h @@ -5,11 +5,9 @@ #include #include -#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) - +#define CWS_HTTP_CONTENT_TYPE 64 #define CWS_HTTP_HEADER_MAX 512 #define CWS_HTTP_HEADER_CONTENT_MAX 1024 -#define CWS_HTTP_CONTENT_TYPE 64 typedef enum cws_http_method { HTTP_GET, @@ -26,11 +24,6 @@ typedef enum cws_http_status { HTTP_NOT_IMPLEMENTED, } cws_http_status_e; -typedef struct mimetype { - const char *ext; - const char *type; -} mimetype; - typedef struct cws_http { int sockfd; cws_http_method_e method; diff --git a/include/http/response.h b/include/http/response.h new file mode 100644 index 0000000..033d553 --- /dev/null +++ b/include/http/response.h @@ -0,0 +1,14 @@ +#ifndef CWS_RESPONSE_H +#define CWS_RESPONSE_H + +#include "http/request.h" +#include + +size_t http_simple_html(char **response, cws_http_status_e status, char *title, char *description); + +size_t http_response_builder(char **response, cws_http_status_e status, char *content_type, + char *body, size_t body_len_bytes); + +void cws_http_send_response(cws_http_s *request, cws_http_status_e status); + +#endif diff --git a/include/server/epoll_utils.h b/include/server/epoll_utils.h deleted file mode 100644 index dde1085..0000000 --- a/include/server/epoll_utils.h +++ /dev/null @@ -1,7 +0,0 @@ -#include "utils/utils.h" - -cws_server_ret cws_epoll_add(int epfd, int sockfd); - -cws_server_ret cws_epoll_del(int epfd, int sockfd); - -int cws_epoll_create_with_fd(int fd); diff --git a/include/utils/debug.h b/include/utils/debug.h index 92a3a70..3c3367b 100644 --- a/include/utils/debug.h +++ b/include/utils/debug.h @@ -1,8 +1,6 @@ #ifndef CWS_COLORS_H #define CWS_COLORS_H -#include - /* ANSI color escape sequences */ #define RED "\033[31m" #define GREEN "\033[32m" diff --git a/include/utils/hash_utils.h b/include/utils/hash_utils.h new file mode 100644 index 0000000..a5f7fcf --- /dev/null +++ b/include/utils/hash_utils.h @@ -0,0 +1,16 @@ +#ifndef CWS_HASH_UTILS_H +#define CWS_HASH_UTILS_H + +unsigned int my_str_hash_fn(const void *key); + +bool my_str_equal_fn(const void *a, const void *b); + +void my_str_free_fn(void *value); + +unsigned int my_int_hash_fn(const void *key); + +bool my_int_equal_fn(const void *a, const void *b); + +void my_int_free_key_fn(void *key); + +#endif diff --git a/include/utils/utils.h b/include/utils/net_utils.h similarity index 74% rename from include/utils/utils.h rename to include/utils/net_utils.h index fb3787a..72cebde 100644 --- a/include/utils/utils.h +++ b/include/utils/net_utils.h @@ -33,13 +33,4 @@ cws_server_ret cws_fd_set_nonblocking(int sockfd); void cws_utils_get_client_ip(struct sockaddr_storage *sa, char *ip); -/* Functions used for hash maps */ -unsigned int my_str_hash_fn(const void *key); -bool my_str_equal_fn(const void *a, const void *b); -void my_str_free_fn(void *value); - -unsigned int my_int_hash_fn(const void *key); -bool my_int_equal_fn(const void *a, const void *b); -void my_int_free_key_fn(void *key); - #endif diff --git a/src/core/epoll_utils.c b/src/core/epoll_utils.c new file mode 100644 index 0000000..b23ab60 --- /dev/null +++ b/src/core/epoll_utils.c @@ -0,0 +1,32 @@ +#include "core/epoll_utils.h" + +#include +#include + +int cws_epoll_add(int epfd, int sockfd) { + struct epoll_event event; + event.events = EPOLLIN; + event.data.fd = sockfd; + const int status = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event); + + return status; +} + +int cws_epoll_del(int epfd, int sockfd) { + const int status = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); + + return status; +} + +int cws_epoll_create_with_fd(int fd) { + int epfd = epoll_create1(0); + if (epfd == -1) { + return -1; + } + + if (cws_epoll_add(epfd, fd) != 0) { + return -1; + } + + return epfd; +} diff --git a/src/server/server.c b/src/core/server.c similarity index 97% rename from src/server/server.c rename to src/core/server.c index 9543ef1..d7935ce 100644 --- a/src/server/server.c +++ b/src/core/server.c @@ -1,4 +1,4 @@ -#include "server/server.h" +#include "core/server.h" #include #include @@ -7,10 +7,10 @@ #include #include -#include "server/epoll_utils.h" -#include "server/worker.h" +#include "core/epoll_utils.h" +#include "core/worker.h" #include "utils/debug.h" -#include "utils/utils.h" +#include "utils/net_utils.h" static void cws_server_setup_hints(struct addrinfo *hints, const char *hostname) { memset(hints, 0, sizeof *hints); diff --git a/src/utils/socket.c b/src/core/socket_utils.c similarity index 96% rename from src/utils/socket.c rename to src/core/socket_utils.c index a6b3f58..5bcb993 100644 --- a/src/utils/socket.c +++ b/src/core/socket_utils.c @@ -1,4 +1,4 @@ -#include "utils/socket.h" +#include "core/socket_utils.h" #include #include diff --git a/src/server/worker.c b/src/core/worker.c similarity index 95% rename from src/server/worker.c rename to src/core/worker.c index 9489d04..5232ba7 100644 --- a/src/server/worker.c +++ b/src/core/worker.c @@ -1,4 +1,4 @@ -#include "server/worker.h" +#include "core/worker.h" #include #include @@ -7,10 +7,10 @@ #include #include -#include "http/http.h" -#include "server/epoll_utils.h" -#include "utils/socket.h" -#include "utils/utils.h" +#include "core/epoll_utils.h" +#include "core/socket_utils.h" +#include "http/request.h" +#include "utils/net_utils.h" static cws_server_ret cws_worker_setup_epoll(cws_worker_s *worker) { worker->epfd = epoll_create1(0); diff --git a/src/http/http.c b/src/http/http.c deleted file mode 100644 index dbba711..0000000 --- a/src/http/http.c +++ /dev/null @@ -1,384 +0,0 @@ -#include "http/http.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "utils/debug.h" -#include "utils/socket.h" -#include "utils/utils.h" - -static cws_http_s *http_new() { - cws_http_s *request = malloc(sizeof(cws_http_s)); - if (!request) { - return NULL; - } - memset(request, 0, sizeof *request); - - request->http_version = string_new("", 16); - request->location = string_new("", 128); - request->location_path = string_new("", 128); - - return request; -} - -static cws_http_method_e http_parse_method(const char *method) { - if (strcmp(method, "GET") == 0) { - return HTTP_GET; - } - - if (strcmp(method, "POST") == 0) { - return HTTP_POST; - } - - return HTTP_UNKNOWN; -} - -static mimetype mimetypes[] = {{"html", "text/html"}, - {"css", "text/css"}, - {"js", "application/javascript"}, - {"jpg", "image/jpeg"}, - {"png", "image/png"}}; - -static int http_get_content_type(char *location_path, char *content_type) { - /* Find last occurrence of a string */ - char *ptr = strrchr(location_path, '.'); - if (ptr == NULL) { - return -1; - } - ptr += 1; - - for (size_t i = 0; i < ARR_SIZE(mimetypes); ++i) { - if (!strcmp(ptr, mimetypes[i].ext)) { - snprintf(content_type, CWS_HTTP_CONTENT_TYPE - 1, "%s", mimetypes[i].type); - } - } - - return 0; -} - -static char *http_status_string(cws_http_status_e status) { - switch (status) { - case HTTP_OK: { - return "200 OK"; - break; - } - case HTTP_NOT_FOUND: { - return "404 Not Found"; - break; - } - case HTTP_NOT_IMPLEMENTED: { - return "501 Not Implemented"; - break; - } - } - - return "?"; -} - -static size_t file_data(const char *path, char **data) { - FILE *file = fopen(path, "rb"); - if (!file) { - return 0; - } - - /* Retrieve file size */ - fseek(file, 0, SEEK_END); - const size_t content_length = ftell(file); - errno = 0; - rewind(file); - if (errno != 0) { - fclose(file); - CWS_LOG_ERROR("Unable to rewind"); - - return 0; - } - - /* Retrieve file data */ - *data = malloc(content_length); - if (!*data) { - fclose(file); - CWS_LOG_ERROR("Unable to allocate file data"); - - return 0; - } - - /* Read file data */ - size_t read_bytes = fread(*data, 1, content_length, file); - fclose(file); - - if (read_bytes != content_length) { - CWS_LOG_ERROR("Partial read from file"); - - return 0; - } - - return content_length; -} - -static cws_server_ret http_send_resource(cws_http_s *request) { - /* Retrieve correct Content-Type */ - char content_type[CWS_HTTP_CONTENT_TYPE]; - http_get_content_type(request->location_path->data, content_type); - CWS_LOG_DEBUG("content-type: %s", content_type); - - /* TODO: Check for keep-alive */ - - char *data = NULL; - char *response; - size_t content_length = file_data(request->location_path->data, &data); - - size_t response_len = - http_response_builder(&response, HTTP_OK, content_type, data, content_length); - - ssize_t sent = cws_send_data(request->sockfd, response, response_len, 0); - CWS_LOG_DEBUG("Sent %zd bytes", sent); - - free(response); - free(data); - - return CWS_SERVER_OK; -} - -static size_t http_simple_html(char **response, cws_http_status_e status, char *title, - char *description) { - char body[512]; - memset(body, 0, sizeof body); - - snprintf(body, sizeof(body), - "\n" - "\n" - " %s\n" - "\n" - "\n" - "

%s

\n" - "" - "", - title, description); - size_t body_len = strlen(body); - - size_t response_len = http_response_builder(response, status, "text/html", body, body_len); - - return response_len; -} - -cws_http_s *cws_http_parse(string_s *request_str) { - if (!request_str || !request_str->data) { - return NULL; - } - - cws_http_s *request = http_new(); - if (request == NULL) { - return NULL; - } - - char *str = strdup(request_str->data); - if (!str) { - cws_http_free(request); - return NULL; - } - char *str_free = str; - - /* Parse HTTP method */ - str += strspn(str, " "); - if (*str == '\0') { - free(str_free); - cws_http_free(request); - return NULL; - } - size_t len = strcspn(str, " "); - if (str[len] == '\0') { - free(str_free); - cws_http_free(request); - return NULL; - } - str[len] = '\0'; - CWS_LOG_DEBUG("method: %s", str); - request->method = http_parse_method(str); - str += len + 1; - - /* Parse location */ - str += strspn(str, " "); - if (*str == '\0') { - free(str_free); - cws_http_free(request); - return NULL; - } - len = strcspn(str, " "); - if (str[len] == '\0') { - free(str_free); - cws_http_free(request); - return NULL; - } - str[len] = '\0'; - string_append(request->location, str); - str += len + 1; - - /* Adjust location path */ - /* @TODO: fix path traversal */ - string_append(request->location_path, "www"); - if (strcmp(request->location->data, "/") == 0) { - string_append(request->location_path, "/index.html"); - } else { - string_append(request->location_path, request->location->data); - } - CWS_LOG_DEBUG("location path: %s", request->location_path->data); - - /* Parse HTTP version */ - str += strspn(str, " \t"); - if (*str == '\0') { - free(str_free); - cws_http_free(request); - return NULL; - } - len = strcspn(str, "\r\n"); - if (len == 0) { - free(str_free); - cws_http_free(request); - return NULL; - } - str[len] = '\0'; - CWS_LOG_DEBUG("version: %s", str); - string_append(request->http_version, str); - str += len; - - /* Parse headers until a blank line (\r\n\r\n) */ - request->headers = - hm_new(my_str_hash_fn, my_str_equal_fn, my_str_free_fn, my_str_free_fn, - sizeof(char) * CWS_HTTP_HEADER_MAX, sizeof(char) * CWS_HTTP_HEADER_CONTENT_MAX); - - str += strspn(str, "\r\n"); - - while (*str != '\0' && *str != '\r') { - char *line_end = strstr(str, "\r\n"); - if (!line_end) { - break; - } - *line_end = '\0'; - - char *colon = strchr(str, ':'); - if (!colon) { - str = line_end + 2; - continue; - } - - *colon = '\0'; - - char *header_key = str; - char *header_value = colon + 1; - header_value += strspn(header_value, " \t"); - - char hk[CWS_HTTP_HEADER_MAX]; - char hv[CWS_HTTP_HEADER_CONTENT_MAX]; - - strncpy(hk, header_key, sizeof(hk) - 1); - hk[sizeof(hk) - 1] = '\0'; - - strncpy(hv, header_value, sizeof(hv) - 1); - hv[sizeof(hv) - 1] = '\0'; - - hm_set(request->headers, hk, hv); - - /* Move to the next line */ - str = line_end + 2; - } - - free(str_free); - - /* TODO: Parse body */ - /* str is at the beginning of the body */ - - return request; -} - -static size_t http_header_len(char *status_code, char *content_type, size_t body_len) { - size_t len = snprintf(NULL, 0, - "HTTP/1.1 %s\r\n" - "Content-Type: %s\r\n" - "Content-Length: %zu\r\n" - "Connection: close\r\n" - "\r\n", - status_code, content_type, body_len); - - return len; -} - -size_t http_response_builder(char **response, cws_http_status_e status, char *content_type, - char *body, size_t body_len_bytes) { - char *status_code = http_status_string(status); - - size_t header_len = http_header_len(status_code, content_type, body_len_bytes); - size_t total_len = header_len + body_len_bytes; - - *response = malloc(total_len + 1); - if (*response == NULL) { - return 0; - } - - snprintf(*response, header_len + 1, - "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", - status_code, content_type, body_len_bytes); - - /* Only append body if we have it */ - if (body && body_len_bytes > 0) { - memcpy(*response + header_len, body, body_len_bytes); - } - - (*response)[total_len] = '\0'; - - return total_len; -} - -void cws_http_send_response(cws_http_s *request, cws_http_status_e status) { - char *response = NULL; - - switch (status) { - case HTTP_OK: - http_send_resource(request); - break; - case HTTP_NOT_FOUND: { - size_t len = http_simple_html(&response, HTTP_NOT_FOUND, "404 Not Found", - "Resource not found, 404."); - cws_send_data(request->sockfd, response, len, 0); - - break; - } - case HTTP_NOT_IMPLEMENTED: { - size_t len = http_simple_html(&response, HTTP_NOT_IMPLEMENTED, "501 Not Implemented", - "Method not implemented, 501."); - cws_send_data(request->sockfd, response, len, 0); - - break; - } - } - - free(response); -} - -void cws_http_free(cws_http_s *request) { - if (!request) { - return; - } - - if (request->headers) { - hm_free(request->headers); - } - - if (request->http_version) { - string_free(request->http_version); - } - - if (request->location) { - string_free(request->location); - } - - if (request->location_path) { - string_free(request->location_path); - } - - free(request); -} diff --git a/src/http/mime.c b/src/http/mime.c new file mode 100644 index 0000000..dd1ab37 --- /dev/null +++ b/src/http/mime.c @@ -0,0 +1,28 @@ +#include "http/mime.h" +#include +#include + +#include "http/request.h" + +static mimetype mimetypes[] = {{"html", "text/html"}, + {"css", "text/css"}, + {"js", "application/javascript"}, + {"jpg", "image/jpeg"}, + {"png", "image/png"}}; + +int http_get_content_type(char *location_path, char *content_type) { + /* Find last occurrence of a string */ + char *ptr = strrchr(location_path, '.'); + if (ptr == NULL) { + return -1; + } + ptr += 1; + + for (size_t i = 0; i < ARR_SIZE(mimetypes); ++i) { + if (!strcmp(ptr, mimetypes[i].ext)) { + snprintf(content_type, CWS_HTTP_CONTENT_TYPE - 1, "%s", mimetypes[i].type); + } + } + + return 0; +} diff --git a/src/http/request.c b/src/http/request.c new file mode 100644 index 0000000..645fd80 --- /dev/null +++ b/src/http/request.c @@ -0,0 +1,190 @@ +#include "http/request.h" + +#include +#include +#include +#include +#include +#include + +#include "utils/debug.h" +#include "utils/hash_utils.h" + +static cws_http_s *http_new() { + cws_http_s *request = malloc(sizeof(cws_http_s)); + if (!request) { + return NULL; + } + memset(request, 0, sizeof *request); + + request->http_version = string_new("", 16); + request->location = string_new("", 128); + request->location_path = string_new("", 128); + + return request; +} + +static cws_http_method_e http_parse_method(const char *method) { + if (strcmp(method, "GET") == 0) { + return HTTP_GET; + } + + if (strcmp(method, "POST") == 0) { + return HTTP_POST; + } + + return HTTP_UNKNOWN; +} + +cws_http_s *cws_http_parse(string_s *request_str) { + if (!request_str || !request_str->data) { + return NULL; + } + + cws_http_s *request = http_new(); + if (request == NULL) { + return NULL; + } + + char *str = strdup(request_str->data); + if (!str) { + cws_http_free(request); + return NULL; + } + char *str_free = str; + + /* Parse HTTP method */ + str += strspn(str, " "); + if (*str == '\0') { + free(str_free); + cws_http_free(request); + return NULL; + } + size_t len = strcspn(str, " "); + if (str[len] == '\0') { + free(str_free); + cws_http_free(request); + return NULL; + } + str[len] = '\0'; + CWS_LOG_DEBUG("method: %s", str); + request->method = http_parse_method(str); + str += len + 1; + + /* Parse location */ + str += strspn(str, " "); + if (*str == '\0') { + free(str_free); + cws_http_free(request); + return NULL; + } + len = strcspn(str, " "); + if (str[len] == '\0') { + free(str_free); + cws_http_free(request); + return NULL; + } + str[len] = '\0'; + string_append(request->location, str); + str += len + 1; + + /* Adjust location path */ + /* @TODO: fix path traversal */ + string_append(request->location_path, "www"); + if (strcmp(request->location->data, "/") == 0) { + string_append(request->location_path, "/index.html"); + } else { + string_append(request->location_path, request->location->data); + } + CWS_LOG_DEBUG("location path: %s", request->location_path->data); + + /* Parse HTTP version */ + str += strspn(str, " \t"); + if (*str == '\0') { + free(str_free); + cws_http_free(request); + return NULL; + } + len = strcspn(str, "\r\n"); + if (len == 0) { + free(str_free); + cws_http_free(request); + return NULL; + } + str[len] = '\0'; + CWS_LOG_DEBUG("version: %s", str); + string_append(request->http_version, str); + str += len; + + /* Parse headers until a blank line (\r\n\r\n) */ + request->headers = + hm_new(my_str_hash_fn, my_str_equal_fn, my_str_free_fn, my_str_free_fn, + sizeof(char) * CWS_HTTP_HEADER_MAX, sizeof(char) * CWS_HTTP_HEADER_CONTENT_MAX); + + str += strspn(str, "\r\n"); + + while (*str != '\0' && *str != '\r') { + char *line_end = strstr(str, "\r\n"); + if (!line_end) { + break; + } + *line_end = '\0'; + + char *colon = strchr(str, ':'); + if (!colon) { + str = line_end + 2; + continue; + } + + *colon = '\0'; + + char *header_key = str; + char *header_value = colon + 1; + header_value += strspn(header_value, " \t"); + + char hk[CWS_HTTP_HEADER_MAX]; + char hv[CWS_HTTP_HEADER_CONTENT_MAX]; + + strncpy(hk, header_key, sizeof(hk) - 1); + hk[sizeof(hk) - 1] = '\0'; + + strncpy(hv, header_value, sizeof(hv) - 1); + hv[sizeof(hv) - 1] = '\0'; + + hm_set(request->headers, hk, hv); + + /* Move to the next line */ + str = line_end + 2; + } + + free(str_free); + + /* TODO: Parse body */ + /* str is at the beginning of the body */ + + return request; +} + +void cws_http_free(cws_http_s *request) { + if (!request) { + return; + } + + if (request->headers) { + hm_free(request->headers); + } + + if (request->http_version) { + string_free(request->http_version); + } + + if (request->location) { + string_free(request->location); + } + + if (request->location_path) { + string_free(request->location_path); + } + + free(request); +} diff --git a/src/http/response.c b/src/http/response.c new file mode 100644 index 0000000..d466e61 --- /dev/null +++ b/src/http/response.c @@ -0,0 +1,174 @@ +#include "http/response.h" + +#include "http/mime.h" +#include "utils/debug.h" +#include +#include +#include +#include +#include + +size_t http_simple_html(char **response, cws_http_status_e status, char *title, char *description) { + char body[512] = {0}; + + snprintf(body, sizeof(body), + "\n" + "\n" + " %s\n" + "\n" + "\n" + "

%s

\n" + "" + "", + title, description); + size_t body_len = strlen(body); + + size_t response_len = http_response_builder(response, status, "text/html", body, body_len); + + return response_len; +} + +static size_t http_header_len(char *status_code, char *content_type, size_t body_len) { + size_t len = snprintf(NULL, 0, + "HTTP/1.1 %s\r\n" + "Content-Type: %s\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n", + status_code, content_type, body_len); + + return len; +} + +static char *http_status_string(cws_http_status_e status) { + switch (status) { + case HTTP_OK: { + return "200 OK"; + break; + } + case HTTP_NOT_FOUND: { + return "404 Not Found"; + break; + } + case HTTP_NOT_IMPLEMENTED: { + return "501 Not Implemented"; + break; + } + } + + return "?"; +} + +static size_t file_data(const char *path, char **data) { + FILE *file = fopen(path, "rb"); + if (!file) { + return 0; + } + + /* Retrieve file size */ + fseek(file, 0, SEEK_END); + const size_t content_length = ftell(file); + errno = 0; + rewind(file); + if (errno != 0) { + fclose(file); + CWS_LOG_ERROR("Unable to rewind"); + + return 0; + } + + /* Retrieve file data */ + *data = malloc(content_length); + if (!*data) { + fclose(file); + CWS_LOG_ERROR("Unable to allocate file data"); + + return 0; + } + + /* Read file data */ + size_t read_bytes = fread(*data, 1, content_length, file); + fclose(file); + + if (read_bytes != content_length) { + CWS_LOG_ERROR("Partial read from file"); + + return 0; + } + + return content_length; +} + +static void http_send_resource(cws_http_s *request) { + /* Retrieve correct Content-Type */ + char content_type[CWS_HTTP_CONTENT_TYPE]; + http_get_content_type(request->location_path->data, content_type); + CWS_LOG_DEBUG("content-type: %s", content_type); + + /* TODO: Check for keep-alive */ + + char *data = NULL; + char *response; + size_t content_length = file_data(request->location_path->data, &data); + + size_t response_len = + http_response_builder(&response, HTTP_OK, content_type, data, content_length); + + ssize_t sent = cws_send_data(request->sockfd, response, response_len, 0); + CWS_LOG_DEBUG("Sent %zd bytes", sent); + + free(response); + free(data); +} + +size_t http_response_builder(char **response, cws_http_status_e status, char *content_type, + char *body, size_t body_len_bytes) { + char *status_code = http_status_string(status); + + size_t header_len = http_header_len(status_code, content_type, body_len_bytes); + size_t total_len = header_len + body_len_bytes; + + *response = malloc(total_len + 1); + if (*response == NULL) { + return 0; + } + + snprintf(*response, header_len + 1, + "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", + status_code, content_type, body_len_bytes); + + /* Only append body if we have it */ + if (body && body_len_bytes > 0) { + memcpy(*response + header_len, body, body_len_bytes); + } + + (*response)[total_len] = '\0'; + + return total_len; +} + +void cws_http_send_response(cws_http_s *request, cws_http_status_e status) { + char *response = NULL; + + switch (status) { + case HTTP_OK: + http_send_resource(request); + break; + case HTTP_NOT_FOUND: { + size_t len = http_simple_html(&response, HTTP_NOT_FOUND, "404 Not Found", + "Resource not found, 404."); + cws_send_data(request->sockfd, response, len, 0); + + break; + } + case HTTP_NOT_IMPLEMENTED: { + size_t len = http_simple_html(&response, HTTP_NOT_IMPLEMENTED, "501 Not Implemented", + "Method not implemented, 501."); + cws_send_data(request->sockfd, response, len, 0); + + break; + } + } + + free(response); +} diff --git a/src/main.c b/src/main.c index 4ec9bc8..f8b4932 100644 --- a/src/main.c +++ b/src/main.c @@ -5,7 +5,7 @@ #include #include "config/config.h" -#include "server/server.h" +#include "core/server.h" #include "utils/debug.h" void cws_signal_handler(int) { diff --git a/src/meson.build b/src/meson.build index d867c88..6a91fbc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,9 +1,14 @@ server = files('main.c') server += files( - 'server/epoll_utils.c', - 'server/server.c', - 'server/worker.c', + 'core/epoll_utils.c', + 'core/server.c', + 'core/socket_utils.c', + 'core/worker.c', ) server += files('config/config.c') -server += files('utils/socket.c', 'utils/utils.c') -server += files('http/http.c') +server += files('utils/hash_utils.c', 'utils/net_utils.c') +server += files( + 'http/mime.c', + 'http/request.c', + 'http/response.c', +) diff --git a/src/server/epoll_utils.c b/src/server/epoll_utils.c deleted file mode 100644 index 474d384..0000000 --- a/src/server/epoll_utils.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "server/epoll_utils.h" - -#include - -#include "utils/debug.h" -#include "utils/utils.h" - -cws_server_ret cws_epoll_add(int epfd, int sockfd) { - struct epoll_event event; - event.events = EPOLLIN; - event.data.fd = sockfd; - const int status = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event); - - if (status != 0) { - CWS_LOG_ERROR("epoll_ctl_add()"); - return CWS_SERVER_EPOLL_ADD_ERROR; - } - - return CWS_SERVER_OK; -} - -cws_server_ret cws_epoll_del(int epfd, int sockfd) { - const int status = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); - - if (status != 0) { - CWS_LOG_ERROR("epoll_ctl_del()"); - return CWS_SERVER_EPOLL_DEL_ERROR; - } - - return CWS_SERVER_OK; -} - -int cws_epoll_create_with_fd(int fd) { - int epfd = epoll_create1(0); - if (epfd == -1) { - return -1; - } - if (cws_epoll_add(epfd, fd) != CWS_SERVER_OK) { - return -1; - } - - return epfd; -} diff --git a/src/utils/hash_utils.c b/src/utils/hash_utils.c new file mode 100644 index 0000000..400f0c8 --- /dev/null +++ b/src/utils/hash_utils.c @@ -0,0 +1,51 @@ +#include "utils/hash_utils.h" + +#include +#include +#include + +unsigned int my_str_hash_fn(const void *key) { + char *key_str = (char *)key; + size_t key_len = strlen(key_str); + + int total = 0; + + for (size_t i = 0; i < key_len; ++i) { + total += (int)key_str[i]; + } + + return total % 2069; +} + +bool my_str_equal_fn(const void *a, const void *b) { + if (strcmp((char *)a, (char *)b) == 0) { + return true; + } + + return false; +} + +void my_str_free_fn(void *value) { + free(value); +} + +unsigned int my_int_hash_fn(const void *key) { + return *(int *)key; +} + +bool my_int_equal_fn(const void *a, const void *b) { + int ai = *(int *)a; + int bi = *(int *)b; + + if (ai == bi) { + return true; + } + + return false; +} + +void my_int_free_key_fn(void *key) { + int fd = *(int *)key; + close(fd); + free(key); +} diff --git a/src/utils/net_utils.c b/src/utils/net_utils.c new file mode 100644 index 0000000..c12a661 --- /dev/null +++ b/src/utils/net_utils.c @@ -0,0 +1,31 @@ +#include "utils/net_utils.h" + +#include +#include +#include + +volatile sig_atomic_t cws_server_run = 1; + +static void cws_utils_convert_ip(int family, void *addr, char *ip, size_t ip_len) { + inet_ntop(family, addr, ip, ip_len); +} + +void cws_utils_get_client_ip(struct sockaddr_storage *sa, char *ip) { + if (sa->ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + cws_utils_convert_ip(AF_INET, &sin->sin_addr, ip, INET_ADDRSTRLEN); + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + cws_utils_convert_ip(AF_INET6, &sin6->sin6_addr, ip, INET6_ADDRSTRLEN); + } +} + +cws_server_ret cws_fd_set_nonblocking(int sockfd) { + const int status = fcntl(sockfd, F_SETFL, O_NONBLOCK); + + if (status == -1) { + return CWS_SERVER_FD_NONBLOCKING_ERROR; + } + + return CWS_SERVER_OK; +} diff --git a/src/utils/utils.c b/src/utils/utils.c deleted file mode 100644 index f3a3b15..0000000 --- a/src/utils/utils.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "utils/utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -volatile sig_atomic_t cws_server_run = 1; - -static void cws_utils_convert_ip(int family, void *addr, char *ip, size_t ip_len) { - inet_ntop(family, addr, ip, ip_len); -} - -void cws_utils_get_client_ip(struct sockaddr_storage *sa, char *ip) { - if (sa->ss_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in *)sa; - cws_utils_convert_ip(AF_INET, &sin->sin_addr, ip, INET_ADDRSTRLEN); - } else if (sa->ss_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; - cws_utils_convert_ip(AF_INET6, &sin6->sin6_addr, ip, INET6_ADDRSTRLEN); - } -} - -cws_server_ret cws_fd_set_nonblocking(int sockfd) { - const int status = fcntl(sockfd, F_SETFL, O_NONBLOCK); - - if (status == -1) { - return CWS_SERVER_FD_NONBLOCKING_ERROR; - } - - return CWS_SERVER_OK; -} - -unsigned int my_str_hash_fn(const void *key) { - char *key_str = (char *)key; - size_t key_len = strlen(key_str); - - int total = 0; - - for (size_t i = 0; i < key_len; ++i) { - total += (int)key_str[i]; - } - - return total % 2069; -} - -bool my_str_equal_fn(const void *a, const void *b) { - if (strcmp((char *)a, (char *)b) == 0) { - return true; - } - - return false; -} - -void my_str_free_fn(void *value) { - free(value); -} - -unsigned int my_int_hash_fn(const void *key) { - return *(int *)key; -} - -bool my_int_equal_fn(const void *a, const void *b) { - int ai = *(int *)a; - int bi = *(int *)b; - - if (ai == bi) { - return true; - } - - return false; -} - -void my_int_free_key_fn(void *key) { - int fd = *(int *)key; - close(fd); - free(key); -}