iri_string/types/generic/
relative.rs

1//! Relative IRI reference.
2
3use crate::components::AuthorityComponents;
4#[cfg(feature = "alloc")]
5use crate::mask_password::password_range_to_hide;
6use crate::mask_password::PasswordMasked;
7use crate::normalize::Normalized;
8use crate::parser::trusted as trusted_parser;
9#[cfg(feature = "alloc")]
10use crate::raw;
11use crate::resolve::FixedBaseResolver;
12use crate::spec::Spec;
13#[cfg(feature = "alloc")]
14use crate::types::RiReferenceString;
15use crate::types::{RiAbsoluteStr, RiFragmentStr, RiQueryStr, RiReferenceStr, RiStr};
16use crate::validate::relative_ref;
17
18define_custom_string_slice! {
19    /// A borrowed slice of a relative IRI reference.
20    ///
21    /// This corresponds to [`irelative-ref` rule] in [RFC 3987]
22    /// (and [`relative-ref` rule] in [RFC 3986]).
23    /// The rule for `irelative-ref` is `irelative-part [ "?" iquery ] [ "#" ifragment ]`.
24    ///
25    /// # Valid values
26    ///
27    /// This type can have a relative IRI reference.
28    ///
29    /// ```
30    /// # use iri_string::types::IriRelativeStr;
31    /// assert!(IriRelativeStr::new("foo").is_ok());
32    /// assert!(IriRelativeStr::new("foo/bar").is_ok());
33    /// assert!(IriRelativeStr::new("/foo").is_ok());
34    /// assert!(IriRelativeStr::new("//foo/bar").is_ok());
35    /// assert!(IriRelativeStr::new("?foo").is_ok());
36    /// assert!(IriRelativeStr::new("#foo").is_ok());
37    /// assert!(IriRelativeStr::new("foo/bar?baz#qux").is_ok());
38    /// // The first path component can have colon if the path is absolute.
39    /// assert!(IriRelativeStr::new("/foo:bar/").is_ok());
40    /// // Second or following path components can have colon.
41    /// assert!(IriRelativeStr::new("foo/bar://baz/").is_ok());
42    /// assert!(IriRelativeStr::new("./foo://bar").is_ok());
43    /// ```
44    ///
45    /// Absolute form of a reference is not allowed.
46    ///
47    /// ```
48    /// # use iri_string::types::IriRelativeStr;
49    /// assert!(IriRelativeStr::new("https://example.com/").is_err());
50    /// // The first path component cannot have colon, if the path is not absolute.
51    /// assert!(IriRelativeStr::new("foo:bar").is_err());
52    /// assert!(IriRelativeStr::new("foo:").is_err());
53    /// assert!(IriRelativeStr::new("foo:/").is_err());
54    /// assert!(IriRelativeStr::new("foo://").is_err());
55    /// assert!(IriRelativeStr::new("foo:///").is_err());
56    /// assert!(IriRelativeStr::new("foo:////").is_err());
57    /// assert!(IriRelativeStr::new("foo://///").is_err());
58    /// ```
59    ///
60    /// Some characters and sequences cannot used in an IRI reference.
61    ///
62    /// ```
63    /// # use iri_string::types::IriRelativeStr;
64    /// // `<` and `>` cannot directly appear in a relative IRI reference.
65    /// assert!(IriRelativeStr::new("<not allowed>").is_err());
66    /// // Broken percent encoding cannot appear in a relative IRI reference.
67    /// assert!(IriRelativeStr::new("%").is_err());
68    /// assert!(IriRelativeStr::new("%GG").is_err());
69    /// ```
70    ///
71    /// [RFC 3986]: https://tools.ietf.org/html/rfc3986
72    /// [RFC 3987]: https://tools.ietf.org/html/rfc3987
73    /// [`irelative-ref` rule]: https://tools.ietf.org/html/rfc3987#section-2.2
74    /// [`relative-ref` rule]: https://tools.ietf.org/html/rfc3986#section-4.2
75    struct RiRelativeStr {
76        validator = relative_ref,
77        expecting_msg = "Relative IRI reference string",
78    }
79}
80
81#[cfg(feature = "alloc")]
82define_custom_string_owned! {
83    /// An owned string of a relative IRI reference.
84    ///
85    /// This corresponds to [`irelative-ref` rule] in [RFC 3987]
86    /// (and [`relative-ref` rule] in [RFC 3986]).
87    /// The rule for `irelative-ref` is `irelative-part [ "?" iquery ] [ "#" ifragment ]`.
88    ///
89    /// For details, see the document for [`RiRelativeStr`].
90    ///
91    /// Enabled by `alloc` or `std` feature.
92    ///
93    /// [RFC 3986]: https://tools.ietf.org/html/rfc3986
94    /// [RFC 3987]: https://tools.ietf.org/html/rfc3987
95    /// [`irelative-ref` rule]: https://tools.ietf.org/html/rfc3987#section-2.2
96    /// [`relative-ref` rule]: https://tools.ietf.org/html/rfc3986#section-4.2
97    /// [`RiRelativeString`]: struct.RiRelativeString.html
98    struct RiRelativeString {
99        validator = relative_ref,
100        slice = RiRelativeStr,
101        expecting_msg = "Relative IRI reference string",
102    }
103}
104
105impl<S: Spec> RiRelativeStr<S> {
106    /// Returns resolved IRI against the given base IRI.
107    ///
108    /// For IRI reference resolution output examples, see [RFC 3986 section 5.4].
109    ///
110    /// If you are going to resolve multiple references against the common base,
111    /// consider using [`FixedBaseResolver`].
112    ///
113    /// # Strictness
114    ///
115    /// The IRI parsers provided by this crate is strict (e.g. `http:g` is
116    /// always interpreted as a composition of the scheme `http` and the path
117    /// `g`), so backward compatible parsing and resolution are not provided.
118    /// About parser and resolver strictness, see [RFC 3986 section 5.4.2]:
119    ///
120    /// > Some parsers allow the scheme name to be present in a relative
121    /// > reference if it is the same as the base URI scheme. This is considered
122    /// > to be a loophole in prior specifications of partial URI
123    /// > [RFC1630](https://tools.ietf.org/html/rfc1630). Its use should be
124    /// > avoided but is allowed for backward compatibility.
125    /// >
126    /// > --- <https://tools.ietf.org/html/rfc3986#section-5.4.2>
127    ///
128    /// # Failures
129    ///
130    /// This method itself does not fail, but IRI resolution without WHATWG URL
131    /// Standard serialization can fail in some minor cases.
132    ///
133    /// To see examples of such unresolvable IRIs, visit the documentation
134    /// for [`normalize`][`crate::normalize`] module.
135    ///
136    /// [RFC 3986 section 5.4]: https://tools.ietf.org/html/rfc3986#section-5.4
137    /// [RFC 3986 section 5.4.2]: https://tools.ietf.org/html/rfc3986#section-5.4.2
138    pub fn resolve_against<'a>(&'a self, base: &'a RiAbsoluteStr<S>) -> Normalized<'a, RiStr<S>> {
139        FixedBaseResolver::new(base).resolve(self.as_ref())
140    }
141
142    /// Returns the proxy to the IRI with password masking feature.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// # use iri_string::validate::Error;
148    /// # #[cfg(feature = "alloc")] {
149    /// use iri_string::format::ToDedicatedString;
150    /// use iri_string::types::IriRelativeStr;
151    ///
152    /// let iri = IriRelativeStr::new("//user:password@example.com/path?query")?;
153    /// let masked = iri.mask_password();
154    /// assert_eq!(masked.to_dedicated_string(), "//user:@example.com/path?query");
155    ///
156    /// assert_eq!(
157    ///     masked.replace_password("${password}").to_string(),
158    ///     "//user:${password}@example.com/path?query"
159    /// );
160    /// # }
161    /// # Ok::<_, Error>(())
162    /// ```
163    #[inline]
164    #[must_use]
165    pub fn mask_password(&self) -> PasswordMasked<'_, Self> {
166        PasswordMasked::new(self)
167    }
168}
169
170/// Components getters.
171impl<S: Spec> RiRelativeStr<S> {
172    /// Returns the authority.
173    ///
174    /// The leading `//` is truncated.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// # use iri_string::validate::Error;
180    /// use iri_string::types::IriRelativeStr;
181    ///
182    /// let iri = IriRelativeStr::new("//example.com/pathpath?queryquery#fragfrag")?;
183    /// assert_eq!(iri.authority_str(), Some("example.com"));
184    /// # Ok::<_, Error>(())
185    /// ```
186    ///
187    /// ```
188    /// # use iri_string::validate::Error;
189    /// use iri_string::types::IriRelativeStr;
190    ///
191    /// let iri = IriRelativeStr::new("foo//bar:baz")?;
192    /// assert_eq!(iri.authority_str(), None);
193    /// # Ok::<_, Error>(())
194    /// ```
195    #[inline]
196    #[must_use]
197    pub fn authority_str(&self) -> Option<&str> {
198        trusted_parser::extract_authority_relative(self.as_str())
199    }
200
201    /// Returns the path.
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// # use iri_string::validate::Error;
207    /// use iri_string::types::IriRelativeStr;
208    ///
209    /// let iri = IriRelativeStr::new("//example.com/pathpath?queryquery#fragfrag")?;
210    /// assert_eq!(iri.path_str(), "/pathpath");
211    /// # Ok::<_, Error>(())
212    /// ```
213    ///
214    /// ```
215    /// # use iri_string::validate::Error;
216    /// use iri_string::types::IriRelativeStr;
217    ///
218    /// let iri = IriRelativeStr::new("foo//bar:baz")?;
219    /// assert_eq!(iri.path_str(), "foo//bar:baz");
220    /// # Ok::<_, Error>(())
221    /// ```
222    #[inline]
223    #[must_use]
224    pub fn path_str(&self) -> &str {
225        trusted_parser::extract_path_relative(self.as_str())
226    }
227
228    /// Returns the query.
229    ///
230    /// The leading question mark (`?`) is truncated.
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// # use iri_string::validate::Error;
236    /// use iri_string::types::{IriQueryStr, IriRelativeStr};
237    ///
238    /// let iri = IriRelativeStr::new("//example.com/pathpath?queryquery#fragfrag")?;
239    /// let query = IriQueryStr::new("queryquery")?;
240    /// assert_eq!(iri.query(), Some(query));
241    /// # Ok::<_, Error>(())
242    /// ```
243    ///
244    /// ```
245    /// # use iri_string::validate::Error;
246    /// use iri_string::types::{IriQueryStr, IriRelativeStr};
247    ///
248    /// let iri = IriRelativeStr::new("foo//bar:baz?")?;
249    /// let query = IriQueryStr::new("")?;
250    /// assert_eq!(iri.query(), Some(query));
251    /// # Ok::<_, Error>(())
252    /// ```
253    #[inline]
254    #[must_use]
255    pub fn query(&self) -> Option<&RiQueryStr<S>> {
256        trusted_parser::extract_query(self.as_str()).map(|query| {
257            // SAFETY: `extract_query` returns the query part of an IRI, and the
258            // returned string should have only valid characters since is the
259            // substring of the source IRI.
260            unsafe { RiQueryStr::new_maybe_unchecked(query) }
261        })
262    }
263
264    /// Returns the query in a raw string slice.
265    ///
266    /// The leading question mark (`?`) is truncated.
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// # use iri_string::validate::Error;
272    /// use iri_string::types::IriRelativeStr;
273    ///
274    /// let iri = IriRelativeStr::new("//example.com/pathpath?queryquery#fragfrag")?;
275    /// assert_eq!(iri.query_str(), Some("queryquery"));
276    /// # Ok::<_, Error>(())
277    /// ```
278    ///
279    /// ```
280    /// # use iri_string::validate::Error;
281    /// use iri_string::types::IriRelativeStr;
282    ///
283    /// let iri = IriRelativeStr::new("foo//bar:baz?")?;
284    /// assert_eq!(iri.query_str(), Some(""));
285    /// # Ok::<_, Error>(())
286    /// ```
287    #[inline]
288    #[must_use]
289    pub fn query_str(&self) -> Option<&str> {
290        trusted_parser::extract_query(self.as_str())
291    }
292
293    /// Returns the fragment part if exists.
294    ///
295    /// A leading `#` character is truncated if the fragment part exists.
296    ///
297    /// # Examples
298    ///
299    /// If the IRI has a fragment part, `Some(_)` is returned.
300    ///
301    /// ```
302    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriRelativeStr}, validate::Error};
303    /// let iri = IriRelativeStr::new("?foo#bar")?;
304    /// let fragment = IriFragmentStr::new("bar")?;
305    /// assert_eq!(iri.fragment(), Some(fragment));
306    /// # Ok::<_, Error>(())
307    /// ```
308    ///
309    /// ```
310    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriRelativeStr}, validate::Error};
311    /// let iri = IriRelativeStr::new("#foo")?;
312    /// let fragment = IriFragmentStr::new("foo")?;
313    /// assert_eq!(iri.fragment(), Some(fragment));
314    /// # Ok::<_, Error>(())
315    /// ```
316    ///
317    /// When the fragment part exists but is empty string, `Some(_)` is returned.
318    ///
319    /// ```
320    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriRelativeStr}, validate::Error};
321    /// let iri = IriRelativeStr::new("#")?;
322    /// let fragment = IriFragmentStr::new("")?;
323    /// assert_eq!(iri.fragment(), Some(fragment));
324    /// # Ok::<_, Error>(())
325    /// ```
326    ///
327    /// If the IRI has no fragment, `None` is returned.
328    ///
329    /// ```
330    /// # use iri_string::{spec::IriSpec, types::IriRelativeStr, validate::Error};
331    /// let iri = IriRelativeStr::new("")?;
332    /// assert_eq!(iri.fragment(), None);
333    /// # Ok::<_, Error>(())
334    /// ```
335    #[must_use]
336    pub fn fragment(&self) -> Option<&RiFragmentStr<S>> {
337        AsRef::<RiReferenceStr<S>>::as_ref(self).fragment()
338    }
339
340    /// Returns the fragment part as a raw string slice if exists.
341    ///
342    /// A leading `#` character is truncated if the fragment part exists.
343    ///
344    /// # Examples
345    ///
346    /// If the IRI has a fragment part, `Some(_)` is returned.
347    ///
348    /// ```
349    /// # use iri_string::{spec::IriSpec, types::IriRelativeStr, validate::Error};
350    /// let iri = IriRelativeStr::new("?foo#bar")?;
351    /// assert_eq!(iri.fragment_str(), Some("bar"));
352    /// # Ok::<_, Error>(())
353    /// ```
354    ///
355    /// ```
356    /// # use iri_string::{spec::IriSpec, types::IriRelativeStr, validate::Error};
357    /// let iri = IriRelativeStr::new("#foo")?;
358    /// assert_eq!(iri.fragment_str(), Some("foo"));
359    /// # Ok::<_, Error>(())
360    /// ```
361    ///
362    /// When the fragment part exists but is empty string, `Some(_)` is returned.
363    ///
364    /// ```
365    /// # use iri_string::{spec::IriSpec, types::IriRelativeStr, validate::Error};
366    /// let iri = IriRelativeStr::new("#")?;
367    /// assert_eq!(iri.fragment_str(), Some(""));
368    /// # Ok::<_, Error>(())
369    /// ```
370    ///
371    /// If the IRI has no fragment, `None` is returned.
372    ///
373    /// ```
374    /// # use iri_string::{spec::IriSpec, types::IriRelativeStr, validate::Error};
375    /// let iri = IriRelativeStr::new("")?;
376    /// assert_eq!(iri.fragment(), None);
377    /// # Ok::<_, Error>(())
378    /// ```
379    #[must_use]
380    pub fn fragment_str(&self) -> Option<&str> {
381        AsRef::<RiReferenceStr<S>>::as_ref(self).fragment_str()
382    }
383
384    /// Returns the authority components.
385    ///
386    /// # Examples
387    ///
388    /// ```
389    /// # use iri_string::validate::Error;
390    /// use iri_string::types::IriRelativeStr;
391    ///
392    /// let iri = IriRelativeStr::new("//user:pass@example.com:8080/pathpath?queryquery")?;
393    /// let authority = iri.authority_components()
394    ///     .expect("authority is available");
395    /// assert_eq!(authority.userinfo(), Some("user:pass"));
396    /// assert_eq!(authority.host(), "example.com");
397    /// assert_eq!(authority.port(), Some("8080"));
398    /// # Ok::<_, Error>(())
399    /// ```
400    ///
401    /// ```
402    /// # use iri_string::validate::Error;
403    /// use iri_string::types::IriRelativeStr;
404    ///
405    /// let iri = IriRelativeStr::new("foo//bar:baz")?;
406    /// assert_eq!(iri.authority_str(), None);
407    /// # Ok::<_, Error>(())
408    /// ```
409    #[inline]
410    #[must_use]
411    pub fn authority_components(&self) -> Option<AuthorityComponents<'_>> {
412        AuthorityComponents::from_iri(self.as_ref())
413    }
414}
415
416#[cfg(feature = "alloc")]
417impl<S: Spec> RiRelativeString<S> {
418    /// Sets the fragment part to the given string.
419    ///
420    /// Removes fragment part (and following `#` character) if `None` is given.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// # use iri_string::validate::Error;
426    /// # #[cfg(feature = "alloc")] {
427    /// use iri_string::types::{IriFragmentStr, IriRelativeString};
428    ///
429    /// let mut iri = IriRelativeString::try_from("//user:password@example.com/path?query#frag.old")?;
430    /// assert_eq!(iri.fragment_str(), Some("frag.old"));
431    ///
432    /// iri.set_fragment(None);
433    /// assert_eq!(iri.fragment(), None);
434    ///
435    /// let frag_new = IriFragmentStr::new("frag-new")?;
436    /// iri.set_fragment(Some(frag_new));
437    /// assert_eq!(iri.fragment_str(), Some("frag-new"));
438    /// # }
439    /// # Ok::<_, Error>(())
440    /// ```
441    ///
442    /// Fragment can be empty, and it is distinguished from the absense of a fragment.
443    ///
444    /// ```
445    /// # use iri_string::validate::Error;
446    /// # #[cfg(feature = "alloc")] {
447    /// use iri_string::types::IriRelativeString;
448    ///
449    /// let mut iri = IriRelativeString::try_from("/path#")?;
450    /// assert_eq!(iri, "/path#");
451    /// assert_eq!(iri.fragment_str(), Some(""), "Fragment is present and empty");
452    ///
453    /// iri.set_fragment(None);
454    /// assert_eq!(iri, "/path", "Note that # is now removed");
455    /// assert_eq!(iri.fragment_str(), None, "Fragment is absent");
456    /// # }
457    /// # Ok::<_, Error>(())
458    /// ```
459    pub fn set_fragment(&mut self, fragment: Option<&RiFragmentStr<S>>) {
460        raw::set_fragment(&mut self.inner, fragment.map(AsRef::as_ref));
461        debug_assert!(relative_ref::<S>(&self.inner).is_ok());
462    }
463
464    /// Removes the password completely (including separator colon) from `self` even if it is empty.
465    ///
466    /// # Examples
467    ///
468    /// ```
469    /// # use iri_string::validate::Error;
470    /// # #[cfg(feature = "alloc")] {
471    /// use iri_string::types::IriRelativeString;
472    ///
473    /// let mut iri = IriRelativeString::try_from("//user:password@example.com/path?query")?;
474    /// iri.remove_password_inline();
475    /// assert_eq!(iri, "//user@example.com/path?query");
476    /// # }
477    /// # Ok::<_, Error>(())
478    /// ```
479    ///
480    /// Even if the password is empty, the password and separator will be removed.
481    ///
482    /// ```
483    /// # use iri_string::validate::Error;
484    /// # #[cfg(feature = "alloc")] {
485    /// use iri_string::types::IriRelativeString;
486    ///
487    /// let mut iri = IriRelativeString::try_from("//user:@example.com/path?query")?;
488    /// iri.remove_password_inline();
489    /// assert_eq!(iri, "//user@example.com/path?query");
490    /// # }
491    /// # Ok::<_, Error>(())
492    /// ```
493    pub fn remove_password_inline(&mut self) {
494        let pw_range = match password_range_to_hide(self.as_slice().as_ref()) {
495            Some(v) => v,
496            None => return,
497        };
498        let separator_colon = pw_range.start - 1;
499        // SAFETY: removing password component and the leading colon preserves
500        // the IRI still syntactically valid.
501        unsafe {
502            let buf = self.as_inner_mut();
503            buf.drain(separator_colon..pw_range.end);
504            debug_assert!(
505                RiRelativeStr::<S>::new(buf).is_ok(),
506                "[validity] the IRI must be valid after the password component is removed"
507            );
508        }
509    }
510
511    /// Replaces the non-empty password in `self` to the empty password.
512    ///
513    /// This leaves the separator colon if the password part was available.
514    ///
515    /// # Examples
516    ///
517    /// ```
518    /// # use iri_string::validate::Error;
519    /// # #[cfg(feature = "alloc")] {
520    /// use iri_string::types::IriRelativeString;
521    ///
522    /// let mut iri = IriRelativeString::try_from("//user:password@example.com/path?query")?;
523    /// iri.remove_nonempty_password_inline();
524    /// assert_eq!(iri, "//user:@example.com/path?query");
525    /// # }
526    /// # Ok::<_, Error>(())
527    /// ```
528    ///
529    /// If the password is empty, it is left as is.
530    ///
531    /// ```
532    /// # use iri_string::validate::Error;
533    /// # #[cfg(feature = "alloc")] {
534    /// use iri_string::types::IriRelativeString;
535    ///
536    /// let mut iri = IriRelativeString::try_from("//user:@example.com/path?query")?;
537    /// iri.remove_nonempty_password_inline();
538    /// assert_eq!(iri, "//user:@example.com/path?query");
539    /// # }
540    /// # Ok::<_, Error>(())
541    /// ```
542    pub fn remove_nonempty_password_inline(&mut self) {
543        let pw_range = match password_range_to_hide(self.as_slice().as_ref()) {
544            Some(v) if !v.is_empty() => v,
545            _ => return,
546        };
547        debug_assert_eq!(
548            self.as_str().as_bytes().get(pw_range.start - 1).copied(),
549            Some(b':'),
550            "[validity] the password component must be prefixed with a separator colon"
551        );
552        // SAFETY: the IRI must be valid after the password component is
553        // replaced with the empty password.
554        unsafe {
555            let buf = self.as_inner_mut();
556            buf.drain(pw_range);
557            debug_assert!(
558                RiRelativeStr::<S>::new(buf).is_ok(),
559                "[validity] the IRI must be valid after the password component \
560                 is replaced with the empty password"
561            );
562        }
563    }
564}
565
566impl_trivial_conv_between_iri! {
567    from_slice: RiRelativeStr,
568    from_owned: RiRelativeString,
569    to_slice: RiReferenceStr,
570    to_owned: RiReferenceString,
571}