HTTP/3: refactored request body parser.

The change reduces diff to the default branch for
src/http/ngx_http_request_body.c.

Also, client Content-Length, if present, is now checked against the real body
size sent by client.
This commit is contained in:
Roman Arutyunyan 2021-01-25 16:16:47 +03:00
parent a7cf99b10d
commit 6f3c821d1f
4 changed files with 501 additions and 99 deletions

View File

@ -66,9 +66,6 @@ struct ngx_http_chunked_s {
ngx_uint_t state;
off_t size;
off_t length;
#if (NGX_HTTP_V3)
void *h3_parse;
#endif
};

View File

@ -87,6 +87,13 @@ ngx_http_read_client_request_body(ngx_http_request_t *r,
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
rc = ngx_http_v3_read_request_body(r);
goto done;
}
#endif
preread = r->header_in->last - r->header_in->pos;
if (preread) {
@ -229,6 +236,18 @@ ngx_http_read_unbuffered_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
rc = ngx_http_v3_read_unbuffered_request_body(r);
if (rc == NGX_OK) {
r->reading_body = 0;
}
return rc;
}
#endif
if (r->connection->read->timedout) {
r->connection->timedout = 1;
return NGX_HTTP_REQUEST_TIME_OUT;
@ -333,10 +352,11 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r)
}
if (n == 0) {
rb->buf->last_buf = 1;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client prematurely closed connection");
}
if (n == NGX_ERROR) {
if (n == 0 || n == NGX_ERROR) {
c->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
@ -583,8 +603,8 @@ ngx_http_discard_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_QUIC)
if (r->connection->quic) {
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
return NGX_OK;
}
#endif
@ -956,15 +976,6 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in)
break;
}
size = cl->buf->last - cl->buf->pos;
if (cl->buf->last_buf && (off_t) size < rb->rest) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client prematurely closed connection");
r->connection->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
tl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (tl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
@ -982,6 +993,8 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in)
b->end = cl->buf->end;
b->flush = r->request_body_no_buffering;
size = cl->buf->last - cl->buf->pos;
if ((off_t) size < rb->rest) {
cl->buf->pos = cl->buf->last;
rb->rest -= size;
@ -1053,16 +1066,7 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
switch (r->http_version) {
#if (NGX_HTTP_V3)
case NGX_HTTP_VERSION_30:
rc = ngx_http_v3_parse_request_body(r, cl->buf, rb->chunked);
break;
#endif
default: /* HTTP/1.x */
rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked);
}
rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked);
if (rc == NGX_OK) {
@ -1146,20 +1150,6 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
continue;
}
if (rc == NGX_AGAIN && cl->buf->last_buf) {
/* last body buffer */
if (rb->chunked->length > 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client prematurely closed connection");
r->connection->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
rc = NGX_DONE;
}
if (rc == NGX_DONE) {
/* a whole response has been parsed successfully */

View File

@ -133,8 +133,8 @@ typedef struct {
void ngx_http_v3_init(ngx_connection_t *c);
ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
ngx_http_chunked_t *ctx);
ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r);
ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r);
uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value);
uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,

View File

