Now working for simple, non-encrypted requests

making requests to something like example.com over a non-encrypted
connection now works. Binary files are unlikely to work at the moment
although I haven't tried. Also, non-encrypted doesn't work.

I have also changed a little about how tests work. Requests tests now
display much better.
This commit is contained in:
Jonathan Hodgson 2022-01-18 21:45:58 +00:00
parent a91a264a7a
commit 8a5bfe9b36
18 changed files with 725 additions and 148 deletions

View file

@ -1,12 +1,50 @@
#include "proxy.h"
void proxy_startListener(unsigned int port){
Response *upstreamGetResponse(Request *request){
//Here we pretend to be a client
int client_fd = 0;
struct sockaddr_in address;
memset( &address, 0, sizeof(address) );
struct hostent *host = gethostbyname(request->host);
Response *rsp = NULL;
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0){
perror("socket failed");
return NULL;
}
address.sin_family = AF_INET;
address.sin_port = htons( 80 );
// We want the request to go out to whatever the host was resolved to
memcpy( &address.sin_addr, host->h_addr_list[0], host->h_length );
if((connect(client_fd, (struct sockaddr *)&address, sizeof(address)))<0) {
perror("connect failed");
return NULL;
}
char *toSend = requestToString(request);
if ( write( client_fd, toSend, strlen(toSend) ) != strlen(toSend) ){
perror( "Write Error" );
return NULL;
}
rsp = newResponseFromSocket( client_fd );
return rsp;
}
void proxy_startListener(unsigned int port){
//we need to act as an http server
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
memset( &address, 0, sizeof(address) );
int addrlen = sizeof(address);
char *response;
Response *response;
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
@ -30,25 +68,44 @@ void proxy_startListener(unsigned int port){
}
while ( true ){
printf("Listening\n");
printf("Listening on port %i\n", port);
if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
(socklen_t*)&addrlen))<0) {
perror("accept");
return ;
}
//I think eventually I'd like a different thread here for each request
//Not sure how to do that yet though so I'll keep everything on the main
//thread
Request *request = newRequestFromSocket(new_socket);
//If this is an https request - this is the first part
if ( strcmp( request->method, "CONNECT" ) == 0 ){
printf("\n\n%s\n\n", requestToString( request ));
}
// If the host is not defined, then it is not a proxy request
// Note that the host here is where the request should be sent, not
// necesarily the hosts header
if ( strcmp( request->host, "" ) == 0 ){
response = webserverGetResponse(request);
} else {
printf("This is a proxy request\n");
response = upstreamGetResponse(request);
}
send(new_socket , response, strlen(response) , 0 );
char *responseStr = responseToString( response );
// I'm also not convinced that strlen is the best function to use here
// When we get to dealing with binary requests / responses, they may
// well have null characters in them
send(new_socket , responseStr, strlen(responseStr) , 0 );
//close(new_socket);
close(new_socket);
freeRequest( request );
freeResponse( response );
free(responseStr);
}
}

View file

@ -6,6 +6,8 @@
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdbool.h>
@ -13,5 +15,6 @@
#include "webserver.h"
void proxy_startListener( unsigned int port );
Response *upstreamGetResponse(Request *request);
#endif /* ifndef PROXY_H */

View file

