Skip to content

Commit

Permalink
Add POST method support and handle HTTP body parsing
Browse files Browse the repository at this point in the history
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
  • Loading branch information
SeriousSamV committed Oct 30, 2024
1 parent 16cedec commit 7a3ae74
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 12 deletions.
51 changes: 45 additions & 6 deletions src/tiny_http/tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@
#include <stdlib.h>
#include <string.h>

/**
*
* @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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/tiny_http/tiny_http_server_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ typedef enum http_version {

typedef enum http_method {
GET = 1,
POST = 2,
} http_method;

typedef struct http_header {
Expand Down
54 changes: 48 additions & 6 deletions test/assert_tiny_http_server_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,75 @@
// Created by Samuel Vishesh Paul on 28/10/24.
//

#define DEBUG 1

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#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;
}

0 comments on commit 7a3ae74

Please sign in to comment.