Skip to main content

iri_string/types/generic/
normal.rs

1//! Usual absolute IRI (fragment part being allowed).
2
3#[cfg(feature = "alloc")]
4use alloc::collections::TryReserveError;
5#[cfg(all(feature = "alloc", not(feature = "std")))]
6use alloc::string::String;
7
8use crate::components::AuthorityComponents;
9#[cfg(feature = "alloc")]
10use crate::mask_password::password_range_to_hide;
11use crate::mask_password::PasswordMasked;
12use crate::normalize::{Error, NormalizationInput, Normalized, NormalizednessCheckMode};
13use crate::parser::trusted as trusted_parser;
14#[cfg(feature = "alloc")]
15use crate::raw;
16use crate::spec::Spec;
17use crate::types::{RiAbsoluteStr, RiFragmentStr, RiQueryStr, RiReferenceStr};
18#[cfg(feature = "alloc")]
19use crate::types::{RiAbsoluteString, RiFragmentString, RiReferenceString};
20use crate::validate::iri;
21
22define_custom_string_slice! {
23    /// A borrowed string of an absolute IRI possibly with fragment part.
24    ///
25    /// This corresponds to [`IRI` rule] in [RFC 3987] (and [`URI` rule] in [RFC 3986]).
26    /// The rule for `IRI` is `scheme ":" ihier-part [ "?" iquery ] [ "#" ifragment ]`.
27    /// In other words, this is [`RiAbsoluteStr`] with fragment part allowed.
28    ///
29    /// # Valid values
30    ///
31    /// This type can have an IRI (which is absolute, and may have fragment part).
32    ///
33    /// ```
34    /// # use iri_string::types::IriStr;
35    /// assert!(IriStr::new("https://user:pass@example.com:8080").is_ok());
36    /// assert!(IriStr::new("https://example.com/").is_ok());
37    /// assert!(IriStr::new("https://example.com/foo?bar=baz").is_ok());
38    /// assert!(IriStr::new("https://example.com/foo?bar=baz#qux").is_ok());
39    /// assert!(IriStr::new("foo:bar").is_ok());
40    /// assert!(IriStr::new("foo:").is_ok());
41    /// // `foo://.../` below are all allowed. See the crate documentation for detail.
42    /// assert!(IriStr::new("foo:/").is_ok());
43    /// assert!(IriStr::new("foo://").is_ok());
44    /// assert!(IriStr::new("foo:///").is_ok());
45    /// assert!(IriStr::new("foo:////").is_ok());
46    /// assert!(IriStr::new("foo://///").is_ok());
47    /// ```
48    ///
49    /// Relative IRI reference is not allowed.
50    ///
51    /// ```
52    /// # use iri_string::types::IriStr;
53    /// // This is relative path.
54    /// assert!(IriStr::new("foo/bar").is_err());
55    /// // `/foo/bar` is an absolute path, but it is authority-relative.
56    /// assert!(IriStr::new("/foo/bar").is_err());
57    /// // `//foo/bar` is termed "network-path reference",
58    /// // or usually called "protocol-relative reference".
59    /// assert!(IriStr::new("//foo/bar").is_err());
60    /// // Same-document reference is relative.
61    /// assert!(IriStr::new("#foo").is_err());
62    /// // Empty string is not a valid absolute IRI.
63    /// assert!(IriStr::new("").is_err());
64    /// ```
65    ///
66    /// Some characters and sequences cannot used in an IRI.
67    ///
68    /// ```
69    /// # use iri_string::types::IriStr;
70    /// // `<` and `>` cannot directly appear in an IRI.
71    /// assert!(IriStr::new("<not allowed>").is_err());
72    /// // Broken percent encoding cannot appear in an IRI.
73    /// assert!(IriStr::new("%").is_err());
74    /// assert!(IriStr::new("%GG").is_err());
75    /// ```
76    ///
77    /// [RFC 3986]: https://www.rfc-editor.org/rfc/rfc3986.html
78    /// [RFC 3987]: https://www.rfc-editor.org/rfc/rfc3987.html
79    /// [`IRI` rule]: https://www.rfc-editor.org/rfc/rfc3987.html#section-2.2
80    /// [`URI` rule]: https://www.rfc-editor.org/rfc/rfc3986.html#section-3
81    /// [`RiAbsoluteStr`]: struct.RiAbsoluteStr.html
82    struct RiStr {
83        validator = iri,
84        expecting_msg = "IRI string",
85    }
86}
87
88#[cfg(feature = "alloc")]
89define_custom_string_owned! {
90    /// An owned string of an absolute IRI possibly with fragment part.
91    ///
92    /// This corresponds to [`IRI` rule] in [RFC 3987] (and [`URI` rule] in [RFC 3986]).
93    /// The rule for `IRI` is `scheme ":" ihier-part [ "?" iquery ] [ "#" ifragment ]`.
94    /// In other words, this is [`RiAbsoluteString`] with fragment part allowed.
95    ///
96    /// For details, see the document for [`RiStr`].
97    ///
98    /// Enabled by `alloc` or `std` feature.
99    ///
100    /// [RFC 3986]: https://www.rfc-editor.org/rfc/rfc3986.html
101    /// [RFC 3987]: https://www.rfc-editor.org/rfc/rfc3987.html
102    /// [`IRI` rule]: https://www.rfc-editor.org/rfc/rfc3987.html#section-2.2
103    /// [`URI` rule]: https://www.rfc-editor.org/rfc/rfc3986.html#section-3
104    /// [`RiAbsoluteString`]: struct.RiAbsoluteString.html
105    struct RiString {
106        validator = iri,
107        slice = RiStr,
108        expecting_msg = "IRI string",
109    }
110}
111
112impl<S: Spec> RiStr<S> {
113    /// Splits the IRI into an absolute IRI part and a fragment part.
114    ///
115    /// A leading `#` character is truncated if the fragment part exists.
116    ///
117    /// # Examples
118    ///
119    /// If the IRI has a fragment part, `Some(_)` is returned.
120    ///
121    /// ```
122    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriStr}, validate::Error};
123    /// let iri = IriStr::new("foo://bar/baz?qux=quux#corge")?;
124    /// let (absolute, fragment) = iri.to_absolute_and_fragment();
125    /// let fragment_expected = IriFragmentStr::new("corge")?;
126    /// assert_eq!(absolute, "foo://bar/baz?qux=quux");
127    /// assert_eq!(fragment, Some(fragment_expected));
128    /// # Ok::<_, Error>(())
129    /// ```
130    ///
131    /// When the fragment part exists but is empty string, `Some(_)` is returned.
132    ///
133    /// ```
134    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriStr}, validate::Error};
135    /// let iri = IriStr::new("foo://bar/baz?qux=quux#")?;
136    /// let (absolute, fragment) = iri.to_absolute_and_fragment();
137    /// let fragment_expected = IriFragmentStr::new("")?;
138    /// assert_eq!(absolute, "foo://bar/baz?qux=quux");
139    /// assert_eq!(fragment, Some(fragment_expected));
140    /// # Ok::<_, Error>(())
141    /// ```
142    ///
143    /// If the IRI has no fragment, `None` is returned.
144    ///
145    /// ```
146    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
147    /// let iri = IriStr::new("foo://bar/baz?qux=quux")?;
148    /// let (absolute, fragment) = iri.to_absolute_and_fragment();
149    /// assert_eq!(absolute, "foo://bar/baz?qux=quux");
150    /// assert_eq!(fragment, None);
151    /// # Ok::<_, Error>(())
152    /// ```
153    #[must_use]
154    pub fn to_absolute_and_fragment(&self) -> (&RiAbsoluteStr<S>, Option<&RiFragmentStr<S>>) {
155        let (prefix, fragment) = trusted_parser::split_fragment(self.as_str());
156        // SAFETY: an IRI without fragment part is also an absolute IRI.
157        let prefix = unsafe {
158            RiAbsoluteStr::new_unchecked_justified(
159                prefix,
160                "a non-relative IRI without fragment is an absolute IRI by definition",
161            )
162        };
163        let fragment = fragment.map(|fragment| {
164            // SAFETY: `trusted_parser::split_fragment()` must return a valid fragment component.
165            unsafe {
166                RiFragmentStr::new_unchecked_justified(
167                    fragment,
168                    "fragment in a valid IRI must also be valid",
169                )
170            }
171        });
172
173        (prefix, fragment)
174    }
175
176    /// Strips the fragment part if exists, and returns [`&RiAbsoluteStr`][`RiAbsoluteStr`].
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
182    /// let iri = IriStr::new("foo://bar/baz?qux=quux#corge")?;
183    /// assert_eq!(iri.to_absolute(), "foo://bar/baz?qux=quux");
184    /// # Ok::<_, Error>(())
185    /// ```
186    ///
187    /// ```
188    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
189    /// let iri = IriStr::new("foo://bar/baz?qux=quux")?;
190    /// assert_eq!(iri.to_absolute(), "foo://bar/baz?qux=quux");
191    /// # Ok::<_, Error>(())
192    /// ```
193    ///
194    /// [`RiAbsoluteStr`]: struct.RiAbsoluteStr.html
195    #[must_use]
196    pub fn to_absolute(&self) -> &RiAbsoluteStr<S> {
197        let prefix_len = trusted_parser::split_fragment(self.as_str()).0.len();
198        // SAFETY: IRI without the fragment part (including a leading `#` character)
199        // is also an absolute IRI.
200        unsafe {
201            RiAbsoluteStr::new_unchecked_justified(
202                &self.as_str()[..prefix_len],
203                "a non-relative IRI without fragment is an absolute IRI by definition",
204            )
205        }
206    }
207
208    /// Returns Ok`(())` if the IRI is normalizable by the RFC 3986 algorithm.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// # use iri_string::validate::Error;
214    /// use iri_string::types::IriStr;
215    ///
216    /// let iri = IriStr::new("HTTP://example.COM/foo/%2e/bar/..")?;
217    /// assert!(iri.ensure_rfc3986_normalizable().is_ok());
218    ///
219    /// let iri2 = IriStr::new("scheme:/..//bar")?;
220    /// // The normalization result would be `scheme://bar` according to RFC
221    /// // 3986, but it is unintended and should be treated as a failure.
222    /// // This crate automatically handles this case so that `.normalize()` won't fail.
223    /// assert!(!iri.ensure_rfc3986_normalizable().is_err());
224    /// # Ok::<_, Error>(())
225    /// ```
226    #[inline]
227    pub fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
228        NormalizationInput::from(self).ensure_rfc3986_normalizable()
229    }
230
231    /// Returns `true` if the IRI is already normalized.
232    ///
233    /// This returns the same result as `self.normalize().to_string() == self`,
234    /// but does this more efficiently without heap allocation.
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// # use iri_string::validate::Error;
240    /// # #[cfg(feature = "alloc")] {
241    /// use iri_string::format::ToDedicatedString;
242    /// use iri_string::types::IriStr;
243    ///
244    /// let iri = IriStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query#fragment")?;
245    /// assert!(!iri.is_normalized());
246    ///
247    /// let normalized = iri.normalize().to_dedicated_string();
248    /// assert_eq!(normalized, "http://example.com/baz?query#fragment");
249    /// assert!(normalized.is_normalized());
250    /// # }
251    /// # Ok::<_, Error>(())
252    /// ```
253    ///
254    /// ```
255    /// # use iri_string::validate::Error;
256    /// # #[cfg(feature = "alloc")] {
257    /// use iri_string::format::ToDedicatedString;
258    /// use iri_string::types::IriStr;
259    ///
260    /// let iri = IriStr::new("scheme:/.///foo")?;
261    /// // Already normalized.
262    /// assert!(iri.is_normalized());
263    /// # }
264    /// # Ok::<_, Error>(())
265    /// ```
266    ///
267    /// ```
268    /// # use iri_string::validate::Error;
269    /// # #[cfg(feature = "alloc")] {
270    /// use iri_string::format::ToDedicatedString;
271    /// use iri_string::types::IriStr;
272    ///
273    /// let iri = IriStr::new("scheme:relative/..//not-a-host")?;
274    /// // Default normalization algorithm assumes the path part to be NOT opaque.
275    /// assert!(!iri.is_normalized());
276    ///
277    /// let normalized = iri.normalize().to_dedicated_string();
278    /// assert_eq!(normalized, "scheme:/.//not-a-host");
279    /// # }
280    /// # Ok::<_, Error>(())
281    /// ```
282    #[must_use]
283    #[inline]
284    pub fn is_normalized(&self) -> bool {
285        trusted_parser::is_normalized::<S>(self.as_str(), NormalizednessCheckMode::Default)
286    }
287
288    /// Returns `true` if the IRI is already normalized in the sense of RFC 3986.
289    ///
290    /// This returns the same result as
291    /// `self.ensure_rfc3986_normalizable() && (self.normalize().to_string() == self)`,
292    /// but does this more efficiently without heap allocation.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// # use iri_string::validate::Error;
298    /// # #[cfg(feature = "alloc")] {
299    /// use iri_string::format::ToDedicatedString;
300    /// use iri_string::types::IriStr;
301    ///
302    /// let iri = IriStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query#fragment")?;
303    /// assert!(!iri.is_normalized_rfc3986());
304    ///
305    /// let normalized = iri.normalize().to_dedicated_string();
306    /// assert_eq!(normalized, "http://example.com/baz?query#fragment");
307    /// assert!(normalized.is_normalized_rfc3986());
308    /// # }
309    /// # Ok::<_, Error>(())
310    /// ```
311    ///
312    /// ```
313    /// # use iri_string::validate::Error;
314    /// # #[cfg(feature = "alloc")] {
315    /// use iri_string::format::ToDedicatedString;
316    /// use iri_string::types::IriStr;
317    ///
318    /// let iri = IriStr::new("scheme:/.///foo")?;
319    /// // Not normalized in the sense of RFC 3986.
320    /// assert!(!iri.is_normalized_rfc3986());
321    /// # }
322    /// # Ok::<_, Error>(())
323    /// ```
324    ///
325    /// ```
326    /// # use iri_string::validate::Error;
327    /// # #[cfg(feature = "alloc")] {
328    /// use iri_string::format::ToDedicatedString;
329    /// use iri_string::types::IriStr;
330    ///
331    /// let iri = IriStr::new("scheme:relative/..//not-a-host")?;
332    /// // RFC 3986 normalization algorithm assumes the path part to be NOT opaque.
333    /// assert!(!iri.is_normalized_rfc3986());
334    ///
335    /// let normalized = iri.normalize().to_dedicated_string();
336    /// assert_eq!(normalized, "scheme:/.//not-a-host");
337    /// # }
338    /// # Ok::<_, Error>(())
339    /// ```
340    #[must_use]
341    #[inline]
342    pub fn is_normalized_rfc3986(&self) -> bool {
343        trusted_parser::is_normalized::<S>(self.as_str(), NormalizednessCheckMode::Rfc3986)
344    }
345
346    /// Returns `true` if the IRI is already normalized in the sense of
347    /// [`normalize_but_preserve_authorityless_relative_path`] method.
348    ///
349    /// This returns the same result as
350    /// `self.normalize_but_preserve_authorityless_relative_path().to_string() == self`,
351    /// but does this more efficiently without heap allocation.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// # use iri_string::validate::Error;
357    /// # #[cfg(feature = "alloc")] {
358    /// use iri_string::format::ToDedicatedString;
359    /// use iri_string::types::IriStr;
360    ///
361    /// let iri = IriStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query#fragment")?;
362    /// assert!(!iri.is_normalized_but_authorityless_relative_path_preserved());
363    ///
364    /// let normalized = iri
365    ///     .normalize_but_preserve_authorityless_relative_path()
366    ///     .to_dedicated_string();
367    /// assert_eq!(normalized, "http://example.com/baz?query#fragment");
368    /// assert!(normalized.is_normalized());
369    /// # }
370    /// # Ok::<_, Error>(())
371    /// ```
372    ///
373    /// ```
374    /// # use iri_string::validate::Error;
375    /// # #[cfg(feature = "alloc")] {
376    /// use iri_string::format::ToDedicatedString;
377    /// use iri_string::types::IriStr;
378    ///
379    /// let iri = IriStr::new("scheme:/.///foo")?;
380    /// // Already normalized in the sense of
381    /// // `normalize_but_opaque_authorityless_relative_path()` method.
382    /// assert!(iri.is_normalized_but_authorityless_relative_path_preserved());
383    /// # }
384    /// # Ok::<_, Error>(())
385    /// ```
386    ///
387    /// ```
388    /// # use iri_string::validate::Error;
389    /// # #[cfg(feature = "alloc")] {
390    /// use iri_string::format::ToDedicatedString;
391    /// use iri_string::types::IriStr;
392    ///
393    /// let iri = IriStr::new("scheme:relative/..//not-a-host")?;
394    /// // Relative path is treated as opaque since the autority component is absent.
395    /// assert!(iri.is_normalized_but_authorityless_relative_path_preserved());
396    /// # }
397    /// # Ok::<_, Error>(())
398    /// ```
399    ///
400    /// [`normalize_but_preserve_authorityless_relative_path`]:
401    ///     `Self::normalize_but_preserve_authorityless_relative_path`
402    #[must_use]
403    #[inline]
404    pub fn is_normalized_but_authorityless_relative_path_preserved(&self) -> bool {
405        trusted_parser::is_normalized::<S>(
406            self.as_str(),
407            NormalizednessCheckMode::PreserveAuthoritylessRelativePath,
408        )
409    }
410
411    /// Returns the normalized IRI.
412    ///
413    /// # Notes
414    ///
415    /// For some abnormal IRIs, the normalization can produce semantically
416    /// incorrect string that looks syntactically valid. To avoid security
417    /// issues by this trap, the normalization algorithm by this crate
418    /// automatically applies the workaround.
419    ///
420    /// If you worry about this, test by [`RiStr::ensure_rfc3986_normalizable`]
421    /// method or [`Normalized::ensure_rfc3986_normalizable`] before using the
422    /// result string.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// # use iri_string::validate::Error;
428    /// # #[cfg(feature = "alloc")] {
429    /// use iri_string::format::ToDedicatedString;
430    /// use iri_string::types::IriStr;
431    ///
432    /// let iri = IriStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query#fragment")?;
433    ///
434    /// let normalized = iri.normalize().to_dedicated_string();
435    /// assert_eq!(normalized, "http://example.com/baz?query#fragment");
436    /// # }
437    /// # Ok::<_, Error>(())
438    /// ```
439    #[inline]
440    #[must_use]
441    pub fn normalize(&self) -> Normalized<'_, Self> {
442        Normalized::from_input(NormalizationInput::from(self)).and_normalize()
443    }
444
445    /// Returns the normalized IRI, but preserving dot segments in relative path
446    /// if the authority component is absent.
447    ///
448    /// This normalization would be similar to that of [WHATWG URL Standard]
449    /// while this implementation is not guaranteed to stricly follow the spec.
450    ///
451    /// Note that this normalization algorithm is not compatible with RFC 3986
452    /// algorithm for some inputs.
453    ///
454    /// Note that case normalization and percent-encoding normalization will
455    /// still be applied to any path.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// # use iri_string::validate::Error;
461    /// # #[cfg(feature = "alloc")] {
462    /// use iri_string::format::ToDedicatedString;
463    /// use iri_string::types::IriStr;
464    ///
465    /// let iri = IriStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query#fragment")?;
466    ///
467    /// let normalized = iri
468    ///     .normalize_but_preserve_authorityless_relative_path()
469    ///     .to_dedicated_string();
470    /// assert_eq!(normalized, "http://example.com/baz?query#fragment");
471    /// # }
472    /// # Ok::<_, Error>(())
473    /// ```
474    ///
475    /// ```
476    /// # use iri_string::validate::Error;
477    /// # #[cfg(feature = "alloc")] {
478    /// use iri_string::format::ToDedicatedString;
479    /// use iri_string::types::IriStr;
480    ///
481    /// let iri = IriStr::new("scheme:relative/../f%6f%6f")?;
482    ///
483    /// let normalized = iri
484    ///     .normalize_but_preserve_authorityless_relative_path()
485    ///     .to_dedicated_string();
486    /// assert_eq!(normalized, "scheme:relative/../foo");
487    /// // `.normalize()` would normalize this to `scheme:/foo`.
488    /// # assert_eq!(iri.normalize().to_dedicated_string(), "scheme:/foo");
489    /// # }
490    /// # Ok::<_, Error>(())
491    /// ```
492    ///
493    /// [WHATWG URL Standard]: https://url.spec.whatwg.org/
494    #[inline]
495    #[must_use]
496    pub fn normalize_but_preserve_authorityless_relative_path(&self) -> Normalized<'_, Self> {
497        Normalized::from_input(NormalizationInput::from(self))
498            .and_normalize_but_preserve_authorityless_relative_path()
499    }
500
501    /// Returns the proxy to the IRI with password masking feature.
502    ///
503    /// # Examples
504    ///
505    /// ```
506    /// # use iri_string::validate::Error;
507    /// # #[cfg(feature = "alloc")] {
508    /// use iri_string::format::ToDedicatedString;
509    /// use iri_string::types::IriStr;
510    ///
511    /// let iri = IriStr::new("http://user:password@example.com/path?query")?;
512    /// let masked = iri.mask_password();
513    /// assert_eq!(masked.to_dedicated_string(), "http://user:@example.com/path?query");
514    ///
515    /// assert_eq!(
516    ///     masked.replace_password("${password}").to_string(),
517    ///     "http://user:${password}@example.com/path?query"
518    /// );
519    /// # }
520    /// # Ok::<_, Error>(())
521    /// ```
522    #[inline]
523    #[must_use]
524    pub fn mask_password(&self) -> PasswordMasked<'_, Self> {
525        PasswordMasked::new(self)
526    }
527}
528
529/// Components getters.
530impl<S: Spec> RiStr<S> {
531    /// Returns the scheme.
532    ///
533    /// The following colon is truncated.
534    ///
535    /// # Examples
536    ///
537    /// ```
538    /// # use iri_string::validate::Error;
539    /// use iri_string::types::IriStr;
540    ///
541    /// let iri = IriStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
542    /// assert_eq!(iri.scheme_str(), "http");
543    /// # Ok::<_, Error>(())
544    /// ```
545    #[inline]
546    #[must_use]
547    pub fn scheme_str(&self) -> &str {
548        trusted_parser::extract_scheme_absolute(self.as_str())
549    }
550
551    /// Returns the authority.
552    ///
553    /// The leading `//` is truncated.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// # use iri_string::validate::Error;
559    /// use iri_string::types::IriStr;
560    ///
561    /// let iri = IriStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
562    /// assert_eq!(iri.authority_str(), Some("example.com"));
563    /// # Ok::<_, Error>(())
564    /// ```
565    ///
566    /// ```
567    /// # use iri_string::validate::Error;
568    /// use iri_string::types::IriStr;
569    ///
570    /// let iri = IriStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
571    /// assert_eq!(iri.authority_str(), None);
572    /// # Ok::<_, Error>(())
573    /// ```
574    #[inline]
575    #[must_use]
576    pub fn authority_str(&self) -> Option<&str> {
577        trusted_parser::extract_authority_absolute(self.as_str())
578    }
579
580    /// Returns the path.
581    ///
582    /// # Examples
583    ///
584    /// ```
585    /// # use iri_string::validate::Error;
586    /// use iri_string::types::IriStr;
587    ///
588    /// let iri = IriStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
589    /// assert_eq!(iri.path_str(), "/pathpath");
590    /// # Ok::<_, Error>(())
591    /// ```
592    ///
593    /// ```
594    /// # use iri_string::validate::Error;
595    /// use iri_string::types::IriStr;
596    ///
597    /// let iri = IriStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
598    /// assert_eq!(iri.path_str(), "uuid:10db315b-fcd1-4428-aca8-15babc9a2da2");
599    /// # Ok::<_, Error>(())
600    /// ```
601    #[inline]
602    #[must_use]
603    pub fn path_str(&self) -> &str {
604        trusted_parser::extract_path_absolute(self.as_str())
605    }
606
607    /// Returns the query.
608    ///
609    /// The leading question mark (`?`) is truncated.
610    ///
611    /// # Examples
612    ///
613    /// ```
614    /// # use iri_string::validate::Error;
615    /// use iri_string::types::{IriQueryStr, IriStr};
616    ///
617    /// let iri = IriStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
618    /// let query = IriQueryStr::new("queryquery")?;
619    /// assert_eq!(iri.query(), Some(query));
620    /// # Ok::<_, Error>(())
621    /// ```
622    ///
623    /// ```
624    /// # use iri_string::validate::Error;
625    /// use iri_string::types::IriStr;
626    ///
627    /// let iri = IriStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
628    /// assert_eq!(iri.query(), None);
629    /// # Ok::<_, Error>(())
630    /// ```
631    #[inline]
632    #[must_use]
633    pub fn query(&self) -> Option<&RiQueryStr<S>> {
634        AsRef::<RiReferenceStr<S>>::as_ref(self).query()
635    }
636
637    /// Returns the query in a raw string slice.
638    ///
639    /// The leading question mark (`?`) is truncated.
640    ///
641    /// # Examples
642    ///
643    /// ```
644    /// # use iri_string::validate::Error;
645    /// use iri_string::types::IriStr;
646    ///
647    /// let iri = IriStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
648    /// assert_eq!(iri.query_str(), Some("queryquery"));
649    /// # Ok::<_, Error>(())
650    /// ```
651    ///
652    /// ```
653    /// # use iri_string::validate::Error;
654    /// use iri_string::types::IriStr;
655    ///
656    /// let iri = IriStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
657    /// assert_eq!(iri.query_str(), None);
658    /// # Ok::<_, Error>(())
659    /// ```
660    #[inline]
661    #[must_use]
662    pub fn query_str(&self) -> Option<&str> {
663        trusted_parser::extract_query(self.as_str())
664    }
665
666    /// Returns the fragment part if exists.
667    ///
668    /// A leading `#` character is truncated if the fragment part exists.
669    ///
670    /// # Examples
671    ///
672    /// ```
673    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriStr}, validate::Error};
674    /// let iri = IriStr::new("foo://bar/baz?qux=quux#corge")?;
675    /// let fragment = IriFragmentStr::new("corge")?;
676    /// assert_eq!(iri.fragment(), Some(fragment));
677    /// # Ok::<_, Error>(())
678    /// ```
679    ///
680    /// ```
681    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriStr}, validate::Error};
682    /// let iri = IriStr::new("foo://bar/baz?qux=quux#")?;
683    /// let fragment = IriFragmentStr::new("")?;
684    /// assert_eq!(iri.fragment(), Some(fragment));
685    /// # Ok::<_, Error>(())
686    /// ```
687    ///
688    /// ```
689    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
690    /// let iri = IriStr::new("foo://bar/baz?qux=quux")?;
691    /// assert_eq!(iri.fragment(), None);
692    /// # Ok::<_, Error>(())
693    /// ```
694    #[inline]
695    #[must_use]
696    pub fn fragment(&self) -> Option<&RiFragmentStr<S>> {
697        AsRef::<RiReferenceStr<S>>::as_ref(self).fragment()
698    }
699
700    /// Returns the fragment part as a raw string slice if exists.
701    ///
702    /// A leading `#` character is truncated if the fragment part exists.
703    ///
704    /// # Examples
705    ///
706    /// ```
707    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
708    /// let iri = IriStr::new("foo://bar/baz?qux=quux#corge")?;
709    /// assert_eq!(iri.fragment_str(), Some("corge"));
710    /// # Ok::<_, Error>(())
711    /// ```
712    ///
713    /// ```
714    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
715    /// let iri = IriStr::new("foo://bar/baz?qux=quux#")?;
716    /// assert_eq!(iri.fragment_str(), Some(""));
717    /// # Ok::<_, Error>(())
718    /// ```
719    ///
720    /// ```
721    /// # use iri_string::{spec::IriSpec, types::IriStr, validate::Error};
722    /// let iri = IriStr::new("foo://bar/baz?qux=quux")?;
723    /// assert_eq!(iri.fragment_str(), None);
724    /// # Ok::<_, Error>(())
725    /// ```
726    #[inline]
727    #[must_use]
728    pub fn fragment_str(&self) -> Option<&str> {
729        AsRef::<RiReferenceStr<S>>::as_ref(self).fragment_str()
730    }
731
732    /// Returns the authority components.
733    ///
734    /// # Examples
735    ///
736    /// ```
737    /// # use iri_string::validate::Error;
738    /// use iri_string::types::IriStr;
739    ///
740    /// let iri = IriStr::new("http://user:pass@example.com:8080/pathpath?queryquery")?;
741    /// let authority = iri.authority_components()
742    ///     .expect("authority is available");
743    /// assert_eq!(authority.userinfo(), Some("user:pass"));
744    /// assert_eq!(authority.host(), "example.com");
745    /// assert_eq!(authority.port(), Some("8080"));
746    /// # Ok::<_, Error>(())
747    /// ```
748    ///
749    /// ```
750    /// # use iri_string::validate::Error;
751    /// use iri_string::types::IriStr;
752    ///
753    /// let iri = IriStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
754    /// assert_eq!(iri.authority_str(), None);
755    /// # Ok::<_, Error>(())
756    /// ```
757    #[inline]
758    #[must_use]
759    pub fn authority_components(&self) -> Option<AuthorityComponents<'_>> {
760        AuthorityComponents::from_iri(self.as_ref())
761    }
762}
763
764#[cfg(feature = "alloc")]
765impl<S: Spec> RiString<S> {
766    /// Splits the IRI into an absolute IRI part and a fragment part.
767    ///
768    /// A leading `#` character is truncated if the fragment part exists.
769    ///
770    /// # Examples
771    ///
772    /// ```
773    /// use std::convert::TryFrom;
774    /// # use iri_string::{spec::IriSpec, types::{IriFragmentString, IriString}, validate::Error};
775    /// let iri = "foo://bar/baz?qux=quux#corge".parse::<IriString>()?;
776    /// let (absolute, fragment) = iri.into_absolute_and_fragment();
777    /// let fragment_expected = IriFragmentString::try_from("corge".to_owned())
778    ///     .map_err(|e| e.validation_error())?;
779    /// assert_eq!(absolute, "foo://bar/baz?qux=quux");
780    /// assert_eq!(fragment, Some(fragment_expected));
781    /// # Ok::<_, Error>(())
782    ///
783    /// ```
784    ///
785    /// ```
786    /// use std::convert::TryFrom;
787    /// # use iri_string::{spec::IriSpec, types::{IriFragmentString, IriString}, validate::Error};
788    /// let iri = "foo://bar/baz?qux=quux#".parse::<IriString>()?;
789    /// let (absolute, fragment) = iri.into_absolute_and_fragment();
790    /// let fragment_expected = IriFragmentString::try_from("".to_owned())
791    ///     .map_err(|e| e.validation_error())?;
792    /// assert_eq!(absolute, "foo://bar/baz?qux=quux");
793    /// assert_eq!(fragment, Some(fragment_expected));
794    /// # Ok::<_, Error>(())
795    /// ```
796    ///
797    /// ```
798    /// use std::convert::TryFrom;
799    /// # use iri_string::{spec::IriSpec, types::IriString, validate::Error};
800    /// let iri = "foo://bar/baz?qux=quux".parse::<IriString>()?;
801    /// let (absolute, fragment) = iri.into_absolute_and_fragment();
802    /// assert_eq!(absolute, "foo://bar/baz?qux=quux");
803    /// assert_eq!(fragment, None);
804    /// # Ok::<_, Error>(())
805    /// ```
806    #[must_use]
807    pub fn into_absolute_and_fragment(self) -> (RiAbsoluteString<S>, Option<RiFragmentString<S>>) {
808        let (prefix, fragment) = raw::split_fragment_owned(self.into());
809        // SAFETY: an IRI without fragment part is also an absolute IRI.
810        let prefix = unsafe {
811            RiAbsoluteString::new_unchecked_justified(
812                prefix,
813                "a non-relative IRI without fragment is an absolute IRI by definition",
814            )
815        };
816        let fragment = fragment.map(|fragment| {
817            // SAFETY: the string returned by `raw::split_fragment_owned()` must
818            // be the fragment part, and must also be a substring of the source IRI.
819            unsafe {
820                RiFragmentString::new_unchecked_justified(
821                    fragment,
822                    "fragment in a valid IRI must also be valid",
823                )
824            }
825        });
826
827        (prefix, fragment)
828    }
829
830    /// Strips the fragment part if exists, and returns an [`RiAbsoluteString`].
831    ///
832    /// # Examples
833    ///
834    /// ```
835    /// # use iri_string::{spec::IriSpec, types::IriString, validate::Error};
836    /// let iri = "foo://bar/baz?qux=quux#corge".parse::<IriString>()?;
837    /// assert_eq!(iri.into_absolute(), "foo://bar/baz?qux=quux");
838    /// # Ok::<_, Error>(())
839    /// ```
840    ///
841    /// ```
842    /// # use iri_string::{spec::IriSpec, types::IriString, validate::Error};
843    /// let iri = "foo://bar/baz?qux=quux".parse::<IriString>()?;
844    /// assert_eq!(iri.into_absolute(), "foo://bar/baz?qux=quux");
845    /// # Ok::<_, Error>(())
846    /// ```
847    ///
848    /// [`RiAbsoluteString`]: struct.RiAbsoluteString.html
849    #[must_use]
850    pub fn into_absolute(self) -> RiAbsoluteString<S> {
851        let mut s: String = self.into();
852        raw::remove_fragment(&mut s);
853        // SAFETY: an IRI without fragment part is also an absolute IRI.
854        unsafe {
855            RiAbsoluteString::new_unchecked_justified(
856                s,
857                "a non-relative IRI without fragment is an absolute IRI by definition",
858            )
859        }
860    }
861
862    /// Sets the fragment part to the given string.
863    ///
864    /// Removes fragment part (and following `#` character) if `None` is given.
865    pub fn set_fragment(&mut self, fragment: Option<&RiFragmentStr<S>>) {
866        raw::set_fragment(&mut self.inner, fragment.map(AsRef::as_ref));
867        debug_assert_eq!(Self::validate(&self.inner), Ok(()));
868    }
869
870    /// Removes the password completely (including separator colon) from `self` even if it is empty.
871    ///
872    /// # Examples
873    ///
874    /// ```
875    /// # use iri_string::validate::Error;
876    /// # #[cfg(feature = "alloc")] {
877    /// use iri_string::types::IriString;
878    ///
879    /// let mut iri = IriString::try_from("http://user:password@example.com/path?query")?;
880    /// iri.remove_password_inline();
881    /// assert_eq!(iri, "http://user@example.com/path?query");
882    /// # }
883    /// # Ok::<_, Error>(())
884    /// ```
885    ///
886    /// Even if the password is empty, the password and separator will be removed.
887    ///
888    /// ```
889    /// # use iri_string::validate::Error;
890    /// # #[cfg(feature = "alloc")] {
891    /// use iri_string::types::IriString;
892    ///
893    /// let mut iri = IriString::try_from("http://user:@example.com/path?query")?;
894    /// iri.remove_password_inline();
895    /// assert_eq!(iri, "http://user@example.com/path?query");
896    /// # }
897    /// # Ok::<_, Error>(())
898    /// ```
899    pub fn remove_password_inline(&mut self) {
900        let pw_range = match password_range_to_hide(self.as_slice().as_ref()) {
901            Some(v) => v,
902            None => return,
903        };
904        let separator_colon = pw_range.start - 1;
905        // SAFETY: the IRI must still be valid after the password component and
906        // the leading separator colon is removed.
907        unsafe {
908            let buf = self.as_inner_mut();
909            buf.drain(separator_colon..pw_range.end);
910            debug_assert_eq!(
911                Self::validate(buf),
912                Ok(()),
913                "the IRI must be valid after the password component is removed"
914            );
915        }
916    }
917
918    /// Replaces the non-empty password in `self` to the empty password.
919    ///
920    /// This leaves the separator colon if the password part was available.
921    ///
922    /// # Examples
923    ///
924    /// ```
925    /// # use iri_string::validate::Error;
926    /// # #[cfg(feature = "alloc")] {
927    /// use iri_string::types::IriString;
928    ///
929    /// let mut iri = IriString::try_from("http://user:password@example.com/path?query")?;
930    /// iri.remove_nonempty_password_inline();
931    /// assert_eq!(iri, "http://user:@example.com/path?query");
932    /// # }
933    /// # Ok::<_, Error>(())
934    /// ```
935    ///
936    /// If the password is empty, it is left as is.
937    ///
938    /// ```
939    /// # use iri_string::validate::Error;
940    /// # #[cfg(feature = "alloc")] {
941    /// use iri_string::types::IriString;
942    ///
943    /// let mut iri = IriString::try_from("http://user:@example.com/path?query")?;
944    /// iri.remove_nonempty_password_inline();
945    /// assert_eq!(iri, "http://user:@example.com/path?query");
946    /// # }
947    /// # Ok::<_, Error>(())
948    /// ```
949    pub fn remove_nonempty_password_inline(&mut self) {
950        let pw_range = match password_range_to_hide(self.as_slice().as_ref()) {
951            Some(v) if !v.is_empty() => v,
952            _ => return,
953        };
954        debug_assert_eq!(
955            self.as_str().as_bytes().get(pw_range.start - 1).copied(),
956            Some(b':'),
957            "the password component must be prefixed with a separator colon"
958        );
959        // SAFETY: the IRI must still be valid if the password is replaced with
960        // empty string.
961        unsafe {
962            let buf = self.as_inner_mut();
963            buf.drain(pw_range);
964            debug_assert_eq!(
965                Self::validate(buf),
966                Ok(()),
967                "the IRI must be valid after the password component is removed"
968            );
969        }
970    }
971
972    /// Replaces the host in-place and returns the new host, if authority is not empty.
973    ///
974    /// If the IRI has no authority, returns `None` without doing nothing. Note
975    /// that an empty host is distinguished from the absence of an authority.
976    ///
977    /// If the new host is invalid (i.e., [`validate::validate_host`][`crate::validate::host`]
978    /// returns `Err(_)`), also returns `None` without doing anything.
979    fn try_replace_host_impl(
980        &mut self,
981        new_host: &'_ str,
982        replace_only_reg_name: bool,
983    ) -> Result<Option<&str>, TryReserveError> {
984        use crate::types::generic::replace_domain_impl;
985
986        let result: Result<Option<core::ops::Range<usize>>, TryReserveError>;
987        {
988            // SAFETY: Replacing the (already existing) host part with another
989            // valid host does not change the class of an IRI.
990            let strbuf = unsafe { self.as_inner_mut() };
991            result = replace_domain_impl::<S>(strbuf, new_host, replace_only_reg_name);
992            debug_assert_eq!(
993                RiAbsoluteStr::<S>::validate(strbuf),
994                Ok(()),
995                "replacing a host with another valid host must keep an IRI valid: raw={strbuf:?}",
996            );
997        }
998        result.map(|opt| opt.map(|range| &self.as_str()[range]))
999    }
1000
1001    /// Replaces the host in-place and returns the new host, if authority is not empty.
1002    ///
1003    /// If the IRI has no authority, returns `None` without doing nothing. Note
1004    /// that an empty host is distinguished from the absence of an authority.
1005    ///
1006    /// If the new host is invalid (i.e., [`validate::validate_host`][`crate::validate::host`]
1007    /// returns `Err(_)`), also returns `None` without doing anything.
1008    ///
1009    /// If you need to replace only when the host is `reg-name` (for example
1010    /// when you attempt to apply IDNA encoding), use
1011    /// [`try_replace_host_reg_name`][`Self::try_replace_host_reg_name`] method
1012    /// instead.
1013    ///
1014    /// # Examples
1015    ///
1016    /// ```
1017    /// # use iri_string::types::UriString;
1018    /// let mut iri =
1019    ///     UriString::try_from("https://user:pass@example.com:443/").unwrap();
1020    /// let new_host_opt = iri.replace_host("www.example.com");
1021    /// assert_eq!(new_host_opt, Some("www.example.com"));
1022    /// assert_eq!(iri.authority_components().unwrap().host(), "www.example.com");
1023    /// assert_eq!(iri, "https://user:pass@www.example.com:443/");
1024    /// ```
1025    pub fn replace_host(&mut self, new_host: &'_ str) -> Option<&str> {
1026        self.try_replace_host(new_host)
1027            .expect("failed to allocate memory when replacing the host part of an IRI")
1028    }
1029
1030    /// Replaces the host in-place and returns the new host, if authority is not empty.
1031    ///
1032    /// This returns `TryReserveError` on memory allocation failure, instead of
1033    /// panicking. Otherwise, this method behaves same as
1034    /// [`replace_host`][`Self::replace_host`] method.
1035    pub fn try_replace_host(&mut self, new_host: &'_ str) -> Result<Option<&str>, TryReserveError> {
1036        self.try_replace_host_impl(new_host, false)
1037    }
1038
1039    /// Replaces the domain name (`reg-name`) in-place and returns the new host,
1040    /// if authority is not empty.
1041    ///
1042    /// If the IRI has no authority or the host is not a reg-name (i.e., is
1043    /// neither an IP-address nor empty), returns `None` without doing nothing.
1044    /// Note that an empty host is distinguished from the absence of an authority.
1045    ///
1046    /// If the new host is invalid (i.e., [`validate::validate_host`][`crate::validate::host`]
1047    /// returns `Err(_)`), also returns `None` without doing anything.
1048    ///
1049    /// # Examples
1050    ///
1051    /// ```
1052    /// # use iri_string::types::UriString;
1053    /// let mut iri =
1054    ///     UriString::try_from("https://user:pass@example.com:443/").unwrap();
1055    /// let new_host_opt = iri.replace_host("www.example.com");
1056    /// assert_eq!(new_host_opt, Some("www.example.com"));
1057    /// assert_eq!(iri.authority_components().unwrap().host(), "www.example.com");
1058    /// assert_eq!(iri, "https://user:pass@www.example.com:443/");
1059    /// ```
1060    ///
1061    /// ```
1062    /// # use iri_string::types::UriString;
1063    /// let mut iri =
1064    ///     UriString::try_from("https://192.168.0.1/").unwrap();
1065    /// let new_host_opt = iri.replace_host_reg_name("localhost");
1066    /// assert_eq!(new_host_opt, None, "IPv4 address is not a reg-name");
1067    /// assert_eq!(iri, "https://192.168.0.1/", "won't be changed");
1068    /// ```
1069    ///
1070    /// To apply IDNA conversion, get the domain by [`AuthorityComponents::reg_name`]
1071    /// method, convert the domain, and then set it by this
1072    /// `replace_host_reg_name` method.
1073    ///
1074    /// ```
1075    /// # extern crate alloc;
1076    /// # use alloc::string::String;
1077    /// # use iri_string::types::IriString;
1078    /// /// Converts the given into IDNA encoding.
1079    /// fn conv_idna(domain: &str) -> String {
1080    ///     /* ... */
1081    /// #   if domain == "\u{03B1}.example.com" {
1082    /// #       "xn--mxa.example.com".into()
1083    /// #   } else {
1084    /// #       unimplemented!()
1085    /// #   }
1086    /// }
1087    ///
1088    /// // U+03B1: GREEK SMALL LETTER ALPHA
1089    /// let mut iri =
1090    ///     IriString::try_from("https://\u{03B1}.example.com/").unwrap();
1091    ///
1092    /// let old_domain = iri
1093    ///     .authority_components()
1094    ///     .expect("authority is not empty")
1095    ///     .reg_name()
1096    ///     .expect("the host is reg-name");
1097    /// assert_eq!(old_domain, "\u{03B1}.example.com");
1098    ///
1099    /// // Get the new host by your own.
1100    /// let new_domain: String = conv_idna(old_domain);
1101    /// assert_eq!(new_domain, "xn--mxa.example.com");
1102    ///
1103    /// let new_host_opt = iri.replace_host(&new_domain);
1104    /// assert_eq!(new_host_opt, Some("xn--mxa.example.com"));
1105    /// assert_eq!(iri.authority_components().unwrap().host(), "xn--mxa.example.com");
1106    /// assert_eq!(iri, "https://xn--mxa.example.com/");
1107    /// ```
1108    pub fn replace_host_reg_name(&mut self, new_host: &'_ str) -> Option<&str> {
1109        self.try_replace_host_reg_name(new_host)
1110            .expect("failed to allocate memory when replacing the host part of an IRI")
1111    }
1112
1113    /// Replaces the domain name (`reg-name`) in-place and returns the new host,
1114    /// if authority is not empty.
1115    ///
1116    /// This returns `TryReserveError` on memory allocation failure, instead of
1117    /// panicking. Otherwise, this method behaves same as
1118    /// [`replace_host_reg_name`][`Self::replace_host_reg_name`] method.
1119    pub fn try_replace_host_reg_name(
1120        &mut self,
1121        new_host: &'_ str,
1122    ) -> Result<Option<&str>, TryReserveError> {
1123        self.try_replace_host_impl(new_host, true)
1124    }
1125}
1126
1127impl_trivial_conv_between_iri! {
1128    from_slice: RiStr,
1129    from_owned: RiString,
1130    to_slice: RiReferenceStr,
1131    to_owned: RiReferenceString,
1132}