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}