From 42c721e954bada47fe3a5639c9110d88fde42727 Mon Sep 17 00:00:00 2001 From: "glm-5.1" Date: Fri, 12 Jun 2026 04:40:43 +0000 Subject: [PATCH] fix: normalize_host handles IPv6 bracket notation Extract strip_port_from_host into shared utils module and update normalize_host to properly strip brackets from IPv6 addresses like [::1]:443 -> ::1 instead of incorrectly using split(':').next(). --- src/config/dynamic_config.rs | 30 ++++++++++++++++++++-- src/lib.rs | 1 + src/tls/redirect.rs | 39 +---------------------------- src/utils.rs | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 40 deletions(-) create mode 100644 src/utils.rs diff --git a/src/config/dynamic_config.rs b/src/config/dynamic_config.rs index b7c9d7a..e7a65a5 100644 --- a/src/config/dynamic_config.rs +++ b/src/config/dynamic_config.rs @@ -50,8 +50,14 @@ pub fn build_routing_table(sites: &[SiteConfig]) -> HashMap } pub fn normalize_host(host: &str) -> String { - let lower = host.to_lowercase(); - lower.split(':').next().unwrap_or(&lower).to_string() + let stripped = crate::utils::strip_port_from_host(host); + let lower = stripped.to_lowercase(); + lower + .strip_prefix('[') + .unwrap_or(&lower) + .strip_suffix(']') + .unwrap_or(&lower) + .to_string() } #[derive(Debug, Deserialize, Clone, PartialEq)] @@ -304,6 +310,26 @@ mod tests { assert_eq!(normalize_host(""), ""); } + #[test] + fn normalize_host_ipv6_with_port() { + assert_eq!(normalize_host("[::1]:443"), "::1"); + } + + #[test] + fn normalize_host_ipv6_long_with_port() { + assert_eq!(normalize_host("[2001:db8::1]:8080"), "2001:db8::1"); + } + + #[test] + fn normalize_host_ipv6_bare() { + assert_eq!(normalize_host("[::1]"), "::1"); + } + + #[test] + fn normalize_host_ipv6_uppercase() { + assert_eq!(normalize_host("[2001:DB8::1]:443"), "2001:db8::1"); + } + #[test] fn routing_table_lookup_finds_site() { let config = test_fixtures::test_dynamic_config(); diff --git a/src/lib.rs b/src/lib.rs index f522d61..82e16f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,4 @@ pub mod rate_limit; pub mod server; pub mod shutdown; pub mod tls; +pub mod utils; diff --git a/src/tls/redirect.rs b/src/tls/redirect.rs index c665a94..808e36f 100644 --- a/src/tls/redirect.rs +++ b/src/tls/redirect.rs @@ -13,19 +13,7 @@ use crate::config::static_config::ListenerConfig; const ACME_CHALLENGE_PREFIX: &str = "/.well-known/acme-challenge/"; -fn strip_port_from_host(host: &str) -> &str { - if host.starts_with('[') { - if let Some(bracket_end) = host.find(']') { - &host[..bracket_end + 1] - } else { - host - } - } else if let Some(colon_pos) = host.rfind(':') { - &host[..colon_pos] - } else { - host - } -} +use crate::utils::strip_port_from_host; pub fn build_redirect_url(host: &str, https_port: u16, path: &str, query: &str) -> String { let hostname = strip_port_from_host(host); @@ -218,29 +206,4 @@ mod tests { let url = build_redirect_url("203.0.113.10", 443, "/", ""); assert_eq!(url, "https://203.0.113.10/"); } - - #[test] - fn test_strip_port_from_host_plain() { - assert_eq!(strip_port_from_host("example.com"), "example.com"); - } - - #[test] - fn test_strip_port_from_host_with_port() { - assert_eq!(strip_port_from_host("example.com:8080"), "example.com"); - } - - #[test] - fn test_strip_port_from_host_ipv6_bare() { - assert_eq!(strip_port_from_host("[::1]"), "[::1]"); - } - - #[test] - fn test_strip_port_from_host_ipv6_with_port() { - assert_eq!(strip_port_from_host("[::1]:8080"), "[::1]"); - } - - #[test] - fn test_strip_port_from_host_ipv4_with_port() { - assert_eq!(strip_port_from_host("192.168.1.1:8080"), "192.168.1.1"); - } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..1a9db69 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,48 @@ +pub fn strip_port_from_host(host: &str) -> &str { + if host.starts_with('[') { + if let Some(bracket_end) = host.find(']') { + &host[..bracket_end + 1] + } else { + host + } + } else if let Some(colon_pos) = host.rfind(':') { + &host[..colon_pos] + } else { + host + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strip_port_plain() { + assert_eq!(strip_port_from_host("example.com"), "example.com"); + } + + #[test] + fn strip_port_with_port() { + assert_eq!(strip_port_from_host("example.com:8080"), "example.com"); + } + + #[test] + fn strip_port_ipv6_bare() { + assert_eq!(strip_port_from_host("[::1]"), "[::1]"); + } + + #[test] + fn strip_port_ipv6_with_port() { + assert_eq!(strip_port_from_host("[::1]:8080"), "[::1]"); + } + + #[test] + fn strip_port_ipv6_with_long_address() { + assert_eq!(strip_port_from_host("[2001:db8::1]:8080"), "[2001:db8::1]"); + } + + #[test] + fn strip_port_ipv4_with_port() { + assert_eq!(strip_port_from_host("192.168.1.1:8080"), "192.168.1.1"); + } +}