From 2bff1322331763847aac1785274887d783a7fa86 Mon Sep 17 00:00:00 2001 From: Samuel Vishesh Paul Date: Fri, 1 Nov 2024 16:24:04 +0530 Subject: [PATCH] Implement `http_server_settings` to control request limits Added a new `http_server_settings` struct to manage maximum header name length, header value length, and body length. Updated parsing functions to use these settings and enforced body size restrictions. Adjusted the test suite to accommodate these changes. --- src/tiny_http/tiny_http_server_lib.c | 56 +++++++++++++++++++++++----- src/tiny_http/tiny_http_server_lib.h | 13 ++++++- test/assert_tiny_http_server_lib.c | 23 +++++++----- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/tiny_http/tiny_http_server_lib.c b/src/tiny_http/tiny_http_server_lib.c index 2de74cd..c1631eb 100644 --- a/src/tiny_http/tiny_http_server_lib.c +++ b/src/tiny_http/tiny_http_server_lib.c @@ -16,6 +16,7 @@ enum parse_http_request_status { PARSE_E_HTTP_METHOD_NOT_SUPPORTED = 3, PARSE_E_MALFORMED_HTTP_REQUEST_LINE = 4, PARSE_E_HTTP_VERSION_NOT_SUPPORTED = 5, + PARSE_E_BODY_TOO_LARGE = 6, }; @@ -31,11 +32,13 @@ void destroy_http_request(http_request *http_request) { return; } if (http_request->body != nullptr) { + // ReSharper disable once CppDFANullDereference free(http_request->body); http_request->body = nullptr; http_request->body_len = 0; } if (http_request->headers != nullptr) { + // ReSharper disable once CppDFANullDereference for (size_t i = 0; i < http_request->headers_cnt; i++) { free(http_request->headers[i]); http_request->headers[i] = nullptr; @@ -43,12 +46,14 @@ void destroy_http_request(http_request *http_request) { http_request->headers = nullptr; http_request->headers_cnt = 0; } + // ReSharper disable once CppDFANullDereference free(http_request->url); http_request->url = nullptr; free(http_request); } enum render_http_response_status render_http_response( + const http_server_settings *const settings, const http_response *http_response, uint8_t **out_response_octets, size_t *out_response_len) { @@ -65,7 +70,9 @@ enum render_http_response_status render_http_response( } *out_response_octets = calloc( // TODO: have a way to predict the size more accurately to avoid over or under allocation - http_response->body_len + http_response->headers_cnt * 2000 + 32, + http_response->body_len + + http_response->headers_cnt * (settings->max_header_name_length + settings->max_header_value_length) + + 32, sizeof(uint8_t)); if (*out_response_octets == nullptr) { fprintf(stderr, "cannot alloc mem for out_response_octets\n"); @@ -156,6 +163,7 @@ enum render_http_response_status render_http_response( /** * + * @param settings * @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) @@ -163,11 +171,14 @@ enum render_http_response_status render_http_response( * @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) { +ssize_t get_body_size_from_header( + const http_server_settings *const settings, + const http_header *const *const headers, + const size_t num_headers) { if (headers == nullptr) return -1; for (size_t i = 0; i < num_headers; i++) { if (headers[i] == nullptr) continue; - if (strncmp(headers[i]->name, "Content-Length", 15) == 0) { + if (strncmp(headers[i]->name, "Content-Length", settings->max_header_name_length) == 0) { return strtol(headers[i]->value, nullptr, 10); } } @@ -177,12 +188,14 @@ ssize_t get_body_size_from_header(const http_header *const *const headers, const /** * Adds the `body` and `body_len` attributes for the `http_request` being parsed * + * @param settings * @param http_packet http packet stream * @param http_packet_len http packet stream length * @param request the http_request object being parsed * @param ptr the http packet stream scan ptr */ enum parse_http_request_status parse_http_request_body( + const http_server_settings * const settings, const uint8_t *const http_packet, const size_t http_packet_len, http_request *request, @@ -195,20 +208,30 @@ enum parse_http_request_status parse_http_request_body( if (*ptr < http_packet_len) { const ssize_t body_len_from_header = request != nullptr && request->headers != nullptr ? get_body_size_from_header( - (const http_header * const * const) request->headers, + settings, + // ReSharper disable once CppRedundantCastExpression + // ReSharper disable once CppDFANullDereference + (const http_header *const *const) request->headers, request->headers_cnt) - : 0; + : -42; if (body_len_from_header >= 0) { request->body_len = body_len_from_header; } else { + // ReSharper disable once CppDFANullDereference request->body_len = http_packet_len - *ptr; } + if (request->body_len > settings->max_body_length) { + fprintf(stderr, "Error: body length too large\n"); + fflush(stderr); + return PARSE_E_BODY_TOO_LARGE; + } request->body = (uint8_t *) strndup((char *) http_packet + *ptr, request->body_len); } return PARSE_OK; } enum parse_http_request_status parse_http_request_headers( + const http_server_settings *const settings, const uint8_t *const http_packet, const size_t http_packet_len, http_request *request, @@ -218,12 +241,15 @@ enum parse_http_request_status parse_http_request_headers( if ((*ptr >= http_packet_len || *ptr + 1 >= http_packet_len) || (http_packet[(*ptr)] == '\r' && http_packet[*ptr + 1] == '\n')) { + // is end-of-headers break; } http_header *header = calloc(1, sizeof(http_header)); const size_t header_name_start_ptr = *ptr; size_t header_name_len = 0; - for (int iter_cnt = 0; *ptr < http_packet_len && iter_cnt < MAX_HTTP_HEADER_NAME_LENGTH; (*ptr)++, iter_cnt++) { + for (int iter_cnt = 0; + *ptr < http_packet_len && iter_cnt < settings->max_header_name_length; + (*ptr)++, iter_cnt++) { if (http_packet[(*ptr)] == ' ') { header_name_len = *ptr - header_name_start_ptr; break; @@ -243,7 +269,8 @@ enum parse_http_request_status parse_http_request_headers( } const size_t header_value_start = *ptr; size_t header_value_len = 0; - for (int iter_cnt = 0; *ptr < http_packet_len && iter_cnt < MAX_HTTP_HEADER_VALUE_LENGTH; + for (int iter_cnt = 0; + *ptr < http_packet_len && iter_cnt < settings->max_header_value_length; (*ptr)++, iter_cnt++) { if (http_packet[(*ptr)] == '\r' && http_packet[*ptr + 1] == '\n') { header_value_len = *ptr - header_value_start; @@ -252,6 +279,7 @@ enum parse_http_request_status parse_http_request_headers( } header->value = strndup((char *) http_packet + *ptr - header_value_len, header_value_len); if (request->headers == nullptr) { + // ReSharper disable once CppDFANullDereference request->headers = calloc(1, sizeof(http_header *)); if (request->headers == nullptr) { fprintf(stderr, "cannot allocate memory for new headers\n"); @@ -265,6 +293,7 @@ enum parse_http_request_status parse_http_request_headers( fflush(stderr); return PARSE_E_ALLOC_MEM_FOR_HEADERS; } + // ReSharper disable once CppDFANullDereference request->headers = new_headers; } request->headers[i] = header; @@ -285,14 +314,17 @@ enum parse_http_request_status parse_http_request_line_from_packet( if (strncmp((char *) http_packet, "GET", 3) == 0) { *ptr += 4; // "GET " - 4 start_uri = 4; + // ReSharper disable once CppDFANullDereference request->method = GET; } else if (strncmp((char *) http_packet, "POST", 4) == 0) { *ptr += 5; // "POST" - 5 start_uri = 5; + // ReSharper disable once CppDFANullDereference request->method = POST; } else if (strncmp((char *) http_packet, "HEAD", 4) == 0) { *ptr += 5; // "HEAD" - 5 start_uri = 5; + // ReSharper disable once CppDFANullDereference request->method = HEAD; } else { fprintf(stderr, "right now, only HTTP GET and POST verbs are supported\n"); @@ -347,12 +379,16 @@ enum parse_http_request_status parse_http_request_line_from_packet( /** * Parses an HTTP request from the given http packet in octets. * + * @param settings * @param http_packet A pointer to the HTTP packet to parse. * @param http_packet_len The length of the HTTP packet. * * @return A pointer to the parsed HTTP request, or nullptr if parsing fails. */ -http_request *parse_http_request(const uint8_t *const http_packet, const size_t http_packet_len) { +http_request *parse_http_request( + const http_server_settings *const settings, + const uint8_t *const http_packet, + const size_t http_packet_len) { if (http_packet != nullptr && http_packet_len <= 5) { fprintf(stderr, "cannot parse http request as it appears empty\n"); fflush(stderr); @@ -374,14 +410,14 @@ http_request *parse_http_request(const uint8_t *const http_packet, const size_t } const enum parse_http_request_status headers_parse_status = - parse_http_request_headers(http_packet, http_packet_len, request, &ptr); + parse_http_request_headers(settings, http_packet, http_packet_len, request, &ptr); if (headers_parse_status != PARSE_OK) { destroy_http_request(request); return nullptr; } const enum parse_http_request_status body_parse_status = - parse_http_request_body(http_packet, http_packet_len, request, &ptr); + parse_http_request_body(settings, http_packet, http_packet_len, request, &ptr); if (body_parse_status != PARSE_OK) { destroy_http_request(request); return nullptr; diff --git a/src/tiny_http/tiny_http_server_lib.h b/src/tiny_http/tiny_http_server_lib.h index 9146a07..f7f7a00 100644 --- a/src/tiny_http/tiny_http_server_lib.h +++ b/src/tiny_http/tiny_http_server_lib.h @@ -47,15 +47,25 @@ typedef struct http_response { size_t body_len; } http_response; +typedef struct http_server_settings { + size_t max_header_name_length; + size_t max_header_value_length; + size_t max_body_length; +} http_server_settings; + /** * Parses an HTTP request from the given http packet in octets. * + * @param settings * @param http_packet A pointer to the HTTP packet to parse. * @param http_packet_len The length of the HTTP packet. * * @return A pointer to the parsed HTTP request, or nullptr if parsing fails. */ -http_request *parse_http_request(const uint8_t *const http_packet, const size_t http_packet_len); +http_request *parse_http_request( + const http_server_settings *const settings, + const uint8_t *const http_packet, + const size_t http_packet_len); /** * Frees the memory allocated for the given HTTP request and its components. @@ -73,6 +83,7 @@ enum render_http_response_status { }; enum render_http_response_status render_http_response( + const http_server_settings *const settings, const http_response *http_response, uint8_t **out_response_octets, size_t *out_response_len); diff --git a/test/assert_tiny_http_server_lib.c b/test/assert_tiny_http_server_lib.c index 8bec698..396a054 100644 --- a/test/assert_tiny_http_server_lib.c +++ b/test/assert_tiny_http_server_lib.c @@ -11,6 +11,12 @@ #include "../src/tiny_http/tiny_http_server_lib.h" +http_server_settings settings = { + .max_header_name_length = 256, + .max_header_value_length = 512, + .max_body_length = 1024 * 1024 * 8, // 8M +}; + void test_request_parse_get_root_curl(void) { // ReSharper disable once CppVariableCanBeMadeConstexpr const uint8_t request[] = "GET / HTTP/1.0\r\n" @@ -18,7 +24,7 @@ void test_request_parse_get_root_curl(void) { "User-Agent: curl/8.7.1\r\n" "Accept: */*\r\n" "\r\n"; - http_request *http_req = parse_http_request(request, strlen((char *) request)); + 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); @@ -46,7 +52,7 @@ void test_request_post_root_curl(void) { "Content-Length: 68\r\n" "\r\n" "{\n \"key1\": \"value1\",\n \"key2\": \"value2\",\n \"key3\": \"value3\"\n}"; - http_request *http_req = parse_http_request(request, strlen((char *) request)); + http_request *http_req = parse_http_request(&settings, request, strlen((char *) request)); assert(http_req != nullptr); assert(http_req->method == POST); assert(http_req->version == HTTP_1_0); @@ -83,7 +89,7 @@ void test_request_post_root_curl_with_wide_chars(void) { "Content-Length: 68\r\n" "\r\n" "{\n \"key1\": \"🐌\",\n \"key2\": \"value2\",\n \"key3\": \"value3\"\n}"; - http_request *http_req = parse_http_request(request, strlen((char *) request)); + http_request *http_req = parse_http_request(&settings, request, strlen((char *) request)); assert(http_req != nullptr); assert(http_req->method == POST); assert(http_req->version == HTTP_1_0); @@ -116,7 +122,7 @@ void test_request_parse_head(void) { "User-Agent: custom-agent/1.0\r\n" "Accept: */*\r\n" "\r\n"; - http_request *http_req = parse_http_request(request, strlen((char *) request)); + http_request *http_req = parse_http_request(&settings, request, strlen((char *) request)); assert(http_req != nullptr); assert(http_req->method == HEAD); assert(http_req->version == HTTP_1_0); @@ -142,7 +148,7 @@ void test_response_render_200_no_body(void) { uint8_t *response_octets = nullptr; size_t response_octets_len = 0; const enum render_http_response_status response_code = render_http_response( - &response, &response_octets, &response_octets_len); + &settings, &response, &response_octets, &response_octets_len); assert(response_code == RENDER_OK); assert(response_octets_len > 0); assert(strncmp((char *) response_octets, "HTTP/1.0 200 OK\r\n", 32) == 0); @@ -158,7 +164,7 @@ void test_response_render_404_no_body(void) { uint8_t *response_octets = nullptr; size_t response_octets_len = 0; const enum render_http_response_status response_code = render_http_response( - &response, &response_octets, &response_octets_len); + &settings, &response, &response_octets, &response_octets_len); assert(response_code == RENDER_OK); assert(response_octets_len > 0); assert(strncmp((char *) response_octets, "HTTP/1.0 404 Not Found\r\n", 32) == 0); @@ -185,7 +191,7 @@ void test_response_render_200_with_body(void) { size_t response_octets_len = 0; const enum render_http_response_status response_code = render_http_response( - &response, &response_octets, &response_octets_len); + &settings, &response, &response_octets, &response_octets_len); assert(response_code == RENDER_OK); assert(response_octets_len > 0); @@ -203,9 +209,6 @@ void test_response_render_200_with_body(void) { } int main() { - char *test = "hello\r\n"; - assert(strnlen(test, 255) == 7); - test_request_parse_get_root_curl(); test_request_post_root_curl(); test_request_post_root_curl_with_wide_chars();