//! URI/IRI builder. //! //! See the documentation of [`Builder`] type. 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::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; /// Port builder. /// /// This type is intended to be created by `From` trait implementations, and /// to be passed to [`Builder::port`] method. #[derive(Debug, Clone)] pub struct PortBuilder<'a>(PortBuilderRepr<'a>); impl Default for PortBuilder<'_> { #[inline] fn default() -> Self { Self(PortBuilderRepr::Empty) } } impl From for PortBuilder<'_> { #[inline] fn from(v: u8) -> Self { Self(PortBuilderRepr::Integer(v.into())) } } impl From 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())) } } /// Internal representation of a port builder. #[derive(Debug, Clone, Copy)] #[non_exhaustive] enum PortBuilderRepr<'a> { /// Empty port. Empty, /// Port as an integer. /// /// Note that RFC 3986 accepts any number of digits as a port, but /// practically (at least in TCP/IP) `u16` is enough. Integer(u16), /// Port as a string. String(&'a str), } /// Userinfo builder. /// /// This type is intended to be created by `From` trait implementations, and /// to be passed to [`Builder::userinfo`] method. #[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); // > Applications should not render as clear text any data after // > the first colon (":") character found within a userinfo // > subcomponent unless the data after the colon is the empty // > string (indicating no password). if matches!(password, None | Some("")) { debug.field("password", &password); } else { debug.field("password", &Some(Censored)); } } debug.finish() } } impl<'a> UserinfoBuilder<'a> { /// Decomposes the userinfo into `user` and `password`. #[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()) } } /// Internal representation of a userinfo builder. #[derive(Clone, Copy)] enum UserinfoRepr<'a> { /// Not specified (absent). None, /// Direct `userinfo` content. Direct(&'a str), /// User name and password. UserPass(&'a str, Option<&'a str>), } /// URI/IRI authority builder. #[derive(Default, Debug, Clone)] struct AuthorityBuilder<'a> { /// Host. host: HostRepr<'a>, /// Port. port: PortBuilder<'a>, /// Userinfo. userinfo: UserinfoBuilder<'a>, } impl AuthorityBuilder<'_> { /// Writes the authority to the given formatter. fn fmt_write_to(&self, f: &mut fmt::Formatter<'_>, normalize: bool) -> fmt::Result { match &self.userinfo.0 { UserinfoRepr::None => {} UserinfoRepr::Direct(userinfo) => { if normalize { PctCaseNormalized::::new(userinfo).fmt(f)?; } else { userinfo.fmt(f)?; } f.write_char('@')?; } UserinfoRepr::UserPass(user, password) => { if normalize { PctCaseNormalized::::new(user).fmt(f)?; } else { f.write_str(user)?; } if let Some(password) = password { f.write_char(':')?; if normalize { PctCaseNormalized::::new(password).fmt(f)?; } else { password.fmt(f)?; } } f.write_char('@')?; } } match self.host { HostRepr::String(host) => { if normalize { normalize::normalize_host_port::(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) => { // Omit empty port if the normalization is enabled. if !(v.is_empty() && normalize) { write!(f, ":{v}")?; } } } Ok(()) } } /// Host representation. #[derive(Debug, Clone, Copy)] enum HostRepr<'a> { /// Direct string representation. String(&'a str), #[cfg(feature = "std")] /// Dedicated IP address type. IpAddr(std::net::IpAddr), } impl Default for HostRepr<'_> { #[inline] fn default() -> Self { Self::String("") } } /// URI/IRI reference builder. /// /// # Usage /// /// 1. Create builder by [`Builder::new()`][`Self::new`]. /// 2. Set (or unset) components and set normalization mode as you wish. /// 3. Validate by [`Builder::build()`][`Self::build`] and get [`Built`] value. /// 4. Use [`core::fmt::Display`] trait to serialize the resulting [`Built`], /// or use [`From`]/[`Into`] traits to convert into an allocated string types. /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// # #[cfg(not(feature = "alloc"))] /// # use iri_string::types::IriStr; /// # #[cfg(feature = "alloc")] /// use iri_string::types::{IriStr, IriString}; /// /// // 1. Create builder. /// let mut builder = Builder::new(); /// /// // 2. Set (or unset) component and normalization mode. /// builder.scheme("http"); /// builder.host("example.com"); /// builder.path("/foo/../"); /// builder.normalize(); /// /// // 3. Validate and create the result. /// let built = builder.build::()?; /// /// # #[cfg(feature = "alloc")] { /// // 4a. Serialize by `Display` trait (or `ToString`). /// let s = built.to_string(); /// assert_eq!(s, "http://example.com/"); /// # } /// /// # #[cfg(feature = "alloc")] { /// // 4b. Convert into an allocated string types. /// // Thanks to pre-validation by `.build::()`, this conversion is infallible! /// let s: IriString = built.into(); /// assert_eq!(s, "http://example.com/"); /// # } /// /// # Ok::<_, Error>(()) /// ``` #[derive(Default, Debug, Clone)] pub struct Builder<'a> { /// Scheme. scheme: Option<&'a str>, /// Authority. authority: Option>, /// Path. path: &'a str, /// Query (without the leading `?`). query: Option<&'a str>, /// Fragment (without the leading `#`). fragment: Option<&'a str>, /// Normalization mode. normalize: bool, } impl<'a> Builder<'a> { /// Creates a builder with empty data. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let builder = Builder::new(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), ""); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] #[must_use] pub fn new() -> Self { Self::default() } /// Writes the authority to the given formatter. /// /// Don't expose this as public, since this method does not validate. /// /// # Preconditions /// /// The IRI string to be built should be a valid IRI reference. /// Callers are responsible to validate the component values before calling /// this method. fn fmt_write_to( &self, f: &mut fmt::Formatter<'_>, path_is_absolute: bool, ) -> fmt::Result { if let Some(scheme) = self.scheme { // Write the 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::(f, self.normalize)?; } if !self.normalize { // No normalization. f.write_str(self.path)?; } else if self.scheme.is_some() || self.authority.is_some() || path_is_absolute { // Apply full syntax-based normalization. let op = normalize::NormalizationOp { mode: NormalizationMode::Default, }; normalize::PathToNormalize::from_single_path(self.path).fmt_write_normalize::( f, op, self.authority.is_some(), )?; } else { // The IRI reference starts with `path` component, and the path is relative. // Skip path segment normalization. PctCaseNormalized::::new(self.path).fmt(f)?; } if let Some(query) = self.query { f.write_char('?')?; if self.normalize { normalize::normalize_query::(f, query)?; } else { f.write_str(query)?; } } if let Some(fragment) = self.fragment { f.write_char('#')?; if self.normalize { normalize::normalize_fragment::(f, fragment)?; } else { f.write_str(fragment)?; } } Ok(()) } /// Builds the proxy object that can be converted to the desired IRI string type. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriStr; /// # #[cfg(feature = "alloc")] /// use iri_string::types::IriString; /// /// let mut builder = Builder::new(); /// /// builder.scheme("http"); /// builder.host("example.com"); /// builder.path("/foo/bar"); /// /// let built = builder.build::()?; /// /// # #[cfg(feature = "alloc")] { /// // The returned value implements `core::fmt::Display` and /// // `core::string::ToString`. /// assert_eq!(built.to_string(), "http://example.com/foo/bar"); /// /// // The returned value implements `Into<{iri_owned_string_type}>`. /// let iri = IriString::from(built); /// // `let iri: IriString = built.into();` is also OK. /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn build(self) -> Result, Error> where T: ?Sized + Buildable<'a>, { >::validate_builder(self) } } // Setters does not return `&mut Self` or `Self` since it introduces needless // ambiguity for users. // For example, if setters return something and allows method chaining, can you // correctly explain what happens with the code below without reading document? // // ```text // let mut builder = Builder::new().foo("foo").bar("bar"); // let baz = builder.baz("baz").clone().build(); // // Should the result be foo+bar+qux, or foo+bar+baz+qux? // let qux = builder.qux("qux").build(); // ``` impl<'a> Builder<'a> { /// Sets the scheme. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.scheme("foo"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "foo:"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn scheme(&mut self, v: &'a str) { self.scheme = Some(v); } /// Unsets the scheme. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.scheme("foo"); /// builder.unset_scheme(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), ""); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_scheme(&mut self) { self.scheme = None; } /// Sets the path. /// /// Note that no methods are provided to "unset" path since every IRI /// references has a path component (although it can be empty). /// If you want to "unset" the path, just set the empty string. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.path("foo/bar"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "foo/bar"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn path(&mut self, v: &'a str) { self.path = v; } /// Initializes the authority builder. #[inline] fn authority_builder(&mut self) -> &mut AuthorityBuilder<'a> { self.authority.get_or_insert_with(AuthorityBuilder::default) } /// Unsets the authority. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.host("example.com"); /// builder.unset_authority(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), ""); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_authority(&mut self) { self.authority = None; } /// Sets the userinfo. /// /// `userinfo` component always have `user` part (but it can be empty). /// /// Note that `("", None)` is considered as an empty userinfo, rather than /// unset userinfo. /// Also note that the user part cannot have colon characters. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.userinfo("user:pass"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//user:pass@"); /// # } /// # Ok::<_, Error>(()) /// ``` /// /// You can specify `(user, password)` pair. /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// /// builder.userinfo(("user", Some("pass"))); /// # #[cfg(feature = "alloc")] { /// assert_eq!( /// builder.clone().build::()?.to_string(), /// "//user:pass@" /// ); /// # } /// # Ok::<_, Error>(()) /// ``` /// /// `("", None)` is considered as an empty userinfo. /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.userinfo(("", None)); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//@"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn userinfo>>(&mut self, v: T) { self.authority_builder().userinfo = v.into(); } /// Unsets the port. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.userinfo("user:pass"); /// // Note that this does not unset the entire authority. /// // Now empty authority is set. /// builder.unset_userinfo(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_userinfo(&mut self) { self.authority_builder().userinfo = UserinfoBuilder::default(); } /// Sets the reg-name or IP address (i.e. host) without port. /// /// Note that no methods are provided to "unset" host. /// Depending on your situation, set empty string as a reg-name, or unset /// the authority entirely by [`unset_authority`][`Self::unset_authority`] /// method. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.host("example.com"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//example.com"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn host(&mut self, v: &'a str) { self.authority_builder().host = HostRepr::String(v); } /// Sets the IP address as a host. /// /// Note that no methods are provided to "unset" host. /// Depending on your situation, set empty string as a reg-name, or unset /// the authority entirely by [`unset_authority`][`Self::unset_authority`] /// method. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// # #[cfg(feature = "std")] { /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.ip_address(std::net::Ipv4Addr::new(192, 0, 2, 0)); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//192.0.2.0"); /// # } /// # } /// # Ok::<_, Error>(()) /// ``` #[cfg(feature = "std")] #[inline] pub fn ip_address>(&mut self, addr: T) { self.authority_builder().host = HostRepr::IpAddr(addr.into()); } /// Sets the port. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.port(80_u16); /// // Accepts other types that implements `Into>`. /// //builder.port(80_u8); /// //builder.port("80"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//:80"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn port>>(&mut self, v: T) { self.authority_builder().port = v.into(); } /// Unsets the port. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.port(80_u16); /// // Note that this does not unset the entire authority. /// // Now empty authority is set. /// builder.unset_port(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "//"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_port(&mut self) { self.authority_builder().port = PortBuilder::default(); } /// Sets the query. /// /// The string after `?` should be specified. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.query("q=example"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "?q=example"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn query(&mut self, v: &'a str) { self.query = Some(v); } /// Unsets the query. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.query("q=example"); /// builder.unset_query(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), ""); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_query(&mut self) { self.query = None; } /// Sets the fragment. /// /// The string after `#` should be specified. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.fragment("anchor"); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "#anchor"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn fragment(&mut self, v: &'a str) { self.fragment = Some(v); } /// Unsets the fragment. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.fragment("anchor"); /// builder.unset_fragment(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), ""); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_fragment(&mut self) { self.fragment = None; } /// Stop normalizing the result. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.scheme("http"); /// // `%75%73%65%72` is "user". /// builder.userinfo("%75%73%65%72"); /// builder.host("EXAMPLE.COM"); /// builder.port(""); /// builder.path("/foo/../%2e%2e/bar/%2e/baz/."); /// /// builder.unset_normalize(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!( /// iri.to_string(), /// "http://%75%73%65%72@EXAMPLE.COM:/foo/../%2e%2e/bar/%2e/baz/." /// ); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn unset_normalize(&mut self) { self.normalize = false; } /// Normalizes the result using RFC 3986 syntax-based normalization and /// WHATWG URL Standard algorithm. /// /// # Normalization /// /// If `scheme` or `authority` component is present or the path is absolute, /// the build result will fully normalized using full syntax-based normalization: /// /// * case normalization ([RFC 3986 6.2.2.1]), /// * percent-encoding normalization ([RFC 3986 6.2.2.2]), and /// * path segment normalization ([RFC 3986 6.2.2.2]). /// /// However, if both `scheme` and `authority` is absent and the path is relative /// (including empty), i.e. the IRI reference to be built starts with the /// relative `path` component, path segment normalization will be omitted. /// This is because the path segment normalization depends on presence or /// absense of the `authority` components, and will remove extra `..` /// segments which should not be ignored. /// /// Note that `path` must already be empty or start with a slash **before /// the normalizaiton** if `authority` is present. /// /// # WHATWG URL Standard /// /// If you need to avoid WHATWG URL Standard serialization, use /// [`Built::ensure_rfc3986_normalizable`] method to test if the result is /// normalizable without WHATWG spec. /// /// # Examples /// /// ``` /// # use iri_string::validate::Error; /// use iri_string::build::Builder; /// use iri_string::types::IriReferenceStr; /// /// let mut builder = Builder::new(); /// builder.scheme("http"); /// // `%75%73%65%72` is "user". /// builder.userinfo("%75%73%65%72"); /// builder.host("EXAMPLE.COM"); /// builder.port(""); /// builder.path("/foo/../%2e%2e/bar/%2e/baz/."); /// /// builder.normalize(); /// /// let iri = builder.build::()?; /// # #[cfg(feature = "alloc")] { /// assert_eq!(iri.to_string(), "http://user@example.com/bar/baz/"); /// # } /// # Ok::<_, Error>(()) /// ``` #[inline] pub fn normalize(&mut self) { self.normalize = true; } } /// [`Display`]-able IRI build result. /// /// The value of this type can generate an IRI using [`From`]/[`Into`] traits or /// [`Display`] trait. /// /// # Security consideration /// /// This can be stringified or directly printed by `std::fmt::Display`, but note /// that this `Display` **does not hide the password part**. Be careful **not to /// print the value using `Display for Built<_>` in public context**. /// /// [`From`]: `core::convert::From` /// [`Into`]: `core::convert::Into` /// [`Display`]: `core::fmt::Display` #[derive(Debug)] pub struct Built<'a, T: ?Sized> { /// Builder with the validated content. builder: Builder<'a>, /// Whether the path is absolute. path_is_absolute: bool, /// String type. _ty_str: PhantomData T>, } impl Clone for Built<'_, T> { #[inline] fn clone(&self) -> Self { Self { builder: self.builder.clone(), path_is_absolute: self.path_is_absolute, _ty_str: PhantomData, } } } /// Implements conversions to a string. macro_rules! impl_stringifiers { ($borrowed:ident, $owned:ident) => { impl Built<'_, $borrowed> { /// Returns Ok`(())` if the IRI is normalizable by the RFC 3986 algorithm. #[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 fmt::Display for Built<'_, $borrowed> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.builder.fmt_write_to::(f, self.path_is_absolute) } } #[cfg(feature = "alloc")] impl ToDedicatedString for Built<'_, $borrowed> { type Target = $owned; #[inline] fn try_to_dedicated_string(&self) -> Result { let s = self.try_to_string()?; Ok(TryFrom::try_from(s) .expect("[validity] the IRI to be built is already validated")) } } #[cfg(feature = "alloc")] impl From>> for $owned { #[inline] fn from(builder: Built<'_, $borrowed>) -> Self { (&builder).into() } } #[cfg(feature = "alloc")] impl From<&Built<'_, $borrowed>> for $owned { #[inline] fn from(builder: &Built<'_, $borrowed>) -> Self { let s = builder.to_string(); Self::try_from(s).expect("[validity] the IRI to be built is already validated") } } }; } impl_stringifiers!(RiReferenceStr, RiReferenceString); impl_stringifiers!(RiStr, RiString); impl_stringifiers!(RiAbsoluteStr, RiAbsoluteString); impl_stringifiers!(RiRelativeStr, RiRelativeString); /// A trait for borrowed IRI string types buildable by the [`Builder`]. pub trait Buildable<'a>: private::Sealed<'a> {} impl<'a, S: Spec> private::Sealed<'a> for RiReferenceStr { fn validate_builder(builder: Builder<'a>) -> Result, Error> { let path_is_absolute = validate_builder_for_iri_reference::(&builder)?; Ok(Built { builder, path_is_absolute, _ty_str: PhantomData, }) } } impl Buildable<'_> for RiReferenceStr {} impl<'a, S: Spec> private::Sealed<'a> for RiStr { fn validate_builder(builder: Builder<'a>) -> Result, Error> { if builder.scheme.is_none() { return Err(Error::new()); } let path_is_absolute = validate_builder_for_iri_reference::(&builder)?; Ok(Built { builder, path_is_absolute, _ty_str: PhantomData, }) } } impl Buildable<'_> for RiStr {} impl<'a, S: Spec> private::Sealed<'a> for RiAbsoluteStr { fn validate_builder(builder: Builder<'a>) -> Result, Error> { if builder.scheme.is_none() { return Err(Error::new()); } if builder.fragment.is_some() { return Err(Error::new()); } let path_is_absolute = validate_builder_for_iri_reference::(&builder)?; Ok(Built { builder, path_is_absolute, _ty_str: PhantomData, }) } } impl Buildable<'_> for RiAbsoluteStr {} impl<'a, S: Spec> private::Sealed<'a> for RiRelativeStr { fn validate_builder(builder: Builder<'a>) -> Result, Error> { if builder.scheme.is_some() { return Err(Error::new()); } let path_is_absolute = validate_builder_for_iri_reference::(&builder)?; Ok(Built { builder, path_is_absolute, _ty_str: PhantomData, }) } } impl Buildable<'_> for RiRelativeStr {} /// Checks whether the builder output is valid IRI reference. /// /// Returns whether the path is absolute. fn validate_builder_for_iri_reference(builder: &Builder<'_>) -> Result { 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::(userinfo)?; } UserinfoRepr::UserPass(user, password) => { // `user` is not allowed to have a colon, since the characters // after the colon is parsed as the password. if user.contains(':') { return Err(Error::new()); } // Note that the syntax of components inside `authority` // (`user` and `password`) is not specified by RFC 3986. parser::validate_userinfo::(user)?; if let Some(password) = password { parser::validate_userinfo::(password)?; } } } match authority.host { HostRepr::String(s) => parser::validate_host::(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::new()); } } } 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() { // Note that the path should already be in an absolute form before normalization. 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::( &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; // If the path is relative (where neither scheme nor authority is // available), the first segment should not contain a colon. 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() { // The path should be absolute or empty. path_is_absolute || builder.path.is_empty() } else if builder.scheme.is_some() || path_is_absolute { // The path should not start with '//'. !builder.path.starts_with("//") } else { // If the path is relative (where neither scheme nor authority is // available), the first segment should not contain a colon. prior_byte2(builder.path.as_bytes(), b'/', b':') != Some(b':') }; } if !is_path_acceptable { return Err(Error::new()); } if let Some(query) = builder.query { parser::validate_query::(query)?; } if let Some(fragment) = builder.fragment { parser::validate_fragment::(fragment)?; } Ok(path_is_absolute) } /// Private module to put the trait to seal. mod private { use super::{Builder, Built, Error}; /// A trait for types buildable by the [`Builder`]. pub trait Sealed<'a> { /// Validates the content of the builder and returns the validated type if possible. fn validate_builder(builder: Builder<'a>) -> Result, Error>; } }