diff options
| author | frosty <gabriel@bwaaa.monster> | 2026-08-16 17:55:19 -0400 |
|---|---|---|
| committer | frosty <gabriel@bwaaa.monster> | 2026-08-16 17:55:19 -0400 |
| commit | acdd9e754a536d542099951e4c9a6acc8cdf1c51 (patch) | |
| tree | 0a1d4333d2b160a8243fe2c35425268cc2690ac9 /src | |
| parent | 916d3497b9316ec6b64646d456d3c03141b1fe21 (diff) | |
| download | beaker-acdd9e754a536d542099951e4c9a6acc8cdf1c51.tar.gz | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/beaker_globals.h | 6 | ||||
| -rw-r--r-- | src/http.c | 163 | ||||
| -rw-r--r-- | src/routing.c | 357 | ||||
| -rw-r--r-- | src/server.c | 130 |
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; @@ -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 = ¶ms->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) { |
