diff --git a/httpd/.gitignore b/httpd/.gitignore index a20dfd8..035927b 100644 --- a/httpd/.gitignore +++ b/httpd/.gitignore @@ -6,3 +6,4 @@ *.log *.core httpd +__pycache__ diff --git a/httpd/src/daemon/daemon.c b/httpd/src/daemon/daemon.c index 99c3826..3ceb251 100644 --- a/httpd/src/daemon/daemon.c +++ b/httpd/src/daemon/daemon.c @@ -48,9 +48,9 @@ int start_daemon(void) pid_t pid = fork(); if (!pid) // Daemon { - start_server("localhost", config->servers->port); + start_server(config->servers->ip, config->servers->port); } - else + else // Parent { // Write pid int err = write_pid(config->pid_file, pid); @@ -59,8 +59,6 @@ int start_daemon(void) kill(pid, SIGINT); return 1; } - - exit(1); } return 0; } diff --git a/httpd/src/http/http.c b/httpd/src/http/http.c index 7152ffd..896866b 100644 --- a/httpd/src/http/http.c +++ b/httpd/src/http/http.c @@ -97,18 +97,30 @@ static bool 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) + + char *target = string_to_charptr(req->target); + int err = is_directory(target); + free(target); + err = 0; // TODO fix that bug + + if (err == -1) // Doesn't exists return false; else if (err == 1) { + // 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 == 0; } + // Exists return true; } @@ -160,6 +172,19 @@ 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 + char *protocol = string_to_charptr(req->protocol); + if (strcmp(protocol, HTTP_VERSION) != 0) + resp->status_code = 505; + free(protocol); +} + // === Functions void http_init(struct config *cfg) @@ -168,7 +193,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); @@ -193,7 +218,7 @@ void handle_request(int client_fd) return; char *method = get_http_method(req->method); char *target = string_to_charptr(req->target); - log_request(method, target, "127.0.0.1"); + log_request(method, target, client_ip); free(method); free(target); @@ -230,6 +255,13 @@ void handle_request(int client_fd) sendfile(client_fd, fd, 0, atoi(cl_str)); } + // Log response + method = get_http_method(req->method); + target = string_to_charptr(req->target); + log_response(resp->status_code, method, target, client_ip); + free(method); + free(target); + // Free string_destroy(str); string_destroy(res); @@ -275,6 +307,10 @@ struct http_response *generate_response(struct http_request *req) char *protocol = HTTP_VERSION; res->protocol = string_create(protocol, strlen(protocol)); + // Target + str_concat_string(config->servers->root_dir, + strlen(config->servers->root_dir), req->target); + // Status code if (req->status_code == 0) { @@ -284,23 +320,34 @@ struct http_response *generate_response(struct http_request *req) res->status_code = 200; } - // Status msg - res->status_msg = generate_status_message(res->status_code); + 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 = + 404; // TODO replace by 403 once find_target is fixed + } } append_header(&res->headers, create_header("Connection", "close")); + // Status msg + res->status_msg = generate_status_message(res->status_code); + return res; } diff --git a/httpd/src/http/http.h b/httpd/src/http/http.h index 7db5980..c3df90c 100644 --- a/httpd/src/http/http.h +++ b/httpd/src/http/http.h @@ -20,6 +20,7 @@ enum http_method { + INVALID_METHOD, GET, // POST, // PUT, @@ -72,7 +73,7 @@ void http_init(struct config *cfg); * * @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 * diff --git a/httpd/src/logger/errors.c b/httpd/src/logger/errors.c index c5f2c6e..c49a0a7 100644 --- a/httpd/src/logger/errors.c +++ b/httpd/src/logger/errors.c @@ -4,6 +4,7 @@ #include #include +#include #include #include "../utils/time/fmt_time.h" @@ -32,8 +33,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; diff --git a/httpd/src/logger/logs.c b/httpd/src/logger/logs.c index 2f98923..f01cde6 100644 --- a/httpd/src/logger/logs.c +++ b/httpd/src/logger/logs.c @@ -4,6 +4,7 @@ #include #include +#include #include "../utils/time/fmt_time.h" #include "errors.h" @@ -35,8 +36,10 @@ void print_log(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] ", time, config.server_cfg->server_name); + free(time); // Print actual log va_list args; @@ -50,14 +53,12 @@ void print_log(char *format, ...) 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); + print_log("received %s, on '%s' from %s", 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, + print_log("responding with %d to %s for %s on '%s'", status_code, client_ip, request_type, target); } diff --git a/httpd/src/main.c b/httpd/src/main.c index 69ace4b..f03aaeb 100644 --- a/httpd/src/main.c +++ b/httpd/src/main.c @@ -24,7 +24,7 @@ int main(int argc, char **argv) switch (config->daemon) { case NO_OPTION: - start_server("localhost", config->servers->port); + start_server(config->servers->ip, config->servers->port); break; case START: diff --git a/httpd/src/server/server.c b/httpd/src/server/server.c index 850754b..6a50a5b 100644 --- a/httpd/src/server/server.c +++ b/httpd/src/server/server.c @@ -114,7 +114,7 @@ void start_server(const char *host, const char *port) // TODO handle signals to stop - handle_request(client_fd); + handle_request(client_fd, "127.0.0.1"); // send_back(client_fd); close(client_fd); } diff --git a/httpd/src/utils/files/files.c b/httpd/src/utils/files/files.c index fe3e215..0daea41 100644 --- a/httpd/src/utils/files/files.c +++ b/httpd/src/utils/files/files.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 200112L + #include "files.h" #include @@ -5,13 +7,13 @@ // #include "../string/string.h" -// bool file_exists(const char *path) +// int file_exists(const char *path) // {} int is_directory(const char *path) { struct stat path_stat; - if (stat(path, &path_stat) != 0) + if (lstat(path, &path_stat) != 0) return S_ISDIR(path_stat.st_mode); else return -1; diff --git a/httpd/src/utils/files/files.h b/httpd/src/utils/files/files.h index cec66e7..ed0f133 100644 --- a/httpd/src/utils/files/files.h +++ b/httpd/src/utils/files/files.h @@ -40,7 +40,7 @@ int is_directory(const char *path); * * @return */ -char *get_file(const char *path); +// char *get_file(const char *path); /* * @brief @@ -85,7 +85,7 @@ ssize_t get_file_content_size(const char *path); * * @return 0 on success, the corresponding error code otherwise */ -int write_to_file(const char *path, struct string* buf); +int write_to_file(const char *path, struct string *buf); /* * @brief diff --git a/httpd/src/utils/string/string.c b/httpd/src/utils/string/string.c index 78b3879..d02d9de 100644 --- a/httpd/src/utils/string/string.c +++ b/httpd/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,45 @@ 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); + } +} + void string_to_lowercase(struct string *str) { for (size_t i = 0; i < str->size; i++) diff --git a/httpd/src/utils/string/string.h b/httpd/src/utils/string/string.h index 3bec520..0d01688 100644 --- a/httpd/src/utils/string/string.h +++ b/httpd/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 ** diff --git a/httpd/src/utils/time/fmt_time.c b/httpd/src/utils/time/fmt_time.c index a2590e4..cc37959 100644 --- a/httpd/src/utils/time/fmt_time.c +++ b/httpd/src/utils/time/fmt_time.c @@ -1,10 +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); + + // return asctime(gmt_time); + strftime(buf, 64, "%a, %d %b %Y %H:%M:%S %Z", gmt_time); + return buf; } diff --git a/httpd/tests/test_root_dir/index.html b/httpd/tests/test_root_dir/index.html new file mode 100644 index 0000000..f5bf13f --- /dev/null +++ b/httpd/tests/test_root_dir/index.html @@ -0,0 +1 @@ +

YEAAH

diff --git a/httpd/tests/test_suite.py b/httpd/tests/test_suite.py new file mode 100644 index 0000000..37388e2 --- /dev/null +++ b/httpd/tests/test_suite.py @@ -0,0 +1,132 @@ +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)