From dea68dbf126f40a8acd09bac885a955be459162e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Jul 2026 18:14:39 +0400 Subject: [PATCH] Upstream: special handling of the "Host" header in proxy and gRPC HTTP/2 and gRPC requests are now always sent with the ":authority" pseudo-header, while the "Host" header is no longer passed. Where a host value is set explicitly with "proxy_set_header Host" or "grpc_set_header Host", that value is conveyed in ":authority". As per RFC 9113, Section 8.3.1, clients and intermediaries MUST generate an ":authority" pseudo-header to convey authority information. For HTTP/1.1, HTTP/2 and gRPC, if the explicit value is empty, then the value of $proxy_host is used instead. As per RFC 9110, Sections 4.2.1 and 4.2.2, the "http" and "https" URI schemes MUST contain a non-empty host identifier. The change enforces this rule by introducing a default. Closes: https://github.com/nginx/nginx/issues/1426 --- src/http/modules/ngx_http_grpc_module.c | 89 +++++++++++++-------- src/http/modules/ngx_http_proxy_module.c | 86 +++++++++++++------- src/http/modules/ngx_http_proxy_module.h | 3 +- src/http/modules/ngx_http_proxy_v2_module.c | 42 +++++----- 4 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 3d0e9d7ee..6f1752e54 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -25,7 +25,7 @@ typedef struct { ngx_array_t *headers_source; ngx_str_t host; - ngx_uint_t host_set; + ngx_http_complex_value_t *host_value; ngx_array_t *grpc_lengths; ngx_array_t *grpc_values; @@ -739,6 +739,7 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) key_len, val_len, uri_len; uintptr_t escape; ngx_buf_t *b; + ngx_str_t host; ngx_uint_t i, next; ngx_chain_t *cl, *body; ngx_list_part_t *part; @@ -809,18 +810,28 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) /* :authority header */ - if (!glcf->host_set) { - if (ctx->host.len > NGX_HTTP_V2_MAX_FIELD) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "too long http2 host: \"%V\"", &ctx->host); - return NGX_ERROR; - } + host.len = 0; - len += 1 + NGX_HTTP_V2_INT_OCTETS + ctx->host.len; + if (glcf->host_value + && ngx_http_complex_value(r, glcf->host_value, &host) != NGX_OK) + { + return NGX_ERROR; + } - if (tmp_len < ctx->host.len) { - tmp_len = ctx->host.len; - } + if (host.len == 0) { + host = ctx->host; + } + + if (host.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "too long http2 host: \"%V\"", &host); + return NGX_ERROR; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + host.len; + + if (tmp_len < host.len) { + tmp_len = host.len; } /* other headers */ @@ -1051,14 +1062,11 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) "grpc header: \":path: %V\"", &r->uri); } - if (!glcf->host_set) { - *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); - b->last = ngx_http_v2_write_value(b->last, ctx->host.data, - ctx->host.len, tmp); + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); + b->last = ngx_http_v2_write_value(b->last, host.data, host.len, tmp); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "grpc header: \":authority: %V\"", &ctx->host); - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":authority: %V\"", &host); ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); @@ -4517,7 +4525,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) * conf->headers.values = NULL; * conf->headers.hash = { NULL, 0 }; * conf->host = { 0, NULL }; - * conf->host_set = 0; + * conf->host_value = NULL; * conf->ssl = 0; * conf->ssl_protocols = 0; * conf->ssl_ciphers = { 0, NULL }; @@ -4725,7 +4733,7 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->headers_source == prev->headers_source) { conf->headers = prev->headers; - conf->host_set = prev->host_set; + conf->host_value = prev->host_value; } rc = ngx_http_grpc_init_headers(cf, conf, &conf->headers, @@ -4743,7 +4751,7 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) && conf->headers_source == prev->headers_source) { prev->headers = conf->headers; - prev->host_set = conf->host_set; + prev->host_value = conf->host_value; } return NGX_CONF_OK; @@ -4754,16 +4762,17 @@ static ngx_int_t ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_headers_t *headers, ngx_keyval_t *default_headers) { - u_char *p; - size_t size; - uintptr_t *code; - ngx_uint_t i; - ngx_array_t headers_names, headers_merged; - ngx_keyval_t *src, *s, *h; - ngx_hash_key_t *hk; - ngx_hash_init_t hash; - ngx_http_script_compile_t sc; - ngx_http_script_copy_code_t *copy; + u_char *p; + size_t size; + uintptr_t *code; + ngx_uint_t i; + ngx_array_t headers_names, headers_merged; + ngx_keyval_t *src, *s, *h; + ngx_hash_key_t *hk; + ngx_hash_init_t hash; + ngx_http_script_compile_t sc; + ngx_http_script_copy_code_t *copy; + ngx_http_compile_complex_value_t ccv; if (headers->hash.buckets) { return NGX_OK; @@ -4799,7 +4808,23 @@ ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, if (src[i].key.len == 4 && ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0) { - conf->host_set = 1; + conf->host_value = ngx_pcalloc(cf->pool, + sizeof(ngx_http_complex_value_t)); + if (conf->host_value == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &src[i].value; + ccv.complex_value = conf->host_value; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_ERROR; + } + + continue; } s = ngx_array_push(&headers_merged); diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 3ae8c9766..384c2ee70 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -759,7 +759,7 @@ static char ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF; static ngx_keyval_t ngx_http_proxy_headers[] = { - { ngx_string("Host"), ngx_string("$proxy_internal_host") }, + { ngx_string("Host"), ngx_string("") }, { ngx_string("Connection"), ngx_string("") }, { ngx_string("Proxy-Connection"), ngx_string("") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, @@ -788,7 +788,7 @@ static ngx_str_t ngx_http_proxy_hide_headers[] = { #if (NGX_HTTP_CACHE) static ngx_keyval_t ngx_http_proxy_cache_headers[] = { - { ngx_string("Host"), ngx_string("$proxy_internal_host") }, + { ngx_string("Host"), ngx_string("") }, { ngx_string("Connection"), ngx_string("") }, { ngx_string("Proxy-Connection"), ngx_string("") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, @@ -825,10 +825,6 @@ static ngx_http_variable_t ngx_http_proxy_vars[] = { { ngx_string("proxy_add_via"), NULL, NULL, 0, NGX_HTTP_VAR_NOHASH, 0 }, #endif - { ngx_string("proxy_internal_host"), NULL, - ngx_http_proxy_host_variable, 1, - NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, - { ngx_string("proxy_internal_body_length"), NULL, ngx_http_proxy_internal_body_length_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, @@ -904,8 +900,6 @@ ngx_http_proxy_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ctx->legacy = 1; - ngx_http_set_ctx(r, ctx, ngx_http_proxy_module); u = r->upstream; @@ -1193,7 +1187,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) key_len, val_len; uintptr_t escape; ngx_buf_t *b; - ngx_str_t method; + ngx_str_t method, host; ngx_uint_t i, unparsed_uri; ngx_chain_t *cl, *body; ngx_list_part_t *part; @@ -1231,6 +1225,20 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + host.len = 0; + + if (plcf->host_value + && ngx_http_complex_value(r, plcf->host_value, &host) != NGX_OK) + { + return NGX_ERROR; + } + + if (plcf->host_value == NULL + || (host.len == 0 && plcf->http_version == NGX_HTTP_VERSION_11)) + { + host = ctx->vars.host_header; + } + if (method.len == 4 && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) { @@ -1300,6 +1308,10 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) ctx->internal_body_length = r->headers_in.content_length_n; } + if (host.len) { + len += sizeof("Host: ") - 1 + host.len + sizeof(CRLF) - 1; + } + le.ip = headers->lengths->elts; le.request = r; le.flushed = 1; @@ -1410,6 +1422,12 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) sizeof(ngx_http_proxy_version) - 1); } + if (host.len) { + b->last = ngx_cpymem(b->last, "Host: ", sizeof("Host: ") - 1); + b->last = ngx_cpymem(b->last, host.data, host.len); + *b->last++ = CR; *b->last++ = LF; + } + ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = headers->values->elts; @@ -2727,11 +2745,6 @@ ngx_http_proxy_host_variable(ngx_http_request_t *r, return NGX_OK; } - if (data == 1 && !ctx->legacy) { - v->not_found = 1; - return NGX_OK; - } - v->len = ctx->vars.host_header.len; v->valid = 1; v->no_cacheable = 0; @@ -3542,7 +3555,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) * conf->headers.values = NULL; * conf->headers.hash = { NULL, 0 }; * conf->headers_cache.lengths = NULL; - * conf->host_set = 0; + * conf->host_value = NULL; * conf->headers_cache.values = NULL; * conf->headers_cache.hash = { NULL, 0 }; * conf->body_lengths = NULL; @@ -4126,7 +4139,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif - conf->host_set = prev->host_set; + conf->host_value = prev->host_value; } rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, @@ -4159,7 +4172,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_CACHE) prev->headers_cache = conf->headers_cache; #endif - prev->host_set = conf->host_set; + prev->host_value = conf->host_value; } return NGX_CONF_OK; @@ -4170,16 +4183,17 @@ static ngx_int_t ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers) { - u_char *p; - size_t size; - uintptr_t *code; - ngx_uint_t i; - ngx_array_t headers_names, headers_merged; - ngx_keyval_t *src, *s, *h; - ngx_hash_key_t *hk; - ngx_hash_init_t hash; - ngx_http_script_compile_t sc; - ngx_http_script_copy_code_t *copy; + u_char *p; + size_t size; + uintptr_t *code; + ngx_uint_t i; + ngx_array_t headers_names, headers_merged; + ngx_keyval_t *src, *s, *h; + ngx_hash_key_t *hk; + ngx_hash_init_t hash; + ngx_http_script_compile_t sc; + ngx_http_script_copy_code_t *copy; + ngx_http_compile_complex_value_t ccv; if (headers->hash.buckets) { return NGX_OK; @@ -4215,7 +4229,23 @@ ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, if (src[i].key.len == 4 && ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0) { - conf->host_set = 1; + conf->host_value = ngx_pcalloc(cf->pool, + sizeof(ngx_http_complex_value_t)); + if (conf->host_value == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &src[i].value; + ccv.complex_value = conf->host_value; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_ERROR; + } + + continue; } s = ngx_array_push(&headers_merged); diff --git a/src/http/modules/ngx_http_proxy_module.h b/src/http/modules/ngx_http_proxy_module.h index a0f79bc13..0675122b2 100644 --- a/src/http/modules/ngx_http_proxy_module.h +++ b/src/http/modules/ngx_http_proxy_module.h @@ -49,7 +49,7 @@ typedef struct { ngx_http_proxy_headers_t headers_cache; #endif ngx_array_t *headers_source; - ngx_uint_t host_set; + ngx_http_complex_value_t *host_value; ngx_array_t *proxy_lengths; ngx_array_t *proxy_values; @@ -102,7 +102,6 @@ typedef struct { unsigned head:1; unsigned internal_chunked:1; unsigned header_sent:1; - unsigned legacy:1; } ngx_http_proxy_ctx_t; diff --git a/src/http/modules/ngx_http_proxy_v2_module.c b/src/http/modules/ngx_http_proxy_v2_module.c index 372ab05e9..cc3f090ca 100644 --- a/src/http/modules/ngx_http_proxy_v2_module.c +++ b/src/http/modules/ngx_http_proxy_v2_module.c @@ -326,7 +326,7 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r) loc_len, body_len; uintptr_t escape; ngx_buf_t *b; - ngx_str_t method, *host; + ngx_str_t method, host; ngx_uint_t i, next, unparsed_uri; ngx_chain_t *cl, *body; ngx_list_part_t *part; @@ -445,20 +445,28 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r) /* :authority header */ - host = &ctx->ctx.vars.host_header; + host.len = 0; - if (!plcf->host_set) { - if (host->len > NGX_HTTP_V2_MAX_FIELD) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "too long http2 host: \"%V\"", host); - return NGX_ERROR; - } + if (plcf->host_value + && ngx_http_complex_value(r, plcf->host_value, &host) != NGX_OK) + { + return NGX_ERROR; + } - len += 1 + NGX_HTTP_V2_INT_OCTETS + host->len; + if (host.len == 0) { + host = ctx->ctx.vars.host_header; + } - if (tmp_len < host->len) { - tmp_len = host->len; - } + if (host.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "too long http2 host: \"%V\"", &host); + return NGX_ERROR; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + host.len; + + if (tmp_len < host.len) { + tmp_len = host.len; } /* other headers */ @@ -719,13 +727,11 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r) val_tmp); } - if (!plcf->host_set) { - *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); - b->last = ngx_http_v2_write_value(b->last, host->data, host->len, tmp); + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); + b->last = ngx_http_v2_write_value(b->last, host.data, host.len, tmp); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http proxy header: \":authority: %V\"", host); - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":authority: %V\"", &host); ngx_memzero(&e, sizeof(ngx_http_script_engine_t));