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, Splitter};
#[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) enum NormalizationMode {
None,
Default,
PreserveAuthoritylessRelativePath,
}
impl NormalizationMode {
#[inline]
#[must_use]
fn case_pct_normalization(self) -> bool {
match self {
Self::None => false,
Self::Default | Self::PreserveAuthoritylessRelativePath => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NormalizednessCheckMode {
Default,
Rfc3986,
PreserveAuthoritylessRelativePath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct NormalizationOp {
pub(crate) mode: NormalizationMode,
}
#[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> {
#[inline]
#[must_use]
pub(crate) fn with_resolution_params<S: Spec>(
base_components: &RiReferenceComponents<'a, S>,
reference: &'a RiReferenceStr<S>,
) -> Self {
let r = RiReferenceComponents::from(reference);
Self::create_normalization_input(
r.iri.as_str(),
&r.splitter,
base_components.iri.as_str(),
&base_components.splitter,
)
}
#[must_use]
fn create_normalization_input(
r_iri: &'a str,
r: &Splitter,
b_iri: &'a str,
b: &Splitter,
) -> Self {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum RefToplevel {
Scheme,
Authority,
Path,
Query,
None,
}
impl RefToplevel {
#[inline]
#[must_use]
fn choose_then<T, F, G>(self, component: RefToplevel, reference: F, base: G) -> T
where
F: FnOnce() -> T,
G: FnOnce() -> T,
{
if self <= component {
reference()
} else {
base()
}
}
}
let ref_toplevel = if r.has_scheme() {
RefToplevel::Scheme
} else if r.has_authority() {
RefToplevel::Authority
} else if !r.is_path_empty(r_iri.len()) {
RefToplevel::Path
} else if r.has_query() {
RefToplevel::Query
} else {
RefToplevel::None
};
let path = match ref_toplevel {
RefToplevel::Scheme | RefToplevel::Authority => {
Path::NeedsProcessing(PathToNormalize::from_single_path(r.path_str(r_iri)))
}
RefToplevel::Path => {
let r_path = r.path_str(r_iri);
if r_path.starts_with('/') {
Path::NeedsProcessing(PathToNormalize::from_single_path(r_path))
} else {
let b_path = b.path_str(b_iri);
let b_path = if b.has_authority() && 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_str(b_iri)),
};
Self {
scheme: r.scheme_str(r_iri).unwrap_or_else(|| {
b.scheme_str(b_iri)
.expect("[validity] non-relative IRI must have a scheme")
}),
authority: ref_toplevel.choose_then(
RefToplevel::Authority,
|| r.authority_str(r_iri),
|| b.authority_str(b_iri),
),
path,
query: ref_toplevel.choose_then(
RefToplevel::Query,
|| r.query_str(r_iri),
|| b.query_str(b_iri),
),
fragment: r.fragment_str(r_iri),
op: NormalizationOp {
mode: NormalizationMode::None,
},
}
}
}
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 {
mode: NormalizationMode::None,
},
}
}
}
#[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 {
mode: NormalizationMode::None,
},
}
}
}
#[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.mode.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.mode.case_pct_normalization() {
normalize_authority::<S>(f, authority)?;
} else {
f.write_str(authority)?;
}
}
match self.input.path {
Path::Done(s) => {
if self.input.op.mode.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.mode.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.mode.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.mode = NormalizationMode::Default;
}
#[inline]
pub fn enable_normalization_preserving_authorityless_relative_path(&mut self) {
self.input.op.mode = NormalizationMode::PreserveAuthoritylessRelativePath;
}
#[inline]
#[must_use]
pub fn and_normalize(mut self) -> Self {
self.enable_normalization();
self
}
#[inline]
#[must_use]
pub fn and_normalize_but_preserve_authorityless_relative_path(mut self) -> Self {
self.enable_normalization_preserving_authorityless_relative_path();
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()
}
}