From 7a3ae740a2bbe081d558791fb6acd1eb08811ee3 Mon Sep 17 00:00:00 2001 From: Samuel Vishesh Paul Date: Thu, 31 Oct 2024 01:38:40 +0530 Subject: [PATCH] Add POST method support and handle HTTP body parsing This update introduces support for the POST method and improves HTTP body extraction in request parsing. It adds a new method to get the Content-Length from headers, ensuring the body is correctly extracted and stored. Additionally, it integrates these changes with new tests to validate the functionality. Took 1 hour 47 minutes --- src/tiny_http/tiny_http_server_lib.c | 51 ++++++++++++++++++++++---- src/tiny_http/tiny_http_server_lib.h | 1 + test/assert_tiny_http_server_lib.c | 54 ++++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/tiny_http/tiny_http_server_lib.c b/src/tiny_http/tiny_http_server_lib.c index ddf69ff..1b770a8 100644 --- a/src/tiny_http/tiny_http_server_lib.c +++ b/src/tiny_http/tiny_http_server_lib.c @@ -8,6 +8,26 @@ #include #include +/** + * + * @param headers http_headers from which we've to get the Content-Length + * @param num_headers number of headers + * @return the value of `Content-Length` header or error (negative) + * @retval > 0 is the actual value + * @retval -1 headers is NULL + * @retval -2 the `Content-Length` header is not found + */ +ssize_t get_body_size_from_header(const http_header *const *const headers, const size_t num_headers) { + if (headers == NULL) return -1; + for (size_t i = 0; i < num_headers; i++) { + if (headers[i] == NULL) continue; + if (strncmp(headers[i]->name, "Content-Length", 15) == 0) { + return strtol(headers[i]->value, nullptr, 10); + } + } + return -2; +} + http_request *parse_http_request(const uint8_t *const http_packet, const size_t http_packet_len) { http_request *request = calloc(1, sizeof(http_request)); if (http_packet != nullptr && http_packet_len <= 5) { @@ -21,6 +41,10 @@ http_request *parse_http_request(const uint8_t *const http_packet, const size_t ptr += 4; // "GET " - 4 start_uri = 4; request->method = GET; + } else if (strncmp((char *) http_packet, "POST", 4) == 0) { + ptr += 5; // "POST" - 5 + start_uri = 5; + request->method = POST; } else { fprintf(stderr, "right now, only HTTP GET is supported"); fflush(stderr); @@ -74,7 +98,9 @@ http_request *parse_http_request(const uint8_t *const http_packet, const size_t ptr += 2; // '\r\n' for (size_t i = 0; ptr < http_packet_len; i++) { - if (ptr + 2 >= http_packet_len) { + if ((ptr >= http_packet_len || ptr + 1 >= http_packet_len) + || (http_packet[ptr] == '\r' + && http_packet[ptr + 1] == '\n')) { break; } http_header *header = calloc(1, sizeof(http_header)); @@ -110,10 +136,18 @@ http_request *parse_http_request(const uint8_t *const http_packet, const size_t } } header->value = strndup((char *) http_packet + ptr - header_value_len, header_value_len); - if (request->headers == NULL) { - request->headers = calloc(1, sizeof(http_header*)); + if (request->headers == nullptr) { + request->headers = calloc(1, sizeof(http_header *)); } else { - const http_header **new_headers = realloc(request->headers, sizeof(http_header*) * (i + 1)); + http_header **new_headers = realloc(request->headers, sizeof(http_header *) * (i + 1)); + if (new_headers == nullptr) { + fprintf(stderr, "cannot allocate memory for new headers"); + fflush(stderr); + free(request->url); + free(request); + free(header); + return nullptr; + } request->headers = new_headers; } request->headers[i] = header; @@ -123,8 +157,13 @@ http_request *parse_http_request(const uint8_t *const http_packet, const size_t ptr += 2; if (ptr < http_packet_len) { - request->body_len = http_packet_len - ptr; - request->body = (uint8_t*) strndup((char *) http_packet + ptr, request->body_len); + const ssize_t body_len_from_header = get_body_size_from_header(request->headers, request->headers_cnt); + if (body_len_from_header > 0) { + request->body_len = body_len_from_header; + } else { + request->body_len = http_packet_len - ptr; + } + request->body = (uint8_t *) strndup((char *) http_packet + ptr, request->body_len); } return request; diff --git a/src/tiny_http/tiny_http_server_lib.h b/src/tiny_http/tiny_http_server_lib.h index feb4c72..5cf6186 100644 --- a/src/tiny_http/tiny_http_server_lib.h +++ b/src/tiny_http/tiny_http_server_lib.h @@ -13,6 +13,7 @@ typedef enum http_version { typedef enum http_method { GET = 1, + POST = 2, } http_method; typedef struct http_header { diff --git a/test/assert_tiny_http_server_lib.c b/test/assert_tiny_http_server_lib.c index b84e196..30bd4a4 100644 --- a/test/assert_tiny_http_server_lib.c +++ b/test/assert_tiny_http_server_lib.c @@ -2,6 +2,8 @@ // Created by Samuel Vishesh Paul on 28/10/24. // +#define DEBUG 1 + #include #include #include @@ -9,26 +11,66 @@ #include "../src/tiny_http/tiny_http_server_lib.h" -int main() { +void test_request_parse_get_root_curl(void) { // ReSharper disable once CppVariableCanBeMadeConstexpr - const uint8_t request[] = "GET / HTTP/1.0\r\nHost: localhost:8085\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\n\r\n"; - const http_request* http_req = parse_http_request(request, strlen((char *)request)); + const uint8_t request[] = "GET / HTTP/1.0\r\n" + "Host: localhost:8085\r\n" + "User-Agent: curl/8.7.1\r\n" + "Accept: */*\r\n" + "\r\n"; + const http_request *http_req = parse_http_request(request, strlen((char *) request)); assert(http_req != nullptr); assert(http_req->method == GET); assert(http_req->version == HTTP_1_0); assert(strncmp(http_req->url, "/", 1) == 0); assert(http_req->headers_cnt == 3); - printf("Header 1: '%s': '%s'\n", http_req->headers[0]->name, http_req->headers[0]->value); assert(strncmp(http_req->headers[0]->name , "Host", 255) == 0); assert(strncmp(http_req->headers[0]->value, "localhost:8085", 255) == 0); - printf("Header 2: '%s': '%s'\n", http_req->headers[1]->name, http_req->headers[1]->value); assert(strncmp(http_req->headers[1]->name, "User-Agent", 255) == 0); assert(strncmp(http_req->headers[1]->value, "curl/8.7.1", 255) == 0); - printf("Header 3: '%s': '%s'\n", http_req->headers[2]->name, http_req->headers[2]->value); 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); +} + +void test_request_post_root_curl(void) { + // ReSharper disable once CppVariableCanBeMadeConstexpr + const uint8_t request[] = "POST /one/two/three HTTP/1.0\r\n" + "Content-Type: application/json\r\n" + "User-Agent: PostmanRuntime/7.42.0\r\n" + "Accept: */*\r\n" + "Host: localhost:8085\r\n" + "Accept-Encoding: gzip, deflate, br\r\n" + "Content-Length: 68\r\n" + "\r\n" + "{\n \"key1\": \"value1\",\n \"key2\": \"value2\",\n \"key3\": \"value3\"\n}"; + const http_request *http_req = parse_http_request(request, strlen((char *) request)); + 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->headers[0]->name, "Content-Type", 255) == 0); + assert(strncmp(http_req->headers[0]->value, "application/json", 255) == 0); + assert(strncmp(http_req->headers[1]->name, "User-Agent", 255) == 0); + assert(strncmp(http_req->headers[1]->value, "PostmanRuntime/7.42.0", 255) == 0); + assert(strncmp(http_req->headers[2]->name, "Accept", 255) == 0); + assert(strncmp(http_req->headers[2]->value, "*/*", 255) == 0); + assert(strncmp(http_req->headers[3]->name, "Host", 255) == 0); + assert(strncmp(http_req->headers[3]->value, "localhost:8085", 255) == 0); + assert(strncmp(http_req->headers[4]->name, "Accept-Encoding", 255) == 0); + assert(strncmp(http_req->headers[4]->value, "gzip, deflate, br", 255) == 0); + assert(strncmp(http_req->headers[5]->name, "Content-Length", 255) == 0); + assert(strncmp(http_req->headers[5]->value, "68", 255) == 0); + assert(http_req->body_len == 68); + assert( + strncmp((char *) http_req->body, + "{\n \"key1\": \"value1\",\n \"key2\": \"value2\",\n \"key3\": \"value3\"\n}", 68) == 0); +} + +int main() { + test_request_parse_get_root_curl(); + test_request_post_root_curl(); return EXIT_SUCCESS; }