Send the client's Host as :authority to HTTP/2 upstreams

Dropping Host lost a virtual host that differs from the CONNECT target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Gregory SchierandClaude Opus 5.5 authored and Stetta task tk_mvr91ax8pkxqchde committed 2026-10-27 15:38:54 -07:00
1 parent 11aca4539d
commit 4abc882e13
2 files changed
+25 -14

No files matched your search

+14 -14
View File
@@ -412,26 +412,26 @@ async fn forward_https(
let (mut parts, body) = req.into_parts();
let uri = match &upstream {
Upstream::H2(_) => url.parse::<Uri>(),
// `Host` moves into `:authority`. Google's servers reset any stream that has both, even
// when they match.
Upstream::H2(_) => match parts.headers.remove(HOST) {
Some(host) => match host.to_str() {
Ok(host) => format!("https://{host}{path}").parse::<Uri>(),
Err(e) => return Ok(flow.fail(e.to_string())),
},
None => url.parse::<Uri>(),
},
Upstream::H1(_) => path.parse::<Uri>(),
};
parts.uri = match uri {
Ok(uri) => uri,
Err(e) => return Ok(flow.fail(e.to_string())),
};
match &upstream {
// The URL carries `:authority`. Google's servers reset any stream that also has `Host`,
// even a matching one.
Upstream::H2(_) => {
parts.headers.remove(HOST);
}
Upstream::H1(_) => {
if !parts.headers.contains_key(HOST)
&& let Ok(host) = target.authority.parse()
{
parts.headers.insert(HOST, host);
}
}
if matches!(upstream, Upstream::H1(_))
&& !parts.headers.contains_key(HOST)
&& let Ok(host) = target.authority.parse()
{
parts.headers.insert(HOST, host);
}
let outgoing = Request::from_parts(parts, flow.capture_request(body));
+11
View File
@@ -441,6 +441,17 @@ async fn https_h2_upstream_gets_authority_without_host() {
assert!(captured_host, "the captured request keeps Host");
}
/// A `Host` other than the CONNECT target still names the virtual host upstream.
#[tokio::test(flavor = "multi_thread")]
async fn https_h2_upstream_gets_the_clients_host_as_authority() {
let h = Harness::start(1024);
let upstream = spawn_upstream(Some(h.pki.server.clone())).await;
let url = format!("https://localhost:{}/authority", upstream.port());
let resp = h.client().get(&url).header("host", "yaak.test:8443").send().await.unwrap();
assert_eq!(resp.text().await.unwrap(), "authority=yaak.test:8443 host=none");
}
#[tokio::test(flavor = "multi_thread")]
async fn https_not_found() {
let h = Harness::start(1024);