From 908596eaa34bcf5b194a8950a8cb51039a2f5376 Mon Sep 17 00:00:00 2001 From: hgn Date: Wed, 21 Aug 2024 19:49:37 +0100 Subject: [PATCH] init --- .gitignore | 1 + http.h | 329 +++++++++++++++++++++++++++++++++++++++++++++++++++++ server.c | 252 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 .gitignore create mode 100644 http.h create mode 100644 server.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36f971e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/* diff --git a/http.h b/http.h new file mode 100644 index 0000000..0a205ed --- /dev/null +++ b/http.h @@ -0,0 +1,329 @@ +struct http_request +{ + char token[ 128 ]; + int token_len; + + enum http_method + { + k_http_method_get, + k_http_method_post + } + method; + + char resource[ 128 ]; + int resource_len; + + char host[ 64 ]; + int host_len; + + char user_agent[ 64 ]; + int user_agent_len; + + enum http_connection + { + k_http_connection_unknown, + k_http_connection_close, + k_http_connection_keepalive, + } + connection; + + enum parse_state + { + k_parse_await, + k_parse_state_method, + k_parse_state_url, + k_parse_state_lf, + k_parse_state_lf_eom, + k_parse_state_csp, + k_parse_state_protocol, + k_parse_state_header_key, + k_parse_state_header_value, + k_parse_state_body, + k_parse_state_complete, + k_parse_state_errors, + k_parse_state_error_out_of_range, + k_parse_state_error_syntax, + k_parse_state_error_method + } + state; + + enum header_key + { + k_header_key_unknown, + k_header_key_user_agent, + k_header_key_host, + k_header_key_connection, + } + header_key; +}; + +unsigned int http_strdjb2( const char *str, int len ) +{ + unsigned int hash = 5381, c; + + for( int i=0; i 127 || c == 0 ) + { + fprintf( stderr, "(parser) Generic out of range error\n" ); + req->state = k_parse_state_error_out_of_range; + return 0; + } + + enum parse_state state = req->state; + + if( state == k_parse_await ) + { + if( c == '\r' || c == '\n' || c == ' ' ) + { + /* ignore whitespace until we get a real character */ + } + else if( c >= 'A' && c <= 'Z' ) + { + req->state = k_parse_state_method; + goto try_again; + } + else + { + fprintf( stderr, "(parser:await) While waiting we got an unwanted character 'x%x'\n", c ); + req->state = k_parse_state_error_syntax; + } + + return 1; + } + + if( state == k_parse_state_method ) + { + if( c >= 'A' && c <= 'Z' ) + { + http_request_admit_char( req, req->token, sizeof(req->token), + &req->token_len, c ); + } + else + { + if( c == ' ' ) + { + req->state = k_parse_state_url; + + if( !strncmp( req->token, "GET", req->token_len ) ) + { + req->method = k_http_method_get; + } + else if( !strncmp( req->token, "POST", req->token_len ) ) + { + req->method = k_http_method_post; + } + else + { + fprintf( stderr, "(parser:method) Unknown method (%.*s)\n", + req->token_len, req->token ); + req->state = k_parse_state_error_method; + } + } + else + { + fprintf( stderr, "(parser:method) Syntax, unexpected char '0x%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + } + } + else if( state == k_parse_state_url ) + { + if( c == ' ' ) + { + req->state = k_parse_state_protocol; + } + else if( c < '!' ) + { + fprintf( stderr, "(parser:url) Syntax, unexpected char '0x%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + else + { + http_request_admit_char( req, req->resource, sizeof(req->resource), + &req->resource_len, c ); + } + } + else if( state == k_parse_state_protocol ) + { + if( c == '\r' ) + { + req->state = k_parse_state_lf; + req->token_len = 0; + } + else if( c >= '!' ) + { + http_request_admit_char( req, req->token, sizeof(req->token), + &req->token_len, c ); + } + else + { + fprintf( stderr, "(parser:protocol) Syntax, unexpected char '0x%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + } + else if( state == k_parse_state_lf || state == k_parse_state_lf_eom ) + { + if( c == '\n' ) + { + if( state == k_parse_state_lf_eom ) req->state = k_parse_state_complete; + else req->state = k_parse_state_header_key; + } + else + { + fprintf( stderr, "(parser:lf|lf_eom) Syntax, expected \\n, got '%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + } + else if( state == k_parse_state_header_key ) + { + if( c == ':' ) + { + req->state = k_parse_state_csp; + + if( !strncmp( req->token, "Host", req->token_len ) ) + { + req->header_key = k_header_key_host; + } + else if( !strncmp( req->token, "Connection", req->token_len ) ) + { + req->header_key = k_header_key_connection; + } + else if( !strncmp( req->token, "User-Agent", req->token_len ) ) + { + req->header_key = k_header_key_user_agent; + } + else + { + req->header_key = k_header_key_unknown; + } + + req->token_len = 0; + } + else if( c == '\r' ) + { + req->state = k_parse_state_lf_eom; + } + else + { + if( c > ' ' ) + { + http_request_admit_char( req, req->token, sizeof(req->token), + &req->token_len, c ); + } + else + { + fprintf( stderr, "(parser:header_key) Syntax, unexpected '%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + } + } + else if( state == k_parse_state_header_value ) + { + if( c == '\r' ) + { + if( req->header_key == k_header_key_connection ) + { + if( !strncmp( req->token, "close", req->token_len ) ) + { + req->connection = k_http_connection_close; + } + else if( !strncmp( req->token, "keep-alive", req->token_len ) ) + { + req->connection = k_http_connection_keepalive; + } + + req->token_len = 0; + } + + req->state = k_parse_state_lf; + } + else + { + if( c >= ' ' ) + { + if( req->header_key == k_header_key_host ) + { + http_request_admit_char( req, req->host, sizeof(req->host), + &req->host_len, c ); + } + else if( req->header_key == k_header_key_connection ) + { + http_request_admit_char( req, req->token, sizeof(req->token), + &req->token_len, c ); + } + else if( req->header_key == k_header_key_user_agent ) + { + http_request_admit_char( req, req->user_agent, sizeof(req->user_agent), + &req->user_agent_len, c ); + } + else if( req->header_key == k_header_key_unknown ) + { + /* into the void.. :) */ + } + } + else + { + fprintf( stderr, "(parser:header_value) Syntax, unexpected '%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + } + } + else if( state == k_parse_state_csp ) + { + if( c == ' ' ) req->state = k_parse_state_header_value; + else + { + fprintf( stderr, "(parser:header_key.csp) Syntax, expected ' ', got '%x'\n", (unsigned int)c ); + req->state = k_parse_state_error_syntax; + } + } + else + return 0; + + if( req->state >= k_parse_state_errors ) return 0; + else return 1; +} + +/* return value + * -1 if parse failure + * number of bytes parsed + */ +static int http_request_parse( struct http_request *req, char *buf, int len ) +{ + for( int i=0; istate == k_parse_state_complete ) + return i; + } + + return len; +} diff --git a/server.c b/server.c new file mode 100644 index 0000000..f1ddca6 --- /dev/null +++ b/server.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "http.h" + +const char *WEBSITE = +"HTTP/1.0 200 OK\r\n" +"Server: u461\r\n" +"Content-Type: text/html\r\n" +"\r\n" +"

HELLO ITS A WEBSITE

\r\n"; + +#define SERVER_HEADER "Server: u461\r\n" +#define HTTP_ERROR( X ) \ + "HTTP/1.0 " X "\r\n" SERVER_HEADER "Connection: close\r\n\r\n" + +const char *k_response_saturated = HTTP_ERROR( "503 Service Unavailable" ); +const char *k_response_parsefail = HTTP_ERROR( "500 Internal Server Error" ); +const char *k_response_toobig = HTTP_ERROR( "413 Payload Too Large" ); +const char *k_response_temp = HTTP_ERROR( "501 Not Implemented" ); + +#define MAX_CLIENTS 10 +#define REQUEST_MAX_SIZE 1024 + +struct client +{ + int connfd, total_request_bytes; + struct http_request request; +} +_clients[ MAX_CLIENTS ]; + +int _run_server = 1; + +static void int_handle( int sig ) +{ + _run_server = 0; + printf( "Ok stopping..\n" ); +} + +int main(int argc, char *argv[]) +{ + signal( SIGINT, int_handle ); + + int listenfd; + struct sockaddr_in serv_addr = {0}; + for( int i=0; iconnfd == -1 ) + { + client = c; + break; + } + } + + if( client ) + { + memset( client, 0, sizeof(struct client) ); + client->connfd = fd; + client->total_request_bytes = 0; + printf( "Accepted new client\n" ); + } + else + { + fprintf( stderr, "Server saturated, cannot accept new client.\n" ); + write( fd, k_response_saturated, strlen(k_response_saturated) ); + close( fd ); + } + } + } + + /* Handle incoming data */ + { + for( int i=0; iconnfd == -1 ) continue; + + char recv_buf[ 512 ]; + int len = recv( client->connfd, recv_buf, sizeof(recv_buf), 0 ); + + if( len == 0 ) + { + /* Client did an orderly shutdown */ + printf( "Client #%d disconnected\n", i ); + close( client->connfd ); + client->connfd = -1; + } + else if( len == -1 ) + { + if( errno == EAGAIN || errno == EWOULDBLOCK ) + { + /* No incoming data read */ + continue; + } + else + { + fprintf( stderr, "recv() error: %s\n", strerror(errno) ); + close( client->connfd ); + client->connfd = -1; + } + } + else + { + //printf( "RAW BUF ------\n%.*s\n-----------\n", len, recv_buf ); + + int remaining = len; + + while( remaining ) + { + /* New data has been read off the socket */ + int amt = http_request_parse( &client->request, + recv_buf + (len-remaining), + remaining ); + if( amt == -1 ) + { + fprintf( stderr, "Failed to parse request from client#%d (reason: %d)\n", i, client->request.state ); + write( client->connfd, + k_response_parsefail, + strlen(k_response_parsefail) ); + close( client->connfd ); + client->connfd = -1; + break; + } + else + { + remaining -= amt; + client->total_request_bytes += amt; + + if( client->request.state == k_parse_state_complete ) + { + /* handle request... */ + printf( "Request info ( " ); + printf( "%s )\n", client->request.method == k_http_method_get? "GET": "POST" ); + printf( " Host: %.*s %s\n", client->request.host_len, client->request.host, client->request.host_len == sizeof(client->request.host)? "[TRUNC]": "" ); + printf( " User-agent: %.*s %s\n", client->request.user_agent_len, client->request.user_agent, client->request.user_agent_len == sizeof(client->request.user_agent)? "[TRUNC]": "" ); + printf( " Resource: %.*s %s\n", client->request.resource_len, client->request.resource, client->request.resource_len == sizeof(client->request.resource)? "[TRUNC]": "" ); + + + if( !strncmp( client->request.resource, "/", + client->request.resource_len ) ) + { + printf( "Gave website :D\n" ); + write( client->connfd, WEBSITE, strlen(WEBSITE) ); + } + else + { + fprintf( stderr, "Responding #%d with 501\n", i ); + write( client->connfd, + k_response_temp, strlen(k_response_temp) ); + } + + client->total_request_bytes = 0; + + /* reset parser */ + memset( &client->request, 0, sizeof(struct http_request) ); + + close( client->connfd ); + client->connfd = -1; + break; + } + else + { + if( client->total_request_bytes > 1024 ) + { + fprintf( stderr, "Client#%d sent us too much\n", i ); + write( client->connfd, + k_response_toobig, strlen(k_response_toobig) ); + close( client->connfd ); + client->connfd = -1; + break; + } + } + } + } + } + } + } + + sleep(1); + } + + for( int i=0; iconnfd == -1 ) continue; + close( client->connfd ); + } + + close( listenfd ); +} -- 2.25.1