Compare commits

...

10 Commits

Author SHA1 Message Date
SuperNovaa41
04abd8b152 progress? 2025-02-07 13:54:59 -05:00
SuperNovaa41
8d810650e5 adds a bunch of shit 2025-01-30 22:37:17 -05:00
SuperNovaa41
71a197c9cb updates todo some more 2025-01-30 15:13:46 -05:00
SuperNovaa41
e1f9046db0 adds a bunch of todos and completes init functions 2025-01-30 15:10:06 -05:00
SuperNovaa41
18579c7981 adds some file stuff and cleans up a bit 2025-01-30 14:24:17 -05:00
SuperNovaa41
97d970c0cd added status codes 2025-01-30 13:50:42 -05:00
SuperNovaa41
986290f6de fixes recv crashing bug 2025-01-30 13:17:18 -05:00
SuperNovaa41
14cc5ef574 finishes commenting structs 2025-01-30 12:58:05 -05:00
SuperNovaa41
e04f9b6d95 adds all the headers in the structs 2025-01-30 12:44:22 -05:00
SuperNovaa41
aaeac2ad7f setup response header struct 2025-01-29 10:38:30 -05:00
9 changed files with 792 additions and 36 deletions

View File

@ -1,7 +1,7 @@
TARGET=http
CC=gcc
OBJ = main.o tcp.o http.o
OBJ = main.o tcp.o http.o file.o
$(TARGET): $(OBJ)
mkdir -p ../build
@ -10,7 +10,8 @@ $(TARGET): $(OBJ)
main.o: include/tcp.h
tcp.o: include/tcp.h include/http.h
http.o: include/http.h
http.o: include/http.h include/file.h
file.o: include/file.h
.PHONY: clean
clean:

71
src/file.c Normal file
View File

@ -0,0 +1,71 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "include/file.h"
void get_file_from_uri(char** uri, char** buffer)
{
char* tok;
FILE* f;
int err, i;
size_t bufsize, newlen;
char* filepath;
// this is assuming absPath, NOT abs uri
//
// absURI is ONLY generate them in requests to proxies, otherwise
// will be an abs path
/**
* TODO: if no file is specified, append index.html
*/
asprintf(&filepath, "./%s", *uri);
if (filepath[strlen(filepath) - 1] == '/')
asprintf(&filepath, "%sindex.html", filepath);
f = fopen(filepath, "r");
if (f == NULL) {
perror("fopen");
return; // TODO: 404
}
err = fseek(f, 0L, SEEK_END); // go to the end of the file
if (err != 0) {
perror("fseek");
fclose(f);
exit(EXIT_FAILURE);
}
bufsize = ftell(f); // lets get the length of the file here
if (bufsize == -1) {
perror("ftell");
fclose(f);
exit(EXIT_FAILURE);
}
(*buffer) = malloc(sizeof(char) * (bufsize + 1));
if (*buffer == NULL) {
perror("malloc");
fclose(f);
exit(EXIT_FAILURE);
}
rewind(f); // go back to the beginning of the file
newlen = fread(*buffer, sizeof(char), bufsize, f);
if (ferror(f) != 0) {
perror("fread");
fclose(f);
exit(EXIT_FAILURE);
} else {
(*buffer)[newlen++] = '\0'; // this is just to be safe
}
fclose(f);
}

View File

