From 59b76fd601e5840043b9868ae2ca2f06b4b4e7a4 Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 11 Aug 2026 03:20:47 -0400 Subject: feat: complete logging overhaul --- src/beaker_globals.c | 8 +- src/beaker_globals.h | 17 +- src/http.c | 118 +++++++----- src/l10n.c | 93 +++++---- src/log.c | 117 ++++++++++++ src/routing.c | 387 ++++++++++++++++++++------------------ src/server.c | 521 ++++++++++++++++++++++++++++----------------------- src/template.c | 413 ++++++++++++++++++++-------------------- 8 files changed, 950 insertions(+), 724 deletions(-) create mode 100644 src/log.c (limited to 'src') diff --git a/src/beaker_globals.c b/src/beaker_globals.c index 3488338..2b1f056 100644 --- a/src/beaker_globals.c +++ b/src/beaker_globals.c @@ -1,4 +1,4 @@ -#include "beaker_globals.h" +#include "beaker_globals.h" RouteHandler handlers[MAX_HANDLERS]; @@ -14,8 +14,12 @@ __thread char current_request_buffer[BUFFER_SIZE]; __thread RequestInfo current_request_info = {0}; +__thread int current_response_status = 0; + +__thread size_t current_response_size = 0; + Locale *locales = NULL; int locale_count = 0; -int locale_capacity = 0; \ No newline at end of file +int locale_capacity = 0; diff --git a/src/beaker_globals.h b/src/beaker_globals.h index 34b29d9..a4a98f0 100644 --- a/src/beaker_globals.h +++ b/src/beaker_globals.h @@ -3,6 +3,7 @@ #include "../beaker.h" #include +#include extern RouteHandler handlers[MAX_HANDLERS]; @@ -18,10 +19,24 @@ extern __thread char current_request_buffer[BUFFER_SIZE]; extern __thread RequestInfo current_request_info; +extern __thread int current_response_status; + +extern __thread size_t current_response_size; + +void beaker_log(const char *level, const char *format, ...); + +void beaker_log_errno_format(const char *level, const char *format, ...); + +void beaker_log_errno(const char *message); + +void beaker_log_request(const char *remote_addr, const char *method, + const char *path, int status, size_t response_size, + double duration_ms); + extern Locale *locales; extern int locale_count; extern int locale_capacity; -#endif \ No newline at end of file +#endif diff --git a/src/http.c b/src/http.c index 778bf51..4c93a27 100644 --- a/src/http.c +++ b/src/http.c @@ -1,10 +1,10 @@ -#include "../beaker.h" -#include "beaker_globals.h" -#include -#include -#include -#include -#include +#include "../beaker.h" +#include "beaker_globals.h" +#include +#include +#include +#include +#include static void build_cookie_headers(char *cookie_headers_buffer, size_t buffer_size) { @@ -12,37 +12,47 @@ static void build_cookie_headers(char *cookie_headers_buffer, cookie_headers_buffer[0] = '\0'; for (int i = 0; i < cookies_to_set_count; i++) { - char single_cookie_header[MAX_VALUE_LEN * 2]; + char single_cookie_header[MAX_VALUE_LEN * 2]; snprintf(single_cookie_header, sizeof(single_cookie_header), "Set-Cookie: %s=%s", cookies_to_set[i].name, cookies_to_set[i].value); if (strlen(cookies_to_set[i].expires) > 0) { - strncat(single_cookie_header, "; Expires=", sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); - strncat(single_cookie_header, cookies_to_set[i].expires, sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, "; Expires=", + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, cookies_to_set[i].expires, + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); } if (strlen(cookies_to_set[i].path) > 0) { - strncat(single_cookie_header, "; Path=", sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); - strncat(single_cookie_header, cookies_to_set[i].path, sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, "; Path=", + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, cookies_to_set[i].path, + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); } if (cookies_to_set[i].http_only) { - strncat(single_cookie_header, "; HttpOnly", sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, "; HttpOnly", + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); } if (cookies_to_set[i].secure) { - strncat(single_cookie_header, "; Secure", sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, "; Secure", + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); } - strncat(single_cookie_header, "\r\n", sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); + strncat(single_cookie_header, "\r\n", + sizeof(single_cookie_header) - strlen(single_cookie_header) - 1); - if (strlen(cookie_headers_buffer) + strlen(single_cookie_header) < buffer_size) { + if (strlen(cookie_headers_buffer) + strlen(single_cookie_header) < + buffer_size) { strncat(cookie_headers_buffer, single_cookie_header, buffer_size - strlen(cookie_headers_buffer) - 1); } else { - fprintf(stderr, "[WARNING] build_cookie_headers: Cookie headers buffer full, truncating\n"); + beaker_log( + "WARN", + "build_cookie_headers: Cookie headers buffer full, truncating\n"); break; } } @@ -50,11 +60,14 @@ static void build_cookie_headers(char *cookie_headers_buffer, void send_status(const char *status_line) { if (current_client_socket == -1) { - fprintf(stderr, "[ERROR] send_status: No client socket set. Cannot send response.\n"); + beaker_log("ERROR", + "send_status: No client socket set. Cannot send response.\n"); return; } char http_response[BUFFER_SIZE]; + current_response_status = atoi(status_line); + current_response_size = 0; snprintf(http_response, sizeof(http_response), "HTTP/1.1 %s\r\n" "Content-Length: 0\r\n" @@ -62,22 +75,26 @@ void send_status(const char *status_line) { "\r\n", status_line); - if (send(current_client_socket, http_response, strlen(http_response), 0) < 0) { - perror("Error sending HTTP status"); - fprintf(stderr, "[ERROR] send_status: Failed to send HTTP status.\n"); + if (send(current_client_socket, http_response, strlen(http_response), 0) < + 0) { + beaker_log_errno_format("ERROR", + "send_status: Failed to send HTTP status.\n"); } } void send_response(const char *html) { if (current_client_socket == -1) { - fprintf(stderr, "[ERROR] send_response: No client socket set. Cannot send response.\n"); + beaker_log("ERROR", + "send_response: No client socket set. Cannot send response.\n"); return; } - char http_response_header[BUFFER_SIZE * 2]; - int content_length = strlen(html); - char cookie_headers[BUFFER_SIZE]; + char http_response_header[BUFFER_SIZE * 2]; + int content_length = strlen(html); + current_response_status = 200; + current_response_size = content_length; + char cookie_headers[BUFFER_SIZE]; build_cookie_headers(cookie_headers, sizeof(cookie_headers)); @@ -85,21 +102,21 @@ void send_response(const char *html) { "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Content-Length: %d\r\n" - "%s" - "Connection: close\r\n" - "\r\n", + "%s" + "Connection: close\r\n" + "\r\n", content_length, cookie_headers); if (send(current_client_socket, http_response_header, strlen(http_response_header), 0) < 0) { - perror("Error sending HTTP header"); - fprintf(stderr, "[ERROR] send_response: Failed to send HTTP header.\n"); + beaker_log_errno_format("ERROR", + "send_response: Failed to send HTTP header.\n"); return; } if (send(current_client_socket, html, content_length, 0) < 0) { - perror("Error sending HTML body"); - fprintf(stderr, "[ERROR] send_response: Failed to send HTML body.\n"); + beaker_log_errno_format("ERROR", + "send_response: Failed to send HTML body.\n"); return; } @@ -109,11 +126,14 @@ void send_response(const char *html) { void send_redirect(const char *location) { if (current_client_socket == -1) { - fprintf(stderr, "[ERROR] send_redirect: No client socket set. Cannot send redirect.\n"); + beaker_log("ERROR", + "send_redirect: No client socket set. Cannot send redirect.\n"); return; } char http_response_header[BUFFER_SIZE * 2]; + current_response_status = 302; + current_response_size = 0; char cookie_headers[BUFFER_SIZE]; build_cookie_headers(cookie_headers, sizeof(cookie_headers)); @@ -128,8 +148,8 @@ void send_redirect(const char *location) { if (send(current_client_socket, http_response_header, strlen(http_response_header), 0) < 0) { - perror("Error sending redirect header"); - fprintf(stderr, "[ERROR] send_redirect: Failed to send redirect header.\n"); + beaker_log_errno_format("ERROR", + "send_redirect: Failed to send redirect header.\n"); return; } @@ -140,9 +160,10 @@ void set_cookie(const char *name, const char *value, const char *expires, const char *path, bool http_only, bool secure) { if (cookies_to_set_count >= MAX_COOKIES) { - fprintf(stderr, - "[WARNING] set_cookie: Maximum number of cookies to set reached. Cannot set cookie '%s'.\n", - name); + beaker_log("WARN", + "set_cookie: Maximum number of cookies to set reached. Cannot " + "set cookie '%s'.\n", + name); return; } @@ -178,7 +199,7 @@ char *get_cookie(const char *cookie_name) { char *cookie_header_start = strstr(current_request_buffer, "\r\nCookie: "); if (cookie_header_start == NULL) { - return NULL; + return NULL; } cookie_header_start += strlen("\r\nCookie: "); @@ -186,22 +207,23 @@ char *get_cookie(const char *cookie_name) { char *cookie_header_end = strstr(cookie_header_start, "\r\n"); if (cookie_header_end == NULL) { - cookie_header_end = (char *)(current_request_buffer + strlen(current_request_buffer)); + cookie_header_end = + (char *)(current_request_buffer + strlen(current_request_buffer)); } size_t cookie_str_len = cookie_header_end - cookie_header_start; char *cookie_str = (char *)malloc(cookie_str_len + 1); if (cookie_str == NULL) { - perror("Failed to allocate memory for raw cookie string"); - fprintf(stderr, "[ERROR] get_cookie: Allocation failed for cookie_str.\n"); + beaker_log_errno_format("ERROR", + "get_cookie: Allocation failed for cookie_str.\n"); return NULL; } strncpy(cookie_str, cookie_header_start, cookie_str_len); - cookie_str[cookie_str_len] = '\0'; + cookie_str[cookie_str_len] = '\0'; - char *token; - char *saveptr_cookie; + char *token; + char *saveptr_cookie; token = strtok_r(cookie_str, ";", &saveptr_cookie); while (token != NULL) { @@ -212,15 +234,15 @@ char *get_cookie(const char *cookie_name) { char *equals_sign = strchr(token, '='); if (equals_sign != NULL) { - size_t name_len = equals_sign - token; + size_t name_len = equals_sign - token; if (name_len == strlen(cookie_name) && strncmp(token, cookie_name, name_len) == 0) { char *cookie_value = strdup(equals_sign + 1); if (cookie_value == NULL) { - perror("Failed to duplicate cookie value"); - fprintf(stderr, "[ERROR] get_cookie: Allocation failed for cookie_value.\n"); + beaker_log_errno_format( + "ERROR", "get_cookie: Allocation failed for cookie_value.\n"); } free(cookie_str); return cookie_value; diff --git a/src/l10n.c b/src/l10n.c index 4d8ac30..43acff2 100644 --- a/src/l10n.c +++ b/src/l10n.c @@ -13,11 +13,15 @@ typedef enum { } IniSection; static char *trim(char *str) { - if (str == NULL) return NULL; - while (*str == ' ' || *str == '\t') str++; - if (*str == '\0') return str; + if (str == NULL) + return NULL; + while (*str == ' ' || *str == '\t') + str++; + if (*str == '\0') + return str; char *end = str + strlen(str) - 1; - while (end > str && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { + while (end > str && + (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { *end = '\0'; end--; } @@ -35,10 +39,13 @@ static void unquote_value(char *value) { static int safe_grow_capacity(int current, int hard_max) { int new_cap; if (current == 0) { - new_cap = (hard_max < INITIAL_LOCALE_KEYS_CAPACITY) ? hard_max : INITIAL_LOCALE_KEYS_CAPACITY; + new_cap = (hard_max < INITIAL_LOCALE_KEYS_CAPACITY) + ? hard_max + : INITIAL_LOCALE_KEYS_CAPACITY; } else { new_cap = current * 2; - if (new_cap > hard_max) new_cap = hard_max; + if (new_cap > hard_max) + new_cap = hard_max; } return new_cap; } @@ -46,8 +53,8 @@ static int safe_grow_capacity(int current, int hard_max) { static int parse_ini_file(const char *path, Locale *locale) { FILE *fp = fopen(path, "r"); if (fp == NULL) { - fprintf(stderr, "[ERROR] parse_ini_file: Could not open '%s': %s\n", - path, strerror(errno)); + beaker_log("ERROR", "parse_ini_file: Could not open '%s': %s\n", path, + strerror(errno)); return -1; } @@ -67,7 +74,8 @@ static int parse_ini_file(const char *path, Locale *locale) { if (*trimmed == '[') { char *close = strchr(trimmed, ']'); - if (close == NULL) continue; + if (close == NULL) + continue; *close = '\0'; char *section_name = trimmed + 1; section_name = trim(section_name); @@ -83,7 +91,8 @@ static int parse_ini_file(const char *path, Locale *locale) { } char *eq = strchr(trimmed, '='); - if (eq == NULL) continue; + if (eq == NULL) + continue; *eq = '\0'; char *key = trim(trimmed); @@ -98,24 +107,28 @@ static int parse_ini_file(const char *path, Locale *locale) { strncpy(locale->meta.name, value, MAX_VALUE_LEN - 1); locale->meta.name[MAX_VALUE_LEN - 1] = '\0'; } else if (strcmp(key, "Direction") == 0) { - strncpy(locale->meta.direction, value, sizeof(locale->meta.direction) - 1); + strncpy(locale->meta.direction, value, + sizeof(locale->meta.direction) - 1); locale->meta.direction[sizeof(locale->meta.direction) - 1] = '\0'; } } else if (current_section == SECTION_KEYS) { if (locale->key_count >= locale->key_capacity) { - int new_cap = safe_grow_capacity(locale->key_capacity, MAX_LOCALE_KEYS_HARD); + int new_cap = + safe_grow_capacity(locale->key_capacity, MAX_LOCALE_KEYS_HARD); if (new_cap <= locale->key_count) { - fprintf(stderr, - "[WARNING] parse_ini_file: Hard key limit (%d) reached in '%s', " - "skipping key '%s'.\n", - MAX_LOCALE_KEYS_HARD, path, key); + beaker_log("WARN", + "parse_ini_file: Hard key limit (%d) reached in '%s', " + "skipping key '%s'.\n", + MAX_LOCALE_KEYS_HARD, path, key); continue; } - LocaleKV *new_keys = realloc(locale->keys, (size_t)new_cap * sizeof(LocaleKV)); + LocaleKV *new_keys = + realloc(locale->keys, (size_t)new_cap * sizeof(LocaleKV)); if (new_keys == NULL) { - fprintf(stderr, - "[ERROR] parse_ini_file: Memory allocation failed for keys in '%s'.\n", - path); + beaker_log( + "ERROR", + "parse_ini_file: Memory allocation failed for keys in '%s'.\n", + path); continue; } locale->keys = new_keys; @@ -133,9 +146,8 @@ static int parse_ini_file(const char *path, Locale *locale) { fclose(fp); if (locale->meta.id[0] == '\0') { - fprintf(stderr, - "[WARNING] parse_ini_file: No Id in [Meta] section of '%s'.\n", - path); + beaker_log("WARN", "parse_ini_file: No Id in [Meta] section of '%s'.\n", + path); return -1; } @@ -147,9 +159,9 @@ int beaker_load_locales(void) { DIR *dir = opendir(LOCALES_DIR); if (dir == NULL) { - fprintf(stderr, - "[ERROR] beaker_load_locales: Could not open directory '%s': %s\n", - LOCALES_DIR, strerror(errno)); + beaker_log("ERROR", + "beaker_load_locales: Could not open directory '%s': %s\n", + LOCALES_DIR, strerror(errno)); return 0; } @@ -165,16 +177,17 @@ int beaker_load_locales(void) { if (locale_count >= locale_capacity) { int new_cap = safe_grow_capacity(locale_capacity, MAX_LOCALES_HARD); if (new_cap <= locale_count) { - fprintf(stderr, - "[WARNING] beaker_load_locales: Hard locale limit (%d) reached, " - "skipping '%s'.\n", - MAX_LOCALES_HARD, name); + beaker_log("WARN", + "beaker_load_locales: Hard locale limit (%d) reached, " + "skipping '%s'.\n", + MAX_LOCALES_HARD, name); break; } Locale *new_locales = realloc(locales, (size_t)new_cap * sizeof(Locale)); if (new_locales == NULL) { - fprintf(stderr, - "[ERROR] beaker_load_locales: Memory allocation failed for locales.\n"); + beaker_log( + "ERROR", + "beaker_load_locales: Memory allocation failed for locales.\n"); break; } locales = new_locales; @@ -186,8 +199,8 @@ int beaker_load_locales(void) { Locale *locale = &locales[locale_count]; if (parse_ini_file(path, locale) == 0) { - fprintf(stderr, "[INFO] beaker_load_locales: Loaded locale '%s' from '%s'\n", - locale->meta.id, name); + beaker_log("DEBUG", "beaker_load_locales: Loaded locale '%s' from '%s'", + locale->meta.id, name); locale_count++; } else { free(locale->keys); @@ -203,8 +216,8 @@ int beaker_load_locales(void) { void beaker_set_locale(TemplateContext *ctx, const char *locale_id) { if (ctx == NULL || locale_id == NULL) { - fprintf(stderr, - "[ERROR] beaker_set_locale: Invalid NULL input (ctx or locale_id).\n"); + beaker_log("ERROR", + "beaker_set_locale: Invalid NULL input (ctx or locale_id).\n"); return; } @@ -216,10 +229,10 @@ void beaker_set_locale(TemplateContext *ctx, const char *locale_id) { context_set(ctx, "__locale_name", meta->name); context_set(ctx, "__locale_direction", meta->direction); } else { - fprintf(stderr, - "[WARNING] beaker_set_locale: Locale '%s' not found. Context vars " - "not set.\n", - locale_id); + beaker_log("WARN", + "beaker_set_locale: Locale '%s' not found. Context vars " + "not set.\n", + locale_id); } } diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..775bd2c --- /dev/null +++ b/src/log.c @@ -0,0 +1,117 @@ +#include "beaker_globals.h" +#include +#include +#include +#include +#include +#include +#include + +enum { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR, + LOG_LEVEL_NONE +}; + +static int configured_log_level(void) { + const char *level = getenv("BEAKER_LOG_LEVEL"); + + if (level == NULL || strcasecmp(level, "INFO") == 0) + return LOG_LEVEL_INFO; + if (strcasecmp(level, "DEBUG") == 0) + return LOG_LEVEL_DEBUG; + if (strcasecmp(level, "WARN") == 0 || strcasecmp(level, "WARNING") == 0) + return LOG_LEVEL_WARN; + if (strcasecmp(level, "ERROR") == 0) + return LOG_LEVEL_ERROR; + if (strcasecmp(level, "NONE") == 0 || strcasecmp(level, "OFF") == 0) + return LOG_LEVEL_NONE; + return LOG_LEVEL_INFO; +} + +static int message_log_level(const char *level) { + if (strcasecmp(level, "DEBUG") == 0) + return LOG_LEVEL_DEBUG; + if (strcasecmp(level, "INFO") == 0 || strcasecmp(level, "ACCESS") == 0) + return LOG_LEVEL_INFO; + if (strcasecmp(level, "WARN") == 0 || strcasecmp(level, "WARNING") == 0 || + strcasecmp(level, "SECURITY") == 0) + return LOG_LEVEL_WARN; + return LOG_LEVEL_ERROR; +} + +static void log_timestamp(char *buffer, size_t size) { + struct timespec now; + struct tm local; + + clock_gettime(CLOCK_REALTIME, &now); + localtime_r(&now.tv_sec, &local); + strftime(buffer, size, "%Y-%m-%dT%H:%M:%S%z", &local); +} + +static void log_safe_value(char *output, const char *input, size_t size) { + size_t i; + + for (i = 0; i + 1 < size && input[i] != '\0'; i++) { + unsigned char c = input[i]; + output[i] = c <= ' ' || c == 127 || c == '"' || c == '\\' ? '_' : c; + } + output[i] = '\0'; +} + +void beaker_log(const char *level, const char *format, ...) { + char message[BUFFER_SIZE]; + char timestamp[32]; + va_list args; + + if (message_log_level(level) < configured_log_level()) + return; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + size_t length = strlen(message); + while (length > 0 && + (message[length - 1] == '\n' || message[length - 1] == '\r')) + message[--length] = '\0'; + log_timestamp(timestamp, sizeof(timestamp)); + flockfile(stderr); + fprintf(stderr, "%s %-6s %s\n", timestamp, level, message); + funlockfile(stderr); +} + +void beaker_log_errno_format(const char *level, const char *format, ...) { + char message[BUFFER_SIZE]; + int error = errno; + va_list args; + + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + size_t length = strlen(message); + while (length > 0 && + (message[length - 1] == '\n' || message[length - 1] == '\r')) + message[--length] = '\0'; + if (length > 0 && message[length - 1] == '.') + message[length - 1] = '\0'; + beaker_log(level, "%s: %s", message, strerror(error)); +} + +void beaker_log_errno(const char *message) { + int error = errno; + beaker_log("ERROR", "%s: %s", message, strerror(error)); +} + +void beaker_log_request(const char *remote_addr, const char *method, + const char *path, int status, size_t response_size, + double duration_ms) { + char safe_method[16]; + char safe_path[MAX_PATH_LEN]; + + log_safe_value(safe_method, method, sizeof(safe_method)); + log_safe_value(safe_path, path, sizeof(safe_path)); + beaker_log("ACCESS", "%s \"%s %s\" %03d %zu %.3fms", remote_addr, safe_method, + safe_path, status, response_size, duration_ms); +} diff --git a/src/routing.c b/src/routing.c index 00886c8..9894768 100644 --- a/src/routing.c +++ b/src/routing.c @@ -1,12 +1,12 @@ -#include "../beaker.h" -#include "beaker_globals.h" -#include -#include -#include -#include -#include -#include +#include "../beaker.h" +#include "beaker_globals.h" #include +#include +#include +#include +#include +#include +#include void set_handler(const char *path, RequestHandler handler) { @@ -20,9 +20,10 @@ void set_handler(const char *path, RequestHandler handler) { handler_count++; } else { - fprintf(stderr, - "[WARNING] set_handler: Maximum number of handlers reached. Cannot register handler for '%s'.\n", - path); + beaker_log("WARN", + "set_handler: Maximum number of handlers reached. Cannot " + "register handler for '%s'.\n", + path); } } @@ -30,9 +31,9 @@ const char *get_mime_type(const char *file_path) { const char *ext = strrchr(file_path, '.'); if (!ext) { - return "application/octet-stream"; + return "application/octet-stream"; } - ext++; + ext++; if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0) return "text/html"; @@ -60,211 +61,215 @@ const char *get_mime_type(const char *file_path) { return "application/octet-stream"; } -static int canonicalize_path(char *canonical, const char *path, size_t max_len) { - char components[MAX_URL_PARAMS][MAX_KEY_LEN]; - int component_count = 0; +static int canonicalize_path(char *canonical, const char *path, + size_t max_len) { + char components[MAX_URL_PARAMS][MAX_KEY_LEN]; + int component_count = 0; - char *path_copy = strdup(path); - if (!path_copy) { - return -1; - } - - char *token = strtok(path_copy, "/"); - while (token) { - if (strcmp(token, ".") == 0) { + char *path_copy = strdup(path); + if (!path_copy) { + return -1; + } - } else if (strcmp(token, "..") == 0) { + char *token = strtok(path_copy, "/"); + while (token) { + if (strcmp(token, ".") == 0) { - if (component_count > 0) { - component_count--; - } else { + } else if (strcmp(token, "..") == 0) { - fprintf(stderr, "[SECURITY] Path traversal attempt: %s\n", path); - free(path_copy); - return -1; - } - } else if (strlen(token) > 0) { + if (component_count > 0) { + component_count--; + } else { - if (component_count < MAX_URL_PARAMS) { - strncpy(components[component_count], token, MAX_KEY_LEN - 1); - components[component_count][MAX_KEY_LEN - 1] = '\0'; - component_count++; - } - } - token = strtok(NULL, "/"); + beaker_log("SECURITY", "Path traversal attempt: %s\n", path); + free(path_copy); + return -1; + } + } else if (strlen(token) > 0) { + + if (component_count < MAX_URL_PARAMS) { + strncpy(components[component_count], token, MAX_KEY_LEN - 1); + components[component_count][MAX_KEY_LEN - 1] = '\0'; + component_count++; + } } + token = strtok(NULL, "/"); + } - free(path_copy); + free(path_copy); - canonical[0] = '\0'; - for (int i = 0; i < component_count; i++) { - if (strlen(canonical) + strlen(components[i]) + 2 > max_len) { - fprintf(stderr, "[ERROR] Canonical path too long\n"); - return -1; - } - strcat(canonical, "/"); - strcat(canonical, components[i]); + canonical[0] = '\0'; + for (int i = 0; i < component_count; i++) { + if (strlen(canonical) + strlen(components[i]) + 2 > max_len) { + beaker_log("ERROR", "Canonical path too long\n"); + return -1; } + strcat(canonical, "/"); + strcat(canonical, components[i]); + } - if (canonical[0] == '\0') { - strcpy(canonical, "/"); - } + if (canonical[0] == '\0') { + strcpy(canonical, "/"); + } - return 0; + return 0; } static bool is_safe_path_component(const char *component) { - if (strstr(component, "..") || - strstr(component, "//")) { - return false; - } + if (strstr(component, "..") || strstr(component, "//")) { + return false; + } - for (size_t i = 0; component[i]; i++) { - if (component[i] < 32 && component[i] != '\t') { - return false; - } + for (size_t i = 0; component[i]; i++) { + if (component[i] < 32 && component[i] != '\t') { + return false; } + } - return true; + return true; } static int url_decode(char *dst, const char *src, size_t dst_size) { - size_t i = 0, j = 0; - - while (src[i] && j < dst_size - 1) { - if (src[i] == '%') { - - if (src[i+1] && src[i+2]) { - char hex[3] = {src[i+1], src[i+2], '\0'}; - char *endptr; - long value = strtol(hex, &endptr, 16); - - if (*endptr != '\0') { - fprintf(stderr, "[SECURITY] Invalid URL encoding: %%%s\n", hex); - return -1; - } - - if (value == 0) { - fprintf(stderr, "[SECURITY] Null byte in URL encoding\n"); - return -1; - } - - dst[j++] = (char)value; - i += 3; - } else { - fprintf(stderr, "[SECURITY] Incomplete URL encoding at end\n"); - return -1; - } - } else if (src[i] == '+') { - - dst[j++] = ' '; - i++; - } else { - dst[j++] = src[i++]; + size_t i = 0, j = 0; + + while (src[i] && j < dst_size - 1) { + if (src[i] == '%') { + + if (src[i + 1] && src[i + 2]) { + char hex[3] = {src[i + 1], src[i + 2], '\0'}; + char *endptr; + long value = strtol(hex, &endptr, 16); + + if (*endptr != '\0') { + beaker_log("SECURITY", "Invalid URL encoding: %%%s\n", hex); + return -1; + } + + if (value == 0) { + beaker_log("SECURITY", "Null byte in URL encoding\n"); + return -1; } + + dst[j++] = (char)value; + i += 3; + } else { + beaker_log("SECURITY", "Incomplete URL encoding at end\n"); + return -1; + } + } else if (src[i] == '+') { + + dst[j++] = ' '; + i++; + } else { + dst[j++] = src[i++]; } + } - dst[j] = '\0'; - return 0; + dst[j] = '\0'; + return 0; } char *parse_request_url(const char *request_line, UrlParams *params) { - char method[16]; - char raw_url_full[MAX_PATH_LEN]; - char http_version[16]; - - if (sscanf(request_line, "%15s %255s %15s", method, raw_url_full, - http_version) != 3) { - fprintf(stderr, "[ERROR] parse_request_url: Malformed request line\n"); - return NULL; - } + char method[16]; + char raw_url_full[MAX_PATH_LEN]; + char http_version[16]; + + if (sscanf(request_line, "%15s %255s %15s", method, raw_url_full, + http_version) != 3) { + beaker_log("ERROR", "parse_request_url: Malformed request line\n"); + return NULL; + } - params->count = 0; + params->count = 0; - char *working_raw = strdup(raw_url_full); - if (!working_raw) { - perror("Failed to allocate memory for URL copy"); - return NULL; - } + char *working_raw = strdup(raw_url_full); + if (!working_raw) { + beaker_log_errno("Failed to allocate memory for URL copy"); + return NULL; + } - char *query_start = strchr(working_raw, '?'); - if (query_start) { - *query_start = '\0'; + char *query_start = strchr(working_raw, '?'); + if (query_start) { + *query_start = '\0'; + } - } + char decoded_path[MAX_PATH_LEN]; + if (url_decode(decoded_path, working_raw, sizeof(decoded_path)) != 0) { + beaker_log("SECURITY", "Invalid URL encoding in path\n"); + free(working_raw); + return NULL; + } - char decoded_path[MAX_PATH_LEN]; - if (url_decode(decoded_path, working_raw, sizeof(decoded_path)) != 0) { - fprintf(stderr, "[SECURITY] Invalid URL encoding in path\n"); - free(working_raw); - return NULL; - } + char canonical_path[MAX_PATH_LEN]; + if (canonicalize_path(canonical_path, decoded_path, sizeof(canonical_path)) != + 0) { + beaker_log("SECURITY", "Path canonicalization failed\n"); + free(working_raw); + return NULL; + } - char canonical_path[MAX_PATH_LEN]; - if (canonicalize_path(canonical_path, decoded_path, sizeof(canonical_path)) != 0) { - fprintf(stderr, "[SECURITY] Path canonicalization failed\n"); + char *path_check = strdup(canonical_path); + if (path_check) { + char *token = strtok(path_check, "/"); + while (token) { + if (!is_safe_path_component(token)) { + beaker_log("SECURITY", "Unsafe path component: %s\n", token); + free(path_check); free(working_raw); return NULL; + } + token = strtok(NULL, "/"); } + free(path_check); + } - char *path_check = strdup(canonical_path); - if (path_check) { - char *token = strtok(path_check, "/"); - while (token) { - if (!is_safe_path_component(token)) { - fprintf(stderr, "[SECURITY] Unsafe path component: %s\n", token); - free(path_check); - free(working_raw); - return NULL; - } - token = strtok(NULL, "/"); - } - free(path_check); - } - - if (query_start) { - char *query_string = query_start + 1; + if (query_start) { + char *query_string = query_start + 1; - char *pair; - char *saveptr; + char *pair; + char *saveptr; - pair = strtok_r(query_string, "&", &saveptr); - while (pair && params->count < MAX_URL_PARAMS) { - char *equals = strchr(pair, '='); - if (equals) { - *equals = '\0'; + pair = strtok_r(query_string, "&", &saveptr); + while (pair && params->count < MAX_URL_PARAMS) { + char *equals = strchr(pair, '='); + if (equals) { + *equals = '\0'; - char *raw_key = pair; - char *raw_val = equals + 1; + char *raw_key = pair; + char *raw_val = equals + 1; - if (url_decode(params->params[params->count].key, raw_key, MAX_KEY_LEN) == 0 && - url_decode(params->params[params->count].value, raw_val, MAX_VALUE_LEN) == 0) { - params->count++; - } - } - pair = strtok_r(NULL, "&", &saveptr); + if (url_decode(params->params[params->count].key, raw_key, + MAX_KEY_LEN) == 0 && + url_decode(params->params[params->count].value, raw_val, + MAX_VALUE_LEN) == 0) { + params->count++; } + } + pair = strtok_r(NULL, "&", &saveptr); } + } - char *final_path = strdup(canonical_path); - free(working_raw); - return final_path; + char *final_path = strdup(canonical_path); + free(working_raw); + return final_path; } -bool serve_static_file_with_mime(const char *request_path_relative_to_static, const char *mime_type) { +bool serve_static_file_with_mime(const char *request_path_relative_to_static, + const char *mime_type) { char full_static_path[MAX_PATH_LEN]; - if (request_path_relative_to_static == NULL || strlen(request_path_relative_to_static) == 0) { - fprintf(stderr, "[ERROR] serve_static_file_with_mime: Empty path provided\n"); + if (request_path_relative_to_static == NULL || + strlen(request_path_relative_to_static) == 0) { + beaker_log("ERROR", "serve_static_file_with_mime: Empty path provided\n"); return false; } if (strstr(request_path_relative_to_static, "..") != NULL || strstr(request_path_relative_to_static, "//") != NULL || request_path_relative_to_static[0] == '/') { - fprintf(stderr, "[SECURITY] Attempted directory traversal: %s\n", - request_path_relative_to_static); + beaker_log("SECURITY", "Attempted directory traversal: %s\n", + request_path_relative_to_static); send_status("403 Forbidden"); return true; } @@ -274,24 +279,27 @@ bool serve_static_file_with_mime(const char *request_path_relative_to_static, co FILE *fp = fopen(full_static_path, "rb"); if (fp == NULL) { - fprintf(stderr, - "[ERROR] serve_static_file_with_mime: File '%s' not found or could not be opened. %s\n", - full_static_path, strerror(errno)); + beaker_log("ERROR", + "serve_static_file_with_mime: File '%s' not found or could not " + "be opened. %s\n", + full_static_path, strerror(errno)); return false; } struct stat st; if (fstat(fileno(fp), &st) < 0) { - perror("fstat error"); - fprintf(stderr, "[ERROR] serve_static_file_with_mime: fstat failed for '%s'.\n", - full_static_path); + beaker_log_errno_format( + "ERROR", "serve_static_file_with_mime: fstat failed for '%s'.\n", + full_static_path); fclose(fp); send_status("500 Internal Server Error"); return true; } long file_size = st.st_size; + current_response_status = 200; + current_response_size = file_size; if (!mime_type || mime_type[0] == '\0') { mime_type = get_mime_type(full_static_path); @@ -308,9 +316,10 @@ bool serve_static_file_with_mime(const char *request_path_relative_to_static, co mime_type, file_size); if (send(current_client_socket, http_header, strlen(http_header), 0) < 0) { - perror("Error sending static file header"); - fprintf(stderr, "[ERROR] serve_static_file_with_mime: Failed to send header for '%s'.\n", - full_static_path); + beaker_log_errno_format( + "ERROR", + "serve_static_file_with_mime: Failed to send header for '%s'.\n", + full_static_path); fclose(fp); return true; } @@ -319,19 +328,21 @@ bool serve_static_file_with_mime(const char *request_path_relative_to_static, co size_t bytes_read; bool send_error = false; - while (!send_error && !feof(fp) && !ferror(fp) && (bytes_read = fread(file_buffer, 1, sizeof(file_buffer), fp)) > 0) { + while (!send_error && !feof(fp) && !ferror(fp) && + (bytes_read = fread(file_buffer, 1, sizeof(file_buffer), fp)) > 0) { if (send(current_client_socket, file_buffer, bytes_read, 0) < 0) { - perror("Error sending static file content"); - fprintf(stderr, "[ERROR] serve_static_file_with_mime: Failed to send content for '%s'.\n", - full_static_path); + beaker_log_errno_format( + "ERROR", + "serve_static_file_with_mime: Failed to send content for '%s'.\n", + full_static_path); send_error = true; } } if (ferror(fp)) { - perror("Error reading static file"); - fprintf(stderr, "[ERROR] serve_static_file_with_mime: Failed to read '%s'.\n", - full_static_path); + beaker_log_errno_format( + "ERROR", "serve_static_file_with_mime: Failed to read '%s'.\n", + full_static_path); fclose(fp); return true; } @@ -347,16 +358,19 @@ bool serve_static_file(const char *request_path_relative_to_static) { bool serve_data(const char *data, size_t size, const char *mime_type) { if (current_client_socket == -1) { - fprintf(stderr, "[ERROR] serve_data: No client socket set. Cannot send data.\n"); + beaker_log("ERROR", + "serve_data: No client socket set. Cannot send data.\n"); return false; } if (data == NULL || size == 0) { - fprintf(stderr, "[ERROR] serve_data: Invalid data or size.\n"); + beaker_log("ERROR", "serve_data: Invalid data or size.\n"); return false; } char http_header[BUFFER_SIZE]; + current_response_status = 200; + current_response_size = size; snprintf(http_header, sizeof(http_header), "HTTP/1.1 200 OK\r\n" @@ -367,17 +381,16 @@ bool serve_data(const char *data, size_t size, const char *mime_type) { mime_type, size); if (send(current_client_socket, http_header, strlen(http_header), 0) < 0) { - perror("Error sending data header"); - fprintf(stderr, "[ERROR] serve_data: Failed to send header.\n"); + beaker_log_errno_format("ERROR", "serve_data: Failed to send header.\n"); return false; } size_t bytes_sent = 0; while (bytes_sent < size) { - size_t chunk = (size - bytes_sent > BUFFER_SIZE) ? BUFFER_SIZE : (size - bytes_sent); + size_t chunk = + (size - bytes_sent > BUFFER_SIZE) ? BUFFER_SIZE : (size - bytes_sent); if (send(current_client_socket, data + bytes_sent, chunk, 0) < 0) { - perror("Error sending data content"); - fprintf(stderr, "[ERROR] serve_data: Failed to send content.\n"); + beaker_log_errno_format("ERROR", "serve_data: Failed to send content.\n"); return false; } bytes_sent += chunk; diff --git a/src/server.c b/src/server.c index 85ad61a..df1db23 100644 --- a/src/server.c +++ b/src/server.c @@ -1,186 +1,192 @@ -#include "../beaker.h" -#include "beaker_globals.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "../beaker.h" +#include "beaker_globals.h" +#include +#include #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #define MAX_PENDING_CONNECTIONS 128 static volatile sig_atomic_t g_shutdown_requested = 0; static void signal_handler(int sig) { - (void)sig; - g_shutdown_requested = 1; + (void)sig; + g_shutdown_requested = 1; } typedef struct { - _Atomic(size_t) sequence; - int socket; + _Atomic(size_t) sequence; + int socket; } WorkSlot; typedef struct { - _Atomic(size_t) head; - _Atomic(size_t) tail; - _Atomic(int) shutdown; - WorkSlot slots[MAX_PENDING_CONNECTIONS]; - pthread_mutex_t mutex; - pthread_cond_t cond; + _Atomic(size_t) head; + _Atomic(size_t) tail; + _Atomic(int) shutdown; + WorkSlot slots[MAX_PENDING_CONNECTIONS]; + pthread_mutex_t mutex; + pthread_cond_t cond; } WorkQueue; static WorkQueue g_work_queue; static void work_queue_init(WorkQueue *queue) { - atomic_store(&queue->head, 0); - atomic_store(&queue->tail, 0); - atomic_store(&queue->shutdown, 0); - for (int i = 0; i < MAX_PENDING_CONNECTIONS; i++) { - atomic_store(&queue->slots[i].sequence, (size_t)i); - } - pthread_mutex_init(&queue->mutex, NULL); - pthread_cond_init(&queue->cond, NULL); + atomic_store(&queue->head, 0); + atomic_store(&queue->tail, 0); + atomic_store(&queue->shutdown, 0); + for (int i = 0; i < MAX_PENDING_CONNECTIONS; i++) { + atomic_store(&queue->slots[i].sequence, (size_t)i); + } + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->cond, NULL); } static void work_queue_destroy(WorkQueue *queue) { - pthread_mutex_destroy(&queue->mutex); - pthread_cond_destroy(&queue->cond); + pthread_mutex_destroy(&queue->mutex); + pthread_cond_destroy(&queue->cond); } static int work_queue_push(WorkQueue *queue, int client_socket) { - size_t tail = atomic_load(&queue->tail); - - for (;;) { - WorkSlot *slot = &queue->slots[tail % MAX_PENDING_CONNECTIONS]; - size_t seq = atomic_load(&slot->sequence); - intptr_t diff = (intptr_t)seq - (intptr_t)tail; - - if (diff == 0) { - if (atomic_compare_exchange_weak(&queue->tail, &tail, tail + 1)) { - slot->socket = client_socket; - atomic_store(&slot->sequence, tail + 1); - pthread_cond_signal(&queue->cond); - return 0; - } - } else if (diff < 0) { - return -1; - } else { - tail = atomic_load(&queue->tail); - } + size_t tail = atomic_load(&queue->tail); + + for (;;) { + WorkSlot *slot = &queue->slots[tail % MAX_PENDING_CONNECTIONS]; + size_t seq = atomic_load(&slot->sequence); + intptr_t diff = (intptr_t)seq - (intptr_t)tail; + + if (diff == 0) { + if (atomic_compare_exchange_weak(&queue->tail, &tail, tail + 1)) { + slot->socket = client_socket; + atomic_store(&slot->sequence, tail + 1); + pthread_cond_signal(&queue->cond); + return 0; + } + } else if (diff < 0) { + return -1; + } else { + tail = atomic_load(&queue->tail); } + } } static int work_queue_pop(WorkQueue *queue) { - size_t head = atomic_load(&queue->head); - - for (;;) { - if (atomic_load(&queue->shutdown)) return -1; - - WorkSlot *slot = &queue->slots[head % MAX_PENDING_CONNECTIONS]; - size_t seq = atomic_load(&slot->sequence); - intptr_t diff = (intptr_t)seq - (intptr_t)(head + 1); - - if (diff == 0) { - if (atomic_compare_exchange_weak(&queue->head, &head, head + 1)) { - int fd = slot->socket; - atomic_store(&slot->sequence, head + MAX_PENDING_CONNECTIONS); - return fd; - } - } else if (diff < 0) { - pthread_mutex_lock(&queue->mutex); - if (!atomic_load(&queue->shutdown) && atomic_load(&queue->head) == head) { - pthread_cond_wait(&queue->cond, &queue->mutex); - } - pthread_mutex_unlock(&queue->mutex); - head = atomic_load(&queue->head); - } else { - head = atomic_load(&queue->head); - } + size_t head = atomic_load(&queue->head); + + for (;;) { + if (atomic_load(&queue->shutdown)) + return -1; + + WorkSlot *slot = &queue->slots[head % MAX_PENDING_CONNECTIONS]; + size_t seq = atomic_load(&slot->sequence); + intptr_t diff = (intptr_t)seq - (intptr_t)(head + 1); + + if (diff == 0) { + if (atomic_compare_exchange_weak(&queue->head, &head, head + 1)) { + int fd = slot->socket; + atomic_store(&slot->sequence, head + MAX_PENDING_CONNECTIONS); + return fd; + } + } else if (diff < 0) { + pthread_mutex_lock(&queue->mutex); + if (!atomic_load(&queue->shutdown) && atomic_load(&queue->head) == head) { + pthread_cond_wait(&queue->cond, &queue->mutex); + } + pthread_mutex_unlock(&queue->mutex); + head = atomic_load(&queue->head); + } else { + head = atomic_load(&queue->head); } + } } static int get_optimal_thread_count(void) { - long cores = sysconf(_SC_NPROCESSORS_ONLN); - if (cores < 1) cores = 1; - return (int)(cores * 2); + long cores = sysconf(_SC_NPROCESSORS_ONLN); + if (cores < 1) + cores = 1; + return (int)(cores * 2); } void handle_client_connection(int new_socket); static void *worker_thread(void *arg) { - (void)arg; - while (1) { - int client_socket = work_queue_pop(&g_work_queue); - if (client_socket < 0) { - break; - } - handle_client_connection(client_socket); + (void)arg; + while (1) { + int client_socket = work_queue_pop(&g_work_queue); + if (client_socket < 0) { + break; } - return NULL; + handle_client_connection(client_socket); + } + return NULL; } -static int initialize_server_socket(const char *ip, int port, int *server_fd_out, +static int initialize_server_socket(const char *ip, int port, + int *server_fd_out, struct sockaddr_in *address_out) { if ((*server_fd_out = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - perror("socket failed"); - fprintf(stderr, "[ERROR] initialize_server_socket: Failed to create socket.\n"); + beaker_log_errno_format( + "ERROR", "initialize_server_socket: Failed to create socket.\n"); return -1; } int opt = 1; if (setsockopt(*server_fd_out, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { - perror("setsockopt SO_REUSEADDR failed"); + beaker_log_errno("setsockopt SO_REUSEADDR failed"); } - // Needed for FreeBSD support. On macOS this allows multiple processes to - // bind the same TCP port, which is surprising for a single-instance server. - #if defined(__FreeBSD__) && defined(SO_REUSEPORT) +// Needed for FreeBSD support. On macOS this allows multiple processes to +// bind the same TCP port, which is surprising for a single-instance server. +#if defined(__FreeBSD__) && defined(SO_REUSEPORT) if (setsockopt(*server_fd_out, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) { - perror("setsockopt SO_REUSEPORT failed"); - fprintf(stderr, "[ERROR] initialize_server_socket: Failed to set SO_REUSEPORT.\n"); + beaker_log_errno_format( + "ERROR", "initialize_server_socket: Failed to set SO_REUSEPORT.\n"); close(*server_fd_out); return -1; } - #endif - +#endif - address_out->sin_family = AF_INET; - address_out->sin_addr.s_addr = inet_addr(ip); - address_out->sin_port = htons(port); + address_out->sin_family = AF_INET; + address_out->sin_addr.s_addr = inet_addr(ip); + address_out->sin_port = htons(port); - if (bind(*server_fd_out, (struct sockaddr *)address_out, sizeof(*address_out)) < 0) { - perror("bind failed"); - fprintf(stderr, "[ERROR] initialize_server_socket: Failed to bind socket to %s:%d.\n", ip, port); + if (bind(*server_fd_out, (struct sockaddr *)address_out, + sizeof(*address_out)) < 0) { + beaker_log_errno_format( + "ERROR", "initialize_server_socket: Failed to bind socket to %s:%d.\n", + ip, port); close(*server_fd_out); return -1; } if (listen(*server_fd_out, 10) < 0) { - perror("listen failed"); - fprintf(stderr, "[ERROR] initialize_server_socket: Failed to listen on socket.\n"); + beaker_log_errno_format( + "ERROR", "initialize_server_socket: Failed to listen on socket.\n"); close(*server_fd_out); return -1; } int flags = fcntl(*server_fd_out, F_GETFL, 0); if (flags < 0 || fcntl(*server_fd_out, F_SETFL, flags | O_NONBLOCK) < 0) { - perror("fcntl O_NONBLOCK failed"); + beaker_log_errno("fcntl O_NONBLOCK failed"); close(*server_fd_out); return -1; } - printf("Beaker server listening on %s:%d\n", ip, port); + beaker_log("INFO", "listening on %s:%d", ip, port); return 0; } @@ -195,73 +201,108 @@ static int set_socket_blocking(int fd) { return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); } +static void finish_client_connection(int socket, char *requested_path, + const char *method, const char *path, + const struct timespec *started_at) { + struct timespec finished_at; + clock_gettime(CLOCK_MONOTONIC, &finished_at); + double duration_ms = (finished_at.tv_sec - started_at->tv_sec) * 1000.0 + + (finished_at.tv_nsec - started_at->tv_nsec) / 1000000.0; + beaker_log_request(current_request_info.remote_addr, method, path, + current_response_status, current_response_size, + duration_ms); + free(requested_path); + close(socket); + current_client_socket = -1; +} + void handle_client_connection(int new_socket) { - current_client_socket = new_socket; - char buffer[BUFFER_SIZE] = {0}; + current_client_socket = new_socket; + char buffer[BUFFER_SIZE] = {0}; + char method[16] = "-"; + char log_path[MAX_PATH_LEN] = "-"; + char *requested_path = NULL; + struct timespec started_at; + + clock_gettime(CLOCK_MONOTONIC, &started_at); + current_response_status = 0; + current_response_size = 0; + memset(¤t_request_info, 0, sizeof(RequestInfo)); + + struct sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + if (getpeername(new_socket, (struct sockaddr *)&client_addr, &client_len) != + 0 || + inet_ntop(AF_INET, &client_addr.sin_addr, + current_request_info.remote_addr, + sizeof(current_request_info.remote_addr)) == NULL) { + strcpy(current_request_info.remote_addr, "-"); + } ssize_t bytes_read = read(new_socket, buffer, BUFFER_SIZE - 1); if (bytes_read < 0) { - perror("read failed"); - fprintf(stderr, "[ERROR] handle_client_connection: Failed to read from client socket.\n"); - close(new_socket); + beaker_log_errno_format( + "ERROR", + "handle_client_connection: Failed to read from client socket.\n"); + finish_client_connection(new_socket, requested_path, method, log_path, + &started_at); return; } - buffer[bytes_read] = '\0'; + buffer[bytes_read] = '\0'; strncpy(current_request_buffer, buffer, BUFFER_SIZE - 1); current_request_buffer[BUFFER_SIZE - 1] = '\0'; - memset(¤t_request_info, 0, sizeof(RequestInfo)); - - char request_line[MAX_PATH_LEN + 64]; - char *first_line_end = strstr(buffer, "\r\n"); + char request_line[MAX_PATH_LEN + 64]; + char *first_line_end = strstr(buffer, "\r\n"); if (first_line_end == NULL) { - fprintf(stderr, "[ERROR] handle_client_connection: Invalid HTTP request: No CRLF found.\n"); + beaker_log( + "ERROR", + "handle_client_connection: Invalid HTTP request: No CRLF found.\n"); send_status("400 Bad Request"); - close(new_socket); + finish_client_connection(new_socket, requested_path, method, log_path, + &started_at); return; } size_t request_line_len = first_line_end - buffer; if (request_line_len >= sizeof(request_line)) { - fprintf(stderr, "[ERROR] handle_client_connection: Request line too long.\n"); + beaker_log("ERROR", "handle_client_connection: Request line too long.\n"); send_status("400 Bad Request"); - close(new_socket); + finish_client_connection(new_socket, requested_path, method, log_path, + &started_at); return; } strncpy(request_line, buffer, request_line_len); - request_line[request_line_len] = '\0'; + request_line[request_line_len] = '\0'; + sscanf(request_line, "%15s %255s", method, log_path); - UrlParams request_params; - char *requested_path = parse_request_url(request_line, &request_params); + UrlParams request_params; + requested_path = parse_request_url(request_line, &request_params); if (requested_path == NULL) { - fprintf(stderr, "[ERROR] handle_client_connection: Could not parse request path. Sending 400 Bad Request.\n"); + beaker_log("ERROR", "handle_client_connection: Could not parse request " + "path. Sending 400 Bad Request.\n"); send_status("400 Bad Request"); - close(new_socket); + finish_client_connection(new_socket, requested_path, method, log_path, + &started_at); return; } -printf("Accessing: %s\n", requested_path); - - struct sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - if (getpeername(new_socket, (struct sockaddr *)&client_addr, &client_len) == 0) { - strncpy(current_request_info.remote_addr, inet_ntoa(client_addr.sin_addr), - sizeof(current_request_info.remote_addr) - 1); - } + strncpy(log_path, requested_path, sizeof(log_path) - 1); + log_path[sizeof(log_path) - 1] = '\0'; bool handled = false; if (strncmp(requested_path, "/static/", strlen("/static/")) == 0) { if (serve_static_file(requested_path + strlen("/static/"))) { - handled = true; + handled = true; } } if (!handled) { - int best_match_handler_index = -1; - size_t best_match_len = 0; + int best_match_handler_index = -1; + size_t best_match_len = 0; for (int i = 0; i < handler_count; i++) { size_t handler_path_len = strlen(handlers[i].path); @@ -281,14 +322,11 @@ printf("Accessing: %s\n", requested_path); if (best_match_handler_index != -1) { handlers[best_match_handler_index].handler(&request_params); - handled = true; + handled = true; } } if (!handled) { - fprintf(stderr, - "[WARNING] handle_client_connection: No handler or static file found for path '%s'. Sending 404 Not Found.\n", - requested_path); const char *not_found_html = "

