diff --git a/src/config/internal_routing.rs b/src/config/internal_routing.rs index 3c531a0..217b9a8 100644 --- a/src/config/internal_routing.rs +++ b/src/config/internal_routing.rs @@ -22,25 +22,27 @@ impl Snapshot { local: SocketAddr, tls: bool, ) -> Option<&Arc> { - for pattern in Self::host_patterns(host) { - let owns_host = self.fallbacks.get(pattern.as_ref()).is_some_and(|routes| { + Self::find_host_pattern(host, |pattern| { + let owns_host = self.fallbacks.get(pattern).is_some_and(|routes| { routes.iter().any(|route| { route.frontend.as_ref().is_some_and(|frontend| frontend.id == vhost_id) && route.bindings.iter().any(|binding| binding.matches(local, tls)) }) }); if !owns_host { - continue; + return None; } - return self.routes.get(pattern.as_ref()).and_then(|routes| { + // Some(None) stops at the owning hostname even when the named + // location is absent. Do not try a less-specific wildcard. + Some(self.routes.get(pattern).and_then(|routes| { routes.iter().find(|route| { route.internal && route.name == target && route.frontend.as_ref().is_some_and(|frontend| frontend.id == vhost_id) && route.bindings.iter().any(|binding| binding.matches(local, tls)) }) - }); - } - None + })) + }) + .flatten() } } diff --git a/src/config/tests.rs b/src/config/tests.rs index f2aeea8..00e8486 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -988,6 +988,58 @@ fn stack_host_lookup_keeps_exact_and_most_specific_wildcard_order() { assert_eq!(no_wildcard_for_tld, None); } +#[test] +fn internal_routing_uses_host_lookup_without_cross_vhost_dispatch() { + let mut config = demo_config("127.0.0.1:9000".parse().unwrap()); + config.vhosts[0].id = "wildcard".into(); + config.vhosts[0].frontend.hosts = vec!["*.example.test".into()]; + config.vhosts[0].routes.push(crate::vhost::PathRoute { + id: "internal-page".into(), + internal: true, + ..Default::default() + }); + let mut exact = config.vhosts[0].clone(); + exact.id = "exact".into(); + exact.frontend.hosts = vec!["api.example.test".into()]; + exact.routes.retain(|route| !route.internal); + config.vhosts.push(exact); + + let snapshot = compile(config, 1, &policy()).unwrap(); + let local = "127.0.0.1:8080".parse().unwrap(); + let internal = snapshot + .select_internal_on_arc("sub.example.test", "wildcard", "internal-page", local, false) + .unwrap(); + assert_eq!(internal.name, "internal-page"); + assert!(internal.internal); + assert!(snapshot + .select_internal_on_arc("sub.example.test", "wildcard", "missing", local, false) + .is_none()); + assert!(snapshot + .select_internal_on_arc("api.example.test", "exact", "internal-page", local, false) + .is_none()); + assert!(snapshot + .select_internal_on_arc("unrelated.test", "wildcard", "internal-page", local, false) + .is_none()); +} + +#[test] +fn owned_host_without_internal_route_stops_wildcard_search() { + let mut visited = Vec::new(); + let match_found = Snapshot::find_host_pattern("api.example.test", |pattern| { + visited.push(pattern.to_owned()); + if pattern == "api.example.test" { + Some(None::<&str>) + } else if pattern == "*.example.test" { + Some(Some("wrong-fallback")) + } else { + None + } + }) + .flatten(); + assert_eq!(match_found, None); + assert_eq!(visited, vec!["api.example.test"]); +} + #[test] fn stack_host_lookup_preserves_long_internal_host_fallback() { // Normalized runtime hosts are <=253 bytes. Direct internal callers with