Skip to content

Commit

Permalink
Implement http_server_settings to control request limits
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
SeriousSamV committed Nov 1, 2024
1 parent 035ed38 commit 2bff132
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 21 deletions.
56 changes: 46 additions & 10 deletions src/tiny_http/tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};


Expand All @@ -31,24 +32,28 @@ 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;
}
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) {
Expand All @@ -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");
Expand Down Expand Up @@ -156,18 +163,22 @@ 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)
* @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) {
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);
}
}
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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");
Expand All @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
13 changes: 12 additions & 1 deletion src/tiny_http/tiny_http_server_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand Down
23 changes: 13 additions & 10 deletions test/assert_tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@

#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"
"Host: localhost:8085\r\n"
"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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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();
Expand Down

0 comments on commit 2bff132

Please sign in to comment.