diff --git a/httpd/.gitignore b/.gitignore similarity index 70% rename from httpd/.gitignore rename to .gitignore index a20dfd8..85e1c00 100644 --- a/httpd/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ *.log *.core httpd +__pycache__ +env/ diff --git a/httpd/Makefile b/Makefile similarity index 78% rename from httpd/Makefile rename to Makefile index 70108ef..0682316 100644 --- a/httpd/Makefile +++ b/Makefile @@ -31,8 +31,14 @@ TARGET=httpd $(TARGET): $(OBJS) $(CC) -o $@ $(OBJS) $(LDFLAGS) $(LDLIBS) -check: - dash tests/run.sh +check: $(TARGET) + cp $(TARGET) tests/$(TARGET) + cd tests + python3 -m venv env + env/bin/python -m pip install requests + env/bin/python -m pip install pytest + env/bin/python -m pip install pytest-timeout + - env/bin/pytest debug: CFLAGS += $(CFLAGS_DBG) debug: $(OBJS) @@ -45,5 +51,7 @@ asan: $(OBJS) $(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LDLIBS) clean: + - pkill -9 $(TARGET) + $(RM) tests/$(TARGET) tests/out.log $(RM) $(TARGET) $(RM) $(OBJS) diff --git a/httpd/src/logger/logs.c b/httpd/src/logger/logs.c deleted file mode 100644 index 2f98923..0000000 --- a/httpd/src/logger/logs.c +++ /dev/null @@ -1,63 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include "logs.h" - -#include -#include - -#include "../utils/time/fmt_time.h" -#include "errors.h" - -// === Static variables - -static struct logs_config config; - -// === Functions - -int log_init(struct config *global_config) -{ - int return_value = 0; - config.enabled = global_config->log; - config.server_cfg = global_config->servers; - config.logfile_fd = open(global_config->log_file, O_WRONLY); - if (config.logfile_fd <= 0) - { - config.enabled = false; - return_value = 1; - } - errlog_init(config.enabled, config.logfile_fd); - return return_value; -} - -void print_log(char *format, ...) -{ - if (!config.enabled) - return; - - // Log prefix (time and server name) - dprintf(config.logfile_fd, "%s [%s] ", get_time(), - config.server_cfg->server_name); - - // Print actual log - va_list args; - va_start(args, format); - vdprintf(config.logfile_fd, format, args); - va_end(args); - - // New line - dprintf(config.logfile_fd, "\n"); -} - -void log_request(char *request_type, char *target, char *client_ip) -{ - print_log("received %s, on '%s' from %s", get_time(), - config.server_cfg->server_name, request_type, target, client_ip); -} - -void log_response(int status_code, char *request_type, char *target, - char *client_ip) -{ - print_log("responding with %d to %s for %s on '%s'", get_time(), - config.server_cfg->server_name, status_code, client_ip, - request_type, target); -} diff --git a/httpd/src/server/server.h b/httpd/src/server/server.h deleted file mode 100644 index 407ec1a..0000000 --- a/httpd/src/server/server.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef SERVER_H -#define SERVER_H - -// #define _POSIX_C_SOURCE 200112L - -/* @brief - * - * @param hpstname - * @param port - */ -void start_server(const char* host, const char* port); - -#endif // ! SERVER_H diff --git a/httpd/src/utils/files/files.c b/httpd/src/utils/files/files.c deleted file mode 100644 index a4a30ee..0000000 --- a/httpd/src/utils/files/files.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "files.h" - -#include -// #include -#include - -// #include "../string/string.h" - -// bool file_exists(const char *path) -// {} - -int is_directory(const char *path) -{ - struct stat path_stat; - if (stat(path, &path_stat) != 0) - return S_ISDIR(path_stat.st_mode); - else - return -1; -} - -// TODO handle logging -// struct string *get_file_content(const char *path) -// { -// // Open file -// FILE *stream = fopen(path, "r"); -// if (stream == NULL) -// return NULL; - -// // Alloc result -// char buf[BUFFER_SIZE]; -// struct string *res = string_create(NULL, 0); -// if (res == NULL) -// { -// return NULL; -// } - -// int nread; -// while ((fgets(buf, BUFFER_SIZE, stream))) -// } - -// TODO not implemented -bool check_filename(struct string *path) -{ - if (path == NULL || path->size <= 0) - return false; - - return true; -} - -ssize_t get_file_content_size(const char *path) -{ - FILE *stream = fopen(path, "r"); - if (stream == NULL) - return -2; - - fseek(stream, 0, SEEK_END); - ssize_t res = ftell(stream); - fclose(stream); - - return res; -} diff --git a/httpd/src/utils/files/files.h b/httpd/src/utils/files/files.h deleted file mode 100644 index 2afc287..0000000 --- a/httpd/src/utils/files/files.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef FILES_H -#define FILES_H - -// === Definitions - -#define BUFFER_SIZE 1024 - -// === Includes - -#include -#include -#include - -#include "../string/string.h" - -// === Functions - -/* - * @brief - * - * @param path - * - * @return - */ -// bool file_exists(const char *path); - -/* - * @brief - * - * @param path - * - * @return 1 if path is a directory, 0 if it is not and -1 if path is not valid - */ -int is_directory(const char* path); - -/* - * @brief - * - * @param path - * - * @return - */ - char* get_file(const char* path); - - -/* - * @brief - * - * @param path - * - * @return - */ -struct string* get_file_content(const char *path); - -/* - * @brief - * - * @param path - * - * @return - */ -bool check_filename(struct string* path); - -/* - * @brief - * - * @param filename - * - * @return - */ -bool sanitize_filename(struct string* filename); - -/* - * @brief - * - * @param path - * - * @return - */ - ssize_t get_file_content_size(const char* path); - -#endif // ! FILES_H diff --git a/httpd/src/utils/time/fmt_time.c b/httpd/src/utils/time/fmt_time.c deleted file mode 100644 index a2590e4..0000000 --- a/httpd/src/utils/time/fmt_time.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "fmt_time.h" - -#include - -char *get_time(void) -{ - time_t local_ts = time(NULL); - struct tm *gmt_time = gmtime(&local_ts); - return asctime(gmt_time); -} diff --git a/httpd/src/config/config.c b/src/config/config.c similarity index 90% rename from httpd/src/config/config.c rename to src/config/config.c index d910424..c8edde6 100644 --- a/httpd/src/config/config.c +++ b/src/config/config.c @@ -5,8 +5,8 @@ #include #include -#include "../utils/string/string.h" -#include "bits/getopt_ext.h" +// #include "../utils/string/string.h" +// #include "bits/getopt_ext.h" #define ARG_VALID 0 #define ARG_INVALID 1 @@ -180,6 +180,19 @@ static void print_arg_error(int err, char **argv, struct option options[], } } +// static void apply_default_values(struct config *cfg) +// { +// // Default file +// if (cfg->servers->default_file == NULL) +// { +// char *default_df = DEFAULT_DF; +// cfg->servers->default_file = +// malloc((strlen(default_df) + 1) * sizeof(char)); +// // TODO handle error +// strcpy(cfg->servers->default_file, default_df); +// } +// } + // == Main functions struct config *parse_configuration(int argc, char *argv[]) @@ -227,10 +240,12 @@ struct config *parse_configuration(int argc, char *argv[]) } } + // apply_default_values(config); + // Check config validity if (check_config(config) != 0) { - printf("%s: Missing mandatory flags, cannot continue.", argv[0]); + printf("%s: Missing mandatory flags, cannot continue.\n", argv[0]); config_destroy(config); return NULL; } diff --git a/httpd/src/config/config.h b/src/config/config.h similarity index 94% rename from httpd/src/config/config.h rename to src/config/config.h index 5caf4ac..72be5a8 100644 --- a/httpd/src/config/config.h +++ b/src/config/config.h @@ -6,6 +6,9 @@ #include +// Default values +#define DEFAULT_DF "index.html" + /* ** @brief Enum daemon ** NO_OPTION if the '--daemon' option is not given @@ -33,7 +36,7 @@ struct config char *pid_file; char *log_file; bool log; - char* protocol_version; + char *protocol_version; struct server_config *servers; enum daemon daemon; diff --git a/httpd/src/daemon/daemon.c b/src/daemon/daemon.c similarity index 69% rename from httpd/src/daemon/daemon.c rename to src/daemon/daemon.c index 0d5f52e..a918966 100644 --- a/httpd/src/daemon/daemon.c +++ b/src/daemon/daemon.c @@ -3,8 +3,10 @@ #include #include #include +#include #include "../server/server.h" +#include "../utils/files/files.h" static struct config *config; @@ -15,7 +17,7 @@ void daemon_init(struct config *cfg) config = cfg; } -int get_pid() +int get_pid(void) { FILE *stream = fopen(config->pid_file, "r"); if (stream == NULL) @@ -43,7 +45,21 @@ void stop_daemon(void) int start_daemon(void) { - start_server("localhost", config->servers->port); + pid_t pid = fork(); + if (!pid) // Daemon + { + start_server(config); + } + else // Parent + { + // Write pid + int err = write_pid(config->pid_file, pid); + if (err != 0) + { + kill(pid, SIGINT); + return 1; + } + } return 0; } diff --git a/httpd/src/daemon/daemon.h b/src/daemon/daemon.h similarity index 82% rename from httpd/src/daemon/daemon.h rename to src/daemon/daemon.h index dff31d9..f1dcae8 100644 --- a/httpd/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -7,11 +7,11 @@ * * @return */ -int get_pid(); +int get_pid(void); /* @brief */ -void daemon_init(struct config* cfg); +void daemon_init(struct config *cfg); /* @brief */ diff --git a/httpd/src/http/headers.c b/src/http/headers.c similarity index 83% rename from httpd/src/http/headers.c rename to src/http/headers.c index 5463d92..ce7806c 100644 --- a/httpd/src/http/headers.c +++ b/src/http/headers.c @@ -5,12 +5,15 @@ #include #include "../utils/parsing/words.h" +#include "../utils/string/string.h" void destroy_headers(struct http_header *headers) { while (headers != NULL) { struct http_header *next = headers->next; + string_destroy(headers->field); + string_destroy(headers->value); free(headers); headers = next; } @@ -39,6 +42,12 @@ ssize_t read_value(struct string *str, size_t offset, struct string **res) if (str->size <= offset + nread || str->data[offset + nread] != '\n') return ERR_HTTP_INVALID_INPUT; + // Trim trailing \r + if ((*res)->size > 0 && (*res)->data[(*res)->size - 1] == '\r') + { + (*res)->size--; + } + return nread; } @@ -50,7 +59,8 @@ ssize_t parse_headers(struct http_request *res, struct string *req, // Yes I know I do one useless allocation but I really don't care at this // point - while (req->data[i] != '\n') // ! Blank line + while (i < req->size && req->data[i] != '\n' + && req->data[i] != '\r') // ! Blank line { if (header == NULL) { @@ -70,21 +80,26 @@ ssize_t parse_headers(struct http_request *res, struct string *req, return ERR_HTTP_OUT_OF_MEMORY; // Read field - ssize_t nread = read_field(req, offset, &header->field); + ssize_t nread = read_field(req, i, &header->field); if (nread <= 0) return nread; // Contains error code when negative - i += nread; + i += nread + 1; // Read value - nread = read_value(req, offset, &header->value); + nread = read_value(req, i, &header->value); if (nread <= 0) return nread; // Contains error code when negative i += nread + 1; } - return i + 1; + if (i < req->size && req->data[i] == '\r') + i++; + if (i < req->size && req->data[i] == '\n') + i++; + + return i; } struct http_header *get_header(struct http_header *headers, const char *field) diff --git a/httpd/src/http/headers.h b/src/http/headers.h similarity index 86% rename from httpd/src/http/headers.h rename to src/http/headers.h index 59592de..460cd3e 100644 --- a/httpd/src/http/headers.h +++ b/src/http/headers.h @@ -57,7 +57,7 @@ ssize_t parse_headers(struct http_request *res, struct string *req, * * @return */ -struct http_header* get_header(struct http_header *headers, const char* field); +struct http_header *get_header(struct http_header *headers, const char *field); /* * @brief @@ -67,7 +67,7 @@ struct http_header* get_header(struct http_header *headers, const char* field); * * @return */ -struct http_header* create_header(const char* field, const char* value); +struct http_header *create_header(const char *field, const char *value); /* * @brief diff --git a/httpd/src/http/http.c b/src/http/http.c similarity index 62% rename from httpd/src/http/http.c rename to src/http/http.c index 8977ee0..4fb2280 100644 --- a/httpd/src/http/http.c +++ b/src/http/http.c @@ -2,13 +2,14 @@ #include #include -#include +// #include #include #include #include #include #include +#include "../config/config.h" #include "../logger/logs.h" #include "../utils/files/files.h" #include "../utils/parsing/words.h" @@ -30,16 +31,16 @@ static ssize_t parse_reqline(struct http_request *res, struct string *req) ssize_t i = 0; ssize_t skipped; - if (res == NULL) + if (res == NULL || req == NULL) return ERR_HTTP_INTERNAL_ERROR; // Method - if (strncmp(req->data, "GET", strlen("GET")) == 0) + if (string_compare_n_str(req, "GET", strlen("GET")) == 0) { res->method = GET; i += strlen("GET"); } - else if (strncmp(req->data, "HEAD", strlen("HEAD")) == 0) + else if (string_compare_n_str(req, "HEAD", strlen("HEAD")) == 0) { res->method = HEAD; i += strlen("HEAD"); @@ -67,9 +68,15 @@ static ssize_t parse_reqline(struct http_request *res, struct string *req) return ERR_HTTP_INVALID_INPUT; i += skipped; - // CRLF (EOL) - if (req->data[i++] != '\r' && req->data[i++] != '\n') + // CRLF (EOL) oh qu'il est casse couilles celui-là + ssize_t req_size = req->size; // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah + if (i < req_size && req->data[i] == '\r') + i++; + if (i >= req_size || req->data[i] != '\n') return ERR_HTTP_INVALID_INPUT; + i++; + // Donc 2h de debug pour ça là ? Plutot envie de me tirer une balle si vous + // voulez mon avis return i; } @@ -91,25 +98,34 @@ static void split_target(struct http_request *req) } } -// Finds a valid path based on the client input -static bool find_target(struct http_request *req) +// Finds a valid path based on the client input and returns the corresponding +// FILES return value (see files.h) +static int find_target(struct http_request *req) { // Check filename if (!check_filename(req->target)) - return false; - int err = is_directory(req->target->data); - if (err == -1) - return false; - else if (err == 1) + return ERR_FILES_FORBIDDEN; + + char *target = string_to_charptr(req->target); + int err = is_directory(target); + free(target); + + if (err == FILES_DIR) // Is a directory { + // Append default file if directory if (req->target->data[req->target->size - 1] != '/') string_concat_str(req->target, "/", 1); string_concat_str(req->target, config->servers->default_file, strlen(config->servers->default_file)); + // Recheck + target = string_to_charptr(req->target); + err = is_directory(target); + free(target); + return err; } - return true; + return err; } // WARNING allocates result on the heap @@ -160,6 +176,51 @@ static struct string *generate_status_message(int status_code) return string_create(message, strlen(message)); } +static void check_req(struct http_request *req, struct http_response *resp) +{ + // Method + if (req->method == INVALID_METHOD) + resp->status_code = 405; + + // Protocol + // Oui il y a plus beau, mais on a le temps ou on l'a pas, moi je l'ai pas, + // alors si t'es pas content t'as qu'à le modifier toi même vu que tu as + // visiblement le code source. <3 + if (string_compare_strictly_n_str(req->protocol, "HTTP/", strlen("HTTP/")) + != 0) + resp->status_code = 400; + else if (string_compare_strictly_n_str(req->protocol, "HTTP/1.1", + strlen("HTTP/1.1")) + != 0) + resp->status_code = 505; + + // Host + if (resp->status_code != 400 && resp->status_code != 505) + { + int host_count = 0; + struct http_header *cur = req->headers; + while (cur != NULL) + { + if (cur->field->size == 4 + && string_compare_n_str(cur->field, "host", 4) == 0) + { + host_count++; + if (cur->value == NULL || cur->value->size == 0) + { + resp->status_code = 400; + break; + } + } + cur = cur->next; + } + + if (host_count != 1) + resp->status_code = 400; + } + + // printf("%s %d\n", req->protocol->data, resp->status_code); +} + // === Functions void http_init(struct config *cfg) @@ -168,7 +229,7 @@ void http_init(struct config *cfg) } // TODO handle logs -void handle_request(int client_fd) +void handle_request(int client_fd, char *client_ip) { char buffer[BUFFER_SIZE]; // Declared in server.h struct string *str = string_create(NULL, 0); @@ -185,27 +246,39 @@ void handle_request(int client_fd) { string_concat_str(str, buffer, nread); } - string_concat_str(str, buffer, nread); + if (nread > 0) + string_concat_str(str, buffer, nread); // Parse request struct http_request *req = parse_request(str); if (req == NULL) + { + free(str); return; + } char *method = get_http_method(req->method); char *target = string_to_charptr(req->target); - log_request(method, target, "127.0.0.1"); + print_log_request(method, target, client_ip); free(method); free(target); // Generate response struct http_response *resp = generate_response(req); if (resp == NULL) + { + free(str); + free(req); return; + } // Format response to string struct string *res = format_response(resp); if (res == NULL) + { + free(str); + free(req); return; + } // Send response ssize_t nsent; @@ -228,8 +301,18 @@ void handle_request(int client_fd) int fd = open(target, O_RDONLY); if (fd > 0) sendfile(client_fd, fd, 0, atoi(cl_str)); + + free(target); + free(cl_str); } + // Log response + method = get_http_method(req->method); + target = string_to_charptr(req->target); + print_log_response(resp->status_code, method, target, client_ip); + free(method); + free(target); + // Free string_destroy(str); string_destroy(res); @@ -248,7 +331,19 @@ struct http_request *parse_request(struct string *req) size_t i = 0; ssize_t nread = parse_reqline(res, req); if (nread <= 0) - return NULL; + { + if (nread == ERR_HTTP_NOT_IMPLEMENTED) + res->status_code = 501; + else + res->status_code = 400; + + if (res->target == NULL) + res->target = string_create("", 0); + if (res->protocol == NULL) + res->protocol = string_create("HTTP/1.1", 8); + + return res; + } // Split path and query split_target(res); @@ -258,7 +353,10 @@ struct http_request *parse_request(struct string *req) // Headers nread = parse_headers(res, req, i); if (nread <= 0) - return NULL; + { + res->status_code = 400; + return res; + } return res; } @@ -272,35 +370,69 @@ struct http_response *generate_response(struct http_request *req) return NULL; // Protocol - char *protocol = HTTP_VERSION; + // char *protocol = HTTP_VERSION; + char *protocol = "HTTP/1.1"; res->protocol = string_create(protocol, strlen(protocol)); + // Target + if (req->status_code == 0) + { + str_concat_string(config->servers->root_dir, + strlen(config->servers->root_dir), req->target); + } + // Status code if (req->status_code == 0) { - if (!find_target(req)) - res->status_code = 404; - else + switch (find_target(req)) + { + case FILES_REG: res->status_code = 200; + break; + case ERR_FILES_NOT_FOUND: + res->status_code = 404; + break; + default: + res->status_code = 403; + break; + } } + else + res->status_code = req->status_code; - // Status msg - res->status_msg = generate_status_message(res->status_code); + // Check protocol and method + if (req->status_code == 0) + check_req(req, res); // Headers char *time = get_time(); append_header(&res->headers, create_header("Date", time)); - // free(time); // Yes, the one that completely disapeared this year - // Oopa + free(time); // Yes, the one that completely disapeared this year if (res->status_code == 200) { - char buf[21] = { 0 }; // (21 ~= log10(2^64)) + 1 (null byte) + char buf[21] = { 0 }; // (20 ~= log10(2^64)) + 1 (null byte) char *target = string_to_charptr(req->target); - sprintf(buf, "%lu", get_file_content_size(target)); - append_header(&res->headers, create_header("Content-Length", buf)); + ssize_t cl = get_file_content_size(target); + free(target); + if (cl >= 0) + { + sprintf(buf, "%lu", cl); + append_header(&res->headers, create_header("Content-Length", buf)); + } + else + { + res->status_code = 403; + } + } + else + { + append_header(&res->headers, create_header("Content-Length", "0")); } append_header(&res->headers, create_header("Connection", "close")); + // Status msg + res->status_msg = generate_status_message(res->status_code); + return res; } @@ -326,10 +458,6 @@ struct string *format_response(struct http_response *resp) string_concat_str(res, "\r\n", 2); - // Time - char *time = get_time(); - string_concat_str(res, time, strlen(time)); - // Headers struct http_header *cur_header = resp->headers; while (cur_header != NULL) @@ -337,6 +465,11 @@ struct string *format_response(struct http_response *resp) string_concat_str(res, cur_header->field->data, cur_header->field->size); + string_concat_str(res, ": ", 2); + + string_concat_str(res, cur_header->value->data, + cur_header->value->size); + string_concat_str(res, "\r\n", 2); cur_header = cur_header->next; diff --git a/httpd/src/http/http.h b/src/http/http.h similarity index 86% rename from httpd/src/http/http.h rename to src/http/http.h index 0a895da..8c3565c 100644 --- a/httpd/src/http/http.h +++ b/src/http/http.h @@ -13,13 +13,14 @@ // === Includes -#include "../utils/string/string.h" #include "../config/config.h" +#include "../utils/string/string.h" // === Enums enum http_method { + INVALID_METHOD, GET, // POST, // PUT, @@ -42,7 +43,7 @@ struct http_header struct http_request { - enum http_method method; + enum http_method method; struct string *target; struct string *queries; struct string *protocol; @@ -66,13 +67,13 @@ struct http_response * * @param cfg */ - void http_init(struct config* cfg); +void http_init(struct config *cfg); /* @brief Reads, parses the request and responds adequately all-in-one * * @param client_fd */ - void handle_request(int client_fd); +void handle_request(int client_fd, char *client_ip); /* @brief Parses the HTTP request and splits it into a request structure * @@ -81,7 +82,7 @@ struct http_response * @return A pointer to the structure containing the request infos on success, * NULL otherwise */ - struct http_request* parse_request(struct string* req); +struct http_request *parse_request(struct string *req); /* @brief Generates a response to the given request * @@ -90,7 +91,7 @@ struct http_response * @return A pointer to the generated response struct on success, * NULL otherwise */ - struct http_response* generate_response(struct http_request* req); +struct http_response *generate_response(struct http_request *req); /* @brief Formats the given response structure into a valid HTTP response * string @@ -100,7 +101,7 @@ struct http_response * @return A pointer to the string containing the response on success, * NULL otherwise */ - struct string* format_response(struct http_response* resp); +struct string *format_response(struct http_response *resp); /* @brief Free all allocated memory inside req and req itself * diff --git a/httpd/src/logger/errors.c b/src/logger/errors.c similarity index 68% rename from httpd/src/logger/errors.c rename to src/logger/errors.c index c5f2c6e..4bbb369 100644 --- a/httpd/src/logger/errors.c +++ b/src/logger/errors.c @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include "../utils/time/fmt_time.h" #include "logs.h" @@ -15,13 +17,16 @@ static struct logs_config config; // === Functions -void errlog_init(bool enabled, int logfile_fd) +void errlog_init(bool enabled, int logfile_fd, struct server_config *serv_cfg) { config.enabled = enabled; + if (logfile_fd == STDOUT_FILENO) + config.logfile_fd = STDERR_FILENO; config.logfile_fd = logfile_fd; + config.server_cfg = serv_cfg; } -void print_err() +void print_err(void) { print_log_err("%s", get_err()); } @@ -32,8 +37,10 @@ void print_log_err(char *format, ...) return; // Log prefix (time and server name) - dprintf(config.logfile_fd, "%s [%s] ", get_time(), + char *time = get_time(); + dprintf(config.logfile_fd, "%s [%s] ERROR ", time, config.server_cfg->server_name); + free(time); // Print actual log va_list args; @@ -48,7 +55,7 @@ void print_log_err(char *format, ...) fprintf(stderr, "Error: %s", get_err()); } -char *get_err() +char *get_err(void) { return strerror(errno); } diff --git a/httpd/src/logger/errors.h b/src/logger/errors.h similarity index 50% rename from httpd/src/logger/errors.h rename to src/logger/errors.h index 2d9303c..09b8be2 100644 --- a/httpd/src/logger/errors.h +++ b/src/logger/errors.h @@ -4,22 +4,24 @@ #include #include +#include "../config/config.h" + /* @brief Initialize the error logging submodule * @warning Do not use 'as is', use log_init() instead */ -void errlog_init(bool enabled, int logfile_fd); +void errlog_init(bool enabled, int logfile_fd, struct server_config *serv_cfg); /* @brief Retrieves the last error with errno and prints the corresponding * error message in the logs and stderr */ -void print_err(); +void print_err(void); -/* @brief Prints error logs, just like print_log(), and also to stderr +/* @brief Prints error logs, just like print_log() but for errors */ -void print_log_err(char* format, ...); +void print_log_err(char *format, ...); -/* @brief Prints error logs, just like print_log() +/* @brief Returns the string corresponding to the last error that happened */ -char* get_err(); +char *get_err(void); #endif // ! ERRORS_H diff --git a/src/logger/logs.c b/src/logger/logs.c new file mode 100644 index 0000000..f82f706 --- /dev/null +++ b/src/logger/logs.c @@ -0,0 +1,79 @@ +#define _POSIX_C_SOURCE 200809L + +#include "logs.h" + +#include +#include +#include +#include + +#include "../utils/time/fmt_time.h" +#include "errors.h" + +// === Static variables + +static struct logs_config config; + +// === Functions + +int log_init(struct config *global_config) +{ + int return_value = 0; + config.enabled = global_config->log; + config.server_cfg = global_config->servers; + if (global_config->log_file != NULL) + { + config.logfile_fd = open(global_config->log_file, O_WRONLY); + if (config.logfile_fd <= 0) + { + config.enabled = false; + return_value = 1; + } + } + else + { + config.logfile_fd = STDOUT_FILENO; + } + + errlog_init(config.enabled, config.logfile_fd, config.server_cfg); + return return_value; +} + +void print_log(char *format, ...) +{ + if (!config.enabled) + return; + + // Log prefix (time and server name) + char *time = get_time(); + dprintf(config.logfile_fd, "%s [%s] ", time, + config.server_cfg->server_name); + free(time); + + // Print actual log + va_list args; + va_start(args, format); + vdprintf(config.logfile_fd, format, args); + va_end(args); + + // New line + dprintf(config.logfile_fd, "\n"); +} + +void print_log_request(char *request_type, char *target, char *client_ip) +{ + print_log("received %s, on '%s' from %s", request_type, target, client_ip); +} + +void print_log_response(int status_code, char *request_type, char *target, + char *client_ip) +{ + print_log("responding with %d to %s for %s on '%s'", status_code, client_ip, + request_type, target); +} + +void log_terminate(void) +{ + if (config.logfile_fd != 0 && config.logfile_fd != STDOUT_FILENO) + close(config.logfile_fd); +} diff --git a/httpd/src/logger/logs.h b/src/logger/logs.h similarity index 68% rename from httpd/src/logger/logs.h rename to src/logger/logs.h index e8f337d..f18a6d4 100644 --- a/httpd/src/logger/logs.h +++ b/src/logger/logs.h @@ -9,7 +9,7 @@ struct logs_config { bool enabled; int logfile_fd; - struct server_config* server_cfg; + struct server_config *server_cfg; }; /* @brief Initializes the logging module @@ -18,7 +18,7 @@ struct logs_config * * @return 0 on success, an error code otherwise */ -int log_init(struct config* config); +int log_init(struct config *config); /* @brief Prints logs (or not) conformly to the config given by the user. * Works like printf (because it uses it under the hood) with a @@ -27,7 +27,7 @@ int log_init(struct config* config); * @param format * @param ... */ -void print_log(char* format, ...); +void print_log(char *format, ...); /* @brief Prints request logs with the adequate format in the logfile * @@ -35,7 +35,7 @@ void print_log(char* format, ...); * @param target * @param client_ip */ -void log_request(char* request_type, char* target, char* client_ip); +void print_log_request(char *request_type, char *target, char *client_ip); /* @brief Prints response logs with the adequate format in the logfile * @@ -44,8 +44,11 @@ void log_request(char* request_type, char* target, char* client_ip); * @param target * @param client_ip */ -void log_response(int status_code, char* request_type, char* target, char* client_ip); - +void print_log_response(int status_code, char *request_type, char *target, + char *client_ip); +/* @brief Gracefully exits the logs module + */ +void log_terminate(void); #endif // ! LOGS_H diff --git a/httpd/src/main.c b/src/main.c similarity index 89% rename from httpd/src/main.c rename to src/main.c index 8e36192..6f2c5b2 100644 --- a/httpd/src/main.c +++ b/src/main.c @@ -18,12 +18,13 @@ int main(int argc, char **argv) // Initialize modules log_init(config); // Ignore ret val http_init(config); + daemon_init(config); // Start server switch (config->daemon) { case NO_OPTION: - start_server("localhost", config->servers->port); + start_server(config); break; case START: @@ -42,5 +43,6 @@ int main(int argc, char **argv) return 2; } + config_destroy(config); return 0; } diff --git a/httpd/src/server/server.c b/src/server/server.c similarity index 61% rename from httpd/src/server/server.c rename to src/server/server.c index 850754b..56c0c53 100644 --- a/httpd/src/server/server.c +++ b/src/server/server.c @@ -7,16 +7,25 @@ // === Includes #include "server.h" +#include #include +#include #include #include +#include #include #include #include "../http/http.h" #include "../logger/errors.h" +#include "../logger/logs.h" // #include "../logger/logs.h" +// === Static variables + +static int server_socket = 0; +static struct config *config; + // === Static functions // Creates and bind the server communication socket @@ -92,11 +101,40 @@ static int get_socket(const char *hostname, const char *port) // } // } +// Retrieves client ipv4 address and stores it in res +// WARNING: res must be of size INET_ADDRSTRLEN +static void get_ip(struct sockaddr *client_addr, char *res) +{ + // NOTE: exceptionally authorized cast + struct in_addr ipAddr = ((struct sockaddr_in *)client_addr)->sin_addr; + inet_ntop(AF_INET, &ipAddr, res, INET_ADDRSTRLEN); +} + +static void signal_handler(int signal) +{ + print_log("EVENT Signal received: %d", signal); + switch (signal) + { + case SIGINT: { + print_log("STOPPING Stopping server..."); + stop_server(); + config_destroy(config); + exit(0); + } + default: + return; + } +} + // === Functions -void start_server(const char *host, const char *port) +void start_server(struct config *cfg) { - int server_socket = get_socket(host, port); + config = cfg; + const char *host = config->servers->ip; + const char *port = config->servers->port; + + server_socket = get_socket(host, port); if (server_socket == -1) // TODO log that return; @@ -105,19 +143,42 @@ void start_server(const char *host, const char *port) if (err == -1) return; + // Signal handling + struct sigaction siga; + siga.sa_flags = 0; + siga.sa_handler = signal_handler; + // initialize mask + if (sigemptyset(&siga.sa_mask) < 0) + // TODO log that + return; + if (sigaction(SIGINT, &siga, NULL) == -1 + || sigaction(SIGPIPE, &siga, NULL) == -1) + return; + // Main loop while (1) { - int client_fd = accept(server_socket, NULL, NULL); + struct sockaddr client_addr; + socklen_t client_addr_len = sizeof(struct sockaddr); + int client_fd = accept(server_socket, &client_addr, &client_addr_len); if (client_fd == -1) continue; // TODO handle signals to stop - handle_request(client_fd); + // Get ip + char client_ip[INET_ADDRSTRLEN]; + get_ip(&client_addr, client_ip); + + handle_request(client_fd, client_ip); // send_back(client_fd); close(client_fd); } + stop_server(); +} + +void stop_server(void) +{ close(server_socket); } diff --git a/src/server/server.h b/src/server/server.h new file mode 100644 index 0000000..0c441ac --- /dev/null +++ b/src/server/server.h @@ -0,0 +1,19 @@ +#ifndef SERVER_H +#define SERVER_H + +#include "../config/config.h" + +/* @brief Starts the HTTP server + * + * @warn Make sure to initialize modules before calling + * + * @param hostname + * @param port + */ +void start_server(struct config *cfg); + +/* @brief Stops the currently running HTTP server + */ +void stop_server(void); + +#endif // ! SERVER_H diff --git a/src/utils/files/files.c b/src/utils/files/files.c new file mode 100644 index 0000000..0f08c25 --- /dev/null +++ b/src/utils/files/files.c @@ -0,0 +1,105 @@ +#define _POSIX_C_SOURCE 200112L + +#include "files.h" + +#include +#include +#include + +// #include "../string/string.h" + +// int file_exists(const char *path) +// {} + +int is_directory(const char *path) +{ + struct stat path_stat; + if (lstat(path, &path_stat) == 0) + { + if (S_ISDIR(path_stat.st_mode)) // Directory + return FILES_DIR; + else if (S_ISREG(path_stat.st_mode)) // Regular file + return FILES_REG; + else + return FILES_OTHER; + } + else + { + if (errno == ENOENT) // File not found + return ERR_FILES_NOT_FOUND; + else // Other errors + return ERR_FILES_FORBIDDEN; + } +} + +// TODO handle logging +// struct string *get_file_content(const char *path) +// { +// // Open file +// FILE *stream = fopen(path, "r"); +// if (stream == NULL) +// return NULL; + +// // Alloc result +// char buf[BUFFER_SIZE]; +// struct string *res = string_create(NULL, 0); +// if (res == NULL) +// { +// return NULL; +// } + +// int nread; +// while ((fgets(buf, BUFFER_SIZE, stream))) +// } + +// TODO not implemented +bool check_filename(struct string *path) +{ + if (path == NULL || path->size <= 0) + return false; + + return true; +} + +ssize_t get_file_content_size(const char *path) +{ + FILE *stream = fopen(path, "r"); + if (stream == NULL) + return -2; + + fseek(stream, 0, SEEK_END); + ssize_t res = ftell(stream); + fclose(stream); + + return res; +} + +int write_to_file(const char *path, struct string *buf) +{ + FILE *stream = fopen(path, "w"); + if (stream == NULL) + return 1; + + fwrite(buf->data, sizeof(char), buf->size, stream); + + fclose(stream); + + return 0; +} + +int write_pid(const char *filepath, int pid) +{ + FILE *stream = fopen(filepath, "w"); + if (stream == NULL) + return 1; + + if (fprintf(stream, "%d", pid) <= 0) + { + fclose(stream); + return 1; + } + + fclose(stream); + + return 0; +} diff --git a/src/utils/files/files.h b/src/utils/files/files.h new file mode 100644 index 0000000..0c2cdcc --- /dev/null +++ b/src/utils/files/files.h @@ -0,0 +1,109 @@ +#ifndef FILES_H +#define FILES_H + +// === Definitions + +#define BUFFER_SIZE 1024 + +// Return codes +#define FILES_REG 0 +#define FILES_DIR 1 +#define FILES_OTHER 8 + +// Errors +#define ERR_FILES_NOT_FOUND -1 +#define ERR_FILES_FORBIDDEN -2 + +// === Includes + +#include +#include +#include + +#include "../string/string.h" + +// === Functions + +/* + * @brief + * + * @param path + * + * @return + */ +// bool file_exists(const char *path); + +/* + * @brief + * + * @param path + * + * @return Returns the corresponding return value / error code (see header + * definitions) + */ +int is_directory(const char *path); + +/* + * @brief + * + * @param path + * + * @return + */ +// char *get_file(const char *path); + +/* + * @brief + * + * @param path + * + * @return + */ +struct string *get_file_content(const char *path); + +/* + * @brief + * + * @param path + * + * @return + */ +bool check_filename(struct string *path); + +/* + * @brief + * + * @param filename + * + * @return + */ +// bool sanitize_filename(struct string *filename); + +/* + * @brief + * + * @param path + * + * @return + */ +ssize_t get_file_content_size(const char *path); + +/* + * @brief + * + * @param path + * + * @return 0 on success, the corresponding error code otherwise + */ +int write_to_file(const char *path, struct string *buf); + +/* + * @brief + * + * @param path + * + * @return 0 on success, the corresponding error code otherwise + */ +int write_pid(const char *filepath, int pid); + +#endif // ! FILES_H diff --git a/httpd/src/utils/parsing/blanks.c b/src/utils/parsing/blanks.c similarity index 100% rename from httpd/src/utils/parsing/blanks.c rename to src/utils/parsing/blanks.c diff --git a/httpd/src/utils/parsing/blanks.h b/src/utils/parsing/blanks.h similarity index 91% rename from httpd/src/utils/parsing/blanks.h rename to src/utils/parsing/blanks.h index 68c9778..417dc0a 100644 --- a/httpd/src/utils/parsing/blanks.h +++ b/src/utils/parsing/blanks.h @@ -8,8 +8,9 @@ // === Includes #include -#include #include +#include + #include "../string/string.h" // === Functions @@ -33,6 +34,6 @@ bool is_blank(char c); /* * Doc: TODO */ -ssize_t skip_blanks(struct string* str, size_t offset); +ssize_t skip_blanks(struct string *str, size_t offset); #endif // ! BLANKS_H diff --git a/httpd/src/utils/parsing/words.c b/src/utils/parsing/words.c similarity index 100% rename from httpd/src/utils/parsing/words.c rename to src/utils/parsing/words.c diff --git a/httpd/src/utils/parsing/words.h b/src/utils/parsing/words.h similarity index 74% rename from httpd/src/utils/parsing/words.h rename to src/utils/parsing/words.h index 265212b..d259bff 100644 --- a/httpd/src/utils/parsing/words.h +++ b/src/utils/parsing/words.h @@ -1,12 +1,12 @@ #ifndef WORDS_H #define WORDS_H - // === Includes -#include #include +#include #include + #include "../string/string.h" // === Functions @@ -19,7 +19,7 @@ bool str_contains(const char *str, char c); /* * Doc: TODO */ -ssize_t read_word(struct string* str, size_t offset, struct string** res); +ssize_t read_word(struct string *str, size_t offset, struct string **res); /* * Doc: TODO @@ -30,8 +30,8 @@ ssize_t read_word_delim(struct string *str, size_t offset, struct string **res, /* * Doc: TODO */ -ssize_t read_word_restrict(struct string *str, size_t offset, struct string **res, - const char *restr); +ssize_t read_word_restrict(struct string *str, size_t offset, + struct string **res, const char *restr); /* * Doc: TODO diff --git a/httpd/src/utils/string/string.c b/src/utils/string/string.c similarity index 59% rename from httpd/src/utils/string/string.c rename to src/utils/string/string.c index 78b3879..5a28b5a 100644 --- a/httpd/src/utils/string/string.c +++ b/src/utils/string/string.c @@ -67,7 +67,7 @@ void string_concat_str(struct string *str, const char *to_concat, size_t size) } else { - str->data = realloc(str->data, new_size); + str->data = realloc(str->data, new_size * sizeof(char)); if (str->data == NULL) return; // Handle ? } @@ -78,6 +78,46 @@ void string_concat_str(struct string *str, const char *to_concat, size_t size) } } +void str_concat_string(const char *str, size_t size, struct string *to_concat) +{ + size_t new_size = to_concat->size + size; + size_t tmp_size = to_concat->size; + + if (new_size == 0) + return; + + to_concat->size = new_size; + if (tmp_size == 0) + { + to_concat->data = malloc(new_size); + if (to_concat->data == NULL) + return; // Handle ? + } + else + { + // Temporary buffer + char *tmp = malloc(tmp_size * sizeof(char)); + if (tmp == NULL) + return; // Handle ? + + // (Duplicate) + memcpy(tmp, to_concat->data, tmp_size); + + // Reallocate string + char *new_data = realloc(to_concat->data, new_size * sizeof(char)); + if (to_concat->data == NULL) + { + to_concat->size = tmp_size; // Restore (original ptr still valid) + return; // Handle ? + } + to_concat->data = new_data; + + memcpy(to_concat->data, str, size); + memcpy(to_concat->data + size, tmp, tmp_size); + free(tmp); + } +} + void string_to_lowercase(struct string *str) { for (size_t i = 0; i < str->size; i++) @@ -114,3 +154,22 @@ char *string_to_charptr(struct string *str) return res; } + +// WARNING takes n as valid, will not stop on '\0' +int string_compare_strictly_n_str(const struct string *str1, const char *str2, + size_t n) +{ + if (str1->size < n) + return -1; + + size_t i = 0; + int res = 0; + while (i < n) + { + res += str1->data[i]; + res -= str2[i]; + i++; + } + + return res; +} diff --git a/httpd/src/utils/string/string.h b/src/utils/string/string.h similarity index 72% rename from httpd/src/utils/string/string.h rename to src/utils/string/string.h index 62bca63..a69f47c 100644 --- a/httpd/src/utils/string/string.h +++ b/src/utils/string/string.h @@ -42,6 +42,16 @@ int string_compare_n_str(const struct string *str1, const char *str2, size_t n); */ void string_concat_str(struct string *str, const char *to_concat, size_t size); +/* + ** @brief Similar to string_concat_str but with str at the beginning of the + * result string + ** + ** @param str + ** @param to_concat + ** @param size + */ +void str_concat_string(const char *str, size_t size, struct string *to_concat); + /* ** @brief Concat a char * with its size in a struct string ** @@ -63,6 +73,17 @@ void string_destroy(struct string *str); ** ** @return a pointer to an allocated memory zone containing the string */ -char* string_to_charptr(struct string *str); +char *string_to_charptr(struct string *str); +/* + ** @brief TODO + ** + ** @param str1 + ** @param str2 + ** @param n + ** + ** @return + */ +int string_compare_strictly_n_str(const struct string *str1, const char *str2, + size_t n); #endif /* ! STRING_H */ diff --git a/src/utils/time/fmt_time.c b/src/utils/time/fmt_time.c new file mode 100644 index 0000000..cc37959 --- /dev/null +++ b/src/utils/time/fmt_time.c @@ -0,0 +1,15 @@ +#include "fmt_time.h" + +#include +#include + +char *get_time(void) +{ + char *buf = malloc(64 * sizeof(char)); // Oui, 64 + time_t local_ts = time(NULL); + struct tm *gmt_time = gmtime(&local_ts); + + // return asctime(gmt_time); + strftime(buf, 64, "%a, %d %b %Y %H:%M:%S %Z", gmt_time); + return buf; +} diff --git a/httpd/src/utils/time/fmt_time.h b/src/utils/time/fmt_time.h similarity index 93% rename from httpd/src/utils/time/fmt_time.h rename to src/utils/time/fmt_time.h index 43f7877..81d5bd8 100644 --- a/httpd/src/utils/time/fmt_time.h +++ b/src/utils/time/fmt_time.h @@ -6,6 +6,6 @@ * * @return A NULL-terminated string containing the fromatted GMT time */ -char* get_time(); +char *get_time(); #endif // ! FMT_TIME_H diff --git a/httpd/config.txt b/tests/config.txt similarity index 90% rename from httpd/config.txt rename to tests/config.txt index 239d20d..77d4974 100644 --- a/httpd/config.txt +++ b/tests/config.txt @@ -1,6 +1,7 @@ [global] log = true pid_file = /tmp/HTTPd.pid +daemon = start [[vhosts]] server_name = my_server diff --git a/httpd/config_reader.sh b/tests/config_reader.sh similarity index 100% rename from httpd/config_reader.sh rename to tests/config_reader.sh diff --git a/tests/test_root_dir/index.html b/tests/test_root_dir/index.html new file mode 100644 index 0000000..f5bf13f --- /dev/null +++ b/tests/test_root_dir/index.html @@ -0,0 +1 @@ +