404 Not Found

The requested URL " "was not located on this server.

"; char not_found_response[BUFFER_SIZE]; @@ -300,130 +338,141 @@ printf("Accessing: %s\n", requested_path); "\r\n%s", strlen(not_found_html), not_found_html); send(new_socket, not_found_response, strlen(not_found_response), 0); + current_response_status = 404; + current_response_size = strlen(not_found_html); } - free(requested_path); - close(new_socket); - current_client_socket = -1; + finish_client_connection(new_socket, requested_path, method, log_path, + &started_at); } int beaker_run(const char *ip, int port) { - beaker_run_with_threads(ip, port, 0); - return 0; + beaker_run_with_threads(ip, port, 0); + return 0; } void beaker_run_with_threads(const char *ip, int port, int num_workers) { - int server_fd; - struct sockaddr_in address; - int addrlen = sizeof(address); + int server_fd; + struct sockaddr_in address; + int addrlen = sizeof(address); - g_shutdown_requested = 0; + g_shutdown_requested = 0; - struct sigaction sa; - sa.sa_handler = signal_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); + struct sigaction sa; + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); - if (num_workers <= 0) { - num_workers = get_optimal_thread_count(); - } + if (num_workers <= 0) { + num_workers = get_optimal_thread_count(); + } - if (initialize_server_socket(ip, port, &server_fd, &address) != 0) { - return; - } + if (initialize_server_socket(ip, port, &server_fd, &address) != 0) { + return; + } - work_queue_init(&g_work_queue); + work_queue_init(&g_work_queue); - pthread_t threads[num_workers]; - for (int i = 0; i < num_workers; i++) { - pthread_create(&threads[i], NULL, worker_thread, NULL); - } + pthread_t threads[num_workers]; + for (int i = 0; i < num_workers; i++) { + pthread_create(&threads[i], NULL, worker_thread, NULL); + } - printf("Beaker server started with %d worker threads\n", num_workers); + beaker_log("DEBUG", "started %d worker threads", num_workers); - struct pollfd pfd = { .fd = server_fd, .events = POLLIN }; + struct pollfd pfd = {.fd = server_fd, .events = POLLIN}; - while (!g_shutdown_requested) { - int ret = poll(&pfd, 1, 1000); - if (ret < 0) { - if (errno == EINTR) continue; - perror("poll failed"); - break; - } - if (ret == 0) continue; - - int new_socket; - while ((new_socket = accept(server_fd, (struct sockaddr *)&address, - (socklen_t *)&addrlen)) >= 0) { - if (set_socket_blocking(new_socket) < 0) { - perror("fcntl clear O_NONBLOCK failed"); - close(new_socket); - continue; - } - if (work_queue_push(&g_work_queue, new_socket) < 0) { - fprintf(stderr, "[WARNING] Work queue full, closing connection\n"); - const char *busy_response = "HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n"; - send(new_socket, busy_response, strlen(busy_response), 0); - close(new_socket); - } - } + while (!g_shutdown_requested) { + int ret = poll(&pfd, 1, 1000); + if (ret < 0) { + if (errno == EINTR) + continue; + beaker_log_errno("poll failed"); + break; + } + if (ret == 0) + continue; + + int new_socket; + while ((new_socket = accept(server_fd, (struct sockaddr *)&address, + (socklen_t *)&addrlen)) >= 0) { + if (set_socket_blocking(new_socket) < 0) { + beaker_log_errno("fcntl clear O_NONBLOCK failed"); + close(new_socket); + continue; + } + if (work_queue_push(&g_work_queue, new_socket) < 0) { + beaker_log("WARN", "work queue full; rejecting connection"); + const char *busy_response = + "HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n"; + send(new_socket, busy_response, strlen(busy_response), 0); + close(new_socket); + } } + } - printf("Shutting down server...\n"); + beaker_log("INFO", "shutting down"); - atomic_store(&g_work_queue.shutdown, 1); - pthread_cond_broadcast(&g_work_queue.cond); + atomic_store(&g_work_queue.shutdown, 1); + pthread_cond_broadcast(&g_work_queue.cond); - for (int i = 0; i < num_workers; i++) { - pthread_join(threads[i], NULL); - } + for (int i = 0; i < num_workers; i++) { + pthread_join(threads[i], NULL); + } - work_queue_destroy(&g_work_queue); - close(server_fd); + work_queue_destroy(&g_work_queue); + close(server_fd); } const char *beaker_get_remote_addr(void) { - return current_request_info.remote_addr; + return current_request_info.remote_addr; } static __thread char g_header_value[MAX_VALUE_LEN]; const char *beaker_get_header(const char *name) { - if (name == NULL) return ""; - - size_t name_len = strlen(name); - if (name_len == 0) return ""; - - if (strstr(name, "\r\n") != NULL) return ""; - - char *buffer = current_request_buffer; - char *search_end = strstr(buffer, "\r\n\r\n"); - if (search_end == NULL) search_end = buffer + strlen(buffer); - while (buffer < search_end) { - if (strncasecmp(buffer, name, name_len) == 0 && buffer[name_len] == ':') { - char *value_start = buffer + name_len + 1; - while (*value_start == ' ') value_start++; - - char *value_end = strstr(value_start, "\r\n"); - if (value_end == NULL) value_end = search_end; - size_t value_len = value_end - value_start; - if (value_len > sizeof(g_header_value)) { - value_len = sizeof(g_header_value) - 1; - } - strncpy(g_header_value, value_start, value_len); - g_header_value[value_len] = '\0'; - return g_header_value; - } - buffer++; - } - + if (name == NULL) + return ""; + + size_t name_len = strlen(name); + if (name_len == 0) + return ""; + + if (strstr(name, "\r\n") != NULL) return ""; + + char *buffer = current_request_buffer; + char *search_end = strstr(buffer, "\r\n\r\n"); + if (search_end == NULL) + search_end = buffer + strlen(buffer); + while (buffer < search_end) { + if (strncasecmp(buffer, name, name_len) == 0 && buffer[name_len] == ':') { + char *value_start = buffer + name_len + 1; + while (*value_start == ' ') + value_start++; + + char *value_end = strstr(value_start, "\r\n"); + if (value_end == NULL) + value_end = search_end; + size_t value_len = value_end - value_start; + if (value_len > sizeof(g_header_value)) { + value_len = sizeof(g_header_value) - 1; + } + strncpy(g_header_value, value_start, value_len); + g_header_value[value_len] = '\0'; + return g_header_value; + } + buffer++; + } + + return ""; } void beaker_set_request_buffer(const char *buffer) { - if (buffer == NULL) return; - strncpy(current_request_buffer, buffer, BUFFER_SIZE - 1); - current_request_buffer[BUFFER_SIZE - 1] = '\0'; + if (buffer == NULL) + return; + strncpy(current_request_buffer, buffer, BUFFER_SIZE - 1); + current_request_buffer[BUFFER_SIZE - 1] = '\0'; } diff --git a/src/template.c b/src/template.c index 8365166..fd03480 100644 --- a/src/template.c +++ b/src/template.c @@ -11,8 +11,7 @@ static char *render_template_segment(const char *template_segment, static ContextVar *find_context_var(TemplateContext *ctx, const char *key) { if (ctx == NULL || key == NULL) { - fprintf(stderr, - "[ERROR] find_context_var: Invalid NULL input (ctx or key).\n"); + beaker_log("ERROR", "find_context_var: Invalid NULL input (ctx or key).\n"); return NULL; } for (int i = 0; i < ctx->count; i++) { @@ -90,8 +89,8 @@ TemplateContext new_context() { void context_set(TemplateContext *ctx, const char *key, const char *value) { if (ctx == NULL || key == NULL || value == NULL) { - fprintf(stderr, - "[ERROR] context_set: Invalid NULL input (ctx, key, or value).\n"); + beaker_log("ERROR", + "context_set: Invalid NULL input (ctx, key, or value).\n"); return; } @@ -114,25 +113,25 @@ void context_set(TemplateContext *ctx, const char *key, const char *value) { new_var->type = CONTEXT_TYPE_STRING; ctx->count++; } else { - fprintf(stderr, - "[WARNING] context_set: TemplateContext is full. Cannot add key " - "'%s'.\n", - key); + beaker_log("WARN", + "context_set: TemplateContext is full. Cannot add key " + "'%s'.\n", + key); } } void context_set_string_array(TemplateContext *ctx, const char *key, char *values[], int count) { if (ctx == NULL || key == NULL || values == NULL) { - fprintf(stderr, "[ERROR] context_set_string_array: Invalid NULL input " - "(ctx, key, or values).\n"); + beaker_log("ERROR", "context_set_string_array: Invalid NULL input " + "(ctx, key, or values).\n"); return; } if (count < 0 || count > MAX_OUTER_ARRAY_ITEMS) { - fprintf(stderr, - "[ERROR] context_set_string_array: Invalid count %d for string " - "array context '%s'. Max %d allowed.\n", - count, key, MAX_OUTER_ARRAY_ITEMS); + beaker_log("ERROR", + "context_set_string_array: Invalid count %d for string " + "array context '%s'. Max %d allowed.\n", + count, key, MAX_OUTER_ARRAY_ITEMS); return; } @@ -147,10 +146,10 @@ void context_set_string_array(TemplateContext *ctx, const char *key, var->key[MAX_KEY_LEN - 1] = '\0'; ctx->count++; } else { - fprintf(stderr, - "[WARNING] context_set_string_array: TemplateContext is full. " - "Cannot add string array key '%s'.\n", - key); + beaker_log("WARN", + "context_set_string_array: TemplateContext is full. " + "Cannot add string array key '%s'.\n", + key); return; } } @@ -158,11 +157,11 @@ void context_set_string_array(TemplateContext *ctx, const char *key, var->type = CONTEXT_TYPE_STRING_ARRAY; var->value.string_array_data.values = (char **)malloc(sizeof(char *) * count); if (var->value.string_array_data.values == NULL) { - perror("Failed to allocate memory for string array pointers"); - fprintf(stderr, - "[ERROR] context_set_string_array: Allocation failed for values " - "for key '%s'.\n", - key); + beaker_log_errno_format( + "ERROR", + "context_set_string_array: Allocation failed for values " + "for key '%s'.\n", + key); return; } var->value.string_array_data.count = count; @@ -174,11 +173,11 @@ void context_set_string_array(TemplateContext *ctx, const char *key, } var->value.string_array_data.values[i] = strdup(values[i]); if (var->value.string_array_data.values[i] == NULL) { - perror("Failed to duplicate string for string array context"); - fprintf(stderr, - "[ERROR] context_set_string_array: Failed to duplicate value for " - "item %d of key '%s'.\n", - i, key); + beaker_log_errno_format( + "ERROR", + "context_set_string_array: Failed to duplicate value for " + "item %d of key '%s'.\n", + i, key); for (int j = 0; j < i; j++) { if (var->value.string_array_data.values[j] != NULL) { @@ -197,15 +196,15 @@ void context_set_array_of_arrays(TemplateContext *ctx, const char *key, char **values_2d[], int outer_count, int inner_counts[]) { if (ctx == NULL || key == NULL || values_2d == NULL || inner_counts == NULL) { - fprintf(stderr, "[ERROR] context_set_array_of_arrays: Invalid NULL input " - "(ctx, key, values_2d, or inner_counts).\n"); + beaker_log("ERROR", "context_set_array_of_arrays: Invalid NULL input " + "(ctx, key, values_2d, or inner_counts).\n"); return; } if (outer_count < 0 || outer_count > MAX_OUTER_ARRAY_ITEMS) { - fprintf(stderr, - "[ERROR] context_set_array_of_arrays: Invalid outer count %d for " - "array of arrays context '%s'. Max %d allowed.\n", - outer_count, key, MAX_OUTER_ARRAY_ITEMS); + beaker_log("ERROR", + "context_set_array_of_arrays: Invalid outer count %d for " + "array of arrays context '%s'. Max %d allowed.\n", + outer_count, key, MAX_OUTER_ARRAY_ITEMS); return; } @@ -220,10 +219,10 @@ void context_set_array_of_arrays(TemplateContext *ctx, const char *key, var->key[MAX_KEY_LEN - 1] = '\0'; ctx->count++; } else { - fprintf(stderr, - "[WARNING] context_set_array_of_arrays: TemplateContext is full. " - "Cannot add array of arrays key '%s'.\n", - key); + beaker_log("WARN", + "context_set_array_of_arrays: TemplateContext is full. " + "Cannot add array of arrays key '%s'.\n", + key); return; } } @@ -236,11 +235,11 @@ void context_set_array_of_arrays(TemplateContext *ctx, const char *key, if (var->value.string_2d_array_data.values == NULL || var->value.string_2d_array_data.inner_counts == NULL) { - perror("Failed to allocate memory for 2D string array pointers or counts"); - fprintf(stderr, - "[ERROR] context_set_array_of_arrays: Allocation failed for key " - "'%s'.\n", - key); + beaker_log_errno_format( + "ERROR", + "context_set_array_of_arrays: Allocation failed for key " + "'%s'.\n", + key); free(var->value.string_2d_array_data.values); free(var->value.string_2d_array_data.inner_counts); var->value.string_2d_array_data.values = NULL; @@ -253,10 +252,10 @@ void context_set_array_of_arrays(TemplateContext *ctx, const char *key, int current_inner_count = inner_counts[i]; if (current_inner_count < 0 || current_inner_count > MAX_INNER_ARRAY_ITEMS) { - fprintf(stderr, - "[ERROR] context_set_array_of_arrays: Invalid inner count %d for " - "item %d in 2D array '%s'. Max %d allowed.\n", - current_inner_count, i, key, MAX_INNER_ARRAY_ITEMS); + beaker_log("ERROR", + "context_set_array_of_arrays: Invalid inner count %d for " + "item %d in 2D array '%s'. Max %d allowed.\n", + current_inner_count, i, key, MAX_INNER_ARRAY_ITEMS); for (int k = 0; k < i; k++) { if (var->value.string_2d_array_data.values[k] != NULL) { @@ -279,11 +278,11 @@ void context_set_array_of_arrays(TemplateContext *ctx, const char *key, var->value.string_2d_array_data.values[i] = (char **)malloc(sizeof(char *) * current_inner_count); if (var->value.string_2d_array_data.values[i] == NULL) { - perror("Failed to allocate memory for inner string array pointers"); - fprintf(stderr, - "[ERROR] context_set_array_of_arrays: Allocation failed for " - "inner array %d of key '%s'.\n", - i, key); + beaker_log_errno_format( + "ERROR", + "context_set_array_of_arrays: Allocation failed for " + "inner array %d of key '%s'.\n", + i, key); for (int k = 0; k < i; k++) { if (var->value.string_2d_array_data.values[k] != NULL) { @@ -311,11 +310,11 @@ void context_set_array_of_arrays(TemplateContext *ctx, const char *key, } var->value.string_2d_array_data.values[i][j] = strdup(values_2d[i][j]); if (var->value.string_2d_array_data.values[i][j] == NULL) { - perror("Failed to duplicate string for inner array context"); - fprintf(stderr, - "[ERROR] context_set_array_of_arrays: Failed to duplicate " - "value for item [%d][%d] of key '%s'.\n", - i, j, key); + beaker_log_errno_format( + "ERROR", + "context_set_array_of_arrays: Failed to duplicate " + "value for item [%d][%d] of key '%s'.\n", + i, j, key); for (int k = 0; k <= i; k++) { if (var->value.string_2d_array_data.values[k] != NULL) { @@ -360,7 +359,7 @@ static char *html_escape(const char *input) { size_t estimated_len = input_len * 5 + 1; char *output = (char *)malloc(estimated_len); if (output == NULL) { - perror("Failed to allocate memory for HTML escape output"); + beaker_log_errno("Failed to allocate memory for HTML escape output"); return NULL; } output[0] = '\0'; @@ -396,7 +395,8 @@ static char *html_escape(const char *input) { estimated_len = (current_output_len + repl_len + 1) * 2; char *new_output = (char *)realloc(output, estimated_len); if (new_output == NULL) { - perror("Failed to reallocate memory for HTML escape output"); + beaker_log_errno( + "Failed to reallocate memory for HTML escape output"); free(output); return NULL; } @@ -410,7 +410,8 @@ static char *html_escape(const char *input) { estimated_len = (current_output_len + 1 + 1) * 2; char *new_output = (char *)realloc(output, estimated_len); if (new_output == NULL) { - perror("Failed to reallocate memory for HTML escape output"); + beaker_log_errno( + "Failed to reallocate memory for HTML escape output"); free(output); return NULL; } @@ -426,8 +427,8 @@ static char *html_escape(const char *input) { static void append_to_buffer(char **buffer, size_t *current_len, size_t *max_len, const char *str_to_add) { if (str_to_add == NULL) { - fprintf(stderr, "[WARNING] append_to_buffer: Attempted to append NULL " - "string. Skipping.\n"); + beaker_log("WARN", "append_to_buffer: Attempted to append NULL " + "string. Skipping.\n"); return; } @@ -442,11 +443,11 @@ static void append_to_buffer(char **buffer, size_t *current_len, char *new_buffer = (char *)realloc(*buffer, *max_len); if (new_buffer == NULL) { - perror("Failed to reallocate buffer for template rendering"); - fprintf(stderr, - "[ERROR] append_to_buffer: Reallocation failed (requested %zu " - "bytes).\n", - *max_len); + beaker_log_errno_format( + "ERROR", + "append_to_buffer: Reallocation failed (requested %zu " + "bytes).\n", + *max_len); free(*buffer); *buffer = NULL; @@ -467,23 +468,22 @@ static char *parse_indexed_tag(const char *tag_content, int *index_val) { char *key_name = strdup(tag_content); if (key_name == NULL) { - perror("Failed to duplicate tag content for simple key"); - fprintf(stderr, "[ERROR] parse_indexed_tag: strdup failed for '%s'.\n", - tag_content); + beaker_log_errno_format( + "ERROR", "parse_indexed_tag: strdup failed for '%s'.\n", tag_content); } return key_name; } const char *close_bracket = strchr(open_bracket, ']'); if (close_bracket == NULL) { - fprintf(stderr, - "[ERROR] parse_indexed_tag: Unclosed bracket in tag '%s'. " - "Returning raw tag content.\n", - tag_content); + beaker_log("ERROR", + "parse_indexed_tag: Unclosed bracket in tag '%s'. " + "Returning raw tag content.\n", + tag_content); char *key_name = strdup(tag_content); if (key_name == NULL) { - perror("Failed to duplicate malformed tag content"); + beaker_log_errno("Failed to duplicate malformed tag content"); } return key_name; } @@ -491,9 +491,8 @@ static char *parse_indexed_tag(const char *tag_content, int *index_val) { size_t key_len = open_bracket - tag_content; char *key_name = (char *)malloc(key_len + 1); if (key_name == NULL) { - perror("Failed to allocate memory for key_name in parse_indexed_tag"); - fprintf(stderr, - "[ERROR] parse_indexed_tag: Allocation failed for key_name.\n"); + beaker_log_errno_format( + "ERROR", "parse_indexed_tag: Allocation failed for key_name.\n"); return NULL; } strncpy(key_name, tag_content, key_len); @@ -502,9 +501,8 @@ static char *parse_indexed_tag(const char *tag_content, int *index_val) { size_t index_str_len = close_bracket - (open_bracket + 1); char *index_str = (char *)malloc(index_str_len + 1); if (index_str == NULL) { - perror("Failed to allocate memory for index_str in parse_indexed_tag"); - fprintf(stderr, - "[ERROR] parse_indexed_tag: Allocation failed for index_str.\n"); + beaker_log_errno_format( + "ERROR", "parse_indexed_tag: Allocation failed for index_str.\n"); free(key_name); return NULL; } @@ -540,8 +538,7 @@ static const char *resolve_compare_value(const Condition *cond, ContextVar *compare_var = find_context_var(ctx, cond->compare_value); if (cond->compare_index >= 0) { - if (compare_var != NULL && - compare_var->type == CONTEXT_TYPE_STRING_ARRAY && + if (compare_var != NULL && compare_var->type == CONTEXT_TYPE_STRING_ARRAY && cond->compare_index < compare_var->value.string_array_data.count) { compare_value = compare_var->value.string_array_data.values[cond->compare_index]; @@ -568,7 +565,8 @@ static bool evaluate_condition(const Condition *cond, TemplateContext *ctx) { return false; } var_value = ""; - } else if (var->type == CONTEXT_TYPE_STRING_ARRAY && cond->compare_index >= 0) { + } else if (var->type == CONTEXT_TYPE_STRING_ARRAY && + cond->compare_index >= 0) { if (cond->compare_index >= 0 && cond->compare_index < var->value.string_array_data.count) { var_value = var->value.string_array_data.values[cond->compare_index]; @@ -596,8 +594,7 @@ static bool evaluate_condition(const Condition *cond, TemplateContext *ctx) { case CONDITION_EQUAL: case CONDITION_NOT_EQUAL: { char compare_buf[MAX_VALUE_LEN]; - const char *compare_value = - resolve_compare_value(cond, ctx, compare_buf); + const char *compare_value = resolve_compare_value(cond, ctx, compare_buf); bool values_equal = strcmp(var_value, compare_value) == 0; return cond->type == CONDITION_EQUAL ? values_equal : !values_equal; } @@ -766,11 +763,11 @@ static char *render_template_segment(const char *template_segment, size_t initial_max_len = BUFFER_SIZE; char *rendered_buffer = (char *)malloc(initial_max_len); if (rendered_buffer == NULL) { - perror("Failed to allocate initial buffer for template segment rendering"); - fprintf(stderr, - "[ERROR] render_template_segment: Failed to allocate initial %zu " - "bytes for rendered_buffer.\n", - initial_max_len); + beaker_log_errno_format( + "ERROR", + "render_template_segment: Failed to allocate initial %zu " + "bytes for rendered_buffer.\n", + initial_max_len); return NULL; } rendered_buffer[0] = '\0'; @@ -786,9 +783,9 @@ static char *render_template_segment(const char *template_segment, if (text_len > 0) { char *text_before_tag = (char *)malloc(text_len + 1); if (text_before_tag == NULL) { - perror("Failed to allocate memory for text_before_tag"); - fprintf(stderr, "[ERROR] render_template_segment: Allocation failed " - "for text_before_tag.\n"); + beaker_log_errno_format("ERROR", + "render_template_segment: Allocation failed " + "for text_before_tag.\n"); free(rendered_buffer); return NULL; } @@ -801,8 +798,8 @@ static char *render_template_segment(const char *template_segment, const char *end_tag = strstr(start_tag, "}}"); if (end_tag == NULL) { - fprintf(stderr, "[ERROR] render_template_segment: Unclosed '{{' tag. " - "Appending remaining template content as-is.\n"); + beaker_log("ERROR", "render_template_segment: Unclosed '{{' tag. " + "Appending remaining template content as-is.\n"); append_to_buffer(&rendered_buffer, ¤t_len, &max_len, start_tag); return rendered_buffer; } @@ -810,9 +807,9 @@ static char *render_template_segment(const char *template_segment, size_t tag_content_len = end_tag - (start_tag + 2); char *tag_content_raw = (char *)malloc(tag_content_len + 1); if (tag_content_raw == NULL) { - perror("Failed to allocate memory for tag_content_raw"); - fprintf(stderr, "[ERROR] render_template_segment: Allocation failed for " - "tag_content_raw.\n"); + beaker_log_errno_format("ERROR", + "render_template_segment: Allocation failed for " + "tag_content_raw.\n"); free(rendered_buffer); return NULL; } @@ -878,9 +875,9 @@ static char *render_template_segment(const char *template_segment, size_t loop_inner_len = loop_end_tag - loop_inner_start; char *loop_inner_template = (char *)malloc(loop_inner_len + 1); if (loop_inner_template == NULL) { - perror("Failed to allocate memory for loop_inner_template"); - fprintf(stderr, "[ERROR] render_template_segment: Allocation " - "failed for loop_inner_template.\n"); + beaker_log_errno_format("ERROR", + "render_template_segment: Allocation " + "failed for loop_inner_template.\n"); free(rendered_buffer); free(tag_content_raw); return NULL; @@ -940,9 +937,9 @@ static char *render_template_segment(const char *template_segment, free_context(&loop_ctx); } } else { - fprintf( - stderr, - "[IGNORE] [ERROR] render_template_segment: List variable '%s' " + beaker_log( + "DEBUG", + "render_template_segment: List variable '%s' " "(type %d) is not an iterable array type for 'for' loop.\n", list_var, list_ctx_var->type); } @@ -952,25 +949,26 @@ static char *render_template_segment(const char *template_segment, continue; } else { - fprintf(stderr, - "[IGNORE] [ERROR] render_template_segment: List variable " - "'%s' not found for 'for' loop. Skipping loop block.\n", - list_var); + beaker_log("DEBUG", + "render_template_segment: List variable " + "'%s' not found for 'for' loop. Skipping loop block.\n", + list_var); current_pos = loop_end_tag + strlen("{{endfor}}"); free(tag_content_raw); continue; } } else { - fprintf(stderr, - "[ERROR] render_template_segment: Malformed 'for' loop tag: " - "'%s'. Expected 'for var in list'. Appending loop tag as-is.\n", - trimmed_tag_content); + beaker_log( + "ERROR", + "render_template_segment: Malformed 'for' loop tag: " + "'%s'. Expected 'for var in list'. Appending loop tag as-is.\n", + trimmed_tag_content); } } else if (strcmp(trimmed_tag_content, "endfor") == 0) { - fprintf(stderr, "[WARNING] render_template_segment: '{{endfor}}' without " - "matching '{{for}}'. Appending endfor tag as-is.\n"); + beaker_log("WARN", "render_template_segment: '{{endfor}}' without " + "matching '{{for}}'. Appending endfor tag as-is.\n"); } else if (strncmp(trimmed_tag_content, "if ", 3) == 0) { @@ -992,7 +990,7 @@ static char *render_template_segment(const char *template_segment, const char *candidates[4] = {next_if, next_else, next_elif, next_endif}; const char *earliest = NULL; int earliest_idx = -1; - + for (int i = 0; i < 4; i++) { if (candidates[i] != NULL) { if (earliest == NULL || candidates[i] < earliest) { @@ -1035,18 +1033,16 @@ static char *render_template_segment(const char *template_segment, } if (endif_tag == NULL) { - fprintf(stderr, - "[ERROR] render_template_segment: Unclosed '{{if}}' tag. " - "Skipping if block.\n"); + beaker_log("ERROR", "render_template_segment: Unclosed '{{if}}' tag. " + "Skipping if block.\n"); current_pos = end_tag + 2; free(tag_content_raw); continue; } if (endif_tag == NULL) { - fprintf(stderr, - "[ERROR] render_template_segment: Unclosed '{{if}}' tag. " - "Skipping if block.\n"); + beaker_log("ERROR", "render_template_segment: Unclosed '{{if}}' tag. " + "Skipping if block.\n"); current_pos = end_tag + 2; free(tag_content_raw); continue; @@ -1139,18 +1135,18 @@ static char *render_template_segment(const char *template_segment, else if (strcmp(trimmed_tag_content, "elif ") == 0 || strcmp(trimmed_tag_content, "elif") == 0) { - fprintf(stderr, "[WARNING] render_template_segment: '{{elif}}' without " - "matching '{{if}}'. Appending elif tag as-is.\n"); + beaker_log("WARN", "render_template_segment: '{{elif}}' without " + "matching '{{if}}'. Appending elif tag as-is.\n"); } else if (strcmp(trimmed_tag_content, "else") == 0) { - fprintf(stderr, "[WARNING] render_template_segment: '{{else}}' without " - "matching '{{if}}'. Appending else tag as-is.\n"); + beaker_log("WARN", "render_template_segment: '{{else}}' without " + "matching '{{if}}'. Appending else tag as-is.\n"); } else if (strcmp(trimmed_tag_content, "endif") == 0) { - fprintf(stderr, "[WARNING] render_template_segment: '{{endif}}' without " - "matching '{{if}}'. Appending endif tag as-is.\n"); + beaker_log("WARN", "render_template_segment: '{{endif}}' without " + "matching '{{if}}'. Appending endif tag as-is.\n"); } else if (strncmp(trimmed_tag_content, "include ", 8) == 0) { @@ -1162,9 +1158,9 @@ static char *render_template_segment(const char *template_segment, size_t filename_len = filename_end - filename_start; char *included_filename = (char *)malloc(filename_len + 1); if (included_filename == NULL) { - perror("Failed to allocate memory for included filename"); - fprintf(stderr, "[ERROR] render_template_segment: Allocation " - "failed for included_filename.\n"); + beaker_log_errno_format("ERROR", + "render_template_segment: Allocation " + "failed for included_filename.\n"); free(rendered_buffer); free(tag_content_raw); return NULL; @@ -1172,13 +1168,16 @@ static char *render_template_segment(const char *template_segment, strncpy(included_filename, filename_start, filename_len); included_filename[filename_len] = '\0'; - if (strstr(included_filename, "..") != NULL || strchr(included_filename, '/') != NULL) { - fprintf(stderr, - "[SECURITY] render_template_segment: Path traversal attempt in include: %s\n", - included_filename); + if (strstr(included_filename, "..") != NULL || + strchr(included_filename, '/') != NULL) { + beaker_log("SECURITY", + "render_template_segment: Path traversal attempt in " + "include: %s\n", + included_filename); free(included_filename); - append_to_buffer(&rendered_buffer, ¤t_len, &max_len, - ""); + append_to_buffer( + &rendered_buffer, ¤t_len, &max_len, + ""); current_pos = end_tag + 2; free(tag_content_raw); continue; @@ -1190,10 +1189,10 @@ static char *render_template_segment(const char *template_segment, included_html); free(included_html); } else { - fprintf(stderr, - "[WARNING] render_template_segment: Failed to render " - "included template '%s'.\n", - included_filename); + beaker_log("WARN", + "render_template_segment: Failed to render " + "included template '%s'.\n", + included_filename); append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "