From 4d147459db07ff57888f8323b5addb0deafd234c Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Tue, 22 Sep 2026 01:52:41 +0100 Subject: [PATCH] HTTP/2: parse Priority request header Parse the RFC 9218 Priority request header, an RFC 8941 Structured Fields Dictionary such as "u=3, i". The header lines are collected during header block processing and, once the block is complete, combined into a single value and parsed once in ngx_http_v2_run_request(), as RFC 8941, Section 4.2, requires all field lines of the same field to be combined before parsing. This mirrors how cookies are handled. The header is still passed through to the request so it stays visible to variables and proxying. A malformed field is ignored, leaving the defaults. A PRIORITY_UPDATE frame overrides the header (RFC 9218, Section 7): if a frame has already been applied to the stream the header is not consulted. Assisted-by: opencode:claude-opus-4.8 --- src/http/v2/ngx_http_v2.c | 124 ++++++++++++++++++++++++++++++++++++++ src/http/v2/ngx_http_v2.h | 2 + 2 files changed, 126 insertions(+) diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 4342ba262..093e6c548 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -160,6 +160,9 @@ 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); static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_priority(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_process_priority(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_construct_host_header(ngx_http_request_t *r); static void ngx_http_v2_run_request(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, @@ -1354,6 +1357,7 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, if (node->priority_set) { stream->priority.client = node->priority; node->priority_set = 0; + stream->priority_update = 1; ngx_http_priority_state_update(&stream->priority); @@ -1741,6 +1745,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, ngx_http_core_main_conf_t *cmcf; static ngx_str_t cookie = ngx_string("cookie"); + static ngx_str_t priority = ngx_string("priority"); header = &h2c->state.header; @@ -1839,6 +1844,15 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, } } + if (header->name.len == priority.len + && ngx_memcmp(header->name.data, priority.data, priority.len) == 0) + { + if (ngx_http_v2_priority(r, header) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + } + if (header->name.len == cookie.len && ngx_memcmp(header->name.data, cookie.data, cookie.len) == 0) { @@ -2201,6 +2215,7 @@ ngx_http_v2_state_priority_update(ngx_http_v2_connection_t *h2c, u_char *pos, } else if (node && node->stream) { stream = node->stream; stream->priority.client = priority; + stream->priority_update = 1; ngx_http_priority_state_update(&stream->priority); } @@ -3904,6 +3919,111 @@ ngx_http_v2_construct_cookie_header(ngx_http_request_t *r) } +static ngx_int_t +ngx_http_v2_priority(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + ngx_str_t *val; + ngx_array_t *priorities; + + priorities = r->stream->priorities; + + if (priorities == NULL) { + priorities = ngx_array_create(r->pool, 1, sizeof(ngx_str_t)); + if (priorities == NULL) { + return NGX_ERROR; + } + + r->stream->priorities = priorities; + } + + val = ngx_array_push(priorities); + if (val == NULL) { + return NGX_ERROR; + } + + val->len = header->value.len; + val->data = header->value.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_process_priority(ngx_http_request_t *r) +{ + u_char *p; + size_t len; + ngx_str_t value, *vals; + ngx_uint_t i; + ngx_array_t *priorities; + ngx_http_priority_t priority; + + priorities = r->stream->priorities; + + if (priorities == NULL) { + return NGX_OK; + } + + /* + * RFC 9218, Section 7: a PRIORITY_UPDATE frame overrides the Priority + * header field, so ignore the header once a frame has been applied. + */ + + if (r->stream->priority_update) { + return NGX_OK; + } + + vals = priorities->elts; + + if (priorities->nelts == 1) { + value = vals[0]; + + } else { + + /* combine the field lines into a single value, "v1, v2, ..." */ + + len = 0; + + for (i = 0; i < priorities->nelts; i++) { + len += vals[i].len + 2; + } + + len -= 2; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_v2_close_stream(r->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + value.data = p; + value.len = len; + + for (i = 0; /* void */ ; i++) { + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (i == priorities->nelts - 1) { + break; + } + + *p++ = ','; *p++ = ' '; + } + } + + if (ngx_http_priority_parse(&value, &priority) != NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 ignoring malformed priority header"); + return NGX_OK; + } + + r->stream->priority.client = priority; + ngx_http_priority_state_update(&r->stream->priority); + + return NGX_OK; +} + + static ngx_int_t ngx_http_v2_construct_host_header(ngx_http_request_t *r) { @@ -3980,6 +4100,10 @@ ngx_http_v2_run_request(ngx_http_request_t *r) goto failed; } + if (ngx_http_v2_process_priority(r) != NGX_OK) { + goto failed; + } + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; if (r->headers_in.connection) { diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index e35c23c3e..4106ce296 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -214,6 +214,7 @@ struct ngx_http_v2_stream_s { ngx_queue_t queue; ngx_array_t *cookies; + ngx_array_t *priorities; ngx_http_priority_state_t priority; @@ -228,6 +229,7 @@ struct ngx_http_v2_stream_s { unsigned rst_sent:1; unsigned no_flow_control:1; unsigned skip_data:1; + unsigned priority_update:1; };