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