From bcbd71bdd0e7b13bb22cf7bb864fa724a063974e Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 30 May 2026 16:36:45 -0400 Subject: [PATCH] HTTP: Add functions to validate HTTP field names and values These functions are not currently used. They will be used in the future, though. There will be multiple changes depending on this. Making this a separate PR allows those changes to be merged independently. These functions are strict and reject anything that does not conform to RFC9110. Signed-off-by: Demi Marie Obenour --- src/http/ngx_http.c | 78 +++++++++++++++++++++++++++++++++++++++++++++ src/http/ngx_http.h | 7 ++++ 2 files changed, 85 insertions(+) diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index a97cc35f1..3ab0b6b68 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -67,6 +67,8 @@ static ngx_int_t ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, static ngx_int_t ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, ngx_http_conf_addr_t *addr); #endif +static ngx_int_t ngx_http_valid_field_name_internal(const ngx_str_t *name, + ngx_int_t allow_uppercase); ngx_uint_t ngx_http_max_module; @@ -2198,3 +2200,79 @@ ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types, return NGX_OK; } + + +ngx_int_t +ngx_http_valid_lowercase_field_name(const ngx_str_t *name) +{ + return ngx_http_valid_field_name_internal(name, 0); +} + + +ngx_int_t +ngx_http_valid_field_name(const ngx_str_t *name) +{ + return ngx_http_valid_field_name_internal(name, 1); +} + + +static ngx_int_t +ngx_http_valid_field_name_internal(const ngx_str_t *name, + ngx_int_t allow_uppercase) +{ + /* Check that the string is not empty. */ + if (name->len == 0) + return NGX_ERROR; + + /* Check that the characters are valid for an HTTP TOKEN */ + for (size_t i = 0; i < name->len; ++i) { + u_char c = name->data[i]; + + /* + * Only allow uppercase letters if the caller asked for it. + * HTTP/2 and HTTP/3 don't allow them. + */ + if (allow_uppercase && 'A' <= c && c <= 'Z') + continue; + + if ('^' <= c && c <= 'z') + continue; + + if ('0' <= c && c <= '9') + continue; + + if ('#' <= c && c <= '\'') + continue; + + if (c == '!' || c == '+' || c == '+' || c == '-' || c == '.' || c == '|' + || c == '~') + continue; + + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_valid_field_value(const ngx_str_t *value) +{ + /* Field values may be empty. */ + if (value->len == 0) + return NGX_OK; + + /* Leading and trailing space and tab are not allowed. */ + if (value->data[0] <= ' ' || value->data[value->len - 1] <= ' ') + return NGX_ERROR; + + /* Check that the characters are valid for an HTTP field value. */ + for (size_t i = 0; i < value->len; ++i) { + u_char c = value->data[i]; + + if (c < 0x20 ? c != '\t' : c == '\x7F') + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 1b0e369f3..003462e3a 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -122,6 +122,13 @@ void ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, ngx_int_t ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, ngx_http_chunked_t *ctx, ngx_uint_t keep_trailers); +/* Check if name is a valid HTTP field name (token). */ +ngx_int_t ngx_http_valid_field_name(const ngx_str_t *name); +/* Check if name is a valid HTTP field name (token) + * without uppercase ASCII letters. */ +ngx_int_t ngx_http_valid_lowercase_field_name(const ngx_str_t *name); +/* Check if value is a valid HTTP field value. */ +ngx_int_t ngx_http_valid_field_value(const ngx_str_t *value); ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c); ngx_int_t ngx_http_process_request_uri(ngx_http_request_t *r);