[PATCH 12 of 14] Request body: error_page 413 handling with HTTP/2 and HTTP/3
Maxim Dounin
mdounin at mdounin.ru
Sat Apr 20 01:00:00 UTC 2024
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1713574645 -10800
# Sat Apr 20 03:57:25 2024 +0300
# Node ID b842b7f11593169f0b44cc1b6aa74e631c17c292
# Parent 718f5b5737277ed252d7bddd2f60c47c2a05f42a
Request body: error_page 413 handling with HTTP/2 and HTTP/3.
When the client_max_body_size limit in ngx_http_core_find_config_phase()
is hit, nginx calls the ngx_http_discard_request_body() function, which
normally sets the r->discard_body flag while discarding the body, and then
reduces the r->headers_in.content_length_n field to 0 when the body is
completely discarded. As such, the client_max_body_size check is skipped
if the request is redirected to an error page, and this makes it possible
to use "error_page 413" without additional settings.
This only works with HTTP/1.x though. The HTTP/2 and HTTP/3 request body
discarding code paths failed to set r->discard_body or reset
r->headers_in.content_length_n, so configuring "error_page 413" did notwork
without additionally clearing the client_max_body_size limit in the
location with error page.
Fix is to set r->headers_in.content_length_n to 0 in the HTTP/2 and HTTP/3
request body discarding code paths (if there is a body). This is essentially
what happens with HTTP/1.x when the body is completely discarded, and makes
it possible to use "error_page 413" with HTTP/2 and HTTP/3 without additional
settings.
Additionally, r->discard_body flag is also set. For HTTP/2, it is not needed,
but serves as an optimization. For HTTP/3, it ensures that the request body
cannot be read after it was discarded, thus bypassing the client_max_body_size
limit.
Further, the r->discard_body flag is now always set after the request body
is discarded (and not cleared once it is fully discarded). While the body
is being discarded, the new r->discarding_body flag is now used. This
slightly optimizes existing code paths in ngx_http_read_client_request_body()
and ngx_http_discard_request_body(), and also makes it easier to only set
ngx_http_discarded_request_body_handler() for HTTP/1.x.
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -2456,6 +2456,7 @@ ngx_http_subrequest(ngx_http_request_t *
sr->internal = 1;
sr->discard_body = r->discard_body;
+ sr->discarding_body = r->discarding_body;
sr->expect_tested = 1;
sr->main_filter_need_in_memory = r->main_filter_need_in_memory;
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -2762,7 +2762,7 @@ ngx_http_finalize_connection(ngx_http_re
if (r->main->count != 1) {
- if (r->discard_body) {
+ if (r->discarding_body) {
r->read_event_handler = ngx_http_discarded_request_body_handler;
ngx_add_timer(r->connection->read, clcf->lingering_timeout);
@@ -2820,7 +2820,7 @@ ngx_http_set_write_handler(ngx_http_requ
r->http_state = NGX_HTTP_WRITING_REQUEST_STATE;
- r->read_event_handler = r->discard_body ?
+ r->read_event_handler = r->discarding_body ?
ngx_http_discarded_request_body_handler:
ngx_http_test_reading;
r->write_event_handler = ngx_http_writer;
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -538,6 +538,7 @@ struct ngx_http_request_s {
unsigned keepalive:1;
unsigned lingering_close:1;
unsigned discard_body:1;
+ unsigned discarding_body:1;
unsigned reading_body:1;
unsigned internal:1;
unsigned error_page:1;
diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c
--- a/src/http/ngx_http_request_body.c
+++ b/src/http/ngx_http_request_body.c
@@ -682,12 +682,24 @@ ngx_http_discard_request_body(ngx_http_r
#if (NGX_HTTP_V2)
if (r->stream) {
r->stream->skip_data = 1;
+
+ if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) {
+ r->headers_in.content_length_n = 0;
+ r->discard_body = 1;
+ }
+
return NGX_OK;
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
+
+ if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) {
+ r->headers_in.content_length_n = 0;
+ r->discard_body = 1;
+ }
+
return NGX_OK;
}
#endif
@@ -706,6 +718,8 @@ ngx_http_discard_request_body(ngx_http_r
return NGX_OK;
}
+ r->discard_body = 1;
+
size = r->header_in->last - r->header_in->pos;
if (size || r->headers_in.chunked) {
@@ -740,7 +754,7 @@ ngx_http_discard_request_body(ngx_http_r
}
r->count++;
- r->discard_body = 1;
+ r->discarding_body = 1;
return NGX_OK;
}
@@ -769,7 +783,7 @@ ngx_http_discarded_request_body_handler(
timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time();
if ((ngx_msec_int_t) timer <= 0) {
- r->discard_body = 0;
+ r->discarding_body = 0;
r->lingering_close = 0;
ngx_http_finalize_request(r, NGX_ERROR);
return;
@@ -782,7 +796,7 @@ ngx_http_discarded_request_body_handler(
rc = ngx_http_read_discarded_request_body(r);
if (rc == NGX_OK) {
- r->discard_body = 0;
+ r->discarding_body = 0;
r->lingering_close = 0;
r->lingering_time = 0;
ngx_http_finalize_request(r, NGX_DONE);
More information about the nginx-devel
mailing list