Skip to content

Commit 217a79e

Browse files
Adam Fiskclaude
andcommitted
fronted: fix strip_port for IPv6 literals; clearer no-provider error
Address Copilot round-2 review on PR #4: - strip_port mistook the last hextet of an unbracketed IPv6 literal (e.g. 2001:db8::1) for a port. Strip a port only for bracketed IPv6 (keep through ']') or a single-colon host:port; leave multi-colon unbracketed IPv6 unchanged. + strip_port_handles_ports_and_ipv6 test. - NoHostMapping was only ever built with a fake provider "*" (rendered as 'provider `*`'); rename to NoFrontingProvider { host } with message 'no fronting provider maps `{host}`' so the no-provider-matched case is clear to operators. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0366d65 commit 217a79e

1 file changed

Lines changed: 30 additions & 8 deletions

File tree

  • crates/flint-fronted/src

crates/flint-fronted/src/lib.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ pub enum Error {
4848
Yaml(#[from] serde_yaml::Error),
4949
#[error("unknown provider `{0}`")]
5050
UnknownProvider(String),
51-
#[error("provider `{provider}` has no fronting mapping for `{host}`")]
52-
NoHostMapping { provider: String, host: String },
51+
#[error("no fronting provider maps `{host}`")]
52+
NoFrontingProvider { host: String },
5353
#[error("front `{front}` resolved to no usable A/AAAA records")]
5454
EmptyResolution { front: String },
5555
#[error("resolving front `{front}` failed: {source}")]
@@ -892,8 +892,7 @@ impl FrontPool {
892892
}
893893
}
894894
if fronts.is_empty() && !saw_provider {
895-
return Err(Error::NoHostMapping {
896-
provider: "*".into(),
895+
return Err(Error::NoFrontingProvider {
897896
host: host.to_owned(),
898897
});
899898
}
@@ -1111,10 +1110,23 @@ fn empty_opt(value: Option<&str>) -> bool {
11111110
}
11121111

11131112
fn strip_port(host: &str) -> &str {
1114-
host.rsplit_once(':')
1115-
.filter(|(_, port)| !port.is_empty() && port.bytes().all(|b| b.is_ascii_digit()))
1116-
.map(|(host, _)| host)
1117-
.unwrap_or(host)
1113+
// Bracketed IPv6 (`[::1]` / `[::1]:443`): keep everything through the closing bracket.
1114+
if host.starts_with('[') {
1115+
return match host.find(']') {
1116+
Some(end) => &host[..=end],
1117+
None => host,
1118+
};
1119+
}
1120+
// Unbracketed: only strip a trailing `:<port>` when there is exactly one colon, so an
1121+
// unbracketed IPv6 literal (multiple colons, e.g. `2001:db8::1`) is left unchanged.
1122+
match host.rsplit_once(':') {
1123+
Some((h, port))
1124+
if !h.contains(':') && !port.is_empty() && port.bytes().all(|b| b.is_ascii_digit()) =>
1125+
{
1126+
h
1127+
}
1128+
_ => host,
1129+
}
11181130
}
11191131

11201132
fn parse_endpoint(s: &str) -> Option<FrontEndpoint> {
@@ -1657,6 +1669,16 @@ providers:
16571669
));
16581670
}
16591671

1672+
#[test]
1673+
fn strip_port_handles_ports_and_ipv6() {
1674+
assert_eq!(strip_port("api.example.com"), "api.example.com");
1675+
assert_eq!(strip_port("api.example.com:443"), "api.example.com");
1676+
// An unbracketed IPv6 literal must not have its last hextet mistaken for a port.
1677+
assert_eq!(strip_port("2001:db8::1"), "2001:db8::1");
1678+
assert_eq!(strip_port("[2001:db8::1]"), "[2001:db8::1]");
1679+
assert_eq!(strip_port("[2001:db8::1]:443"), "[2001:db8::1]");
1680+
}
1681+
16601682
#[test]
16611683
fn fronts_for_host_skips_malformed_masquerades() {
16621684
let cfg = parse_config_yaml(

0 commit comments

Comments
 (0)