init
authorhgn <hgodden00@gmail.com>
Wed, 21 Aug 2024 18:49:37 +0000 (19:49 +0100)
committerhgn <hgodden00@gmail.com>
Wed, 21 Aug 2024 18:49:37 +0000 (19:49 +0100)
.gitignore [new file with mode: 0644]
http.h [new file with mode: 0644]
server.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..36f971e
--- /dev/null
@@ -0,0 +1 @@
+bin/*
diff --git a/http.h b/http.h
new file mode 100644 (file)
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<len; i ++ )
+   {
+      hash = ((hash << 5) + hash) + str[i];
+   }
+
+   return hash;
+}
+
+static void http_request_admit_char( struct http_request *req, 
+                                     char *buf, int buf_len, 
+                                     int *buf_ptr, char c )
+{
+   int i = *buf_ptr;
+
+   if( i < buf_len )
+   {
+      buf[ i ++ ] = c;
+   }
+
+   *buf_ptr = i;
+}
+
+/* returns 0 if fail */
+static int http_request_parse_char( struct http_request *req, char c )
+{
+try_again:;
+
+   if( c > 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; i<len; i ++ )
+   {
+      if( !http_request_parse_char( req, buf[i] ) )
+         return -1;
+
+      if( req->state == k_parse_state_complete )
+         return i;
+   }
+
+   return len;
+}
diff --git a/server.c b/server.c
new file mode 100644 (file)
index 0000000..f1ddca6
--- /dev/null
+++ b/server.c
@@ -0,0 +1,252 @@
+#include <sys/socket.h>
+#include <sys/fcntl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include <poll.h>
+#include <signal.h>
+
+#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"
+"<html><body><h1>HELLO ITS A WEBSITE</h1></body></html>\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; i<MAX_CLIENTS; i ++ )
+   {
+      _clients[i].connfd = -1;
+   }
+
+       listenfd = socket( AF_INET, SOCK_STREAM, 0 );
+
+   if( listenfd == -1 )
+   {
+      fprintf( stderr, "socket() error: %s\n", strerror(errno) );
+      return 1;
+   }
+
+   if( fcntl( listenfd, F_SETFL, O_NONBLOCK ) == -1 )
+   {
+      fprintf( stderr, "fcntl() error: %s\n", strerror(errno) );
+      return 2;
+   }
+
+       serv_addr.sin_family = AF_INET;
+       serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
+       serv_addr.sin_port = htons( 1521 );
+
+       if( bind( listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr) ) == -1 )
+   {
+      fprintf( stderr, "bind() error: %s\n", strerror(errno) );
+      return 3;
+   }
+
+       if( listen( listenfd, 10 ) == -1 )
+   {
+      fprintf( stderr, "listen() error: %s\n", strerror(errno) );
+      return 4;
+   }
+
+       while( _run_server )
+       {
+      /* accepting clients */
+      {
+         int fd = accept( listenfd, (struct sockaddr*)NULL, NULL );
+         if( fd == -1 )
+         {
+            if( !( errno == EAGAIN || errno == EWOULDBLOCK ) )
+            {
+               fprintf( stderr, "accept() warning: %s\n", strerror(errno) );
+            }
+         }
+         else
+         {
+            struct client *client = NULL;
+            for( int i=0; i<MAX_CLIENTS; i ++ )
+            {
+               struct client *c = &_clients[ i ];
+               if( c->connfd == -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; i<MAX_CLIENTS; i ++ )
+         {
+            struct client *client = &_clients[ i ];
+            if( client->connfd == -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; i<MAX_CLIENTS; i ++ )
+   {
+      struct client *client = &_clients[ i ];
+      if( client->connfd == -1 ) continue;
+      close( client->connfd );
+   }
+
+   close( listenfd );
+}