aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-08-16 17:55:19 -0400
committerfrosty <gabriel@bwaaa.monster>2026-08-16 17:55:19 -0400
commitacdd9e754a536d542099951e4c9a6acc8cdf1c51 (patch)
tree0a1d4333d2b160a8243fe2c35425268cc2690ac9
parent916d3497b9316ec6b64646d456d3c03141b1fe21 (diff)
downloadbeaker-master.tar.gz
fix: harden HTTP request and response parsingHEADmasterindev
-rw-r--r--src/beaker_globals.h6
-rw-r--r--src/http.c163
-rw-r--r--src/routing.c357
-rw-r--r--src/server.c130
4 files changed, 467 insertions, 189 deletions
diff --git a/src/beaker_globals.h b/src/beaker_globals.h
index e01704c..a69bc43 100644
--- a/src/beaker_globals.h
+++ b/src/beaker_globals.h
@@ -61,6 +61,12 @@ int beaker_send_all(int socket, const void *buffer, size_t length);
void beaker_reset_write_deadline(void);
+bool beaker_is_valid_http_token(const char *value);
+
+bool beaker_is_valid_http_token_span(const char *value, size_t length);
+
+bool beaker_is_valid_header_value(const char *value);
+
extern Locale *locales;
extern int locale_count;
diff --git a/src/http.c b/src/http.c
index bfd6f9b..974e761 100644
--- a/src/http.c
+++ b/src/http.c
@@ -6,17 +6,109 @@
#include <sys/socket.h>
#include <unistd.h>
+static bool is_http_token_char(unsigned char c) {
+ if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z')) {
+ return true;
+ }
+
+ return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
+ c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
+ c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
+}
+
+bool beaker_is_valid_http_token(const char *value) {
+ if (value == NULL) {
+ return false;
+ }
+
+ return beaker_is_valid_http_token_span(value, strlen(value));
+}
+
+bool beaker_is_valid_http_token_span(const char *value, size_t length) {
+ if (value == NULL || length == 0)
+ return false;
+
+ for (size_t i = 0; i < length; i++) {
+ if (!is_http_token_char((unsigned char)value[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool beaker_is_valid_header_value(const char *value) {
+ if (value == NULL) {
+ return false;
+ }
+
+ for (size_t i = 0; value[i] != '\0'; i++) {
+ unsigned char c = (unsigned char)value[i];
+ if (c < 0x20 || c == 0x7f) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool is_valid_cookie_value(const char *value) {
+ if (value == NULL) {
+ return false;
+ }
+
+ for (size_t i = 0; value[i] != '\0'; i++) {
+ unsigned char c = (unsigned char)value[i];
+ if (!(c == 0x21 || (c >= 0x23 && c <= 0x2b) || (c >= 0x2d && c <= 0x3a) ||
+ (c >= 0x3c && c <= 0x5b) || (c >= 0x5d && c <= 0x7e))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool is_valid_cookie_attribute(const char *value) {
+ return value == NULL ||
+ (beaker_is_valid_header_value(value) && strchr(value, ';') == NULL);
+}
+
+static bool parse_status_line(const char *status_line, int *status_code) {
+ if (status_line == NULL || status_code == NULL)
+ return false;
+
+ size_t status_line_len = strlen(status_line);
+ if (status_line_len < 5 || status_line_len >= 128 || status_line[0] < '0' ||
+ status_line[0] > '9' || status_line[1] < '0' || status_line[1] > '9' ||
+ status_line[2] < '0' || status_line[2] > '9' || status_line[3] != ' ' ||
+ status_line[4] == '\0' || !beaker_is_valid_header_value(status_line)) {
+ return false;
+ }
+
+ int code = (status_line[0] - '0') * 100 + (status_line[1] - '0') * 10 +
+ (status_line[2] - '0');
+ if (code < 100 || code > 599) {
+ return false;
+ }
+
+ *status_code = code;
+ return true;
+}
+
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];
+ char single_cookie_header[MAX_KEY_LEN * 2 + MAX_VALUE_LEN * 2 + 128];
- snprintf(single_cookie_header, sizeof(single_cookie_header),
- "Set-Cookie: %s=%s", cookies_to_set[i].name,
- cookies_to_set[i].value);
+ int header_length = snprintf(
+ single_cookie_header, sizeof(single_cookie_header), "Set-Cookie: %s=%s",
+ cookies_to_set[i].name, cookies_to_set[i].value);
+ if (header_length < 0 ||
+ (size_t)header_length >= sizeof(single_cookie_header)) {
+ beaker_log("ERROR", "build_cookie_headers: Cookie is too large.");
+ continue;
+ }
if (strlen(cookies_to_set[i].expires) > 0) {
strncat(single_cookie_header, "; Expires=",
@@ -70,15 +162,26 @@ void send_status(const char *status_line) {
return;
}
+ int status_code;
+ if (!parse_status_line(status_line, &status_code)) {
+ beaker_log("SECURITY", "send_status: Rejected invalid HTTP status line.");
+ status_line = "500 Internal Server Error";
+ status_code = 500;
+ }
+
char http_response[BUFFER_SIZE];
- current_response_status = atoi(status_line);
+ current_response_status = status_code;
current_response_size = 0;
- snprintf(http_response, sizeof(http_response),
- "HTTP/1.1 %s\r\n"
- "Content-Length: 0\r\n"
- "Connection: close\r\n"
- "\r\n",
- status_line);
+ int response_length = snprintf(http_response, sizeof(http_response),
+ "HTTP/1.1 %s\r\n"
+ "Content-Length: 0\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ status_line);
+ if (response_length < 0 || (size_t)response_length >= sizeof(http_response)) {
+ beaker_log("ERROR", "send_status: Failed to construct HTTP status.");
+ return;
+ }
if (beaker_send_all(current_client_socket, http_response,
strlen(http_response)) < 0) {
@@ -135,6 +238,14 @@ void send_redirect(const char *location) {
return;
}
+ if (location == NULL || location[0] == '\0' ||
+ !beaker_is_valid_header_value(location)) {
+ beaker_log("SECURITY", "send_redirect: Rejected invalid Location value.");
+ beaker_clear_response_cookies();
+ send_status("500 Internal Server Error");
+ return;
+ }
+
char http_response_header[BUFFER_SIZE * 2];
current_response_status = 302;
current_response_size = 0;
@@ -143,13 +254,20 @@ void send_redirect(const char *location) {
build_cookie_headers(cookie_headers, sizeof(cookie_headers));
beaker_clear_response_cookies();
- snprintf(http_response_header, sizeof(http_response_header),
- "HTTP/1.1 302 Found\r\n"
- "Location: %s\r\n"
- "%s"
- "Connection: close\r\n"
- "\r\n",
- location, cookie_headers);
+ int header_length =
+ snprintf(http_response_header, sizeof(http_response_header),
+ "HTTP/1.1 302 Found\r\n"
+ "Location: %s\r\n"
+ "%s"
+ "Connection: close\r\n"
+ "\r\n",
+ location, cookie_headers);
+ if (header_length < 0 ||
+ (size_t)header_length >= sizeof(http_response_header)) {
+ beaker_log("SECURITY", "send_redirect: Location value is too long.");
+ send_status("500 Internal Server Error");
+ return;
+ }
if (beaker_send_all(current_client_socket, http_response_header,
strlen(http_response_header)) < 0) {
@@ -162,6 +280,15 @@ void send_redirect(const char *location) {
void set_cookie(const char *name, const char *value, const char *expires,
const char *path, bool http_only, bool secure) {
+ if (!beaker_is_valid_http_token(name) || !is_valid_cookie_value(value) ||
+ !is_valid_cookie_attribute(expires) || !is_valid_cookie_attribute(path) ||
+ strlen(name) >= MAX_KEY_LEN || strlen(value) >= MAX_VALUE_LEN ||
+ (expires != NULL && strlen(expires) >= MAX_VALUE_LEN) ||
+ (path != NULL && strlen(path) >= MAX_KEY_LEN)) {
+ beaker_log("SECURITY", "set_cookie: Rejected invalid cookie data.");
+ return;
+ }
+
if (cookies_to_set_count >= MAX_COOKIES) {
beaker_log("WARN",
"set_cookie: Maximum number of cookies to set reached. Cannot "
diff --git a/src/routing.c b/src/routing.c
index d5f8285..c0348aa 100644
--- a/src/routing.c
+++ b/src/routing.c
@@ -61,197 +61,233 @@ const char *get_mime_type(const char *file_path) {
return "application/octet-stream";
}
+static int hex_value(unsigned char c) {
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+static int url_decode(char *dst, size_t dst_size, const char *src,
+ size_t src_len, bool plus_as_space) {
+ size_t src_index = 0;
+ size_t dst_index = 0;
+
+ if (dst == NULL || dst_size == 0 || src == NULL)
+ return -1;
+
+ while (src_index < src_len) {
+ unsigned char decoded;
+ if (src[src_index] == '%') {
+ if (src_index + 2 >= src_len) {
+ beaker_log("SECURITY", "url_decode: Incomplete percent encoding.");
+ return -1;
+ }
+ int high = hex_value((unsigned char)src[src_index + 1]);
+ int low = hex_value((unsigned char)src[src_index + 2]);
+ if (high < 0 || low < 0) {
+ beaker_log("SECURITY", "url_decode: Invalid percent encoding.");
+ return -1;
+ }
+ decoded = (unsigned char)((high << 4) | low);
+ src_index += 3;
+ } else {
+ decoded = (unsigned char)src[src_index++];
+ if (plus_as_space && decoded == '+')
+ decoded = ' ';
+ }
+
+ if (decoded == 0 || decoded < 0x20 || decoded == 0x7f) {
+ beaker_log("SECURITY", "url_decode: Rejected control character.");
+ return -1;
+ }
+ if (dst_index + 1 >= dst_size) {
+ beaker_log("SECURITY", "url_decode: Decoded value is too long.");
+ return -1;
+ }
+ dst[dst_index++] = (char)decoded;
+ }
+
+ dst[dst_index] = '\0';
+ return 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) {
+ if (canonical == NULL || path == NULL || max_len < 2 || path[0] != '/') {
return -1;
}
- char *token = strtok(path_copy, "/");
- while (token) {
- if (strcmp(token, ".") == 0) {
+ size_t output_len = 1;
+ canonical[0] = '/';
+ canonical[1] = '\0';
- } else if (strcmp(token, "..") == 0) {
+ const char *cursor = path + 1;
+ while (*cursor != '\0') {
+ while (*cursor == '/')
+ cursor++;
+ if (*cursor == '\0')
+ break;
- if (component_count > 0) {
- component_count--;
- } else {
+ const char *component = cursor;
+ while (*cursor != '\0' && *cursor != '/')
+ cursor++;
+ size_t component_len = (size_t)(cursor - component);
- beaker_log("SECURITY", "Path traversal attempt: %s\n", path);
- free(path_copy);
+ if (component_len == 1 && component[0] == '.') {
+ continue;
+ }
+ if (component_len == 2 && component[0] == '.' && component[1] == '.') {
+ if (output_len == 1) {
+ beaker_log("SECURITY", "canonicalize_path: Path escapes root.");
return -1;
}
- } else if (strlen(token) > 0) {
+ while (output_len > 1 && canonical[output_len - 1] != '/')
+ output_len--;
+ if (output_len > 1)
+ output_len--;
+ canonical[output_len] = '\0';
+ continue;
+ }
- 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++;
+ for (size_t i = 0; i < component_len; i++) {
+ unsigned char c = (unsigned char)component[i];
+ if (c == '\\' || c < 0x20 || c == 0x7f) {
+ beaker_log("SECURITY",
+ "canonicalize_path: Rejected unsafe path character.");
+ return -1;
}
}
- token = strtok(NULL, "/");
- }
-
- free(path_copy);
- 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");
+ size_t separator_len = output_len > 1 ? 1 : 0;
+ if (output_len + separator_len + component_len >= max_len) {
+ beaker_log("SECURITY", "canonicalize_path: Path is too long.");
return -1;
}
- strcat(canonical, "/");
- strcat(canonical, components[i]);
- }
-
- if (canonical[0] == '\0') {
- strcpy(canonical, "/");
+ if (separator_len != 0)
+ canonical[output_len++] = '/';
+ memcpy(canonical + output_len, component, component_len);
+ output_len += component_len;
+ canonical[output_len] = '\0';
}
return 0;
}
-static bool is_safe_path_component(const char *component) {
+static int extract_request_target(const char *request_line, char *target,
+ size_t target_size) {
+ if (request_line == NULL || target == NULL || target_size == 0)
+ return -1;
- if (strstr(component, "..") || strstr(component, "//")) {
- return false;
+ const char *first_space = strchr(request_line, ' ');
+ if (first_space == NULL || first_space == request_line ||
+ first_space[1] == ' ')
+ return -1;
+ const char *second_space = strchr(first_space + 1, ' ');
+ if (second_space == NULL || second_space == first_space + 1 ||
+ second_space[1] == '\0' || strchr(second_space + 1, ' ') != NULL ||
+ strchr(request_line, '\t') != NULL) {
+ return -1;
}
- for (size_t i = 0; component[i]; i++) {
- if (component[i] < 32 && component[i] != '\t') {
- return false;
- }
+ size_t method_len = (size_t)(first_space - request_line);
+ if (method_len >= 16)
+ return -1;
+ char method[16];
+ memcpy(method, request_line, method_len);
+ method[method_len] = '\0';
+ if (!beaker_is_valid_http_token(method))
+ return -1;
+
+ size_t version_len = strlen(second_space + 1);
+ if (version_len == 0 || version_len >= 16 ||
+ !beaker_is_valid_header_value(second_space + 1)) {
+ return -1;
}
- return true;
+ size_t target_len = (size_t)(second_space - (first_space + 1));
+ if (target_len >= target_size)
+ return -1;
+ memcpy(target, first_space + 1, target_len);
+ target[target_len] = '\0';
+ return 0;
}
-static int url_decode(char *dst, const char *src, size_t dst_size) {
- size_t i = 0, j = 0;
-
- while (src[i] && j < dst_size - 1) {
- if (src[i] == '%') {
+static int parse_query_params(const char *query, UrlParams *params) {
+ const char *cursor = query;
+ while (*cursor != '\0') {
+ const char *pair_end = strchr(cursor, '&');
+ if (pair_end == NULL)
+ pair_end = cursor + strlen(cursor);
- if (src[i + 1] && src[i + 2]) {
- char hex[3] = {src[i + 1], src[i + 2], '\0'};
- char *endptr;
- long value = strtol(hex, &endptr, 16);
-
- if (*endptr != '\0') {
- beaker_log("SECURITY", "Invalid URL encoding: %%%s\n", hex);
- return -1;
- }
-
- if (value == 0) {
- beaker_log("SECURITY", "Null byte in URL encoding\n");
- return -1;
- }
-
- dst[j++] = (char)value;
- i += 3;
- } else {
- beaker_log("SECURITY", "Incomplete URL encoding at end\n");
+ const char *equals = memchr(cursor, '=', (size_t)(pair_end - cursor));
+ if (equals != NULL) {
+ if (params->count >= MAX_URL_PARAMS) {
+ beaker_log("SECURITY", "parse_query_params: Too many parameters.");
return -1;
}
- } else if (src[i] == '+') {
- dst[j++] = ' ';
- i++;
- } else {
- dst[j++] = src[i++];
+ UrlParam *param = &params->params[params->count];
+ if (url_decode(param->key, sizeof(param->key), cursor,
+ (size_t)(equals - cursor), true) != 0 ||
+ url_decode(param->value, sizeof(param->value), equals + 1,
+ (size_t)(pair_end - (equals + 1)), true) != 0) {
+ return -1;
+ }
+ params->count++;
}
- }
- dst[j] = '\0';
+ if (*pair_end == '\0')
+ break;
+ cursor = pair_end + 1;
+ }
return 0;
}
char *parse_request_url(const char *request_line, UrlParams *params) {
- char method[16];
- char raw_url_full[MAX_PATH_LEN];
- char http_version[16];
-
- if (sscanf(request_line, "%15s %255s %15s", method, raw_url_full,
- http_version) != 3) {
- beaker_log("ERROR", "parse_request_url: Malformed request line\n");
+ if (params == NULL)
return NULL;
- }
-
params->count = 0;
- char *working_raw = strdup(raw_url_full);
- if (!working_raw) {
- beaker_log_errno("Failed to allocate memory for URL copy");
+ char raw_target[MAX_PATH_LEN];
+ if (extract_request_target(request_line, raw_target, sizeof(raw_target)) !=
+ 0) {
+ beaker_log("ERROR", "parse_request_url: Malformed request line.");
return NULL;
}
- char *query_start = strchr(working_raw, '?');
- if (query_start) {
- *query_start = '\0';
+ char *query_start = strchr(raw_target, '?');
+ size_t raw_path_len = query_start == NULL
+ ? strlen(raw_target)
+ : (size_t)(query_start - raw_target);
+ if (raw_path_len == 0 || raw_target[0] != '/') {
+ beaker_log("SECURITY", "parse_request_url: Invalid request target.");
+ return NULL;
}
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);
+ if (url_decode(decoded_path, sizeof(decoded_path), raw_target, raw_path_len,
+ false) != 0) {
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 *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);
- }
-
- if (query_start) {
- char *query_string = query_start + 1;
-
- 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';
-
- 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 (query_start != NULL && parse_query_params(query_start + 1, params) != 0) {
+ params->count = 0;
+ return NULL;
}
char *final_path = strdup(canonical_path);
- free(working_raw);
+ if (final_path == NULL)
+ beaker_log_errno("parse_request_url: Failed to allocate result path");
return final_path;
}
@@ -305,15 +341,31 @@ bool serve_static_file_with_mime(const char *request_path_relative_to_static,
mime_type = get_mime_type(full_static_path);
}
+ if (!beaker_is_valid_header_value(mime_type) ||
+ strlen(mime_type) >= MAX_VALUE_LEN) {
+ beaker_log("SECURITY",
+ "serve_static_file_with_mime: Rejected invalid MIME type.");
+ fclose(fp);
+ send_status("500 Internal Server Error");
+ return true;
+ }
+
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);
+ int header_length = 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 (header_length < 0 || (size_t)header_length >= sizeof(http_header)) {
+ beaker_log("ERROR",
+ "serve_static_file_with_mime: Failed to construct header.");
+ fclose(fp);
+ send_status("500 Internal Server Error");
+ return true;
+ }
if (beaker_send_all(current_client_socket, http_header, strlen(http_header)) <
0) {
@@ -369,17 +421,30 @@ bool serve_data(const char *data, size_t size, const char *mime_type) {
return false;
}
+ if (mime_type == NULL || mime_type[0] == '\0' ||
+ !beaker_is_valid_header_value(mime_type) ||
+ strlen(mime_type) >= MAX_VALUE_LEN) {
+ beaker_log("SECURITY", "serve_data: Rejected invalid MIME type.");
+ send_status("500 Internal Server Error");
+ 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"
- "Content-Type: %s\r\n"
- "Content-Length: %zu\r\n"
- "Connection: close\r\n"
- "\r\n",
- mime_type, size);
+ int header_length = snprintf(http_header, sizeof(http_header),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ mime_type, size);
+ if (header_length < 0 || (size_t)header_length >= sizeof(http_header)) {
+ beaker_log("ERROR", "serve_data: Failed to construct header.");
+ send_status("500 Internal Server Error");
+ return false;
+ }
if (beaker_send_all(current_client_socket, http_header, strlen(http_header)) <
0) {
diff --git a/src/server.c b/src/server.c
index 7d5eb8f..33fa54a 100644
--- a/src/server.c
+++ b/src/server.c
@@ -21,6 +21,8 @@
static volatile sig_atomic_t g_shutdown_requested = 0;
+static bool validate_request_headers(const char *request_buffer);
+
static void signal_handler(int sig) {
(void)sig;
g_shutdown_requested = 1;
@@ -280,6 +282,14 @@ void handle_client_connection(int new_socket) {
&started_at);
return;
}
+ if (!validate_request_headers(buffer)) {
+ beaker_log("SECURITY",
+ "handle_client_connection: Rejected malformed headers.");
+ send_status("400 Bad Request");
+ finish_client_connection(new_socket, requested_path, method, log_path,
+ &started_at);
+ return;
+ }
size_t request_line_len = first_line_end - buffer;
if (request_line_len >= sizeof(request_line)) {
beaker_log("ERROR", "handle_client_connection: Request line too long.\n");
@@ -454,42 +464,112 @@ const char *beaker_get_remote_addr(void) {
static __thread char g_header_value[MAX_VALUE_LEN];
+static bool is_valid_request_header_value(const char *start, const char *end) {
+ for (const char *cursor = start; cursor < end; cursor++) {
+ unsigned char c = (unsigned char)*cursor;
+ if ((c < 0x20 && c != '\t') || c == 0x7f)
+ return false;
+ }
+ return true;
+}
+
+static bool validate_request_headers(const char *request_buffer) {
+ if (request_buffer == NULL)
+ return false;
+
+ const char *request_line_end = strstr(request_buffer, "\r\n");
+ if (request_line_end == NULL)
+ return false;
+
+ const char *cursor = request_line_end + 2;
+ int host_count = 0;
+ while (true) {
+ const char *line_end = strstr(cursor, "\r\n");
+ if (line_end == NULL)
+ return false;
+ if (line_end == cursor)
+ return host_count <= 1;
+ if (*cursor == ' ' || *cursor == '\t')
+ return false;
+
+ const char *colon = memchr(cursor, ':', (size_t)(line_end - cursor));
+ if (colon == NULL || colon == cursor)
+ return false;
+
+ size_t name_len = (size_t)(colon - cursor);
+ if (!beaker_is_valid_http_token_span(cursor, name_len) ||
+ !is_valid_request_header_value(colon + 1, line_end)) {
+ return false;
+ }
+
+ if (name_len == strlen("Host") &&
+ strncasecmp(cursor, "Host", name_len) == 0 && ++host_count > 1) {
+ return false;
+ }
+ cursor = line_end + 2;
+ }
+}
+
const char *beaker_get_header(const char *name) {
- if (name == NULL)
- return "";
+ g_header_value[0] = '\0';
+ if (!beaker_is_valid_http_token(name))
+ return g_header_value;
+
+ size_t requested_name_len = strlen(name);
+ const char *request_line_end = strstr(current_request_buffer, "\r\n");
+ if (request_line_end == NULL)
+ return g_header_value;
+
+ const char *cursor = request_line_end + 2;
+ bool found = false;
+ while (true) {
+ const char *line_end = strstr(cursor, "\r\n");
+ if (line_end == NULL || line_end == cursor)
+ break;
+ if (*cursor == ' ' || *cursor == '\t')
+ return g_header_value;
- size_t name_len = strlen(name);
- if (name_len == 0)
- return "";
+ const char *colon = memchr(cursor, ':', (size_t)(line_end - cursor));
+ if (colon == NULL || colon == cursor)
+ return g_header_value;
- if (strstr(name, "\r\n") != NULL)
- return "";
+ size_t header_name_len = (size_t)(colon - cursor);
+ if (header_name_len == requested_name_len &&
+ strncasecmp(cursor, name, requested_name_len) == 0) {
+ if (found) {
+ beaker_log("SECURITY",
+ "beaker_get_header: Rejected duplicate header value.");
+ g_header_value[0] = '\0';
+ return g_header_value;
+ }
- char *buffer = current_request_buffer;
- char *search_end = strstr(buffer, "\r\n\r\n");
- if (search_end == NULL)
- search_end = buffer + strlen(buffer);
- while (buffer < search_end) {
- if (strncasecmp(buffer, name, name_len) == 0 && buffer[name_len] == ':') {
- char *value_start = buffer + name_len + 1;
- while (*value_start == ' ')
+ const char *value_start = colon + 1;
+ while (value_start < line_end &&
+ (*value_start == ' ' || *value_start == '\t')) {
value_start++;
+ }
+ const char *value_end = line_end;
+ while (value_end > value_start &&
+ (value_end[-1] == ' ' || value_end[-1] == '\t')) {
+ value_end--;
+ }
- char *value_end = strstr(value_start, "\r\n");
- if (value_end == NULL)
- value_end = search_end;
- size_t value_len = value_end - value_start;
- if (value_len >= sizeof(g_header_value)) {
- value_len = sizeof(g_header_value) - 1;
+ size_t value_len = (size_t)(value_end - value_start);
+ if (value_len >= sizeof(g_header_value) ||
+ !is_valid_request_header_value(value_start, value_end)) {
+ beaker_log("SECURITY",
+ "beaker_get_header: Rejected invalid header value.");
+ g_header_value[0] = '\0';
+ return g_header_value;
}
- strncpy(g_header_value, value_start, value_len);
+ memcpy(g_header_value, value_start, value_len);
g_header_value[value_len] = '\0';
- return g_header_value;
+ found = true;
}
- buffer++;
+ cursor = line_end + 2;
}
- return "";
+ return g_header_value;
}
void beaker_set_request_buffer(const char *buffer) {