This commit is contained in:
Demi Marie Obenour 2026-05-22 03:28:07 +08:00 committed by GitHub
commit 178eaafbfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 267 additions and 145 deletions

View file

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

@ -101,6 +101,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 */
@ -866,6 +871,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' <= ch && ch <= '9') || ch == '-') {
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,
@ -879,7 +918,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;
@ -930,22 +968,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;
}
@ -989,32 +1019,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
break;
}
if (ch == CR) {
r->header_name_end = p;
r->header_start = p;
r->header_end = p;
state = sw_almost_done;
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;
}
@ -1027,6 +1032,7 @@ 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;
@ -1037,13 +1043,14 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
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;
@ -1051,6 +1058,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;
@ -1061,7 +1069,9 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
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;
}
@ -1071,29 +1081,20 @@ 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;
@ -1102,8 +1103,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
switch (ch) {
case LF:
goto done;
case CR:
break;
default:
return NGX_HTTP_PARSE_INVALID_HEADER;
}
@ -1857,6 +1856,11 @@ ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b,
break;
case LF:
goto done;
default:
if (ch < 0x20 || ch == 0x7f) {
return NGX_ERROR;
}
break;
}
break;
@ -2222,25 +2226,27 @@ 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_before_semi_bws,
sw_chunk_extension,
sw_chunk_extension_bws_before_equal,
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++) {
@ -2285,62 +2291,60 @@ 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 ';':
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 ';':
case ' ':
case '\t':
state = sw_chunk_extension;
break;
case ' ':
case '\t':
state = sw_chunk_extension_before_semi_bws;
break; /* BWS */
default:
goto invalid;
}
break;
case sw_chunk_extension:
case sw_chunk_extension_before_semi_bws:
switch (ch) {
case CR:
state = sw_chunk_extension_almost_done;
case ' ':
case '\t':
break;
case LF:
case ';':
state = sw_chunk_extension;
break;
default:
goto invalid;
}
break;
case sw_chunk_extension_almost_done:
if (ch == LF) {
state = sw_chunk_data;
case sw_chunk_extension:
if (ngx_http_token_char(ch)) {
state = sw_chunk_extension_name;
break;
}
goto invalid;
switch (ch) {
case ' ':
case '\t':
break; /* BWS */
default:
goto invalid;
}
break;
case sw_chunk_data:
rc = NGX_OK;
goto data;
if (ctx->size != 0) {
rc = NGX_OK;
goto data;
}
case sw_after_data:
switch (ch) {
case CR:
state = sw_after_data_almost_done;
@ -2357,18 +2361,79 @@ 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_chunk_extension_name:
if (ngx_http_token_char(ch)) {
break;
case LF:
}
state = sw_chunk_extension_bws_before_equal;
/* fall through */
case sw_chunk_extension_bws_before_equal:
switch (ch) {
case ' ':
case '\t':
break; /* BWS */
case '=':
state = sw_chunk_extension_value_start;
break;
default:
goto invalid;
}
break;
case sw_last_chunk_extension_almost_done:
case sw_chunk_extension_value_start:
if (ngx_http_token_char(ch)) {
state = sw_chunk_extension_unquoted_value;
break;
}
switch (ch) {
case ' ':
case '\t':
break; /* BWS */
case '"':
state = sw_chunk_extension_quoted_value;
break;
default:
goto invalid;
}
break;
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) {
if (ctx->size) {
state = sw_chunk_data;
break;
}
if (keep_trailers) {
goto done;
}
@ -2378,16 +2443,17 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
goto invalid;
case sw_trailer:
switch (ch) {
case CR:
if (ch == CR) {
state = sw_trailer_almost_done;
break;
case LF:
goto invalid;
default:
state = sw_trailer_header;
}
break;
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_almost_done:
if (ch == LF) {
@ -2395,15 +2461,44 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
}
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:
goto invalid;
}
break;
goto invalid;
case sw_trailer_header_almost_done:
if (ch == LF) {
@ -2420,53 +2515,63 @@ data:
ctx->state = state;
b->pos = pos;
if (ctx->size > NGX_MAX_OFF_T_VALUE - 5) {
if (ctx->size > NGX_MAX_OFF_T_VALUE - 13) {
goto invalid;
}
off_t min_length = (ctx->size ? ctx->size + 7 /* CRLF "0" CRLF CRLF */
: 2 /* CRLF */);
switch (state) {
case sw_chunk_start:
ctx->length = 5 /* "0" CRLF CRLF */;
break;
case sw_chunk_size:
ctx->length = 2 /* CRLF */
+ (ctx->size ? ctx->size + 7 /* CRLF "0" CRLF CRLF */
: 2 /* CRLF */);
break;
case sw_chunk_extension:
ctx->length = 2 /* CRLF */ + ctx->size + 7 /* CRLF "0" CRLF CRLF */;
case sw_chunk_extension_before_semi:
case sw_chunk_extension_unquoted_value:
ctx->length = 2 /* CRLF */ + min_length;
break;
case sw_chunk_extension_almost_done:
ctx->length = 1 /* LF */ + ctx->size + 7 /* CRLF "0" CRLF CRLF */;
ctx->length = 1 /* LF */ + min_length;
break;
case sw_chunk_extension_before_semi_bws:
ctx->length = 6 /* ;a=b CRLF */ + min_length;
break;
case sw_chunk_extension:
ctx->length = 5 /* a=b CRLF */ + min_length;
break;
case sw_chunk_extension_bws_before_equal:
case sw_chunk_extension_name:
ctx->length = 4 /* =b CRLF */ + min_length;
break;
case sw_chunk_extension_value_start:
ctx->length = 3 /* b CRLF */ + min_length;
break;
case sw_chunk_extension_quoted_value:
ctx->length = 3 /* " CRLF */ + min_length;
break;
case sw_chunk_extension_value_quoted_backslash:
ctx->length = 4 /* a" CRLF */ + min_length;
break;
case sw_chunk_data:
ctx->length = ctx->size + 7 /* CRLF "0" CRLF CRLF */;
break;
case sw_after_data:
ctx->length = 7 /* CRLF "0" CRLF CRLF */;
ctx->length = min_length;
break;
case sw_after_data_almost_done:
ctx->length = 6 /* LF "0" CRLF CRLF */;
break;
case sw_last_chunk_extension:
ctx->length = 4 /* CRLF CRLF */;
break;
case sw_last_chunk_extension_almost_done:
ctx->length = 3 /* LF CRLF */;
break;
case sw_trailer:
ctx->length = 2 /* CRLF */;
break;
case sw_trailer_almost_done:
ctx->length = 1 /* LF */;
case sw_trailer_name:
ctx->length = 5 /* : CRLF CRLF */;
break;
case sw_trailer_header:
case sw_trailer_value:
ctx->length = 4 /* CRLF CRLF */;
break;
case sw_trailer_header_almost_done:
ctx->length = 3 /* LF CRLF */;
break;
case sw_trailer_almost_done:
ctx->length = 1 /* LF */;
break;
}