1mod error;
78mod path;
79mod pct_case;
80
81use core::fmt::{self, Display as _, Write as _};
82use core::marker::PhantomData;
83
84#[cfg(feature = "alloc")]
85use alloc::collections::TryReserveError;
86
87use crate::components::{RiReferenceComponents, Splitter};
88#[cfg(feature = "alloc")]
89use crate::format::{ToDedicatedString, ToStringFallible};
90use crate::parser::str::rfind_split_hole;
91use crate::parser::trusted::is_ascii_only_host;
92use crate::spec::Spec;
93use crate::types::{RiAbsoluteStr, RiReferenceStr, RiStr};
94#[cfg(feature = "alloc")]
95use crate::types::{RiAbsoluteString, RiString};
96
97pub use self::error::Error;
98pub(crate) use self::path::{Path, PathCharacteristic, PathToNormalize};
99pub(crate) use self::pct_case::{
100    is_pct_case_normalized, NormalizedAsciiOnlyHost, PctCaseNormalized,
101};
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub(crate) enum NormalizationMode {
106    None,
108    Default,
113    PreserveAuthoritylessRelativePath,
118}
119
120impl NormalizationMode {
121    #[inline]
127    #[must_use]
128    fn case_pct_normalization(self) -> bool {
129        match self {
130            Self::None => false,
131            Self::Default | Self::PreserveAuthoritylessRelativePath => true,
132        }
133    }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub(crate) enum NormalizednessCheckMode {
139    Default,
141    Rfc3986,
143    PreserveAuthoritylessRelativePath,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub(crate) struct NormalizationOp {
151    pub(crate) mode: NormalizationMode,
153}
154
155#[derive(Debug, Clone, Copy)]
157pub(crate) struct NormalizationInput<'a> {
158    scheme: &'a str,
160    authority: Option<&'a str>,
162    path: Path<'a>,
164    query: Option<&'a str>,
166    fragment: Option<&'a str>,
168    op: NormalizationOp,
170}
171
172impl<'a> NormalizationInput<'a> {
173    #[inline]
175    #[must_use]
176    pub(crate) fn with_resolution_params<S: Spec>(
177        base_components: &RiReferenceComponents<'a, S>,
178        reference: &'a RiReferenceStr<S>,
179    ) -> Self {
180        let r = RiReferenceComponents::from(reference);
181
182        Self::create_normalization_input(
183            r.iri.as_str(),
184            &r.splitter,
185            base_components.iri.as_str(),
186            &base_components.splitter,
187        )
188    }
189
190    #[must_use]
192    fn create_normalization_input(
193        r_iri: &'a str,
194        r: &Splitter,
195        b_iri: &'a str,
196        b: &Splitter,
197    ) -> Self {
198        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
200        enum RefToplevel {
201            Scheme,
203            Authority,
205            Path,
207            Query,
209            None,
211        }
212
213        impl RefToplevel {
214            #[inline]
217            #[must_use]
218            fn choose_then<T, F, G>(self, component: RefToplevel, reference: F, base: G) -> T
219            where
220                F: FnOnce() -> T,
221                G: FnOnce() -> T,
222            {
223                if self <= component {
224                    reference()
225                } else {
226                    base()
227                }
228            }
229        }
230
231        let ref_toplevel = if r.has_scheme() {
232            RefToplevel::Scheme
233        } else if r.has_authority() {
234            RefToplevel::Authority
235        } else if !r.is_path_empty(r_iri.len()) {
236            RefToplevel::Path
237        } else if r.has_query() {
238            RefToplevel::Query
239        } else {
240            RefToplevel::None
241        };
242
243        let path = match ref_toplevel {
244            RefToplevel::Scheme | RefToplevel::Authority => {
245                Path::NeedsProcessing(PathToNormalize::from_single_path(r.path_str(r_iri)))
246            }
247            RefToplevel::Path => {
248                let r_path = r.path_str(r_iri);
249                if r_path.starts_with('/') {
250                    Path::NeedsProcessing(PathToNormalize::from_single_path(r_path))
251                } else {
252                    let b_path = b.path_str(b_iri);
259                    let b_path = if b.has_authority() && b_path.is_empty() {
260                        "/"
261                    } else {
262                        b_path
263                    };
264                    Path::NeedsProcessing(PathToNormalize::from_paths_to_be_resolved(
265                        b_path, r_path,
266                    ))
267                }
268            }
269            RefToplevel::Query | RefToplevel::None => Path::Done(b.path_str(b_iri)),
270        };
271
272        Self {
273            scheme: r.scheme_str(r_iri).unwrap_or_else(|| {
274                b.scheme_str(b_iri)
275                    .expect("[validity] non-relative IRI must have a scheme")
276            }),
277            authority: ref_toplevel.choose_then(
278                RefToplevel::Authority,
279                || r.authority_str(r_iri),
280                || b.authority_str(b_iri),
281            ),
282            path,
283            query: ref_toplevel.choose_then(
284                RefToplevel::Query,
285                || r.query_str(r_iri),
286                || b.query_str(b_iri),
287            ),
288            fragment: r.fragment_str(r_iri),
289            op: NormalizationOp {
290                mode: NormalizationMode::None,
291            },
292        }
293    }
294}
295
296impl<'a, S: Spec> From<&'a RiStr<S>> for NormalizationInput<'a> {
297    fn from(iri: &'a RiStr<S>) -> Self {
298        let components = RiReferenceComponents::<S>::from(iri.as_ref());
299        let (scheme, authority, path, query, fragment) = components.to_major();
300        let scheme = scheme.expect("[validity] `absolute IRI must have `scheme`");
301        let path = Path::NeedsProcessing(PathToNormalize::from_single_path(path));
302
303        NormalizationInput {
304            scheme,
305            authority,
306            path,
307            query,
308            fragment,
309            op: NormalizationOp {
310                mode: NormalizationMode::None,
311            },
312        }
313    }
314}
315
316#[cfg(feature = "alloc")]
317impl<'a, S: Spec> From<&'a RiString<S>> for NormalizationInput<'a> {
318    #[inline]
319    fn from(iri: &'a RiString<S>) -> Self {
320        Self::from(iri.as_slice())
321    }
322}
323
324impl<'a, S: Spec> From<&'a RiAbsoluteStr<S>> for NormalizationInput<'a> {
325    fn from(iri: &'a RiAbsoluteStr<S>) -> Self {
326        let components = RiReferenceComponents::<S>::from(iri.as_ref());
327        let (scheme, authority, path, query, fragment) = components.to_major();
328        let scheme = scheme.expect("[validity] `absolute IRI must have `scheme`");
329        let path = Path::NeedsProcessing(PathToNormalize::from_single_path(path));
330
331        NormalizationInput {
332            scheme,
333            authority,
334            path,
335            query,
336            fragment,
337            op: NormalizationOp {
338                mode: NormalizationMode::None,
339            },
340        }
341    }
342}
343
344#[cfg(feature = "alloc")]
345impl<'a, S: Spec> From<&'a RiAbsoluteString<S>> for NormalizationInput<'a> {
346    #[inline]
347    fn from(iri: &'a RiAbsoluteString<S>) -> Self {
348        Self::from(iri.as_slice())
349    }
350}
351
352impl NormalizationInput<'_> {
353    pub(crate) fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
357        if self.authority.is_some() {
358            return Ok(());
359        }
360        match self.path {
361            Path::Done(_) => Ok(()),
362            Path::NeedsProcessing(path) => path.ensure_rfc3986_normalizable_with_authority_absent(),
363        }
364    }
365}
366
367struct NormalizedInner<'a, S> {
376    input: NormalizationInput<'a>,
378    _spec: PhantomData<fn() -> S>,
380}
381
382impl<S: Spec> fmt::Debug for NormalizedInner<'_, S> {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        f.debug_struct("Normalized")
385            .field("input", &self.input)
386            .finish()
387    }
388}
389
390impl<'a, S: Spec> NormalizedInner<'a, S> {
391    #[inline]
393    #[must_use]
394    fn from_input(input: NormalizationInput<'a>) -> Self {
395        Self {
396            input,
397            _spec: PhantomData,
398        }
399    }
400}
401
402impl<S: Spec> fmt::Display for NormalizedInner<'_, S> {
403    #[inline]
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        if self.input.op.mode.case_pct_normalization() {
407            normalize_scheme(f, self.input.scheme)?;
408        } else {
409            f.write_str(self.input.scheme)?;
410        }
411        f.write_str(":")?;
412
413        if let Some(authority) = self.input.authority {
415            f.write_str("//")?;
416            if self.input.op.mode.case_pct_normalization() {
417                normalize_authority::<S>(f, authority)?;
418            } else {
419                f.write_str(authority)?;
421            }
422        }
423
424        match self.input.path {
426            Path::Done(s) => {
427                if self.input.op.mode.case_pct_normalization() {
428                    PathToNormalize::from_single_path(s).fmt_write_normalize::<S, _>(
430                        f,
431                        self.input.op,
432                        self.input.authority.is_some(),
433                    )?
434                } else {
435                    f.write_str(s)?
437                }
438            }
439            Path::NeedsProcessing(path) => {
440                path.fmt_write_normalize::<S, _>(f, self.input.op, self.input.authority.is_some())?
441            }
442        }
443
444        if let Some(query) = self.input.query {
446            f.write_char('?')?;
447            if self.input.op.mode.case_pct_normalization() {
448                normalize_query::<S>(f, query)?;
449            } else {
450                f.write_str(query)?;
451            }
452        }
453
454        if let Some(fragment) = self.input.fragment {
456            f.write_char('#')?;
457            if self.input.op.mode.case_pct_normalization() {
458                normalize_fragment::<S>(f, fragment)?;
459            } else {
460                f.write_str(fragment)?;
461            }
462        }
463
464        Ok(())
465    }
466}
467
468pub(crate) fn normalize_scheme(f: &mut fmt::Formatter<'_>, scheme: &str) -> fmt::Result {
470    scheme
480        .chars()
481        .map(|c| c.to_ascii_lowercase())
482        .try_for_each(|c| f.write_char(c))
483}
484
485fn normalize_authority<S: Spec>(f: &mut fmt::Formatter<'_>, authority: &str) -> fmt::Result {
487    let host_port = match rfind_split_hole(authority, b'@') {
488        Some((userinfo, host_port)) => {
489            PctCaseNormalized::<S>::new(userinfo).fmt(f)?;
492            f.write_char('@')?;
493            host_port
494        }
495        None => authority,
496    };
497    normalize_host_port::<S>(f, host_port)
498}
499
500pub(crate) fn normalize_host_port<S: Spec>(
502    f: &mut fmt::Formatter<'_>,
503    host_port: &str,
504) -> fmt::Result {
505    let host_port = host_port.strip_suffix(':').unwrap_or(host_port);
515
516    if is_ascii_only_host(host_port) {
520        NormalizedAsciiOnlyHost::new(host_port).fmt(f)
522    } else {
523        PctCaseNormalized::<S>::new(host_port).fmt(f)
524    }
525}
526
527pub(crate) fn normalize_query<S: Spec>(f: &mut fmt::Formatter<'_>, query: &str) -> fmt::Result {
529    PctCaseNormalized::<S>::new(query).fmt(f)
531}
532
533pub(crate) fn normalize_fragment<S: Spec>(
535    f: &mut fmt::Formatter<'_>,
536    fragment: &str,
537) -> fmt::Result {
538    PctCaseNormalized::<S>::new(fragment).fmt(f)
540}
541
542pub struct Normalized<'a, T: ?Sized> {
550    input: NormalizationInput<'a>,
552    _ty_str: PhantomData<fn() -> T>,
554}
555
556impl<T: ?Sized> fmt::Debug for Normalized<'_, T> {
557    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558        f.debug_struct("Normalized")
559            .field("input", &self.input)
560            .finish()
561    }
562}
563
564impl<'a, T: ?Sized> Normalized<'a, T> {
565    #[inline]
567    #[must_use]
568    pub(crate) fn from_input(input: NormalizationInput<'a>) -> Self {
569        Self {
570            input,
571            _ty_str: PhantomData,
572        }
573    }
574
575    #[inline]
580    pub fn enable_normalization(&mut self) {
581        self.input.op.mode = NormalizationMode::Default;
582    }
583
584    #[inline]
592    pub fn enable_normalization_preserving_authorityless_relative_path(&mut self) {
593        self.input.op.mode = NormalizationMode::PreserveAuthoritylessRelativePath;
594    }
595
596    #[inline]
598    #[must_use]
599    pub fn and_normalize(mut self) -> Self {
600        self.enable_normalization();
601        self
602    }
603
604    #[inline]
612    #[must_use]
613    pub fn and_normalize_but_preserve_authorityless_relative_path(mut self) -> Self {
614        self.enable_normalization_preserving_authorityless_relative_path();
615        self
616    }
617
618    #[inline]
622    pub fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
623        self.input.ensure_rfc3986_normalizable()
624    }
625}
626
627impl<S: Spec> fmt::Display for Normalized<'_, RiStr<S>> {
628    #[inline]
629    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630        NormalizedInner::<S>::from_input(self.input).fmt(f)
631    }
632}
633
634impl<S: Spec> fmt::Display for Normalized<'_, RiAbsoluteStr<S>> {
635    #[inline]
636    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637        NormalizedInner::<S>::from_input(self.input).fmt(f)
638    }
639}
640
641#[cfg(feature = "alloc")]
642impl<S: Spec> ToDedicatedString for Normalized<'_, RiStr<S>> {
643    type Target = RiString<S>;
644
645    fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
646        let s = self.try_to_string()?;
647        Ok(TryFrom::try_from(s).expect("[validity] the normalization result must be a valid IRI"))
648    }
649}
650
651#[cfg(feature = "alloc")]
652impl<S: Spec> From<Normalized<'_, RiStr<S>>> for RiString<S> {
653    #[inline]
654    fn from(v: Normalized<'_, RiStr<S>>) -> Self {
655        v.to_dedicated_string()
656    }
657}
658
659#[cfg(feature = "alloc")]
660impl<S: Spec> From<&Normalized<'_, RiStr<S>>> for RiString<S> {
661    #[inline]
662    fn from(v: &Normalized<'_, RiStr<S>>) -> Self {
663        v.to_dedicated_string()
664    }
665}
666
667#[cfg(feature = "alloc")]
668impl<S: Spec> ToDedicatedString for Normalized<'_, RiAbsoluteStr<S>> {
669    type Target = RiAbsoluteString<S>;
670
671    fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
672        let s = self.try_to_string()?;
673        Ok(TryFrom::try_from(s).expect("[validity] the normalization result must be a valid IRI"))
674    }
675}
676
677#[cfg(feature = "alloc")]
678impl<S: Spec> From<Normalized<'_, RiAbsoluteStr<S>>> for RiAbsoluteString<S> {
679    #[inline]
680    fn from(v: Normalized<'_, RiAbsoluteStr<S>>) -> Self {
681        v.to_dedicated_string()
682    }
683}
684
685#[cfg(feature = "alloc")]
686impl<S: Spec> From<&Normalized<'_, RiAbsoluteStr<S>>> for RiAbsoluteString<S> {
687    #[inline]
688    fn from(v: &Normalized<'_, RiAbsoluteStr<S>>) -> Self {
689        v.to_dedicated_string()
690    }
691}