This commit is contained in:
Demi Marie Obenour 2025-07-11 20:51:04 +02:00 committed by GitHub
commit 31696f8481
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 738 additions and 856 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)
{

View File

@ -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;
}