From 1609cb6fb34ea0bfb0d75a0f7f2eb661c46fa45f Mon Sep 17 00:00:00 2001 From: zihaofu245 <172541422+ZihaoFU245@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:33:32 +0800 Subject: [PATCH 1/3] Allow HTTP CONNECT method passthrough This commit allows h2/h3 connect method to passthrough nginx internal request parsing. Modules can then handle CONNECT method by setting allow_connect bit field to 1. The logic still aligns with HTTP/1.1, allow_connect is always checked first, 405 will return before 400 (Header parsing) --- src/http/ngx_http_request_body.c | 12 +++- src/http/v2/ngx_http_v2.c | 91 +++++++++++++++++++++---------- src/http/v3/ngx_http_v3_request.c | 60 +++++++++++++++----- 3 files changed, 120 insertions(+), 43 deletions(-) diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index 1d8e4081a..e06d14f5a 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -79,7 +79,17 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, r->request_body = rb; - if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { + if (r->headers_in.content_length_n < 0 + && !r->headers_in.chunked +#if (NGX_HTTP_V2) + && !(r->stream && r->method == NGX_HTTP_CONNECT) +#endif +#if (NGX_HTTP_V3) + && !(r->http_version == NGX_HTTP_VERSION_30 + && r->method == NGX_HTTP_CONNECT) +#endif + ) + { r->request_body_no_buffering = 0; post_handler(r); return NGX_OK; diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 69cb0ae09..2f87574f7 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3570,34 +3570,64 @@ 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) { - u_char *p; + size_t len; + u_char *p; + ngx_http_core_srv_conf_t *cscf; static const u_char ending[] = " HTTP/2.0"; - if (r->method_name.len == 0 - || r->schema.len == 0 - || r->unparsed_uri.len == 0) - { - if (r->method_name.len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent no :method header"); - - } else if (r->schema.len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent no :scheme header"); - - } else { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent no :path header"); - } - + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :method header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } - r->request_line.len = r->method_name.len + 1 - + r->unparsed_uri.len - + sizeof(ending) - 1; + if (r->method == NGX_HTTP_CONNECT) { + goto method_connect; + } + + if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :scheme header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->unparsed_uri.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :path header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + len = r->unparsed_uri.len; + + goto construct_request_line; + +method_connect: + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (!cscf->allow_connect) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :authority header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + len = r->headers_in.server.len; + +construct_request_line: + + r->request_line.len = r->method_name.len + 1 + len + sizeof(ending) - 1; p = ngx_pnalloc(r->pool, r->request_line.len + 1); if (p == NULL) { @@ -3611,7 +3641,18 @@ ngx_http_v2_construct_request_line(ngx_http_request_t *r) *p++ = ' '; - p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + if (r->method == NGX_HTTP_CONNECT) { + p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len); + + r->uri_start = (u_char *) "/"; + r->uri_end = r->uri_start + 1; + ngx_str_set(&r->uri, "/"); + ngx_str_set(&r->unparsed_uri, "/"); + r->valid_unparsed_uri = 1; + + } else { + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + } ngx_memcpy(p, ending, sizeof(ending)); @@ -3920,12 +3961,6 @@ ngx_http_v2_run_request(ngx_http_request_t *r) r->headers_in.chunked = 1; } - if (r->method == NGX_HTTP_CONNECT) { - ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client sent CONNECT method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - goto failed; - } - if (r->method == NGX_HTTP_TRACE) { ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client sent TRACE method"); ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6b487289a..ebbdafbe3 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -909,10 +909,11 @@ failed: static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) { - size_t len; - u_char *p; - ngx_int_t rc; - ngx_str_t host; + size_t len, target_len; + u_char *p; + ngx_int_t rc; + ngx_str_t host; + ngx_http_core_srv_conf_t *cscf; in_port_t port; if (r->request_line.len) { @@ -925,6 +926,10 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) goto failed; } + if (r->method == NGX_HTTP_CONNECT) { + goto method_connect; + } + if (r->schema.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no \":scheme\" header"); @@ -937,9 +942,35 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) goto failed; } - len = r->method_name.len + 1 - + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3.0") - 1; + target_len = (size_t) (r->uri_end - r->uri_start); + + goto construct_request_line; + +method_connect: + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (!cscf->allow_connect) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->host_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":authority\" header"); + goto failed; + } + + r->uri_start = (u_char *) "/"; + r->uri_end = r->uri_start + 1; + + target_len = (size_t) (r->host_end - r->host_start); + +construct_request_line: + + len = r->method_name.len + 1 + target_len + 1 + sizeof("HTTP/3.0") - 1; p = ngx_pnalloc(r->pool, len); if (p == NULL) { @@ -951,7 +982,14 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) p = ngx_cpymem(p, r->method_name.data, r->method_name.len); *p++ = ' '; - p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + + if (r->method == NGX_HTTP_CONNECT) { + p = ngx_cpymem(p, r->host_start, target_len); + + } else { + p = ngx_cpymem(p, r->uri_start, target_len); + } + *p++ = ' '; p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); @@ -1136,12 +1174,6 @@ 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); From bddc7d41a11a70a215b752436aa380d30ce06863 Mon Sep 17 00:00:00 2001 From: zihaofu245 <172541422+ZihaoFU245@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:53:57 +0800 Subject: [PATCH 2/3] Allow Extended CONNECT to pass header parsing For classic connect method, it does not contain :path nor :scheme, so set it to "/" for nginx core to continue parsing. For extended connect, there are :path, :scheme, :path and :protocol. If scheme and path is provided, then set it to value in the icoming headers. :protocol header is not in nginx core. It is still not handled --- src/http/v2/ngx_http_v2.c | 35 +++++++++++++++++++++++++---- src/http/v3/ngx_http_v3_request.c | 37 ++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 2f87574f7..4413fa42c 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3572,6 +3572,7 @@ ngx_http_v2_construct_request_line(ngx_http_request_t *r) { size_t len; u_char *p; + ngx_uint_t classic_connect; ngx_http_core_srv_conf_t *cscf; static const u_char ending[] = " HTTP/2.0"; @@ -3587,6 +3588,8 @@ ngx_http_v2_construct_request_line(ngx_http_request_t *r) goto method_connect; } + classic_connect = 0; + if (r->schema.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no :scheme header"); @@ -3616,14 +3619,38 @@ method_connect: return NGX_ERROR; } - if (r->headers_in.server.len == 0) { + if (r->schema.len == 0 && r->unparsed_uri.len == 0) { + classic_connect = 1; + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :authority header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + len = r->headers_in.server.len; + + goto construct_request_line; + } + + classic_connect = 0; + + if (r->schema.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent no :authority header"); + "client sent no :scheme header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } - len = r->headers_in.server.len; + if (r->unparsed_uri.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :path header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + len = r->unparsed_uri.len; construct_request_line: @@ -3641,7 +3668,7 @@ construct_request_line: *p++ = ' '; - if (r->method == NGX_HTTP_CONNECT) { + if (classic_connect) { p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len); r->uri_start = (u_char *) "/"; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ebbdafbe3..0ce0083f4 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -912,6 +912,7 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) size_t len, target_len; u_char *p; ngx_int_t rc; + ngx_uint_t classic_connect; ngx_str_t host; ngx_http_core_srv_conf_t *cscf; in_port_t port; @@ -930,6 +931,8 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) goto method_connect; } + classic_connect = 0; + if (r->schema.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no \":scheme\" header"); @@ -957,16 +960,38 @@ method_connect: return NGX_ERROR; } - if (r->host_start == NULL) { + if (r->schema.len == 0 && r->uri_start == NULL) { + classic_connect = 1; + + if (r->host_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":authority\" header"); + goto failed; + } + + r->uri_start = (u_char *) "/"; + r->uri_end = r->uri_start + 1; + + target_len = (size_t) (r->host_end - r->host_start); + + goto construct_request_line; + } + + classic_connect = 0; + + if (r->schema.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent no \":authority\" header"); + "client sent no \":scheme\" header"); goto failed; } - r->uri_start = (u_char *) "/"; - r->uri_end = r->uri_start + 1; + if (r->uri_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":path\" header"); + goto failed; + } - target_len = (size_t) (r->host_end - r->host_start); + target_len = (size_t) (r->uri_end - r->uri_start); construct_request_line: @@ -983,7 +1008,7 @@ construct_request_line: p = ngx_cpymem(p, r->method_name.data, r->method_name.len); *p++ = ' '; - if (r->method == NGX_HTTP_CONNECT) { + if (classic_connect) { p = ngx_cpymem(p, r->host_start, target_len); } else { From 342b52e9e8b412f18a4ec916a06c633330021da1 Mon Sep 17 00:00:00 2001 From: zihaofu245 <172541422+ZihaoFU245@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:01:22 +0800 Subject: [PATCH 3/3] HTTP: Add :protocol header parsing Exetnded CONNECT method, including connect-udp, contains a :protocol header. Previously nginx core does not contain this parsing logic. In `ngx_http_request_s` added a field `connect_protocol`, it stores values of `:protocol`. In pesudo header parsing, distinguish between classic connect method or extended connect method. `:scheme`, `:uri` and `:protocol` must be all present for extended connect. And reject `:protocol` header for non connect methods. --- src/http/ngx_http_request.h | 1 + src/http/v2/ngx_http_v2.c | 53 ++++++++++++++++++++++++++++++- src/http/v3/ngx_http_v3_request.c | 39 ++++++++++++++++++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 6d18cc3c6..440923c8f 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -427,6 +427,7 @@ struct ngx_http_request_s { ngx_str_t method_name; ngx_str_t http_protocol; ngx_str_t schema; + ngx_str_t connect_protocol; ngx_chain_t *out; ngx_http_request_t *main; diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 4413fa42c..8999b8387 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -154,6 +154,8 @@ 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_parse_protocol(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); @@ -3325,6 +3327,16 @@ ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) break; + case 8: + if (ngx_memcmp(header->name.data, "protocol", + sizeof("protocol") - 1) + == 0) + { + return ngx_http_v2_parse_protocol(r, &header->value); + } + + break; + case 9: if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) == 0) @@ -3567,6 +3579,29 @@ ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) } +static ngx_int_t +ngx_http_v2_parse_protocol(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->connect_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :protocol header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :protocol header"); + + return NGX_DECLINED; + } + + r->connect_protocol = *value; + + return NGX_OK; +} + + static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r) { @@ -3588,6 +3623,13 @@ ngx_http_v2_construct_request_line(ngx_http_request_t *r) goto method_connect; } + if (r->connect_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent :protocol header with non-CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + classic_connect = 0; if (r->schema.len == 0) { @@ -3619,7 +3661,9 @@ method_connect: return NGX_ERROR; } - if (r->schema.len == 0 && r->unparsed_uri.len == 0) { + if (r->schema.len == 0 && r->unparsed_uri.len == 0 + && r->connect_protocol.len == 0) + { classic_connect = 1; if (r->headers_in.server.len == 0) { @@ -3643,6 +3687,13 @@ method_connect: return NGX_ERROR; } + if (r->connect_protocol.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent extended CONNECT without :protocol header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + if (r->unparsed_uri.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no :path header"); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0ce0083f4..e3225cad4 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -896,6 +896,27 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, return NGX_OK; } + if (name->len == 9 && ngx_strncmp(name->data, ":protocol", 9) == 0) { + + if (r->connect_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":protocol\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":protocol\" header"); + goto failed; + } + + r->connect_protocol = *value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 protocol \"%V\"", value); + return NGX_OK; + } + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent unknown pseudo-header \"%V\"", name); @@ -931,6 +952,13 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) goto method_connect; } + if (r->connect_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent \":protocol\" header with non-CONNECT " + "method"); + goto failed; + } + classic_connect = 0; if (r->schema.len == 0) { @@ -960,7 +988,9 @@ method_connect: return NGX_ERROR; } - if (r->schema.len == 0 && r->uri_start == NULL) { + if (r->schema.len == 0 && r->uri_start == NULL + && r->connect_protocol.len == 0) + { classic_connect = 1; if (r->host_start == NULL) { @@ -985,6 +1015,13 @@ method_connect: goto failed; } + if (r->connect_protocol.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent extended CONNECT without " + "\":protocol\" header"); + goto failed; + } + if (r->uri_start == NULL) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent no \":path\" header");