Skip to content

Commit

Permalink
Add memory management and refactor HTTP request parsing
Browse files Browse the repository at this point in the history
Introduce `destroy_http_request` for proper memory cleanup and refactor the HTTP request parsing logic into modular functions: `parse_http_request_line_from_packet`, `parse_http_request_headers`, and `parse_http_request_body`. This improves code readability and maintainability, reduces redundancy, and enhances error handling.
  • Loading branch information
SeriousSamV committed Oct 31, 2024
1 parent a698045 commit 11577e6
Showing 1 changed file with 183 additions and 97 deletions.
280 changes: 183 additions & 97 deletions src/tiny_http/tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@
#include <stdlib.h>
#include <string.h>


/**
* Frees the memory allocated for the given HTTP request and its components.
*
* @param http_request The pointer to the HTTP request to be destroyed.
*/
void destroy_http_request(http_request *http_request) {
if (http_request == nullptr) {
fprintf(stderr, "http_request is already null");
fflush(stderr);
return;
}
if (http_request->body != nullptr) {
free(http_request->body);
http_request->body = nullptr;
http_request->body_len = 0;
}
if (http_request->headers != nullptr) {
for (size_t i = 0; i < http_request->headers_cnt; i++) {
free(http_request->headers[i]);
http_request->headers = nullptr;
}
http_request->headers_cnt = 0;
}
free(http_request->url);
http_request->url = nullptr;
free(http_request);
}

/**
*
* @param headers http_headers from which we've to get the Content-Length
Expand All @@ -18,153 +47,210 @@
* @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;
if (headers == nullptr) return -1;
for (size_t i = 0; i < num_headers; i++) {
if (headers[i] == NULL) continue;
if (headers[i] == nullptr) 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) {
fprintf(stderr, "cannot parse http request as it appears empty");
/**
* Adds the `body` and `body_len` attributes for the `http_request` being parsed
*
* @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
*/
void parse_http_request_body(
const uint8_t * const http_packet,
const size_t http_packet_len,
http_request *request,
const size_t *ptr) {
if (request == nullptr) {
fprintf(stderr, "Error: null request\n");
fflush(stderr);
return nullptr;
return;
}
size_t ptr = 0;
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,
request->headers_cnt) : 0;
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);
}
}

enum parse_http_request_status {
PARSE_OK = 0,
E_REQ_IS_NULL = -11,
E_MALFORMED_HTTP_HEADER = 1,
E_ALLOC_MEM_FOR_HEADERS = 2,
E_HTTP_METHOD_NOT_SUPPORTED = 3,
E_MALFORMED_HTTP_REQUEST_LINE = 4,
E_HTTP_VERSION_NOT_SUPPORTED = 5,
};

enum parse_http_request_status parse_http_request_headers(
const uint8_t * const http_packet,
const size_t http_packet_len,
http_request *request,
size_t *ptr) {
if (request == nullptr) return E_REQ_IS_NULL;
for (size_t i = 0; *ptr < http_packet_len; i++) {
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));
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++) {
if (http_packet[(*ptr)] == ' ') {
header_name_len = *ptr - header_name_start_ptr;
break;
}
}
if (header_name_len == 0) {
fprintf(stderr, "malformed header");
fflush(stderr);
return E_MALFORMED_HTTP_HEADER;
}
header->name = strndup((char *) http_packet + *ptr - header_name_len, header_name_len - 1);

for (; *ptr < http_packet_len; (*ptr)++) {
if (http_packet[(*ptr)] != ' ') {
break;
}
}
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; (*ptr)++, iter_cnt++) {
if (http_packet[(*ptr)] == '\r' && http_packet[*ptr + 1] == '\n') {
header_value_len = *ptr - header_value_start;
break;
}
}
header->value = strndup((char *) http_packet + *ptr - header_value_len, header_value_len);
if (request->headers == nullptr) {
request->headers = calloc(1, sizeof(http_header *));
if (request->headers == nullptr) {
fprintf(stderr, "cannot allocate memory for new headers");
fflush(stderr);
return E_ALLOC_MEM_FOR_HEADERS;
}
} else {
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);
return E_ALLOC_MEM_FOR_HEADERS;
}
request->headers = new_headers;
}
request->headers[i] = header;
request->headers_cnt = i + 1;
*ptr += 2;
}
*ptr += 2;
return PARSE_OK;
}