YEAAH

diff --git a/tests/test_suite.py b/tests/test_suite.py new file mode 100644 index 0000000..504f05a --- /dev/null +++ b/tests/test_suite.py @@ -0,0 +1,178 @@ +import subprocess as sp +import http +import requests +import socket +import pytest +import time + +host = "127.0.0.1" +port = "6994" + +executable = "./httpd" + +def spawn_httpd(stdout_filename, args=[]): + with open(stdout_filename,"w") as f: + httpd_proc = sp.Popen([executable,"--pid_file","/tmp/HTTPd.pid","--ip",host,"--port", port, "--root_dir","test_root_dir/","--server_name","httpd"] if args == [] else [executable] + args, stdout=f,stderr=sp.PIPE,bufsize=0) + time.sleep(0.2) + + return httpd_proc + +def kill_httpd(proc): + #proc.send_signal(sp.SIGINT) + proc.kill() + +# @pytest.mark.timeout(2) +def test_bad_config(): + proc = spawn_httpd("out.log", ["hello","world"]) + proc.wait(1) + try: + assert proc.returncode == 2 + finally: + kill_httpd(proc) + +# @pytest.mark.timeout(2) +def test_get_index(): + proc = spawn_httpd("out.log") + req = requests.get(f"http://{host}:{port}/index.html") + assert req.status_code == 200 + with open("./test_root_dir/index.html","r") as f: + try: + assert f.read() == req.text + finally: + kill_httpd(proc) + +# @pytest.mark.timeout(2) +def test_get_default(): + proc = spawn_httpd("out.log") + req = requests.get(f"http://{host}:{port}/") + assert req.status_code == 200 + with open("./test_root_dir/index.html","r") as f: + try: + assert f.read() == req.text + finally: + kill_httpd(proc) + +# @pytest.mark.timeout(2) +def test_no_file(): + proc = spawn_httpd("out.log") + req = requests.get(f"http://{host}:{port}/notindex.html") + assert req.status_code == 404 + +# @pytest.mark.timeout(2) +def test_bad_request(): + proc = spawn_httpd("out.log") + sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock.connect((host,int(port))) + + request = f"GET /index.html FTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n" + + sock.sendall(request.encode()) + + resp = sock.recv(1024) + resp_decoded = resp.decode() + + try: + assert "400 Bad Request" in resp_decoded + finally: + kill_httpd(proc) + +# @pytest.mark.timeout(2) +def test_invalid_method(): + proc = spawn_httpd("out.log") + sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock.connect((host,int(port))) + + request = f"PUT /index.html HTTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n" + + sock.sendall(request.encode()) + + response = http.client.HTTPResponse(sock) + response.begin() + + try: + assert response.status == 405 + finally: + kill_httpd(proc) + + +# @pytest.mark.timeout(2) +def test_invalid_version(): + proc = spawn_httpd("out.log") + sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock.connect((host,int(port))) + + request = f"GET /index.html HTTP/1.2\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n" + + sock.sendall(request.encode()) + + response = http.client.HTTPResponse(sock) + response.begin() + + try: + assert response.status == 505 + finally: + kill_httpd(proc) + +# @pytest.mark.timeout(2) +def test_bad_request(): + proc = spawn_httpd("out.log") + sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock.connect((host,int(port))) + + request = f"GET /index.html FTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n" + + sock.sendall(request.encode()) + + response = http.client.HTTPResponse(sock) + response.begin() + + try: + assert response.status == 400 + finally: + kill_httpd(proc) + +@pytest.mark.timeout(2) +def test_head_index(): + proc = spawn_httpd("out.log") + try: + req = requests.head(f"http://{host}:{port}/index.html") + assert req.status_code == 200 + assert req.text == "" + finally: + kill_httpd(proc) + +@pytest.mark.timeout(2) +def test_missing_host(): + proc = spawn_httpd("out.log") + sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock.connect((host,int(port))) + + request = f"GET /index.html HTTP/1.1\r\nConnection: close\r\n\r\n" + + sock.sendall(request.encode()) + + response = http.client.HTTPResponse(sock) + response.begin() + + try: + assert response.status == 400 + finally: + kill_httpd(proc) + +@pytest.mark.timeout(2) +def test_directory_traversal(): + proc = spawn_httpd("out.log") + sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock.connect((host,int(port))) + + request = f"GET /../test_suite.py HTTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n" + + sock.sendall(request.encode()) + + response = http.client.HTTPResponse(sock) + response.begin() + + try: + assert response.status in [400, 403, 404] + finally: + kill_httpd(proc) diff --git a/tests/tests_mieux.sh b/tests/tests_mieux.sh new file mode 100755 index 0000000..6851190 --- /dev/null +++ b/tests/tests_mieux.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Simple test script for HTTP/1.1 Host header compliance +# Usage: ./test_host_compliance.sh [IP] [PORT] + +IP=${1:-"127.0.0.1"} +PORT=${2:-"6996"} + +echo "Targeting server at $IP:$PORT" + +test_req() { + NAME="$1" + PAYLOAD="$2" + EXPECTED="$3" + + echo -n "Test: $NAME ... " + # Send payload, wait max 1s for response + RESP=$(printf "$PAYLOAD" | nc -w 1 $IP $PORT 2>/dev/null | head -n 1) + + if echo "$RESP" | grep -q "$EXPECTED"; then + echo "PASS" + else + echo "FAIL (Expected '$EXPECTED', got '$RESP')" + fi +} + +# 1. Valid Request +test_req "Valid Request" "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" "200 OK" + +# 2. Missing Host Header +test_req "Missing Host" "GET / HTTP/1.1\r\n\r\n" "400 Bad Request" + +# 3. Empty Host Header +test_req "Empty Host" "GET / HTTP/1.1\r\nHost:\r\n\r\n" "400 Bad Request" + +# 4. Multiple Host Headers +test_req "Multiple Hosts" "GET / HTTP/1.1\r\nHost: a\r\nHost: b\r\n\r\n" "400 Bad Request" + +# 5. Bad Protocol Version (Should be 505 now with the fix) +test_req "Bad Protocol (HTTP/1.0)" "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n" "505" \ No newline at end of file