oxiri/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![deny(unsafe_code)]
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::borrow::{Borrow, Cow};
8use std::cmp::Ordering;
9use std::convert::{TryFrom, TryInto};
10use std::error::Error;
11use std::fmt;
12use std::hash::{Hash, Hasher};
13use std::net::{AddrParseError, Ipv6Addr};
14use std::ops::Deref;
15use std::str::{Chars, FromStr};
16
17/// A [RFC 3987](https://www.ietf.org/rfc/rfc3987.html) IRI reference.
18///
19/// Instances of this type may be absolute or relative,
20/// unlike [`Iri`].
21///
22/// ```
23/// use oxiri::{Iri, IriRef};
24///
25/// // Parse and validate base IRI
26/// let base_iri = IriRef::parse("../bar/baz")?;
27///
28/// // Validate and resolve relative IRI
29/// let iri = base_iri.resolve("bat#foo")?;
30/// assert_eq!(iri.into_inner(), "../bar/bat#foo");
31///
32/// // IriRef's *can* also be absolute.
33/// assert!(IriRef::parse("http://foo.com/bar/baz").is_ok());
34///
35/// // It is possible to build an IriRef from an Iri object
36/// IriRef::from(Iri::parse("http://foo.com/bar")?);
37/// # Result::<(), oxiri::IriParseError>::Ok(())
38/// ```
39#[derive(Clone, Copy)]
40pub struct IriRef<T> {
41    iri: T,
42    positions: IriElementsPositions,
43}
44
45impl<T: Deref<Target = str>> IriRef<T> {
46    /// Parses and validates the IRI-reference following the grammar from [RFC 3987](https://www.ietf.org/rfc/rfc3987.html).
47    ///
48    /// This operation keeps internally the `iri` parameter and does not allocate.
49    ///
50    /// Use [`parse_unchecked`](Self::parse_unchecked) if you already know the IRI is valid to get faster processing.
51    ///
52    /// ```
53    /// use oxiri::IriRef;
54    ///
55    /// IriRef::parse("//foo.com/bar/baz")?;
56    /// # Result::<(), oxiri::IriParseError>::Ok(())
57    /// ```
58    pub fn parse(iri: T) -> Result<Self, IriParseError> {
59        let positions = IriParser::<_, false>::parse(&iri, None, &mut VoidOutputBuffer::default())?;
60        Ok(Self { iri, positions })
61    }
62
63    /// Variant of [`parse`](Self::parse) that assumes that the IRI is valid to skip validation.
64    ///
65    /// ```
66    /// use oxiri::IriRef;
67    ///
68    /// IriRef::parse_unchecked("//foo.com/bar/baz");
69    /// ```
70    pub fn parse_unchecked(iri: T) -> Self {
71        let positions =
72            IriParser::<_, true>::parse(&iri, None, &mut VoidOutputBuffer::default()).unwrap();
73        Self { iri, positions }
74    }
75
76    /// Validates and resolved a relative IRI against the current IRI
77    /// following [RFC 3986](https://www.ietf.org/rfc/rfc3986.html) relative URI resolution algorithm.
78    ///
79    /// Use [`resolve_unchecked`](Self::resolve_unchecked) if you already know the IRI is valid to get faster processing.
80    ///
81    /// ```
82    /// use oxiri::IriRef;
83    ///
84    /// let base_iri = IriRef::parse("//foo.com/bar/baz")?;
85    /// let iri = base_iri.resolve("bat#foo")?;
86    /// assert_eq!(iri.into_inner(), "//foo.com/bar/bat#foo");
87    /// # Result::<(), oxiri::IriParseError>::Ok(())
88    /// ```
89    pub fn resolve(&self, iri: &str) -> Result<IriRef<String>, IriParseError> {
90        let mut target_buffer = String::with_capacity(self.iri.len() + iri.len());
91        let positions = IriParser::<_, false>::parse(iri, Some(self.as_ref()), &mut target_buffer)?;
92        Ok(IriRef {
93            iri: target_buffer,
94            positions,
95        })
96    }
97
98    /// Variant of [`resolve`](Self::resolve) that assumes that the IRI is valid to skip validation.
99    ///
100    /// ```
101    /// use oxiri::IriRef;
102    ///
103    /// let base_iri = IriRef::parse_unchecked("//foo.com/bar/baz");
104    /// let iri = base_iri.resolve_unchecked("bat#foo");
105    /// assert_eq!(iri.into_inner(), "//foo.com/bar/bat#foo");
106    /// ```
107    pub fn resolve_unchecked(&self, iri: &str) -> IriRef<String> {
108        let mut target_buffer = String::with_capacity(self.iri.len() + iri.len());
109        let positions =
110            IriParser::<_, true>::parse(iri, Some(self.as_ref()), &mut target_buffer).unwrap();
111        IriRef {
112            iri: target_buffer,
113            positions,
114        }
115    }
116
117    /// Validates and resolved a relative IRI against the current IRI
118    /// following [RFC 3986](https://www.ietf.org/rfc/rfc3986.html) relative URI resolution algorithm.
119    ///
120    /// It outputs the resolved IRI into `target_buffer` to avoid any memory allocation.
121    ///
122    /// Use [`resolve_into_unchecked`](Self::resolve_into_unchecked) if you already know the IRI is valid to get faster processing.
123    ///
124    /// ```
125    /// use oxiri::IriRef;
126    ///
127    /// let base_iri = IriRef::parse("//foo.com/bar/baz")?;
128    /// let mut result = String::default();
129    /// let iri = base_iri.resolve_into("bat#foo", &mut result)?;
130    /// assert_eq!(result, "//foo.com/bar/bat#foo");
131    /// # Result::<(), oxiri::IriParseError>::Ok(())
132    /// ```
133    pub fn resolve_into(&self, iri: &str, target_buffer: &mut String) -> Result<(), IriParseError> {
134        IriParser::<_, false>::parse(iri, Some(self.as_ref()), target_buffer)?;
135        Ok(())
136    }
137
138    /// Variant of [`resolve_into`](Self::resolve_into) that assumes that the IRI is valid to skip validation.
139    ///
140    /// ```
141    /// use oxiri::IriRef;
142    ///
143    /// let base_iri = IriRef::parse_unchecked("//foo.com/bar/baz");
144    /// let mut result = String::default();
145    /// let iri = base_iri.resolve_into_unchecked("bat#foo", &mut result);
146    /// assert_eq!(result, "//foo.com/bar/bat#foo");
147    /// ```
148    pub fn resolve_into_unchecked(&self, iri: &str, target_buffer: &mut String) {
149        IriParser::<_, true>::parse(iri, Some(self.as_ref()), target_buffer).unwrap();
150    }
151
152    /// Returns an `IriRef` borrowing this IRI's text.
153    #[inline]
154    pub fn as_ref(&self) -> IriRef<&str> {
155        IriRef {
156            iri: &self.iri,
157            positions: self.positions,
158        }
159    }
160
161    /// Returns the underlying IRI representation.
162    ///
163    /// ```
164    /// use oxiri::IriRef;
165    ///
166    /// let iri = IriRef::parse("//example.com/foo")?;
167    /// assert_eq!(iri.as_str(), "//example.com/foo");
168    /// # Result::<(), oxiri::IriParseError>::Ok(())
169    /// ```
170    #[inline]
171    pub fn as_str(&self) -> &str {
172        &self.iri
173    }
174
175    /// Returns the underlying IRI representation.
176    ///
177    /// ```
178    /// use oxiri::IriRef;
179    ///
180    /// let iri = IriRef::parse("//example.com/foo")?;
181    /// assert_eq!(iri.into_inner(), "//example.com/foo");
182    /// # Result::<(), oxiri::IriParseError>::Ok(())
183    /// ```
184    #[inline]
185    pub fn into_inner(self) -> T {
186        self.iri
187    }
188
189    /// Whether this IRI is an absolute IRI reference or not.
190    ///
191    /// ```
192    /// use oxiri::IriRef;
193    ///
194    /// assert!(IriRef::parse("http://example.com/foo")?.is_absolute());
195    /// assert!(!IriRef::parse("/foo")?.is_absolute());
196    /// # Result::<(), oxiri::IriParseError>::Ok(())
197    /// ```
198    #[inline]
199    pub fn is_absolute(&self) -> bool {
200        self.positions.scheme_end != 0
201    }
202
203    /// Returns the IRI scheme if it exists.
204    ///
205    /// Beware: the scheme case is not normalized. Use case insensitive comparisons if you look for a specific scheme.
206    /// ```
207    /// use oxiri::IriRef;
208    ///
209    /// let iri = IriRef::parse("hTTp://example.com")?;
210    /// assert_eq!(iri.scheme(), Some("hTTp"));
211    /// # Result::<(), oxiri::IriParseError>::Ok(())
212    /// ```
213    #[inline]
214    pub fn scheme(&self) -> Option<&str> {
215        if self.positions.scheme_end == 0 {
216            None
217        } else {
218            Some(&self.iri[..self.positions.scheme_end - 1])
219        }
220    }
221
222    /// Returns the IRI authority if it exists.
223    ///
224    /// Beware: the host case is not normalized. Use case insensitive comparisons if you look for a specific host.
225    ///
226    /// ```
227    /// use oxiri::IriRef;
228    ///
229    /// let http = IriRef::parse("http://foo:pass@example.com:80/my/path")?;
230    /// assert_eq!(http.authority(), Some("foo:pass@example.com:80"));
231    ///
232    /// let mailto = IriRef::parse("mailto:foo@bar.com")?;
233    /// assert_eq!(mailto.authority(), None);
234    /// # Result::<(), oxiri::IriParseError>::Ok(())
235    /// ```
236    #[inline]
237    pub fn authority(&self) -> Option<&str> {
238        if self.positions.scheme_end + 2 > self.positions.authority_end {
239            None
240        } else {
241            Some(&self.iri[self.positions.scheme_end + 2..self.positions.authority_end])
242        }
243    }
244
245    /// Returns the IRI path.
246    ///
247    /// ```
248    /// use oxiri::IriRef;
249    ///
250    /// let http = IriRef::parse("http://foo:pass@example.com:80/my/path?foo=bar")?;
251    /// assert_eq!(http.path(), "/my/path");
252    ///
253    /// let mailto = IriRef::parse("mailto:foo@bar.com")?;
254    /// assert_eq!(mailto.path(), "foo@bar.com");
255    /// # Result::<(), oxiri::IriParseError>::Ok(())
256    /// ```
257    #[inline]
258    pub fn path(&self) -> &str {
259        &self.iri[self.positions.authority_end..self.positions.path_end]
260    }
261
262    /// Returns the IRI query if it exists.
263    ///
264    /// ```
265    /// use oxiri::IriRef;
266    ///
267    /// let iri = IriRef::parse("http://example.com/my/path?query=foo#frag")?;
268    /// assert_eq!(iri.query(), Some("query=foo"));
269    /// # Result::<(), oxiri::IriParseError>::Ok(())
270    /// ```
271    #[inline]
272    pub fn query(&self) -> Option<&str> {
273        if self.positions.path_end >= self.positions.query_end {
274            None
275        } else {
276            Some(&self.iri[self.positions.path_end + 1..self.positions.query_end])
277        }
278    }
279
280    /// Returns the IRI fragment if it exists.
281    ///
282    /// ```
283    /// use oxiri::IriRef;
284    ///
285    /// let iri = IriRef::parse("http://example.com/my/path?query=foo#frag")?;
286    /// assert_eq!(iri.fragment(), Some("frag"));
287    /// # Result::<(), oxiri::IriParseError>::Ok(())
288    /// ```
289    #[inline]
290    pub fn fragment(&self) -> Option<&str> {
291        if self.positions.query_end >= self.iri.len() {
292            None
293        } else {
294            Some(&self.iri[self.positions.query_end + 1..])
295        }
296    }
297}
298
299impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<IriRef<Rhs>> for IriRef<Lft> {
300    #[inline]
301    fn eq(&self, other: &IriRef<Rhs>) -> bool {
302        self.iri.eq(&other.iri)
303    }
304}
305
306impl<T: PartialEq<str>> PartialEq<str> for IriRef<T> {
307    #[inline]
308    fn eq(&self, other: &str) -> bool {
309        self.iri.eq(other)
310    }
311}
312
313impl<'a, T: PartialEq<&'a str>> PartialEq<&'a str> for IriRef<T> {
314    #[inline]
315    fn eq(&self, other: &&'a str) -> bool {
316        self.iri.eq(other)
317    }
318}
319
320impl<T: PartialEq<String>> PartialEq<String> for IriRef<T> {
321    #[inline]
322    fn eq(&self, other: &String) -> bool {
323        self.iri.eq(other)
324    }
325}
326
327impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<Cow<'a, str>> for IriRef<T> {
328    #[inline]
329    fn eq(&self, other: &Cow<'a, str>) -> bool {
330        self.iri.eq(other)
331    }
332}
333
334impl<T: PartialEq<str>> PartialEq<IriRef<T>> for str {
335    #[inline]
336    fn eq(&self, other: &IriRef<T>) -> bool {
337        other.iri.eq(self)
338    }
339}
340
341impl<'a, T: PartialEq<&'a str>> PartialEq<IriRef<T>> for &'a str {
342    #[inline]
343    fn eq(&self, other: &IriRef<T>) -> bool {
344        other.iri.eq(self)
345    }
346}
347
348impl<T: PartialEq<String>> PartialEq<IriRef<T>> for String {
349    #[inline]
350    fn eq(&self, other: &IriRef<T>) -> bool {
351        other.iri.eq(self)
352    }
353}
354
355impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<IriRef<T>> for Cow<'a, str> {
356    #[inline]
357    fn eq(&self, other: &IriRef<T>) -> bool {
358        other.iri.eq(self)
359    }
360}
361
362impl<T: Eq> Eq for IriRef<T> {}
363
364impl<T: Hash> Hash for IriRef<T> {
365    #[inline]
366    fn hash<H: Hasher>(&self, state: &mut H) {
367        self.iri.hash(state)
368    }
369}
370
371impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<IriRef<Rhs>> for IriRef<Lft> {
372    #[inline]
373    fn partial_cmp(&self, other: &IriRef<Rhs>) -> Option<Ordering> {
374        self.iri.partial_cmp(&other.iri)
375    }
376}
377
378impl<T: Ord> Ord for IriRef<T> {
379    #[inline]
380    fn cmp(&self, other: &Self) -> Ordering {
381        self.iri.cmp(&other.iri)
382    }
383}
384
385impl<T: Deref<Target = str>> Deref for IriRef<T> {
386    type Target = str;
387
388    #[inline]
389    fn deref(&self) -> &str {
390        self.iri.deref()
391    }
392}
393
394impl<T: AsRef<str>> AsRef<str> for IriRef<T> {
395    #[inline]
396    fn as_ref(&self) -> &str {
397        self.iri.as_ref()
398    }
399}
400
401impl<T: Borrow<str>> Borrow<str> for IriRef<T> {
402    #[inline]
403    fn borrow(&self) -> &str {
404        self.iri.borrow()
405    }
406}
407
408impl<T: fmt::Debug> fmt::Debug for IriRef<T> {
409    #[inline]
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        self.iri.fmt(f)
412    }
413}
414
415impl<T: fmt::Display> fmt::Display for IriRef<T> {
416    #[inline]
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        self.iri.fmt(f)
419    }
420}
421
422impl FromStr for IriRef<String> {
423    type Err = IriParseError;
424
425    #[inline]
426    fn from_str(iri: &str) -> Result<Self, IriParseError> {
427        Self::parse(iri.to_owned())
428    }
429}
430
431impl<'a> From<IriRef<&'a str>> for IriRef<String> {
432    #[inline]
433    fn from(iri: IriRef<&'a str>) -> Self {
434        Self {
435            iri: iri.iri.into(),
436            positions: iri.positions,
437        }
438    }
439}
440
441impl<'a> From<IriRef<Cow<'a, str>>> for IriRef<String> {
442    #[inline]
443    fn from(iri: IriRef<Cow<'a, str>>) -> Self {
444        Self {
445            iri: iri.iri.into(),
446            positions: iri.positions,
447        }
448    }
449}
450
451impl From<IriRef<Box<str>>> for IriRef<String> {
452    #[inline]
453    fn from(iri: IriRef<Box<str>>) -> Self {
454        Self {
455            iri: iri.iri.into(),
456            positions: iri.positions,
457        }
458    }
459}
460
461impl<'a> From<IriRef<&'a str>> for IriRef<Cow<'a, str>> {
462    #[inline]
463    fn from(iri: IriRef<&'a str>) -> Self {
464        Self {
465            iri: iri.iri.into(),
466            positions: iri.positions,
467        }
468    }
469}
470
471impl From<IriRef<String>> for IriRef<Cow<'_, str>> {
472    #[inline]
473    fn from(iri: IriRef<String>) -> Self {
474        Self {
475            iri: iri.iri.into(),
476            positions: iri.positions,
477        }
478    }
479}
480
481impl<'a> From<&'a IriRef<String>> for IriRef<&'a str> {
482    #[inline]
483    fn from(iri: &'a IriRef<String>) -> Self {
484        Self {
485            iri: &iri.iri,
486            positions: iri.positions,
487        }
488    }
489}
490
491impl<'a> From<&'a IriRef<Cow<'a, str>>> for IriRef<&'a str> {
492    #[inline]
493    fn from(iri: &'a IriRef<Cow<'a, str>>) -> Self {
494        Self {
495            iri: &iri.iri,
496            positions: iri.positions,
497        }
498    }
499}
500
501#[cfg(feature = "serde")]
502impl<T: Serialize> Serialize for IriRef<T> {
503    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
504        self.iri.serialize(serializer)
505    }
506}
507
508#[cfg(feature = "serde")]
509impl<'de, T: Deref<Target = str> + Deserialize<'de>> Deserialize<'de> for IriRef<T> {
510    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
511        use serde::de::Error;
512
513        Self::parse(T::deserialize(deserializer)?).map_err(Error::custom)
514    }
515}
516
517/// A [RFC 3987](https://www.ietf.org/rfc/rfc3987.html) IRI.
518///
519/// Instances of this type are guaranteed to be absolute,
520/// unlike [`IriRef`].
521///
522/// ```
523/// use std::convert::TryFrom;
524/// use oxiri::{Iri, IriRef};
525///
526/// // Parse and validate base IRI
527/// let base_iri = Iri::parse("http://foo.com/bar/baz")?;
528///
529/// // Validate and resolve relative IRI
530/// let iri = base_iri.resolve("bat#foo")?;
531/// assert_eq!(iri.into_inner(), "http://foo.com/bar/bat#foo");
532///
533/// // Iri::parse will err on relative IRIs.
534/// assert!(Iri::parse("../bar/baz").is_err());
535///
536/// // It is possible to build an Iri from an IriRef object
537/// Iri::try_from(IriRef::parse("http://foo.com/bar")?)?;
538/// # Result::<(), oxiri::IriParseError>::Ok(())
539/// ```
540#[derive(Clone, Copy)]
541pub struct Iri<T>(IriRef<T>);
542
543impl<T: Deref<Target = str>> Iri<T> {
544    /// Parses and validates the IRI following the grammar from [RFC 3987](https://www.ietf.org/rfc/rfc3987.html).
545    ///
546    /// This operation keeps internally the `iri` parameter and does not allocate.
547    ///
548    /// Use [`parse_unchecked`](Self::parse_unchecked) if you already know the IRI is valid to get faster processing.
549    ///
550    /// ```
551    /// use oxiri::Iri;
552    ///
553    /// Iri::parse("http://foo.com/bar/baz")?;
554    /// # Result::<(), oxiri::IriParseError>::Ok(())
555    /// ```
556    pub fn parse(iri: T) -> Result<Self, IriParseError> {
557        IriRef::parse(iri)?.try_into()
558    }
559
560    /// Variant of [`parse`](Self::parse) that assumes that the IRI is valid to skip validation.
561    ///
562    /// ```
563    /// use oxiri::Iri;
564    ///
565    /// Iri::parse_unchecked("http://foo.com/bar/baz");
566    /// ```
567    pub fn parse_unchecked(iri: T) -> Self {
568        Iri(IriRef::parse_unchecked(iri))
569    }
570
571    /// Validates and resolved a relative IRI against the current IRI
572    /// following [RFC 3986](https://www.ietf.org/rfc/rfc3986.html) relative URI resolution algorithm.
573    ///
574    /// Use [`resolve_unchecked`](Self::resolve_unchecked) if you already know the IRI is valid to get faster processing.
575    ///
576    /// ```
577    /// use oxiri::Iri;
578    ///
579    /// let base_iri = Iri::parse("http://foo.com/bar/baz")?;
580    /// let iri = base_iri.resolve("bat#foo")?;
581    /// assert_eq!(iri.into_inner(), "http://foo.com/bar/bat#foo");
582    /// # Result::<(), oxiri::IriParseError>::Ok(())
583    /// ```
584    pub fn resolve(&self, iri: &str) -> Result<Iri<String>, IriParseError> {
585        Ok(Iri(self.0.resolve(iri)?))
586    }
587
588    /// Variant of [`resolve`](Self::resolve) that assumes that the IRI is valid to skip validation.
589    ///
590    /// ```
591    /// use oxiri::Iri;
592    ///
593    /// let base_iri = Iri::parse_unchecked("http://foo.com/bar/baz");
594    /// let iri = base_iri.resolve_unchecked("bat#foo");
595    /// assert_eq!(iri.into_inner(), "http://foo.com/bar/bat#foo");
596    /// ```
597    pub fn resolve_unchecked(&self, iri: &str) -> Iri<String> {
598        Iri(self.0.resolve_unchecked(iri))
599    }
600
601    /// Validates and resolved a relative IRI against the current IRI
602    /// following [RFC 3986](https://www.ietf.org/rfc/rfc3986.html) relative URI resolution algorithm.
603    ///
604    /// It outputs the resolved IRI into `target_buffer` to avoid any memory allocation.
605    ///
606    /// Use [`resolve_into_unchecked`](Self::resolve_into_unchecked) if you already know the IRI is valid to get faster processing.
607    ///
608    /// ```
609    /// use oxiri::Iri;
610    ///
611    /// let base_iri = Iri::parse("http://foo.com/bar/baz")?;
612    /// let mut result = String::default();
613    /// let iri = base_iri.resolve_into("bat#foo", &mut result)?;
614    /// assert_eq!(result, "http://foo.com/bar/bat#foo");
615    /// # Result::<(), oxiri::IriParseError>::Ok(())
616    /// ```
617    pub fn resolve_into(&self, iri: &str, target_buffer: &mut String) -> Result<(), IriParseError> {
618        self.0.resolve_into(iri, target_buffer)
619    }
620
621    /// Variant of [`resolve_into`](Self::resolve_into) that assumes that the IRI is valid to skip validation.
622    ///
623    /// ```
624    /// use oxiri::Iri;
625    ///
626    /// let base_iri = Iri::parse_unchecked("http://foo.com/bar/baz");
627    /// let mut result = String::default();
628    /// let iri = base_iri.resolve_into_unchecked("bat#foo", &mut result);
629    /// assert_eq!(result, "http://foo.com/bar/bat#foo");
630    /// ```
631    pub fn resolve_into_unchecked(&self, iri: &str, target_buffer: &mut String) {
632        self.0.resolve_into_unchecked(iri, target_buffer)
633    }
634
635    /// Returns an IRI that, when resolved against the current IRI returns `abs`.
636    ///
637    /// This function returns an error
638    /// if is not possible to build a relative IRI that can resolve to the same IRI.
639    /// For example, when the path contains `/../`.
640    ///
641    /// Note that the output of this function might change in minor releases.
642    ///
643    /// ```
644    /// use oxiri::Iri;
645    ///
646    /// let base_iri = Iri::parse("http://foo.com/bar/baz")?;
647    /// let iri = Iri::parse("http://foo.com/bar/bat#foo")?;
648    /// let relative_iri = base_iri.relativize(&iri)?;
649    /// assert_eq!(relative_iri, "bat#foo");
650    /// # Result::<(), Box<dyn std::error::Error>>::Ok(())
651    /// ```
652    pub fn relativize<T2: Deref<Target = str>>(
653        &self,
654        abs: &Iri<T2>,
655    ) -> Result<IriRef<String>, IriRelativizeError> {
656        let base = self;
657        let abs_authority = abs.authority();
658        let base_authority = base.authority();
659        let abs_path = abs.path();
660        let base_path = base.path();
661        let abs_query = abs.query();
662        let base_query = base.query();
663
664        // We validate the path, resolving algorithm eats /. and /.. in hierarchical path
665        for segment in abs_path.split('/') {
666            if matches!(segment, "." | "..") {
667                return Err(IriRelativizeError {});
668            }
669        }
670
671        if abs.scheme() != base.scheme()
672            || abs_authority.is_none()
673                && (base_authority.is_some()
674                    || abs_path.is_empty()
675                        && (!base_path.is_empty() || abs_query.is_none() && base_query.is_some()))
676            || abs_path
677                // Might confuse with a scheme
678                .split_once(':')
679                .map_or(false, |(candidate_scheme, _)| {
680                    !candidate_scheme.contains('/')
681                })
682        {
683            return Ok(IriRef {
684                iri: abs.0.to_string(),
685                positions: abs.0.positions,
686            });
687        }
688        if abs_authority != base_authority
689            // the resolution algorithm does not handle empty paths:
690            || abs_path.is_empty() && (!base_path.is_empty() || base_query.is_some())
691            // confusion with authority:
692            || abs_path.starts_with("//")
693        {
694            return Ok(IriRef {
695                iri: abs.0[abs.0.positions.scheme_end..].to_string(),
696                positions: IriElementsPositions {
697                    scheme_end: 0,
698                    authority_end: abs.0.positions.authority_end - abs.0.positions.scheme_end,
699                    path_end: abs.0.positions.path_end - abs.0.positions.scheme_end,
700                    query_end: abs.0.positions.query_end - abs.0.positions.scheme_end,
701                },
702            });
703        }
704        if abs_path != base_path || abs_query.is_none() && base_query.is_some() {
705            let number_of_shared_characters = abs_path
706                .chars()
707                .zip(base_path.chars())
708                .take_while(|(l, r)| l == r)
709                .map(|(l, _)| l.len_utf8())
710                .sum::<usize>();
711            // We decrease until finding a /
712            let number_of_shared_characters = abs_path[..number_of_shared_characters]
713                .rfind('/')
714                .map_or(0, |n| n + 1);
715            return if abs_path[number_of_shared_characters..].starts_with('/')
716                || base_path[number_of_shared_characters..].contains('/')
717                || abs_path[number_of_shared_characters..].contains(':')
718            {
719                // We output the full path because we have a / or an empty end
720                if !abs_path.starts_with('/') && !base_path.is_empty() {
721                    // We output the full IRI because we have a path that does not start with /,
722                    // so it can't be considered as absolute
723                    Ok(IriRef {
724                        iri: abs.0.to_string(),
725                        positions: abs.0.positions,
726                    })
727                } else {
728                    Ok(IriRef {
729                        iri: abs.0[abs.0.positions.authority_end..].to_string(),
730                        positions: IriElementsPositions {
731                            scheme_end: 0,
732                            authority_end: 0,
733                            path_end: abs.0.positions.path_end - abs.0.positions.authority_end,
734                            query_end: abs.0.positions.query_end - abs.0.positions.authority_end,
735                        },
736                    })
737                }
738            } else if abs_path[number_of_shared_characters..].is_empty() {
739                // We use "."
740                let mut iri = String::with_capacity(
741                    abs.0.len() - abs.0.positions.authority_end - number_of_shared_characters + 1,
742                );
743                iri.push('.');
744                iri.push_str(&abs.0[abs.0.positions.authority_end + number_of_shared_characters..]);
745                Ok(IriRef {
746                    iri,
747                    positions: IriElementsPositions {
748                        scheme_end: 0,
749                        authority_end: 0,
750                        path_end: abs.0.positions.path_end
751                            - abs.0.positions.authority_end
752                            - number_of_shared_characters
753                            + 1,
754                        query_end: abs.0.positions.query_end
755                            - abs.0.positions.authority_end
756                            - number_of_shared_characters
757                            + 1,
758                    },
759                })
760            } else {
761                // We just override the last element
762                Ok(IriRef {
763                    iri: abs.0[abs.0.positions.authority_end + number_of_shared_characters..]
764                        .to_string(),
765                    positions: IriElementsPositions {
766                        scheme_end: 0,
767                        authority_end: 0,
768                        path_end: abs.0.positions.path_end
769                            - abs.0.positions.authority_end
770                            - number_of_shared_characters,
771                        query_end: abs.0.positions.query_end
772                            - abs.0.positions.authority_end
773                            - number_of_shared_characters,
774                    },
775                })
776            };
777        }
778        if abs_query != base_query {
779            return Ok(IriRef {
780                iri: abs.0[abs.0.positions.path_end..].to_string(),
781                positions: IriElementsPositions {
782                    scheme_end: 0,
783                    authority_end: 0,
784                    path_end: 0,
785                    query_end: abs.0.positions.query_end - abs.0.positions.path_end,
786                },
787            });
788        }
789        Ok(IriRef {
790            iri: abs.0[abs.0.positions.query_end..].to_string(),
791            positions: IriElementsPositions {
792                scheme_end: 0,
793                authority_end: 0,
794                path_end: 0,
795                query_end: 0,
796            },
797        })
798    }
799
800    /// Returns an IRI borrowing this IRI's text
801    #[inline]
802    pub fn as_ref(&self) -> Iri<&str> {
803        Iri(self.0.as_ref())
804    }
805
806    /// Returns the underlying IRI representation.
807    ///
808    /// ```
809    /// use oxiri::Iri;
810    ///
811    /// let iri = Iri::parse("http://example.com/foo")?;
812    /// assert_eq!(iri.as_str(), "http://example.com/foo");
813    /// # Result::<(), oxiri::IriParseError>::Ok(())
814    /// ```
815    #[inline]
816    pub fn as_str(&self) -> &str {
817        self.0.as_str()
818    }
819
820    /// Returns the underlying IRI representation.
821    ///
822    /// ```
823    /// use oxiri::Iri;
824    ///
825    /// let iri = Iri::parse("http://example.com/foo")?;
826    /// assert_eq!(iri.into_inner(), "http://example.com/foo");
827    /// # Result::<(), oxiri::IriParseError>::Ok(())
828    /// ```
829    #[inline]
830    pub fn into_inner(self) -> T {
831        self.0.into_inner()
832    }
833
834    /// Returns the IRI scheme.
835    ///
836    /// Beware: the scheme case is not normalized. Use case insensitive comparisons if you look for a specific scheme.
837    ///
838    /// ```
839    /// use oxiri::Iri;
840    ///
841    /// let iri = Iri::parse("hTTp://example.com")?;
842    /// assert_eq!(iri.scheme(), "hTTp");
843    /// # Result::<(), oxiri::IriParseError>::Ok(())
844    /// ```
845    #[inline]
846    pub fn scheme(&self) -> &str {
847        self.0.scheme().expect("The IRI should be absolute")
848    }
849
850    /// Returns the IRI authority if it exists.
851    ///
852    /// Beware: the host case is not normalized. Use case insensitive comparisons if you look for a specific host.
853    ///
854    /// ```
855    /// use oxiri::Iri;
856    ///
857    /// let http = Iri::parse("http://foo:pass@example.com:80/my/path")?;
858    /// assert_eq!(http.authority(), Some("foo:pass@example.com:80"));
859    ///
860    /// let mailto = Iri::parse("mailto:foo@bar.com")?;
861    /// assert_eq!(mailto.authority(), None);
862    /// # Result::<(), oxiri::IriParseError>::Ok(())
863    /// ```
864    #[inline]
865    pub fn authority(&self) -> Option<&str> {
866        self.0.authority()
867    }
868
869    /// Returns the IRI path.
870    ///
871    /// ```
872    /// use oxiri::Iri;
873    ///
874    /// let http = Iri::parse("http://foo:pass@example.com:80/my/path?foo=bar")?;
875    /// assert_eq!(http.path(), "/my/path");
876    ///
877    /// let mailto = Iri::parse("mailto:foo@bar.com")?;
878    /// assert_eq!(mailto.path(), "foo@bar.com");
879    /// # Result::<(), oxiri::IriParseError>::Ok(())
880    /// ```
881    #[inline]
882    pub fn path(&self) -> &str {
883        self.0.path()
884    }
885
886    /// Returns the IRI query if it exists.
887    ///
888    /// ```
889    /// use oxiri::Iri;
890    ///
891    /// let iri = Iri::parse("http://example.com/my/path?query=foo#frag")?;
892    /// assert_eq!(iri.query(), Some("query=foo"));
893    /// # Result::<(), oxiri::IriParseError>::Ok(())
894    /// ```
895    #[inline]
896    pub fn query(&self) -> Option<&str> {
897        self.0.query()
898    }
899
900    /// Returns the IRI fragment if it exists.
901    ///
902    /// ```
903    /// use oxiri::Iri;
904    ///
905    /// let iri = Iri::parse("http://example.com/my/path?query=foo#frag")?;
906    /// assert_eq!(iri.fragment(), Some("frag"));
907    /// # Result::<(), oxiri::IriParseError>::Ok(())
908    /// ```
909    #[inline]
910    pub fn fragment(&self) -> Option<&str> {
911        self.0.fragment()
912    }
913}
914
915impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<Iri<Rhs>> for Iri<Lft> {
916    #[inline]
917    fn eq(&self, other: &Iri<Rhs>) -> bool {
918        self.0.eq(&other.0)
919    }
920}
921
922impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<IriRef<Rhs>> for Iri<Lft> {
923    #[inline]
924    fn eq(&self, other: &IriRef<Rhs>) -> bool {
925        self.0.eq(other)
926    }
927}
928
929impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<Iri<Rhs>> for IriRef<Lft> {
930    #[inline]
931    fn eq(&self, other: &Iri<Rhs>) -> bool {
932        self.eq(&other.0)
933    }
934}
935
936impl<T: PartialEq<str>> PartialEq<str> for Iri<T> {
937    #[inline]
938    fn eq(&self, other: &str) -> bool {
939        self.0.eq(other)
940    }
941}
942
943impl<'a, T: PartialEq<&'a str>> PartialEq<&'a str> for Iri<T> {
944    #[inline]
945    fn eq(&self, other: &&'a str) -> bool {
946        self.0.eq(other)
947    }
948}
949
950impl<T: PartialEq<String>> PartialEq<String> for Iri<T> {
951    #[inline]
952    fn eq(&self, other: &String) -> bool {
953        self.0.eq(other)
954    }
955}
956
957impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<Cow<'a, str>> for Iri<T> {
958    #[inline]
959    fn eq(&self, other: &Cow<'a, str>) -> bool {
960        self.0.eq(other)
961    }
962}
963
964impl<T: PartialEq<str>> PartialEq<Iri<T>> for str {
965    #[inline]
966    fn eq(&self, other: &Iri<T>) -> bool {
967        self.eq(&other.0)
968    }
969}
970
971impl<'a, T: PartialEq<&'a str>> PartialEq<Iri<T>> for &'a str {
972    #[inline]
973    fn eq(&self, other: &Iri<T>) -> bool {
974        self.eq(&other.0)
975    }
976}
977
978impl<T: PartialEq<String>> PartialEq<Iri<T>> for String {
979    #[inline]
980    fn eq(&self, other: &Iri<T>) -> bool {
981        self.eq(&other.0)
982    }
983}
984
985impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<Iri<T>> for Cow<'a, str> {
986    #[inline]
987    fn eq(&self, other: &Iri<T>) -> bool {
988        self.eq(&other.0)
989    }
990}
991
992impl<T: Eq> Eq for Iri<T> {}
993
994impl<T: Hash> Hash for Iri<T> {
995    #[inline]
996    fn hash<H: Hasher>(&self, state: &mut H) {
997        self.0.hash(state)
998    }
999}
1000
1001impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<Iri<Rhs>> for Iri<Lft> {
1002    #[inline]
1003    fn partial_cmp(&self, other: &Iri<Rhs>) -> Option<Ordering> {
1004        self.0.partial_cmp(&other.0)
1005    }
1006}
1007
1008impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<IriRef<Rhs>> for Iri<Lft> {
1009    #[inline]
1010    fn partial_cmp(&self, other: &IriRef<Rhs>) -> Option<Ordering> {
1011        self.0.partial_cmp(other)
1012    }
1013}
1014
1015impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<Iri<Rhs>> for IriRef<Lft> {
1016    #[inline]
1017    fn partial_cmp(&self, other: &Iri<Rhs>) -> Option<Ordering> {
1018        self.partial_cmp(&other.0)
1019    }
1020}
1021
1022impl<T: Ord> Ord for Iri<T> {
1023    #[inline]
1024    fn cmp(&self, other: &Self) -> Ordering {
1025        self.0.cmp(&other.0)
1026    }
1027}
1028
1029impl<T: Deref<Target = str>> Deref for Iri<T> {
1030    type Target = str;
1031
1032    #[inline]
1033    fn deref(&self) -> &str {
1034        self.0.deref()
1035    }
1036}
1037
1038impl<T: AsRef<str>> AsRef<str> for Iri<T> {
1039    #[inline]
1040    fn as_ref(&self) -> &str {
1041        self.0.as_ref()
1042    }
1043}
1044
1045impl<T: Borrow<str>> Borrow<str> for Iri<T> {
1046    #[inline]
1047    fn borrow(&self) -> &str {
1048        self.0.borrow()
1049    }
1050}
1051
1052impl<T: fmt::Debug> fmt::Debug for Iri<T> {
1053    #[inline]
1054    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1055        self.0.fmt(f)
1056    }
1057}
1058
1059impl<T: fmt::Display> fmt::Display for Iri<T> {
1060    #[inline]
1061    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1062        self.0.fmt(f)
1063    }
1064}
1065
1066impl FromStr for Iri<String> {
1067    type Err = IriParseError;
1068
1069    #[inline]
1070    fn from_str(iri: &str) -> Result<Self, IriParseError> {
1071        Self::parse(iri.to_owned())
1072    }
1073}
1074
1075impl<'a> From<Iri<&'a str>> for Iri<String> {
1076    #[inline]
1077    fn from(iri: Iri<&'a str>) -> Self {
1078        Self(iri.0.into())
1079    }
1080}
1081
1082impl<'a> From<Iri<Cow<'a, str>>> for Iri<String> {
1083    #[inline]
1084    fn from(iri: Iri<Cow<'a, str>>) -> Self {
1085        Self(iri.0.into())
1086    }
1087}
1088
1089impl From<Iri<Box<str>>> for Iri<String> {
1090    #[inline]
1091    fn from(iri: Iri<Box<str>>) -> Self {
1092        Self(iri.0.into())
1093    }
1094}
1095
1096impl<'a> From<Iri<&'a str>> for Iri<Cow<'a, str>> {
1097    #[inline]
1098    fn from(iri: Iri<&'a str>) -> Self {
1099        Self(iri.0.into())
1100    }
1101}
1102
1103impl From<Iri<String>> for Iri<Cow<'_, str>> {
1104    #[inline]
1105    fn from(iri: Iri<String>) -> Self {
1106        Self(iri.0.into())
1107    }
1108}
1109
1110impl<'a> From<&'a Iri<String>> for Iri<&'a str> {
1111    #[inline]
1112    fn from(iri: &'a Iri<String>) -> Self {
1113        Self(iri.0.as_ref())
1114    }
1115}
1116
1117impl<'a> From<&'a Iri<Cow<'a, str>>> for Iri<&'a str> {
1118    #[inline]
1119    fn from(iri: &'a Iri<Cow<'a, str>>) -> Self {
1120        Self(iri.0.as_ref())
1121    }
1122}
1123
1124impl<T: Deref<Target = str>> From<Iri<T>> for IriRef<T> {
1125    fn from(iri: Iri<T>) -> Self {
1126        iri.0
1127    }
1128}
1129
1130impl<T: Deref<Target = str>> TryFrom<IriRef<T>> for Iri<T> {
1131    type Error = IriParseError;
1132
1133    fn try_from(iri: IriRef<T>) -> Result<Self, IriParseError> {
1134        if iri.is_absolute() {
1135            Ok(Self(iri))
1136        } else {
1137            Err(IriParseError {
1138                kind: IriParseErrorKind::NoScheme,
1139            })
1140        }
1141    }
1142}
1143
1144#[cfg(feature = "serde")]
1145impl<T: Serialize> Serialize for Iri<T> {
1146    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1147        self.0.serialize(serializer)
1148    }
1149}
1150
1151#[cfg(feature = "serde")]
1152impl<'de, T: Deref<Target = str> + Deserialize<'de>> Deserialize<'de> for Iri<T> {
1153    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1154        use serde::de::Error;
1155        IriRef::deserialize(deserializer)?
1156            .try_into()
1157            .map_err(Error::custom)
1158    }
1159}
1160
1161/// An error raised during [`Iri`] or [`IriRef`] validation.
1162#[derive(Debug)]
1163pub struct IriParseError {
1164    kind: IriParseErrorKind,
1165}
1166
1167impl fmt::Display for IriParseError {
1168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1169        match &self.kind {
1170            IriParseErrorKind::NoScheme => write!(f, "No scheme found in an absolute IRI"),
1171            IriParseErrorKind::InvalidHostCharacter(c) => {
1172                write!(f, "Invalid character '{c}' in host")
1173            }
1174            IriParseErrorKind::InvalidHostIp(e) => write!(f, "Invalid host IP ({e})"),
1175            IriParseErrorKind::InvalidPortCharacter(c) => write!(f, "Invalid character '{c}'"),
1176            IriParseErrorKind::InvalidIriCodePoint(c) => {
1177                write!(f, "Invalid IRI code point '{c}'")
1178            }
1179            IriParseErrorKind::InvalidPercentEncoding(cs) => write!(
1180                f,
1181                "Invalid IRI percent encoding '{}'",
1182                cs.iter().flatten().cloned().collect::<String>()
1183            ),
1184            IriParseErrorKind::PathStartingWithTwoSlashes => {
1185                write!(f, "An IRI path is not allowed to start with //")
1186            }
1187        }
1188    }
1189}
1190
1191impl Error for IriParseError {
1192    fn source(&self) -> Option<&(dyn Error + 'static)> {
1193        if let IriParseErrorKind::InvalidHostIp(e) = &self.kind {
1194            Some(e)
1195        } else {
1196            None
1197        }
1198    }
1199}
1200
1201#[derive(Debug)]
1202enum IriParseErrorKind {
1203    NoScheme,
1204    InvalidHostCharacter(char),
1205    InvalidHostIp(AddrParseError),
1206    InvalidPortCharacter(char),
1207    InvalidIriCodePoint(char),
1208    InvalidPercentEncoding([Option<char>; 3]),
1209    PathStartingWithTwoSlashes,
1210}
1211
1212/// An error raised when calling [`Iri::relativize`].
1213///
1214/// It can happen when it is not possible to build a relative IRI that can resolve to the same IRI.
1215/// For example, when the path contains `/../`.
1216#[derive(Debug)]
1217pub struct IriRelativizeError {}
1218
1219impl fmt::Display for IriRelativizeError {
1220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1221        write!(
1222            f,
1223            "It is not possible to make this IRI relative because it contains `/..` or `/.`"
1224        )
1225    }
1226}
1227
1228impl Error for IriRelativizeError {}
1229
1230#[derive(Debug, Clone, Copy)]
1231struct IriElementsPositions {
1232    scheme_end: usize,
1233    authority_end: usize,
1234    path_end: usize,
1235    query_end: usize,
1236}
1237
1238trait OutputBuffer {
1239    fn push(&mut self, c: char);
1240
1241    fn push_str(&mut self, s: &str);
1242
1243    fn clear(&mut self);
1244
1245    fn truncate(&mut self, new_len: usize);
1246
1247    fn len(&self) -> usize;
1248
1249    fn as_str(&self) -> &str;
1250}
1251
1252#[derive(Default)]
1253struct VoidOutputBuffer {
1254    len: usize,
1255}
1256
1257impl OutputBuffer for VoidOutputBuffer {
1258    #[inline]
1259    fn push(&mut self, c: char) {
1260        self.len += c.len_utf8();
1261    }
1262
1263    #[inline]
1264    fn push_str(&mut self, s: &str) {
1265        self.len += s.len();
1266    }
1267
1268    #[inline]
1269    fn clear(&mut self) {
1270        self.len = 0;
1271    }
1272
1273    #[inline]
1274    fn truncate(&mut self, new_len: usize) {
1275        self.len = new_len;
1276    }
1277
1278    #[inline]
1279    fn len(&self) -> usize {
1280        self.len
1281    }
1282
1283    #[inline]
1284    fn as_str(&self) -> &str {
1285        ""
1286    }
1287}
1288
1289impl OutputBuffer for String {
1290    #[inline]
1291    fn push(&mut self, c: char) {
1292        self.push(c);
1293    }
1294
1295    #[inline]
1296    fn push_str(&mut self, s: &str) {
1297        self.push_str(s);
1298    }
1299
1300    #[inline]
1301    fn clear(&mut self) {
1302        self.clear();
1303    }
1304
1305    #[inline]
1306    fn truncate(&mut self, new_len: usize) {
1307        self.truncate(new_len);
1308    }
1309
1310    #[inline]
1311    fn len(&self) -> usize {
1312        self.len()
1313    }
1314
1315    #[inline]
1316    fn as_str(&self) -> &str {
1317        self.as_str()
1318    }
1319}
1320
1321struct ParserInput<'a> {
1322    value: Chars<'a>,
1323    position: usize,
1324}
1325
1326impl ParserInput<'_> {
1327    #[inline]
1328    fn next(&mut self) -> Option<char> {
1329        if let Some(head) = self.value.next() {
1330            self.position += head.len_utf8();
1331            Some(head)
1332        } else {
1333            None
1334        }
1335    }
1336
1337    #[inline]
1338    fn front(&self) -> Option<char> {
1339        self.value.clone().next()
1340    }
1341
1342    #[inline]
1343    fn starts_with(&self, c: char) -> bool {
1344        self.value.as_str().starts_with(c)
1345    }
1346}
1347
1348/// parser implementing https://url.spec.whatwg.org/#concept-basic-url-parser without the normalization or backward compatibility bits to comply with RFC 3987
1349///
1350/// A sub function takes care of each state
1351struct IriParser<'a, O: OutputBuffer, const UNCHECKED: bool> {
1352    iri: &'a str,
1353    base: Option<IriRef<&'a str>>,
1354    input: ParserInput<'a>,
1355    output: &'a mut O,
1356    output_positions: IriElementsPositions,
1357    input_scheme_end: usize,
1358}
1359
1360impl<'a, O: OutputBuffer, const UNCHECKED: bool> IriParser<'a, O, UNCHECKED> {
1361    fn parse(
1362        iri: &'a str,
1363        base: Option<IriRef<&'a str>>,
1364        output: &'a mut O,
1365    ) -> Result<IriElementsPositions, IriParseError> {
1366        let mut parser = Self {
1367            iri,
1368            base,
1369            input: ParserInput {
1370                value: iri.chars(),
1371                position: 0,
1372            },
1373            output,
1374            output_positions: IriElementsPositions {
1375                scheme_end: 0,
1376                authority_end: 0,
1377                path_end: 0,
1378                query_end: 0,
1379            },
1380            input_scheme_end: 0,
1381        };
1382        parser.parse_scheme_start()?;
1383        Ok(parser.output_positions)
1384    }
1385
1386    fn parse_scheme_start(&mut self) -> Result<(), IriParseError> {
1387        match self.input.front() {
1388            Some(':') => {
1389                if UNCHECKED {
1390                    self.parse_scheme()
1391                } else {
1392                    self.parse_error(IriParseErrorKind::NoScheme)
1393                }
1394            }
1395            Some(c) if c.is_ascii_alphabetic() => self.parse_scheme(),
1396            _ => self.parse_relative(),
1397        }
1398    }
1399
1400    fn parse_scheme(&mut self) -> Result<(), IriParseError> {
1401        loop {
1402            let c = self.input.next();
1403            match c {
1404                Some(c) if c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.' => {
1405                    self.output.push(c)
1406                }
1407                Some(':') => {
1408                    self.output.push(':');
1409                    self.output_positions.scheme_end = self.output.len();
1410                    self.input_scheme_end = self.input.position;
1411                    return if self.input.starts_with('/') {
1412                        self.input.next();
1413                        self.output.push('/');
1414                        self.parse_path_or_authority()
1415                    } else {
1416                        self.output_positions.authority_end = self.output.len();
1417                        self.parse_path::<false>()
1418                    };
1419                }
1420                _ => {
1421                    self.input = ParserInput {
1422                        value: self.iri.chars(),
1423                        position: 0,
1424                    }; // reset
1425                    self.output.clear();
1426                    return self.parse_relative();
1427                }
1428            }
1429        }
1430    }
1431
1432    fn parse_path_or_authority(&mut self) -> Result<(), IriParseError> {
1433        if self.input.starts_with('/') {
1434            self.input.next();
1435            self.output.push('/');
1436            self.parse_authority()
1437        } else {
1438            self.output_positions.authority_end = self.output.len() - 1;
1439            self.parse_path::<false>()
1440        }
1441    }
1442
1443    fn parse_relative(&mut self) -> Result<(), IriParseError> {
1444        if let Some(base) = self.base {
1445            match self.input.front() {
1446                None => {
1447                    self.output.push_str(&base.iri[..base.positions.query_end]);
1448                    self.output_positions.scheme_end = base.positions.scheme_end;
1449                    self.output_positions.authority_end = base.positions.authority_end;
1450                    self.output_positions.path_end = base.positions.path_end;
1451                    self.output_positions.query_end = base.positions.query_end;
1452                    Ok(())
1453                }
1454                Some('/') => {
1455                    self.input.next();
1456                    self.parse_relative_slash(&base)
1457                }
1458                Some('?') => {
1459                    self.input.next();
1460                    self.output.push_str(&base.iri[..base.positions.path_end]);
1461                    self.output.push('?');
1462                    self.output_positions.scheme_end = base.positions.scheme_end;
1463                    self.output_positions.authority_end = base.positions.authority_end;
1464                    self.output_positions.path_end = base.positions.path_end;
1465                    self.parse_query()
1466                }
1467                Some('#') => {
1468                    self.input.next();
1469                    self.output.push_str(&base.iri[..base.positions.query_end]);
1470                    self.output_positions.scheme_end = base.positions.scheme_end;
1471                    self.output_positions.authority_end = base.positions.authority_end;
1472                    self.output_positions.path_end = base.positions.path_end;
1473                    self.output_positions.query_end = base.positions.query_end;
1474                    self.output.push('#');
1475                    self.parse_fragment()
1476                }
1477                _ => {
1478                    self.output.push_str(&base.iri[..base.positions.path_end]);
1479                    self.output_positions.scheme_end = base.positions.scheme_end;
1480                    self.output_positions.authority_end = base.positions.authority_end;
1481                    self.output_positions.path_end = base.positions.path_end;
1482                    self.remove_last_segment();
1483                    self.parse_relative_path::<true>()
1484                }
1485            }
1486        } else {
1487            self.output_positions.scheme_end = 0;
1488            self.input_scheme_end = 0;
1489            if self.input.starts_with('/') {
1490                self.input.next();
1491                self.output.push('/');
1492                self.parse_path_or_authority()
1493            } else {
1494                self.output_positions.authority_end = 0;
1495                self.parse_relative_path::<false>()
1496            }
1497        }
1498    }
1499
1500    fn parse_relative_path<const REMOVE_DOT_SEGMENTS: bool>(
1501        &mut self,
1502    ) -> Result<(), IriParseError> {
1503        while let Some(c) = self.input.front() {
1504            if matches!(c, '/' | '?' | '#') {
1505                break;
1506            }
1507            self.input.next();
1508            self.read_url_codepoint_or_echar(c, |c| is_iunreserved_or_sub_delims(c) || c == '@')?;
1509        }
1510        self.parse_path::<REMOVE_DOT_SEGMENTS>()
1511    }
1512
1513    fn parse_relative_slash(&mut self, base: &IriRef<&'a str>) -> Result<(), IriParseError> {
1514        if self.input.starts_with('/') {
1515            self.input.next();
1516            self.output.push_str(&base.iri[..base.positions.scheme_end]);
1517            self.output_positions.scheme_end = base.positions.scheme_end;
1518            self.output.push('/');
1519            self.output.push('/');
1520            self.parse_authority()
1521        } else {
1522            self.output
1523                .push_str(&base.iri[..base.positions.authority_end]);
1524            self.output.push('/');
1525            self.output_positions.scheme_end = base.positions.scheme_end;
1526            self.output_positions.authority_end = base.positions.authority_end;
1527            self.parse_path::<true>()
1528        }
1529    }
1530
1531    fn parse_authority(&mut self) -> Result<(), IriParseError> {
1532        // @ are not allowed in IRI authorities so not need to take care of ambiguities
1533        loop {
1534            let c = self.input.next();
1535            match c {
1536                Some('@') => {
1537                    self.output.push('@');
1538                    return self.parse_host();
1539                }
1540                None | Some('[') | Some('/') | Some('?') | Some('#') => {
1541                    self.input = ParserInput {
1542                        value: self.iri[self.input_scheme_end + 2..].chars(),
1543                        position: self.input_scheme_end + 2,
1544                    };
1545                    self.output.truncate(self.output_positions.scheme_end + 2);
1546                    return self.parse_host();
1547                }
1548                Some(c) => {
1549                    self.read_url_codepoint_or_echar(c, |c| {
1550                        is_iunreserved_or_sub_delims(c) || c == ':'
1551                    })?;
1552                }
1553            }
1554        }
1555    }
1556
1557    fn parse_host(&mut self) -> Result<(), IriParseError> {
1558        if self.input.starts_with('[') {
1559            // IP v6
1560            let start_position = self.input.position;
1561            while let Some(c) = self.input.next() {
1562                self.output.push(c);
1563                if c == ']' {
1564                    let ip = &self.iri[start_position + 1..self.input.position - 1];
1565                    if !UNCHECKED {
1566                        if ip.starts_with('v') || ip.starts_with('V') {
1567                            self.validate_ip_v_future(ip)?;
1568                        } else if let Err(error) = Ipv6Addr::from_str(ip) {
1569                            return self.parse_error(IriParseErrorKind::InvalidHostIp(error));
1570                        }
1571                    }
1572
1573                    let c = self.input.next();
1574                    return match c {
1575                        Some(':') => {
1576                            self.output.push(':');
1577                            self.parse_port()
1578                        }
1579                        None | Some('/') | Some('?') | Some('#') => {
1580                            self.output_positions.authority_end = self.output.len();
1581                            self.parse_path_start(c)
1582                        }
1583                        Some(c) => {
1584                            if UNCHECKED {
1585                                self.output.push(c);
1586                                continue;
1587                            } else {
1588                                self.parse_error(IriParseErrorKind::InvalidHostCharacter(c))
1589                            }
1590                        }
1591                    };
1592                }
1593            }
1594            if UNCHECKED {
1595                // We consider it's valid even if it's not finished
1596                self.output_positions.authority_end = self.output.len();
1597                self.parse_path_start(None)
1598            } else {
1599                self.parse_error(IriParseErrorKind::InvalidHostCharacter('['))
1600            }
1601        } else {
1602            // Other host
1603            loop {
1604                let c = self.input.next();
1605                match c {
1606                    Some(':') => {
1607                        self.output.push(':');
1608                        return self.parse_port();
1609                    }
1610                    None | Some('/') | Some('?') | Some('#') => {
1611                        self.output_positions.authority_end = self.output.len();
1612                        return self.parse_path_start(c);
1613                    }
1614                    Some(c) => self.read_url_codepoint_or_echar(c, is_iunreserved_or_sub_delims)?,
1615                }
1616            }
1617        }
1618    }
1619
1620    fn parse_port(&mut self) -> Result<(), IriParseError> {
1621        loop {
1622            let c = self.input.next();
1623            match c {
1624                Some('/') | Some('?') | Some('#') | None => {
1625                    self.output_positions.authority_end = self.output.len();
1626                    return self.parse_path_start(c);
1627                }
1628                Some(c) => {
1629                    if UNCHECKED || c.is_ascii_digit() {
1630                        self.output.push(c)
1631                    } else {
1632                        return self.parse_error(IriParseErrorKind::InvalidPortCharacter(c));
1633                    }
1634                }
1635            }
1636        }
1637    }
1638
1639    fn parse_path_start(&mut self, c: Option<char>) -> Result<(), IriParseError> {
1640        match c {
1641            None => {
1642                self.output_positions.path_end = self.output.len();
1643                self.output_positions.query_end = self.output.len();
1644                Ok(())
1645            }
1646            Some('?') => {
1647                self.output_positions.path_end = self.output.len();
1648                self.output.push('?');
1649                self.parse_query()
1650            }
1651            Some('#') => {
1652                self.output_positions.path_end = self.output.len();
1653                self.output_positions.query_end = self.output.len();
1654                self.output.push('#');
1655                self.parse_fragment()
1656            }
1657            Some('/') => {
1658                self.output.push('/');
1659                self.parse_path::<false>()
1660            }
1661            Some(c) => {
1662                self.read_url_codepoint_or_echar(c, |c| {
1663                    is_iunreserved_or_sub_delims(c) || matches!(c, ':' | '@')
1664                })?;
1665                self.parse_path::<false>()
1666            }
1667        }
1668    }
1669
1670    fn parse_path<const REMOVE_DOT_SEGMENTS: bool>(&mut self) -> Result<(), IriParseError> {
1671        loop {
1672            let c = self.input.next();
1673            match c {
1674                None | Some('/') | Some('?') | Some('#') => {
1675                    let output_str = self.output.as_str();
1676                    if REMOVE_DOT_SEGMENTS {
1677                        let output_path = &output_str[self.output_positions.authority_end..];
1678                        if output_path.ends_with("/..") {
1679                            self.output.truncate(self.output.len() - 3);
1680                            self.remove_last_segment();
1681                        } else if output_path.ends_with("/.") || output_path == "." {
1682                            self.output.truncate(self.output.len() - 1);
1683                        } else if output_path == ".." {
1684                            self.output.truncate(self.output.len() - 2);
1685                        } else if c == Some('/') {
1686                            self.output.push('/');
1687                            continue;
1688                        }
1689                    } else if c == Some('/') {
1690                        self.output.push('/');
1691                        continue;
1692                    }
1693
1694                    // We validate that the path does not start with "//" and is ambiguous with an authority
1695                    // It can happen after resolving ./ and ../
1696                    let output_str = self.output.as_str();
1697                    if REMOVE_DOT_SEGMENTS
1698                        && !UNCHECKED
1699                        && output_str[self.output_positions.authority_end..].starts_with("//")
1700                        && self.output_positions.authority_end == self.output_positions.scheme_end
1701                    {
1702                        return self.parse_error(IriParseErrorKind::PathStartingWithTwoSlashes);
1703                    }
1704
1705                    if c == Some('?') {
1706                        self.output_positions.path_end = self.output.len();
1707                        self.output.push('?');
1708                        return self.parse_query();
1709                    } else if c == Some('#') {
1710                        self.output_positions.path_end = self.output.len();
1711                        self.output_positions.query_end = self.output.len();
1712                        self.output.push('#');
1713                        return self.parse_fragment();
1714                    } else if c.is_none() {
1715                        self.output_positions.path_end = self.output.len();
1716                        self.output_positions.query_end = self.output.len();
1717                        return Ok(());
1718                    }
1719                }
1720                Some(c) => self.read_url_codepoint_or_echar(c, |c| {
1721                    is_iunreserved_or_sub_delims(c) || matches!(c, ':' | '@')
1722                })?,
1723            }
1724        }
1725    }
1726
1727    fn parse_query(&mut self) -> Result<(), IriParseError> {
1728        while let Some(c) = self.input.next() {
1729            if c == '#' {
1730                self.output_positions.query_end = self.output.len();
1731                self.output.push('#');
1732                return self.parse_fragment();
1733            } else {
1734                self.read_url_codepoint_or_echar(c, |c| {
1735                    is_iunreserved_or_sub_delims(c) || matches!(c, ':' | '@' | '/' | '?' | '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}')
1736                })?
1737            }
1738        }
1739        self.output_positions.query_end = self.output.len();
1740        Ok(())
1741    }
1742
1743    fn parse_fragment(&mut self) -> Result<(), IriParseError> {
1744        while let Some(c) = self.input.next() {
1745            self.read_url_codepoint_or_echar(c, |c| {
1746                is_iunreserved_or_sub_delims(c) || matches!(c, ':' | '@' | '/' | '?')
1747            })?;
1748        }
1749        Ok(())
1750    }
1751
1752    fn remove_last_segment(&mut self) {
1753        if let Some(last_slash_position) =
1754            self.output.as_str()[self.output_positions.authority_end..].rfind('/')
1755        {
1756            self.output
1757                .truncate(last_slash_position + self.output_positions.authority_end);
1758            self.output.push('/');
1759        } else {
1760            self.output.truncate(self.output_positions.authority_end);
1761            if self.output_positions.authority_end > self.output_positions.scheme_end {
1762                self.output.push('/');
1763            }
1764        }
1765    }
1766
1767    fn read_url_codepoint_or_echar(
1768        &mut self,
1769        c: char,
1770        valid: impl Fn(char) -> bool,
1771    ) -> Result<(), IriParseError> {
1772        if UNCHECKED || valid(c) {
1773            self.output.push(c);
1774            Ok(())
1775        } else if c == '%' {
1776            self.read_echar()
1777        } else {
1778            self.parse_error(IriParseErrorKind::InvalidIriCodePoint(c))
1779        }
1780    }
1781
1782    fn read_echar(&mut self) -> Result<(), IriParseError> {
1783        let c1 = self.input.next();
1784        let c2 = self.input.next();
1785        if c1.map_or(false, |c| c.is_ascii_hexdigit())
1786            && c2.map_or(false, |c| c.is_ascii_hexdigit())
1787        {
1788            self.output.push('%');
1789            self.output.push(c1.unwrap());
1790            self.output.push(c2.unwrap());
1791            Ok(())
1792        } else {
1793            self.parse_error(IriParseErrorKind::InvalidPercentEncoding([
1794                Some('%'),
1795                c1,
1796                c2,
1797            ]))
1798        }
1799    }
1800
1801    fn parse_error<T>(&self, kind: IriParseErrorKind) -> Result<T, IriParseError> {
1802        Err(IriParseError { kind })
1803    }
1804
1805    // IPvFuture      = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
1806    fn validate_ip_v_future(&self, ip: &str) -> Result<(), IriParseError> {
1807        let mut chars = ip.chars();
1808
1809        let c = chars.next().ok_or(IriParseError {
1810            kind: IriParseErrorKind::InvalidHostCharacter(']'),
1811        })?;
1812        if !matches!(c, 'v' | 'V') {
1813            return self.parse_error(IriParseErrorKind::InvalidHostCharacter(c));
1814        };
1815
1816        let mut with_a_version = false;
1817        for c in &mut chars {
1818            if c == '.' {
1819                break;
1820            } else if c.is_ascii_hexdigit() {
1821                with_a_version = true;
1822            } else {
1823                return self.parse_error(IriParseErrorKind::InvalidHostCharacter(c));
1824            }
1825        }
1826        if !with_a_version {
1827            return self.parse_error(IriParseErrorKind::InvalidHostCharacter(
1828                chars.next().unwrap_or(']'),
1829            ));
1830        }
1831
1832        if chars.as_str().is_empty() {
1833            return self.parse_error(IriParseErrorKind::InvalidHostCharacter(']'));
1834        };
1835        for c in chars {
1836            if !is_unreserved_or_sub_delims(c) && c != ':' {
1837                return self.parse_error(IriParseErrorKind::InvalidHostCharacter(c));
1838            }
1839        }
1840
1841        Ok(())
1842    }
1843}
1844
1845fn is_iunreserved_or_sub_delims(c: char) -> bool {
1846    matches!(c,
1847        'a'..='z'
1848        | 'A'..='Z'
1849        | '0'..='9'
1850        | '!'
1851        | '$'
1852        | '&'
1853        | '\''
1854        | '('
1855        | ')'
1856        | '*'
1857        | '+'
1858        | ','
1859        | '-'
1860        | '.'
1861        | ';'
1862        | '='
1863        | '_'
1864        | '~'
1865        | '\u{A0}'..='\u{D7FF}'
1866        | '\u{F900}'..='\u{FDCF}'
1867        | '\u{FDF0}'..='\u{FFEF}'
1868        | '\u{10000}'..='\u{1FFFD}'
1869        | '\u{20000}'..='\u{2FFFD}'
1870        | '\u{30000}'..='\u{3FFFD}'
1871        | '\u{40000}'..='\u{4FFFD}'
1872        | '\u{50000}'..='\u{5FFFD}'
1873        | '\u{60000}'..='\u{6FFFD}'
1874        | '\u{70000}'..='\u{7FFFD}'
1875        | '\u{80000}'..='\u{8FFFD}'
1876        | '\u{90000}'..='\u{9FFFD}'
1877        | '\u{A0000}'..='\u{AFFFD}'
1878        | '\u{B0000}'..='\u{BFFFD}'
1879        | '\u{C0000}'..='\u{CFFFD}'
1880        | '\u{D0000}'..='\u{DFFFD}'
1881        | '\u{E1000}'..='\u{EFFFD}'
1882    )
1883}
1884
1885fn is_unreserved_or_sub_delims(c: char) -> bool {
1886    matches!(c,
1887        'a'..='z'
1888        | 'A'..='Z'
1889        | '0'..='9'
1890        | '!'
1891        | '$'
1892        | '&'
1893        | '\''
1894        | '('
1895        | ')'
1896        | '*'
1897        | '+'
1898        | ','
1899        | '-'
1900        | '.'
1901        | ';'
1902        | '='
1903        | '_'
1904        | '~'
1905    )
1906}