#![allow(clippy::module_name_repetitions)]
use crate::error::ResultExt;
use crate::error::{Component, Error as IriError, ErrorKind, Result as IriResult};
use crate::{parse, ValidateStr};
use crate::{Normalize, Scheme};
use regex::Regex;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Port(u16);
#[derive(Clone, Debug, Eq)]
pub enum HostKind {
IPV4(Ipv4Addr),
IPV6(Ipv6Addr),
IPVFuture(u16, String),
DomainName(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Host(HostKind);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct UserInfo {
user_name: String,
password: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Authority {
user_info: Option<UserInfo>,
host: Host,
port: Option<Port>,
}
lazy_static! {
static ref IPV4: Regex =
Regex::new(r"^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:(\d+))?$").unwrap();
static ref IPVMORE: Regex =
Regex::new(r"^\[(v([0-9A-Fa-f]+)\.)?([0-9A-Fa-f:]+)](:(\d+))?$").unwrap();
static ref IP_FUTURE: Regex = Regex::new(r"^[0-9A-Fa-f:]+$").unwrap();
}
impl Display for Port {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, ":{}", self.0)
}
}
impl FromStr for Port {
type Err = IriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match u16::from_str(s) {
Ok(port) => Ok(Self(port)),
Err(e) => Err(IriError::with_chain(
e,
ErrorKind::ParsePortError(s.to_string()),
)),
}
}
}
impl From<u16> for Port {
fn from(v: u16) -> Self {
Self(v)
}
}
impl Port {
pub fn new(raw_port: u16) -> Self {
Self(raw_port)
}
pub fn ftp_data() -> Port {
20.into()
}
pub fn ftp_control() -> Port {
21.into()
}
pub fn ssh() -> Port {
22.into()
}
pub fn telnet() -> Port {
23.into()
}
pub fn tftp() -> Port {
69.into()
}
pub fn gopher() -> Port {
70.into()
}
pub fn http() -> Port {
80.into()
}
pub fn nntp() -> Port {
119.into()
}
pub fn imap() -> Port {
143.into()
}
pub fn snmp() -> Port {
161.into()
}
pub fn snmp_trap() -> Port {
162.into()
}
pub fn imap3() -> Port {
220.into()
}
pub fn ldap() -> Port {
389.into()
}
pub fn https() -> Port {
443.into()
}
pub fn rtsp() -> Port {
554.into()
}
pub fn ipp() -> Port {
631.into()
}
pub fn iris_beep() -> Port {
702.into()
}
pub fn dict() -> Port {
2628.into()
}
pub fn stun() -> Port {
3478.into()
}
pub fn diameter() -> Port {
3868.into()
}
pub fn iax() -> Port {
4569.into()
}
pub fn sip() -> Port {
5060.into()
}
pub fn sips() -> Port {
5061.into()
}
pub fn vnc() -> Port {
5500.into()
}
pub fn coap() -> Port {
5683.into()
}
pub fn coaps() -> Port {
5684.into()
}
pub fn default_for(scheme: &Scheme) -> Option<Port> {
let scheme = scheme.value();
match scheme.as_str() {
"ftp" => Some(Self::ftp_data()),
"ssh" => Some(Self::ssh()),
"telnet" => Some(Self::telnet()),
"tftp" => Some(Self::tftp()),
"gopher" => Some(Self::gopher()),
"http" => Some(Self::http()),
"nntp" => Some(Self::nntp()),
"imap" => Some(Self::imap()),
"snmp" => Some(Self::snmp()),
"ldap" => Some(Self::ldap()),
"https" => Some(Self::https()),
"rtsp" => Some(Self::rtsp()),
"ipp" => Some(Self::ipp()),
"iris.beep" => Some(Self::iris_beep()),
"dict" => Some(Self::dict()),
"stun" => Some(Self::stun()),
"aaa" => Some(Self::diameter()),
"iax" => Some(Self::iax()),
"sip" => Some(Self::sip()),
"sips" => Some(Self::sips()),
"vnc" => Some(Self::vnc()),
"coap" => Some(Self::coap()),
"coaps" => Some(Self::coaps()),
"ws" => Some(Self::http()),
"wss" => Some(Self::https()),
_ => None,
}
}
pub fn value(&self) -> &u16 {
&self.0
}
}
impl PartialEq for HostKind {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::DomainName(lhs), Self::DomainName(rhs)) => {
lhs.to_lowercase() == rhs.to_lowercase()
}
(Self::IPV4(lhs), Self::IPV4(rhs)) => lhs == rhs,
(Self::IPV6(lhs), Self::IPV6(rhs)) => lhs == rhs,
(Self::IPVFuture(lv, ld), Self::IPVFuture(rv, rd)) => {
lv == rv && ld.to_uppercase() == rd.to_uppercase()
}
_ => false,
}
}
}
impl Hash for HostKind {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::DomainName(v) => v.to_lowercase().hash(state),
Self::IPV4(v) => v.hash(state),
Self::IPV6(v) => v.hash(state),
Self::IPVFuture(v, vv) => {
v.hash(state);
vv.to_uppercase().hash(state)
}
}
}
}
impl Display for HostKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
HostKind::IPV4(address) => write!(f, "{}", address),
HostKind::IPV6(address) => write!(f, "[{}]", address),
HostKind::IPVFuture(version, address) => write!(f, "[v{:X}.{}]", version, address),
HostKind::DomainName(address) => write!(f, "{}", address),
}
}
}
impl Normalize for HostKind {
fn normalize(self) -> IriResult<Self>
where
Self: Sized,
{
Ok(match self {
HostKind::IPVFuture(version, address) => {
HostKind::IPVFuture(version, address.to_uppercase())
}
HostKind::DomainName(name) => HostKind::DomainName(name.to_lowercase()),
_ => self,
})
}
}
impl Display for Host {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Host {
type Err = IriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (host, port) = parse_ihost(s)?;
if port.is_some() {
Err(ErrorKind::ParseHostError(s.to_string()).into())
} else {
Ok(host)
}
}
}
impl ValidateStr for Host {
fn is_valid(s: &str) -> bool {
parse::is_ihost(s)
}
}
impl Normalize for Host {
fn normalize(self) -> IriResult<Self>
where
Self: Sized,
{
self.0.normalize().map(Self)
}
}
impl Host {
pub fn new_domain_name(name: &str) -> IriResult<Self> {
if parse::is_ireg_name(name) {
Ok(Self(HostKind::DomainName(name.to_string())))
} else {
Err(ErrorKind::ParseHostError(name.to_string()).into())
}
}
pub fn new_ipv4_address(address: Ipv4Addr) -> IriResult<Self> {
Ok(Self(HostKind::IPV4(address)))
}
pub fn new_ipv6_address(address: Ipv6Addr) -> IriResult<Self> {
Ok(Self(HostKind::IPV6(address)))
}
pub fn new_ipv_future_address(version: u16, address: &str) -> IriResult<Self> {
if IP_FUTURE.is_match(address) {
Ok(Self(HostKind::IPVFuture(version, address.to_string())))
} else {
Err(ErrorKind::ParseIpAddressError(address.to_string()).into())
}
}
pub fn is_domain_name(&self) -> bool {
matches!(&self.0, HostKind::DomainName(_))
}
pub fn is_ipv4_address(&self) -> bool {
matches!(&self.0, HostKind::IPV4(_))
}
pub fn is_ipv6_address(&self) -> bool {
matches!(&self.0, HostKind::IPV6(_))
}
pub fn is_ip_future_address(&self) -> bool {
matches!(&self.0, HostKind::IPVFuture(_, _))
}
pub fn value(&self) -> &HostKind {
&self.0
}
}
impl Display for UserInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.user_name)?;
if let Some(password) = &self.password {
write!(f, ":{}", password)?;
}
write!(f, "@")
}
}
impl FromStr for UserInfo {
type Err = IriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (user_name, password) = parse_iuserinfo(s)?;
Ok(Self {
user_name,
password,
})
}
}
impl UserInfo {
pub fn new(user_name: &str) -> IriResult<Self> {
if !user_name.is_empty() && parse::is_iuserinfo(user_name) {
Ok(Self {
user_name: user_name.to_string(),
password: None,
})
} else {
Err(ErrorKind::InvalidChar(Component::Authority).into())
}
}
pub fn new_with_password(user_name: &str, password: &str) -> IriResult<Self> {
if !user_name.is_empty()
&& parse::is_iuserinfo(user_name)
&& !password.is_empty()
&& parse::is_iuserinfo(password)
{
Ok(Self {
user_name: user_name.to_string(),
password: Some(password.to_string()),
})
} else {
Err(ErrorKind::InvalidChar(Component::Authority).into())
}
}
pub fn user_name(&self) -> &String {
&self.user_name
}
pub fn has_password(&self) -> bool {
self.password.is_some()
}
pub fn password(&self) -> &Option<String> {
&self.password
}
pub fn set_user_name(&mut self, user_name: &str) -> IriResult<()> {
if parse::is_iuserinfo(user_name) {
self.user_name = user_name.to_string();
Ok(())
} else {
Err(ErrorKind::InvalidChar(Component::Authority).into())
}
}
pub fn set_password(&mut self, password: &str) -> IriResult<()> {
if parse::is_iuserinfo(password) {
self.password = Some(password.to_string());
Ok(())
} else {
Err(ErrorKind::InvalidChar(Component::Authority).into())
}
}
pub fn unset_password(&mut self) {
self.password = None
}
}
impl Display for Authority {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "//")?;
if let Some(user_info) = &self.user_info {
write!(f, "{}", user_info)?;
}
write!(f, "{}", self.host)?;
if let Some(port) = &self.port {
write!(f, "{}", port)?;
}
Ok(())
}
}
impl FromStr for Authority {
type Err = IriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_authority(s)
}
}
impl ValidateStr for Authority {}
impl Normalize for Authority {
fn normalize(self) -> IriResult<Self> {
Ok(Self {
host: self.host.normalize()?,
..self
})
}
}
impl Authority {
pub fn new(host: Host) -> Self {
Self {
host,
user_info: None,
port: None,
}
}
pub fn new_with_port(host: Host, port: Port) -> Self {
Self {
host,
user_info: None,
port: Some(port),
}
}
pub fn new_with_user_info(host: Host, user_info: UserInfo) -> Self {
Self {
host,
user_info: Some(user_info),
port: None,
}
}
pub fn new_with_port_and_user_info(host: Host, port: Port, user_info: UserInfo) -> Self {
Self {
host,
user_info: Some(user_info),
port: Some(port),
}
}
pub fn host(&self) -> &Host {
&self.host
}
pub fn has_port(&self) -> bool {
self.port.is_some()
}
pub fn port(&self) -> &Option<Port> {
&self.port
}
pub fn user_info(&self) -> &Option<UserInfo> {
&self.user_info
}
pub fn has_user_info(&self) -> bool {
self.user_info.is_some()
}
pub fn set_host(&mut self, host: Host) {
self.host = host;
}
pub fn set_port(&mut self, port: Port) {
self.port = Some(port);
}
pub fn unset_port(&mut self) {
self.port = None;
}
pub fn set_user_info(&mut self, user_info: UserInfo) {
self.user_info = Some(user_info);
}
pub fn unset_user_info(&mut self) {
self.user_info = None;
}
}
fn parse_authority(s: &str) -> IriResult<Authority> {
let parts = s.split('@').collect::<Vec<&str>>();
match parts.len() {
1 => {
let (host, port) = parse_ihost(s)?;
if let Some(port) = port {
Ok(Authority::new_with_port(host, port))
} else {
Ok(Authority::new(host))
}
}
2 => {
let user_info = match parse_iuserinfo(parts.get(0).unwrap())? {
(user_name, None) => UserInfo::new(&user_name)?,
(user_name, Some(password)) => UserInfo::new_with_password(&user_name, &password)?,
};
let (host, port) = parse_ihost(parts.get(1).unwrap())?;
if let Some(port) = port {
Ok(Authority::new_with_port_and_user_info(
host, port, user_info,
))
} else {
Ok(Authority::new_with_user_info(host, user_info))
}
}
_ => Err(ErrorKind::ParseAuthorityError(s.to_string()).into()),
}
}
fn parse_iuserinfo(s: &str) -> IriResult<(String, Option<String>)> {
let parts = s.split('@').collect::<Vec<&str>>();
if parts.iter().all(|s| parse::is_iuserinfo(s)) {
match parts.len() {
1 => Ok((s.to_string(), None)),
2 => Ok((
(*parts.get(0).unwrap()).to_string(),
Some((*parts.get(0).unwrap()).to_string()),
)),
_ => Err(ErrorKind::ParseUserInfoError(s.to_string()).into()),
}
} else {
Err(ErrorKind::InvalidChar(Component::Authority).into())
}
}
#[allow(clippy::unnecessary_unwrap)]
fn parse_ihost(s: &str) -> IriResult<(Host, Option<Port>)> {
if let Some(captures) = IPV4.captures(s) {
let address = captures.get(1).unwrap().as_str();
Ok((
Host(HostKind::IPV4(address.parse().chain_err(|| {
ErrorKind::ParseIpAddressError(address.to_string())
})?)),
match captures.get(3) {
None => None,
Some(port) => Some(Port::from_str(port.as_str()).unwrap()),
},
))
} else if let Some(captures) = IPVMORE.captures(s) {
if captures.get(2).is_none() {
let address = captures.get(3).unwrap().as_str();
Ok((
Host(HostKind::IPV6(address.parse().chain_err(|| {
ErrorKind::ParseIpAddressError(address.to_string())
})?)),
match captures.get(5) {
None => None,
Some(port) => Some(Port::from_str(port.as_str()).unwrap()),
},
))
} else {
let version = captures.get(2).unwrap().as_str();
Ok((
Host(HostKind::IPVFuture(
version
.parse()
.chain_err(|| ErrorKind::ParseIpAddressError(version.to_string()))?,
captures.get(3).unwrap().as_str().to_string(),
)),
match captures.get(5) {
None => None,
Some(port) => Some(Port::from_str(port.as_str()).unwrap()),
},
))
}
} else {
let parts = s.split(':').collect::<Vec<&str>>();
match parts.len() {
1 => {
if parse::is_ireg_name(s) {
Ok((Host(HostKind::DomainName(s.to_string())), None))
} else {
Err(ErrorKind::ParseHostError(s.to_string()).into())
}
}
2 => {
let host = parts.get(0).unwrap();
let port = Port::from_str(parts.get(1).unwrap());
if parse::is_ireg_name(host) && port.is_ok() {
Ok((
Host(HostKind::DomainName(host.to_string())),
Some(port.unwrap()),
))
} else {
Err(ErrorKind::ParseHostError(s.to_string()).into())
}
}
_ => Err(ErrorKind::ParseHostError(s.to_string()).into()),
}
}
}