diff --git a/auto/modules b/auto/modules index 572eebe6e..ab1e4af23 100644 --- a/auto/modules +++ b/auto/modules @@ -103,7 +103,7 @@ if [ $HTTP = YES ]; then if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then - HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS" + HTTP_SRCS="$HTTP_SRCS $HTTP_V23_COMMON_SRCS" fi diff --git a/auto/sources b/auto/sources index 46408ee53..a5c173fb9 100644 --- a/auto/sources +++ b/auto/sources @@ -257,5 +257,6 @@ NGX_WIN32_RC="src/os/win32/nginx.rc" HTTP_FILE_CACHE_SRCS=src/http/ngx_http_file_cache.c -HTTP_HUFF_SRCS="src/http/ngx_http_huff_decode.c - src/http/ngx_http_huff_encode.c" +HTTP_V23_COMMON_SRCS="src/http/ngx_http_huff_decode.c \ + src/http/ngx_http_huff_encode.c \ + src/http/ngx_http_v23_common.c" diff --git a/src/http/modules/ngx_http_proxy_v2_module.c b/src/http/modules/ngx_http_proxy_v2_module.c index 7cff58d3b..39d52e190 100644 --- a/src/http/modules/ngx_http_proxy_v2_module.c +++ b/src/http/modules/ngx_http_proxy_v2_module.c @@ -132,10 +132,6 @@ static ngx_int_t ngx_http_proxy_v2_parse_header(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); -static ngx_int_t ngx_http_proxy_v2_validate_header_name(ngx_http_request_t *r, - ngx_str_t *s); -static ngx_int_t ngx_http_proxy_v2_validate_header_value(ngx_http_request_t *r, - ngx_str_t *s); static ngx_int_t ngx_http_proxy_v2_parse_rst_stream(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); static ngx_int_t ngx_http_proxy_v2_parse_goaway(ngx_http_request_t *r, @@ -3208,29 +3204,7 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, ctx->value = *ngx_http_v2_get_static_value(ctx->index); } - if (!ctx->index) { - if (ngx_http_proxy_v2_validate_header_name(r, &ctx->name) - != NGX_OK) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream sent invalid header: \"%V: %V\"", - &ctx->name, &ctx->value); - return NGX_ERROR; - } - } - - if (!ctx->index || ctx->literal) { - if (ngx_http_proxy_v2_validate_header_value(r, &ctx->value) - != NGX_OK) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream sent invalid header: \"%V: %V\"", - &ctx->name, &ctx->value); - return NGX_ERROR; - } - } - - return NGX_OK; + return ngx_http_v23_validate_header(r, &ctx->name, &ctx->value, 0); } ctx->rest -= p - b->pos; @@ -3245,50 +3219,6 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, } -static ngx_int_t -ngx_http_proxy_v2_validate_header_name(ngx_http_request_t *r, ngx_str_t *s) -{ - u_char ch; - ngx_uint_t i; - - for (i = 0; i < s->len; i++) { - ch = s->data[i]; - - if (ch == ':' && i > 0) { - return NGX_ERROR; - } - - if (ch >= 'A' && ch <= 'Z') { - return NGX_ERROR; - } - - if (ch <= 0x20 || ch == 0x7f) { - return NGX_ERROR; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_proxy_v2_validate_header_value(ngx_http_request_t *r, ngx_str_t *s) -{ - u_char ch; - ngx_uint_t i; - - for (i = 0; i < s->len; i++) { - ch = s->data[i]; - - if (ch == '\0' || ch == CR || ch == LF) { - return NGX_ERROR; - } - } - - return NGX_OK; -} - - static ngx_int_t ngx_http_proxy_v2_parse_rst_stream(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 4e4511cc5..d971a815a 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -184,6 +184,15 @@ ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log); size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower); +ngx_int_t ngx_http_v23_validate_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value, ngx_int_t is_client); +/* + * Parse an HTTP/2 and/or HTTP/3 method. + */ +ngx_int_t ngx_http_v23_parse_method(ngx_http_request_t *r, + ngx_str_t *value); +ngx_int_t ngx_http_v23_parse_scheme(ngx_http_request_t *r, + ngx_str_t *value); #endif diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 4e1c67282..30db88e57 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -266,6 +266,29 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_srv_conf_t, ignore_invalid_headers), NULL }, + { ngx_string("reject_leading_trailing_whitespace"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_core_srv_conf_t, reject_leading_trailing_whitespace), + NULL }, + + { ngx_string("reject_leading_trailing_whitespace_client"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_core_srv_conf_t, + reject_leading_trailing_whitespace_client), + NULL }, + + { ngx_string("reject_leading_trailing_whitespace_upstream"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_core_srv_conf_t, + reject_leading_trailing_whitespace_upstream), + NULL }, + { ngx_string("merge_slashes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -3547,6 +3570,9 @@ ngx_http_core_create_srv_conf(ngx_conf_t *cf) cscf->ignore_invalid_headers = NGX_CONF_UNSET; cscf->merge_slashes = NGX_CONF_UNSET; cscf->underscores_in_headers = NGX_CONF_UNSET; + cscf->reject_leading_trailing_whitespace = NGX_CONF_UNSET; + cscf->reject_leading_trailing_whitespace_client = NGX_CONF_UNSET; + cscf->reject_leading_trailing_whitespace_upstream = NGX_CONF_UNSET; cscf->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; @@ -3595,6 +3621,22 @@ ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->underscores_in_headers, prev->underscores_in_headers, 0); + if (conf->reject_leading_trailing_whitespace_client == NGX_CONF_UNSET) { + conf->reject_leading_trailing_whitespace_client = ( + conf->reject_leading_trailing_whitespace != NGX_CONF_UNSET ? + conf->reject_leading_trailing_whitespace : + (prev->reject_leading_trailing_whitespace_client != NGX_CONF_UNSET ? + prev->reject_leading_trailing_whitespace_client : 0)); + } + + if (conf->reject_leading_trailing_whitespace_upstream == NGX_CONF_UNSET) { + conf->reject_leading_trailing_whitespace_upstream = ( + conf->reject_leading_trailing_whitespace != NGX_CONF_UNSET ? + conf->reject_leading_trailing_whitespace : + (prev->reject_leading_trailing_whitespace_upstream != NGX_CONF_UNSET ? + prev->reject_leading_trailing_whitespace_upstream : 0)); + } + if (conf->server_names.nelts == 0) { /* the array has 4 empty preallocated elements, so push cannot fail */ sn = ngx_array_push(&conf->server_names); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index a13d7ade5..905a80150 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -212,6 +212,9 @@ typedef struct { unsigned allow_connect:1; ngx_http_core_loc_conf_t **named_locations; + ngx_flag_t reject_leading_trailing_whitespace; + ngx_flag_t reject_leading_trailing_whitespace_client; + ngx_flag_t reject_leading_trailing_whitespace_upstream; } ngx_http_core_srv_conf_t; diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index ac10f561a..a2483e481 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -1027,6 +1027,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case sw_space_before_value: switch (ch) { case ' ': + case '\t': break; case CR: r->header_start = p; @@ -1051,6 +1052,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case sw_value: switch (ch) { case ' ': + case '\t': r->header_end = p; state = sw_space_after_value; break; @@ -1071,6 +1073,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case sw_space_after_value: switch (ch) { case ' ': + case '\t': break; case CR: state = sw_almost_done; @@ -1145,6 +1148,150 @@ header_done: } +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + +ngx_int_t +ngx_http_v23_parse_method(ngx_http_request_t *r, ngx_str_t *value) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE }, + { 7, "CONNECT", NGX_HTTP_CONNECT } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = value->len; + r->method_name.data = value->data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v23_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) +{ + u_char c, ch; + ngx_uint_t i; + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :scheme header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :scheme header"); + + return NGX_DECLINED; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :scheme header: \"%V\"", value); + + return NGX_DECLINED; + } + + r->schema = *value; + + return NGX_OK; +} + + ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r) { diff --git a/src/http/ngx_http_v23_common.c b/src/http/ngx_http_v23_common.c new file mode 100644 index 000000000..81d167949 --- /dev/null +++ b/src/http/ngx_http_v23_common.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2026 Demi Marie Obenour + */ + + +#include +#include +#include + + +static inline ngx_int_t +ngx_isspace(u_char ch); + + +ngx_int_t +ngx_http_v23_validate_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value, ngx_int_t is_client) +{ + u_char ch; + ngx_str_t tmp; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + if (is_client) { + r->invalid_header = 0; + } + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (name->len < 1) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "%s sent empty header name", + is_client ? "client" : "upstream"); + + return NGX_ERROR; + } + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if (is_client + && ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers))) + { + continue; + } + + if (ch <= 0x20 || ch == 0x7f || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "%s sent invalid header name: \"%V\"", + is_client ? "client" : "upstream", name); + + return NGX_ERROR; + } + + if (is_client) { + r->invalid_header = 1; + } + } + + /* Keep subsequent code from having to special-case empty strings. */ + if (value->len == 0) { + return NGX_OK; + } + + for (i = 0; i != value->len; i++) { + ch = value->data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "%s sent header \"%V\" with " + "invalid value: \"%V\"", + is_client ? "client" : "upstream", + name, value); + + return NGX_ERROR; + } + } + + if (!ngx_isspace(value->data[0]) + && !ngx_isspace(value->data[value->len - 1])) { + /* Fast path: nothing to strip. */ + return NGX_OK; + } + + if (is_client ? cscf->reject_leading_trailing_whitespace_client + : cscf->reject_leading_trailing_whitespace_upstream) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "%s sent header \"%V\" with " + "leading or trailing space", + is_client ? "client" : "upstream", name); + + return NGX_ERROR; + } + + tmp = *value; + + /* + * Strip trailing whitespace. Do this first so that + * if the string is all whitespace, tmp.data is not a + * past-the-end pointer, which cannot be safely passed + * to memmove(). After the loop, the string is either + * empty or ends with a non-whitespace character. + */ + while (tmp.len && ngx_isspace(tmp.data[tmp.len - 1])) { + tmp.len--; + } + + /* Strip leading whitespace */ + if (tmp.len && ngx_isspace(tmp.data[0])) { + /* + * Last loop guaranteed that 'tmp' does not end with whitespace, and + * this check guarantees it is not empty and starts with whitespace. + * Therefore, 'tmp' must end with a non-whitespace character, and must + * be of length at least 2. This means that it is safe to keep going + * until a non-whitespace character is found. + */ + do { + tmp.len--; + tmp.data++; + } while (ngx_isspace(tmp.data[0])); + + /* Move remaining string to start of buffer. */ + memmove(value->data, tmp.data, tmp.len); + } + + /* + * NUL-pad the data, so that if it was NUL-terminated before, it stil is. + * At least one byte will have been stripped, so value->data + tmp.len + * is not a past-the-end pointer. + */ + memset(value->data + tmp.len, '\0', value->len - tmp.len); + + /* Fix up length and return. */ + value->len = tmp.len; + return NGX_OK; +} + + +static inline ngx_int_t +ngx_isspace(u_char ch) +{ + return ch == ' ' || ch == '\t'; +} diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 69cb0ae09..2ab00d7b7 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -142,16 +142,10 @@ static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame( static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); -static ngx_int_t ngx_http_v2_validate_header(ngx_http_request_t *r, - ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value); -static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, - ngx_str_t *value); -static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, - ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); @@ -1775,7 +1769,8 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, fc = r->connection; /* TODO Optimization: validate headers while parsing. */ - if (ngx_http_v2_validate_header(r, header) != NGX_OK) { + if (ngx_http_v23_validate_header(r, &header->name, &header->value, 1) + != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } @@ -3242,58 +3237,6 @@ ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c) } -static ngx_int_t -ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) -{ - u_char ch; - ngx_uint_t i; - ngx_http_core_srv_conf_t *cscf; - - r->invalid_header = 0; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { - ch = header->name.data[i]; - - if ((ch >= 'a' && ch <= 'z') - || (ch == '-') - || (ch >= '0' && ch <= '9') - || (ch == '_' && cscf->underscores_in_headers)) - { - continue; - } - - if (ch <= 0x20 || ch == 0x7f || ch == ':' - || (ch >= 'A' && ch <= 'Z')) - { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", - &header->name); - - return NGX_ERROR; - } - - r->invalid_header = 1; - } - - for (i = 0; i != header->value.len; i++) { - ch = header->value.data[i]; - - if (ch == '\0' || ch == LF || ch == CR) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent header \"%V\" with " - "invalid value: \"%V\"", - &header->name, &header->value); - - return NGX_ERROR; - } - } - - return NGX_OK; -} - - static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) { @@ -3314,13 +3257,13 @@ ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) == 0) { - return ngx_http_v2_parse_method(r, &header->value); + return ngx_http_v23_parse_method(r, &header->value); } if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) == 0) { - return ngx_http_v2_parse_scheme(r, &header->value); + return ngx_http_v23_parse_scheme(r, &header->value); } break; @@ -3382,147 +3325,6 @@ ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value) } -static ngx_int_t -ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_str_t *value) -{ - size_t k, len; - ngx_uint_t n; - const u_char *p, *m; - - /* - * This array takes less than 256 sequential bytes, - * and if typical CPU cache line size is 64 bytes, - * it is prefetched for 4 load operations. - */ - static const struct { - u_char len; - const u_char method[11]; - uint32_t value; - } tests[] = { - { 3, "GET", NGX_HTTP_GET }, - { 4, "POST", NGX_HTTP_POST }, - { 4, "HEAD", NGX_HTTP_HEAD }, - { 7, "OPTIONS", NGX_HTTP_OPTIONS }, - { 8, "PROPFIND", NGX_HTTP_PROPFIND }, - { 3, "PUT", NGX_HTTP_PUT }, - { 5, "MKCOL", NGX_HTTP_MKCOL }, - { 6, "DELETE", NGX_HTTP_DELETE }, - { 4, "COPY", NGX_HTTP_COPY }, - { 4, "MOVE", NGX_HTTP_MOVE }, - { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, - { 4, "LOCK", NGX_HTTP_LOCK }, - { 6, "UNLOCK", NGX_HTTP_UNLOCK }, - { 5, "PATCH", NGX_HTTP_PATCH }, - { 5, "TRACE", NGX_HTTP_TRACE }, - { 7, "CONNECT", NGX_HTTP_CONNECT } - }, *test; - - if (r->method_name.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate :method header"); - - return NGX_DECLINED; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty :method header"); - - return NGX_DECLINED; - } - - r->method_name.len = value->len; - r->method_name.data = value->data; - - len = r->method_name.len; - n = sizeof(tests) / sizeof(tests[0]); - test = tests; - - do { - if (len == test->len) { - p = r->method_name.data; - m = test->method; - k = len; - - do { - if (*p++ != *m++) { - goto next; - } - } while (--k); - - r->method = test->value; - return NGX_OK; - } - - next: - test++; - - } while (--n); - - p = r->method_name.data; - - do { - if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid method: \"%V\"", - &r->method_name); - - return NGX_DECLINED; - } - - p++; - - } while (--len); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) -{ - u_char c, ch; - ngx_uint_t i; - - if (r->schema.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate :scheme header"); - - return NGX_DECLINED; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty :scheme header"); - - return NGX_DECLINED; - } - - for (i = 0; i < value->len; i++) { - ch = value->data[i]; - - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - continue; - } - - if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') - && i > 0) - { - continue; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid :scheme header: \"%V\"", value); - - return NGX_DECLINED; - } - - r->schema = *value; - - return NGX_OK; -} - static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6b487289a..db4f580e3 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -17,8 +17,6 @@ static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); -static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, - ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); @@ -31,30 +29,6 @@ static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in); -static const struct { - ngx_str_t name; - ngx_uint_t method; -} ngx_http_v3_methods[] = { - - { ngx_string("GET"), NGX_HTTP_GET }, - { ngx_string("POST"), NGX_HTTP_POST }, - { ngx_string("HEAD"), NGX_HTTP_HEAD }, - { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, - { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, - { ngx_string("PUT"), NGX_HTTP_PUT }, - { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, - { ngx_string("DELETE"), NGX_HTTP_DELETE }, - { ngx_string("COPY"), NGX_HTTP_COPY }, - { ngx_string("MOVE"), NGX_HTTP_MOVE }, - { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, - { ngx_string("LOCK"), NGX_HTTP_LOCK }, - { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, - { ngx_string("PATCH"), NGX_HTTP_PATCH }, - { ngx_string("TRACE"), NGX_HTTP_TRACE }, - { ngx_string("CONNECT"), NGX_HTTP_CONNECT } -}; - - void ngx_http_v3_init_stream(ngx_connection_t *c) { @@ -632,7 +606,7 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, r->v3_parse->header_limit -= len; - if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + if (ngx_http_v23_validate_header(r, name, value, 1) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } @@ -701,64 +675,10 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } -static ngx_int_t -ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, - ngx_str_t *value) -{ - u_char ch; - ngx_uint_t i; - ngx_http_core_srv_conf_t *cscf; - - r->invalid_header = 0; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - for (i = (name->data[0] == ':'); i != name->len; i++) { - ch = name->data[i]; - - if ((ch >= 'a' && ch <= 'z') - || (ch == '-') - || (ch >= '0' && ch <= '9') - || (ch == '_' && cscf->underscores_in_headers)) - { - continue; - } - - if (ch <= 0x20 || ch == 0x7f || ch == ':' - || (ch >= 'A' && ch <= 'Z')) - { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", name); - - return NGX_ERROR; - } - - r->invalid_header = 1; - } - - for (i = 0; i != value->len; i++) { - ch = value->data[i]; - - if (ch == '\0' || ch == LF || ch == CR) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent header \"%V\" with " - "invalid value: \"%V\"", name, value); - - return NGX_ERROR; - } - } - - return NGX_OK; -} - - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { - u_char ch, c; - ngx_uint_t i; - if (r->request_line.len) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent out of order pseudo-headers"); @@ -766,44 +686,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, } if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { - - if (r->method_name.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":method\" header"); + if (ngx_http_v23_parse_method(r, value) != NGX_OK) { goto failed; } - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty \":method\" header"); - goto failed; - } - - r->method_name = *value; - - for (i = 0; i < sizeof(ngx_http_v3_methods) - / sizeof(ngx_http_v3_methods[0]); i++) - { - if (value->len == ngx_http_v3_methods[i].name.len - && ngx_strncmp(value->data, - ngx_http_v3_methods[i].name.data, value->len) - == 0) - { - r->method = ngx_http_v3_methods[i].method; - break; - } - } - - for (i = 0; i < value->len; i++) { - ch = value->data[i]; - - if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid method: \"%V\"", value); - goto failed; - } - } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 method \"%V\" %ui", value, r->method); return NGX_OK; @@ -840,41 +726,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { - if (r->schema.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":scheme\" header"); + if (ngx_http_v23_parse_scheme(r, value) != NGX_OK) { goto failed; } - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty \":scheme\" header"); - goto failed; - } - - for (i = 0; i < value->len; i++) { - ch = value->data[i]; - - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - continue; - } - - if (((ch >= '0' && ch <= '9') - || ch == '+' || ch == '-' || ch == '.') - && i > 0) - { - continue; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid \":scheme\" header: \"%V\"", - value); - goto failed; - } - - r->schema = *value; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 schema \"%V\"", value); return NGX_OK;