use core::fmt::{self, Display as _, Write as _};
use core::marker::PhantomData;
#[cfg(feature = "alloc")]
use alloc::collections::TryReserveError;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::string::ToString;
use crate::format::Censored;
#[cfg(feature = "alloc")]
use crate::format::{ToDedicatedString, ToStringFallible};
use crate::normalize::{self, NormalizationMode, PathCharacteristic, PctCaseNormalized};
use crate::parser::str::{find_split, prior_byte2};
use crate::parser::trusted as trusted_parser;
use crate::parser::validate as parser;
use crate::spec::Spec;
use crate::types::{RiAbsoluteStr, RiReferenceStr, RiRelativeStr, RiStr};
#[cfg(feature = "alloc")]
use crate::types::{RiAbsoluteString, RiReferenceString, RiRelativeString, RiString};
use crate::validate::{Error, ErrorKind};
#[derive(Debug, Clone)]
pub struct PortBuilder<'a>(PortBuilderRepr<'a>);
impl Default for PortBuilder<'_> {
#[inline]
fn default() -> Self {
Self(PortBuilderRepr::Empty)
}
}
impl From<u8> for PortBuilder<'_> {
#[inline]
fn from(v: u8) -> Self {
Self(PortBuilderRepr::Integer(v.into()))
}
}
impl From<u16> for PortBuilder<'_> {
#[inline]
fn from(v: u16) -> Self {
Self(PortBuilderRepr::Integer(v))
}
}
impl<'a> From<&'a str> for PortBuilder<'a> {
#[inline]
fn from(v: &'a str) -> Self {
Self(PortBuilderRepr::String(v))
}
}
#[cfg(feature = "alloc")]
impl<'a> From<&'a alloc::string::String> for PortBuilder<'a> {
#[inline]
fn from(v: &'a alloc::string::String) -> Self {
Self(PortBuilderRepr::String(v.as_str()))
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
enum PortBuilderRepr<'a> {
Empty,
Integer(u16),
String(&'a str),
}
#[derive(Clone)]
pub struct UserinfoBuilder<'a>(UserinfoRepr<'a>);
impl Default for UserinfoBuilder<'_> {
#[inline]
fn default() -> Self {
Self(UserinfoRepr::None)
}
}
impl fmt::Debug for UserinfoBuilder<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("UserinfoBuilder");
if let Some((user, password)) = self.to_user_password() {
debug.field("user", &user);
if matches!(password, None | Some("")) {
debug.field("password", &password);
} else {
debug.field("password", &Some(Censored));
}
}
debug.finish()
}
}
impl<'a> UserinfoBuilder<'a> {
#[must_use]
fn to_user_password(&self) -> Option<(&'a str, Option<&'a str>)> {
match &self.0 {
UserinfoRepr::None => None,
UserinfoRepr::Direct(s) => match find_split(s, b':') {
None => Some((s, None)),
Some((user, password)) => Some((user, Some(password))),
},
UserinfoRepr::UserPass(user, password) => Some((*user, *password)),
}
}
}
impl<'a> From<&'a str> for UserinfoBuilder<'a> {
#[inline]
fn from(direct: &'a str) -> Self {
Self(UserinfoRepr::Direct(direct))
}
}
impl<'a> From<(&'a str, &'a str)> for UserinfoBuilder<'a> {
#[inline]
fn from((user, password): (&'a str, &'a str)) -> Self {
Self(UserinfoRepr::UserPass(user, Some(password)))
}
}
impl<'a> From<(&'a str, Option<&'a str>)> for UserinfoBuilder<'a> {
#[inline]
fn from((user, password): (&'a str, Option<&'a str>)) -> Self {
Self(UserinfoRepr::UserPass(user, password))
}
}
#[cfg(feature = "alloc")]
impl<'a> From<&'a alloc::string::String> for UserinfoBuilder<'a> {
#[inline]
fn from(v: &'a alloc::string::String) -> Self {
Self::from(v.as_str())
}
}
#[derive(Clone, Copy)]
enum UserinfoRepr<'a> {
None,
Direct(&'a str),
UserPass(&'a str, Option<&'a str>),
}
#[derive(Default, Debug, Clone)]
struct AuthorityBuilder<'a> {
host: HostRepr<'a>,
port: PortBuilder<'a>,
userinfo: UserinfoBuilder<'a>,
}
impl AuthorityBuilder<'_> {
fn fmt_write_to<S: Spec>(&self, f: &mut fmt::Formatter<'_>, normalize: bool) -> fmt::Result {
match &self.userinfo.0 {
UserinfoRepr::None => {}
UserinfoRepr::Direct(userinfo) => {
if normalize {
PctCaseNormalized::<S>::new(userinfo).fmt(f)?;
} else {
userinfo.fmt(f)?;
}
f.write_char('@')?;
}
UserinfoRepr::UserPass(user, password) => {
if normalize {
PctCaseNormalized::<S>::new(user).fmt(f)?;
} else {
f.write_str(user)?;
}
if let Some(password) = password {
f.write_char(':')?;
if normalize {
PctCaseNormalized::<S>::new(password).fmt(f)?;
} else {
password.fmt(f)?;
}
}
f.write_char('@')?;
}
}
match self.host {
HostRepr::String(host) => {
if normalize {
normalize::normalize_host_port::<S>(f, host)?;
} else {
f.write_str(host)?;
}
}
#[cfg(feature = "std")]
HostRepr::IpAddr(ipaddr) => match ipaddr {
std::net::IpAddr::V4(v) => v.fmt(f)?,
std::net::IpAddr::V6(v) => write!(f, "[{v}]")?,
},
}
match self.port.0 {
PortBuilderRepr::Empty => {}
PortBuilderRepr::Integer(v) => write!(f, ":{v}")?,
PortBuilderRepr::String(v) => {
if !(v.is_empty() && normalize) {
write!(f, ":{v}")?;
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
enum HostRepr<'a> {
String(&'a str),
#[cfg(feature = "std")]
IpAddr(std::net::IpAddr),
}
impl Default for HostRepr<'_> {
#[inline]
fn default() -> Self {
Self::String("")
}
}
#[derive(Default, Debug, Clone)]
pub struct Builder<'a> {
scheme: Option<&'a str>,
authority: Option<AuthorityBuilder<'a>>,
path: &'a str,
query: Option<&'a str>,
fragment: Option<&'a str>,
normalize: bool,
}
impl<'a> Builder<'a> {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn fmt_write_to<S: Spec>(
&self,
f: &mut fmt::Formatter<'_>,
path_is_absolute: bool,
) -> fmt::Result {
if let Some(scheme) = self.scheme {
if self.normalize {
normalize::normalize_scheme(f, scheme)?;
} else {
f.write_str(scheme)?;
}
f.write_char(':')?;
}
if let Some(authority) = &self.authority {
f.write_str("//")?;
authority.fmt_write_to::<S>(f, self.normalize)?;
}
if !self.normalize {
f.write_str(self.path)?;
} else if self.scheme.is_some() || self.authority.is_some() || path_is_absolute {
let op = normalize::NormalizationOp {
mode: NormalizationMode::Default,
};
normalize::PathToNormalize::from_single_path(self.path).fmt_write_normalize::<S, _>(
f,
op,
self.authority.is_some(),
)?;
} else {
PctCaseNormalized::<S>::new(self.path).fmt(f)?;
}
if let Some(query) = self.query {
f.write_char('?')?;
if self.normalize {
normalize::normalize_query::<S>(f, query)?;
} else {
f.write_str(query)?;
}
}
if let Some(fragment) = self.fragment {
f.write_char('#')?;
if self.normalize {
normalize::normalize_fragment::<S>(f, fragment)?;
} else {
f.write_str(fragment)?;
}
}
Ok(())
}
#[inline]
pub fn build<T>(self) -> Result<Built<'a, T>, Error>
where
T: ?Sized + Buildable<'a>,
{
<T as private::Sealed<'a>>::validate_builder(self)
}
}
impl<'a> Builder<'a> {
#[inline]
pub fn scheme(&mut self, v: &'a str) {
self.scheme = Some(v);
}
#[inline]
pub fn unset_scheme(&mut self) {
self.scheme = None;
}
#[inline]
pub fn path(&mut self, v: &'a str) {
self.path = v;
}
#[inline]
fn authority_builder(&mut self) -> &mut AuthorityBuilder<'a> {
self.authority.get_or_insert_with(AuthorityBuilder::default)
}
#[inline]
pub fn unset_authority(&mut self) {
self.authority = None;
}
#[inline]
pub fn userinfo<T: Into<UserinfoBuilder<'a>>>(&mut self, v: T) {
self.authority_builder().userinfo = v.into();
}
#[inline]
pub fn unset_userinfo(&mut self) {
self.authority_builder().userinfo = UserinfoBuilder::default();
}
#[inline]
pub fn host(&mut self, v: &'a str) {
self.authority_builder().host = HostRepr::String(v);
}
#[cfg(feature = "std")]
#[inline]
pub fn ip_address<T: Into<std::net::IpAddr>>(&mut self, addr: T) {
self.authority_builder().host = HostRepr::IpAddr(addr.into());
}
#[inline]
pub fn port<T: Into<PortBuilder<'a>>>(&mut self, v: T) {
self.authority_builder().port = v.into();
}
#[inline]
pub fn unset_port(&mut self) {
self.authority_builder().port = PortBuilder::default();
}
#[inline]
pub fn query(&mut self, v: &'a str) {
self.query = Some(v);
}
#[inline]
pub fn unset_query(&mut self) {
self.query = None;
}
#[inline]
pub fn fragment(&mut self, v: &'a str) {
self.fragment = Some(v);
}
#[inline]
pub fn unset_fragment(&mut self) {
self.fragment = None;
}
#[inline]
pub fn unset_normalize(&mut self) {
self.normalize = false;
}
#[inline]
pub fn normalize(&mut self) {
self.normalize = true;
}
}
impl<'a, S: Spec> From<&'a RiReferenceStr<S>> for Builder<'a> {
fn from(iri: &'a RiReferenceStr<S>) -> Self {
let (scheme, authority_str, path, query, fragment) =
trusted_parser::decompose_iri_reference(iri).to_major();
let authority = authority_str.map(|authority_str| {
let authority_components =
trusted_parser::authority::decompose_authority(authority_str);
AuthorityBuilder {
host: HostRepr::String(authority_components.host()),
port: authority_components
.port()
.map_or_else(Default::default, Into::into),
userinfo: authority_components
.userinfo()
.map_or_else(Default::default, Into::into),
}
});
Self {
scheme,
authority,
path,
query,
fragment,
normalize: false,
}
}
}
impl<'a, S: Spec> From<&'a RiAbsoluteStr<S>> for Builder<'a> {
#[inline]
fn from(iri: &'a RiAbsoluteStr<S>) -> Self {
Self::from(AsRef::<RiReferenceStr<S>>::as_ref(iri))
}
}
impl<'a, S: Spec> From<&'a RiRelativeStr<S>> for Builder<'a> {
#[inline]
fn from(iri: &'a RiRelativeStr<S>) -> Self {
Self::from(AsRef::<RiReferenceStr<S>>::as_ref(iri))
}
}
impl<'a, S: Spec> From<&'a RiStr<S>> for Builder<'a> {
#[inline]
fn from(iri: &'a RiStr<S>) -> Self {
Self::from(AsRef::<RiReferenceStr<S>>::as_ref(iri))
}
}
#[derive(Debug)]
pub struct Built<'a, T: ?Sized> {
builder: Builder<'a>,
path_is_absolute: bool,
_ty_str: PhantomData<fn() -> T>,
}
impl<T: ?Sized> Clone for Built<'_, T> {
#[inline]
fn clone(&self) -> Self {
Self {
builder: self.builder.clone(),
path_is_absolute: self.path_is_absolute,
_ty_str: PhantomData,
}
}
}
macro_rules! impl_stringifiers {
($borrowed:ident, $owned:ident) => {
impl<S: Spec> Built<'_, $borrowed<S>> {
#[inline]
pub fn ensure_rfc3986_normalizable(&self) -> Result<(), normalize::Error> {
if self.builder.authority.is_none() {
let path = normalize::PathToNormalize::from_single_path(self.builder.path);
path.ensure_rfc3986_normalizable_with_authority_absent()?;
}
Ok(())
}
}
impl<S: Spec> fmt::Display for Built<'_, $borrowed<S>> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.builder.fmt_write_to::<S>(f, self.path_is_absolute)
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> ToDedicatedString for Built<'_, $borrowed<S>> {
type Target = $owned<S>;
#[inline]
fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
let s = self.try_to_string()?;
Ok(unsafe {
Self::Target::new_unchecked_justified(
s,
"the IRI to be built is already validated",
)
})
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> From<Built<'_, $borrowed<S>>> for $owned<S> {
#[inline]
fn from(builder: Built<'_, $borrowed<S>>) -> Self {
(&builder).into()
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> From<&Built<'_, $borrowed<S>>> for $owned<S> {
#[inline]
fn from(builder: &Built<'_, $borrowed<S>>) -> Self {
let s = builder.to_string();
unsafe {
Self::new_unchecked_justified(s, "the IRI to be built is already validated")
}
}
}
};
}
impl_stringifiers!(RiReferenceStr, RiReferenceString);
impl_stringifiers!(RiStr, RiString);
impl_stringifiers!(RiAbsoluteStr, RiAbsoluteString);
impl_stringifiers!(RiRelativeStr, RiRelativeString);
pub trait Buildable<'a>: private::Sealed<'a> {}
impl<'a, S: Spec> private::Sealed<'a> for RiReferenceStr<S> {
fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
Ok(Built {
builder,
path_is_absolute,
_ty_str: PhantomData,
})
}
}
impl<S: Spec> Buildable<'_> for RiReferenceStr<S> {}
impl<'a, S: Spec> private::Sealed<'a> for RiStr<S> {
fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
if builder.scheme.is_none() {
return Err(Error::with_kind(ErrorKind::InvalidScheme));
}
let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
Ok(Built {
builder,
path_is_absolute,
_ty_str: PhantomData,
})
}
}
impl<S: Spec> Buildable<'_> for RiStr<S> {}
impl<'a, S: Spec> private::Sealed<'a> for RiAbsoluteStr<S> {
fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
if builder.scheme.is_none() {
return Err(Error::with_kind(ErrorKind::InvalidScheme));
}
if builder.fragment.is_some() {
return Err(Error::with_kind(ErrorKind::UnexpectedFragment));
}
let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
Ok(Built {
builder,
path_is_absolute,
_ty_str: PhantomData,
})
}
}
impl<S: Spec> Buildable<'_> for RiAbsoluteStr<S> {}
impl<'a, S: Spec> private::Sealed<'a> for RiRelativeStr<S> {
fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
if builder.scheme.is_some() {
return Err(Error::with_kind(ErrorKind::UnexpectedAbsolute));
}
let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
Ok(Built {
builder,
path_is_absolute,
_ty_str: PhantomData,
})
}
}
impl<S: Spec> Buildable<'_> for RiRelativeStr<S> {}
fn validate_builder_for_iri_reference<S: Spec>(builder: &Builder<'_>) -> Result<bool, Error> {
if let Some(scheme) = builder.scheme {
parser::validate_scheme(scheme)?;
}
if let Some(authority) = &builder.authority {
match &authority.userinfo.0 {
UserinfoRepr::None => {}
UserinfoRepr::Direct(userinfo) => {
parser::validate_userinfo::<S>(userinfo)?;
}
UserinfoRepr::UserPass(user, password) => {
if user.contains(':') {
return Err(Error::with_kind(ErrorKind::InvalidUserInfo));
}
parser::validate_userinfo::<S>(user)?;
if let Some(password) = password {
parser::validate_userinfo::<S>(password)?;
}
}
}
match authority.host {
HostRepr::String(s) => parser::validate_host::<S>(s)?,
#[cfg(feature = "std")]
HostRepr::IpAddr(_) => {}
}
if let PortBuilderRepr::String(s) = authority.port.0 {
if !s.bytes().all(|b| b.is_ascii_digit()) {
return Err(Error::with_kind(ErrorKind::InvalidPort));
}
}
}
let path_is_absolute: bool;
let mut is_path_acceptable;
if builder.normalize {
if builder.scheme.is_some() || builder.authority.is_some() || builder.path.starts_with('/')
{
if builder.authority.is_some() {
is_path_acceptable = builder.path.is_empty() || builder.path.starts_with('/');
} else {
is_path_acceptable = true;
}
let op = normalize::NormalizationOp {
mode: NormalizationMode::Default,
};
let path_characteristic = PathCharacteristic::from_path_to_display::<S>(
&normalize::PathToNormalize::from_single_path(builder.path),
op,
builder.authority.is_some(),
);
path_is_absolute = path_characteristic.is_absolute();
is_path_acceptable = is_path_acceptable
&& match path_characteristic {
PathCharacteristic::CommonAbsolute | PathCharacteristic::CommonRelative => true,
PathCharacteristic::StartsWithDoubleSlash
| PathCharacteristic::RelativeFirstSegmentHasColon => {
builder.scheme.is_some() || builder.authority.is_some()
}
};
} else {
path_is_absolute = false;
is_path_acceptable = prior_byte2(builder.path.as_bytes(), b'/', b':') != Some(b':');
}
} else {
path_is_absolute = builder.path.starts_with('/');
is_path_acceptable = if builder.authority.is_some() {
path_is_absolute || builder.path.is_empty()
} else if builder.scheme.is_some() || path_is_absolute {
!builder.path.starts_with("//")
} else {
prior_byte2(builder.path.as_bytes(), b'/', b':') != Some(b':')
};
}
if !is_path_acceptable {
return Err(Error::with_kind(ErrorKind::InvalidPath));
}
if let Some(query) = builder.query {
parser::validate_query::<S>(query)?;
}
if let Some(fragment) = builder.fragment {
parser::validate_fragment::<S>(fragment)?;
}
Ok(path_is_absolute)
}
mod private {
use super::{Builder, Built, Error};
pub trait Sealed<'a> {
fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error>;
}
}