#include "../beaker.h" #include "beaker_globals.h" #include #include #include #include #include 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) { 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) { 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; const 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; } 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); 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; } typedef enum { CONDITION_NONE, CONDITION_TRUTHY, CONDITION_EXISTS, CONDITION_EQUAL, CONDITION_NOT_EQUAL, } ConditionType; typedef struct { ConditionType type; char var_name[MAX_KEY_LEN]; char compare_value[MAX_VALUE_LEN]; int compare_index; bool negate; } Condition; static bool evaluate_condition(const Condition *cond, TemplateContext *ctx) { if (cond == NULL || cond->type == CONDITION_NONE) { return false; } ContextVar *var = find_context_var(ctx, cond->var_name); const char *var_value = NULL; if (var == NULL) { if (cond->type == CONDITION_TRUTHY) { return false; } var_value = ""; } else if (var->type == CONTEXT_TYPE_STRING) { var_value = var->value.string_val; } else { if (cond->type == CONDITION_TRUTHY) { return false; } var_value = ""; } switch (cond->type) { case CONDITION_TRUTHY: return (var_value != NULL && strlen(var_value) > 0); case CONDITION_EXISTS: { bool exists = (var != NULL); return cond->negate ? !exists : exists; } case CONDITION_EQUAL: { const char *compare_value = cond->compare_value; char compare_buf[MAX_VALUE_LEN]; 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 && cond->compare_index >= 0 && cond->compare_index < compare_var->value.string_array_data.count) { compare_value = compare_var->value.string_array_data .values[cond->compare_index]; } } else if (compare_var != NULL && compare_var->type == CONTEXT_TYPE_STRING) { strncpy(compare_buf, compare_var->value.string_val, MAX_VALUE_LEN - 1); compare_buf[MAX_VALUE_LEN - 1] = '\0'; compare_value = compare_buf; } return (strcmp(var_value, compare_value) == 0); } case CONDITION_NOT_EQUAL: { const char *compare_value = cond->compare_value; char compare_buf[MAX_VALUE_LEN]; 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 && cond->compare_index >= 0 && cond->compare_index < compare_var->value.string_array_data.count) { compare_value = compare_var->value.string_array_data .values[cond->compare_index]; } } else if (compare_var != NULL && compare_var->type == CONTEXT_TYPE_STRING) { strncpy(compare_buf, compare_var->value.string_val, MAX_VALUE_LEN - 1); compare_buf[MAX_VALUE_LEN - 1] = '\0'; compare_value = compare_buf; } return (strcmp(var_value, compare_value) != 0); } default: return false; } } static Condition parse_condition(const char *condition_str) { Condition cond; cond.type = CONDITION_NONE; cond.var_name[0] = '\0'; cond.compare_value[0] = '\0'; cond.compare_index = -1; cond.negate = false; if (condition_str == NULL || *condition_str == '\0') { return cond; } const char *eq_pos = strstr(condition_str, "=="); const char *neq_pos = strstr(condition_str, "!="); if (eq_pos != NULL && neq_pos != NULL) { if (eq_pos < neq_pos) { neq_pos = NULL; } else { eq_pos = NULL; } } if (eq_pos != NULL) { size_t var_len = eq_pos - condition_str; if (var_len >= MAX_KEY_LEN) { var_len = MAX_KEY_LEN - 1; } strncpy(cond.var_name, condition_str, var_len); cond.var_name[var_len] = '\0'; const char *value_start = eq_pos + 2; while (*value_start == ' ' || *value_start == '\t') { value_start++; } if (*value_start == '"') { value_start++; const char *value_end = strchr(value_start, '"'); if (value_end != NULL) { size_t val_len = value_end - value_start; if (val_len >= MAX_VALUE_LEN) { val_len = MAX_VALUE_LEN - 1; } strncpy(cond.compare_value, value_start, val_len); cond.compare_value[val_len] = '\0'; } } else { size_t val_len = strlen(value_start); if (val_len >= MAX_VALUE_LEN) { val_len = MAX_VALUE_LEN - 1; } strncpy(cond.compare_value, value_start, val_len); cond.compare_value[val_len] = '\0'; } cond.type = CONDITION_EQUAL; } else if (neq_pos != NULL) { size_t var_len = neq_pos - condition_str; if (var_len >= MAX_KEY_LEN) { var_len = MAX_KEY_LEN - 1; } strncpy(cond.var_name, condition_str, var_len); cond.var_name[var_len] = '\0'; const char *value_start = neq_pos + 2; while (*value_start == ' ' || *value_start == '\t') { value_start++; } if (*value_start == '"') { value_start++; const char *value_end = strchr(value_start, '"'); if (value_end != NULL) { size_t val_len = value_end - value_start; if (val_len >= MAX_VALUE_LEN) { val_len = MAX_VALUE_LEN - 1; } strncpy(cond.compare_value, value_start, val_len); cond.compare_value[val_len] = '\0'; } } else { size_t val_len = strlen(value_start); if (val_len >= MAX_VALUE_LEN) { val_len = MAX_VALUE_LEN - 1; } strncpy(cond.compare_value, value_start, val_len); cond.compare_value[val_len] = '\0'; } cond.type = CONDITION_NOT_EQUAL; } else if (strncmp(condition_str, "exists ", 7) == 0) { const char *var_start = condition_str + 7; while (*var_start == ' ' || *var_start == '\t') { var_start++; } size_t var_len = strlen(var_start); if (var_len >= MAX_KEY_LEN) { var_len = MAX_KEY_LEN - 1; } strncpy(cond.var_name, var_start, var_len); cond.var_name[var_len] = '\0'; cond.type = CONDITION_EXISTS; } else if (strncmp(condition_str, "not exists ", 11) == 0) { const char *var_start = condition_str + 11; while (*var_start == ' ' || *var_start == '\t') { var_start++; } size_t var_len = strlen(var_start); if (var_len >= MAX_KEY_LEN) { var_len = MAX_KEY_LEN - 1; } strncpy(cond.var_name, var_start, var_len); cond.var_name[var_len] = '\0'; cond.type = CONDITION_EXISTS; cond.negate = true; } else { size_t var_len = strlen(condition_str); if (var_len >= MAX_KEY_LEN) { var_len = MAX_KEY_LEN - 1; } strncpy(cond.var_name, condition_str, var_len); cond.var_name[var_len] = '\0'; cond.type = CONDITION_TRUTHY; } char *p = cond.var_name; char *end = cond.var_name + strlen(cond.var_name) - 1; while (p < end && (*end == ' ' || *end == '\t')) { *end = '\0'; end--; } char *bracket = strchr(cond.compare_value, '['); if (bracket != NULL) { char *close_bracket = strchr(bracket, ']'); if (close_bracket != NULL) { *bracket = '\0'; cond.compare_index = atoi(bracket + 1); } } return cond; } 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, "if ", 3) == 0) { const char *condition_str = trimmed_tag_content + 3; Condition cond = parse_condition(condition_str); const char *else_tag = NULL; const char *elif_tag = NULL; const char *endif_tag = NULL; int if_depth = 1; const char *search_pos = end_tag + 2; while (*search_pos != '\0' && if_depth > 0) { const char *next_if = strstr(search_pos, "{{if "); const char *next_else = strstr(search_pos, "{{else}}"); const char *next_elif = strstr(search_pos, "{{elif "); const char *next_endif = strstr(search_pos, "{{endif}}"); 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) { earliest = candidates[i]; earliest_idx = i; } } } if (earliest == NULL) { break; } if (earliest_idx == 0) { if_depth++; search_pos = earliest + strlen("{{if "); continue; } else if (earliest_idx == 1) { else_tag = earliest; search_pos = earliest + strlen("{{else}}"); continue; } else if (earliest_idx == 2) { if (elif_tag == NULL) { elif_tag = earliest; } search_pos = earliest + strlen("{{elif "); const char *elif_end = strstr(search_pos, "}}"); if (elif_end != NULL) { search_pos = elif_end + 2; } continue; } else { if_depth--; if (if_depth == 0) { endif_tag = earliest; } search_pos = earliest + strlen("{{endif}}"); continue; } } if (endif_tag == NULL) { fprintf(stderr, "[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"); current_pos = end_tag + 2; free(tag_content_raw); continue; } bool condition_met = evaluate_condition(&cond, ctx); const char *block_start = end_tag + 2; const char *block_end = NULL; if (condition_met) { if (elif_tag != NULL && elif_tag < endif_tag) { block_end = elif_tag; } else if (else_tag != NULL) { block_end = else_tag; } else { block_end = endif_tag; } } else { bool elif_matched = false; if (elif_tag != NULL && elif_tag < endif_tag) { const char *test_elif = elif_tag; while (test_elif != NULL && test_elif < endif_tag) { const char *elif_cond_start = test_elif + strlen("{{elif "); const char *elif_cond_end = strstr(elif_cond_start, "}}"); if (elif_cond_end == NULL || elif_cond_end >= endif_tag) { break; } size_t elif_cond_len = elif_cond_end - elif_cond_start; char *elif_cond_str = (char *)malloc(elif_cond_len + 1); if (elif_cond_str != NULL) { strncpy(elif_cond_str, elif_cond_start, elif_cond_len); elif_cond_str[elif_cond_len] = '\0'; Condition elif_cond = parse_condition(elif_cond_str); if (evaluate_condition(&elif_cond, ctx)) { block_start = test_elif + strlen("{{elif "); const char *elif_end = strstr(block_start, "}}"); if (elif_end != NULL) { block_start = elif_end + 2; } block_end = else_tag ? else_tag : endif_tag; elif_matched = true; free(elif_cond_str); break; } free(elif_cond_str); } test_elif = strstr(test_elif + strlen("{{elif "), "{{elif "); if (test_elif == NULL || test_elif >= endif_tag) { break; } } } if (!elif_matched) { if (else_tag && else_tag < endif_tag) { block_start = else_tag + strlen("{{else}}"); block_end = endif_tag; } else { block_end = endif_tag; block_start = endif_tag; } } } size_t block_len = block_end - block_start; if (block_len > 0) { char *block_template = (char *)malloc(block_len + 1); if (block_template != NULL) { strncpy(block_template, block_start, block_len); block_template[block_len] = '\0'; char *rendered_block = render_template_segment(block_template, ctx); if (rendered_block != NULL) { append_to_buffer(&rendered_buffer, ¤t_len, &max_len, rendered_block); free(rendered_block); } free(block_template); } } current_pos = endif_tag + strlen("{{endif}}"); free(tag_content_raw); continue; } 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"); } else if (strcmp(trimmed_tag_content, "else") == 0) { fprintf(stderr, "[WARNING] 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"); } else if (strncmp(trimmed_tag_content, "include ", 8) == 0) { const char *filename_start = trimmed_tag_content + strlen("include "); if (*filename_start == '"') { filename_start++; const 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'; if (strstr(included_filename, "..") != NULL || strchr(included_filename, '/') != NULL) { fprintf(stderr, "[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, ""); current_pos = end_tag + 2; free(tag_content_raw); continue; } 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, ""); } 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 if (strncmp(trimmed_tag_content, "l(\"", 3) == 0) { const char *key_start = trimmed_tag_content + 3; const char *key_end = strstr(key_start, "\")"); if (key_end == NULL) { fprintf(stderr, "[ERROR] render_template_segment: Malformed l() tag '%s'. " "Expected l(\"key\"). Appending as-is.\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; } size_t key_len = key_end - key_start; char l10n_key[MAX_KEY_LEN]; if (key_len >= MAX_KEY_LEN) key_len = MAX_KEY_LEN - 1; strncpy(l10n_key, key_start, key_len); l10n_key[key_len] = '\0'; bool is_safe = false; const char *after_close = key_end + 2; if (strncmp(after_close, "|safe", 5) == 0) { is_safe = true; } const char *value_to_append = NULL; ContextVar *locale_var = find_context_var(ctx, "__locale"); if (locale_var != NULL && locale_var->type == CONTEXT_TYPE_STRING) { const char *current_locale_id = locale_var->value.string_val; for (int i = 0; i < locale_count; i++) { if (strcmp(locales[i].meta.id, current_locale_id) == 0) { for (int j = 0; j < locales[i].key_count; j++) { if (strcmp(locales[i].keys[j].key, l10n_key) == 0) { value_to_append = locales[i].keys[j].value; break; } } break; } } } if (value_to_append != NULL) { if (is_safe) { append_to_buffer(&rendered_buffer, ¤t_len, &max_len, value_to_append); } else { char *escaped = html_escape(value_to_append); if (escaped) { append_to_buffer(&rendered_buffer, ¤t_len, &max_len, escaped); free(escaped); } else { append_to_buffer(&rendered_buffer, ¤t_len, &max_len, value_to_append); } } } else { fprintf(stderr, "[WARNING] render_template_segment: l10n key '%s' not found " "for current locale. Appending key as-is.\n", l10n_key); append_to_buffer(&rendered_buffer, ¤t_len, &max_len, l10n_key); } free(tag_content_raw); current_pos = end_tag + 2; continue; } 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]; if (strstr(template_file, "..") != NULL) { fprintf(stderr, "[SECURITY] render_template: Path traversal attempt: %s\n", template_file); return NULL; } 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); if (file_size < 0) { perror("Error getting template file size"); fprintf(stderr, "[ERROR] render_template: Failed to get size of '%s'.\n", full_path); fclose(fp); return NULL; } fseek(fp, 0, SEEK_SET); if (file_size == 0) { char *empty_result = render_template_segment("", ctx); fclose(fp); return empty_result; } char *template_content = (char *)malloc((size_t)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; } size_t bytes_read = fread(template_content, 1, (size_t)file_size, fp); if (bytes_read != (size_t)file_size) { perror("Error reading template file"); fprintf(stderr, "[ERROR] render_template: Failed to read complete template '%s'.\n", full_path); free(template_content); fclose(fp); return NULL; } template_content[file_size] = '\0'; fclose(fp); char *rendered_html = render_template_segment(template_content, ctx); free(template_content); return rendered_html; }