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}.
master
Jonathan Hodgson 3 years ago
parent 0e2b9dae2b
commit 48e3092317
  1. 55
      src/request.c
  2. 24
      src/request.h
  3. 106
      src/requestresponse.c
  4. 46
      src/requestresponse.h
  5. 38
      src/response.c
  6. 34
      src/response.h
  7. 5
      tests/request.test.c
  8. 186
      tests/requestresponse.test.c
  9. 109
      tests/response.test.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;
}

@ -4,27 +4,11 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "readline.h"
#include <netinet/in.h>
#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[]);

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

@ -0,0 +1,46 @@
#ifndef REQUESTRESPONSE
#define REQUESTRESPONSE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//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
*/

@ -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);
}
}

@ -0,0 +1,34 @@
#ifndef RESPONSE_H
#define RESPONSE_H
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#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 */

@ -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 */

@ -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 */

@ -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 */
Loading…
Cancel
Save