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}