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}