From 1ba504d6342bd9bb5c7e6a584900b92d6e30455a Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 14 Mar 2025 21:14:01 -0400 Subject: [PATCH 1/8] HTTP: Consider tab as whitespace in field value HTTP considers 0x09 (horizontal tab) to be valid horizontal whitespace in a field value, and there are badly-behaved clients in the wild that rely on this behavior and cannot be fixed. This also ensures that NGINX is not itself such a badly-behaved client and that, for HTTP/1.x requests, the values of the $http_* variables agree with what upstream servers will see. Fixes: #187 --- src/http/ngx_http_parse.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index a45c04554..4dea76cb1 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -972,6 +972,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; @@ -996,6 +997,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; @@ -1016,6 +1018,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; From 6bd9e8ce72d69e01780c8c8ad5d3e59fd8d7da5e Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 25 Mar 2025 13:38:59 -0400 Subject: [PATCH 2/8] HTTP: Do not log headers with unsanitized values These could contain control charactes (including newlines!) and mess up the logs. --- src/http/v2/ngx_http_v2.c | 6 ++---- src/http/v3/ngx_http_v3_request.c | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 91a28b228..88b7cc6de 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3258,8 +3258,7 @@ ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) || (ch >= 'A' && ch <= 'Z')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", - &header->name); + "client sent invalid header name"); return NGX_ERROR; } @@ -3273,8 +3272,7 @@ ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) 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); + "invalid value", &header->name); return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e41ad50a8..5fa6f0442 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -719,7 +719,7 @@ ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, || (ch >= 'A' && ch <= 'Z')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", name); + "client sent invalid header name"); return NGX_ERROR; } @@ -733,7 +733,7 @@ ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, 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); + "invalid value", name); return NGX_ERROR; } From 3a45410074678ffea8401d898753a1a6152bcfdc Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 25 Mar 2025 14:58:32 -0400 Subject: [PATCH 3/8] HTTP: Use common header validation function for HTTP/2 and HTTP/3 The header validation required by HTTP/2 and HTTP/3 is identical, so use a common function for both. This will make it easier to add additional validation in the future. Move the function to ngx_http_parse.c so that it can share code with the HTTP/1.x parser. --- src/http/ngx_http_parse.c | 60 +++++++++++++++++++++++++++++++ src/http/v2/ngx_http_v2.c | 55 ++-------------------------- src/http/v3/ngx_http_v3_request.c | 55 +--------------------------- 3 files changed, 63 insertions(+), 107 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 4dea76cb1..0e253d99f 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -1093,6 +1093,66 @@ header_done: } +#if (NGX_HTTP_V2 || NGX_HTTP_V3) +ngx_int_t +ngx_http_v23_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); + + if (name->len < 1) { + ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, + "BUG: internal zero-length header name"); + + return NGX_ERROR; + } + + 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"); + + 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", name, value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} +#endif + + ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r) { diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 88b7cc6de..25b71aa4a 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -142,8 +142,6 @@ 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, @@ -1774,7 +1772,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) + != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } @@ -3232,56 +3231,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"); - - 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", &header->name); - - 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) { diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5fa6f0442..e2ffc7543 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); @@ -632,7 +630,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) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } @@ -692,57 +690,6 @@ 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"); - - 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", name); - - 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) From c68c7adcdbe01440c6e82e8051f776838cc0f641 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 25 Mar 2025 14:58:32 -0400 Subject: [PATCH 4/8] HTTP: Allow rejecting leading and trailing whitespace in HTTP2+ fields All versions of HTTP forbid field (header and trailer) values from having leading or trailing horizontal whitespace (0x20 and 0x09). In HTTP/1.0 and HTTP/1.1, leading and trailing whitespace must be stripped from the field value before further processing. In HTTP/2 and HTTP/3, leading and trailing whitespace must cause the entire message to be considered malformed. Willy Tarreau (lead developer of HAProxy) has indicated that there are clients that actually do send leading and/or trailing whitespace in HTTP/2 and/or HTTP/3 cookie headers, which is why HAProxy accepts them. Therefore, the fix is disabled by default and must be enabled with the reject_leading_trailing_whitespace directive. Stripping leading and/or trailing whitespace would require either allocating a new buffer or changing the pointers in the existing buffer, and I am not familiar enough with NGINX to know if subsequent code expects a buffer that was allocated in a particualar way. If header values were ever passed to ngx_pfree(), munging them to skip leading whitespace would mean that a request with leading whitespace would cause ngx_pfree() to be called with an invalid pointer, which would be a security vulnerability. Rejecting the request doesn't introduce any new error paths that clients cannot already trigger, and it doesn't risk violating any invariants that existing code might assume. Also, Varnish Cache rejects HTTP/2 requests with leading and/or trailing whitespace in field values, so there is precedent for doing so. --- src/http/ngx_http.h | 3 ++- src/http/ngx_http_core_module.c | 11 +++++++++++ src/http/ngx_http_core_module.h | 1 + src/http/ngx_http_parse.c | 24 +++++++++++++++++++----- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index cb4a1e68a..2d891be77 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -97,7 +97,6 @@ int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); int ngx_http_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg); #endif - ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b); ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r); ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r, @@ -178,6 +177,8 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #if (NGX_HTTP_V2 || NGX_HTTP_V3) +ngx_int_t ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value); 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, diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 92c3eae8a..495d476ac 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -259,6 +259,13 @@ 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("merge_slashes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -3476,6 +3483,7 @@ 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->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; @@ -3522,6 +3530,9 @@ 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); + ngx_conf_merge_value(conf->reject_leading_trailing_whitespace, + prev->reject_leading_trailing_whitespace, 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 e7e266bf8..8e7e9eb8b 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -208,6 +208,7 @@ typedef struct { #endif ngx_http_core_loc_conf_t **named_locations; + ngx_flag_t reject_leading_trailing_whitespace; } ngx_http_core_srv_conf_t; diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 0e253d99f..2b083660c 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -1136,16 +1136,30 @@ ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name, r->invalid_header = 1; } - for (i = 0; i != value->len; i++) { - ch = value->data[i]; - - if (ch == '\0' || ch == LF || ch == CR) { + if (value->len > 0) { + if (cscf->reject_leading_trailing_whitespace + && (value->data[0] == ' ' || value->data[0] == '\t' + || value->data[value->len - 1] == ' ' + || value->data[value->len - 1] == '\t')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent header \"%V\" with " - "invalid value", name, value); + " leading or trailing space", + name); return NGX_ERROR; } + + 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", name, value); + + return NGX_ERROR; + } + } } return NGX_OK; From bccf2b1f3b0411187813c94ac7b9c2b748314195 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 13 Mar 2025 01:36:49 -0400 Subject: [PATCH 5/8] HTTP: Reject hop-by-hop headers in HTTP/2 and HTTP/3 requests RFC9113 and RFC9114 both require requests with connection-specific headers to be treated as malformed, with the exception of "te: trailers". Reject requests containing them. --- src/http/ngx_http_parse.c | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 2b083660c..286bec23a 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -1098,6 +1098,7 @@ ngx_int_t ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { + int bad; u_char ch; ngx_uint_t i; ngx_http_core_srv_conf_t *cscf; @@ -1162,6 +1163,46 @@ ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name, } } + bad = 0; + switch (name->len) { +#define X(s) \ + case sizeof("" s) - 1: \ + bad = memcmp(name->data, s, sizeof(s) - 1) == 0; \ + break; + X("upgrade"); + X("transfer-encoding"); +#undef X + case 10: + switch (name->data[0]) { + case 'c': + bad = memcmp(name->data + 1, "onnection", 9) == 0; + break; + case 'k': + bad = memcmp(name->data + 1, "eep-alive", 9) == 0; + break; + default: + break; + } + break; + case 2: + /* te: trailiers is allowed, all other te values forbidden */ + bad = name->data[0] == 't' && name->data[1] == 'e' + && !(value->len == 8 && memcmp(value->data, "trailers", 8) == 0); + break; + } + + /* Proxy-* headers are not allowed */ + if (name->len >= 6 && memcmp(name->data, "proxy-", 6) == 0) { + bad = 1; + } + + if (bad) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent forbidden hop-by-hop header \"%V\" with " + "value: \"%V\"", name, value); + + return NGX_ERROR; + } return NGX_OK; } #endif From 2fbc33e2de8fa08728d188fd9c5a07afacdd6f92 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 14 Mar 2025 01:00:59 -0400 Subject: [PATCH 6/8] HTTP: reject invalid header names HTTP headers must be an RFC9110 token, so only a subset of characters are permitted. RFC9113 and RFC9114 require rejecting invalid header characters in HTTP/2 and HTTP/3 respectively, so reject them in HTTP/1.0 and HTTP/1.1 for consistency. This also requires removing the ignore hack for (presumably ancient) versions of IIS. --- src/http/ngx_http_parse.c | 68 +++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 286bec23a..e5dd40e59 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -811,6 +811,29 @@ done: return NGX_OK; } +static ngx_int_t +ngx_http_non_alnum_dash_header_char(u_char ch) +{ + switch (ch) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return 1; + default: + return 0; + } +} ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, @@ -824,7 +847,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, sw_space_before_value, sw_value, sw_space_after_value, - sw_ignore_line, sw_almost_done, sw_header_almost_done } state; @@ -875,22 +897,14 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == '_') { - if (allow_underscores) { - hash = ngx_hash(0, ch); - r->lowcase_header[0] = ch; - i = 1; - - } else { - hash = 0; - i = 0; - r->invalid_header = 1; - } - + if (ch == '_' && allow_underscores) { + hash = ngx_hash(0, ch); + r->lowcase_header[0] = ch; + i = 1; break; } - if (ch <= 0x20 || ch == 0x7f || ch == ':') { + if (!ngx_http_non_alnum_dash_header_char(ch)) { r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -949,17 +963,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, goto done; } - /* IIS may send the duplicate "HTTP/1.1 ..." lines */ - if (ch == '/' - && r->upstream - && p - r->header_name_start == 4 - && ngx_strncmp(r->header_name_start, "HTTP", 4) == 0) - { - state = sw_ignore_line; - break; - } - - if (ch <= 0x20 || ch == 0x7f) { + if (!ngx_http_non_alnum_dash_header_char(ch)) { r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1034,17 +1038,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, } break; - /* ignore header line */ - case sw_ignore_line: - switch (ch) { - case LF: - state = sw_start; - break; - default: - break; - } - break; - /* end of header line */ case sw_almost_done: switch (ch) { @@ -1125,8 +1118,7 @@ ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name, continue; } - if (ch <= 0x20 || ch == 0x7f || ch == ':' - || (ch >= 'A' && ch <= 'Z')) + if (!ngx_http_non_alnum_dash_header_char(ch)) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid header name"); From c346375a8b862edc0e833acdb2ba7dc8917c15fd Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 21 Mar 2025 19:48:30 -0400 Subject: [PATCH 7/8] HTTP/3: Do not allow invalid pseudo-header fields RFC9114 requires invalid pseudo-header fields to be rejected, and this is consistent with HTTP/2. --- src/http/v3/ngx_http_v3_request.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e2ffc7543..66b8356a8 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -635,6 +635,10 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, return NGX_ERROR; } + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } + if (r->invalid_header) { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); @@ -646,10 +650,6 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } } - if (name->len && name->data[0] == ':') { - return ngx_http_v3_process_pseudo_header(r, name, value); - } - if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { return NGX_ERROR; } From 6a78618113a65314895b58fde085be01471582ba Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 22 Mar 2025 01:03:00 -0400 Subject: [PATCH 8/8] HTTP: Use common header code for v2 and v3 This makes the behavior of HTTP/2 and HTTP/3 much more similar. In particular, the HTTP/3 :authority pseudoheader is used to set the Host header, instead of the virtual server. This is arguably less correct, but it is consistent with the existing HTTP/2 behavior and unbreaks users of PHP-FPM and other FastCGI applications. In the future, NGINX could have a config option that caused :authority and Host to be treated separately in both HTTP/2 and HTTP/3. --- src/http/ngx_http.c | 300 ++++++++++++++++++++++++++++++ src/http/ngx_http.h | 2 + src/http/ngx_http_request.c | 2 +- src/http/v2/ngx_http_v2.c | 293 +---------------------------- src/http/v3/ngx_http_v3_request.c | 237 ++--------------------- 5 files changed, 320 insertions(+), 514 deletions(-) diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index d835f896e..7a02db7ea 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -68,6 +68,17 @@ static ngx_int_t ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, ngx_http_conf_addr_t *addr); #endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3) +static ngx_int_t ngx_http_v23_parse_path(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v23_parse_method(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v23_parse_scheme(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v23_parse_authority(ngx_http_request_t *r, + ngx_str_t *value); +#endif + ngx_uint_t ngx_http_max_module; @@ -1227,6 +1238,295 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, return ngx_http_add_address(cf, cscf, port, lsopt); } +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + +ngx_int_t +ngx_http_v23_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_int_t rc; + + name->len--; + name->data++; + + switch (name->len) { + case 4: + if (ngx_memcmp(name->data, "path", sizeof("path") - 1) + == 0) + { + rc = ngx_http_v23_parse_path(r, value); + goto known; + } + + break; + + case 6: + if (ngx_memcmp(name->data, "method", sizeof("method") - 1) + == 0) + { + rc = ngx_http_v23_parse_method(r, value); + goto known; + } + + if (ngx_memcmp(name->data, "scheme", sizeof("scheme") - 1) + == 0) + { + rc = ngx_http_v23_parse_scheme(r, value); + goto known; + } + + break; + + case 9: + if (ngx_memcmp(name->data, "authority", sizeof("authority") - 1) + == 0) + { + rc = ngx_http_v23_parse_authority(r, value); + goto known; + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \":%V\"", + name); + rc = NGX_DECLINED; +known: + if (rc == NGX_DECLINED) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + rc = NGX_ABORT; + } + return rc; +} + + +static ngx_int_t +ngx_http_v23_parse_path(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static 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; +} + + +static 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; +} + + +static ngx_int_t +ngx_http_v23_parse_authority(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t host = ngx_string("host"); + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't'); + + h->key.len = host.len; + h->key.data = host.data; + + h->value.len = value->len; + h->value.data = value->data; + + h->lowcase_key = host.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + +#endif static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 2d891be77..ce2292ac0 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -177,6 +177,8 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #if (NGX_HTTP_V2 || NGX_HTTP_V3) +ngx_int_t ngx_http_v23_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value); ngx_int_t ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ceac8d307..f7de651e4 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2015,7 +2015,7 @@ ngx_http_process_request_header(ngx_http_request_t *r) } if (r->headers_in.transfer_encoding) { - if (r->http_version < NGX_HTTP_VERSION_11) { + if (r->http_version != NGX_HTTP_VERSION_11) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent HTTP/1.0 request with " "\"Transfer-Encoding\" header"); diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 25b71aa4a..ba2fb64cc 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -142,16 +142,6 @@ 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_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); static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header); @@ -1779,7 +1769,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, } if (header->name.data[0] == ':') { - rc = ngx_http_v2_pseudo_header(r, header); + rc = ngx_http_v23_pseudo_header(r, &header->name, &header->value); if (rc == NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1789,12 +1779,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_state_header_complete(h2c, pos, end); } - if (rc == NGX_ABORT) { - goto error; - } - if (rc == NGX_DECLINED) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } @@ -3230,282 +3215,6 @@ ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c) return node; } - -static ngx_int_t -ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) -{ - header->name.len--; - header->name.data++; - - switch (header->name.len) { - case 4: - if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) - == 0) - { - return ngx_http_v2_parse_path(r, &header->value); - } - - break; - - case 6: - if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) - == 0) - { - return ngx_http_v2_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); - } - - break; - - case 9: - if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) - == 0) - { - return ngx_http_v2_parse_authority(r, &header->value); - } - - break; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent unknown pseudo-header \":%V\"", - &header->name); - - return NGX_DECLINED; -} - - -static ngx_int_t -ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value) -{ - if (r->unparsed_uri.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate :path header"); - - return NGX_DECLINED; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty :path header"); - - return NGX_DECLINED; - } - - r->uri_start = value->data; - r->uri_end = value->data + value->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid :path header: \"%V\"", value); - - return NGX_DECLINED; - } - - if (ngx_http_process_request_uri(r) != NGX_OK) { - /* - * request has been finalized already - * in ngx_http_process_request_uri() - */ - return NGX_ABORT; - } - - return NGX_OK; -} - - -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) -{ - ngx_table_elt_t *h; - ngx_http_header_t *hh; - ngx_http_core_main_conf_t *cmcf; - - static ngx_str_t host = ngx_string("host"); - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - return NGX_ERROR; - } - - h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't'); - - h->key.len = host.len; - h->key.data = host.data; - - h->value.len = value->len; - h->value.data = value->data; - - h->lowcase_key = host.data; - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh == NULL) { - return NGX_ERROR; - } - - if (hh->handler(r, h, hh->offset) != NGX_OK) { - /* - * request has been finalized already - * in ngx_http_process_host() - */ - return NGX_ABORT; - } - - return NGX_OK; -} - - static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r) { diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 66b8356a8..0f949d4e7 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -28,31 +28,6 @@ static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); 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) { @@ -389,6 +364,14 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) c->data = r; c->requests = (c->quic->id >> 2) + 1; + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); @@ -694,153 +677,17 @@ 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"); - goto failed; + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; } - 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"); - 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; + if (ngx_http_v23_pseudo_header(r, name, value) != NGX_OK) { + return NGX_ERROR; } - - if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { - - if (r->uri_start) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":path\" header"); - goto failed; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty \":path\" header"); - goto failed; - } - - r->uri_start = value->data; - r->uri_end = value->data + value->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid \":path\" header: \"%V\"", - value); - goto failed; - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 path \"%V\"", value); - return NGX_OK; - } - - 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"); - 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; - } - - if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { - - if (r->host_start) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":authority\" header"); - goto failed; - } - - r->host_start = value->data; - r->host_end = value->data + value->len; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 authority \"%V\"", value); - return NGX_OK; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent unknown pseudo-header \"%V\"", name); - -failed: - - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; + return NGX_OK; } @@ -928,14 +775,6 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) r->headers_in.server = host; } - if (ngx_list_init(&r->headers_in.headers, r->pool, 20, - sizeof(ngx_table_elt_t)) - != NGX_OK) - { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - return NGX_OK; failed: @@ -975,38 +814,11 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } - if (r->headers_in.server.len == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent neither \":authority\" nor \"Host\" header"); - goto failed; + if (ngx_http_process_request_header(r) != NGX_OK) { + return NGX_ERROR; } - if (r->headers_in.host) { - if (r->headers_in.host->value.len != r->headers_in.server.len - || ngx_memcmp(r->headers_in.host->value.data, - r->headers_in.server.data, - r->headers_in.server.len) - != 0) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent \":authority\" and \"Host\" headers " - "with different values"); - goto failed; - } - } - - if (r->headers_in.content_length) { - r->headers_in.content_length_n = - ngx_atoof(r->headers_in.content_length->value.data, - r->headers_in.content_length->value.len); - - if (r->headers_in.content_length_n == NGX_ERROR) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid \"Content-Length\" header"); - goto failed; - } - - } else { + if (!r->headers_in.content_length) { b = r->header_in; n = b->last - b->pos; @@ -1029,24 +841,7 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) } } - if (r->method == NGX_HTTP_CONNECT) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - return NGX_ERROR; - } - - if (r->method == NGX_HTTP_TRACE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - return NGX_ERROR; - } - return NGX_OK; - -failed: - - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; }