HTTP: Use common validation for :method pseudo-headers

The code was duplicated between HTTP/2 and HTTP/3.  No functional change
intended.
This commit is contained in:
Demi Marie Obenour 2025-09-29 13:47:08 -04:00
parent 3f28843859
commit b75ebbaacc
4 changed files with 102 additions and 158 deletions

View File

@ -188,6 +188,11 @@ size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
*/
ngx_int_t ngx_http_v23_fixup_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value);
/*
* Parse an HTTP/2 and/or HTTP/3 method.
*/
ngx_int_t
ngx_http_v23_parse_method(ngx_http_request_t *r, ngx_str_t *value);
#endif

View File

@ -1137,6 +1137,101 @@ header_done:
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
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 inline ngx_int_t
ngx_isspace(u_char ch)

View File

@ -146,8 +146,6 @@ 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,
@ -3251,7 +3249,7 @@ ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header)
if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1)
== 0)
{
return ngx_http_v2_parse_method(r, &header->value);
return ngx_http_v23_parse_method(r, &header->value);
}
if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1)
@ -3319,102 +3317,6 @@ 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)
{
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)
{

View File

@ -29,30 +29,6 @@ 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)
{
@ -704,44 +680,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
}
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");
if (ngx_http_v23_parse_method(r, value) != NGX_OK) {
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;