diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 8d5385c1d..4eb0d8a0c 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -2658,7 +2658,24 @@ ngx_http_proxy_process_trailer(ngx_http_request_t *r, ngx_buf_t *buf) if (rc == NGX_OK) { - /* a header line has been parsed successfully */ + /* A trailer line has been parsed successfully. + * Do not allow trailers that would, if turned into + * headers, interfere with request framing. */ + switch (r->header_name_end - r->header_name_start) { +#define X(x) \ + case sizeof(x "") - 1: \ + /* The size is always less than the number of bytes in \ + * the pre-casefolded area. */ \ + if (memcmp(r->lowcase_header, x, sizeof(x) - 1) == 0) { \ + return NGX_ERROR; \ + } else break + X("transfer-encoding"); + X("content-length"); + X("upgrade"); +#undef X + default: + break; + } h = ngx_list_push(&r->upstream->headers_in.trailers); if (h == NULL) { diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 7f2b4225a..7c92fbec0 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -68,6 +68,17 @@ static ngx_int_t ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, ngx_http_conf_addr_t *addr); #endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3) +static ngx_int_t ngx_http_v23_parse_path(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v23_parse_method(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v23_parse_scheme(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v23_parse_authority(ngx_http_request_t *r, + ngx_str_t *value); +#endif + ngx_uint_t ngx_http_max_module; @@ -1228,6 +1239,295 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, return ngx_http_add_address(cf, cscf, port, lsopt); } +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + +ngx_int_t +ngx_http_v23_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_int_t rc; + + name->len--; + name->data++; + + switch (name->len) { + case 4: + if (ngx_memcmp(name->data, "path", sizeof("path") - 1) + == 0) + { + rc = ngx_http_v23_parse_path(r, value); + goto known; + } + + break; + + case 6: + if (ngx_memcmp(name->data, "method", sizeof("method") - 1) + == 0) + { + rc = ngx_http_v23_parse_method(r, value); + goto known; + } + + if (ngx_memcmp(name->data, "scheme", sizeof("scheme") - 1) + == 0) + { + rc = ngx_http_v23_parse_scheme(r, value); + goto known; + } + + break; + + case 9: + if (ngx_memcmp(name->data, "authority", sizeof("authority") - 1) + == 0) + { + rc = ngx_http_v23_parse_authority(r, value); + goto known; + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \":%V\"", + name); + rc = NGX_DECLINED; +known: + if (rc == NGX_DECLINED) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + rc = NGX_ABORT; + } + return rc; +} + + +static ngx_int_t +ngx_http_v23_parse_path(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v23_parse_method(ngx_http_request_t *r, ngx_str_t *value) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE }, + { 7, "CONNECT", NGX_HTTP_CONNECT } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = value->len; + r->method_name.data = value->data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v23_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) +{ + u_char c, ch; + ngx_uint_t i; + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :scheme header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :scheme header"); + + return NGX_DECLINED; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :scheme header: \"%V\"", value); + + return NGX_DECLINED; + } + + r->schema = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v23_parse_authority(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t host = ngx_string("host"); + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't'); + + h->key.len = host.len; + h->key.data = host.data; + + h->value.len = value->len; + h->value.data = value->data; + + h->lowcase_key = host.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + +#endif static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 7d98f5cd7..54739209b 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -97,7 +97,6 @@ int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); int ngx_http_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg); #endif - ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b); ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r); ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r, @@ -183,6 +182,14 @@ ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log); size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower); +/* + * Check if a header name and/or value is valid. If the value is valid, + * strip leading and trailing space from it. + */ +ngx_int_t ngx_http_v23_fixup_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v23_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); #endif diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index c75ddb849..541f252b3 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -259,6 +259,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_srv_conf_t, ignore_invalid_headers), NULL }, + { ngx_string("reject_leading_trailing_whitespace"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_core_srv_conf_t, reject_leading_trailing_whitespace), + NULL }, + { ngx_string("merge_slashes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -3514,6 +3521,7 @@ ngx_http_core_create_srv_conf(ngx_conf_t *cf) cscf->ignore_invalid_headers = NGX_CONF_UNSET; cscf->merge_slashes = NGX_CONF_UNSET; cscf->underscores_in_headers = NGX_CONF_UNSET; + cscf->reject_leading_trailing_whitespace = NGX_CONF_UNSET; cscf->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; @@ -3560,6 +3568,9 @@ ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->underscores_in_headers, prev->underscores_in_headers, 0); + ngx_conf_merge_value(conf->reject_leading_trailing_whitespace, + prev->reject_leading_trailing_whitespace, 0); + if (conf->server_names.nelts == 0) { /* the array has 4 empty preallocated elements, so push cannot fail */ sn = ngx_array_push(&conf->server_names); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index a794144aa..4d4f2bfc3 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -208,6 +208,7 @@ typedef struct { #endif ngx_http_core_loc_conf_t **named_locations; + ngx_flag_t reject_leading_trailing_whitespace; } ngx_http_core_srv_conf_t; diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index a45c04554..f7214ca55 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -97,6 +97,11 @@ static uint32_t usual[] = { #endif +static inline ngx_int_t +ngx_http_field_value_char(u_char ch) +{ + return ch >= 0x20 ? ch != 0x7f : ch == 0x09; +} /* gcc, icc, msvc and others compile these switches as an jump table */ @@ -492,10 +497,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->http_minor = 9; state = sw_almost_done; break; - case LF: - r->uri_end = p; - r->http_minor = 9; - goto done; case '.': r->complex_uri = 1; state = sw_uri; @@ -565,10 +566,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->http_minor = 9; state = sw_almost_done; break; - case LF: - r->uri_end = p; - r->http_minor = 9; - goto done; #if (NGX_WIN32) case '\\': r->complex_uri = 1; @@ -615,10 +612,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->http_minor = 9; state = sw_almost_done; break; - case LF: - r->uri_end = p; - r->http_minor = 9; - goto done; case '#': r->complex_uri = 1; break; @@ -639,9 +632,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->http_minor = 9; state = sw_almost_done; break; - case LF: - r->http_minor = 9; - goto done; case 'H': r->http_protocol.data = p; state = sw_http_H; @@ -742,10 +732,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) break; } - if (ch == LF) { - goto done; - } - if (ch == ' ') { state = sw_spaces_after_digit; break; @@ -769,8 +755,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case CR: state = sw_almost_done; break; - case LF: - goto done; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } @@ -811,6 +795,40 @@ done: return NGX_OK; } +static ngx_int_t +ngx_http_non_alnum_dash_header_char(u_char ch) +{ + switch (ch) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return 1; + default: + return 0; + } +} + +static ngx_int_t +ngx_http_token_char(u_char ch) +{ + u_char c = (ch | 0x20); + if (('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '-') { + return 1; + } + + return ngx_http_non_alnum_dash_header_char(ch); +} ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, @@ -824,7 +842,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, sw_space_before_value, sw_value, sw_space_after_value, - sw_ignore_line, sw_almost_done, sw_header_almost_done } state; @@ -860,9 +877,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, r->header_end = p; state = sw_header_almost_done; break; - case LF: - r->header_end = p; - goto header_done; default: state = sw_name; @@ -875,22 +889,14 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == '_') { - if (allow_underscores) { - hash = ngx_hash(0, ch); - r->lowcase_header[0] = ch; - i = 1; - - } else { - hash = 0; - i = 0; - r->invalid_header = 1; - } - + if (ch == '_' && allow_underscores) { + hash = ngx_hash(0, ch); + r->lowcase_header[0] = ch; + i = 1; break; } - if (ch <= 0x20 || ch == 0x7f || ch == ':') { + if (!ngx_http_non_alnum_dash_header_char(ch)) { r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -942,24 +948,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == LF) { - r->header_name_end = p; - r->header_start = p; - r->header_end = p; - goto done; - } - - /* IIS may send the duplicate "HTTP/1.1 ..." lines */ - if (ch == '/' - && r->upstream - && p - r->header_name_start == 4 - && ngx_strncmp(r->header_name_start, "HTTP", 4) == 0) - { - state = sw_ignore_line; - break; - } - - if (ch <= 0x20 || ch == 0x7f) { + if (!ngx_http_non_alnum_dash_header_char(ch)) { r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -972,23 +961,21 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case sw_space_before_value: switch (ch) { case ' ': + case '\t': break; case CR: r->header_start = p; r->header_end = p; state = sw_almost_done; break; - case LF: - r->header_start = p; - r->header_end = p; - goto done; - case '\0': + default: + if (ch > 0x20 && ch != 0x7f) { + r->header_start = p; + state = sw_value; + break; + } r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; - default: - r->header_start = p; - state = sw_value; - break; } break; @@ -996,6 +983,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case sw_value: switch (ch) { case ' ': + case '\t': r->header_end = p; state = sw_space_after_value; break; @@ -1003,10 +991,9 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, r->header_end = p; state = sw_almost_done; break; - case LF: - r->header_end = p; - goto done; - case '\0': + default: + if (ch > 0x20 && ch != 0x7f) + break; r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1016,52 +1003,42 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case sw_space_after_value: switch (ch) { case ' ': + case '\t': break; case CR: state = sw_almost_done; break; - case LF: - goto done; - case '\0': + default: + if (ch > 0x20 && ch != 0x7f) { + state = sw_value; + break; + } r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; - default: - state = sw_value; - break; - } - break; - - /* ignore header line */ - case sw_ignore_line: - switch (ch) { - case LF: - state = sw_start; - break; - default: - break; } break; /* end of header line */ case sw_almost_done: - switch (ch) { - case LF: - goto done; - case CR: - break; - default: + if (ch != LF) { return NGX_HTTP_PARSE_INVALID_HEADER; } - break; + + b->pos = p + 1; + r->state = sw_start; + r->header_hash = hash; + r->lowcase_index = i; + return NGX_OK; /* end of header */ case sw_header_almost_done: - switch (ch) { - case LF: - goto header_done; - default: + if (ch != LF) { return NGX_HTTP_PARSE_INVALID_HEADER; } + + b->pos = p + 1; + r->state = sw_start; + return NGX_HTTP_PARSE_HEADER_DONE; } } @@ -1071,25 +1048,156 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, r->lowcase_index = i; return NGX_AGAIN; - -done: - - b->pos = p + 1; - r->state = sw_start; - r->header_hash = hash; - r->lowcase_index = i; - - return NGX_OK; - -header_done: - - b->pos = p + 1; - r->state = sw_start; - - return NGX_HTTP_PARSE_HEADER_DONE; } +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + + +ngx_int_t +ngx_http_v23_fixup_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + int bad; + u_char ch; + ngx_str_t tmp; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (name->len < 1) { + ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, + "BUG: internal zero-length header name"); + + return NGX_ERROR; + } + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (!ngx_http_non_alnum_dash_header_char(ch)) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + /* Keep subsequent code from having to special-case empty strings. */ + if (value->len == 0) { + return NGX_OK; + } + + for (i = 0; i != value->len; i++) { + if (!ngx_http_field_value_char(value->data[i])) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value", name); + + return NGX_ERROR; + } + } + + bad = 0; + switch (name->len) { +#define X(s) \ + case sizeof("" s) - 1: \ + bad = memcmp(name->data, s, sizeof(s) - 1) == 0; \ + break + X("upgrade"); + X("transfer-encoding"); + X("proxy-connection"); + X("proxy-authorization"); + X("proxy-authenticate"); +#undef X + case 10: + bad = memcmp(name->data, "connection", 10) == 0 + || memcmp(name->data, "keep-alive", 10) == 0; + break; + case 2: + /* te: trailiers is allowed, all other te values forbidden */ + bad = name->data[0] == 't' && name->data[1] == 'e' + && !(value->len == 8 && memcmp(value->data, "trailers", 8) == 0); + break; + } + + if (bad) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent forbidden hop-by-hop header \"%V\" with " + "value: \"%V\"", name, value); + + return NGX_ERROR; + } + + tmp = *value; + + if (tmp.data[0] > 0x20 && tmp.data[tmp.len - 1] > 0x20) { + /* Fast path: nothing to strip. */ + return NGX_OK; + } + + if (cscf->reject_leading_trailing_whitespace) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "leading or trailing space", + name); + return NGX_ERROR; + } + + /* + * Strip trailing whitespace. Do this first so that + * if the string is all whitespace, tmp.data is not a + * past-the-end pointer (which cannot be safely passed + * to memmove()) + */ + while (tmp.len && tmp.data[tmp.len - 1] <= 0x20) { + tmp.len--; + } + + /* Strip leading whitespace */ + if (tmp.len && tmp.data[0] <= 0x20) { + /* + * Last loop guaranteed that 'tmp' does not end with whitespace, so + * it's safe to keep going until a non-whitespace character is found. + */ + do { + tmp.len--; + tmp.data++; + } while (tmp.data[0] <= 0x20); + + /* Move remaining string to start of buffer. */ + memmove(value->data, tmp.data, tmp.len); + } + + /* + * NUL-pad the data, so that if it was NUL-terminated before, it stil is. + * At least one byte will have been stripped, so value->data + tmp.len + * is not a past-the-end pointer. + */ + memset(value->data + tmp.len, '\0', value->len - tmp.len); + + /* Fix up length and return. */ + value->len = tmp.len; + return NGX_OK; +} +#endif + + ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r) { @@ -1784,8 +1892,6 @@ ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b, case CR: state = sw_almost_done; break; - case LF: - goto done; default: return NGX_ERROR; } @@ -1798,8 +1904,12 @@ ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b, state = sw_almost_done; break; - case LF: goto done; + default: + if (ch < 0x20 || ch == 0x7f) { + return NGX_ERROR; + } + break; } break; @@ -2147,25 +2257,25 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, enum { sw_chunk_start = 0, sw_chunk_size, + sw_chunk_extension_before_semi, sw_chunk_extension, + sw_chunk_extension_name, + sw_chunk_extension_value_start, + sw_chunk_extension_quoted_value, + sw_chunk_extension_value_quoted_backslash, + sw_chunk_extension_unquoted_value, sw_chunk_extension_almost_done, sw_chunk_data, - sw_after_data, sw_after_data_almost_done, - sw_last_chunk_extension, - sw_last_chunk_extension_almost_done, sw_trailer, sw_trailer_almost_done, - sw_trailer_header, + sw_trailer_name, + sw_trailer_value, sw_trailer_header_almost_done } state; state = ctx->state; - if (state == sw_chunk_data && ctx->size == 0) { - state = sw_after_data; - } - rc = NGX_AGAIN; for (pos = b->pos; pos < b->last; pos++) { @@ -2210,82 +2320,101 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, ctx->size = ctx->size * 16 + (c - 'a' + 10); break; } + /* fall through */ - if (ctx->size == 0) { - - switch (ch) { - case CR: - state = sw_last_chunk_extension_almost_done; - break; - case LF: - if (keep_trailers) { - goto done; - } - state = sw_trailer; - break; - case ';': - case ' ': - case '\t': - state = sw_last_chunk_extension; - break; - default: - goto invalid; - } - - break; - } - + case sw_chunk_extension_before_semi: +before_semi: switch (ch) { case CR: state = sw_chunk_extension_almost_done; break; - case LF: - state = sw_chunk_data; - break; case ';': - case ' ': - case '\t': state = sw_chunk_extension; break; default: goto invalid; } - break; case sw_chunk_extension: - switch (ch) { - case CR: - state = sw_chunk_extension_almost_done; + if (ngx_http_token_char(ch)) { + state = sw_chunk_extension_name; break; - case LF: - state = sw_chunk_data; } - break; + goto invalid; + + case sw_chunk_extension_name: + if (ngx_http_token_char(ch)) { + break; + } + if (ch == '=') { + state = sw_chunk_extension_value_start; + break; + } + goto invalid; + + case sw_chunk_extension_value_start: + if (ngx_http_token_char(ch)) { + state = sw_chunk_extension_unquoted_value; + break; + } + if (ch == '"') { + state = sw_chunk_extension_quoted_value; + break; + } + goto invalid; + + case sw_chunk_extension_quoted_value: + if (ch == '"') { + state = sw_chunk_extension_before_semi; + break; + } + if (ch == '\\') { + state = sw_chunk_extension_value_quoted_backslash; + break; + } + if (ngx_http_field_value_char(ch)) { + break; + } + goto invalid; + + case sw_chunk_extension_value_quoted_backslash: + if (ngx_http_field_value_char(ch)) { + state = sw_chunk_extension_quoted_value; + break; + } + goto invalid; + + case sw_chunk_extension_unquoted_value: + if (ngx_http_token_char(ch)) { + break; + } + goto before_semi; case sw_chunk_extension_almost_done: if (ch == LF) { - state = sw_chunk_data; + if (ctx->size) { + state = sw_chunk_data; + break; + } + if (keep_trailers) { + goto done; + } + state = sw_trailer; break; } goto invalid; case sw_chunk_data: - rc = NGX_OK; - goto data; - - case sw_after_data: - switch (ch) { - case CR: + if (ctx->size != 0) { + rc = NGX_OK; + goto data; + } + if (ch == CR) { state = sw_after_data_almost_done; break; - case LF: - state = sw_chunk_start; - break; - default: - goto invalid; } - break; + goto invalid; case sw_after_data_almost_done: if (ch == LF) { @@ -2294,56 +2423,63 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, } goto invalid; - case sw_last_chunk_extension: - switch (ch) { - case CR: - state = sw_last_chunk_extension_almost_done; + case sw_trailer: + if (ch == CR) { + state = sw_trailer_almost_done; break; - case LF: - if (keep_trailers) { - goto done; - } - state = sw_trailer; } - break; - - case sw_last_chunk_extension_almost_done: - if (ch == LF) { - if (keep_trailers) { - goto done; - } - state = sw_trailer; + if (ngx_http_token_char(ch)) { + state = sw_trailer_name; + r->lowcase_index = 1; + r->lowcase_header[0] = (ch | 0x20); break; } goto invalid; - case sw_trailer: - switch (ch) { - case CR: - state = sw_trailer_almost_done; - break; - case LF: - goto done; - default: - state = sw_trailer_header; - } - break; - case sw_trailer_almost_done: if (ch == LF) { goto done; } goto invalid; - case sw_trailer_header: - switch (ch) { - case CR: + case sw_trailer_name: + if (ngx_http_token_char(ch)) { + if (r->lowcase_index < NGX_HTTP_LC_HEADER_LEN) { + /* ASCII uppercase letters become the lowercase ones. + * '-' is unchanged. */ + r->lowcase_header[r->lowcase_index++] = (ch | 0x20); + } + break; + } + if (ch == ':') { + switch (r->lowcase_index) { +#define X(v) \ + case sizeof(v "") - 1: \ + if (memcmp(r->lowcase_header, v, r->lowcase_index) != 0) { \ + goto invalid; \ + } \ + break + X("transfer-encoding"); + X("content-length"); + X("upgrade"); +#undef X + default: + break; + } + state = sw_trailer_value; + break; + } + goto invalid; + + case sw_trailer_value: + if (ngx_http_field_value_char(ch)) { + break; + } + if (ch == CR) { state = sw_trailer_header_almost_done; break; - case LF: - state = sw_trailer; } - break; + goto invalid; case sw_trailer_header_almost_done: if (ch == LF) { @@ -2360,44 +2496,57 @@ data: ctx->state = state; b->pos = pos; - if (ctx->size > NGX_MAX_OFF_T_VALUE - 5) { + if (ctx->size > NGX_MAX_OFF_T_VALUE - 11) { goto invalid; } - + off_t min_length = (ctx->size ? ctx->size + 6 /* CR LF "0" CR LF LF */ + : 1 /* LF */); switch (state) { - case sw_chunk_start: - ctx->length = 3 /* "0" LF LF */; + ctx->length = 4 /* "0" CR LF LF */; break; case sw_chunk_size: - ctx->length = 1 /* LF */ - + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */ - : 1 /* LF */); + case sw_chunk_extension_before_semi: + case sw_chunk_extension_unquoted_value: + ctx->length = 2 /* CR LF */ + min_length; + break; + case sw_chunk_extension_almost_done: + ctx->length = 1 /* LF */ + min_length; break; case sw_chunk_extension: - case sw_chunk_extension_almost_done: - ctx->length = 1 /* LF */ + ctx->size + 4 /* LF "0" LF LF */; + ctx->length = 5 /* a=b CR LF */ + min_length; + break; + case sw_chunk_extension_name: + ctx->length = 4 /* =b CR LF */ + min_length; + break; + case sw_trailer_name: + ctx->length = 3 /* : LF LF */; + break; + case sw_trailer_value: + ctx->length = 2 /* LF LF */; + break; + case sw_chunk_extension_value_start: + ctx->length = 3 /* b CR LF */ + min_length; + break; + case sw_chunk_extension_quoted_value: + ctx->length = 3 /* " CR LF */ + min_length; + break; + case sw_chunk_extension_value_quoted_backslash: + ctx->length = 4 /* a" CR LF */ + min_length; break; case sw_chunk_data: - ctx->length = ctx->size + 4 /* LF "0" LF LF */; + ctx->length = min_length; break; - case sw_after_data: case sw_after_data_almost_done: - ctx->length = 4 /* LF "0" LF LF */; - break; - case sw_last_chunk_extension: - case sw_last_chunk_extension_almost_done: - ctx->length = 2 /* LF LF */; + ctx->length = 5 /* LF "0" CR LF LF */; break; case sw_trailer: case sw_trailer_almost_done: ctx->length = 1 /* LF */; break; - case sw_trailer_header: case sw_trailer_header_almost_done: ctx->length = 2 /* LF LF */; break; - } return rc; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ceac8d307..092d25509 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2015,9 +2015,9 @@ ngx_http_process_request_header(ngx_http_request_t *r) } if (r->headers_in.transfer_encoding) { - if (r->http_version < NGX_HTTP_VERSION_11) { + if (r->http_version != NGX_HTTP_VERSION_11) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent HTTP/1.0 request with " + "client sent non-HTTP/1.1 request with " "\"Transfer-Encoding\" header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 91a28b228..daf0cffdc 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -142,18 +142,6 @@ static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame( static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); -static ngx_int_t ngx_http_v2_validate_header(ngx_http_request_t *r, - ngx_http_v2_header_t *header); -static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, - ngx_http_v2_header_t *header); -static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, - ngx_str_t *value); -static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, - ngx_str_t *value); -static ngx_int_t 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); 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); @@ -1774,13 +1762,13 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, fc = r->connection; /* TODO Optimization: validate headers while parsing. */ - if (ngx_http_v2_validate_header(r, header) != NGX_OK) { + if (ngx_http_v23_fixup_header(r, &header->name, &header->value) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } if (header->name.data[0] == ':') { - rc = ngx_http_v2_pseudo_header(r, header); + rc = ngx_http_v23_pseudo_header(r, &header->name, &header->value); if (rc == NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1794,11 +1782,6 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, goto error; } - if (rc == NGX_DECLINED) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - goto error; - } - return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } @@ -3231,334 +3214,6 @@ ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c) return node; } - -static ngx_int_t -ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) -{ - u_char ch; - ngx_uint_t i; - ngx_http_core_srv_conf_t *cscf; - - r->invalid_header = 0; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { - ch = header->name.data[i]; - - if ((ch >= 'a' && ch <= 'z') - || (ch == '-') - || (ch >= '0' && ch <= '9') - || (ch == '_' && cscf->underscores_in_headers)) - { - continue; - } - - if (ch <= 0x20 || ch == 0x7f || ch == ':' - || (ch >= 'A' && ch <= 'Z')) - { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", - &header->name); - - return NGX_ERROR; - } - - r->invalid_header = 1; - } - - for (i = 0; i != header->value.len; i++) { - ch = header->value.data[i]; - - if (ch == '\0' || ch == LF || ch == CR) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent header \"%V\" with " - "invalid value: \"%V\"", - &header->name, &header->value); - - return NGX_ERROR; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) -{ - header->name.len--; - header->name.data++; - - switch (header->name.len) { - case 4: - if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) - == 0) - { - return ngx_http_v2_parse_path(r, &header->value); - } - - break; - - case 6: - if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) - == 0) - { - return ngx_http_v2_parse_method(r, &header->value); - } - - if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) - == 0) - { - return ngx_http_v2_parse_scheme(r, &header->value); - } - - break; - - case 9: - if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) - == 0) - { - return ngx_http_v2_parse_authority(r, &header->value); - } - - break; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent unknown pseudo-header \":%V\"", - &header->name); - - return NGX_DECLINED; -} - - -static ngx_int_t -ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_str_t *value) -{ - if (r->unparsed_uri.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate :path header"); - - return NGX_DECLINED; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty :path header"); - - return NGX_DECLINED; - } - - r->uri_start = value->data; - r->uri_end = value->data + value->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid :path header: \"%V\"", value); - - return NGX_DECLINED; - } - - if (ngx_http_process_request_uri(r) != NGX_OK) { - /* - * request has been finalized already - * in ngx_http_process_request_uri() - */ - return NGX_ABORT; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_str_t *value) -{ - size_t k, len; - ngx_uint_t n; - const u_char *p, *m; - - /* - * This array takes less than 256 sequential bytes, - * and if typical CPU cache line size is 64 bytes, - * it is prefetched for 4 load operations. - */ - static const struct { - u_char len; - const u_char method[11]; - uint32_t value; - } tests[] = { - { 3, "GET", NGX_HTTP_GET }, - { 4, "POST", NGX_HTTP_POST }, - { 4, "HEAD", NGX_HTTP_HEAD }, - { 7, "OPTIONS", NGX_HTTP_OPTIONS }, - { 8, "PROPFIND", NGX_HTTP_PROPFIND }, - { 3, "PUT", NGX_HTTP_PUT }, - { 5, "MKCOL", NGX_HTTP_MKCOL }, - { 6, "DELETE", NGX_HTTP_DELETE }, - { 4, "COPY", NGX_HTTP_COPY }, - { 4, "MOVE", NGX_HTTP_MOVE }, - { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, - { 4, "LOCK", NGX_HTTP_LOCK }, - { 6, "UNLOCK", NGX_HTTP_UNLOCK }, - { 5, "PATCH", NGX_HTTP_PATCH }, - { 5, "TRACE", NGX_HTTP_TRACE }, - { 7, "CONNECT", NGX_HTTP_CONNECT } - }, *test; - - if (r->method_name.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate :method header"); - - return NGX_DECLINED; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty :method header"); - - return NGX_DECLINED; - } - - r->method_name.len = value->len; - r->method_name.data = value->data; - - len = r->method_name.len; - n = sizeof(tests) / sizeof(tests[0]); - test = tests; - - do { - if (len == test->len) { - p = r->method_name.data; - m = test->method; - k = len; - - do { - if (*p++ != *m++) { - goto next; - } - } while (--k); - - r->method = test->value; - return NGX_OK; - } - - next: - test++; - - } while (--n); - - p = r->method_name.data; - - do { - if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid method: \"%V\"", - &r->method_name); - - return NGX_DECLINED; - } - - p++; - - } while (--len); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) -{ - u_char c, ch; - ngx_uint_t i; - - if (r->schema.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate :scheme header"); - - return NGX_DECLINED; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty :scheme header"); - - return NGX_DECLINED; - } - - for (i = 0; i < value->len; i++) { - ch = value->data[i]; - - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - continue; - } - - if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') - && i > 0) - { - continue; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid :scheme header: \"%V\"", value); - - return NGX_DECLINED; - } - - r->schema = *value; - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) -{ - ngx_table_elt_t *h; - ngx_http_header_t *hh; - ngx_http_core_main_conf_t *cmcf; - - static ngx_str_t host = ngx_string("host"); - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - return NGX_ERROR; - } - - h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't'); - - h->key.len = host.len; - h->key.data = host.data; - - h->value.len = value->len; - h->value.data = value->data; - - h->lowcase_key = host.data; - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh == NULL) { - return NGX_ERROR; - } - - if (hh->handler(r, h, hh->offset) != NGX_OK) { - /* - * request has been finalized already - * in ngx_http_process_host() - */ - return NGX_ABORT; - } - - return NGX_OK; -} - - static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r) { diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e41ad50a8..3e22b4414 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -17,8 +17,6 @@ static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); -static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, - ngx_str_t *name, ngx_str_t *value); 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); @@ -30,31 +28,6 @@ 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 { - ngx_str_t name; - ngx_uint_t method; -} ngx_http_v3_methods[] = { - - { ngx_string("GET"), NGX_HTTP_GET }, - { ngx_string("POST"), NGX_HTTP_POST }, - { ngx_string("HEAD"), NGX_HTTP_HEAD }, - { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, - { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, - { ngx_string("PUT"), NGX_HTTP_PUT }, - { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, - { ngx_string("DELETE"), NGX_HTTP_DELETE }, - { ngx_string("COPY"), NGX_HTTP_COPY }, - { ngx_string("MOVE"), NGX_HTTP_MOVE }, - { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, - { ngx_string("LOCK"), NGX_HTTP_LOCK }, - { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, - { ngx_string("PATCH"), NGX_HTTP_PATCH }, - { ngx_string("TRACE"), NGX_HTTP_TRACE }, - { ngx_string("CONNECT"), NGX_HTTP_CONNECT } -}; - - void ngx_http_v3_init_stream(ngx_connection_t *c) { @@ -391,6 +364,14 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) c->data = r; c->requests = (c->quic->id >> 2) + 1; + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); @@ -632,11 +613,15 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, r->v3_parse->header_limit -= len; - if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + if (ngx_http_v23_fixup_header(r, name, value) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } + if (r->invalid_header) { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); @@ -648,10 +633,6 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } } - if (name->len && name->data[0] == ':') { - return ngx_http_v3_process_pseudo_header(r, name, value); - } - if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { return NGX_ERROR; } @@ -692,208 +673,21 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } -static ngx_int_t -ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, - ngx_str_t *value) -{ - u_char ch; - ngx_uint_t i; - ngx_http_core_srv_conf_t *cscf; - - r->invalid_header = 0; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - for (i = (name->data[0] == ':'); i != name->len; i++) { - ch = name->data[i]; - - if ((ch >= 'a' && ch <= 'z') - || (ch == '-') - || (ch >= '0' && ch <= '9') - || (ch == '_' && cscf->underscores_in_headers)) - { - continue; - } - - if (ch <= 0x20 || ch == 0x7f || ch == ':' - || (ch >= 'A' && ch <= 'Z')) - { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", name); - - return NGX_ERROR; - } - - r->invalid_header = 1; - } - - for (i = 0; i != value->len; i++) { - ch = value->data[i]; - - if (ch == '\0' || ch == LF || ch == CR) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent header \"%V\" with " - "invalid value: \"%V\"", name, value); - - return NGX_ERROR; - } - } - - return NGX_OK; -} - - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { - u_char ch, c; - ngx_uint_t i; - if (r->request_line.len) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent out of order pseudo-headers"); - goto failed; + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; } - if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { - - if (r->method_name.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":method\" header"); - goto failed; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty \":method\" header"); - goto failed; - } - - r->method_name = *value; - - for (i = 0; i < sizeof(ngx_http_v3_methods) - / sizeof(ngx_http_v3_methods[0]); i++) - { - if (value->len == ngx_http_v3_methods[i].name.len - && ngx_strncmp(value->data, - ngx_http_v3_methods[i].name.data, value->len) - == 0) - { - r->method = ngx_http_v3_methods[i].method; - break; - } - } - - for (i = 0; i < value->len; i++) { - ch = value->data[i]; - - if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid method: \"%V\"", value); - goto failed; - } - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 method \"%V\" %ui", value, r->method); - return NGX_OK; + if (ngx_http_v23_pseudo_header(r, name, value) != NGX_OK) { + return NGX_ERROR; } - - if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { - - if (r->uri_start) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":path\" header"); - goto failed; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty \":path\" header"); - goto failed; - } - - r->uri_start = value->data; - r->uri_end = value->data + value->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid \":path\" header: \"%V\"", - value); - goto failed; - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 path \"%V\"", value); - return NGX_OK; - } - - if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { - - if (r->schema.len) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":scheme\" header"); - goto failed; - } - - if (value->len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent empty \":scheme\" header"); - goto failed; - } - - for (i = 0; i < value->len; i++) { - ch = value->data[i]; - - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - continue; - } - - if (((ch >= '0' && ch <= '9') - || ch == '+' || ch == '-' || ch == '.') - && i > 0) - { - continue; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid \":scheme\" header: \"%V\"", - value); - goto failed; - } - - r->schema = *value; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 schema \"%V\"", value); - return NGX_OK; - } - - if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { - - if (r->host_start) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent duplicate \":authority\" header"); - goto failed; - } - - r->host_start = value->data; - r->host_end = value->data + value->len; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 authority \"%V\"", value); - return NGX_OK; - } - - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent unknown pseudo-header \"%V\"", name); - -failed: - - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; + return NGX_OK; } @@ -981,14 +775,6 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) r->headers_in.server = host; } - if (ngx_list_init(&r->headers_in.headers, r->pool, 20, - sizeof(ngx_table_elt_t)) - != NGX_OK) - { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - return NGX_OK; failed: @@ -1028,38 +814,11 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } - if (r->headers_in.server.len == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent neither \":authority\" nor \"Host\" header"); - goto failed; + if (ngx_http_process_request_header(r) != NGX_OK) { + return NGX_ERROR; } - if (r->headers_in.host) { - if (r->headers_in.host->value.len != r->headers_in.server.len - || ngx_memcmp(r->headers_in.host->value.data, - r->headers_in.server.data, - r->headers_in.server.len) - != 0) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent \":authority\" and \"Host\" headers " - "with different values"); - 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, c->log, 0, - "client sent invalid \"Content-Length\" header"); - goto failed; - } - - } else { + if (!r->headers_in.content_length) { b = r->header_in; n = b->last - b->pos; @@ -1082,24 +841,7 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) } } - if (r->method == NGX_HTTP_CONNECT) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - return NGX_ERROR; - } - - if (r->method == NGX_HTTP_TRACE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - return NGX_ERROR; - } - return NGX_OK; - -failed: - - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; }