@ -3,22 +3,7 @@
#include <stdio.h>
#include "include/http.h"
// what we want to do here is start parsing the http message
// GET / HTTP/1.1
// Host: localhost:8080
// User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
// Accept-Language: en-US,en;q=0.5
// Accept-Encoding: gzip, deflate, br, zstd
// Connection: keep-alive
// Upgrade-Insecure-Requests: 1
// Sec-Fetch-Dest: document
// Sec-Fetch-Mode: navigate
// Sec-Fetch-Site: none
// Sec-Fetch-User: ?1
// Priority: u=0, i
#include "include/file.h"
http_method_t str_to_http_method(char* input)
{
@ -44,7 +29,161 @@ const char* http_method_to_str(http_method_t input)
}
}
void parse_request_line(char** line, http_request* request)
const char* http_status_code_to_str(http_status_code_t code)
{
switch(code) {
case HTTP100:
return "Continue";
case HTTP101:
return "Switching Protocols";
case HTTP200:
return "OK";
case HTTP201:
return "Created";
case HTTP202:
return "Accepted";
case HTTP203:
return "Non-Authoritative Information";
case HTTP204:
return "No Content";
case HTTP205:
return "Reset Content";
case HTTP206:
return "Partial Content";
case HTTP300:
return "Multiple Choices";
case HTTP301:
return "Moved Permanently";
case HTTP302:
return "Found";
case HTTP303:
return "See Other";
case HTTP304:
return "Not Modified";
case HTTP305:
return "Use Proxy";
case HTTP307:
return "Temporary Redirect";
case HTTP400:
return "Bad Request";
case HTTP401:
return "Unauthorized";
case HTTP402:
return "Payment Required";
case HTTP403:
return "Forbidden";
case HTTP404:
return "Not Found";
case HTTP405:
return "Method Not Allowed";
case HTTP406:
return "Not Acceptable";
case HTTP407:
return "Proxy Authentication Required";
case HTTP408:
return "Request Time-out";
case HTTP409:
return "Conflict";
case HTTP410:
return "Gone";
case HTTP411:
return "Length Required";
case HTTP412:
return "Precondition Failed";
case HTTP413:
return "Request Entity Too Large";
case HTTP414:
return "Request-URI Too Large";
case HTTP415:
return "Unsupported Media Type";
case HTTP416:
return "Requested range not satisfiable";
case HTTP417:
return "Expectation Failed";
case HTTP500:
return "Internal Server Error";
case HTTP501:
return "Not Implemented";
case HTTP502:
return "Bad Gateway";
case HTTP503:
return "Service Unavailable";
case HTTP504:
return "Gateway Time-out";
case HTTP505:
return "HTTP Version not supported";
default:
return "UNKNOWN";
}
}
void init_general_headers(struct general_headers* headers)
{
headers->cache_control = "";
headers->connection = "";
headers->date = "";
headers->pragma = "";
headers->trailer = "";
headers->transfer_encoding = "";
headers->upgrade = "";
headers->via = "";
headers->warning = "";
}
void init_http_request(http_request* request)
{
init_general_headers(&(request->general_headers));
request->request_headers.accept = "";
request->request_headers.accept_charset = "";
request->request_headers.accept_encoding = "";
request->request_headers.accept_language = "";
request->request_headers.authorization = "";
request->request_headers.expect = "";
request->request_headers.from = "";
request->request_headers.host = "";
request->request_headers.if_match = "";
request->request_headers.if_modified_since = "";
request->request_headers.if_none_match = "";
request->request_headers.if_range = "";
request->request_headers.if_unmodified_since = "";
request->request_headers.max_forwards = "";
request->request_headers.proxy_authorization = "";
request->request_headers.range = "";
request->request_headers.referer = "";
request->request_headers.te = "";
request->request_headers.user_agent = "";
}
void init_http_response(http_response* response)
{
init_general_headers(&(response->general_headers));
response->response_headers.content_length = 0;
response->response_headers.accept_ranges = "";
response->response_headers.age = "";
response->response_headers.etag = "";
response->response_headers.location = "";
response->response_headers.proxy_authenticate = "";
response->response_headers.retry_after = "";
response->response_headers.server = "";
response->response_headers.vary = "";
response->response_headers.www_authenticate = "";
}
void free_http_request(http_request* request)
{
free(request->request_line.request_uri);
}
void free_http_response(http_response* response)
{
free(response->message_body);
}
void parse_request_line(char** line, http_request_line* request)
{
char *tok, *ver, *num;
@ -103,6 +242,7 @@ void parse_http_request(char** buffer, http_request* request)
// first lets split each line
char* line;
http_request_line r_line;
// parse the first line
line = strtok(*buffer, "\n");
@ -111,7 +251,9 @@ void parse_http_request(char** buffer, http_request* request)
exit(EXIT_FAILURE);
}
parse_request_line(&line, request);
parse_request_line(&line, &r_line);
request->request_line = r_line;
/**
* TODO: start accepting the headers
@ -132,3 +274,24 @@ void parse_http_request(char** buffer, http_request* request)
}
*/
}
void create_http_response(http_request* request, http_response* response)
{
get_file_from_uri(&(request->request_line.request_uri), &(response->message_body));
init_http_response(response);
response->response_headers.content_length = strlen(response->message_body);
// HTTP/1.1
response->status.version.major = 1;
response->status.version.minor = 1;
// TODO: obviouslly add something to determine the status code to use
response->status.status_code = HTTP200;
response->status.reason_phrase = http_status_code_to_str(response->status.status_code);
response->response_headers.content_type = "text/html";
}

6
src/include/file.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef FILE_H
#define FILE_H
void get_file_from_uri(char** uri, char** buffer);
#endif

334
src/include/headers.h Normal file
View File

@ -0,0 +1,334 @@
#include <stdlib.h>
#ifndef HEADERS_H
#define HEADERS_H
struct general_headers {
/**
* Cache-Control: ...
*
* Used to control the cache
*
* TODO: implement cache,
* for no we will be using "no-cache" for response
*/
const char* cache_control;
/**
* Connection: ...
*
* requests will often have "keep-alive", but since we don't
* support maintain constant connections, every header must have
* "close"
*
* TODO: implmenet constant connections?
*/
const char* connection;
/**
* Date: HTTP-date
*
* must always be included unless the status code is 100 or 101
* or 500/503 if its inconvenient/impossible to generate a valid Date
*/
const char* date;
/**
* Pragma: ...
*
* no-cache control directive should be paired with Pragma: "no-cache"
*/
const char* pragma;
/**
* Trailer: fieldnames...
*
* This indicates that there will be a trailing header with the provided
* field names
*
* If there is no trailer, this field should not include anything
*
* can not include the following header fields:
* transfer-encoding
* content-length
* trailer
*/
const char* trailer;
/**
* Transfer-Encoding: ...
*
* Indicates what type of transformation has been applied to
* the message body
*/
const char* transfer_encoding;
/**
* Upgrade: ...
*
* Allows the client to specify what additional protocols
* it supports and would like to use
*
* Must use this in the 101 (Switching Protocols) response
*/
const char* upgrade;
/**
* Via: ...
*
* MUST be used by gateways and proxies to indicate
* intermediate protocols and receipients
*/
const char* via;
/**
* Warning: ...
*
* carries additional info about the status or transformation
* of a message, which might not be reflected in the message
*
* may carry more than one header TODO: implement this i suppose
*/
const char* warning;
};
struct response_headers {
/**
* Content-Length: ...
*
* length of the content
*/
uint content_length;
/**
* Accept-Ranges: ...
*
* If we should accept a range request for a resource, defaults to NONE
*
*
*/
const char* accept_ranges;
/**
* Age: ...
*
* represents time in seconds
*
* time since the response was generated, this is for cached responses,
* if we are cacheing, this header is REQUIRED
*/
const char* age;
/**
* ETag: ...
*
* Current value of the entity tag for the requested tag
* only useful if we're sending an entity body+header aswell
*/
const char* etag;
/**
* Location: absoluteURI
*
* For 201 (created), this should be the new source that was created
* for 3xx this should indicate the servers preferred URI for automatic redirection
*/
const char* location;
/**
* Proxy-Authenticate: ...
*
* For 407 this is a required field
*/
const char* proxy_authenticate;
/**
* Retry-After: (HTTP-date | delta-seconds)
*
* can be used with a 503 to indicate how long its expeccted to be unavailable
* may be used with 3xx to tell how long to wait before redirecting
*/
const char* retry_after;
/**
* Server: product/version ...
*
* Information about the HTTP server
*/
const char* server;
/**
* Vary: (* | field-name)
*
* This is for a cacheable (or non cacheable reponse)
* that is subject to server-driven negotiation
*/
const char* vary;
/**
* WWW-Authenticate: ...
*
* required for 401
*/
const char* www_authenticate;
/**
* Content-Type: ...
*
* type of the content
*/
const char* content_type;
};
struct request_headers {
/**
* Accept: ...
*
* Specify all mediatypes which are acceptable for the response
*
*/
const char* accept;
/**
* Accept-Charset: ...
*
* Sppeicgy the character sets that are acceptable for the response
*/
const char* accept_charset;
/**
* Accept-Encoding: ...
*
* restricts the content-codings that are acceptable in the response
*/
const char* accept_encoding;
/**
* Accept-Language: ...
*
* restricts the set of langauges that are preferred as a response to the request
*/
const char* accept_language;
/**
* Authorization: ...
*
* Used to auth itself with the server
*/
const char* authorization;
/**
* Expect: ...
*
* Used to indicate the server behaviour required by the client
*
* A server that doeesn't understand/unable to compy, must respond
* with the appropriate error status
* If the expectations cant be met, respond with 417, otherwise, some 4xx
*/
const char* expect;
/**
* From: mailbox
*
* If given, SHOULD contain an internet email address for the
* user that controls the requesting user agent
*/
const char* from;
/**
* Host: ...
*
* Contains the host and port of the resource being requested
*/
const char* host;
/**
* If-Match: ...
*
* used with a method to make it conditional
* uses entity tags..
*/
const char* if_match;
/**
* If-Modified-Since: ...
*
* used with a method to make it conditional
* uses entity tags..
*/
const char* if_modified_since;
/**
* If-None-Match: ...
*
* used with a method to make it conditional
* uses entity tags..
*/
const char* if_none_match;
/**
* If-Range: ...
*
* used with a method to make it conditional
* uses entity tags..
*/
const char* if_range;
/**
* If-Unmodified-Since: ...
*
* used with a method to make it conditional
* uses entity tags..
*/
const char* if_unmodified_since;
/**
* Max-Forwards: ...
*
* Provided with TRACE and OPTIONS method to limit
* the number of proxies or gateways that can forward the request
*/
const char* max_forwards;
/**
* Proxy-Authorization: ...
*
* Allows the client to identify itself to a proxy which requires auth
*/
const char* proxy_authorization;
/**
* Range: ...
*
* Specify range...
*/
const char* range;
/**
* Referer: ...
*
* Specify the address from which the request-uri was obtained
*/
const char* referer;
/**
* TE: ...
*
* what transfer-encodings it is willing to accept in the response
* and whether or not it is willing to accept trailer fields
* in a chunked transfer-coding
*/
const char* te;
/**
* User-Agent: ...
*
* The useragent of the accessing client
*
*/
const char* user_agent;
};
#endif

