iri_string/types/generic/
normal.rs

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