fluent_uri/
component.rs

1//! URI/IRI components.
2
3use crate::{
4    encoding::{
5        encoder::{IRegName, IUserinfo, Port, RegName, Userinfo},
6        table, EStr, Encoder,
7    },
8    internal::{AuthMeta, HostMeta},
9};
10use core::{marker::PhantomData, num::ParseIntError};
11use ref_cast::{ref_cast_custom, RefCastCustom};
12
13#[cfg(feature = "net")]
14use crate::net::{Ipv4Addr, Ipv6Addr};
15
16#[cfg(all(feature = "net", feature = "std"))]
17use std::{
18    io,
19    net::{SocketAddr, ToSocketAddrs},
20};
21
22/// An authority component for IRI.
23pub type IAuthority<'a> = Authority<'a, IUserinfo, IRegName>;
24
25/// A parsed host component for IRI.
26pub type IHost<'a> = Host<'a, IRegName>;
27
28/// A [scheme] component.
29///
30/// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
31///
32/// # Comparison
33///
34/// `Scheme`s are compared case-insensitively. You should do a case-insensitive
35/// comparison if the scheme specification allows both letter cases in the scheme name.
36///
37/// # Examples
38///
39/// ```
40/// use fluent_uri::{component::Scheme, Uri};
41///
42/// const SCHEME_HTTP: &Scheme = Scheme::new_or_panic("http");
43///
44/// let scheme = Uri::parse("HTTP://EXAMPLE.COM/")?.scheme();
45///
46/// // Case-insensitive comparison.
47/// assert_eq!(scheme, SCHEME_HTTP);
48/// // Case-sensitive comparison.
49/// assert_eq!(scheme.as_str(), "HTTP");
50/// # Ok::<_, fluent_uri::error::ParseError>(())
51/// ```
52#[derive(RefCastCustom)]
53#[repr(transparent)]
54pub struct Scheme {
55    inner: str,
56}
57
58impl Scheme {
59    #[ref_cast_custom]
60    #[inline]
61    pub(crate) const fn new_validated(scheme: &str) -> &Scheme;
62
63    /// Converts a string slice to `&Scheme`.
64    ///
65    /// # Panics
66    ///
67    /// Panics if the string is not a valid scheme name according to
68    /// [Section 3.1 of RFC 3986][scheme]. For a non-panicking variant,
69    /// use [`new`](Self::new).
70    ///
71    /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
72    #[inline]
73    #[must_use]
74    pub const fn new_or_panic(s: &str) -> &Scheme {
75        match Self::new(s) {
76            Some(scheme) => scheme,
77            None => panic!("invalid scheme"),
78        }
79    }
80
81    /// Converts a string slice to `&Scheme`, returning `None` if the conversion fails.
82    #[inline]
83    #[must_use]
84    pub const fn new(s: &str) -> Option<&Scheme> {
85        if matches!(s.as_bytes(), [first, rem @ ..]
86        if first.is_ascii_alphabetic() && table::SCHEME.validate(rem))
87        {
88            Some(Scheme::new_validated(s))
89        } else {
90            None
91        }
92    }
93
94    /// Returns the scheme component as a string slice.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// use fluent_uri::Uri;
100    ///
101    /// let uri = Uri::parse("http://example.com/")?;
102    /// assert_eq!(uri.scheme().as_str(), "http");
103    /// let uri = Uri::parse("HTTP://EXAMPLE.COM/")?;
104    /// assert_eq!(uri.scheme().as_str(), "HTTP");
105    /// # Ok::<_, fluent_uri::error::ParseError>(())
106    /// ```
107    #[inline]
108    #[must_use]
109    pub fn as_str(&self) -> &str {
110        &self.inner
111    }
112}
113
114impl PartialEq for Scheme {
115    #[inline]
116    fn eq(&self, other: &Self) -> bool {
117        self.inner.eq_ignore_ascii_case(&other.inner)
118    }
119}
120
121impl Eq for Scheme {}
122
123#[derive(Clone, Copy)]
124struct AuthorityInner<'a> {
125    val: &'a str,
126    meta: AuthMeta,
127}
128
129impl<'a> AuthorityInner<'a> {
130    fn userinfo(&self) -> Option<&'a EStr<IUserinfo>> {
131        let host_start = self.meta.host_bounds.0;
132        (host_start != 0).then(|| EStr::new_validated(&self.val[..host_start - 1]))
133    }
134
135    fn host(&self) -> &'a str {
136        let (start, end) = self.meta.host_bounds;
137        &self.val[start..end]
138    }
139
140    fn port(&self) -> Option<&'a EStr<Port>> {
141        let host_end = self.meta.host_bounds.1;
142        (host_end != self.val.len()).then(|| EStr::new_validated(&self.val[host_end + 1..]))
143    }
144
145    fn port_to_u16(&self) -> Result<Option<u16>, ParseIntError> {
146        self.port()
147            .filter(|s| !s.is_empty())
148            .map(|s| s.as_str().parse())
149            .transpose()
150    }
151
152    #[cfg(all(feature = "net", feature = "std"))]
153    fn socket_addrs(&self, default_port: u16) -> io::Result<impl Iterator<Item = SocketAddr>> {
154        use std::vec;
155
156        let port = self
157            .port_to_u16()
158            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid port value"))?
159            .unwrap_or(default_port);
160
161        match self.meta.host_meta {
162            HostMeta::Ipv4(addr) => Ok(vec![(addr, port).into()].into_iter()),
163            HostMeta::Ipv6(addr) => Ok(vec![(addr, port).into()].into_iter()),
164            HostMeta::IpvFuture => Err(io::Error::new(
165                io::ErrorKind::InvalidInput,
166                "address mechanism not supported",
167            )),
168            HostMeta::RegName => {
169                let name = EStr::<IRegName>::new_validated(self.host());
170                let name = name.decode().into_string().map_err(|_| {
171                    io::Error::new(
172                        io::ErrorKind::InvalidInput,
173                        "registered name does not decode to valid UTF-8",
174                    )
175                })?;
176                (&name[..], port).to_socket_addrs()
177            }
178        }
179    }
180}
181
182/// An [authority] component.
183///
184/// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
185#[derive(Clone, Copy)]
186pub struct Authority<'a, UserinfoE = Userinfo, RegNameE = RegName> {
187    inner: AuthorityInner<'a>,
188    _marker: PhantomData<(UserinfoE, RegNameE)>,
189}
190
191impl<'a, T, U> Authority<'a, T, U> {
192    pub(crate) fn cast<V, W>(self) -> Authority<'a, V, W> {
193        Authority {
194            inner: self.inner,
195            _marker: PhantomData,
196        }
197    }
198}
199
200impl<'a, UserinfoE: Encoder, RegNameE: Encoder> Authority<'a, UserinfoE, RegNameE> {
201    pub(crate) const fn new(val: &'a str, meta: AuthMeta) -> Self {
202        Self {
203            inner: AuthorityInner { val, meta },
204            _marker: PhantomData,
205        }
206    }
207
208    /// An empty authority component.
209    pub const EMPTY: Authority<'static, UserinfoE, RegNameE> = Authority::new("", AuthMeta::EMPTY);
210
211    pub(crate) fn meta(&self) -> AuthMeta {
212        self.inner.meta
213    }
214
215    /// Returns the authority component as a string slice.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use fluent_uri::Uri;
221    ///
222    /// let uri = Uri::parse("http://user@example.com:8080/")?;
223    /// let auth = uri.authority().unwrap();
224    /// assert_eq!(auth.as_str(), "user@example.com:8080");
225    /// # Ok::<_, fluent_uri::error::ParseError>(())
226    /// ```
227    #[inline]
228    #[must_use]
229    pub fn as_str(&self) -> &'a str {
230        self.inner.val
231    }
232
233    /// Returns the optional [userinfo] subcomponent.
234    ///
235    /// [userinfo]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use fluent_uri::{encoding::EStr, Uri};
241    ///
242    /// let uri = Uri::parse("http://user@example.com/")?;
243    /// let auth = uri.authority().unwrap();
244    /// assert_eq!(auth.userinfo(), Some(EStr::new_or_panic("user")));
245    ///
246    /// let uri = Uri::parse("http://example.com/")?;
247    /// let auth = uri.authority().unwrap();
248    /// assert_eq!(auth.userinfo(), None);
249    /// # Ok::<_, fluent_uri::error::ParseError>(())
250    /// ```
251    #[must_use]
252    pub fn userinfo(&self) -> Option<&'a EStr<UserinfoE>> {
253        self.inner.userinfo().map(EStr::cast)
254    }
255
256    /// Returns the [host] subcomponent as a string slice.
257    ///
258    /// The host subcomponent is always present, although it may be empty.
259    ///
260    /// The square brackets enclosing an IPv6 or IPvFuture address are included.
261    ///
262    /// Note that ASCII characters within a host are *case-insensitive*.
263    ///
264    /// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// use fluent_uri::Uri;
270    ///
271    /// let uri = Uri::parse("http://user@example.com:8080/")?;
272    /// let auth = uri.authority().unwrap();
273    /// assert_eq!(auth.host(), "example.com");
274    ///
275    /// let uri = Uri::parse("file:///path/to/file")?;
276    /// let auth = uri.authority().unwrap();
277    /// assert_eq!(auth.host(), "");
278    ///
279    /// let uri = Uri::parse("http://[::1]")?;
280    /// let auth = uri.authority().unwrap();
281    /// assert_eq!(auth.host(), "[::1]");
282    /// # Ok::<_, fluent_uri::error::ParseError>(())
283    /// ```
284    #[must_use]
285    pub fn host(&self) -> &'a str {
286        self.inner.host()
287    }
288
289    /// Returns the parsed [host] subcomponent.
290    ///
291    /// Note that ASCII characters within a host are *case-insensitive*.
292    ///
293    /// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use fluent_uri::{component::Host, encoding::EStr, Uri};
299    #[cfg_attr(feature = "net", doc = "use std::net::{Ipv4Addr, Ipv6Addr};")]
300    ///
301    /// let uri = Uri::parse("foo://127.0.0.1")?;
302    /// let auth = uri.authority().unwrap();
303    #[cfg_attr(
304        feature = "net",
305        doc = "assert!(matches!(auth.host_parsed(), Host::Ipv4(Ipv4Addr::LOCALHOST)));"
306    )]
307    #[cfg_attr(
308        not(feature = "net"),
309        doc = "assert!(matches!(auth.host_parsed(), Host::Ipv4 { .. }));"
310    )]
311    ///
312    /// let uri = Uri::parse("foo://[::1]")?;
313    /// let auth = uri.authority().unwrap();
314    #[cfg_attr(
315        feature = "net",
316        doc = "assert!(matches!(auth.host_parsed(), Host::Ipv6(Ipv6Addr::LOCALHOST)));"
317    )]
318    #[cfg_attr(
319        not(feature = "net"),
320        doc = "assert!(matches!(auth.host_parsed(), Host::Ipv6 { .. }));"
321    )]
322    ///
323    /// let uri = Uri::parse("foo://[v1.addr]")?;
324    /// let auth = uri.authority().unwrap();
325    /// // The API design for IPvFuture addresses is to be determined.
326    /// assert!(matches!(auth.host_parsed(), Host::IpvFuture { .. }));
327    ///
328    /// let uri = Uri::parse("foo://localhost")?;
329    /// let auth = uri.authority().unwrap();
330    /// assert!(matches!(auth.host_parsed(), Host::RegName(name) if name == "localhost"));
331    /// # Ok::<_, fluent_uri::error::ParseError>(())
332    /// ```
333    #[must_use]
334    pub fn host_parsed(&self) -> Host<'a, RegNameE> {
335        match self.inner.meta.host_meta {
336            #[cfg(feature = "net")]
337            HostMeta::Ipv4(addr) => Host::Ipv4(addr),
338            #[cfg(feature = "net")]
339            HostMeta::Ipv6(addr) => Host::Ipv6(addr),
340
341            #[cfg(not(feature = "net"))]
342            HostMeta::Ipv4() => Host::Ipv4(),
343            #[cfg(not(feature = "net"))]
344            HostMeta::Ipv6() => Host::Ipv6(),
345
346            HostMeta::IpvFuture => Host::IpvFuture,
347            HostMeta::RegName => Host::RegName(EStr::new_validated(self.host())),
348        }
349    }
350
351    /// Returns the optional [port] subcomponent.
352    ///
353    /// A scheme may define a default port to use when the port is
354    /// not present or is empty.
355    ///
356    /// Note that the port may be empty, with leading zeros, or larger than [`u16::MAX`].
357    /// It is up to you to decide whether to deny such ports, fallback to the scheme's
358    /// default if it is empty, ignore the leading zeros, or use a special addressing
359    /// mechanism that allows ports larger than [`u16::MAX`].
360    ///
361    /// [port]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
362    ///
363    /// # Examples
364    ///
365    /// ```
366    /// use fluent_uri::{encoding::EStr, Uri};
367    ///
368    /// let uri = Uri::parse("foo://localhost:4673/")?;
369    /// let auth = uri.authority().unwrap();
370    /// assert_eq!(auth.port(), Some(EStr::new_or_panic("4673")));
371    ///
372    /// let uri = Uri::parse("foo://localhost:/")?;
373    /// let auth = uri.authority().unwrap();
374    /// assert_eq!(auth.port(), Some(EStr::EMPTY));
375    ///
376    /// let uri = Uri::parse("foo://localhost/")?;
377    /// let auth = uri.authority().unwrap();
378    /// assert_eq!(auth.port(), None);
379    ///
380    /// let uri = Uri::parse("foo://localhost:123456/")?;
381    /// let auth = uri.authority().unwrap();
382    /// assert_eq!(auth.port(), Some(EStr::new_or_panic("123456")));
383    /// # Ok::<_, fluent_uri::error::ParseError>(())
384    /// ```
385    #[must_use]
386    pub fn port(&self) -> Option<&'a EStr<Port>> {
387        self.inner.port()
388    }
389
390    /// Converts the [port] subcomponent to `u16`, if present and nonempty.
391    ///
392    /// Returns `Ok(None)` if the port is not present or is empty. Leading zeros are ignored.
393    ///
394    /// [port]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
395    ///
396    /// # Errors
397    ///
398    /// Returns `Err` if the port cannot be parsed into `u16`.
399    ///
400    /// # Examples
401    ///
402    /// ```
403    /// use fluent_uri::Uri;
404    ///
405    /// let uri = Uri::parse("foo://localhost:4673/")?;
406    /// let auth = uri.authority().unwrap();
407    /// assert_eq!(auth.port_to_u16(), Ok(Some(4673)));
408    ///
409    /// let uri = Uri::parse("foo://localhost/")?;
410    /// let auth = uri.authority().unwrap();
411    /// assert_eq!(auth.port_to_u16(), Ok(None));
412    ///
413    /// let uri = Uri::parse("foo://localhost:/")?;
414    /// let auth = uri.authority().unwrap();
415    /// assert_eq!(auth.port_to_u16(), Ok(None));
416    ///
417    /// let uri = Uri::parse("foo://localhost:123456/")?;
418    /// let auth = uri.authority().unwrap();
419    /// assert!(auth.port_to_u16().is_err());
420    /// # Ok::<_, fluent_uri::error::ParseError>(())
421    /// ```
422    pub fn port_to_u16(&self) -> Result<Option<u16>, ParseIntError> {
423        self.inner.port_to_u16()
424    }
425
426    /// Converts the host and the port subcomponent to an iterator of resolved [`SocketAddr`]s.
427    ///
428    /// The default port is used if the port component is not present or is empty.
429    /// A registered name is first [decoded] and then resolved with [`ToSocketAddrs`].
430    /// Punycode encoding is **not** performed prior to resolution.
431    ///
432    /// [decoded]: EStr::decode
433    ///
434    /// # Errors
435    ///
436    /// Returns `Err` if any of the following is true.
437    ///
438    /// - The port cannot be parsed into `u16`.
439    /// - The host is an IPvFuture address.
440    /// - A registered name does not decode to valid UTF-8 or fails to resolve.
441    #[cfg(all(feature = "net", feature = "std"))]
442    pub fn socket_addrs(&self, default_port: u16) -> io::Result<impl Iterator<Item = SocketAddr>> {
443        self.inner.socket_addrs(default_port)
444    }
445
446    /// Checks whether a userinfo subcomponent is present.
447    ///
448    /// # Examples
449    ///
450    /// ```
451    /// use fluent_uri::Uri;
452    ///
453    /// let uri = Uri::parse("http://user@example.com/")?;
454    /// assert!(uri.authority().unwrap().has_userinfo());
455    ///
456    /// let uri = Uri::parse("http://example.com/")?;
457    /// assert!(!uri.authority().unwrap().has_userinfo());
458    /// # Ok::<_, fluent_uri::error::ParseError>(())
459    #[inline]
460    #[must_use]
461    pub fn has_userinfo(&self) -> bool {
462        self.inner.meta.host_bounds.0 != 0
463    }
464
465    /// Checks whether a port subcomponent is present.
466    ///
467    /// # Examples
468    ///
469    /// ```
470    /// use fluent_uri::Uri;
471    ///
472    /// let uri = Uri::parse("foo://localhost:4673/")?;
473    /// assert!(uri.authority().unwrap().has_port());
474    ///
475    /// // The port subcomponent can be empty.
476    /// let uri = Uri::parse("foo://localhost:/")?;
477    /// assert!(uri.authority().unwrap().has_port());
478    ///
479    /// let uri = Uri::parse("foo://localhost/")?;
480    /// let auth = uri.authority().unwrap();
481    /// assert!(!uri.authority().unwrap().has_port());
482    /// # Ok::<_, fluent_uri::error::ParseError>(())
483    /// ```
484    #[inline]
485    #[must_use]
486    pub fn has_port(&self) -> bool {
487        self.inner.meta.host_bounds.1 != self.inner.val.len()
488    }
489}
490
491/// A parsed [host] component.
492///
493/// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
494#[derive(Clone, Copy)]
495#[cfg_attr(fuzzing, derive(PartialEq, Eq))]
496pub enum Host<'a, RegNameE: Encoder = RegName> {
497    /// An IPv4 address.
498    #[cfg_attr(not(feature = "net"), non_exhaustive)]
499    Ipv4(
500        /// The address.
501        #[cfg(feature = "net")]
502        Ipv4Addr,
503    ),
504    /// An IPv6 address.
505    #[cfg_attr(not(feature = "net"), non_exhaustive)]
506    Ipv6(
507        /// The address.
508        #[cfg(feature = "net")]
509        Ipv6Addr,
510    ),
511    /// An IP address of future version.
512    ///
513    /// This variant is marked as non-exhaustive because the API design
514    /// for IPvFuture addresses is to be determined.
515    #[non_exhaustive]
516    IpvFuture,
517    /// A registered name.
518    ///
519    /// Note that ASCII characters within a registered name are *case-insensitive*.
520    RegName(&'a EStr<RegNameE>),
521}