From 48e3092317f7f5d0c89e06673c655c59491fb46b Mon Sep 17 00:00:00 2001 From: Jonathan Hodgson Date: Mon, 10 Jan 2022 09:36:18 +0000 Subject: [PATCH] Split out some stuff that is shared between request and response I now have files with the infinitely imaginative names requestrespons.{c,h,test.c}. --- src/request.c | 55 ++--------- src/request.h | 24 +---- src/requestresponse.c | 106 ++++++++++++++++++++ src/requestresponse.h | 46 +++++++++ src/response.c | 38 +++++++ src/response.h | 34 +++++++ tests/request.test.c | 5 + tests/requestresponse.test.c | 186 +++++++++++++++++++++++++++++++++++ tests/response.test.c | 109 ++++++++++++++++++++ 9 files changed, 533 insertions(+), 70 deletions(-) create mode 100644 src/requestresponse.c create mode 100644 src/requestresponse.h create mode 100644 src/response.c create mode 100644 src/response.h create mode 100644 tests/requestresponse.test.c create mode 100644 tests/response.test.c diff --git a/src/request.c b/src/request.c index ffec9fd..991c247 100644 --- a/src/request.c +++ b/src/request.c @@ -1,45 +1,5 @@ #include "request.h" -Header* newHeader(char *str){ - Header *header = malloc(sizeof(Header)); - memset(header, 0, sizeof(Header)); - - int position = -1; - - - for ( unsigned int i = 0; i < strlen(str); i++ ){ - if ( str[i] == ':' ){ - position = i; - break; - } - } - - if ( position == -1 ){ - printf("Header without colon. Not sure what to do\n"); - return NULL; - } - - - // We want to allocate 1 more than the length so we can have a \0 at the end - header->name = malloc( sizeof(char) * ( position + 1 ) ); - memset(header->name, '\0', position+1); - strncpy( header->name, str, position ); - - for ( unsigned int i = position+1; i < strlen(str); i++ ){ - if ( str[i] == '\t' || str[i] == ' ' ) continue; - position = i; - break; - } - - - //Anything left is the value - header->value = malloc( sizeof(char) * ( strlen(str) - position ) ); - memset(header->value, '\0', ( strlen(str) - position ) ); - strcpy( header->value, str + position ); - - - return header; -} Request* newRequest(){ Request *request = malloc(sizeof(Request)); @@ -101,18 +61,15 @@ Request* newRequestFromSocket(int socket){ int valread; char line[1024] = {0}; // The first line will give us some important information - //valread = fdReadLine( socket, line, 1024); - char hello[] = "this is a test"; + valread = fdReadLine( socket, line, 1024); - printf("HERE\nLine is %s\n", line); + requestFirstLine(req, line); //a length of 2 will indicate an empty line which will split the headers //from the body (if there is a body) - while ( valread > 2 ){ - printf("%s",line ); - //valread = fdReadLine( socket , line, 1024); - } - send(socket , "this is a test" , 15 , 0 ); - printf("Hello message sent\n"); + //while ( valread > 2 ){ + // printf("%s",line ); + // //valread = fdReadLine( socket , line, 1024); + //} return req; } diff --git a/src/request.h b/src/request.h index 3b7735a..2987718 100644 --- a/src/request.h +++ b/src/request.h @@ -4,27 +4,11 @@ #include #include #include -#include "readline.h" #include +#include "readline.h" +#include "requestresponse.h" -/* -* A struct reperesenting an http header -*/ -typedef struct { - char *name; - // The spec doesn't specify a max size for headers, but nginx doesn't accept headers bigger than 4096 so that's probably ok for us - char *value; -} Header; - -/* -* A linked list wrapper around the headers -*/ -typedef struct HeaderList HeaderList; -struct HeaderList { - Header header; - HeaderList *next; -}; /* * A struct reperesenting an http request @@ -41,12 +25,10 @@ typedef struct { char *body; } Request; -Header* newHeader(); Request* newRequest(); void requestFirstLine( Request *req, char line[] ); Request* newRequestFromSocket(int socket); -void* addHeader(Request *req, char line[]); -void* interpretLine1(Request *req, char line[]); +//void* requestAddHeader(Request *req, char line[]); diff --git a/src/requestresponse.c b/src/requestresponse.c new file mode 100644 index 0000000..85b52bd --- /dev/null +++ b/src/requestresponse.c @@ -0,0 +1,106 @@ +#include "requestresponse.h" + +Header* newHeader(char *str){ + Header *header = malloc(sizeof(Header)); + memset(header, 0, sizeof(Header)); + + int position = -1; + + + for ( unsigned int i = 0; i < strlen(str); i++ ){ + if ( str[i] == ':' ){ + position = i; + break; + } + } + + if ( position == -1 ){ + printf("Header without colon. Not sure what to do\n"); + return NULL; + } + + + // We want to allocate 1 more than the length so we can have a \0 at the end + header->name = malloc( sizeof(char) * ( position + 1 ) ); + memset(header->name, '\0', position+1); + strncpy( header->name, str, position ); + + for ( unsigned int i = position+1; i < strlen(str); i++ ){ + if ( str[i] == '\t' || str[i] == ' ' ) continue; + position = i; + break; + } + + + //Anything left is the value + header->value = malloc( sizeof(char) * ( strlen(str) - position ) ); + memset(header->value, '\0', ( strlen(str) - position ) ); + strcpy( header->value, str + position ); + + return header; +} + +void addHeader(HeaderList *headers, char *str){ + HeaderList *newListItem = malloc(sizeof(HeaderList)); + + newListItem->header = newHeader(str); + newListItem->next = NULL; + + HeaderList *last = headers; + + while ( last->next != NULL ) last = last->next; + + last->next = newListItem; +} + + +// Will return the first header in a header list with the name matching name +// It matches name case insensitively as header names are case insensitive + +Header* getHeader(HeaderList *headers, char *name){ + HeaderList *curr = headers; + while ( curr != NULL ){ + // Header names are case insensitive + if ( strcasecmp( curr->header->name, name) == 0 ){ + return curr->header; + } + curr = curr->next; + } + return NULL; +} + + +unsigned int countHeaders(HeaderList *headers){ + unsigned int count = 0; + while (headers != NULL){ + count++; + headers = headers->next; + } + return count; +} + +unsigned int headerListCharLength(HeaderList *headers){ + unsigned int length = 0; + while (headers != NULL){ + // 4 = colon + space + \r + \n + length += strlen(headers->header->name) + strlen(headers->header->value) + 4; + headers = headers->next; + } + return length; +} + + +char* headersToString(HeaderList *headers){ + int len = headerListCharLength(headers) + 1; //null pointer + HeaderList *curr = headers; + char *retStr = malloc( len * sizeof(char) ); + memset(retStr, '\0', len * sizeof(char) ); + while ( curr != NULL ){ + strcat( retStr, curr->header->name ); + strcat( retStr, ": " ); + strcat( retStr, curr->header->value ); + strcat( retStr, "\r\n" ); + curr = curr->next; + } + return retStr; +} diff --git a/src/requestresponse.h b/src/requestresponse.h new file mode 100644 index 0000000..f5abad1 --- /dev/null +++ b/src/requestresponse.h @@ -0,0 +1,46 @@ +#ifndef REQUESTRESPONSE +#define REQUESTRESPONSE + + +#include +#include +#include + +//This file contains definitions that are shared between requests and responses such as header +/* +* A struct reperesenting an http header +*/ +typedef struct { + char *name; + // The spec doesn't specify a max size for headers, but nginx doesn't accept headers bigger than 4096 so that's probably ok for us + char *value; +} Header; + +/* +* A linked list wrapper around the headers +*/ +typedef struct HeaderList HeaderList; +struct HeaderList { + Header *header; + HeaderList *next; +}; + +//Creates a new header struct from a string like "Content-Length: 123" +Header* newHeader(char *str); + +//Adds a header to the end of a header list +void addHeader(HeaderList *headers, char *str); + +Header* getHeader(HeaderList *headers, char *name); + +unsigned int countHeaders(HeaderList *headers); + + +unsigned int headerListCharLength(HeaderList *headers); + +char* headersToString(HeaderList *headers); + +#endif /* ifndef REQUESTRESPONSE + +//THis file contains definitions that are shared between requests and responses such as header + */ diff --git a/src/response.c b/src/response.c new file mode 100644 index 0000000..bacdf7a --- /dev/null +++ b/src/response.c @@ -0,0 +1,38 @@ +#include "response.h" + +Response* newResponse(){ + Response *response = malloc( sizeof( Response ) ); + memset(response, 0, sizeof(Response)); + response->headers = malloc( sizeof( HeaderList ) ); + response->headers->header = newHeader("Content-Length: 0"); + response->headers->next = NULL; + addHeader(response->headers, "Content-Type: text/plain"); + response->statusCode = 200; + response->statusMessage = "OK"; + response->version = 1.1; + return response; +} + +char* responseToString( Response *rsp ){ + // HTTP/x.x xxx OK \r\n + int fullLength = 13 + strlen(rsp->statusMessage) + 2 + + // Headers \r\n\r\n + headerListCharLength(rsp->headers) + 4 + strlen(rsp->body); + char retString[fullLength]; + + sprintf(retString, "HTTP/%.1f %3d %s\r\n%s\r\n%s", rsp->version, rsp->statusCode, + rsp->statusMessage,headersToString(rsp->headers),rsp->body); + return strdup(retString); +} + +void responseSetBody(Response *rsp, char *string, bool updateContentLength){ + rsp->body = string; + + + if ( updateContentLength ){ + Header *contentLengthHeader = getHeader(rsp->headers, "content-length"); + char *value = malloc(sizeof(char) * 20); + sprintf(value, "%lu", strlen(string) ); + contentLengthHeader->value = strdup(value); + } +} diff --git a/src/response.h b/src/response.h new file mode 100644 index 0000000..a18a667 --- /dev/null +++ b/src/response.h @@ -0,0 +1,34 @@ +#ifndef RESPONSE_H +#define RESPONSE_H + +#include +#include +#include +#include +#include + +#include "readline.h" +#include "requestresponse.h" + + +/* +* A struct reperesenting an http request +*/ +typedef struct { + // Common versions are: 0.9, 1.0, 1.1, 2.0 + float version; + int statusCode; + char *statusMessage; + HeaderList *headers; + char *body; + unsigned int headerLength; +} Response; + +Response* newResponse(); +char *responseToString(Response *rsp); +void responseSetBody(Response *rsp, char *string, bool updateContentLength); +//void* responseAddHeader(Response *req, char line[]); + + + +#endif /* ifndef REQUEST_H */ diff --git a/tests/request.test.c b/tests/request.test.c index 0f1ee0a..80790b0 100644 --- a/tests/request.test.c +++ b/tests/request.test.c @@ -1,4 +1,8 @@ +#ifndef REQUEST_TEST +#define REQUEST_TEST + #include "munit/munit.h" +#include "../src/readline.c" #include "../src/request.c" typedef struct { @@ -160,3 +164,4 @@ int main (int argc, char* argv[]) { } #endif /* ifndef MAINTEST */ +#endif /* ifndef REQUEST_TEST */ diff --git a/tests/requestresponse.test.c b/tests/requestresponse.test.c new file mode 100644 index 0000000..f9a26e2 --- /dev/null +++ b/tests/requestresponse.test.c @@ -0,0 +1,186 @@ +#ifndef REQUESTRESPONSE_TEST +#define REQUESTRESPONSE_TEST + +#include "munit/munit.h" +#include "../src/requestresponse.c" + +typedef struct { + char *fullLine; + char *name; + char *value; +} HeaderTestCase; + +static HeaderTestCase testCases[] = { + { "Content-Encoding: gzip", "Content-Encoding", "gzip" }, + { "Accept-Ranges: bytes", "Accept-Ranges", "bytes" }, + { "Age: 186432", "Age", "186432" }, + { "Cache-Control: max-age=604800", "Cache-Control", "max-age=604800" }, + { "Content-Type: text/html; charset=UTF-8", "Content-Type", "text/html; charset=UTF-8" }, + { "Date: Thu, 06 Jan 2022 18:52:13 GMT", "Date", "Thu, 06 Jan 2022 18:52:13 GMT" }, + { "Etag: \"3147526947+ident\"", "Etag", "\"3147526947+ident\"" }, + { "Expires: Thu, 13 Jan 2022 18:52:13 GMT", "Expires", "Thu, 13 Jan 2022 18:52:13 GMT" }, + { "Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT", "Last-Modified", "Thu, 17 Oct 2019 07:18:26 GMT" }, + { "Server: ECS (nyb/1D13)", "Server", "ECS (nyb/1D13)" }, + { "X-Cache: HIT", "X-Cache", "HIT" }, + { "Content-Length: 648", "Content-Length", "648" } +}; + +MunitResult testHeadersName(const MunitParameter params[], + void* user_data_or_fixture){ + for ( int i = 0; i < sizeof(testCases) / sizeof(HeaderTestCase); i++ ){ + Header *test = newHeader(testCases[i].fullLine); + munit_assert_string_equal( testCases[i].name, test->name ); + } + return MUNIT_OK; +} + +MunitResult testHeadersValue(const MunitParameter params[], + void* user_data_or_fixture){ + for ( int i = 0; i < sizeof(testCases) / sizeof(HeaderTestCase); i++ ){ + Header *test = newHeader(testCases[i].fullLine); + munit_assert_string_equal( testCases[i].value, test->value ); + } + return MUNIT_OK; +} + + +MunitResult testHeadersListCount(const MunitParameter params[], + void* user_data_or_fixture){ + HeaderList *list = malloc( sizeof( HeaderList ) ); + list->header = newHeader("Content-Length: 0"); + list->next = malloc( sizeof( HeaderList ) ); + list->next->header = newHeader("Test-header: 5"); + list->next->next = NULL; + munit_assert_int( 2, ==, countHeaders(list) ); + list->next->next = malloc( sizeof( HeaderList ) ); + list->next->next->header = newHeader("Test-header: 5"); + list->next->next->next = NULL; + munit_assert_int( 3, ==, countHeaders(list) ); + return MUNIT_OK; +} + +MunitResult testHeadersListAdd(const MunitParameter params[], + void* user_data_or_fixture){ + HeaderList *list = malloc( sizeof( HeaderList ) ); + list->header = newHeader("Content-Length: 0"); + addHeader( list, "Test-header: 5" ); + munit_assert_int( 2, ==, countHeaders(list) ); + addHeader( list, "Another-Test-header: 50" ); + munit_assert_int( 3, ==, countHeaders(list) ); + return MUNIT_OK; +} + +MunitResult testHeadersListCharLength(const MunitParameter params[], + void* user_data_or_fixture){ + HeaderList *list = malloc( sizeof( HeaderList ) ); + list->header = newHeader("Content-Length: 0"); + munit_assert_int( headerListCharLength( list ) , ==, 19 ); + addHeader( list, "Test-header: 5" ); + munit_assert_int( headerListCharLength( list ) , ==, 35 ); + addHeader( list, "Another-Test-header: 50" ); + munit_assert_int( headerListCharLength( list ) , ==, 60 ); + return MUNIT_OK; +} + +MunitResult testHeadersToString(const MunitParameter params[], + void* user_data_or_fixture){ + HeaderList *list = malloc( sizeof( HeaderList ) ); + list->header = newHeader("Content-Length: 0"); + addHeader( list, "Test-header: 5" ); + munit_assert_string_equal( headersToString(list), "Content-Length: 0\r\nTest-header: 5\r\n" ); + return MUNIT_OK; +} + +MunitResult testGetHeader(const MunitParameter params[], + void* user_data_or_fixture){ + HeaderList *list = malloc( sizeof( HeaderList ) ); + list->header = newHeader("Content-Length: 0"); + addHeader( list, "Test-header: 5" ); + addHeader( list, "Another-Test-header: 50" ); + Header *chosen = getHeader( list, "content-length" ); + munit_assert_string_equal( chosen->value, "0" ); + chosen = getHeader( list, "ConTENt-LenGth" ); + munit_assert_string_equal( chosen->value, "0" ); + chosen = getHeader( list, "test-header" ); + munit_assert_string_equal( chosen->value, "5" ); + chosen = getHeader( list, "another-test-header" ); + munit_assert_string_equal( chosen->value, "50" ); + chosen = getHeader( list, "Does-not-exist" ); + munit_assert_null( chosen ); + return MUNIT_OK; +} + +static MunitTest requestresponse_tests[] = { + { + "/headers/name", /* name */ + testHeadersName, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/headers/value", /* name */ + testHeadersValue, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/headerslist/count", /* name */ + testHeadersListCount, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/headerslist/add", /* name */ + testHeadersListAdd, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/headerslist/charLength", /* name */ + testHeadersListCharLength, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/headerslist/toString", /* name */ + testHeadersToString, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/headerslist/getHeader", /* name */ + testGetHeader, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, +/* Mark the end of the array with an entry where the test +* function is NULL */ + { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } +}; + +MunitSuite requestresponse_test_suite = { + "/requestresponse", /* name */ + requestresponse_tests, /* tests */ + NULL, /* suites */ + 1, /* iterations */ + MUNIT_SUITE_OPTION_NONE /* options */ +}; + +#ifndef MAINTEST +#define MAINTEST + +int main (int argc, char* argv[]) { + return munit_suite_main(&requestresponse_test_suite, NULL, argc, argv); +} + +#endif /* ifndef MAINTEST */ + +#endif /* ifndef REQUESTRESPONSE_TEST */ diff --git a/tests/response.test.c b/tests/response.test.c new file mode 100644 index 0000000..ed0dc72 --- /dev/null +++ b/tests/response.test.c @@ -0,0 +1,109 @@ +#ifndef RESPONSE_TEST +#define RESPONSE_TEST value + +#include "munit/munit.h" +#ifndef REQUESTRESPONSE_C +#define REQUESTRESPONSE_C value +#include "requestresponse.test.c" +#endif /* ifndef REQUESTRESPONSE_C */ +#include "../src/response.c" + + +MunitResult testResponseNewStatus(const MunitParameter params[], + void* user_data_or_fixture){ + Response *rsp = newResponse(); + munit_assert_int( rsp->statusCode, ==, 200 ); + return MUNIT_OK; +} + +MunitResult testResponseNewStatusMessage(const MunitParameter params[], + void* user_data_or_fixture){ + Response *rsp = newResponse(); + munit_assert_string_equal( rsp->statusMessage, "OK" ); + return MUNIT_OK; +} + +MunitResult testResponseNewVersion(const MunitParameter params[], + void* user_data_or_fixture){ + Response *rsp = newResponse(); + munit_assert_float( rsp->version, ==, 1.1 ); + return MUNIT_OK; +} + +MunitResult testResponseSetBody(const MunitParameter params[], + void* user_data_or_fixture){ + Response *rsp = newResponse(); + responseSetBody( rsp, "Testing", 1 ); + munit_assert_string_equal( rsp->body, "Testing" ); + munit_assert_string_equal( getHeader( rsp->headers, "content-length" )->value, "7" ); + return MUNIT_OK; +} + +MunitResult testResponseToString(const MunitParameter params[], + void* user_data_or_fixture){ + Response *rsp = newResponse(); + responseSetBody( rsp, "Testing", 1 ); + munit_assert_string_equal( responseToString( rsp ), "HTTP/1.1 200 OK\r\nContent-Length: 7\r\nContent-Type: text/plain\r\n\r\nTesting" ); + return MUNIT_OK; +} + +static MunitTest response_tests[] = { + { + "/new/status", /* name */ + testResponseNewStatus, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/new/statusMessage", /* name */ + testResponseNewStatusMessage, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/new/version", /* name */ + testResponseNewVersion, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/set/body", /* name */ + testResponseSetBody, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, { + "/to/string", /* name */ + testResponseToString, /* test */ + NULL, /* setup */ + NULL, /* tear_down */ + MUNIT_TEST_OPTION_NONE, /* options */ + NULL /* parameters */ + }, +/* Mark the end of the array with an entry where the test +* function is NULL */ + { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } +}; + +MunitSuite response_test_suite = { + "/response", /* name */ + response_tests, /* tests */ + NULL, /* suites */ + 1, /* iterations */ + MUNIT_SUITE_OPTION_NONE /* options */ +}; + +#ifndef MAINTEST +#define MAINTEST + +int main (int argc, char* argv[]) { + return munit_suite_main(&response_test_suite, NULL, argc, argv); +} + +#endif /* ifndef MAINTEST */ + +#endif /* ifndef RESPONSE_TEST */