Skip to main content

iri_string/types/generic/
absolute.rs

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