diff options
| author | frosty <frosty@illegalfirearms.store> | 2025-12-28 03:26:05 -0500 |
|---|---|---|
| committer | frosty <frosty@illegalfirearms.store> | 2025-12-28 03:26:05 -0500 |
| commit | 4af132cf6adeeeeb5d6764c378bec2d05cad042f (patch) | |
| tree | e422cff2831424775ba5c20196064f94cbe1e5c3 /src | |
Migrated from GitHub
Diffstat (limited to 'src')
| -rw-r--r-- | src/beaker_globals.c | 13 | ||||
| -rw-r--r-- | src/beaker_globals.h | 18 | ||||
| -rw-r--r-- | src/http.c | 216 | ||||
| -rw-r--r-- | src/routing.c | 226 | ||||
| -rw-r--r-- | src/server.c | 181 | ||||
| -rw-r--r-- | src/template.c | 961 |
6 files changed, 1615 insertions, 0 deletions
diff --git a/src/beaker_globals.c b/src/beaker_globals.c new file mode 100644 index 0000000..2ea5ab6 --- /dev/null +++ b/src/beaker_globals.c @@ -0,0 +1,13 @@ +#include "beaker_globals.h" + +RouteHandler handlers[MAX_HANDLERS]; + +int handler_count = 0; + +int current_client_socket = -1; + +Cookie cookies_to_set[MAX_COOKIES]; + +int cookies_to_set_count = 0; + +char current_request_buffer[BUFFER_SIZE];
\ No newline at end of file diff --git a/src/beaker_globals.h b/src/beaker_globals.h new file mode 100644 index 0000000..94aabdf --- /dev/null +++ b/src/beaker_globals.h @@ -0,0 +1,18 @@ +#ifndef BEAKER_GLOBALS_H +#define BEAKER_GLOBALS_H + +#include "../beaker.h" + +extern RouteHandler handlers[MAX_HANDLERS]; + +extern int handler_count; + +extern int current_client_socket; + +extern Cookie cookies_to_set[MAX_COOKIES]; + +extern int cookies_to_set_count; + +extern char current_request_buffer[BUFFER_SIZE]; + +#endif
\ No newline at end of file diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..349f66f --- /dev/null +++ b/src/http.c @@ -0,0 +1,216 @@ +#include "../beaker.h" +#include "beaker_globals.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> + +static void build_cookie_headers(char *cookie_headers_buffer, + size_t buffer_size) { + + cookie_headers_buffer[0] = '\0'; + + for (int i = 0; i < cookies_to_set_count; i++) { + 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) { + strcat(single_cookie_header, "; Expires="); + strcat(single_cookie_header, cookies_to_set[i].expires); + } + + if (strlen(cookies_to_set[i].path) > 0) { + strcat(single_cookie_header, "; Path="); + strcat(single_cookie_header, cookies_to_set[i].path); + } + + if (cookies_to_set[i].http_only) { + strcat(single_cookie_header, "; HttpOnly"); + } + + if (cookies_to_set[i].secure) { + strcat(single_cookie_header, "; Secure"); + } + + strcat(single_cookie_header, "\r\n"); + + strncat(cookie_headers_buffer, single_cookie_header, + buffer_size - strlen(cookie_headers_buffer) - 1); + } +} + +void send_response(const char *html) { + + if (current_client_socket == -1) { + fprintf(stderr, "[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]; + + build_cookie_headers(cookie_headers, sizeof(cookie_headers)); + + snprintf(http_response_header, sizeof(http_response_header), + "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", + 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"); + 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"); + return; + } + + cookies_to_set_count = 0; +} + +void send_redirect(const char *location) { + + if (current_client_socket == -1) { + fprintf(stderr, "[ERROR] send_redirect: No client socket set. Cannot send redirect.\n"); + return; + } + + char http_response_header[BUFFER_SIZE]; + + snprintf(http_response_header, sizeof(http_response_header), + "HTTP/1.1 302 Found\r\n" + "Location: %s\r\n" + "Connection: close\r\n" + "\r\n", + 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"); + return; + } + + cookies_to_set_count = 0; +} + +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); + return; + } + + Cookie *new_cookie = &cookies_to_set[cookies_to_set_count]; + + strncpy(new_cookie->name, name, MAX_KEY_LEN - 1); + new_cookie->name[MAX_KEY_LEN - 1] = '\0'; + + strncpy(new_cookie->value, value, MAX_VALUE_LEN - 1); + new_cookie->value[MAX_VALUE_LEN - 1] = '\0'; + + if (expires && strlen(expires) > 0) { + strncpy(new_cookie->expires, expires, MAX_VALUE_LEN - 1); + new_cookie->expires[MAX_VALUE_LEN - 1] = '\0'; + } else { + new_cookie->expires[0] = '\0'; + } + + if (path && strlen(path) > 0) { + strncpy(new_cookie->path, path, MAX_KEY_LEN - 1); + new_cookie->path[MAX_KEY_LEN - 1] = '\0'; + } else { + new_cookie->path[0] = '\0'; + } + + new_cookie->http_only = http_only; + new_cookie->secure = secure; + + cookies_to_set_count++; +} + +char *get_cookie(const char *cookie_name) { + + char *cookie_header_start = strstr(current_request_buffer, "\r\nCookie: "); + if (cookie_header_start == NULL) { + return NULL; + } + + cookie_header_start += strlen("\r\nCookie: "); + + 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)); + } + + 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"); + return NULL; + } + + strncpy(cookie_str, cookie_header_start, cookie_str_len); + cookie_str[cookie_str_len] = '\0'; + + char *cookie_str_copy = strdup(cookie_str); + if (cookie_str_copy == NULL) { + perror("Failed to duplicate cookie string for strtok_r"); + fprintf(stderr, "[ERROR] get_cookie: Duplication failed for cookie_str_copy.\n"); + free(cookie_str); + return NULL; + } + + char *token; + char *saveptr_cookie; + + token = strtok_r(cookie_str_copy, ";", &saveptr_cookie); + while (token != NULL) { + + while (*token == ' ') { + token++; + } + + char *equals_sign = strchr(token, '='); + if (equals_sign != NULL) { + 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"); + } + free(cookie_str); + free(cookie_str_copy); + return cookie_value; + } + } + + token = strtok_r(NULL, ";", &saveptr_cookie); + } + + free(cookie_str); + free(cookie_str_copy); + return NULL; +}
\ No newline at end of file diff --git a/src/routing.c b/src/routing.c new file mode 100644 index 0000000..156e843 --- /dev/null +++ b/src/routing.c @@ -0,0 +1,226 @@ +#include "../beaker.h" +#include "beaker_globals.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +void set_handler(const char *path, RequestHandler handler) { + + if (handler_count < MAX_HANDLERS) { + + strncpy(handlers[handler_count].path, path, MAX_PATH_LEN - 1); + handlers[handler_count].path[MAX_PATH_LEN - 1] = '\0'; + + handlers[handler_count].handler = handler; + + handler_count++; + } else { + + fprintf(stderr, + "[WARNING] set_handler: Maximum number of handlers reached. Cannot register handler for '%s'.\n", + path); + } +} + +char *parse_request_url(const char *request_line, UrlParams *params) { + char method[16]; + char path_with_query_buffer[MAX_PATH_LEN]; + char http_version[16]; + + if (sscanf(request_line, "%15s %255s %15s", method, path_with_query_buffer, + http_version) != 3) { + fprintf(stderr, "[ERROR] parse_request_url: Malformed request line: '%s'\n", + request_line); + return NULL; + } + + params->count = 0; + + char *working_url_copy = strdup(path_with_query_buffer); + if (working_url_copy == NULL) { + perror("Failed to allocate memory for working URL copy"); + return NULL; + } + + char *query_start = strchr(working_url_copy, '?'); + char *path_only_for_return; + + if (query_start != NULL) { + *query_start = '\0'; + path_only_for_return = working_url_copy; + char *query_string = query_start + 1; + + char *token; + char *saveptr_query; + + token = strtok_r(query_string, "&", &saveptr_query); + while (token != NULL && params->count < MAX_URL_PARAMS) { + char *equals_sign = strchr(token, '='); + if (equals_sign != NULL) { + size_t key_len = equals_sign - token; + + strncpy(params->params[params->count].key, token, key_len); + params->params[params->count].key[key_len] = '\0'; + strncpy(params->params[params->count].value, equals_sign + 1, + MAX_VALUE_LEN - 1); + params->params[params->count].value[MAX_VALUE_LEN - 1] = '\0'; + params->count++; + } + token = strtok_r(NULL, "&", &saveptr_query); + } + } else { + + path_only_for_return = working_url_copy; + } + + char *final_requested_path = strdup(path_only_for_return); + if (final_requested_path == NULL) { + perror("Failed to allocate final requested path"); + free(working_url_copy); + return NULL; + } + + char *segment; + int segment_index = 0; + char *saveptr_path; + + segment = strtok_r(working_url_copy, "/", &saveptr_path); + while (segment != NULL) { + + if (segment_index > 0) { + if (params->count < MAX_URL_PARAMS) { + char param_key[MAX_KEY_LEN]; + + snprintf(param_key, sizeof(param_key), "param%d", segment_index - 1); + + strncpy(params->params[params->count].key, param_key, MAX_KEY_LEN - 1); + params->params[params->count].key[MAX_KEY_LEN - 1] = '\0'; + strncpy(params->params[params->count].value, segment, + MAX_VALUE_LEN - 1); + params->params[params->count].value[MAX_VALUE_LEN - 1] = '\0'; + params->count++; + } else { + fprintf(stderr, + "[WARNING] parse_request_url: Max URL parameters reached. Skipping path segment '%s'.\n", + segment); + } + } + segment_index++; + segment = strtok_r(NULL, "/", &saveptr_path); + } + + free(working_url_copy); + + return final_requested_path; +} + +const char *get_mime_type(const char *file_path) { + + const char *ext = strrchr(file_path, '.'); + if (!ext) { + return "application/octet-stream"; + } + ext++; + + if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0) + return "text/html"; + if (strcmp(ext, "css") == 0) + return "text/css"; + if (strcmp(ext, "js") == 0) + return "application/javascript"; + if (strcmp(ext, "json") == 0) + return "application/json"; + if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) + return "image/jpeg"; + if (strcmp(ext, "png") == 0) + return "image/png"; + if (strcmp(ext, "gif") == 0) + return "image/gif"; + if (strcmp(ext, "ico") == 0) + return "image/x-icon"; + if (strcmp(ext, "svg") == 0) + return "image/svg+xml"; + if (strcmp(ext, "pdf") == 0) + return "application/pdf"; + if (strcmp(ext, "txt") == 0) + return "text/plain"; + + return "application/octet-stream"; +} + +bool serve_static_file(const char *request_path_relative_to_static) { + char full_static_path[MAX_PATH_LEN]; + + if (strstr(request_path_relative_to_static, "..") != NULL) { + fprintf(stderr, "[SECURITY] Attempted directory traversal: %s\n", + request_path_relative_to_static); + + const char *forbidden_response = + "HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n"; + send(current_client_socket, forbidden_response, strlen(forbidden_response), 0); + return true; + } + + snprintf(full_static_path, sizeof(full_static_path), "%s%s", STATIC_DIR, + request_path_relative_to_static); + + FILE *fp = fopen(full_static_path, "rb"); + if (fp == NULL) { + fprintf(stderr, + "[ERROR] serve_static_file: 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: fstat failed for '%s'.\n", + full_static_path); + fclose(fp); + + const char *server_error_response = + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n"; + send(current_client_socket, server_error_response, strlen(server_error_response), 0); + return true; + } + + long file_size = st.st_size; + const char *mime_type = get_mime_type(full_static_path); + + char http_header[BUFFER_SIZE]; + + snprintf(http_header, sizeof(http_header), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %ld\r\n" + "Connection: close\r\n" + "\r\n", + 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: Failed to send header for '%s'.\n", full_static_path); + fclose(fp); + return true; + } + + char file_buffer[BUFFER_SIZE]; + size_t bytes_read; + + while ((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: Failed to send content for '%s'.\n", full_static_path); + break; + } + } + + fclose(fp); + return true; +}
\ No newline at end of file diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..e6a460a --- /dev/null +++ b/src/server.c @@ -0,0 +1,181 @@ +#include "../beaker.h" +#include "beaker_globals.h" +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> + +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"); + return -1; + } + + int opt = 1; + if (setsockopt(*server_fd_out, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, + sizeof(opt))) { + perror("setsockopt failed"); + fprintf(stderr, "[ERROR] initialize_server_socket: Failed to set socket options.\n"); + close(*server_fd_out); + return -1; + } + + 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); + 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"); + close(*server_fd_out); + return -1; + } + + printf("Beaker server listening on %s:%d\n", ip, port); + return 0; +} + +static void handle_client_connection(int new_socket) { + current_client_socket = new_socket; + char buffer[BUFFER_SIZE] = {0}; + + 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); + return; + } + buffer[bytes_read] = '\0'; + + strncpy(current_request_buffer, buffer, BUFFER_SIZE - 1); + current_request_buffer[BUFFER_SIZE - 1] = '\0'; + + 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"); + const char *bad_request = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; + send(new_socket, bad_request, strlen(bad_request), 0); + close(new_socket); + 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"); + const char *bad_request = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; + send(new_socket, bad_request, strlen(bad_request), 0); + close(new_socket); + return; + } + strncpy(request_line, buffer, request_line_len); + request_line[request_line_len] = '\0'; + + UrlParams request_params; + char *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"); + const char *bad_request = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; + send(new_socket, bad_request, strlen(bad_request), 0); + close(new_socket); + return; + } + printf("Accessing: %s\n", requested_path); + + bool handled = false; + + if (strncmp(requested_path, "/static/", strlen("/static/")) == 0) { + + if (serve_static_file(requested_path + strlen("/static/"))) { + handled = true; + } + } + + if (!handled) { + 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); + + if (strncmp(requested_path, handlers[i].path, handler_path_len) == 0) { + + if (handler_path_len == strlen(requested_path) || + requested_path[handler_path_len] == '/') { + + if (handler_path_len > best_match_len) { + best_match_len = handler_path_len; + best_match_handler_index = i; + } + } + } + } + + if (best_match_handler_index != -1) { + handlers[best_match_handler_index].handler(&request_params); + 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 = "<h1>404 Not Found</h1><p>The requested URL " + "was not located on this server.</p>"; + char not_found_response[BUFFER_SIZE]; + snprintf(not_found_response, sizeof(not_found_response), + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n%s", + strlen(not_found_html), not_found_html); + send(new_socket, not_found_response, strlen(not_found_response), 0); + } + + free(requested_path); + close(new_socket); + current_client_socket = -1; +} + +int beaker_run(const char *ip, int port) { + int server_fd; + struct sockaddr_in address; + int addrlen = sizeof(address); + + if (initialize_server_socket(ip, port, &server_fd, &address) != 0) { + return -1; + } + + while (true) { + int new_socket; + + if ((new_socket = accept(server_fd, (struct sockaddr *)&address, + (socklen_t *)&addrlen)) < 0) { + perror("accept failed"); + fprintf(stderr, "[ERROR] beaker_run: Failed to accept connection.\n"); + continue; + } + + handle_client_connection(new_socket); + } + + close(server_fd); + return 0; +}
\ No newline at end of file diff --git a/src/template.c b/src/template.c new file mode 100644 index 0000000..494b011 --- /dev/null +++ b/src/template.c @@ -0,0 +1,961 @@ +#include "../beaker.h" +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static char *render_template_segment(const char *template_segment, + TemplateContext *ctx); + +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"); + return NULL; + } + for (int i = 0; i < ctx->count; i++) { + if (strcmp(ctx->vars[i].key, key) == 0) { + return &ctx->vars[i]; + } + } + return NULL; +} + +static void free_string_array_var(ContextVar *var) { + if (var == NULL || var->value.string_array_data.values == NULL) { + return; + } + for (int i = 0; i < var->value.string_array_data.count; i++) { + if (var->value.string_array_data.values[i] != NULL) { + free(var->value.string_array_data.values[i]); + var->value.string_array_data.values[i] = NULL; + } + } + free(var->value.string_array_data.values); + var->value.string_array_data.values = NULL; + var->value.string_array_data.count = 0; +} + +static void free_string_2d_array_var(ContextVar *var) { + if (var == NULL || var->value.string_2d_array_data.values == NULL) { + return; + } + for (int i = 0; i < var->value.string_2d_array_data.outer_count; i++) { + if (var->value.string_2d_array_data.values[i] != NULL) { + for (int j = 0; j < var->value.string_2d_array_data.inner_counts[i]; + j++) { + if (var->value.string_2d_array_data.values[i][j] != NULL) { + free(var->value.string_2d_array_data.values[i][j]); + var->value.string_2d_array_data.values[i][j] = NULL; + } + } + free(var->value.string_2d_array_data.values[i]); + var->value.string_2d_array_data.values[i] = NULL; + } + } + free(var->value.string_2d_array_data.values); + var->value.string_2d_array_data.values = NULL; + + if (var->value.string_2d_array_data.inner_counts != NULL) { + free(var->value.string_2d_array_data.inner_counts); + var->value.string_2d_array_data.inner_counts = NULL; + } + var->value.string_2d_array_data.outer_count = 0; +} + +TemplateContext new_context() { + TemplateContext ctx; + ctx.count = 0; + return ctx; +} + +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"); + return; + } + + ContextVar *var = find_context_var(ctx, key); + if (var != NULL) { + + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(var); + } else if (var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(var); + } + + strncpy(var->value.string_val, value, MAX_VALUE_LEN - 1); + var->value.string_val[MAX_VALUE_LEN - 1] = '\0'; + var->type = CONTEXT_TYPE_STRING; + return; + } + + if (ctx->count < MAX_CONTEXT_VARS) { + ContextVar *new_var = &ctx->vars[ctx->count]; + strncpy(new_var->key, key, MAX_KEY_LEN - 1); + new_var->key[MAX_KEY_LEN - 1] = '\0'; + strncpy(new_var->value.string_val, value, MAX_VALUE_LEN - 1); + new_var->value.string_val[MAX_VALUE_LEN - 1] = '\0'; + new_var->type = CONTEXT_TYPE_STRING; + ctx->count++; + } else { + fprintf(stderr, + "[WARNING] 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"); + 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); + return; + } + + ContextVar *var = find_context_var(ctx, key); + if (var != NULL) { + + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(var); + } else if (var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(var); + } + } else { + + if (ctx->count < MAX_CONTEXT_VARS) { + var = &ctx->vars[ctx->count]; + strncpy(var->key, key, MAX_KEY_LEN - 1); + 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); + return; + } + } + + 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); + return; + } + var->value.string_array_data.count = count; + + for (int i = 0; i < count; i++) { + if (values[i] == NULL) { + fprintf(stderr, + "[ERROR] context_set_string_array: NULL value at index %d for " + "key '%s'. Skipping.\n", + i, key); + var->value.string_array_data.values[i] = NULL; + continue; + } + 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); + + for (int j = 0; j < i; j++) { + if (var->value.string_array_data.values[j] != NULL) { + free(var->value.string_array_data.values[j]); + } + } + free(var->value.string_array_data.values); + var->value.string_array_data.values = NULL; + var->value.string_array_data.count = 0; + return; + } + } +} + +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"); + 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); + return; + } + + ContextVar *var = find_context_var(ctx, key); + if (var != NULL) { + + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(var); + } else if (var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(var); + } + } else { + + if (ctx->count < MAX_CONTEXT_VARS) { + var = &ctx->vars[ctx->count]; + strncpy(var->key, key, MAX_KEY_LEN - 1); + 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); + return; + } + } + + var->type = CONTEXT_TYPE_STRING_2D_ARRAY; + var->value.string_2d_array_data.values = + (char ***)malloc(sizeof(char **) * outer_count); + var->value.string_2d_array_data.inner_counts = + (int *)malloc(sizeof(int) * outer_count); + + 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); + 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; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + var->value.string_2d_array_data.outer_count = outer_count; + + for (int i = 0; i < outer_count; i++) { + 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); + + for (int k = 0; k < i; k++) { + if (var->value.string_2d_array_data.values[k] != NULL) { + for (int l = 0; l < var->value.string_2d_array_data.inner_counts[k]; + l++) { + if (var->value.string_2d_array_data.values[k][l] != NULL) { + free(var->value.string_2d_array_data.values[k][l]); + } + } + free(var->value.string_2d_array_data.values[k]); + } + } + 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; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + + 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); + + for (int k = 0; k < i; k++) { + if (var->value.string_2d_array_data.values[k] != NULL) { + for (int l = 0; l < var->value.string_2d_array_data.inner_counts[k]; + l++) { + if (var->value.string_2d_array_data.values[k][l] != NULL) { + free(var->value.string_2d_array_data.values[k][l]); + } + } + free(var->value.string_2d_array_data.values[k]); + } + } + 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; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + var->value.string_2d_array_data.inner_counts[i] = current_inner_count; + + for (int j = 0; j < current_inner_count; j++) { + if (values_2d[i][j] == NULL) { + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: NULL value at index " + "[%d][%d] for key '%s'. Skipping.\n", + i, j, key); + var->value.string_2d_array_data.values[i][j] = NULL; + continue; + } + 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); + + for (int k = 0; k <= i; k++) { + if (var->value.string_2d_array_data.values[k] != NULL) { + for (int l = 0; + l < + (k == i ? j : var->value.string_2d_array_data.inner_counts[k]); + l++) { + if (var->value.string_2d_array_data.values[k][l] != NULL) { + free(var->value.string_2d_array_data.values[k][l]); + } + } + free(var->value.string_2d_array_data.values[k]); + } + } + 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; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + } + } +} + +void free_context(TemplateContext *ctx) { + if (ctx == NULL) { + return; + } + for (int i = 0; i < ctx->count; i++) { + if (ctx->vars[i].type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(&ctx->vars[i]); + } else if (ctx->vars[i].type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(&ctx->vars[i]); + } + } + ctx->count = 0; +} + +static char *html_escape(const char *input) { + if (input == NULL) { + return strdup(""); + } + + size_t input_len = strlen(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"); + return NULL; + } + output[0] = '\0'; + size_t current_output_len = 0; + + for (size_t i = 0; i < input_len; i++) { + const char *replacement = NULL; + + switch (input[i]) { + case '&': + replacement = "&"; + break; + case '<': + replacement = "<"; + break; + case '>': + replacement = ">"; + break; + case '"': + replacement = """; + break; + case '\'': + replacement = "'"; + break; + default: + break; + } + + if (replacement) { + size_t repl_len = strlen(replacement); + + if (current_output_len + repl_len + 1 > estimated_len) { + 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"); + free(output); + return NULL; + } + output = new_output; + } + strcat(output, replacement); + current_output_len += repl_len; + } else { + + if (current_output_len + 1 + 1 > estimated_len) { + 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"); + free(output); + return NULL; + } + output = new_output; + } + output[current_output_len++] = input[i]; + output[current_output_len] = '\0'; + } + } + return output; +} + +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"); + return; + } + + size_t add_len = strlen(str_to_add); + + if (*current_len + add_len + 1 > *max_len) { + *max_len = (*current_len + add_len + 1) * 2; + + if (*max_len < BUFFER_SIZE * 2) { + *max_len = BUFFER_SIZE * 2; + } + + 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); + free(*buffer); + *buffer = NULL; + + exit(EXIT_FAILURE); + } + *buffer = new_buffer; + } + + strcat(*buffer, str_to_add); + *current_len += add_len; +} + +static char *parse_indexed_tag(const char *tag_content, int *index_val) { + *index_val = -1; + + char *open_bracket = strchr(tag_content, '['); + if (open_bracket == NULL) { + + 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); + } + return key_name; + } + + 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); + + char *key_name = strdup(tag_content); + if (key_name == NULL) { + perror("Failed to duplicate malformed tag content"); + } + return key_name; + } + + 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"); + return NULL; + } + strncpy(key_name, tag_content, key_len); + key_name[key_len] = '\0'; + + 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"); + free(key_name); + return NULL; + } + strncpy(index_str, open_bracket + 1, index_str_len); + index_str[index_str_len] = '\0'; + + *index_val = atoi(index_str); + free(index_str); + + return key_name; +} + +static char *render_template_segment(const char *template_segment, + TemplateContext *ctx) { + 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); + return NULL; + } + rendered_buffer[0] = '\0'; + size_t current_len = 0; + size_t max_len = initial_max_len; + + const char *current_pos = template_segment; + const char *start_tag; + + while ((start_tag = strstr(current_pos, "{{")) != NULL) { + + size_t text_len = start_tag - current_pos; + 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"); + free(rendered_buffer); + return NULL; + } + strncpy(text_before_tag, current_pos, text_len); + text_before_tag[text_len] = '\0'; + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + text_before_tag); + free(text_before_tag); + } + + 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"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, start_tag); + return rendered_buffer; + } + + 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"); + free(rendered_buffer); + return NULL; + } + strncpy(tag_content_raw, start_tag + 2, tag_content_len); + tag_content_raw[tag_content_len] = '\0'; + + char *trimmed_tag_content = tag_content_raw; + while (*trimmed_tag_content != '\0' && + (*trimmed_tag_content == ' ' || *trimmed_tag_content == '\t' || + *trimmed_tag_content == '\n' || *trimmed_tag_content == '\r')) { + trimmed_tag_content++; + } + + if (*trimmed_tag_content == '\0') { + free(tag_content_raw); + current_pos = end_tag + 2; + continue; + } else { + char *end_trimmed = trimmed_tag_content + strlen(trimmed_tag_content) - 1; + while (end_trimmed >= trimmed_tag_content && + (*end_trimmed == ' ' || *end_trimmed == '\t' || + *end_trimmed == '\n' || *end_trimmed == '\r')) { + } + *(end_trimmed + 1) = '\0'; + } + + if (strncmp(trimmed_tag_content, "for ", 4) == 0) { + char loop_var[MAX_KEY_LEN]; + char list_var[MAX_KEY_LEN]; + if (sscanf(trimmed_tag_content, "for %s in %s", loop_var, list_var) == + 2) { + ContextVar *list_ctx_var = find_context_var(ctx, list_var); + + const char *loop_end_tag = strstr(end_tag + 2, "{{endfor}}"); + if (loop_end_tag == NULL) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + free(tag_content_raw); + current_pos = end_tag + 2; + continue; + } + + if (list_ctx_var != NULL) { + + bool is_empty = false; + if (list_ctx_var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + is_empty = + (list_ctx_var->value.string_2d_array_data.outer_count == 0); + } else if (list_ctx_var->type == CONTEXT_TYPE_STRING_ARRAY) { + is_empty = (list_ctx_var->value.string_array_data.count == 0); + } else { + + is_empty = true; + } + + if (is_empty) { + current_pos = loop_end_tag + strlen("{{endfor}}"); + free(tag_content_raw); + continue; + } + + const char *loop_inner_start = end_tag + 2; + 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"); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + strncpy(loop_inner_template, loop_inner_start, loop_inner_len); + loop_inner_template[loop_inner_len] = '\0'; + + if (list_ctx_var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + for (int i = 0; + i < list_ctx_var->value.string_2d_array_data.outer_count; + i++) { + TemplateContext loop_ctx = new_context(); + + for (int j = 0; j < ctx->count; j++) { + if (ctx->vars[j].type == CONTEXT_TYPE_STRING) { + context_set(&loop_ctx, ctx->vars[j].key, + ctx->vars[j].value.string_val); + } + } + + context_set_string_array( + &loop_ctx, loop_var, + list_ctx_var->value.string_2d_array_data.values[i], + list_ctx_var->value.string_2d_array_data.inner_counts[i]); + + char *rendered_loop_item = + render_template_segment(loop_inner_template, &loop_ctx); + if (rendered_loop_item) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + rendered_loop_item); + free(rendered_loop_item); + } + free_context(&loop_ctx); + } + } else if (list_ctx_var->type == CONTEXT_TYPE_STRING_ARRAY) { + for (int i = 0; i < list_ctx_var->value.string_array_data.count; + i++) { + TemplateContext loop_ctx = new_context(); + + for (int j = 0; j < ctx->count; j++) { + if (ctx->vars[j].type == CONTEXT_TYPE_STRING) { + context_set(&loop_ctx, ctx->vars[j].key, + ctx->vars[j].value.string_val); + } + } + + context_set(&loop_ctx, loop_var, + list_ctx_var->value.string_array_data.values[i]); + + char *rendered_loop_item = + render_template_segment(loop_inner_template, &loop_ctx); + if (rendered_loop_item) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + rendered_loop_item); + free(rendered_loop_item); + } + free_context(&loop_ctx); + } + } else { + fprintf( + stderr, + "[IGNORE] [ERROR] render_template_segment: List variable '%s' " + "(type %d) is not an iterable array type for 'for' loop.\n", + list_var, list_ctx_var->type); + } + free(loop_inner_template); + current_pos = loop_end_tag + strlen("{{endfor}}"); + free(tag_content_raw); + continue; + } else { + + fprintf(stderr, + "[IGNORE] [ERROR] 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); + } + } + + else if (strcmp(trimmed_tag_content, "endfor") == 0) { + fprintf(stderr, "[WARNING] render_template_segment: '{{endfor}}' without " + "matching '{{for}}'. Appending endfor tag as-is.\n"); + } + + else if (strncmp(trimmed_tag_content, "include ", 8) == 0) { + const char *filename_start = trimmed_tag_content + strlen("include "); + if (*filename_start == '"') { + filename_start++; + char *filename_end = strchr(filename_start, '"'); + if (filename_end != NULL) { + 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"); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + strncpy(included_filename, filename_start, filename_len); + included_filename[filename_len] = '\0'; + + char *included_html = render_template(included_filename, ctx); + if (included_html) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + included_html); + free(included_html); + } else { + fprintf(stderr, + "[WARNING] render_template_segment: Failed to render " + "included template '%s'.\n", + included_filename); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + "<!-- Failed to include "); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + included_filename); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, " -->"); + } + free(included_filename); + current_pos = end_tag + 2; + free(tag_content_raw); + continue; + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Malformed include tag " + "'%s'. Missing closing quote.\n", + trimmed_tag_content); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "{{"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "}}"); + free(tag_content_raw); + + current_pos = end_tag + 2; + + continue; + + } + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Malformed include tag '%s'. " + "Expected quoted filename.\n", + trimmed_tag_content); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "{{"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "}}"); + } + } + + else { + bool is_safe = false; + char *processing_tag_content = strdup(trimmed_tag_content); + if (processing_tag_content == NULL) { + perror("Failed to duplicate tag content for processing"); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + + char *safe_flag_pos_in_processing = + strstr(processing_tag_content, "|safe"); + if (safe_flag_pos_in_processing != NULL) { + is_safe = true; + *safe_flag_pos_in_processing = '\0'; + } + + int index_val = -1; + char *key_name = parse_indexed_tag(processing_tag_content, &index_val); + free(processing_tag_content); + + if (key_name == NULL) { + fprintf(stderr, + "[ERROR] render_template_segment: parse_indexed_tag failed for " + "'%s'.\n", + trimmed_tag_content); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + + const char *value_to_append = NULL; + ContextVar *var = find_context_var(ctx, key_name); + + if (var != NULL) { + if (index_val == -1) { + if (var->type == CONTEXT_TYPE_STRING) { + value_to_append = var->value.string_val; + } else { + fprintf( + stderr, + "[WARNING] render_template_segment: Variable '%s' is not a " + "simple string (type %d). Not rendering as simple string.\n", + key_name, var->type); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "{{"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "}}"); + } + } else { + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + if (index_val >= 0 && + index_val < var->value.string_array_data.count) { + value_to_append = var->value.string_array_data.values[index_val]; + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Index %d out of bounds " + "for '%s' (count %d). Appending tag as-is.\n", + index_val, key_name, var->value.string_array_data.count); + } + } else { + fprintf( + stderr, + "[ERROR] render_template_segment: Variable '%s' (type %d) is " + "not a string array for indexed access. Appending tag as-is.\n", + key_name, var->type); + } + } + } else { + fprintf(stderr, + "[WARNING] render_template_segment: Key '%s' not found in " + "context. Appending tag as-is.\n", + key_name); + } + + if (value_to_append != NULL) { + if (is_safe) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + value_to_append); + } else { + char *escaped_value = html_escape(value_to_append); + if (escaped_value) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + escaped_value); + free(escaped_value); + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Failed to HTML escape " + "value for key '%s'. Appending raw.\n", + key_name); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + value_to_append); + } + } + } + + free(key_name); + + free(tag_content_raw); + + current_pos = end_tag + 2; + + continue; + + } + + free(tag_content_raw); + current_pos = end_tag + 2; + } + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, current_pos); + return rendered_buffer; +} + +char *render_template(const char *template_file, TemplateContext *ctx) { + char full_path[MAX_PATH_LEN]; + + snprintf(full_path, sizeof(full_path), "%s%s", TEMPLATES_DIR, template_file); + + FILE *fp = fopen(full_path, "r"); + if (fp == NULL) { + perror("Error opening template file"); + fprintf(stderr, + "[ERROR] render_template: Could not open template '%s'. %s\n", + full_path, strerror(errno)); + return NULL; + } + + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + char *template_content = (char *)malloc(file_size + 1); + if (template_content == NULL) { + perror("Error allocating memory for template content"); + fprintf(stderr, + "[ERROR] render_template: Failed to allocate %ld bytes for " + "template content.\n", + file_size + 1); + fclose(fp); + return NULL; + } + fread(template_content, 1, file_size, fp); + template_content[file_size] = '\0'; + fclose(fp); + + char *rendered_html = render_template_segment(template_content, ctx); + free(template_content); + return rendered_html; +}
\ No newline at end of file |
