From 1b584f3a33b09ff2e2170d4dfbcea0c4dd3b8e61 Mon Sep 17 00:00:00 2001 From: ari melody Date: Sun, 21 Sep 2025 13:16:13 +0100 Subject: [PATCH] =?UTF-8?q?first=20commit!=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + src/appstate.h | 7 ++ src/http.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++ src/http.h | 117 ++++++++++++++++++++++++ src/log.c | 63 +++++++++++++ src/log.h | 19 ++++ src/main.c | 158 ++++++++++++++++++++++++++++++++ src/node.c | 65 ++++++++++++++ src/node.h | 14 +++ 9 files changed, 683 insertions(+) create mode 100644 .gitignore create mode 100644 src/appstate.h create mode 100644 src/http.c create mode 100644 src/http.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/main.c create mode 100644 src/node.c create mode 100644 src/node.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d4f62e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +http.dSYM/ +http diff --git a/src/appstate.h b/src/appstate.h new file mode 100644 index 0000000..c01fc22 --- /dev/null +++ b/src/appstate.h @@ -0,0 +1,7 @@ +#include "log.h" + +typedef struct AppState { + int sock; + Logger *log; +} AppState; +static AppState app = {0}; diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..120f3e3 --- /dev/null +++ b/src/http.c @@ -0,0 +1,238 @@ +#include "http.h" +#include +#include +#include +#include +#include + +char *http_method_string(HTTPMethod method) { + switch (method) { + case METHOD_GET: + return METHOD_GET_STR; + case METHOD_HEAD: + return METHOD_HEAD_STR; + case METHOD_POST: + return METHOD_POST_STR; + case METHOD_PUT: + return METHOD_PUT_STR; + case METHOD_DELETE: + return METHOD_DELETE_STR; + case METHOD_CONNECT: + return METHOD_CONNECT_STR; + case METHOD_OPTIONS: + return METHOD_OPTIONS_STR; + case METHOD_TRACE: + return METHOD_TRACE_STR; + case METHOD_PATCH: + return METHOD_PATCH_STR; + default: + return "???"; + } +} + +int parse_method(HTTPRequest *request, char *data, size_t data_len) { + char *end = strchr(data, ' '); + if (end == NULL) + return -1; + size_t len = (end - data) % 8; + char method[len + 1]; + strncpy(method, data, len); + method[len] = 0; + + if (strcmp(method, METHOD_GET_STR) == 0) + request->method = METHOD_GET; + else if (strcmp(method, METHOD_HEAD_STR) == 0) + request->method = METHOD_HEAD; + else if (strcmp(method, METHOD_POST_STR) == 0) + request->method = METHOD_POST; + else if (strcmp(method, METHOD_PUT_STR) == 0) + request->method = METHOD_PUT; + else if (strcmp(method, METHOD_DELETE_STR) == 0) + request->method = METHOD_DELETE; + else if (strcmp(method, METHOD_CONNECT_STR) == 0) + request->method = METHOD_CONNECT; + else if (strcmp(method, METHOD_OPTIONS_STR) == 0) + request->method = METHOD_OPTIONS; + else if (strcmp(method, METHOD_TRACE_STR) == 0) + request->method = METHOD_TRACE; + else if (strcmp(method, METHOD_PATCH_STR) == 0) + request->method = METHOD_PATCH; + else + return -1; + + return 0; +} + +int parse_request_uri(HTTPRequest *request, char *data, size_t data_len) { + char *start = strstr(data, " /"); + if (start == NULL) + return -1; + start++; + char *end = strchr(start, ' '); + size_t len = end - start; + request->uri = malloc(sizeof(char) * (len + 1)); + strncpy(request->uri, start, len); + request->uri[len] = 0; + + return 0; +} + +int parse_headers(HTTPRequest *request, char *data, size_t data_len) { + request->headers = NULL; + + char *start = data; + while (start < data + data_len) { + char *end = strstr(start, "\r\n"); + if (end == NULL) + break; + size_t linelen = end - start; + char line[linelen + 1]; + strncpy(line, start, linelen); + line[linelen] = 0; + + char *keyend = strstr(line, ":"); + if (keyend == NULL) + break; + + char *valstart = keyend + 1; + while (*valstart == ' ' && valstart < line + linelen) + valstart++; + if (valstart == NULL || valstart >= line + linelen) + break; + + size_t keylen = keyend - line; + size_t valuelen = linelen - (valstart - line); + + HTTPHeader *header = malloc(sizeof(HTTPHeader)); + header->key = malloc(sizeof(char) * (keylen + 1)); + header->value = malloc(sizeof(char) * (valuelen + 1)); + strncpy(header->key, line, keylen); + header->key[keylen] = 0; + strncpy(header->value, valstart, valuelen); + header->value[valuelen] = 0; + + request->headers = add_node(request->headers, header->key, header); + + start += linelen + 2; // skip \r\n + } + + return 0; +} + +void _free_header_nodes(Node *tree) { + if (tree == NULL) + return; + _free_header_nodes(tree->left); + _free_header_nodes(tree->right); + HTTPHeader *header = tree->data; + free(header->key); + free(header->value); + free(tree); +} +void free_http_request(HTTPRequest *request) { + free(request->body); + _free_header_nodes(request->headers); + free(request); +} + +void _push_http_header_list(Node *tree, HTTPHeader **list, size_t index) { + if (tree == NULL) + return; + _push_http_header_list(tree->right, list, index); + list[index + 1] = tree->data; + _push_http_header_list(tree->left, list, index + 2); +} +size_t list_http_headers(HTTPRequest *request, HTTPHeader **list) { + size_t len = tree_size(request->headers); + list = malloc(sizeof(HTTPHeader*) * len); + _push_http_header_list(request->headers, list, 0); + return len; +} + +char *get_http_header(HTTPRequest *request, char *key) { + Node *node = get_node(request->headers, key); + if (node == NULL) + return NULL; + HTTPHeader *header = node->data; + if (header == NULL) + return NULL; + return header->value; +} + +int recvhttp(int sock, HTTPRequest *request, struct sockaddr *addr, socklen_t *addr_len) { + size_t buflen = RECV_BUFFER_SIZE; + char *recvbuf = malloc(buflen); + memset(recvbuf, 0, buflen); + size_t total_bytes = 0; + int bytes = 0; + + while (1) { + bytes = recvfrom( + sock, + recvbuf + total_bytes, + RECV_BUFFER_SIZE, + 0, + addr, + addr_len); + + if (bytes == -1) { + fprintf(stderr, "recv failed: %s\n", strerror(errno)); + return -1; + } + + total_bytes += bytes; + + // HTTP request end + if (total_bytes >= 4 && strcmp(recvbuf + total_bytes - 4, "\r\n\r\n") == 0) + break; + + if (bytes < RECV_BUFFER_SIZE) + break; + + // add to buffer size if we expect more data + size_t newbuflen = buflen + RECV_BUFFER_SIZE; + char *newbuf = malloc(newbuflen); + memset(newbuf, 0, newbuflen); + memcpy(newbuf, recvbuf, buflen); + free(recvbuf); + recvbuf = newbuf; + buflen = newbuflen; + } + + if (recvbuf[total_bytes] != 0) { + if (total_bytes == buflen) { + size_t newbuflen = buflen + 1; + char *newbuf = malloc(newbuflen); + memcpy(newbuf, recvbuf, buflen); + free(recvbuf); + recvbuf = newbuf; + buflen = newbuflen; + } + recvbuf[total_bytes] = 0; + } + + if (parse_method(request, recvbuf, total_bytes) == -1) { + fprintf(stderr, "failed to parse request method\n"); + return -1; + } + + if (parse_request_uri(request, recvbuf, total_bytes) == -1) { + fprintf(stderr, "failed to parse request uri\n"); + return -1; + } + + char *header_start = strstr(recvbuf, "\r\n"); + if (header_start == NULL || (header_start - recvbuf) + 2 > total_bytes) { + fprintf(stderr, "warn: client with no headers?\n"); + } else { + header_start += 2; + if (parse_headers(request, header_start, total_bytes) == -1) { + fprintf(stderr, "failed to parse request headers\n"); + return -1; + } + } + + request->body = strstr(recvbuf, "\r\n\r\n"); + request->body_len = request->body != NULL ? strlen(request->body) : 0; + return 0; +} diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..3c6672d --- /dev/null +++ b/src/http.h @@ -0,0 +1,117 @@ +#include "node.h" + +#include + +#define SOCK_QUEUE 1024 +#define RECV_BUFFER_SIZE 1024 // starting length; buffer size will be increased as needed + +#define METHOD_GET_STR "GET" +#define METHOD_HEAD_STR "HEAD" +#define METHOD_POST_STR "POST" +#define METHOD_PUT_STR "PUT" +#define METHOD_DELETE_STR "DELETE" +#define METHOD_CONNECT_STR "CONNECT" +#define METHOD_OPTIONS_STR "OPTIONS" +#define METHOD_TRACE_STR "TRACE" +#define METHOD_PATCH_STR "PATCH" + +typedef enum HTTPMethod { + METHOD_GET, + METHOD_HEAD, + METHOD_POST, + METHOD_PUT, + METHOD_DELETE, + METHOD_CONNECT, + METHOD_OPTIONS, + METHOD_TRACE, + METHOD_PATCH, +} HTTPMethod; + +#define HTTP_Continue 100 +#define HTTP_Switching_Protocols 101 +#define HTTP_Processing 102 +#define HTTP_Early_Hints 103 +#define HTTP_OK 200 +#define HTTP_Created 201 +#define HTTP_Accepted 202 +#define HTTP_Non_Authoritative_Information 203 +#define HTTP_No_Content 204 +#define HTTP_Reset_Content 205 +#define HTTP_Partial_Content 206 +#define HTTP_Multi_Status 207 +#define HTTP_Already_Reported 208 +#define HTTP_IM_Used 226 +#define HTTP_Multiple_Choices 300 +#define HTTP_Moved_Permanently 301 +#define HTTP_Found 302 +#define HTTP_See_Other 303 +#define HTTP_Not_Modified 304 +#define HTTP_Temporary_Redirect 307 +#define HTTP_Permanent_Redirect 308 +#define HTTP_Bad_Request 400 +#define HTTP_Unauthorized 401 +#define HTTP_Payment_Required 402 +#define HTTP_Forbidden 403 +#define HTTP_Not_Found 404 +#define HTTP_Method_Not_Allowed 405 +#define HTTP_Not_Acceptable 406 +#define HTTP_Proxy_Authentication_Required 407 +#define HTTP_Request_Timeout 408 +#define HTTP_Conflict 409 +#define HTTP_Gone 410 +#define HTTP_Length_Required 411 +#define HTTP_Precondition_Failed 412 +#define HTTP_Content_Too_Large 413 +#define HTTP_URI_Too_Long 414 +#define HTTP_Unsupported_Media_Type 415 +#define HTTP_Range_Not_Satisfiable 416 +#define HTTP_Expectation_Failed 417 +#define HTTP_TEAPOT 418 +#define HTTP_Misdirected_Request 421 +#define HTTP_Unprocessable_Content 422 +#define HTTP_Locked 423 +#define HTTP_Failed_Dependency 424 +#define HTTP_Too_Early 425 +#define HTTP_Upgrade_Required 426 +#define HTTP_Precondition_Required 428 +#define HTTP_Too_Many_Requests 429 +#define HTTP_Request_Header_Fields_Too_Large 431 +#define HTTP_Unavailable_For_Legal_Reasons 451 +#define HTTP_Internal_Server_Error 500 +#define HTTP_Not_Implemented 501 +#define HTTP_Bad_Gateway 502 +#define HTTP_Service_Unavailable 503 +#define HTTP_Gateway_Timeout 504 +#define HTTP_HTTP_Version_Not_Supported 505 +#define HTTP_Variant_Also_Negotiates 506 +#define HTTP_Insufficient_Storage 507 +#define HTTP_Loop_Detected 508 +#define HTTP_Not_Extended 510 +#define HTTP_Network_Authentication_Required 511 + +typedef struct HTTPRequest { + HTTPMethod method; + char *uri; + Node *headers; + struct sockaddr_in addr; + char *body; + size_t body_len; +} HTTPRequest; + +typedef struct HTTPHeader { + char *key; + char *value; +} HTTPHeader; + +static const char RESPONSE_OK[] = +"HTTP/1.1 200 OK\r\n" +"Server: ari's awesome server\r\n" +"Content-Length: %ld\r\n" +"\r\n" +"%s"; + +int recvhttp(int sock, HTTPRequest *request, struct sockaddr *addr, socklen_t *addr_len); +void free_http_request(HTTPRequest *request); +char *http_method_string(HTTPMethod method); +size_t list_http_headers(HTTPRequest *request, HTTPHeader **list); +char *get_http_header(HTTPRequest *request, char *key); diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..31460a6 --- /dev/null +++ b/src/log.c @@ -0,0 +1,63 @@ +#include "log.h" +#include +#include +#include + +#define TIMESTAMP_SIZE 32 + +void newlog(Logger *logger, const char *name, FILE *file) { + logger->name = name; + logger->file = file; + logger->lock = 0; +} + +void _gettimestamp(char *result, size_t result_len) { + time_t timer = time(NULL); + struct tm *tm_info = localtime(&timer); + strftime(result, result_len, "%Y-%m-%d %H:%M:%S", tm_info); +} + +void _logf(Logger *logger, const char *type, const char *format, va_list args) { + while (logger->lock != 0); + logger->lock = 1; + + char timestamp[TIMESTAMP_SIZE] = {0}; + _gettimestamp(timestamp, TIMESTAMP_SIZE); + + fprintf(logger->file, "[%s] [%s] ", timestamp, type); + vfprintf(logger->file, format, args); + fprintf(logger->file, "\n"); + + logger->lock = 0; +} + +void loginfo(Logger *logger, const char *format, ...) { + va_list args; + va_start(args, format); + _logf(logger, LOG_INFO, format, args); + va_end(args); +} + +void logwarn(Logger *logger, const char *format, ...) { + va_list args; + va_start(args, format); + _logf(logger, LOG_WARN, format, args); + va_end(args); +} + +void logerror(Logger *logger, const char *format, ...) { + va_list args; + va_start(args, format); + _logf(logger, LOG_ERROR, format, args); + va_end(args); +} + +void logdebug(Logger *logger, const char *format, ...) { +#ifdef DEBUG + va_list args; + va_start(args, format); + _logf(logger, LOG_DEBUG, format, args); + va_end(args); +#else +#endif /* ifdef DEBUG */ +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..e42158e --- /dev/null +++ b/src/log.h @@ -0,0 +1,19 @@ +#include + +#define LOG_INFO "INFO" +#define LOG_WARN "WARN" +#define LOG_ERROR "ERROR" +#define LOG_DEBUG "DEBUG" + +typedef struct Logger { + const char *name; + FILE *file; + char lock; +} Logger; + +void newlog(Logger *logger, const char *name, FILE *file); + +void loginfo(Logger *logger, const char *format, ...); +void logwarn(Logger *logger, const char *format, ...); +void logerror(Logger *logger, const char *format, ...); +void logdebug(Logger *logger, const char *format, ...); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e719344 --- /dev/null +++ b/src/main.c @@ -0,0 +1,158 @@ +#include "appstate.h" +#include "http.h" + +#include <_stdio.h> +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMESTAMP_SIZE 32 + +static void catch_signal(int signo) { + if (close(app.sock)) { + fprintf(stderr, "failed to close socket: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +int serve(AppState *app) { + struct sockaddr addr; + socklen_t addr_len = sizeof(struct sockaddr); + int accept_sock = accept( + app->sock, + &addr, + &addr_len); + + if (accept_sock == -1) { + fprintf(stderr, "failed to accept socket connection: %s\n", strerror(errno)); + return -1; + } + + // TODO: hand off request to thread pool + + HTTPRequest request; + if (recvhttp(accept_sock, &request, &addr, &addr_len) == -1) { + fprintf(stderr, "failed to receive HTTP request\n"); + return -1; + } + char *straddr = inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr); + + loginfo(app->log, "%s: %s %s [%s] (%ld bytes)", + straddr, + http_method_string(request.method), + request.uri, + get_http_header(&request, "User-Agent"), + request.body_len); + // printf("headers:\n"); + // HTTPHeader **headers; + // size_t headers_len = list_http_headers(&request, headers); + // for (size_t i = 0; i < headers_len; i++) { + // HTTPHeader *header = headers[i]; + // printf("%s: %s\n", header->key, header->value); + // } + + char *response_body; + if (asprintf(&response_body, "Received %ld bytes from you!\n", request.body_len) == -1) { + fprintf(stderr, "failed to format response body: %s\n", strerror(errno)); + return -1; + } + + char *response; + if (asprintf(&response, RESPONSE_OK, strlen(response_body), response_body) == -1) { + fprintf(stderr, "failed to format response: %s\n", strerror(errno)); + return -1; + } + free(response_body); + + if (send(accept_sock, response, strlen(response), 0) == -1) { + fprintf(stderr, "failed to send data to socket: %s\n", strerror(errno)); + return -1; + } + free(response); + + if (close(accept_sock) == -1) { + fprintf(stderr, "failed to close client socket: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +int create_server(struct in_addr in_addr, uint16_t port) { + struct sockaddr_in service; + memset(&service, 0, sizeof(struct sockaddr_in)); + service.sin_family = AF_INET; + service.sin_addr = in_addr; + service.sin_port = htons(port); + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + fprintf(stderr, "failed to create socket: %s\n", strerror(errno)); + return -1; + } + + if (bind(sock, (struct sockaddr *)&service, sizeof(struct sockaddr_in)) == -1) { + close(sock); + fprintf(stderr, "failed to bind to port: %s\n", strerror(errno)); + return -1; + } + + if (listen(sock, SOCK_QUEUE) == -1) { + close(sock); + fprintf(stderr, "failed to listen: %s\n", strerror(errno)); + return -1; + } + + return sock; +} + +int main(int argc, char *argv[]) { + if (argc >= 2 && strcmp(argv[1], "help") == 0) { + printf("usage: %s [address]\n", argv[0]); + exit(0); + } + if (argc < 2) { + fprintf(stderr, "usage: %s [address]\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (signal(SIGINT, catch_signal) == SIG_ERR) { + fprintf(stderr, "failed to set signal handler: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + int err = 0; + in_port_t port = atoi(argv[1]); + char *straddr; + if (argc >= 3) + straddr = argv[2]; + else + straddr = "0.0.0.0"; + + struct in_addr in_address; + inet_aton(straddr, &in_address); + + int sock = create_server(in_address, port); + if (sock == -1) + exit(EXIT_FAILURE); + + app.sock = sock; + app.log = malloc(sizeof(Logger)); + newlog(app.log, "http", stdout); + + printf("now listening on %s:%d\n", straddr, port); + while (serve(&app) != -1); + + if (close(sock) == -1) { + fprintf(stderr, "failed to close socket: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + return EXIT_SUCCESS; +} diff --git a/src/node.c b/src/node.c new file mode 100644 index 0000000..ad087d9 --- /dev/null +++ b/src/node.c @@ -0,0 +1,65 @@ +#include "node.h" +#include +#include + +Node *add_node(Node *tree, char *key, void *data) { + if (tree == NULL) { + tree = malloc(sizeof(Node)); + tree->key = malloc(sizeof(char) * strlen(key)); + strcpy(tree->key, key); + tree->data = data; + tree->left = tree->right = NULL; + return tree; + } + + if (strcmp(tree->key, key) > 0) + tree->right = add_node(tree->right, key, data); + else + tree->left = add_node(tree->left, key, data); + return tree; +} + +Node *get_node(Node *tree, char *key) { + if (tree == NULL) + return NULL; + + int cmp = strcmp(tree->key, key); + if (cmp == 0) + return tree; + else if (cmp > 0) + return get_node(tree->right, key); + else + return get_node(tree->left, key); +} + +size_t tree_size(Node *tree) { + if (tree == NULL) + return 0; + return tree_size(tree->left) + tree_size(tree->right) + 1; +} + +void _push_node_list(Node *tree, Node **list, size_t index) { + if (tree == NULL) + return; + _push_node_list(tree->right, list, index); + list[index + 1] = tree; + _push_node_list(tree->left, list, index + 2); +} +size_t list_nodes(Node *tree, Node **list) { + if (tree == NULL) + return 0; + size_t len = tree_size(tree); + *list = malloc(sizeof(Node*) * len); + + _push_node_list(tree, list, 0); + + return len; +} + +void free_node(Node *node) { + free_node(node->left); + free(node->key); + free(node->data); + free_node(node->right); + free(node); +} diff --git a/src/node.h b/src/node.h new file mode 100644 index 0000000..aae98a3 --- /dev/null +++ b/src/node.h @@ -0,0 +1,14 @@ +#include + +typedef struct Node { + char *key; + void *data; + struct Node *left; + struct Node *right; +} Node; + +Node *add_node(Node *tree, char *key, void *data); +Node *get_node(Node *tree, char *key); +void free_node(Node *node); +size_t list_nodes(Node *tree, Node **list); +size_t tree_size(Node *tree);