mod error;
mod path;
mod pct_case;
use core::fmt::{self, Display as _, Write as _};
use core::marker::PhantomData;
#[cfg(feature = "alloc")]
use alloc::collections::TryReserveError;
use crate::components::RiReferenceComponents;
#[cfg(feature = "alloc")]
use crate::format::{ToDedicatedString, ToStringFallible};
use crate::parser::str::rfind_split_hole;
use crate::parser::trusted::is_ascii_only_host;
use crate::spec::Spec;
use crate::types::{RiAbsoluteStr, RiReferenceStr, RiStr};
#[cfg(feature = "alloc")]
use crate::types::{RiAbsoluteString, RiString};
pub use self::error::Error;
pub(crate) use self::path::{Path, PathCharacteristic, PathToNormalize};
pub(crate) use self::pct_case::{
is_pct_case_normalized, NormalizedAsciiOnlyHost, PctCaseNormalized,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct NormalizationOp {
pub(crate) case_pct_normalization: bool,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct NormalizationInput<'a> {
scheme: &'a str,
authority: Option<&'a str>,
path: Path<'a>,
query: Option<&'a str>,
fragment: Option<&'a str>,
op: NormalizationOp,
}
impl<'a> NormalizationInput<'a> {
#[must_use]
pub(crate) fn with_resolution_params<S: Spec>(
base_components: &RiReferenceComponents<'a, S>,
reference: &'a RiReferenceStr<S>,
) -> Self {
let b = base_components;
let r = RiReferenceComponents::from(reference);
let (r_scheme, r_authority, r_path, r_query, r_fragment) = r.to_major();
let (b_scheme, b_authority, b_path, b_query, _) = b.to_major();
let b_scheme = b_scheme.expect("[validity] non-relative IRI must have a scheme");
Self::create_normalization_input(
r_scheme,
r_authority,
r_path,
r_query,
r_fragment,
b_scheme,
b_authority,
b_path,
b_query,
)
}
#[must_use]
#[allow(clippy::too_many_arguments)]
fn create_normalization_input(
r_scheme: Option<&'a str>,
r_authority: Option<&'a str>,
r_path: &'a str,
r_query: Option<&'a str>,
r_fragment: Option<&'a str>,
b_scheme: &'a str,
b_authority: Option<&'a str>,
b_path: &'a str,
b_query: Option<&'a str>,
) -> Self {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum RefToplevel {
Scheme,
Authority,
Path,
Query,
None,
}
impl RefToplevel {
fn choose<T>(self, component: RefToplevel, reference: T, base: T) -> T {
if self <= component {
reference
} else {
base
}
}
}
let ref_toplevel = if r_scheme.is_some() {
RefToplevel::Scheme
} else if r_authority.is_some() {
RefToplevel::Authority
} else if !r_path.is_empty() {
RefToplevel::Path
} else if r_query.is_some() {
RefToplevel::Query
} else {
RefToplevel::None
};
let path = match ref_toplevel {
RefToplevel::Scheme | RefToplevel::Authority => {
Path::NeedsProcessing(PathToNormalize::from_single_path(r_path))
}
RefToplevel::Path => {
if r_path.starts_with('/') {
Path::NeedsProcessing(PathToNormalize::from_single_path(r_path))
} else {
let b_path = if b_authority.is_some() && b_path.is_empty() {
"/"
} else {
b_path
};
Path::NeedsProcessing(PathToNormalize::from_paths_to_be_resolved(
b_path, r_path,
))
}
}
RefToplevel::Query | RefToplevel::None => Path::Done(b_path),
};
Self {
scheme: r_scheme.unwrap_or(b_scheme),
authority: ref_toplevel.choose(RefToplevel::Authority, r_authority, b_authority),
path,
query: ref_toplevel.choose(RefToplevel::Query, r_query, b_query),
fragment: r_fragment,
op: NormalizationOp {
case_pct_normalization: false,
},
}
}
}
impl<'a, S: Spec> From<&'a RiStr<S>> for NormalizationInput<'a> {
fn from(iri: &'a RiStr<S>) -> Self {
let components = RiReferenceComponents::<S>::from(iri.as_ref());
let (scheme, authority, path, query, fragment) = components.to_major();
let scheme = scheme.expect("[validity] `absolute IRI must have `scheme`");
let path = Path::NeedsProcessing(PathToNormalize::from_single_path(path));
NormalizationInput {
scheme,
authority,
path,
query,
fragment,
op: NormalizationOp {
case_pct_normalization: false,
},
}
}
}
#[cfg(feature = "alloc")]
impl<'a, S: Spec> From<&'a RiString<S>> for NormalizationInput<'a> {
#[inline]
fn from(iri: &'a RiString<S>) -> Self {
Self::from(iri.as_slice())
}
}
impl<'a, S: Spec> From<&'a RiAbsoluteStr<S>> for NormalizationInput<'a> {
fn from(iri: &'a RiAbsoluteStr<S>) -> Self {
let components = RiReferenceComponents::<S>::from(iri.as_ref());
let (scheme, authority, path, query, fragment) = components.to_major();
let scheme = scheme.expect("[validity] `absolute IRI must have `scheme`");
let path = Path::NeedsProcessing(PathToNormalize::from_single_path(path));
NormalizationInput {
scheme,
authority,
path,
query,
fragment,
op: NormalizationOp {
case_pct_normalization: false,
},
}
}
}
#[cfg(feature = "alloc")]
impl<'a, S: Spec> From<&'a RiAbsoluteString<S>> for NormalizationInput<'a> {
#[inline]
fn from(iri: &'a RiAbsoluteString<S>) -> Self {
Self::from(iri.as_slice())
}
}
impl NormalizationInput<'_> {
pub(crate) fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
if self.authority.is_some() {
return Ok(());
}
match self.path {
Path::Done(_) => Ok(()),
Path::NeedsProcessing(path) => path.ensure_rfc3986_normalizable_with_authority_absent(),
}
}
}
struct NormalizedInner<'a, S> {
input: NormalizationInput<'a>,
_spec: PhantomData<fn() -> S>,
}
impl<S: Spec> fmt::Debug for NormalizedInner<'_, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Normalized")
.field("input", &self.input)
.finish()
}
}
impl<'a, S: Spec> NormalizedInner<'a, S> {
#[inline]
#[must_use]
fn from_input(input: NormalizationInput<'a>) -> Self {
Self {
input,
_spec: PhantomData,
}
}
}
impl<S: Spec> fmt::Display for NormalizedInner<'_, S> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.input.op.case_pct_normalization {
normalize_scheme(f, self.input.scheme)?;
} else {
f.write_str(self.input.scheme)?;
}
f.write_str(":")?;
if let Some(authority) = self.input.authority {
f.write_str("//")?;
if self.input.op.case_pct_normalization {
normalize_authority::<S>(f, authority)?;
} else {
f.write_str(authority)?;
}
}
match self.input.path {
Path::Done(s) => {
if self.input.op.case_pct_normalization {
PathToNormalize::from_single_path(s).fmt_write_normalize::<S, _>(
f,
self.input.op,
self.input.authority.is_some(),
)?
} else {
f.write_str(s)?
}
}
Path::NeedsProcessing(path) => {
path.fmt_write_normalize::<S, _>(f, self.input.op, self.input.authority.is_some())?
}
}
if let Some(query) = self.input.query {
f.write_char('?')?;
if self.input.op.case_pct_normalization {
normalize_query::<S>(f, query)?;
} else {
f.write_str(query)?;
}
}
if let Some(fragment) = self.input.fragment {
f.write_char('#')?;
if self.input.op.case_pct_normalization {
normalize_fragment::<S>(f, fragment)?;
} else {
f.write_str(fragment)?;
}
}
Ok(())
}
}
pub(crate) fn normalize_scheme(f: &mut fmt::Formatter<'_>, scheme: &str) -> fmt::Result {
scheme
.chars()
.map(|c| c.to_ascii_lowercase())
.try_for_each(|c| f.write_char(c))
}
fn normalize_authority<S: Spec>(f: &mut fmt::Formatter<'_>, authority: &str) -> fmt::Result {
let host_port = match rfind_split_hole(authority, b'@') {
Some((userinfo, host_port)) => {
PctCaseNormalized::<S>::new(userinfo).fmt(f)?;
f.write_char('@')?;
host_port
}
None => authority,
};
normalize_host_port::<S>(f, host_port)
}
pub(crate) fn normalize_host_port<S: Spec>(
f: &mut fmt::Formatter<'_>,
host_port: &str,
) -> fmt::Result {
let host_port = host_port.strip_suffix(':').unwrap_or(host_port);
if is_ascii_only_host(host_port) {
NormalizedAsciiOnlyHost::new(host_port).fmt(f)
} else {
PctCaseNormalized::<S>::new(host_port).fmt(f)
}
}
pub(crate) fn normalize_query<S: Spec>(f: &mut fmt::Formatter<'_>, query: &str) -> fmt::Result {
PctCaseNormalized::<S>::new(query).fmt(f)
}
pub(crate) fn normalize_fragment<S: Spec>(
f: &mut fmt::Formatter<'_>,
fragment: &str,
) -> fmt::Result {
PctCaseNormalized::<S>::new(fragment).fmt(f)
}
pub struct Normalized<'a, T: ?Sized> {
input: NormalizationInput<'a>,
_ty_str: PhantomData<fn() -> T>,
}
impl<T: ?Sized> fmt::Debug for Normalized<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Normalized")
.field("input", &self.input)
.finish()
}
}
impl<'a, T: ?Sized> Normalized<'a, T> {
#[inline]
#[must_use]
pub(crate) fn from_input(input: NormalizationInput<'a>) -> Self {
Self {
input,
_ty_str: PhantomData,
}
}
#[inline]
pub fn enable_normalization(&mut self) {
self.input.op.case_pct_normalization = true;
}
#[inline]
#[must_use]
pub fn and_normalize(mut self) -> Self {
self.enable_normalization();
self
}
#[inline]
pub fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
self.input.ensure_rfc3986_normalizable()
}
}
impl<S: Spec> fmt::Display for Normalized<'_, RiStr<S>> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
NormalizedInner::<S>::from_input(self.input).fmt(f)
}
}
impl<S: Spec> fmt::Display for Normalized<'_, RiAbsoluteStr<S>> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
NormalizedInner::<S>::from_input(self.input).fmt(f)
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> ToDedicatedString for Normalized<'_, RiStr<S>> {
type Target = RiString<S>;
fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
let s = self.try_to_string()?;
Ok(TryFrom::try_from(s).expect("[validity] the normalization result must be a valid IRI"))
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> From<Normalized<'_, RiStr<S>>> for RiString<S> {
#[inline]
fn from(v: Normalized<'_, RiStr<S>>) -> Self {
v.to_dedicated_string()
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> From<&Normalized<'_, RiStr<S>>> for RiString<S> {
#[inline]
fn from(v: &Normalized<'_, RiStr<S>>) -> Self {
v.to_dedicated_string()
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> ToDedicatedString for Normalized<'_, RiAbsoluteStr<S>> {
type Target = RiAbsoluteString<S>;
fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
let s = self.try_to_string()?;
Ok(TryFrom::try_from(s).expect("[validity] the normalization result must be a valid IRI"))
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> From<Normalized<'_, RiAbsoluteStr<S>>> for RiAbsoluteString<S> {
#[inline]
fn from(v: Normalized<'_, RiAbsoluteStr<S>>) -> Self {
v.to_dedicated_string()
}
}
#[cfg(feature = "alloc")]
impl<S: Spec> From<&Normalized<'_, RiAbsoluteStr<S>>> for RiAbsoluteString<S> {
#[inline]
fn from(v: &Normalized<'_, RiAbsoluteStr<S>>) -> Self {
v.to_dedicated_string()
}
}