Mercurial > hg > nginx-quic
changeset 8282:6bd8ed493b85 quic
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.
author | Roman Arutyunyan <arut@nginx.com> |
---|---|
date | Mon, 25 Jan 2021 16:16:47 +0300 |
parents | a346905c359f |
children | a9034b10dacc |
files | src/http/ngx_http.h src/http/ngx_http_request_body.c src/http/v3/ngx_http_v3.h src/http/v3/ngx_http_v3_request.c |
diffstat | 4 files changed, 491 insertions(+), 89 deletions(-) [+] |
line wrap: on
line diff
--- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -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 };
--- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -87,6 +87,13 @@ ngx_http_read_client_request_body(ngx_ht } #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(ng } #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 } 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_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_ 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_ 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 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 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 */
--- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -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,
--- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -19,6 +19,10 @@ static ngx_int_t ngx_http_v3_process_pse 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_h 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_h 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_connection_t *c; - ngx_http_v3_parse_data_t *st; - enum { - sw_start = 0, - sw_skip - }; + 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; + } - c = r->connection; - st = ctx->h3_parse; + 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; + } - if (st == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse request body"); + 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; - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t)); - if (st == NULL) { - goto failed; - } + 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); + } +} + - ctx->h3_parse = st; +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; } - while (b->pos < b->last && ctx->size == 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_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + rb = r->request_body; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read client request body"); - rc = ngx_http_v3_parse_data(c, st, *b->pos++); + 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"); - if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request body"); - goto failed; - } + 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 (rc == NGX_ERROR) { - goto failed; - } + 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 */ - if (rc == NGX_AGAIN) { - ctx->state = sw_skip; - continue; + 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; + } } - if (rc == NGX_DONE) { - return NGX_DONE; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body rest %O", rb->rest); + + if (rb->rest == 0) { + break; } - /* rc == NGX_OK */ + 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); - ctx->size = st->length; - ctx->state = sw_start; + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } } - if (ctx->state == sw_skip) { - ctx->length = 1; - return NGX_AGAIN; + if (c->read->timer_set) { + ngx_del_timer(c->read); } - 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: + +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; + } - return NGX_ERROR; + 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; }