use crate::{
encoding::{
encoder::{IRegName, IUserinfo, Port, RegName, Userinfo},
table, EStr, Encoder,
},
internal::{AuthMeta, HostMeta},
};
use core::{marker::PhantomData, num::ParseIntError};
use ref_cast::{ref_cast_custom, RefCastCustom};
#[cfg(feature = "net")]
use crate::net::{Ipv4Addr, Ipv6Addr};
#[cfg(all(feature = "net", feature = "std"))]
use std::{
io,
net::{SocketAddr, ToSocketAddrs},
};
pub type IAuthority<'a> = Authority<'a, IUserinfo, IRegName>;
pub type IHost<'a> = Host<'a, IRegName>;
#[derive(RefCastCustom)]
#[repr(transparent)]
pub struct Scheme {
inner: str,
}
impl Scheme {
#[ref_cast_custom]
#[inline]
pub(crate) const fn new_validated(scheme: &str) -> &Scheme;
#[inline]
#[must_use]
pub const fn new_or_panic(s: &str) -> &Scheme {
match Self::new(s) {
Some(scheme) => scheme,
None => panic!("invalid scheme"),
}
}
#[inline]
#[must_use]
pub const fn new(s: &str) -> Option<&Scheme> {
if matches!(s.as_bytes(), [first, rem @ ..]
if first.is_ascii_alphabetic() && table::SCHEME.validate(rem))
{
Some(Scheme::new_validated(s))
} else {
None
}
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
&self.inner
}
}
impl PartialEq for Scheme {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner.eq_ignore_ascii_case(&other.inner)
}
}
impl Eq for Scheme {}
#[derive(Clone, Copy)]
struct AuthorityInner<'a> {
val: &'a str,
meta: AuthMeta,
}
impl<'a> AuthorityInner<'a> {
fn userinfo(&self) -> Option<&'a EStr<IUserinfo>> {
let host_start = self.meta.host_bounds.0;
(host_start != 0).then(|| EStr::new_validated(&self.val[..host_start - 1]))
}
fn host(&self) -> &'a str {
let (start, end) = self.meta.host_bounds;
&self.val[start..end]
}
fn port(&self) -> Option<&'a EStr<Port>> {
let host_end = self.meta.host_bounds.1;
(host_end != self.val.len()).then(|| EStr::new_validated(&self.val[host_end + 1..]))
}
fn port_to_u16(&self) -> Result<Option<u16>, ParseIntError> {
self.port()
.filter(|s| !s.is_empty())
.map(|s| s.as_str().parse())
.transpose()
}
#[cfg(all(feature = "net", feature = "std"))]
fn socket_addrs(&self, default_port: u16) -> io::Result<impl Iterator<Item = SocketAddr>> {
use std::vec;
let port = self
.port_to_u16()
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid port value"))?
.unwrap_or(default_port);
match self.meta.host_meta {
HostMeta::Ipv4(addr) => Ok(vec![(addr, port).into()].into_iter()),
HostMeta::Ipv6(addr) => Ok(vec![(addr, port).into()].into_iter()),
HostMeta::IpvFuture => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"address mechanism not supported",
)),
HostMeta::RegName => {
let name = EStr::<IRegName>::new_validated(self.host());
let name = name.decode().into_string().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"registered name does not decode to valid UTF-8",
)
})?;
(&name[..], port).to_socket_addrs()
}
}
}
}
#[derive(Clone, Copy)]
pub struct Authority<'a, UserinfoE = Userinfo, RegNameE = RegName> {
inner: AuthorityInner<'a>,
_marker: PhantomData<(UserinfoE, RegNameE)>,
}
impl<'a, T, U> Authority<'a, T, U> {
pub(crate) fn cast<V, W>(self) -> Authority<'a, V, W> {
Authority {
inner: self.inner,
_marker: PhantomData,
}
}
}
impl<'a, UserinfoE: Encoder, RegNameE: Encoder> Authority<'a, UserinfoE, RegNameE> {
pub(crate) const fn new(val: &'a str, meta: AuthMeta) -> Self {
Self {
inner: AuthorityInner { val, meta },
_marker: PhantomData,
}
}
pub const EMPTY: Authority<'static, UserinfoE, RegNameE> = Authority::new("", AuthMeta::EMPTY);
pub(crate) fn meta(&self) -> AuthMeta {
self.inner.meta
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &'a str {
self.inner.val
}
#[must_use]
pub fn userinfo(&self) -> Option<&'a EStr<UserinfoE>> {
self.inner.userinfo().map(EStr::cast)
}
#[must_use]
pub fn host(&self) -> &'a str {
self.inner.host()
}
#[cfg_attr(feature = "net", doc = "use std::net::{Ipv4Addr, Ipv6Addr};")]
#[cfg_attr(
feature = "net",
doc = "assert!(matches!(auth.host_parsed(), Host::Ipv4(Ipv4Addr::LOCALHOST)));"
)]
#[cfg_attr(
not(feature = "net"),
doc = "assert!(matches!(auth.host_parsed(), Host::Ipv4 { .. }));"
)]
#[cfg_attr(
feature = "net",
doc = "assert!(matches!(auth.host_parsed(), Host::Ipv6(Ipv6Addr::LOCALHOST)));"
)]
#[cfg_attr(
not(feature = "net"),
doc = "assert!(matches!(auth.host_parsed(), Host::Ipv6 { .. }));"
)]
#[must_use]
pub fn host_parsed(&self) -> Host<'a, RegNameE> {
match self.inner.meta.host_meta {
#[cfg(feature = "net")]
HostMeta::Ipv4(addr) => Host::Ipv4(addr),
#[cfg(feature = "net")]
HostMeta::Ipv6(addr) => Host::Ipv6(addr),
#[cfg(not(feature = "net"))]
HostMeta::Ipv4() => Host::Ipv4(),
#[cfg(not(feature = "net"))]
HostMeta::Ipv6() => Host::Ipv6(),
HostMeta::IpvFuture => Host::IpvFuture,
HostMeta::RegName => Host::RegName(EStr::new_validated(self.host())),
}
}
#[must_use]
pub fn port(&self) -> Option<&'a EStr<Port>> {
self.inner.port()
}
pub fn port_to_u16(&self) -> Result<Option<u16>, ParseIntError> {
self.inner.port_to_u16()
}
#[cfg(all(feature = "net", feature = "std"))]
pub fn socket_addrs(&self, default_port: u16) -> io::Result<impl Iterator<Item = SocketAddr>> {
self.inner.socket_addrs(default_port)
}
#[inline]
#[must_use]
pub fn has_userinfo(&self) -> bool {
self.inner.meta.host_bounds.0 != 0
}
#[inline]
#[must_use]
pub fn has_port(&self) -> bool {
self.inner.meta.host_bounds.1 != self.inner.val.len()
}
}
#[derive(Clone, Copy)]
#[cfg_attr(fuzzing, derive(PartialEq, Eq))]
pub enum Host<'a, RegNameE: Encoder = RegName> {
#[cfg_attr(not(feature = "net"), non_exhaustive)]
Ipv4(
#[cfg(feature = "net")]
Ipv4Addr,
),
#[cfg_attr(not(feature = "net"), non_exhaustive)]
Ipv6(
#[cfg(feature = "net")]
Ipv6Addr,
),
#[non_exhaustive]
IpvFuture,
RegName(&'a EStr<RegNameE>),
}