This commit is contained in:
Demi Marie Obenour 2025-07-16 20:05:23 +02:00 committed by GitHub
commit b93ca6e65c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 139 additions and 109 deletions

View File

@ -183,6 +183,12 @@ 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); 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, size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
ngx_uint_t lower); 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);
#endif #endif

View File

@ -10,6 +10,11 @@
#include <ngx_http.h> #include <ngx_http.h>
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
static inline ngx_int_t ngx_isspace(u_char ch);
#endif
static uint32_t usual[] = { static uint32_t usual[] = {
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
@ -972,6 +977,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
case sw_space_before_value: case sw_space_before_value:
switch (ch) { switch (ch) {
case ' ': case ' ':
case '\t':
break; break;
case CR: case CR:
r->header_start = p; r->header_start = p;
@ -996,6 +1002,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
case sw_value: case sw_value:
switch (ch) { switch (ch) {
case ' ': case ' ':
case '\t':
r->header_end = p; r->header_end = p;
state = sw_space_after_value; state = sw_space_after_value;
break; break;
@ -1016,6 +1023,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
case sw_space_after_value: case sw_space_after_value:
switch (ch) { switch (ch) {
case ' ': case ' ':
case '\t':
break; break;
case CR: case CR:
state = sw_almost_done; state = sw_almost_done;
@ -1090,6 +1098,129 @@ header_done:
} }
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
static inline ngx_int_t
ngx_isspace(u_char ch)
{
return ch == ' ' || ch == '\t';
}
ngx_int_t
ngx_http_v23_fixup_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_str_t *value)
{
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 (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;
}
/* Keep subsequent code from having to special-case empty strings. */
if (value->len == 0) {
return NGX_OK;
}
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;
}
}
tmp = *value;
if (!ngx_isspace(tmp.data[0])
&& !ngx_isspace(tmp.data[tmp.len - 1])) {
/* Fast path: nothing to strip. */
return NGX_OK;
}
/*
* 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(). After the loop, the string is either
* empty or ends with a non-whitespace character.
*/
while (tmp.len && ngx_isspace(tmp.data[tmp.len - 1])) {
tmp.len--;
}
/* Strip leading whitespace */
if (tmp.len && ngx_isspace(tmp.data[0])) {
/*
* Last loop guaranteed that 'tmp' does not end with whitespace, and
* this check guarantees it is not empty and starts with whitespace.
* Therefore, 'tmp' must end with a non-whitespace character, and must
* be of length at least 2. This means that it is safe to keep going
* until a non-whitespace character is found.
*/
do {
tmp.len--;
tmp.data++;
} while (ngx_isspace(tmp.data[0]));
/* 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_int_t
ngx_http_parse_uri(ngx_http_request_t *r) ngx_http_parse_uri(ngx_http_request_t *r)
{ {

View File

@ -142,8 +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, static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c,
ngx_http_v2_out_frame_t *frame); 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, static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r,
ngx_http_v2_header_t *header); ngx_http_v2_header_t *header);
static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r,
@ -1774,7 +1772,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos,
fc = r->connection; fc = r->connection;
/* TODO Optimization: validate headers while parsing. */ /* 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); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
goto error; goto error;
} }
@ -3232,58 +3230,6 @@ ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c)
} }
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 static ngx_int_t
ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header)
{ {

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 void ngx_http_v3_process_request(ngx_event_t *rev);
static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value); 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, static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value); ngx_str_t *name, ngx_str_t *value);
static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r);
@ -632,7 +630,7 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name,
r->v3_parse->header_limit -= len; 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); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR; return NGX_ERROR;
} }
@ -692,57 +690,6 @@ 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 static ngx_int_t
ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_str_t *value) ngx_str_t *value)