use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use crate::url::errors::InvalidUri;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Authority {
userinfo: Option<String>,
host: Host,
port: Option<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Host {
Ipv4(Ipv4Addr),
Ipv6(Ipv6Addr),
RegName(String),
}
impl Authority {
pub fn parse_with_remainder(s: &str) -> Result<(Option<Self>, &str), InvalidUri> {
if s.is_empty() {
return Ok((None, s));
}
let authority_end = s.find(&['/', '?', '#'][..]).unwrap_or(s.len());
let authority_str = &s[..authority_end];
let remainder = &s[authority_end..];
if authority_str.is_empty() {
return Ok((None, remainder));
}
let authority = Self::parse(authority_str)?;
Ok((Some(authority), remainder))
}
pub fn parse(s: &str) -> Result<Self, InvalidUri> {
let (userinfo, host_port) = if let Some(at_pos) = s.rfind('@') {
let userinfo = &s[..at_pos];
let userinfo = if userinfo.is_empty() {
None
} else {
Some(userinfo.to_string())
};
(userinfo, &s[at_pos + 1..])
} else {
(None, s)
};
let (host, port) = Self::parse_host_port(host_port)?;
Ok(Authority {
userinfo,
host,
port,
})
}
pub fn parse_host_port(s: &str) -> Result<(Host, Option<u16>), InvalidUri> {
if s.starts_with('[') {
let close_bracket = s
.find(']')
.ok_or_else(|| InvalidUri::new("unclosed IPv6 bracket"))?;
let ipv6_str = &s[1..close_bracket];
let ipv6: Ipv6Addr = ipv6_str
.parse()
.map_err(|_| InvalidUri::new(format!("invalid IPv6 address: {ipv6_str}")))?;
let after_bracket = &s[close_bracket + 1..];
let port = if let Some(stripped) = after_bracket.strip_prefix(':') {
Some(Self::parse_port(stripped)?)
} else if after_bracket.is_empty() {
None
} else {
return Err(InvalidUri::new("invalid characters after IPv6 bracket"));
};
return Ok((Host::Ipv6(ipv6), port));
}
if let Some(colon_pos) = s.rfind(':') {
let host_part = &s[..colon_pos];
let port_part = &s[colon_pos + 1..];
if let Ok(ipv4) = host_part.parse::<Ipv4Addr>() {
let port = Some(Self::parse_port(port_part)?);
return Ok((Host::Ipv4(ipv4), port));
}
if host_part.is_empty() {
return Err(InvalidUri::new("empty host"));
}
Self::validate_reg_name(host_part)?;
let port = Some(Self::parse_port(port_part)?);
Ok((Host::RegName(host_part.to_string()), port))
} else {
if s.is_empty() {
return Err(InvalidUri::new("empty host"));
}
if let Ok(ipv4) = s.parse::<Ipv4Addr>() {
return Ok((Host::Ipv4(ipv4), None));
}
Self::validate_reg_name(s)?;
Ok((Host::RegName(s.to_string()), None))
}
}
pub fn parse_port(s: &str) -> Result<u16, InvalidUri> {
s.parse::<u16>()
.map_err(|_| InvalidUri::new(format!("invalid port: {s}")))
}
fn validate_reg_name(s: &str) -> Result<(), InvalidUri> {
for c in s.chars() {
if !(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '.'
|| c == '_'
|| c == '~'
|| c == '!'
|| c == '$'
|| c == '&'
|| c == '\''
|| c == '('
|| c == ')'
|| c == '*'
|| c == '+'
|| c == ','
|| c == ';'
|| c == '='
|| c == '%')
{
return Err(InvalidUri::new(format!("invalid host character: {c}")));
}
}
Ok(())
}
#[must_use]
pub fn host(&self) -> &Host {
&self.host
}
#[must_use]
pub fn port(&self) -> Option<u16> {
self.port
}
#[must_use]
pub fn userinfo(&self) -> Option<&str> {
self.userinfo.as_deref()
}
}
impl Host {
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Host::Ipv4(_) => None,
Host::Ipv6(_) => None,
Host::RegName(name) => Some(name),
}
}
#[must_use]
pub fn as_ipv4(&self) -> Option<Ipv4Addr> {
match self {
Host::Ipv4(addr) => Some(*addr),
_ => None,
}
}
#[must_use]
pub fn as_ipv6(&self) -> Option<Ipv6Addr> {
match self {
Host::Ipv6(addr) => Some(*addr),
_ => None,
}
}
#[must_use]
pub fn is_ip_addr(&self) -> bool {
matches!(self, Host::Ipv4(_) | Host::Ipv6(_))
}
#[must_use]
pub fn to_string_for_display(&self) -> String {
match self {
Host::Ipv4(addr) => addr.to_string(),
Host::Ipv6(addr) => format!("[{addr}]"),
Host::RegName(name) => name.clone(),
}
}
}
impl fmt::Display for Authority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(userinfo) = &self.userinfo {
write!(f, "{userinfo}@")?;
}
write!(f, "{}", self.host.to_string_for_display())?;
if let Some(port) = self.port {
write!(f, ":{port}")?;
}
Ok(())
}
}
impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_for_display())
}
}