use core::net::{IpAddr, Ipv6Addr};
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorContext, ErrorExt};
pub(crate) fn split_port_from_str(s: &str) -> Result<(&str, u16), BoxError> {
if let Some(colon) = s.as_bytes().iter().rposition(|c| *c == b':') {
match s[colon + 1..].parse() {
Ok(port) => Ok((&s[..colon], port)),
Err(err) => Err(err.context("parse port as u16")),
}
} else {
Err(BoxError::from_static_str("missing port"))
}
}
pub(crate) fn try_to_parse_str_to_ip(value: &str) -> Option<IpAddr> {
if value.starts_with('[') || value.ends_with(']') {
let value = value
.strip_prefix('[')
.and_then(|value| value.strip_suffix(']'))?;
if ipv6_bracket_has_zone(value.as_bytes()) {
return None;
}
Some(IpAddr::V6(value.parse::<Ipv6Addr>().ok()?))
} else {
value.parse::<IpAddr>().ok()
}
}
#[inline]
pub(crate) fn find_userinfo_split(bytes: &[u8]) -> Option<usize> {
bytes.iter().rposition(|&b| b == b'@')
}
#[inline]
pub(crate) fn ipv6_bracket_has_zone(inside_brackets: &[u8]) -> bool {
inside_brackets.contains(&b'%')
}
#[inline]
pub(crate) fn parse_port_bytes(bytes: &[u8]) -> Option<u16> {
let s = core::str::from_utf8(bytes).ok()?;
s.parse::<u16>().ok()
}
pub(crate) fn parse_bracketed_ipv6_with_port(
s: &str,
last_colon: usize,
) -> Result<(Ipv6Addr, crate::address::OptPort), BoxError> {
use crate::address::OptPort;
let first_part = &s[..last_colon];
debug_assert!(
first_part.contains(':'),
"parse_bracketed_ipv6_with_port: caller must check ':' in s[..last_colon]"
);
if first_part.starts_with('[') || first_part.ends_with(']') {
let value = first_part
.strip_prefix('[')
.and_then(|value| value.strip_suffix(']'))
.context("strip brackets from ipv6 host w/ trailing port")?;
if ipv6_bracket_has_zone(value.as_bytes()) {
return Err(BoxError::from_static_str(
"ipv6 zone identifiers (RFC 6874) are not supported",
));
}
let addr = value
.parse::<Ipv6Addr>()
.context("parse ipv6 host inside brackets")?;
let port_bytes = &s.as_bytes()[last_colon + 1..];
let port = if port_bytes.is_empty() {
OptPort::Empty
} else {
OptPort::Set(parse_port_bytes(port_bytes).context("parse port string as u16")?)
};
Ok((addr, port))
} else {
let addr = s
.parse::<Ipv6Addr>()
.context("parse bare ipv6 host w/o trailing port")?;
Ok((addr, OptPort::Unset))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_bracketed_v6_with_port() {
use crate::address::OptPort;
let s = "[2001:db8::1]:443";
let last_colon = s.rfind(':').unwrap();
let (addr, port) = parse_bracketed_ipv6_with_port(s, last_colon).unwrap();
assert_eq!(addr, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
assert_eq!(port, OptPort::Set(443));
}
#[test]
fn parse_bracketed_v6_empty_port() {
use crate::address::OptPort;
let s = "[2001:db8::1]:";
let last_colon = s.rfind(':').unwrap();
let (addr, port) = parse_bracketed_ipv6_with_port(s, last_colon).unwrap();
assert_eq!(addr, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
assert_eq!(port, OptPort::Empty);
}
#[test]
fn parse_bare_v6_no_port() {
use crate::address::OptPort;
let s = "2001:db8::1";
let last_colon = s.rfind(':').unwrap();
let (addr, port) = parse_bracketed_ipv6_with_port(s, last_colon).unwrap();
assert_eq!(addr, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
assert_eq!(port, OptPort::Unset);
}
#[test]
fn parse_bare_v6_loopback() {
use crate::address::OptPort;
let s = "::1";
let last_colon = s.rfind(':').unwrap();
let (addr, port) = parse_bracketed_ipv6_with_port(s, last_colon).unwrap();
assert_eq!(addr, Ipv6Addr::LOCALHOST);
assert_eq!(port, OptPort::Unset);
}
#[test]
fn rejects_half_bracket() {
let s = "[2001:db8::1:443";
let last_colon = s.rfind(':').unwrap();
parse_bracketed_ipv6_with_port(s, last_colon).unwrap_err();
let s = "2001:db8::1]:443";
let last_colon = s.rfind(':').unwrap();
parse_bracketed_ipv6_with_port(s, last_colon).unwrap_err();
}
#[test]
fn rejects_bad_port() {
let s = "[::1]:notaport";
let last_colon = s.rfind(':').unwrap();
parse_bracketed_ipv6_with_port(s, last_colon).unwrap_err();
}
#[test]
fn rejects_bad_address_inside_brackets() {
let s = "[zz::1]:443";
let last_colon = s.rfind(':').unwrap();
parse_bracketed_ipv6_with_port(s, last_colon).unwrap_err();
}
}