use anyhow::{Context, Result};
use super::host::JumpHost;
pub fn parse_single_jump_host(host_spec: &str) -> Result<JumpHost> {
if host_spec.is_empty() {
anyhow::bail!("Empty jump host specification");
}
let parts: Vec<&str> = host_spec.splitn(2, '@').collect();
let (user, host_port) = if parts.len() == 2 {
(Some(parts[0].to_string()), parts[1])
} else {
(None, parts[0])
};
let user = if let Some(username) = user {
Some(crate::utils::sanitize_username(&username).with_context(|| {
format!("Invalid username in jump host specification: '{host_spec}'")
})?)
} else {
None
};
let (host, port) = parse_host_port(host_port)
.with_context(|| format!("Invalid host:port specification: '{host_port}'"))?;
let host = crate::utils::sanitize_hostname(&host)
.with_context(|| format!("Invalid hostname in jump host specification: '{host}'"))?;
Ok(JumpHost::new(host, user, port))
}
pub fn parse_host_port(host_port: &str) -> Result<(String, Option<u16>)> {
if host_port.is_empty() {
anyhow::bail!("Empty host specification");
}
if host_port.starts_with('[') {
if let Some(bracket_end) = host_port.find(']') {
let ipv6_addr = &host_port[1..bracket_end];
if ipv6_addr.is_empty() {
anyhow::bail!("Empty IPv6 address in brackets");
}
let remaining = &host_port[bracket_end + 1..];
if remaining.is_empty() {
return Ok((ipv6_addr.to_string(), None));
} else if let Some(port_str) = remaining.strip_prefix(':') {
if port_str.is_empty() {
anyhow::bail!("Empty port specification after IPv6 address");
}
let port = port_str
.parse::<u16>()
.with_context(|| format!("Invalid port number: '{port_str}'"))?;
if port == 0 {
anyhow::bail!("Port number cannot be zero");
}
return Ok((ipv6_addr.to_string(), Some(port)));
} else {
anyhow::bail!("Invalid characters after IPv6 address: '{remaining}'");
}
} else {
anyhow::bail!("Unclosed bracket in IPv6 address");
}
}
if let Some(colon_pos) = host_port.rfind(':') {
let host_part = &host_port[..colon_pos];
let port_part = &host_port[colon_pos + 1..];
if host_part.is_empty() {
anyhow::bail!("Empty hostname");
}
if port_part.is_empty() {
anyhow::bail!("Empty port specification");
}
match port_part.parse::<u16>() {
Ok(port) => {
if port == 0 {
anyhow::bail!("Port number cannot be zero");
}
Ok((host_part.to_string(), Some(port)))
}
Err(e) => {
if port_part.chars().all(|c| c.is_ascii_digit()) {
anyhow::bail!("Invalid port number: '{port_part}' ({e})");
} else {
Ok((host_port.to_string(), None))
}
}
}
} else {
Ok((host_port.to_string(), None))
}
}