Skip to main content

uni_addr/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::must_use_candidate)]
3#![deprecated(
4    since = "0.0.0",
5    note = "This crate is deprecated, use `uaddr` instead: https://crates.io/crates/uaddr"
6)]
7
8use std::borrow::Cow;
9use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
10use std::str::FromStr;
11use std::sync::Arc;
12use std::{fmt, io};
13
14#[cfg(unix)]
15pub mod unix;
16
17/// The prefix for Unix domain socket URIs.
18///
19/// - `unix:///path/to/socket` for a pathname socket address.
20/// - `unix://@abstract.unix.socket` for an abstract socket address.
21pub const UNIX_URI_PREFIX: &str = "unix://";
22
23wrapper_lite::wrapper!(
24    #[wrapper_impl(Debug)]
25    #[wrapper_impl(Display)]
26    #[wrapper_impl(AsRef)]
27    #[wrapper_impl(Deref)]
28    #[repr(align(cache))]
29    #[derive(Clone, PartialEq, Eq, Hash)]
30    /// A unified address type that can represent:
31    ///
32    /// - [`std::net::SocketAddr`]
33    /// - [`unix::SocketAddr`] (a wrapper over
34    ///   [`std::os::unix::net::SocketAddr`])
35    /// - A host name with port. See [`ToSocketAddrs`].
36    ///
37    /// # Parsing Behaviour
38    ///
39    /// - Checks if the address started with [`UNIX_URI_PREFIX`]: parse as a UDS
40    ///   address.
41    /// - Checks if the address is started with a alphabetic character (a-z,
42    ///   A-Z): treat as a host name. Notes that we will not validate if the
43    ///   host name is valid.
44    /// - Tries to parse as a network socket address.
45    /// - Otherwise, treats the input as a host name.
46    pub struct UniAddr(UniAddrInner);
47);
48
49impl From<SocketAddr> for UniAddr {
50    fn from(addr: SocketAddr) -> Self {
51        UniAddr::from_inner(UniAddrInner::Inet(addr))
52    }
53}
54
55#[cfg(unix)]
56impl From<std::os::unix::net::SocketAddr> for UniAddr {
57    fn from(addr: std::os::unix::net::SocketAddr) -> Self {
58        UniAddr::from_inner(UniAddrInner::Unix(addr.into()))
59    }
60}
61
62#[cfg(all(unix, feature = "feat-tokio"))]
63impl From<tokio::net::unix::SocketAddr> for UniAddr {
64    fn from(addr: tokio::net::unix::SocketAddr) -> Self {
65        UniAddr::from_inner(UniAddrInner::Unix(unix::SocketAddr::from(addr.into())))
66    }
67}
68
69#[cfg(feature = "feat-socket2")]
70impl TryFrom<socket2::SockAddr> for UniAddr {
71    type Error = io::Error;
72
73    fn try_from(addr: socket2::SockAddr) -> Result<Self, Self::Error> {
74        UniAddr::try_from(&addr)
75    }
76}
77
78#[cfg(feature = "feat-socket2")]
79impl TryFrom<&socket2::SockAddr> for UniAddr {
80    type Error = io::Error;
81
82    fn try_from(addr: &socket2::SockAddr) -> Result<Self, Self::Error> {
83        if let Some(addr) = addr.as_socket() {
84            return Ok(Self::from(addr));
85        }
86
87        #[cfg(unix)]
88        if let Some(addr) = addr.as_unix() {
89            return Ok(Self::from(addr));
90        }
91
92        #[cfg(unix)]
93        if addr.is_unnamed() {
94            return Ok(Self::from(crate::unix::SocketAddr::new_unnamed()));
95        }
96
97        #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))]
98        if let Some(addr) = addr.as_abstract_namespace() {
99            return crate::unix::SocketAddr::new_abstract(addr).map(Self::from);
100        }
101
102        Err(io::Error::new(
103            io::ErrorKind::Other,
104            "unsupported address type",
105        ))
106    }
107}
108
109#[cfg(feature = "feat-socket2")]
110impl TryFrom<UniAddr> for socket2::SockAddr {
111    type Error = io::Error;
112
113    fn try_from(addr: UniAddr) -> Result<Self, Self::Error> {
114        socket2::SockAddr::try_from(&addr)
115    }
116}
117
118#[cfg(feature = "feat-socket2")]
119impl TryFrom<&UniAddr> for socket2::SockAddr {
120    type Error = io::Error;
121
122    fn try_from(addr: &UniAddr) -> Result<Self, Self::Error> {
123        match &addr.inner {
124            UniAddrInner::Inet(addr) => Ok(socket2::SockAddr::from(*addr)),
125            #[cfg(unix)]
126            UniAddrInner::Unix(addr) => socket2::SockAddr::unix(addr.to_os_string()),
127            UniAddrInner::Host(_) => Err(io::Error::new(
128                io::ErrorKind::Other,
129                "The host name address must be resolved before converting to SockAddr",
130            )),
131        }
132    }
133}
134
135#[cfg(unix)]
136impl From<crate::unix::SocketAddr> for UniAddr {
137    fn from(addr: crate::unix::SocketAddr) -> Self {
138        UniAddr::from_inner(UniAddrInner::Unix(addr))
139    }
140}
141
142impl FromStr for UniAddr {
143    type Err = ParseError;
144
145    fn from_str(addr: &str) -> Result<Self, Self::Err> {
146        Self::new(addr)
147    }
148}
149
150#[cfg(feature = "feat-serde")]
151impl serde::Serialize for UniAddr {
152    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153    where
154        S: serde::Serializer,
155    {
156        serializer.serialize_str(&self.to_str())
157    }
158}
159
160#[cfg(feature = "feat-serde")]
161impl<'de> serde::Deserialize<'de> for UniAddr {
162    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163    where
164        D: serde::Deserializer<'de>,
165    {
166        Self::new(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
167    }
168}
169
170impl UniAddr {
171    #[inline]
172    /// Creates a new [`UniAddr`] from its string representation.
173    ///
174    /// # Errors
175    ///
176    /// Not a valid address string.
177    pub fn new(addr: &str) -> Result<Self, ParseError> {
178        if addr.is_empty() {
179            return Err(ParseError::Empty);
180        }
181
182        #[cfg(unix)]
183        if let Some(addr) = addr.strip_prefix(UNIX_URI_PREFIX) {
184            return unix::SocketAddr::new(addr)
185                .map(UniAddrInner::Unix)
186                .map(Self::from_inner)
187                .map_err(ParseError::InvalidUDSAddress);
188        }
189
190        #[cfg(not(unix))]
191        if let Some(_addr) = addr.strip_prefix(UNIX_URI_PREFIX) {
192            return Err(ParseError::Unsupported);
193        }
194
195        let Some((host, port)) = addr.rsplit_once(':') else {
196            return Err(ParseError::InvalidPort);
197        };
198
199        let Ok(port) = port.parse::<u16>() else {
200            return Err(ParseError::InvalidPort);
201        };
202
203        // Short-circuit: IPv4 address starts with a digit.
204        if host.chars().next().is_some_and(|c| c.is_ascii_digit()) {
205            return Ipv4Addr::from_str(host)
206                .map(|ip| SocketAddr::V4(SocketAddrV4::new(ip, port)))
207                .map(UniAddrInner::Inet)
208                .map(Self::from_inner)
209                .map_err(|_| ParseError::InvalidHost)
210                .or_else(|_| {
211                    // A host name may also start with a digit.
212                    Self::new_host(addr, Some((host, port)))
213                });
214        }
215
216        // Short-circuit: if starts with '[' and ends with ']', may be an IPv6 address
217        // and can never be a host.
218        if let Some(ipv6_addr) = host.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
219            return Ipv6Addr::from_str(ipv6_addr)
220                .map(|ip| SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)))
221                .map(UniAddrInner::Inet)
222                .map(Self::from_inner)
223                .map_err(|_| ParseError::InvalidHost);
224        }
225
226        // Fallback: check if is a valid host name.
227        Self::new_host(addr, Some((host, port)))
228    }
229
230    /// Creates a new [`UniAddr`] from a string containing a host name and port,
231    /// like `example.com:8080`.
232    ///
233    /// # Errors
234    ///
235    /// - [`ParseError::InvalidHost`] if the host name is invalid.
236    /// - [`ParseError::InvalidPort`] if the port is invalid.
237    pub fn new_host(addr: &str, parsed: Option<(&str, u16)>) -> Result<Self, ParseError> {
238        let (hostname, _port) = match parsed {
239            Some((hostname, port)) => (hostname, port),
240            None => addr
241                .rsplit_once(':')
242                .ok_or(ParseError::InvalidPort)
243                .and_then(|(hostname, port)| {
244                    let Ok(port) = port.parse::<u16>() else {
245                        return Err(ParseError::InvalidPort);
246                    };
247
248                    Ok((hostname, port))
249                })?,
250        };
251
252        Self::validate_host_name(hostname.as_bytes()).map_err(|()| ParseError::InvalidHost)?;
253
254        Ok(Self::from_inner(UniAddrInner::Host(Arc::from(addr))))
255    }
256
257    // https://github.com/rustls/pki-types/blob/b8c04aa6b7a34875e2c4a33edc9b78d31da49523/src/server_name.rs
258    const fn validate_host_name(input: &[u8]) -> Result<(), ()> {
259        enum State {
260            Start,
261            Next,
262            NumericOnly { len: usize },
263            NextAfterNumericOnly,
264            Subsequent { len: usize },
265            Hyphen { len: usize },
266        }
267
268        use State::{Hyphen, Next, NextAfterNumericOnly, NumericOnly, Start, Subsequent};
269
270        /// "Labels must be 63 characters or less."
271        const MAX_LABEL_LENGTH: usize = 63;
272
273        /// <https://devblogs.microsoft.com/oldnewthing/20120412-00/?p=7873>
274        const MAX_NAME_LENGTH: usize = 253;
275
276        let mut state = Start;
277
278        if input.len() > MAX_NAME_LENGTH {
279            return Err(());
280        }
281
282        let mut idx = 0;
283        while idx < input.len() {
284            let ch = input[idx];
285            state = match (state, ch) {
286                (Start | Next | NextAfterNumericOnly | Hyphen { .. }, b'.') => {
287                    return Err(());
288                }
289                (Subsequent { .. }, b'.') => Next,
290                (NumericOnly { .. }, b'.') => NextAfterNumericOnly,
291                (Subsequent { len } | NumericOnly { len } | Hyphen { len }, _)
292                    if len >= MAX_LABEL_LENGTH =>
293                {
294                    return Err(());
295                }
296                (Start | Next | NextAfterNumericOnly, b'0'..=b'9') => NumericOnly { len: 1 },
297                (NumericOnly { len }, b'0'..=b'9') => NumericOnly { len: len + 1 },
298                (Start | Next | NextAfterNumericOnly, b'a'..=b'z' | b'A'..=b'Z' | b'_') => {
299                    Subsequent { len: 1 }
300                }
301                (Subsequent { len } | NumericOnly { len } | Hyphen { len }, b'-') => {
302                    Hyphen { len: len + 1 }
303                }
304                (
305                    Subsequent { len } | NumericOnly { len } | Hyphen { len },
306                    b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'0'..=b'9',
307                ) => Subsequent { len: len + 1 },
308                _ => return Err(()),
309            };
310            idx += 1;
311        }
312
313        if matches!(
314            state,
315            Start | Hyphen { .. } | NumericOnly { .. } | NextAfterNumericOnly | Next
316        ) {
317            return Err(());
318        }
319
320        Ok(())
321    }
322
323    /// Resolves the address if it is a host name.
324    ///
325    /// By default, we utilize the method [`ToSocketAddrs::to_socket_addrs`]
326    /// provided by the standard library to perform DNS resolution, which is a
327    /// **blocking** operation and may take an arbitrary amount of time to
328    /// complete, use with caution when called in asynchronous contexts.
329    ///
330    /// # Errors
331    ///
332    /// Resolution failure, or if no socket address resolved.
333    pub fn blocking_resolve_socket_addrs(&mut self) -> io::Result<()> {
334        self.blocking_resolve_socket_addrs_with(ToSocketAddrs::to_socket_addrs)
335    }
336
337    /// Resolves the address if it is a host name using a custom resolver
338    /// function.
339    ///
340    /// # Errors
341    ///
342    /// Resolution failure, or if no socket address resolved.
343    pub fn blocking_resolve_socket_addrs_with<F, A>(&mut self, f: F) -> io::Result<()>
344    where
345        F: FnOnce(&str) -> io::Result<A>,
346        A: Iterator<Item = SocketAddr>,
347    {
348        if let UniAddrInner::Host(addr) = self.as_inner() {
349            let resolved = f(addr)?.next().ok_or_else(|| {
350                io::Error::new(
351                    io::ErrorKind::Other,
352                    "Host resolution failed, no available address",
353                )
354            })?;
355
356            *self = Self::from_inner(UniAddrInner::Inet(resolved));
357        }
358
359        Ok(())
360    }
361
362    #[cfg(feature = "feat-tokio")]
363    /// Asynchronously resolves the address if it is a host name.
364    ///
365    /// This method will spawn a blocking Tokio task to perform the resolution
366    /// using [`ToSocketAddrs::to_socket_addrs`] provided by the standard
367    /// library.
368    ///
369    /// # Errors
370    ///
371    /// Resolution failure, or if no socket address resolved.
372    pub async fn resolve_socket_addrs(&mut self) -> io::Result<()> {
373        if let UniAddrInner::Host(addr) = self.as_inner() {
374            let addr = addr.clone();
375            let resolved = tokio::task::spawn_blocking(move || addr.to_socket_addrs())
376                .await??
377                .next()
378                .ok_or_else(|| {
379                    io::Error::new(
380                        io::ErrorKind::Other,
381                        "Host resolution failed, no available address",
382                    )
383                })?;
384
385            *self = Self::from_inner(UniAddrInner::Inet(resolved));
386        }
387
388        Ok(())
389    }
390
391    #[inline]
392    /// Serializes the address to a string.
393    pub fn to_str(&self) -> Cow<'_, str> {
394        self.as_inner().to_str()
395    }
396}
397
398#[non_exhaustive]
399#[derive(Debug, Clone, PartialEq, Eq, Hash)]
400/// See [`UniAddr`].
401///
402/// Generally, you should use [`UniAddr`] instead of this type directly, as
403/// we expose this type only for easier pattern matching. A valid [`UniAddr`]
404/// can be constructed only through [`FromStr`] implementation.
405pub enum UniAddrInner {
406    /// See [`SocketAddr`].
407    Inet(SocketAddr),
408
409    #[cfg(unix)]
410    /// See [`SocketAddr`](crate::unix::SocketAddr).
411    Unix(crate::unix::SocketAddr),
412
413    /// A host name with port.
414    ///
415    /// Please refer to [`ToSocketAddrs`], and
416    /// [`UniAddr::blocking_resolve_socket_addrs`], etc to resolve the
417    /// address when needed.
418    Host(Arc<str>),
419}
420
421impl fmt::Display for UniAddrInner {
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        self.to_str().fmt(f)
424    }
425}
426
427impl UniAddrInner {
428    #[inline]
429    /// Serializes the address to a string.
430    pub fn to_str(&self) -> Cow<'_, str> {
431        match self {
432            Self::Inet(addr) => addr.to_string().into(),
433            #[cfg(unix)]
434            Self::Unix(addr) => addr
435                .to_os_string_impl(UNIX_URI_PREFIX, "@")
436                .to_string_lossy()
437                .to_string()
438                .into(),
439            Self::Host(host) => Cow::Borrowed(host),
440        }
441    }
442}
443
444#[derive(Debug)]
445/// Errors that can occur when parsing a [`UniAddr`] from a string.
446pub enum ParseError {
447    /// Empty input string
448    Empty,
449
450    /// Invalid or missing hostname, or an invalid Ipv4 / IPv6 address
451    InvalidHost,
452
453    /// Invalid address format: missing or invalid port
454    InvalidPort,
455
456    /// Invalid UDS address format
457    InvalidUDSAddress(io::Error),
458
459    /// Unsupported address type on this platform
460    Unsupported,
461}
462
463impl fmt::Display for ParseError {
464    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465        match self {
466            Self::Empty => write!(f, "empty address string"),
467            Self::InvalidHost => write!(f, "invalid or missing host address"),
468            Self::InvalidPort => write!(f, "invalid or missing port"),
469            Self::InvalidUDSAddress(err) => write!(f, "invalid UDS address: {err}"),
470            Self::Unsupported => write!(f, "unsupported address type on this platform"),
471        }
472    }
473}
474
475impl std::error::Error for ParseError {
476    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
477        match self {
478            Self::InvalidUDSAddress(err) => Some(err),
479            _ => None,
480        }
481    }
482}
483
484impl From<ParseError> for io::Error {
485    fn from(value: ParseError) -> Self {
486        io::Error::new(io::ErrorKind::Other, value)
487    }
488}
489
490#[cfg(test)]
491mod tests {
492    #![allow(non_snake_case)]
493
494    use rstest::rstest;
495
496    use super::*;
497
498    #[rstest]
499    #[case("0.0.0.0:0")]
500    #[case("0.0.0.0:8080")]
501    #[case("127.0.0.1:0")]
502    #[case("127.0.0.1:8080")]
503    #[case("[::]:0")]
504    #[case("[::]:8080")]
505    #[case("[::1]:0")]
506    #[case("[::1]:8080")]
507    #[case("example.com:8080")]
508    #[case("1example.com:8080")]
509    #[cfg_attr(unix, case("unix://"))]
510    #[cfg_attr(
511        any(target_os = "android", target_os = "linux", target_os = "cygwin"),
512        case("unix://@")
513    )]
514    #[cfg_attr(unix, case("unix:///tmp/test_UniAddr_new_Display.socket"))]
515    #[cfg_attr(
516        any(target_os = "android", target_os = "linux", target_os = "cygwin"),
517        case("unix://@test_UniAddr_new_Display.socket")
518    )]
519    fn test_UniAddr_new_Display(#[case] addr: &str) {
520        let addr_displayed = UniAddr::new(addr).unwrap().to_string();
521
522        assert_eq!(
523            addr_displayed, addr,
524            "addr_displayed {addr_displayed:?} != {addr:?}"
525        );
526    }
527
528    #[rstest]
529    #[case("example.com:8080")]
530    #[case("1example.com:8080")]
531    #[should_panic]
532    #[case::panic("1example.com")]
533    #[should_panic]
534    #[case::panic("1example.com.")]
535    #[should_panic]
536    #[case::panic("1example.com.:14514")]
537    #[should_panic]
538    #[case::panic("1example.com:1919810")]
539    #[should_panic]
540    #[case::panic("this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name:19810")]
541    fn test_UniAddr_new_host(#[case] addr: &str) {
542        let addr_displayed = UniAddr::new_host(addr, None).unwrap().to_string();
543
544        assert_eq!(
545            addr_displayed, addr,
546            "addr_displayed {addr_displayed:?} != {addr:?}"
547        );
548    }
549
550    #[rstest]
551    #[should_panic]
552    #[case::panic("")]
553    #[should_panic]
554    #[case::panic("not-an-address")]
555    #[should_panic]
556    #[case::panic("127.0.0.1")]
557    #[should_panic]
558    #[case::panic("127.0.0.1:99999")]
559    #[should_panic]
560    #[case::panic("127.0.0.256:99999")]
561    #[should_panic]
562    #[case::panic("::1")]
563    #[should_panic]
564    #[case::panic("[::1]")]
565    #[should_panic]
566    #[case::panic("[::1]:99999")]
567    #[should_panic]
568    #[case::panic("[::gg]:99999")]
569    #[should_panic]
570    #[case::panic("example.com")]
571    #[should_panic]
572    #[case::panic("example.com:99999")]
573    #[should_panic]
574    #[case::panic("examp😀le.com:99999")]
575    fn test_UniAddr_new_invalid(#[case] addr: &str) {
576        let _ = UniAddr::new(addr).unwrap();
577    }
578
579    #[cfg(not(unix))]
580    #[test]
581    fn test_UniAddr_new_unsupported() {
582        // Unix sockets should be unsupported on non-Unix platforms
583        let result = UniAddr::new("unix:///tmp/test.sock");
584
585        assert!(matches!(result.unwrap_err(), ParseError::Unsupported));
586    }
587
588    #[rstest]
589    #[case("0.0.0.0:0")]
590    #[case("0.0.0.0:8080")]
591    #[case("127.0.0.1:0")]
592    #[case("127.0.0.1:8080")]
593    #[case("[::]:0")]
594    #[case("[::]:8080")]
595    #[case("[::1]:0")]
596    #[case("[::1]:8080")]
597    #[cfg_attr(unix, case("unix:///tmp/test_socket2_sock_addr_conversion.socket"))]
598    #[cfg_attr(unix, case("unix://"))]
599    #[cfg_attr(
600        any(target_os = "android", target_os = "linux", target_os = "cygwin"),
601        case("unix://@test_socket2_sock_addr_conversion.socket")
602    )]
603    fn test_socket2_SockAddr_conversion(#[case] addr: &str) {
604        let uni_addr = UniAddr::new(addr).unwrap();
605        let sock_addr = socket2::SockAddr::try_from(&uni_addr).unwrap();
606        let uni_addr_converted = UniAddr::try_from(sock_addr).unwrap();
607
608        assert_eq!(
609            uni_addr, uni_addr_converted,
610            "{uni_addr} != {uni_addr_converted}"
611        );
612    }
613}