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}