@ -19,6 +19,10 @@ 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);
static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r);
static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r);
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 {
@ -625,12 +629,18 @@ failed:
static ngx_int_t
ngx_http_v3_process_request_header(ngx_http_request_t *r)
{
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
c = r->connection;
if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
return NGX_ERROR;
}
if (r->headers_in.server.len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent neither \":authority\" nor \"Host\" header");
goto failed;
}
@ -642,7 +652,7 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r)
r->headers_in.server.len)
!= 0)
{
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent \":authority\" and \"Host\" headers "
"with different values");
goto failed;
@ -655,10 +665,32 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r)
r->headers_in.content_length->value.len);
if (r->headers_in.content_length_n == NGX_ERROR) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid \"Content-Length\" header");
goto failed;
}
} else {
b = r->header_in;
n = b->last - b->pos;
if (n == 0) {
n = c->recv(c, b->start, b->end - b->start);
if (n == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (n > 0) {
b->pos = b->start;
b->last = b->start + n;
}
}
if (n != 0) {
r->headers_in.chunked = 1;
}
}
return NGX_OK;
@ -671,74 +703,457 @@ failed:
ngx_int_t
ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
ngx_http_chunked_t *ctx)
ngx_http_v3_read_request_body(ngx_http_request_t *r)
{
size_t preread;
ngx_int_t rc;
ngx_chain_t *cl, out;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
rb = r->request_body;
preread = r->header_in->last - r->header_in->pos;
if (preread) {
/* there is the pre-read part of the request body */
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 client request body preread %uz", preread);
out.buf = r->header_in;
out.next = NULL;
cl = &out;
} else {
cl = NULL;
}
rc = ngx_http_v3_request_body_filter(r, cl);
if (rc != NGX_OK) {
return rc;
}
if (rb->rest == 0) {
/* the whole request body was pre-read */
r->request_body_no_buffering = 0;
rb->post_handler(r);
return NGX_OK;
}
if (rb->rest < 0) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"negative request body rest");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size);
if (rb->buf == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->read_event_handler = ngx_http_v3_read_client_request_body_handler;
r->write_event_handler = ngx_http_request_empty_handler;
return ngx_http_v3_do_read_client_request_body(r);
}
static void
ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
if (r->connection->read->timedout) {
r->connection->timedout = 1;
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
rc = ngx_http_v3_do_read_client_request_body(r);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
ngx_http_finalize_request(r, rc);
}
}
ngx_int_t
ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r)
{
ngx_int_t rc;
if (r->connection->read->timedout) {
r->connection->timedout = 1;
return NGX_HTTP_REQUEST_TIME_OUT;
}
rc = ngx_http_v3_do_read_client_request_body(r);
if (rc == NGX_OK) {
r->reading_body = 0;
}
return rc;
}
static ngx_int_t
ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r)
{
off_t rest;
size_t size;
ssize_t n;
ngx_int_t rc;
ngx_chain_t out;
ngx_connection_t *c;
ngx_http_v3_parse_data_t *st;
enum {
sw_start = 0,
sw_skip
};
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
st = ctx->h3_parse;
rb = r->request_body;
if (st == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse request body");
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 read client request body");
st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t));
if (st == NULL) {
goto failed;
for ( ;; ) {
for ( ;; ) {
if (rb->buf->last == rb->buf->end) {
/* update chains */
rc = ngx_http_v3_request_body_filter(r, NULL);
if (rc != NGX_OK) {
return rc;
}
if (rb->busy != NULL) {
if (r->request_body_no_buffering) {
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"busy buffers after request body flush");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
rb->buf->pos = rb->buf->start;
rb->buf->last = rb->buf->start;
}
size = rb->buf->end - rb->buf->last;
rest = rb->rest - (rb->buf->last - rb->buf->pos);
if ((off_t) size > rest) {
size = (size_t) rest;
}
n = c->recv(c, rb->buf->last, size);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 client request body recv %z", n);
if (n == NGX_AGAIN) {
break;
}
if (n == 0) {
rb->buf->last_buf = 1;
}
if (n == NGX_ERROR) {
c->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
rb->buf->last += n;
/* pass buffer to request body filter chain */
out.buf = rb->buf;
out.next = NULL;
rc = ngx_http_v3_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
if (rb->rest == 0) {
break;
}
if (rb->buf->last < rb->buf->end) {
break;
}
}
ctx->h3_parse = st;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 client request body rest %O", rb->rest);
if (rb->rest == 0) {
break;
}
if (!c->read->ready) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_add_timer(c->read, clcf->client_body_timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
}
while (b->pos < b->last && ctx->size == 0) {
rc = ngx_http_v3_parse_data(c, st, *b->pos++);
if (rc > 0) {
ngx_http_v3_finalize_connection(c, rc,
"could not parse request body");
goto failed;
}
if (rc == NGX_ERROR) {
goto failed;
}
if (rc == NGX_AGAIN) {
ctx->state = sw_skip;
continue;
}
if (rc == NGX_DONE) {
return NGX_DONE;
}
/* rc == NGX_OK */
ctx->size = st->length;
ctx->state = sw_start;
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (ctx->state == sw_skip) {
ctx->length = 1;
return NGX_AGAIN;
}
if (b->pos == b->last) {
ctx->length = ctx->size;
return NGX_AGAIN;
if (!r->request_body_no_buffering) {
r->read_event_handler = ngx_http_block_reading;
rb->post_handler(r);
}
return NGX_OK;
failed:
return NGX_ERROR;
}
static ngx_int_t
ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
off_t max;
size_t size;
ngx_int_t rc;
ngx_buf_t *b;
ngx_uint_t last;
ngx_chain_t *cl, *out, *tl, **ll;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
ngx_http_v3_parse_data_t *st;
rb = r->request_body;
st = r->h3_parse;
if (rb->rest == -1) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 request body filter");
st = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_data_t));
if (st == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->h3_parse = st;
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
rb->rest = cscf->large_client_header_buffers.size;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
max = r->headers_in.content_length_n;
if (max == -1 && clcf->client_max_body_size) {
max = clcf->client_max_body_size;
}
out = NULL;
ll = &out;
last = 0;
for (cl = in; cl; cl = cl->next) {
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
"http3 body buf "
"t:%d f:%d %p, pos %p, size: %z file: %O, size: %O",
cl->buf->temporary, cl->buf->in_file,
cl->buf->start, cl->buf->pos,
cl->buf->last - cl->buf->pos,
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
if (cl->buf->last_buf) {
last = 1;
}
b = NULL;
while (cl->buf->pos < cl->buf->last) {
if (st->length == 0) {
r->request_length++;
rc = ngx_http_v3_parse_data(r->connection, st, *cl->buf->pos++);
if (rc == NGX_AGAIN) {
continue;
}
if (rc == NGX_DONE) {
last = 1;
goto done;
}
if (rc > 0) {
ngx_http_v3_finalize_connection(r->connection, rc,
"client sent invalid body");
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent invalid body");
return NGX_HTTP_BAD_REQUEST;
}
if (rc == NGX_ERROR) {
ngx_http_v3_finalize_connection(r->connection,
NGX_HTTP_V3_ERR_INTERNAL_ERROR,
"internal error");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* rc == NGX_OK */
}
if (max != -1 && (uint64_t) (max - rb->received) < st->length) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client intended to send too large "
"body: %O+%uL bytes",
rb->received, st->length);
return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
}
if (b
&& st->length <= 128
&& (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length)
{
rb->received += st->length;
r->request_length += st->length;
if (st->length < 8) {
while (st->length) {
*b->last++ = *cl->buf->pos++;
st->length--;
}
} else {
ngx_memmove(b->last, cl->buf->pos, st->length);
b->last += st->length;
cl->buf->pos += st->length;
st->length = 0;
}
continue;
}
tl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (tl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b = tl->buf;
ngx_memzero(b, sizeof(ngx_buf_t));
b->temporary = 1;
b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body;
b->start = cl->buf->pos;
b->pos = cl->buf->pos;
b->last = cl->buf->last;
b->end = cl->buf->end;
b->flush = r->request_body_no_buffering;
*ll = tl;
ll = &tl->next;
size = cl->buf->last - cl->buf->pos;
if (size > st->length) {
cl->buf->pos += (size_t) st->length;
rb->received += st->length;
r->request_length += st->length;
st->length = 0;
} else {
st->length -= size;
rb->received += size;
r->request_length += size;
cl->buf->pos = cl->buf->last;
}
b->last = cl->buf->pos;
}
}
done:
if (last) {
if (st->length > 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client prematurely closed stream");
r->connection->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
if (r->headers_in.content_length_n == -1) {
r->headers_in.content_length_n = rb->received;
} else if (r->headers_in.content_length_n != rb->received) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent less body data than expected: "
"%O out of %O bytes of request body received",
rb->received, r->headers_in.content_length_n);
return NGX_HTTP_BAD_REQUEST;
}
rb->rest = 0;
tl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (tl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b = tl->buf;
ngx_memzero(b, sizeof(ngx_buf_t));
b->last_buf = 1;
*ll = tl;
ll = &tl->next;
} else {
/* set rb->rest, amount of data we want to see next time */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
rb->rest = (off_t) cscf->large_client_header_buffers.size;
}
rc = ngx_http_top_request_body_filter(r, out);
ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out,
(ngx_buf_tag_t) &ngx_http_read_client_request_body);
return rc;
}