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