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)
This commit is contained in:
zihaofu245 2026-04-13 15:33:32 +08:00
parent d44205284f
commit 352ee91f5f
3 changed files with 120 additions and 43 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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);