@ -10,12 +10,13 @@ Request* newRequest(){
void requestFirstLine( Request *req, char line[] ){
char method[20] = {'\0'}; //Get, post, etc.
char *url = malloc(sizeof(char) *2048); // This may contain the method, the path, the domain and so on
// This may contain the method, the path, the domain and so on
char *url = malloc(sizeof(char) *2048);
memset(url, '\0', sizeof(char) * 2048);
char *currentPos;
char *currentPos = url;
float version = 0;
char protocol[6] = {'\0'};
char host[254] = {'\0'};
int port = -1; // 0 is a valid port number, -1 isn't
//https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
char path[2000] = {'\0'};
@ -24,34 +25,64 @@ void requestFirstLine( Request *req, char line[] ){
req->version = version;
//We've pulled out the easy bits. Now to go through the url and pull out what we need
currentPos = url;
sscanf( currentPos, "%5[^:/]", protocol );
if ( strlen( protocol ) > 0 ){
currentPos = currentPos + strlen(protocol) + 3;
int protEnd = strpos(url, "://" );
if ( protEnd > -1 ){
req->protocol = strndup( url, protEnd );
currentPos += protEnd + 3;
} else {
req->protocol = "";
}
sscanf( currentPos, "%253[^:/]", host );
if ( strlen( host ) > 0 ){
currentPos = currentPos + strlen(host);
req->host = strdup(host);
} else {
req->host = "";
}
if ( currentPos[0] == ':' ){
currentPos++;
sscanf( currentPos, "%i", &port );
req->port = port;
currentPos += (int)floor( log10( port ) ) + 1;
} else {
req->port = -1;
}
sscanf( currentPos, "%2000[^? ]", path );
if ( strlen( path ) > 0 ){
currentPos = currentPos + strlen(path);
currentPos += strlen(path);
req->path = strdup(path);
} else {
req->path = "";
}
req->protocol = malloc(sizeof(char) * strlen(protocol));
strcpy(req->protocol, protocol);
req->host = malloc(sizeof(char) * strlen(host));
strcpy(req->host, host);
req->path = malloc(sizeof(char) * strlen(path));
strcpy(req->path, path);
if ( strlen( currentPos ) > 0 ){
req->queryString = strdup( currentPos );
} else {
req->queryString = "";
}
//We try and work out port and protocol if we don't have them
if ( req->port == -1){
if ( strcmp( "https", req->protocol ) == 0 )
req->port = 443;
else
req->port = 80;
}
if ( strlen(req->protocol) == 0 ){
if ( req->port == 443 )
req->protocol = "https";
else
req->protocol = "http";
}
//The query string is anything left
req->queryString = malloc(sizeof(char) * strlen(currentPos));
strcpy(req->queryString, currentPos);
free(url);
}
@ -70,30 +101,38 @@ Request* newRequestFromSocket(int socket){
//from the body (if there is a body)
valread = fdReadLine( socket, line, 1024);
while ( valread > 2 ){
printf("%s",line );
requestAddHeader( req, line );
//I believe at this point all the headers are done.
valread = fdReadLine( socket , line, 1024);
}
//TODO: make requests work with a body
//contentLength = getHeader( req->headers, "content-length" );
//if ( contentLength != NULL ){
// printf( "Content length is %i\n", atoi(contentLength->value) );
//}
return req;
}
char* requestToString( Request *req, bool proxy){
char* requestToString( Request *req){
unsigned int fullLength = strlen(req->method) + 1 + sizeof( req->path ) +
//11 = [space]http/1.1\r\n
sizeof( req->queryString ) + 11 + headerListCharLength( req->headers );
if ( proxy )
fullLength += sizeof(req->method) + 3 + sizeof(req->host);
if ( strcmp( req->method, "CONNECT" ) == 0 )
fullLength += sizeof(req->host);
char retString[fullLength];
memset( retString, '\0', fullLength );
if (proxy)
sprintf(retString, "%s %s://%s%s%s HTTP/%.1f\r\n%s", req->method,
req->method, req->host, req->path, req->queryString, req->version,
headersToString(req->headers));
if ( strcmp( req->method, "CONNECT" ) == 0 )
sprintf(retString, "%s %s HTTP/%.1f\r\n%s\r\n", req->method,
req->host, req->version, headersToString(req->headers));
else
sprintf(retString, "%s %s%s HTTP/%.1f\r\n%s", req->method,
sprintf(retString, "%s %s%s HTTP/%.1f\r\n%s\r\n", req->method,
req->path, req->queryString, req->version,
headersToString(req->headers));
// rsp->statusMessage,headersToString(rsp->headers),rsp->body);
@ -111,5 +150,15 @@ void requestAddHeader( Request *req, char header[] ){
}
void freeRequest( Request *req ){
free(req->method);
free(req->protocol);
free(req->host);
free(req->path);
freeHeaderList( req->headers );
free(req->queryString);
free(req->body);
free(req);
}

View file

@ -6,9 +6,12 @@
#include <stdbool.h>
#include <string.h>
#include <netinet/in.h>
#include <math.h>
#include "readline.h"
#include "requestresponse.h"
#include "./util.h"
/*
@ -21,9 +24,10 @@ typedef struct {
char *protocol;
char *host;
char *path;
int port;
HeaderList *headers;
char *queryString;
char *body;
void *body;
} Request;
Request* newRequest();
@ -32,12 +36,11 @@ Request* newRequestFromSocket(int socket);
/*
* requestToString
* @prarm req the request to convert
* @param proxy whether we want a proxy request (adds the host to the first line)
*/
char* requestToString( Request *req, bool proxy );
char* requestToString( Request *req );
void requestAddHeader( Request *req, char header[] );
//void* requestAddHeader(Request *req, char line[]);
void freeRequest( Request *req );
#endif /* ifndef REQUEST_H */

View file

@ -4,42 +4,49 @@ Header* newHeader(char *str){
Header *header = malloc(sizeof(Header));
memset(header, 0, sizeof(Header));
int position = -1;
char *position = str;
char *end = str + strlen(str) - 1;
//Let's loose any trailing whitespace
while ( ( *end == '\n' || *end == '\r' ) && end > str ) end--;
while ( *position != ':' && position < end ) position++;
for ( unsigned int i = 0; i < strlen(str); i++ ){
if ( str[i] == ':' ){
position = i;
break;
}
}
if ( position == -1 ){
if ( position == end ){
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 );
header->name = malloc( sizeof(char) * ( position + 1 - str ) );
memset(header->name, '\0', position + 1 - str );
strncpy( header->name, str, position - str );
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 );
//Skip over the colon
position++;
//Skip over any trailing whitespace
while ( *position == '\t' || *position == ' ' ) position++;
//Add 2 when allocating so that we have space for a \0 at the end
header->value = malloc( sizeof(char) * ( end + 2 - position ) );
memset(header->value, '\0', ( end + 2 - position ) );
strncpy( header->value, position, end + 1 - position );
return header;
}
void freeHeader(Header *header){
free(header->name);
free(header->value);
free(header);
}
void addHeader(HeaderList *headers, char *str){
HeaderList *newListItem = malloc(sizeof(HeaderList));
@ -53,6 +60,16 @@ void addHeader(HeaderList *headers, char *str){
last->next = newListItem;
}
void freeHeaderList(HeaderList *headers){
HeaderList *current;
while ( headers != NULL ){
current = headers;
freeHeader( current->header );
headers = headers->next;
free( current );
}
}
// 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

View file

@ -28,9 +28,13 @@ struct HeaderList {
//Creates a new header struct from a string like "Content-Length: 123"
Header* newHeader(char *str);
void freeHeader(Header *header);
//Adds a header to the end of a header list
void addHeader(HeaderList *headers, char *str);
void freeHeaderList(HeaderList *headers);
Header* getHeader(HeaderList *headers, char *name);
unsigned int countHeaders(HeaderList *headers);

View file

@ -3,25 +3,38 @@
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;
response->headers = NULL;
response->statusMessage = NULL;
response->body = NULL;
return response;
}
void responseBarebones(Response *rsp){
rsp->headers = malloc( sizeof( HeaderList ) );
rsp->headers->header = newHeader("Content-Length: 0");
rsp->headers->next = NULL;
addHeader(rsp->headers, "Content-Type: text/plain");
rsp->statusCode = 200;
rsp->statusMessage = "OK";
rsp->version = 1.1;
}
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);
headerListCharLength(rsp->headers) + 4;
if ( rsp->body != NULL ) fullLength += strlen(rsp->body) + 2;
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);
sprintf(retString, "HTTP/%.1f %3d %s\r\n%s\r\n", rsp->version, rsp->statusCode,
rsp->statusMessage,headersToString(rsp->headers));
if ( rsp->body != NULL ) sprintf(retString + strlen(retString), "%s\r\n", (char *)rsp->body);
return strdup(retString);
}
@ -47,7 +60,65 @@ void responseAddHeader(Response *rsp, char header[]){
}
void responseFirstLine( Response *rsp, char line[] ){
// HTTP/1.1 200 OK
float version = 0;
int statusCode = 0;
char statusMessage[256] = {'\0'};
sscanf( line, "HTTP/%f %i %255[^\n]", &version, &statusCode, statusMessage );
rsp->version = version;
rsp->statusCode = statusCode;
rsp->statusMessage = strdup(statusMessage);
}
Response* newResponseFromSocket(int socket){
Response *rsp = newResponse();
int valread;
char line[1024] = {0};
Header *contentLength = NULL;
unsigned int bodySize = 0, bodyRead = 0;
valread = fdReadLine( socket, line, 1024);
responseFirstLine(rsp, line);
//a length of 2 will indicate an empty line which will split the headers
//from the body (if there is a body)
valread = fdReadLine( socket, line, 1024);
while ( valread > 2 ){
responseAddHeader( rsp, line );
valread = fdReadLine( socket , line, 1024);
}
//I believe at this point all the headers are done.
contentLength = getHeader( rsp->headers, "content-length" );
if ( contentLength != NULL ){
bodySize = atoi( contentLength->value );
if (bodySize > 0){
//When the body is not binary data, it is useful to treat it as a
//string. For this reason, I want the bite immediately after it to
//be \0
rsp->body = malloc( bodySize + 1 );
memset( rsp->body + bodySize + 1, '\0', 1 );
while ( bodyRead < bodySize ){
valread=read( socket, rsp->body + bodyRead, bodySize - bodyRead );
if (valread < 0){
perror("Unable to read");
return NULL;
}
bodyRead += valread;
}
}
}
return rsp;
}
void freeResponse( Response *rsp ){
free(rsp->statusMessage);
freeHeaderList( rsp->headers );
free(rsp->body);
free(rsp);
}

View file

@ -20,15 +20,30 @@ typedef struct {
int statusCode;
char *statusMessage;
HeaderList *headers;
char *body;
void *body;
unsigned int headerLength;
} Response;
Response* newResponse();
/*
* creates the minium viable valid response
*/
void responseBarebones(Response *rsp);
char *responseToString(Response *rsp);
/* sets the body of a response to a string
* @param rsp the response
* @param string the string
* @param updateContentLength whether the content-length header should be auto-updated
* Note: if you want to set a binary body, use responseSetBodyRaw
*/
void responseSetBody(Response *rsp, char *string, bool updateContentLength);
// TODO:
//void responseSetBodyRaw(Response *rsp, void *body, size_t size, bool updateContentLength);
void responseAddHeader(Response *rsp, char header[]);
Response* newResponseFromSocket(int socket);
void responseFirstLine( Response *req, char line[] );
void freeResponse( Response *rsp );
#endif /* ifndef REQUEST_H */

7
src/util.c Normal file
View file

@ -0,0 +1,7 @@
#include "util.h"
int strpos(char *haystack, char *needle) {
char *p = strstr(haystack, needle);
if ( p != NULL ) return p - haystack;
return -1;
}

9
src/util.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef UTIL_H
#define UTIL_H
#include <stdio.h>
#include <string.h>
int strpos(char *haystack, char *needle);
#endif

View file

@ -1,7 +1,8 @@
#include "webserver.h"
char* webserverGetResponse( Request *req ){
Response* webserverGetResponse( Request *req ){
Response *rsp = newResponse();
responseBarebones(rsp);
responseSetBody(rsp, "Test", 1);
return responseToString(rsp);
return rsp;
}

View file

@ -7,7 +7,7 @@
#include "request.h"
#include "response.h"
char* webserverGetResponse( Request *req );
Response* webserverGetResponse( Request *req );
#endif /* ifndef WEBSERVER_H