View File

@ -1,3 +1,5 @@
#include "headers.h"
#ifndef HTTP_H
#define HTTP_H
@ -7,6 +9,28 @@ typedef enum {
UNKNOWN,
} http_method_t;
typedef enum {
HTTP100 = 100,
HTTP101,
HTTP200 = 200,
HTTP201, HTTP202, HTTP203,
HTTP204, HTTP205, HTTP206,
HTTP300 = 300,
HTTP301, HTTP302, HTTP303,
HTTP304, HTTP305,
HTTP307 = 307,
HTTP400 = 400,
HTTP401, HTTP402, HTTP403,
HTTP404, HTTP405, HTTP406,
HTTP407, HTTP408, HTTP409,
HTTP410, HTTP411, HTTP412,
HTTP413, HTTP414, HTTP415,
HTTP416, HTTP417,
HTTP500 = 500,
HTTP501, HTTP502, HTTP503,
HTTP504, HTTP505
} http_status_code_t;
struct http_version {
int major;
int minor;
@ -16,15 +40,44 @@ typedef struct {
http_method_t method;
char* request_uri;
struct http_version version;
} http_request_line;
typedef struct {
struct http_version version;
http_status_code_t status_code;
const char* reason_phrase;
} http_status_line;
typedef struct {
http_request_line request_line;
struct general_headers general_headers;
struct request_headers request_headers;
} http_request;
typedef struct {
http_status_line status;
struct general_headers general_headers;
struct response_headers response_headers;
char* message_body;
} http_response;
http_method_t str_to_http_method(char* input);
const char* http_method_to_str(http_method_t input);
void parse_request_line(char** line, http_request* request);
const char* http_status_code_to_str(http_status_code_t code);
void parse_request_line(char** line, http_request_line* request);
void init_general_headers(struct general_headers* headers);
void init_http_request(http_request* request);
void init_http_response(http_response* response);
void free_http_request(http_request* request);
void free_http_response(http_response* response);
void parse_http_request(char** buffer, http_request* request);
void create_http_response(http_request* request, http_response* response);
#endif

View File

@ -16,7 +16,7 @@ typedef struct {
void setup_socket(server_conn_t* conn);
void recv_message(char** buffer, int socket);
int recv_message(char** buffer, int socket);
/// Threaded function to handle http requests
/// takes client socket as an argument

View File

@ -5,6 +5,40 @@
#include "include/tcp.h"
/**
* TODO:
*
* we need to parse the request headers and put them into a struct
*
* also need to implement the features the headers are requesting
*
* for now we're just using the request line and the status line for basic info
* but eventually i would like to start implementing headers
*
* file config for port, file location, etc
*
* send INTERNAL SERVER ERROR for failures instead of exiting the thread
*
* send 404 for if the file couldnt be found (use errno for this)
*
* setup the http_request parser to fill the request struct
*
* set the init_response to fill the required default headers (e.g. date)
*
* TODO: see where content-length is supposed to be ?
* TODO: first thing we want to do is start sending a basic response
* aka, make the status line, fill content-length and fill the message body,
* then send it
*
* cleanup, make sure everything is being free'd and properly cleaned up
*
* final:
* would like to have a cache at some point
* make sure everything is fully rfc compliant
* segment the code even more to make stuff as resuable as possible
*/
int main(void)
{
server_conn_t server;

122
src/tcp.c
View File

@ -5,6 +5,7 @@
#include <netinet/in.h>
#include <poll.h>
#include <errno.h>
#include <string.h>
#include "include/tcp.h"
#include "include/http.h"
@ -42,7 +43,7 @@ void setup_socket(server_conn_t* conn)
}
}
void recv_message(char** buffer, int socket)
int recv_message(char** buffer, int socket)
{
ulong current_size, bytes_recv;
int status;
@ -70,42 +71,135 @@ void recv_message(char** buffer, int socket)
// lets recv BUFFER_INC_LEN amount of bytes
status = recv(socket, *buffer + bytes_recv, BUFFER_INC_LEN, MSG_DONTWAIT);
if (status < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) // these signify that that were just no data to read, dw about this
// these signify that that were just no data to read, dw about this
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("recv");
break; // don't want to crash on a recv failure, lets just break
}
return -1;
}
bytes_recv += status;
} while (status == BUFFER_INC_LEN); // break when we stop receiving, status < BUFFER_INC_LEN means no more data after this last call
// break when we stop receiving, status < BUFFER_INC_LEN
// means no more data after this last call
} while (status == BUFFER_INC_LEN);
(*buffer)[bytes_recv] = '\0';
return 0;
}
void add_header_to_string(char** msg_to_send, const char* headers[][2], size_t len)
{
int i;
for (i = 0; i < len; i++) {
if (strcmp(headers[i][0], "") == 0) // if its empty we don't want this header
continue;
(*msg_to_send) = realloc(*msg_to_send, sizeof(char) *
(strlen(*msg_to_send) +
strlen(headers[i][0]) +
strlen(headers[i][1])
));
strcat(*msg_to_send, headers[i][1]);
strcat(*msg_to_send, headers[i][0]);
}
}
void send_message(int socket, http_response* response)
{
int err;
size_t msg_len;
char* msg_to_send = NULL;
asprintf(&msg_to_send, "HTTP/%d.%d %d %s", response->status.version.major,
response->status.version.minor, response->status.status_code,
response->status.reason_phrase);
const char* general_headers[][2] = {
{response->general_headers.cache_control, "\nCache-Control: "},
{response->general_headers.connection, "\nConnection: "},
{response->general_headers.date, "\nDate: "},
{response->general_headers.pragma, "\nPragma: "},
{response->general_headers.trailer, "\nTrailer: "},
{response->general_headers.transfer_encoding, "\nTransfer-Encoding: "},
{response->general_headers.upgrade, "\nUpgrade: "},
{response->general_headers.via, "\nVia: "},
{response->general_headers.warning, "\nWarning: "},
};
add_header_to_string(&msg_to_send, general_headers, 9);
const char* response_headers[][2] = {
{response->response_headers.accept_ranges, "\nAccept-Ranges: "},
{response->response_headers.age, "\nAge: "},
{response->response_headers.etag, "\nETag: "},
{response->response_headers.location, "\nLocation: "},
{response->response_headers.proxy_authenticate, "\nProxy-Authenticate: "},
{response->response_headers.retry_after, "\nRetry-After: "},
{response->response_headers.server, "\nServer: "},
{response->response_headers.vary, "\nVary: "},
{response->response_headers.www_authenticate, "\nWWW-Authenticate: "},
{response->response_headers.content_type, "\nContent-Type: "},
};
add_header_to_string(&msg_to_send, response_headers, 9);
asprintf(&msg_to_send, "%s\nContent-Length: %d\n\n", msg_to_send, response->response_headers.content_length);
asprintf(&msg_to_send, "%s%s\n", msg_to_send, response->message_body);
msg_len = strlen(msg_to_send);
err = send(socket, msg_to_send, msg_len, 0);
if (err < 0)
perror("send");
free(msg_to_send);
}
void* connection_handler(void* arg)
{
int client_socket = *(int*) arg;
http_request request;
http_response response;
//http_request_line request;
char* buffer = NULL;
int err;
recv_message(&buffer, client_socket);
// recv message
err = recv_message(&buffer, client_socket);
if (err == -1) { // if we had a recv error lets throw away the result
free(buffer);
close(client_socket);
return NULL;
}
puts(buffer);
parse_http_request(&buffer, &request);
printf("%s %s %1.1f\n", http_method_to_str(request.method), request.request_uri, request.version);
// puts(buffer);
/**
* Handle HTTP request here :)
*/
// recv message
// parse
//
parse_http_request(&buffer, &request);
printf("%s %s %d.%d\n", http_method_to_str(request.request_line.method),
request.request_line.request_uri, request.request_line.version.major,
request.request_line.version.minor);
// create payload
//
create_http_response(&request, &response);
free_http_request(&request);
// send payload
send_message(client_socket, &response);
free_http_response(&response);
free(buffer);
close(client_socket);