enum parse_http_request_status parse_http_request_line_from_packet(
const uint8_t * const http_packet,
const size_t http_packet_len,
http_request *request,
size_t *ptr) {
size_t start_uri = 0;
if (strncmp((char *) http_packet, "GET", 3) == 0) {
ptr += 4; // "GET " - 4
*ptr += 4; // "GET " - 4
start_uri = 4;
request->method = GET;
} else if (strncmp((char *) http_packet, "POST", 4) == 0) {
ptr += 5; // "POST" - 5
*ptr += 5; // "POST" - 5
start_uri = 5;
request->method = POST;
} else {
fprintf(stderr, "right now, only HTTP GET is supported");
fprintf(stderr, "right now, only HTTP GET and POST verbs are supported");
fflush(stderr);
free(request);
return nullptr;
return E_HTTP_METHOD_NOT_SUPPORTED;
}
#ifdef DEBUG
printf("request method: %d", request->method);
#endif

for (int iter_cnt = 0; ptr < http_packet_len && iter_cnt < MAX_URL_LENGTH; ptr++, iter_cnt++) {
if (http_packet[ptr] == ' ') {
request->url = strndup((char *) &http_packet[start_uri], ptr - start_uri);
ptr++;
for (int iter_cnt = 0; *ptr < http_packet_len && iter_cnt < MAX_URL_LENGTH; (*ptr)++, iter_cnt++) {
if (http_packet[(*ptr)] == ' ') {
request->url = strndup((char *) &http_packet[start_uri], *ptr - start_uri);
(*ptr)++;
break;
}
}
#ifdef DEBUG
printf("request url: '%s'", request->url);
#endif
if (ptr >= http_packet_len) {
return request;
if (*ptr >= http_packet_len) {
return PARSE_OK;
}

if (strncmp((char *) http_packet + ptr, "HTTP", 4) == 0) {
ptr += 5; // 'HTTP/' - 5
if (strncmp((char *) http_packet + *ptr, "HTTP", 4) == 0) {
*ptr += 5; // 'HTTP/' - 5
} else {
fprintf(stderr, "illegal http packet");
fflush(stderr);
free(request->url);
free(request);
return nullptr;
return E_MALFORMED_HTTP_REQUEST_LINE;
}
if (strncmp((char *) http_packet + ptr, "1.0", 3) == 0) {
if (strncmp((char *) http_packet + *ptr, "1.0", 3) == 0) {
request->version = HTTP_1_0;
ptr += 3;
*ptr += 3;
#ifdef DEBUG
printf("http version: %d", request->version);
#endif
} else {
fprintf(stderr, "right now, only HTTP 1.0 is supported");
fflush(stderr);
free(request->url);
free(request);
return nullptr;
return E_HTTP_VERSION_NOT_SUPPORTED;
}

if (ptr >= http_packet_len || ptr + 2 >= http_packet_len) {
return request;
if (*ptr >= http_packet_len || *ptr + 2 >= http_packet_len) {
return PARSE_OK;
}
*ptr += 2; // '\r\n'
return PARSE_OK;
}

ptr += 2; // '\r\n'
for (size_t i = 0; ptr < http_packet_len; i++) {
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));
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++) {
if (http_packet[ptr] == ' ') {
header_name_len = ptr - header_name_start_ptr;
break;
}
}
if (header_name_len == 0) {
fprintf(stderr, "misformed header");
fflush(stderr);
free(request->url);
free(request);
free(header);
return nullptr;
}
header->name = strndup((char *) http_packet + ptr - header_name_len, header_name_len - 1);

for (; ptr < http_packet_len; ptr++) {
if (http_packet[ptr] != ' ') {
break;
}
}
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; ptr++, iter_cnt++) {
if (http_packet[ptr] == '\r' && http_packet[ptr + 1] == '\n') {
header_value_len = ptr - header_value_start;
break;
}
}
header->value = strndup((char *) http_packet + ptr - header_value_len, header_value_len);
if (request->headers == nullptr) {
request->headers = calloc(1, sizeof(http_header *));
} else {
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;
request->headers_cnt = i + 1;
ptr += 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) {
fprintf(stderr, "cannot parse http request as it appears empty");
fflush(stderr);
destroy_http_request(request);
return nullptr;
}
size_t ptr = 0;
const enum parse_http_request_status request_line_parse_status =
parse_http_request_line_from_packet(http_packet, http_packet_len, request, &ptr);
if (request_line_parse_status != PARSE_OK) {
destroy_http_request(request);
return nullptr;
}

ptr += 2;
if (ptr < http_packet_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);
const enum parse_http_request_status parse_status =
parse_http_request_headers(http_packet, http_packet_len, request, &ptr);
if (parse_status != PARSE_OK) {
destroy_http_request(request);
return nullptr;
}

parse_http_request_body(http_packet, http_packet_len, request, &ptr);

return request;
}

0 comments on commit 11577e6

Please sign in to comment.