From 352ee91f5f40f53ecc2b107ba1b78344b7d34763 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] 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);