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