mirror of https://github.com/nginx/nginx.git
HTTP/2: fixed handling of the ":authority" header.
Previously, it misused the Host header processing resulting in
400 (Bad Request) errors for a valid request that contains both
":authority" and Host headers with the same value, treating it
after 37984f0be
as if client sent more than one Host header.
Such an overly strict handling violates RFC 9113.
The fix is to process ":authority" as a distinct header, similarly
to processing an authority component in the HTTP/1.x request line.
This allows to disambiguate and compare Host and ":authority"
values after all headers were processed.
With this change, the ngx_http_process_request_header() function
can no longer be used here, certain parts were inlined similar to
the HTTP/3 module.
To provide compatibility for misconfigurations that use $http_host
to return the value of the ":authority" header, the Host header,
if missing, is now reconstructed from ":authority".
This commit is contained in:
parent
a238bb3d22
commit
ede5623b15
|
@ -158,8 +158,7 @@ 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);
|
||||
static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r);
|
||||
static ngx_int_t ngx_http_v2_construct_host_header(ngx_http_request_t *r,
|
||||
ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_v2_construct_host_header(ngx_http_request_t *r);
|
||||
static void ngx_http_v2_run_request(ngx_http_request_t *r);
|
||||
static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r,
|
||||
u_char *pos, size_t size, ngx_uint_t last, ngx_uint_t flush);
|
||||
|
@ -3519,7 +3518,41 @@ 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)
|
||||
{
|
||||
return ngx_http_v2_construct_host_header(r, value);
|
||||
ngx_int_t rc;
|
||||
|
||||
if (r->host_start) {
|
||||
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
|
||||
"client sent duplicate \":authority\" header");
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
r->host_start = value->data;
|
||||
r->host_end = value->data + value->len;
|
||||
|
||||
rc = ngx_http_validate_host(value, r->pool, 0);
|
||||
|
||||
if (rc == NGX_DECLINED) {
|
||||
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
|
||||
"client sent invalid \":authority\" header");
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
if (ngx_http_set_virtual_server(r, value) == NGX_ERROR) {
|
||||
/*
|
||||
* request has been finalized already
|
||||
* in ngx_http_set_virtual_server()
|
||||
*/
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
r->headers_in.server = *value;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
|
@ -3699,7 +3732,7 @@ ngx_http_v2_construct_cookie_header(ngx_http_request_t *r)
|
|||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_v2_construct_host_header(ngx_http_request_t *r, ngx_str_t *value)
|
||||
ngx_http_v2_construct_host_header(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_table_elt_t *h;
|
||||
ngx_http_header_t *hh;
|
||||
|
@ -3709,6 +3742,7 @@ ngx_http_v2_construct_host_header(ngx_http_request_t *r, ngx_str_t *value)
|
|||
|
||||
h = ngx_list_push(&r->headers_in.headers);
|
||||
if (h == NULL) {
|
||||
ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
@ -3717,8 +3751,8 @@ ngx_http_v2_construct_host_header(ngx_http_request_t *r, ngx_str_t *value)
|
|||
h->key.len = host.len;
|
||||
h->key.data = host.data;
|
||||
|
||||
h->value.len = value->len;
|
||||
h->value.data = value->data;
|
||||
h->value.len = r->host_end - r->host_start;
|
||||
h->value.data = r->host_start;
|
||||
|
||||
h->lowcase_key = host.data;
|
||||
|
||||
|
@ -3728,6 +3762,7 @@ ngx_http_v2_construct_host_header(ngx_http_request_t *r, ngx_str_t *value)
|
|||
h->lowcase_key, h->key.len);
|
||||
|
||||
if (hh == NULL) {
|
||||
ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
@ -3736,7 +3771,7 @@ ngx_http_v2_construct_host_header(ngx_http_request_t *r, ngx_str_t *value)
|
|||
* request has been finalized already
|
||||
* in ngx_http_process_host()
|
||||
*/
|
||||
return NGX_ABORT;
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
@ -3746,6 +3781,7 @@ ngx_http_v2_construct_host_header(ngx_http_request_t *r, ngx_str_t *value)
|
|||
static void
|
||||
ngx_http_v2_run_request(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_str_t host;
|
||||
ngx_connection_t *fc;
|
||||
ngx_http_v2_srv_conf_t *h2scf;
|
||||
ngx_http_v2_connection_t *h2c;
|
||||
|
@ -3773,24 +3809,78 @@ ngx_http_v2_run_request(ngx_http_request_t *r)
|
|||
|
||||
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
|
||||
|
||||
if (ngx_http_process_request_header(r) != NGX_OK) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (r->headers_in.content_length_n > 0 && r->stream->in_closed) {
|
||||
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
|
||||
"client prematurely closed stream");
|
||||
|
||||
r->stream->skip_data = 1;
|
||||
|
||||
if (r->headers_in.server.len == 0) {
|
||||
ngx_log_error(NGX_LOG_INFO, fc->log, 0,
|
||||
"client sent neither \":authority\" nor \"Host\" header");
|
||||
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (r->headers_in.content_length_n == -1 && !r->stream->in_closed) {
|
||||
if (r->host_end) {
|
||||
|
||||
host.len = r->host_end - r->host_start;
|
||||
host.data = r->host_start;
|
||||
|
||||
if (r->headers_in.host) {
|
||||
if (r->headers_in.host->value.len != host.len
|
||||
|| ngx_memcmp(r->headers_in.host->value.data, host.data,
|
||||
host.len)
|
||||
!= 0)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, fc->log, 0,
|
||||
"client sent \":authority\" and \"Host\" headers "
|
||||
"with different values");
|
||||
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* compatibility for $http_host */
|
||||
|
||||
if (ngx_http_v2_construct_host_header(r) != NGX_OK) {
|
||||
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, fc->log, 0,
|
||||
"client sent invalid \"Content-Length\" header");
|
||||
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (r->headers_in.content_length_n > 0 && r->stream->in_closed) {
|
||||
ngx_log_error(NGX_LOG_INFO, fc->log, 0,
|
||||
"client prematurely closed stream");
|
||||
|
||||
r->stream->skip_data = 1;
|
||||
|
||||
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
} else if (!r->stream->in_closed) {
|
||||
r->headers_in.chunked = 1;
|
||||
}
|
||||
|
||||
if (r->method == NGX_HTTP_CONNECT) {
|
||||
ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client sent CONNECT method");
|
||||
ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (r->method == NGX_HTTP_TRACE) {
|
||||
ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client sent TRACE method");
|
||||
ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
h2c = r->stream->connection;
|
||||
|
||||
h2c->payload_bytes += r->request_length;
|
||||
|
|
Loading…
Reference in New Issue