diff --git a/CMakeLists.txt b/CMakeLists.txt index 63caf26..56c51a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,13 @@ enable_testing() add_subdirectory(./tiny_libs/TinyLittleURLUtils) -add_library(tiny_http_server_lib src/tiny_http/tiny_http_server_lib.c src/tiny_http/tiny_http_server_lib.h) +add_library(tiny_http_server_lib STATIC src/tiny_http/tiny_http_server_lib.c src/tiny_http/tiny_http_server_lib.h) +target_include_directories(tiny_http_server_lib PUBLIC src/tiny_http) +target_link_libraries(tiny_http_server_lib PRIVATE tiny_url_encoder_lib) add_executable(assert_tiny_http_server_lib test/assert_tiny_http_server_lib.c) -target_link_libraries(assert_tiny_http_server_lib tiny_http_server_lib) +target_link_libraries(assert_tiny_http_server_lib + PRIVATE tiny_http_server_lib + PRIVATE tiny_url_decoder_lib) add_test(test_tiny_http_server_lib assert_tiny_http_server_lib) diff --git a/src/tiny_http/tiny_http_server_lib.c b/src/tiny_http/tiny_http_server_lib.c index 7bfeb4f..884d842 100644 --- a/src/tiny_http/tiny_http_server_lib.c +++ b/src/tiny_http/tiny_http_server_lib.c @@ -3,6 +3,7 @@ // #include "tiny_http_server_lib.h" +#include "tiny_url_decoder_lib.h" #include #include @@ -17,6 +18,7 @@ enum parse_http_request_status { PARSE_E_MALFORMED_HTTP_REQUEST_LINE = 4, PARSE_E_HTTP_VERSION_NOT_SUPPORTED = 5, PARSE_E_BODY_TOO_LARGE = 6, + PARSE_E_URL_DECODE = 7, }; @@ -47,8 +49,8 @@ void destroy_http_request(http_request *http_request) { http_request->headers_cnt = 0; } // ReSharper disable once CppDFANullDereference - free(http_request->url); - http_request->url = nullptr; + free(http_request->path); + http_request->path = nullptr; free(http_request); } @@ -340,7 +342,52 @@ enum parse_http_request_status parse_http_request_line_from_packet( *ptr < http_packet_len && iter_cnt < settings->max_url_length; (*ptr)++, iter_cnt++) { if (http_packet[(*ptr)] == ' ') { - request->url = strndup((char *) &http_packet[start_uri], *ptr - start_uri); + if (*ptr - start_uri <= 0) { + fprintf(stderr, "malformed request line: not able to find path\n"); + fflush(stderr); + return PARSE_E_MALFORMED_HTTP_REQUEST_LINE; + } + if (*ptr - start_uri == 1) { + if (http_packet[*ptr - 1] == '/') { + request->path = strdup("/"); + (*ptr)++; + break; + } + } + char *raw_path = strndup((char *) &http_packet[start_uri], *ptr - start_uri); + char *tok_state = calloc(*ptr - start_uri + 1, sizeof(char *)); + request->path = calloc(*ptr - start_uri + 1, sizeof(char)); + size_t path_len = 0; + char *token = strtok_r(raw_path, "/", &tok_state); + while (token != nullptr) { + uint8_t *url_decoded = nullptr; + size_t url_decoded_len = 0; + const enum url_decode_result res = url_decode( + (const uint8_t *) token, + strlen(token), + &url_decoded, + &url_decoded_len); + if (res != URL_DEC_OK) { + fprintf(stderr, "cannot decode URL: %s\n", token); + fflush(stderr); + if (url_decoded != nullptr) free(url_decoded); + free(token); + free(tok_state); + free(raw_path); + return PARSE_E_URL_DECODE; + } + *(request->path + path_len) = '/'; + path_len++; + strncpy(request->path + path_len, (char *) url_decoded, url_decoded_len); + path_len += url_decoded_len; + free(url_decoded); + url_decoded = nullptr; + url_decoded_len = 0; + token = strtok_r(nullptr, "/", &tok_state); + } + free(token); + free(tok_state); + free(raw_path); (*ptr)++; break; } diff --git a/src/tiny_http/tiny_http_server_lib.h b/src/tiny_http/tiny_http_server_lib.h index 2d9c649..661a5c9 100644 --- a/src/tiny_http/tiny_http_server_lib.h +++ b/src/tiny_http/tiny_http_server_lib.h @@ -25,7 +25,7 @@ typedef struct http_header { typedef struct http_request { http_version version; http_method method; - char *url; + char *path; http_header **headers; size_t headers_cnt; uint8_t *body; diff --git a/test/assert_tiny_http_server_lib.c b/test/assert_tiny_http_server_lib.c index e5d69ff..045994c 100644 --- a/test/assert_tiny_http_server_lib.c +++ b/test/assert_tiny_http_server_lib.c @@ -29,7 +29,7 @@ void test_request_parse_get_root_curl(void) { assert(http_req != nullptr); assert(http_req->method == GET); assert(http_req->version == HTTP_1_0); - assert(strncmp(http_req->url, "/", 1) == 0); + assert(strncmp(http_req->path, "/", 1) == 0); assert(http_req->headers_cnt == 3); assert(strncmp(http_req->headers[0]->name , "Host", 255) == 0); assert(strncmp(http_req->headers[0]->value, "localhost:8085", 255) == 0); @@ -57,7 +57,7 @@ void test_request_post_root_curl(void) { assert(http_req != nullptr); assert(http_req->method == POST); assert(http_req->version == HTTP_1_0); - assert(strncmp(http_req->url, "/one/two/three", 255) == 0); + assert(strncmp(http_req->path, "/one/two/three", 255) == 0); assert(http_req->headers_cnt == 6); assert(strncmp(http_req->headers[0]->name, "Content-Type", 255) == 0); assert(strncmp(http_req->headers[0]->value, "application/json", 255) == 0); @@ -94,7 +94,7 @@ void test_request_post_root_curl_with_wide_chars(void) { assert(http_req != nullptr); assert(http_req->method == POST); assert(http_req->version == HTTP_1_0); - assert(strncmp(http_req->url, "/one/🐌/three", 255) == 0); + assert(strncmp(http_req->path, "/one/🐌/three", 255) == 0); assert(http_req->headers_cnt == 7); assert(strncmp(http_req->headers[0]->name, "Content-Type", 255) == 0); assert(strncmp(http_req->headers[0]->value, "application/json", 255) == 0); @@ -127,7 +127,7 @@ void test_request_parse_head(void) { assert(http_req != nullptr); assert(http_req->method == HEAD); assert(http_req->version == HTTP_1_0); - assert(strncmp(http_req->url, "/test", 255) == 0); + assert(strncmp(http_req->path, "/test", 255) == 0); assert(http_req->headers_cnt == 3); assert(strncmp(http_req->headers[0]->name, "Host", 255) == 0); assert(strncmp(http_req->headers[0]->value, "localhost:8085", 255) == 0); @@ -209,11 +209,35 @@ void test_response_render_200_with_body(void) { } } +void test_request_parse_get_urlencoded_path() { + const uint8_t request[] = "GET /some%20path%20with%20spaces HTTP/1.0\r\n" + "Host: localhost:8085\r\n" + "User-Agent: curl/7.68.0\r\n" + "Accept: */*\r\n" + "\r\n"; + http_request *http_req = parse_http_request(&settings, request, strlen((char *) request)); + assert(http_req != nullptr); + assert(http_req->method == GET); + assert(http_req->version == HTTP_1_0); + assert(strncmp(http_req->path, "/some path with spaces", 28) == 0); + assert(http_req->headers_cnt == 3); + assert(strncmp(http_req->headers[0]->name, "Host", 255) == 0); + assert(strncmp(http_req->headers[0]->value, "localhost:8085", 255) == 0); + assert(strncmp(http_req->headers[1]->name, "User-Agent", 255) == 0); + assert(strncmp(http_req->headers[1]->value, "curl/7.68.0", 255) == 0); + assert(strncmp(http_req->headers[2]->name, "Accept", 255) == 0); + assert(strncmp(http_req->headers[2]->value, "*/*", 255) == 0); + assert(http_req->body == nullptr); + assert(http_req->body_len == 0); + destroy_http_request(http_req); +} + int main() { test_request_parse_get_root_curl(); test_request_post_root_curl(); test_request_post_root_curl_with_wide_chars(); test_request_parse_head(); + test_request_parse_get_urlencoded_path(); test_response_render_200_no_body(); test_response_render_404_no_body();