diff --git a/include/br_protocols.h b/include/br_protocols.h index c323c34..70d4865 100644 --- a/include/br_protocols.h +++ b/include/br_protocols.h @@ -50,7 +50,7 @@ typedef enum { typedef struct { char* req; size_t req_s; - int status_code; + int status; GHashTable* headers; char* body; char* __full_text; @@ -58,8 +58,8 @@ typedef struct { } BrHttpResponse; #define BrHttpResponse_unwrap(r) \ - "BrHttpResponse\n{status: %d\tfull_size: %ld\treq:\"%s\"}\n%s\n", \ - (r)->status_code, (r)->__full_text_s, (r)->req, (r)->body + "BrHttpResponse\n{status: %d\tfull_size: %ld\treq:\"%s\"}\n%s\n", \ + (r)->status, (r)->__full_text_s, (r)->req, (r)->body /** * Parses the data from the connection and converts it into a HttpResponse diff --git a/include/br_util.h b/include/br_util.h index e2216f9..ef0057a 100644 --- a/include/br_util.h +++ b/include/br_util.h @@ -51,6 +51,7 @@ bool is_ip(const char* input); /* Removes all characters starting from the first ':' */ void uri_strip(char* str); /* Tries to obtain the port from the URI and returns it. + * Removes the port from the URI string. * If the URI does not contain a port, returns -1 */ int parse_port(const char* URI); /** @@ -67,7 +68,7 @@ int parse_port(const char* URI); * addr will be 9, because that's where the domain starts('w') */ BR_PROTOCOL capture_protocol(const char* uri, int* start_addr); -/* Returns an absolute path to request. From the URI and the request path. +/* Returns an absolute path to request. From the URI and the request path. If the request is already an absolute path, does nothing. * Returned string is a null terminated static char array */ const char* to_abs_path(const char* uri, const char* request_path); /* Obtains a valid host name from given IP address */ diff --git a/src/br_protocols.c b/src/br_protocols.c index f77f752..fec9a05 100644 --- a/src/br_protocols.c +++ b/src/br_protocols.c @@ -25,49 +25,73 @@ * Parses the HTML body to find subsequent links to pull */ BR_PRT_STATUS _get_links(BrHttpResponse* r); +static void _http_response_destroy_kv(gpointer key, gpointer value, + gpointer user_data); + +static BR_PRT_STATUS _parse_http_headers(BrHttpResponse* h_resp, char* l_begin, + char* l_end); +static bool _parse_http_header(GHashTable* headers, char* l_begin, char* l_end); BR_PRT_STATUS br_http_response_new(BrSession* s, BrHttpResponse* h_resp) { MEMMOVE(s->req, h_resp->req); - char status_code_str[255]; + char status_msg[255]; char* l_begin = s->resp; - char* l_end = s->resp; + char* l_end; if ((l_end = strstr(l_begin, "\r\n")) != NULL) { float _v; - if (sscanf(l_begin, "HTTP/%f %d %s\r\n", &_v, &h_resp->status_code, - status_code_str) - != 3) { + if (sscanf(l_begin, "HTTP/%f %d %s\r\n", &_v, &h_resp->status, + status_msg) + != 3) return ERROR(BR_PRT_HTTP_NO_STATUS_CODE); - } l_begin = l_end + 2; } else return ERROR(BR_PRT_HTTP_NO_STATUS_CODE); + return _parse_http_headers(h_resp, l_begin, l_end); +} + +static BR_PRT_STATUS _parse_http_headers(BrHttpResponse* r, char* l_begin, + char* l_end) +{ GHashTable* headers = g_hash_table_new(g_str_hash, g_str_equal); + size_t h_count = 0; while ((l_end = strstr(l_begin, "\r\n")) != NULL) { if (l_end == l_begin) { - h_resp->headers = headers; - h_resp->body = l_end + 2; + if (h_count) { + r->headers = headers; + } else { + g_hash_table_foreach(headers, _http_response_destroy_kv, NULL); + g_hash_table_destroy(headers); + } + r->body = l_end + 2; return BR_PRT_HTTP_OK; } - char* k = calloc(BR_HTTP_HEADER_SIZE / 4, sizeof(char)); - char* v = calloc(BR_HTTP_HEADER_SIZE, sizeof(char)); - char* delimeter; - if ((delimeter = strchr(l_begin, ':')) != NULL) { - memcpy(k, l_begin, delimeter - l_begin); - k[delimeter - l_begin] = 0; - memcpy(v, delimeter + 2, l_end - delimeter - 2); - v[l_end - delimeter - 2] = 0; - g_hash_table_insert(headers, k, v); - } else { - WARN(BR_PRT_HTTP_INVALID_HEADER); - free(k); - free(v); - } + h_count += _parse_http_header(headers, l_begin, l_end); l_begin = l_end + 2; } + g_hash_table_destroy(headers); return ERROR(BR_PRT_HTTP_INVALID_HEADERS); } +static bool _parse_http_header(GHashTable* headers, char* l_begin, char* l_end) +{ + char* k = calloc(BR_HTTP_HEADER_SIZE / 4, sizeof(char)); + char* v = calloc(BR_HTTP_HEADER_SIZE, sizeof(char)); + char* delimeter; + if ((delimeter = strchr(l_begin, ':')) != NULL) { + memcpy(k, l_begin, delimeter - l_begin); + k[delimeter - l_begin] = 0; + memcpy(v, delimeter + 2, l_end - delimeter - 2); + v[l_end - delimeter - 2] = 0; + g_hash_table_insert(headers, k, v); + return true; + } + WARN(BR_PRT_HTTP_INVALID_HEADER); + free(k); + free(v); + return false; +} + static void _http_response_print_kv(gpointer key, gpointer value, gpointer user_data) { diff --git a/src/br_text.c b/src/br_text.c index f362901..5e2e6ba 100644 --- a/src/br_text.c +++ b/src/br_text.c @@ -1,5 +1,4 @@ #include "br_txt.h" -#include /** * Moves inside the given NULL terminated string until a non-space character * is found, returning its address */ diff --git a/src/br_util.c b/src/br_util.c index e79ed28..1c6cce7 100644 --- a/src/br_util.c +++ b/src/br_util.c @@ -50,7 +50,7 @@ const char* to_abs_path(const char* uri, const char* request_path) if (!(is_null_terminated(uri, -1) && is_null_terminated(request_path, -1))) return NULL; static char r[MAX_URI_LENGTH]; - if (strstr(request_path, "://") == NULL) + if (strstr(request_path, "://") != NULL) return request_path; const char* idx = request_path[0] != '/' ? request_path : request_path + 1; size_t uri_s = strlen(uri); @@ -82,17 +82,21 @@ BR_PROTOCOL capture_protocol(const char* uri, int* start_addr) return BR_PROTOCOL_UNSUPPORTED; if (!strncmp("gemini", uri, 6)) { // gemini:// - *start_addr = 9; + if (start_addr != NULL) + *start_addr = 9; return BR_PROTOCOL_GEMINI; } else if (!strncmp("https", uri, 5)) { // https:// - *start_addr = 8; + if (start_addr != NULL) + *start_addr = 8; return BR_PROTOCOL_HTTPS; } else if (!strncmp("http", uri, 4)) { - *start_addr = 7; + if (start_addr != NULL) + *start_addr = 7; return BR_PROTOCOL_HTTP; } else if (!strncmp("gopher", uri, 6)) { - *start_addr = 9; + if (start_addr != NULL) + *start_addr = 9; return BR_PROTOCOL_GOPHER; } return BR_PROTOCOL_UNSUPPORTED; diff --git a/src/test.c b/src/test.c index 44fa362..de00f86 100644 --- a/src/test.c +++ b/src/test.c @@ -1,13 +1,21 @@ #include "assert.h" +#include "br_net.h" +#include "br_protocols.h" #include "br_util.h" +#include typedef void (*TestFn)(void); #define TEST(t) \ { \ - printf("[ ] TEST %s", #t); \ - t(); \ - printf("\r[OK] TEST %s\n", #t); \ + fputs("[ ] TEST " #t, stdout); \ + fflush(stdout); \ + test_##t(); \ + fputs("\r[OK] TEST " #t "\n", stdout); \ } +/****************************************************************************** + br_util.h +*****************************************************************************/ + void test_is_null_terminated() { assert(is_null_terminated("", -1)); @@ -31,14 +39,143 @@ void test_is_ip() assert(!is_ip("100")); assert(!is_ip("100.100.100.100.100")); assert(!is_ip("496.496.496.496")); - assert(!is_ip("https://www.google.com")); - assert(!is_ip("www.google.com")); + assert(!is_ip("https://some.url.com")); + assert(!is_ip("some.url.com")); assert(!is_ip("")); assert(!is_ip(NULL)); } +void test_uri_strip() +{ + char* u1 = NULL; + char u2[] = "test:test"; + char u3[] = "test_text"; + char u4[] = "test:"; + char u5[] = ""; + char u6[] = "::"; + uri_strip(u1); + uri_strip(u2); + uri_strip(u3); + uri_strip(u4); + uri_strip(u5); + uri_strip(u6); + assert(u1 == NULL); + assert(!strncmp(u2, "test", 4)); + assert(!strncmp(u3, "test_text", 9)); + assert(!strncmp(u4, "test", 4)); + assert(!strncmp(u5, "", 1)); + assert(!strncmp(u6, "", 1)); +} + +void test_to_abs_path() +{ + assert(to_abs_path(NULL, "/") == NULL); + assert(to_abs_path("some.url.com", NULL) == NULL); + assert( + !strncmp(to_abs_path("gemini://some.url/", "gemini://some.url/search"), + "gemini://some.url/search", 24)); + assert(!strncmp(to_abs_path("https://some.url.com/", "/search"), + "https://some.url.com/search", 27)); + assert(!strncmp(to_abs_path("https://some.url.com/", "search"), + "https://some.url.com/search", 27)); +} + +void test_parse_port() +{ + assert(parse_port(NULL) == -1); + assert(parse_port("some.url.com") == -1); + assert(parse_port("255.255.255.255") == -1); + char c[] = "255.255.255.255:24"; + assert(parse_port(c) == 24); + char d[] = "https://some.url.com:443"; + assert(parse_port(d) == -1); + char e[] = "some.url.com:25"; + assert(parse_port(e) == 25); + char f[] = "some.url.com:"; + assert(parse_port(f) == 0); +} + +void test_capture_protocol() +{ + int start_addr; + char uri[] = "https://some.url.com"; + assert(capture_protocol(uri, &start_addr) == BR_PROTOCOL_HTTPS); + assert(!strcmp(uri + start_addr, "some.url.com")); + + char uri2[] = "gemini://some.url.com:1965/path/to?search=10"; + assert(capture_protocol(uri2, &start_addr) == BR_PROTOCOL_GEMINI); + assert(start_addr == 9); + + assert(capture_protocol(NULL, &start_addr) == BR_PROTOCOL_UNSUPPORTED); + assert(capture_protocol(uri2, NULL) == BR_PROTOCOL_GEMINI); +} + +/****************************************************************************** + br_protocols.h +*****************************************************************************/ + +void test_br_http_response_new() +{ + BrSession s = {0}; + s.req = malloc(1024 * sizeof(char)); + sprintf(s.req, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); + s.req_s = 1024; + s.protocol = BR_PROTOCOL_HTTPS; + s.resp = malloc(1024 * sizeof(char)); + sprintf(s.resp, "HTTP/1.1 301 Moved Permanently\r\nHost: " + "www.duckduckgo.com\r\n\r\n301 Moved" + "Permanently" + "

301 Moved Permanently

" + "
nginx
\r\n"); + BrHttpResponse r = {0}; + assert(br_http_response_new(&s, &r) == BR_PRT_HTTP_OK); + assert(r.status == 301); + assert(r.req != NULL && r.body != NULL); + assert(r.headers != NULL); + assert(g_hash_table_contains(r.headers, "Host")); +} + +void test_br_http_response_new_when_no_headers() +{ + BrSession s = {0}; + s.req = malloc(1024 * sizeof(char)); + sprintf(s.req, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); + s.req_s = 1024; + s.protocol = BR_PROTOCOL_HTTPS; + s.resp = malloc(1024 * sizeof(char)); + sprintf(s.resp, "HTTP/1.1 404 Not Found\r\n\r\nNot Found" + "" + "
nginx
\r\n"); + BrHttpResponse r = {0}; + assert(br_http_response_new(&s, &r) == BR_PRT_HTTP_OK); + assert(r.status == 404); + assert(r.req != NULL && r.body != NULL); + assert(r.headers == NULL); +} + +void test_br_http_response_new_when_invalid_headers() +{ + BrSession s = {0}; + s.req = malloc(1024 * sizeof(char)); + sprintf(s.req, "\b\\k\r\nd\\009;0"); + s.req_s = 1024; + s.protocol = BR_PROTOCOL_HTTPS; + s.resp = malloc(1024 * sizeof(char)); + sprintf(s.resp, "\b\\k\r\nd\\009;0\r\r\r\n\r\nNot Found" + "" + "
nginx
\r\n"); + BrHttpResponse r = {0}; + assert(br_http_response_new(&s, &r) == BR_PRT_HTTP_OK); +} int main(void) { - TEST(test_is_ip); - TEST(test_is_null_terminated); + TEST(is_ip); + TEST(is_null_terminated); + TEST(uri_strip); + TEST(to_abs_path); + TEST(parse_port); + TEST(capture_protocol); + TEST(br_http_response_new); + TEST(br_http_response_new_when_no_headers); + TEST(br_http_response_new_when_invalid_headers); }