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