From b1057adfd09dd7e658b5102e1d271a817dd0d076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soner=20=C3=87=C3=B6kmen?= Date: Mon, 1 Jul 2024 20:06:03 +0200 Subject: [PATCH] feat: http 414, 431 and 505 were implemented, http codes were added --- CMakeLists.txt | 10 +- src/core/http/sn_http_code.c | 14 ++ src/core/http/sn_http_code.h | 138 +++++++++++++++++ .../http/{s_sock_loop.c => sn_sock_loop.c} | 129 +++++++++++----- src/core/{s_logger.c => sn_logger.c} | 14 +- src/core/{s_logger.h => sn_logger.h} | 4 +- test/helpers/test_helpers.c | 11 +- test/runner/test_runner.c | 3 + test/s_sock_loop_test.c | 96 ------------ test/sn_sock_loop_test.c | 146 ++++++++++++++++++ 10 files changed, 414 insertions(+), 151 deletions(-) create mode 100644 src/core/http/sn_http_code.c create mode 100644 src/core/http/sn_http_code.h rename src/core/http/{s_sock_loop.c => sn_sock_loop.c} (65%) rename src/core/{s_logger.c => sn_logger.c} (68%) rename src/core/{s_logger.h => sn_logger.h} (89%) delete mode 100644 test/s_sock_loop_test.c create mode 100644 test/sn_sock_loop_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 15f1ea1..111cc36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,11 @@ target_include_directories(pico # Include snail project add_library(snail STATIC src/snail.h - src/core/http/s_sock_loop.c - src/core/s_logger.h - src/core/s_logger.c + src/core/http/sn_sock_loop.c + src/core/sn_logger.h + src/core/sn_logger.c + src/core/http/sn_http_code.h + src/core/http/sn_http_code.c ) target_include_directories(snail @@ -74,7 +76,7 @@ endif () # SNAIL_TEST # ######################################################################## # Include snail test project -add_executable(snail_test test/s_sock_loop_test.c +add_executable(snail_test test/sn_sock_loop_test.c test/runner/test_runner.h test/runner/test_runner.c test/helpers/test_helpers.h diff --git a/src/core/http/sn_http_code.c b/src/core/http/sn_http_code.c new file mode 100644 index 0000000..dadc84b --- /dev/null +++ b/src/core/http/sn_http_code.c @@ -0,0 +1,14 @@ +#include "sn_http_code.h" + + +#define SN_HTTP_ENUM_GENERATOR(name, msg) case HTTP_ ## name: return msg; + +const char *sn_http_get_description(sn_http_code_t code) { + switch (code) { + SN_HTTP_CODE_MAP(SN_HTTP_ENUM_GENERATOR) + default: + return ""; + } +} + +#undef SN_HTTP_ENUM_GENERATOR diff --git a/src/core/http/sn_http_code.h b/src/core/http/sn_http_code.h new file mode 100644 index 0000000..0d82ca8 --- /dev/null +++ b/src/core/http/sn_http_code.h @@ -0,0 +1,138 @@ +#ifndef SNAIL_SN_HTTP_CODE_H +#define SNAIL_SN_HTTP_CODE_H + + +#define CONTINUE 100 +#define SWITCHING_PROTOCOLS 101 +#define PROCESSING 102 +#define OK 200 +#define CREATED 201 +#define ACCEPTED 202 +#define NON_AUTHORITATIVE_INFORMATION 203 +#define NO_CONTENT 204 +#define RESET_CONTENT 205 +#define PARTIAL_CONTENT 206 +#define MULTI_STATUS 207 +#define ALREADY_REPORTED 208 +#define IM_USED 226 +#define MULTIPLE_CHOICES 300 +#define MOVED_PERMANENTLY 301 +#define FOUND 302 +#define SEE_OTHER 303 +#define NOT_MODIFIED 304 +#define USE_PROXY 305 +#define SWITCH_PROXY 306 +#define TEMPORARY_REDIRECT 307 +#define PERMANENT_REDIRECT 308 +#define BAD_REQUEST 400 +#define UNAUTHORIZED 401 +#define PAYMENT_REQUIRED 402 +#define FORBIDDEN 403 +#define NOT_FOUND 404 +#define METHOD_NOT_ALLOWED 405 +#define NOT_ACCEPTABLE 406 +#define PROXY_AUTHENTICATION_REQUIRED 407 +#define REQUEST_TIMEOUT 408 +#define CONFLICT 409 +#define GONE 410 +#define LENGTH_REQUIRED 411 +#define PRECONDITION_FAILED 412 +#define PAYLOAD_TOO_LARGE 413 +#define URI_TOO_LONG 414 +#define UNSUPPORTED_MEDIA_TYPE 415 +#define RANGE_NOT_SATISFIABLE 416 +#define EXPECTATION_FAILED 417 +#define I_AM_A_TEAPOT 418 +#define MISDIRECTED_REQUEST 421 +#define UNPROCESSABLE_ENTITY 422 +#define LOCKED 423 +#define FAILED_DEPENDENCY 424 +#define UPGRADE_REQUIRED 426 +#define PRECONDITION_REQUIRED 428 +#define TOO_MANY_REQUESTS 429 +#define REQUEST_HEADER_FIELDS_TOO_LARGE 431 +#define UNAVAILABLE_FOR_LEGAL_REASONS 451 +#define INTERNAL_SERVER_ERROR 500 +#define NOT_IMPLEMENTED 501 +#define BAD_GATEWAY 502 +#define SERVICE_UNAVAILABLE 503 +#define GATEWAY_TIMEOUT 504 +#define HTTP_VERSION_NOT_SUPPORTED 505 +#define VARIANT_ALSO_NEGOTIATES 506 +#define INSUFFICIENT_STORAGE 507 +#define LOOP_DETECTED 508 +#define NOT_EXTENDED 510 +#define NETWORK_AUTHENTICATION_REQUIRED 511 + +#define SN_HTTP_CODE_MAP(XX) \ + XX(CONTINUE , "Continue") \ + XX(SWITCHING_PROTOCOLS , "Switching Protocols") \ + XX(PROCESSING , "Processing") \ + XX(OK , "OK") \ + XX(CREATED , "Created") \ + XX(ACCEPTED , "Accepted") \ + XX(NON_AUTHORITATIVE_INFORMATION , "Non-Authoritative Information") \ + XX(NO_CONTENT , "No Content") \ + XX(RESET_CONTENT , "Reset Content") \ + XX(PARTIAL_CONTENT , "Partial Content") \ + XX(MULTI_STATUS , "Multi-Status") \ + XX(ALREADY_REPORTED , "Already Reported") \ + XX(IM_USED , "IM Used") \ + XX(MULTIPLE_CHOICES , "Multiple Choices") \ + XX(MOVED_PERMANENTLY , "Moved Permanently") \ + XX(FOUND , "Found") \ + XX(SEE_OTHER , "See Other") \ + XX(NOT_MODIFIED , "Not Modified") \ + XX(USE_PROXY , "Use Proxy") \ + XX(SWITCH_PROXY , "Switch Proxy") \ + XX(TEMPORARY_REDIRECT , "Temporary Redirect ") \ + XX(PERMANENT_REDIRECT , "Permanent Redirect") \ + XX(BAD_REQUEST , "Bad Request") \ + XX(UNAUTHORIZED , "Unauthorized") \ + XX(PAYMENT_REQUIRED , "Payment Required") \ + XX(FORBIDDEN , "Forbidden") \ + XX(NOT_FOUND , "Not Found") \ + XX(METHOD_NOT_ALLOWED , "Method Not Allowed") \ + XX(NOT_ACCEPTABLE , "Not Acceptable") \ + XX(PROXY_AUTHENTICATION_REQUIRED , "Proxy Authentication Required") \ + XX(REQUEST_TIMEOUT , "Request Timeout") \ + XX(CONFLICT , "Conflict") \ + XX(GONE , "Gone") \ + XX(LENGTH_REQUIRED , "Length Required") \ + XX(PRECONDITION_FAILED , "Precondition Failed") \ + XX(PAYLOAD_TOO_LARGE , "Payload Too Large") \ + XX(URI_TOO_LONG , "URI Too Long") \ + XX(UNSUPPORTED_MEDIA_TYPE , "Unsupported Media Type") \ + XX(RANGE_NOT_SATISFIABLE , "Range Not Satisfiable") \ + XX(EXPECTATION_FAILED , "Expectation Failed") \ + XX(I_AM_A_TEAPOT , "I'm a teapot") \ + XX(MISDIRECTED_REQUEST , "Misdirected Request") \ + XX(UNPROCESSABLE_ENTITY , "Unprocessable Content") \ + XX(LOCKED , "Locked") \ + XX(FAILED_DEPENDENCY , "Failed Dependency") \ + XX(UPGRADE_REQUIRED , "Upgrade Required") \ + XX(PRECONDITION_REQUIRED , "Precondition Required") \ + XX(TOO_MANY_REQUESTS , "Too Many Requests") \ + XX(REQUEST_HEADER_FIELDS_TOO_LARGE , "Request Header Fields Too Large") \ + XX(UNAVAILABLE_FOR_LEGAL_REASONS , "Unavailable For Legal Reasons") \ + XX(INTERNAL_SERVER_ERROR , "Internal Server Error") \ + XX(NOT_IMPLEMENTED , "Not Implemented") \ + XX(BAD_GATEWAY , "Bad Gateway") \ + XX(SERVICE_UNAVAILABLE , "Service Unavailable") \ + XX(GATEWAY_TIMEOUT , "Gateway Timeout") \ + XX(HTTP_VERSION_NOT_SUPPORTED , "HTTP Version Not Supported") \ + XX(VARIANT_ALSO_NEGOTIATES , "Variant Also Negotiates") \ + XX(INSUFFICIENT_STORAGE , "Insufficient Storage") \ + XX(LOOP_DETECTED , "Loop Detected") \ + XX(NOT_EXTENDED , "Not Extended") \ + XX(NETWORK_AUTHENTICATION_REQUIRED , "Network Authentication Required") \ + +typedef enum { +#define DEF_HTTP_ENUM(code, _) HTTP_ ## code = code, + SN_HTTP_CODE_MAP(DEF_HTTP_ENUM) +#undef DEF_HTTP_ENUM +} sn_http_code_t; + +const char *sn_http_get_description(sn_http_code_t code); + +#endif //SNAIL_SN_HTTP_CODE_H \ No newline at end of file diff --git a/src/core/http/s_sock_loop.c b/src/core/http/sn_sock_loop.c similarity index 65% rename from src/core/http/s_sock_loop.c rename to src/core/http/sn_sock_loop.c index e113e46..487da58 100644 --- a/src/core/http/s_sock_loop.c +++ b/src/core/http/sn_sock_loop.c @@ -3,15 +3,16 @@ #include #include #include -#include "../s_logger.h" +#include "sn_http_code.h" +#include "../sn_logger.h" - -#define HTTP_MAX_HEADER_SIZE (4096) // 4KB for request headers. -#define HTTP_MAX_URI_SIZE (1024) // 1KB for request URI. -#define HTTP_MAX_BODY_SIZE (1024 * 1024) // 1MB for request body. -#define HTTP_MAX_HEADER_COUNT ((int)(HTTP_MAX_HEADER_SIZE / 8)) // Num of maximum headers. -#define INC_SOCKET_BUF_SIZE (65536) -#define TCP_MAX_CON_BACKLOG (128) +#define HTTP_MAX_TOTAL_HEADER_SIZE (4096) // 4KB for request headers. +#define HTTP_MAX_SINGLE_HEADER_SIZE (1024) // 1KB for single request header. +#define HTTP_MAX_URI_SIZE (1024) // 1KB for request URI. +#define HTTP_MAX_BODY_SIZE (1024 * 1024) // 1MB for request body. +#define HTTP_MAX_HEADER_COUNT ((int)(HTTP_MAX_TOTAL_HEADER_SIZE / 8)) // Num of maximum headers. +#define INC_SOCKET_BUF_SIZE (65536) +#define TCP_MAX_CON_BACKLOG (128) typedef struct sock_http_data { const char *path; @@ -37,7 +38,7 @@ typedef struct sock_loop_data { * UV: uv_close_cb * https://docs.libuv.org/en/v1.x/handle.html#c.uv_close_cb */ -static void close_callback(uv_handle_t *handle) { +static void on_close_callback(uv_handle_t *handle) { if (handle != NULL) { free(handle); } @@ -59,8 +60,8 @@ static void sock_http_data_free(sock_http_data *loop_data) { static void sock_loop_data_free(sock_loop_data *loop_data) { if (loop_data->tcp_client != NULL && !uv_is_closing((uv_handle_t *) loop_data->tcp_client)) { - uv_read_stop((uv_stream_t *)loop_data->tcp_client); - uv_close((uv_handle_t *) loop_data->tcp_client, close_callback); + uv_read_stop((uv_stream_t *) loop_data->tcp_client); + uv_close((uv_handle_t *) loop_data->tcp_client, on_close_callback); } if (loop_data->work_req != NULL) { @@ -75,50 +76,99 @@ static void sock_loop_data_free(sock_loop_data *loop_data) { free(loop_data); } -static inline SN_INLINE void clear_socket_objects(uv_stream_t *stream, const uv_buf_t *buf) { +static inline SN_INLINE void clear_loop_request(uv_stream_t *stream, const uv_buf_t *buf) { sock_loop_data_free((sock_loop_data *) stream->data); - free(buf->base); + if (buf != NULL) { + free(buf->base); + } } /* * UV: uv_write_cb * https://docs.libuv.org/en/v1.x/stream.html#c.uv_write_cb */ -static void handle_response_sent(uv_write_t *req, SN_UNUSED int status) { +static void on_write_callback(uv_write_t *req, SN_UNUSED int status) { sock_loop_data_free((sock_loop_data *) req->data); } +static void write_response(sock_loop_data *loop_data) { + uv_buf_t buf = uv_buf_init(loop_data->http_data->response_buf, strlen(loop_data->http_data->response_buf) + 1); + uv_write_t *write_req = malloc(sizeof(uv_write_t)); + if (write_req == NULL) { + sn_log_err("Cannot create write_req object.\n"); + sock_loop_data_free(loop_data); + return; + } + loop_data->write_req = write_req; + loop_data->write_req->data = loop_data; + uv_write(loop_data->write_req, (uv_stream_t *) loop_data->tcp_client, &buf, 1, on_write_callback); +} + /* * UV: uv_work_cb * https://docs.libuv.org/en/v1.x/threadpool.html#c.uv_work_cb */ -static void handle_request(uv_work_t *work_req) { +static void on_work_callback(uv_work_t *work_req) { sock_loop_data *loop_data = work_req->data; loop_data->http_data->response_buf = strdup( - "HTTP/1.1 200 OK\nContent-Type: text/html\n\nHello World

Hello World

"); + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nHello World

Hello World

"); } /* * UV: uv_after_work_cb * https://docs.libuv.org/en/v1.x/threadpool.html#c.uv_after_work_cb */ -static void handle_response(uv_work_t *req, int status) { +static void on_after_work_callback(uv_work_t *req, int status) { sock_loop_data *loop_data = req->data; if (status == UV_ECANCELED) { sn_log_info("Request was cancelled: %s\n", uv_strerror(status)); sock_loop_data_free(loop_data); return; } - uv_buf_t buf = uv_buf_init(loop_data->http_data->response_buf, strlen(loop_data->http_data->response_buf) + 1); - uv_write_t *write_req = malloc(sizeof(uv_write_t)); - if (write_req == NULL) { - sn_log_err("Cannot create write_req object.\n"); - sock_loop_data_free(loop_data); + write_response(loop_data); +} + +static sn_http_code_t check_fail_fast_err(sock_http_data *http_data, int minor_version) { + size_t total_header_size = 0; + if (minor_version != 0 && minor_version != 1) { + return HTTP_VERSION_NOT_SUPPORTED; + } + if (http_data->path_length > HTTP_MAX_URI_SIZE) { + return HTTP_URI_TOO_LONG; + } + for (int i = 0; i < http_data->header_length; i++) { + size_t header_length = http_data->headers[i].name_len + http_data->headers[i].value_len; + if (header_length > HTTP_MAX_SINGLE_HEADER_SIZE) { + return HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE; + } + total_header_size += header_length; + if (total_header_size > HTTP_MAX_TOTAL_HEADER_SIZE) { + return HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE; + } + } + sn_log_err("HED: %zu\n", total_header_size); + return -1; +} + +static void send_fail_fast_response(sock_loop_data *loop_data, sn_http_code_t code) { + const char *response_text = sn_http_get_description(code); + sn_log_err("Http early return with Code=%d, Details=%s\n", code, response_text); + asprintf(&(loop_data->http_data->response_buf), + "HTTP/1.1 %d %s\r\nContent-Type: text/plain\r\nContent-Length: %lu\r\n\r\n%s", + code, response_text, strlen(response_text) + 1, response_text + ); + write_response(loop_data); +} + +static void dispatch_request(sock_loop_data *loop_data) { + loop_data->work_req = malloc(sizeof(uv_work_t)); + if (loop_data->work_req == NULL) { + sn_log_err("Cannot create work_req object.\n"); + clear_loop_request((uv_stream_t *) loop_data->tcp_client, NULL); return; } - loop_data->write_req = write_req; - loop_data->write_req->data = loop_data; - uv_write(loop_data->write_req, (uv_stream_t *) loop_data->tcp_client, &buf, 1, handle_response_sent); + loop_data->work_req->data = loop_data; + uv_queue_work(uv_default_loop(), loop_data->work_req, on_work_callback, on_after_work_callback); } /* @@ -140,11 +190,12 @@ static void on_buffer_alloc_callback(SN_UNUSED uv_handle_t *handle, SN_UNUSED si * https://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb */ static void on_read_callback(uv_stream_t *stream, ssize_t buf_size, const uv_buf_t *buf) { + sn_http_code_t fail_fast_code; sock_loop_data *loop_data = stream->data; if (buf_size == -1 || buf_size == UV_EOF) { sn_log_err("Cannot read socket: %zd\n", buf_size); - clear_socket_objects(stream, buf); + clear_loop_request(stream, buf); return; } @@ -152,7 +203,7 @@ static void on_read_callback(uv_stream_t *stream, ssize_t buf_size, const uv_buf loop_data->http_data = malloc(sizeof(sock_http_data)); if (loop_data->http_data == NULL) { sn_log_err("Cannot create http_data object.\n"); - clear_socket_objects(stream, buf); + clear_loop_request(stream, buf); return; } loop_data->http_data->header_length = HTTP_MAX_HEADER_COUNT; @@ -161,13 +212,13 @@ static void on_read_callback(uv_stream_t *stream, ssize_t buf_size, const uv_buf sock_http_data *http_data = loop_data->http_data; http_data->buf_cursor = http_data->buf_length; http_data->buf_length = http_data->buf_length + buf_size; - if (http_data->request_buf == NULL) { - http_data->request_buf = buf->base; + if (http_data->buf_length == 0) { + memcpy(http_data->request_buf, buf->base, buf_size); } else { void *allocated_memory = realloc(http_data->request_buf, http_data->buf_length); if (allocated_memory == NULL) { sn_log_err("Cannot allocate memory for incoming stream.\n"); - clear_socket_objects(stream, buf); + clear_loop_request(stream, buf); return; } http_data->request_buf = allocated_memory; @@ -182,23 +233,25 @@ static void on_read_callback(uv_stream_t *stream, ssize_t buf_size, const uv_buf if (req_size == -1) { sn_log_err("Cannot parse the HTTP request.\n"); - clear_socket_objects(stream, buf); + clear_loop_request(stream, buf); return; } if (req_size == -2) { + free(buf->base); return; } - loop_data->work_req = malloc(sizeof(uv_work_t)); - if (loop_data->work_req == NULL) { - sn_log_err("Cannot create work_req object.\n"); - clear_socket_objects(stream, buf); + free(buf->base); + uv_read_stop(stream); + fail_fast_code = check_fail_fast_err(http_data, minor_version); + + if (fail_fast_code == -1) { + dispatch_request(loop_data); return; } - loop_data->work_req->data = loop_data; - uv_read_stop(stream); - uv_queue_work(uv_default_loop(), loop_data->work_req, handle_request, handle_response); + + send_fail_fast_response(loop_data, fail_fast_code); } /* diff --git a/src/core/s_logger.c b/src/core/sn_logger.c similarity index 68% rename from src/core/s_logger.c rename to src/core/sn_logger.c index 20556d1..e54707c 100644 --- a/src/core/s_logger.c +++ b/src/core/sn_logger.c @@ -1,18 +1,18 @@ #include #include -#include "s_logger.h" +#include "sn_logger.h" static int min_log_level = SN_LOG_LEVEL_DEBUG; #define GET_OUTPUT_DEV(LEVEL) ((LEVEL < SN_LOG_LEVEL_ERROR) ? stdout : stderr) -#define WRITE_LOG(LEVEL, FMT) if (min_log_level <= LEVEL) { \ - va_list args; \ - va_start(args, FMT); \ - vfprintf(GET_OUTPUT_DEV(LEVEL), fmt, args); \ - va_end(args); \ - } +#define WRITE_LOG(LEVEL, FMT) if (min_log_level <= LEVEL) { \ + va_list args; \ + va_start(args, FMT); \ + vfprintf(GET_OUTPUT_DEV(LEVEL), fmt, args); \ + va_end(args); \ + } void sn_log_set_level(int level) { if (level <= SN_LOG_LEVEL_DEBUG) { diff --git a/src/core/s_logger.h b/src/core/sn_logger.h similarity index 89% rename from src/core/s_logger.h rename to src/core/sn_logger.h index 9050cb9..d282a78 100644 --- a/src/core/s_logger.h +++ b/src/core/sn_logger.h @@ -1,5 +1,5 @@ -#ifndef SNAIL_S_LOGGER_H -#define SNAIL_S_LOGGER_H +#ifndef SNAIL_SN_LOGGER_H +#define SNAIL_SN_LOGGER_H #include "../snail.h" diff --git a/test/helpers/test_helpers.c b/test/helpers/test_helpers.c index 1f69099..0984562 100644 --- a/test/helpers/test_helpers.c +++ b/test/helpers/test_helpers.c @@ -38,9 +38,9 @@ static socket_handler_t open_socket(int attempt, const char *host, const char *p } static http_code_t parse_status_code(const char *http_response) { - int handled, status = -1; - handled = sscanf(http_response, "HTTP/1.1 %d", &status); - if (handled < 1) { + int handled, status = -1, minor = 1; + handled = sscanf(http_response, "HTTP/1.%d %d", &minor, &status); + if (handled < 2) { return -1; } return status; @@ -60,7 +60,10 @@ http_code_t th_read_http_code(socket_handler_t sock_fd) { break; } if (byte_received == 0) { - http_code = parse_status_code(response); + http_code = -1; + if (response != NULL) { + http_code = parse_status_code(response); + } break; } if (response == NULL) { diff --git a/test/runner/test_runner.c b/test/runner/test_runner.c index c4e3b16..469317c 100644 --- a/test/runner/test_runner.c +++ b/test/runner/test_runner.c @@ -85,6 +85,9 @@ bool tr_run_suit(tr_test_suit *suit) { if (tear_up_result.status == false) { fprintf(stderr, " %d: CASE: %s\nSTATUS: TEAR-UP FAILED\nERROR:%s\n", i + 1, active_case->name, tear_up_result.output); suit_result = false; + if (active_case->tear_down != NULL) { + active_case->tear_down(arg); + } continue; } arg = tear_up_result.data; diff --git a/test/s_sock_loop_test.c b/test/s_sock_loop_test.c deleted file mode 100644 index 180826c..0000000 --- a/test/s_sock_loop_test.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "runner/test_runner.h" -#include "helpers/test_helpers.h" - -tr_test_result create_socket() { - socket_handler_t sock_fd = th_connect_server("0.0.0.0", "3000", 5, 1); - - if (sock_fd < 0) { - return tr_fail("Cannot create socket.\n"); - } - - socket_handler_t *args = malloc(sizeof (socket_handler_t)); - if (args == NULL) { - return tr_fail("Cannot allocate memory.\n"); - } - *args = sock_fd; - - return tr_success_ext(args, "Socket created.\n"); -} - -tr_test_result close_socket(const void *args) { - if (args == NULL) { - return tr_success("Socket is empty.\n"); - } - - socket_handler_t sock_fd = *((socket_handler_t*)args); - close(sock_fd); - - return tr_success("Socket was closed.\n"); -} - -tr_test_result minimal_http_request_case(const void *args) { - int http_code; - ssize_t byte_sent = 0, total_byte_sent = 0; - size_t request_size = 0; - socket_handler_t sock_fd = *((socket_handler_t*)args); - - char *http_request = "GET /index.html HTTP/1.1\r\nHost:localhost\r\nContent-Type:application/json\r\n\r\n"; - request_size = (size_t)strlen(http_request); - - while(1) { - if (total_byte_sent >= request_size) { - break; - } - int buf_size = (5 < (request_size - total_byte_sent)) ? 5 : (request_size - total_byte_sent); - byte_sent = send(sock_fd, http_request + total_byte_sent, buf_size, 0); - if (byte_sent < 0) { - return tr_fail("Cannot send data.\n"); - } - total_byte_sent += byte_sent; - } - - http_code = th_read_http_code(sock_fd); - - if (http_code == 200) { - return tr_success("Http:200"); - } - - return tr_fail("Expected: 200, Found: %d", http_code); -} - -int run_tests() { - tr_test_suit *suit = tr_new_suit("HTTP PARSE SUIT"); - - tr_add_test_case(suit, - tr_new_case("Minimal HTTP Request", create_socket, minimal_http_request_case, close_socket)); - - return tr_run_suit(suit); -} - -int main() { - pid_t pid = fork(); - - if (pid < 0) { - fprintf(stderr, "Cannot fork process!"); - return EXIT_FAILURE; - } - - if (pid == 0) { - sn_listen(3000); - return EXIT_SUCCESS; - } - - int status = run_tests(); - - kill(pid, SIGTERM); - - return status == 1 ? EXIT_SUCCESS : EXIT_FAILURE; -} \ No newline at end of file diff --git a/test/sn_sock_loop_test.c b/test/sn_sock_loop_test.c new file mode 100644 index 0000000..9f355fa --- /dev/null +++ b/test/sn_sock_loop_test.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "runner/test_runner.h" +#include "helpers/test_helpers.h" + +const char *generate_rand_str(int length) { + char *str = malloc((sizeof(char) * length) + 1); + for (int i = 0; i < length - 1; i++){ + str[i] = (char) (rand() % 26 + 65); + } + str[length] = 0; + return str; +} + +tr_test_result create_socket() { + socket_handler_t sock_fd = th_connect_server("0.0.0.0", "3000", 5, 1); + + if (sock_fd < 0) { + return tr_fail("Cannot create socket.\n"); + } + + socket_handler_t *args = malloc(sizeof(socket_handler_t)); + if (args == NULL) { + return tr_fail("Cannot allocate memory.\n"); + } + + *args = sock_fd; + + return tr_success_ext(args, "Socket created.\n"); +} + +tr_test_result close_socket(const void *args) { + if (args == NULL) { + return tr_success("Socket is empty.\n"); + } + + close(*((socket_handler_t *) args)); + + return tr_success("Socket was closed.\n"); +} + +tr_test_result send_request_assert_response(socket_handler_t sock_fd, const char *request, http_code_t expected_http_code) { + http_code_t actual_http_code; + ssize_t byte_sent = 0, total_byte_sent = 0; + size_t request_size = 0; + request_size = (size_t) strlen(request); + + while (1) { + if (total_byte_sent >= request_size) { + break; + } + byte_sent = send(sock_fd, request + total_byte_sent, request_size - total_byte_sent, 0); + if (byte_sent < 0) { + return tr_fail("Cannot send data.\n"); + } + total_byte_sent += byte_sent; + } + + actual_http_code = th_read_http_code(sock_fd); + + if (actual_http_code == expected_http_code) { + return tr_success("Expected %d, Found: %d", expected_http_code, actual_http_code); + } + + return tr_fail("Expected %d, Found: %d", expected_http_code, actual_http_code); +} + +tr_test_result valid_http_request(const void *args) { + char *request = "GET /index.html HTTP/1.1\r\nHost:localhost\r\n\r\n"; + return send_request_assert_response(*((socket_handler_t *) args), request, 200); +} + +tr_test_result invalid_http_version(const void *args) { + char *request = "GET /index.html HTTP/1.3\r\nHost:localhost\r\n\r\n"; + return send_request_assert_response(*((socket_handler_t *) args), request, 505); +} + +tr_test_result too_long_uri(const void *args) { + char *request; + asprintf(&request, "GET /%s HTTP/1.1\r\nHost:localhost\r\n\r\n", generate_rand_str(1025)); + return send_request_assert_response(*((socket_handler_t *) args), request, 414); +} + +tr_test_result too_large_header_single(const void *args) { + char *request; + asprintf(&request, "GET /path HTTP/1.1\r\nHeader-Name:%s\r\n\r\n", generate_rand_str(1015)); + return send_request_assert_response(*((socket_handler_t *) args), request, 431); +} + +tr_test_result too_large_header_total(const void *args) { + char *request; + char *headers[4]; + + for (int i = 0; i < 4; i++) { + asprintf(&headers[i], "Header-%d: %s", i + 1, generate_rand_str(1016)); + } + + asprintf(&request, "GET /path HTTP/1.1\r\nHost:localhost\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", + headers[0], headers[1], headers[2], headers[3]); + + return send_request_assert_response(*((socket_handler_t *) args), request, 431); +} + +int run_tests() { + tr_test_suit *suit = tr_new_suit("HTTP PARSE SUIT"); + + tr_add_test_case(suit, + tr_new_case("Valid HTTP Request", create_socket, valid_http_request, close_socket)); + tr_add_test_case(suit, + tr_new_case("Invalid HTTP Version", create_socket, invalid_http_version, close_socket)); + tr_add_test_case(suit, + tr_new_case("Too Long URI", create_socket, too_long_uri, close_socket)); + tr_add_test_case(suit, + tr_new_case("Too Large Header (Single)", create_socket, too_large_header_single, close_socket)); + tr_add_test_case(suit, + tr_new_case("Too Large Header (Total)", create_socket, too_large_header_total, close_socket)); + return tr_run_suit(suit); +} + +int main() { + srand(time(NULL)); + pid_t pid = fork(); + + if (pid < 0) { + fprintf(stderr, "Cannot fork process.\n"); + return EXIT_FAILURE; + } + + if (pid == 0) { + sn_listen(3000); + return EXIT_SUCCESS; + } + + int status = run_tests(); + + kill(pid, SIGTERM); + + return status == 1 ? EXIT_SUCCESS : EXIT_FAILURE; +} \ No newline at end of file