aboutsummaryrefslogtreecommitdiff
path: root/src/routing.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/routing.c')
-rw-r--r--src/routing.c373
1 files changed, 193 insertions, 180 deletions
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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include "../beaker.h"
+#include "beaker_globals.h"
#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
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 *path_copy = strdup(path);
+ if (!path_copy) {
+ return -1;
+ }
- char *token = strtok(path_copy, "/");
- while (token) {
- if (strcmp(token, ".") == 0) {
+ char *token = strtok(path_copy, "/");
+ while (token) {
+ if (strcmp(token, ".") == 0) {
- } else if (strcmp(token, "..") == 0) {
+ } else if (strcmp(token, "..") == 0) {
- if (component_count > 0) {
- component_count--;
- } else {
+ if (component_count > 0) {
+ component_count--;
+ } else {
- fprintf(stderr, "[SECURITY] Path traversal attempt: %s\n", path);
- free(path_copy);
- return -1;
- }
- } else if (strlen(token) > 0) {
+ 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, "/");
+ 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;
+ size_t i = 0, j = 0;
- while (src[i] && j < dst_size - 1) {
- if (src[i] == '%') {
+ 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 (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 (*endptr != '\0') {
+ beaker_log("SECURITY", "Invalid URL encoding: %%%s\n", hex);
+ return -1;
+ }
- if (value == 0) {
- fprintf(stderr, "[SECURITY] Null byte in URL encoding\n");
- return -1;
- }
+ if (value == 0) {
+ beaker_log("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++] = (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++] = ' ';
+ 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];
+ 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;
- }
+ 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;