ruma_identifiers/
server_name.rs

1//! Matrix-spec compliant server names.
2
3use std::net::Ipv4Addr;
4
5/// A Matrix-spec compliant [server name].
6///
7/// It consists of a host and an optional port (separated by a colon if present).
8///
9/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
10#[repr(transparent)]
11#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct ServerName(str);
13
14opaque_identifier_validated!(ServerName, ruma_identifiers_validation::server_name::validate);
15
16impl ServerName {
17    /// Returns the host of the server name.
18    ///
19    /// That is: Return the part of the server name before `:<port>` or the full server name if
20    /// there is no port.
21    pub fn host(&self) -> &str {
22        if let Some(end_of_ipv6) = self.0.find(']') {
23            &self.0[..=end_of_ipv6]
24        } else {
25            // It's not ipv6, so ':' means the port starts
26            let end_of_host = self.0.find(':').unwrap_or(self.0.len());
27            &self.0[..end_of_host]
28        }
29    }
30
31    /// Returns the port of the server name, if any.
32    pub fn port(&self) -> Option<u16> {
33        #[allow(clippy::unnecessary_lazy_evaluations)]
34        let end_of_host = self
35            .0
36            .find(']')
37            .map(|i| i + 1)
38            .or_else(|| self.0.find(':'))
39            .unwrap_or_else(|| self.0.len());
40
41        (self.0.len() != end_of_host).then(|| {
42            assert!(self.as_bytes()[end_of_host] == b':');
43            self.0[end_of_host + 1..].parse().unwrap()
44        })
45    }
46
47    /// Returns true if and only if the server name is an IPv4 or IPv6 address.
48    pub fn is_ip_literal(&self) -> bool {
49        self.host().parse::<Ipv4Addr>().is_ok() || self.0.starts_with('[')
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use std::convert::TryFrom;
56
57    use super::ServerName;
58
59    #[test]
60    fn ipv4_host() {
61        assert!(<&ServerName>::try_from("127.0.0.1").is_ok());
62    }
63
64    #[test]
65    fn ipv4_host_and_port() {
66        assert!(<&ServerName>::try_from("1.1.1.1:12000").is_ok());
67    }
68
69    #[test]
70    fn ipv6() {
71        assert!(<&ServerName>::try_from("[::1]").is_ok());
72    }
73
74    #[test]
75    fn ipv6_with_port() {
76        assert!(<&ServerName>::try_from("[1234:5678::abcd]:5678").is_ok());
77    }
78
79    #[test]
80    fn dns_name() {
81        assert!(<&ServerName>::try_from("example.com").is_ok());
82    }
83
84    #[test]
85    fn dns_name_with_port() {
86        assert!(<&ServerName>::try_from("ruma.io:8080").is_ok());
87    }
88
89    #[test]
90    fn empty_string() {
91        assert!(<&ServerName>::try_from("").is_err());
92    }
93
94    #[test]
95    fn invalid_ipv6() {
96        assert!(<&ServerName>::try_from("[test::1]").is_err());
97    }
98
99    #[test]
100    fn ipv4_with_invalid_port() {
101        assert!(<&ServerName>::try_from("127.0.0.1:").is_err());
102    }
103
104    #[test]
105    fn ipv6_with_invalid_port() {
106        assert!(<&ServerName>::try_from("[fe80::1]:100000").is_err());
107        assert!(<&ServerName>::try_from("[fe80::1]!").is_err());
108    }
109
110    #[test]
111    fn dns_name_with_invalid_port() {
112        assert!(<&ServerName>::try_from("matrix.org:hello").is_err());
113    }
114
115    #[test]
116    fn parse_ipv4_host() {
117        let server_name = <&ServerName>::try_from("127.0.0.1").unwrap();
118        assert!(server_name.is_ip_literal());
119        assert_eq!(server_name.host(), "127.0.0.1");
120    }
121
122    #[test]
123    fn parse_ipv4_host_and_port() {
124        let server_name = <&ServerName>::try_from("1.1.1.1:12000").unwrap();
125        assert!(server_name.is_ip_literal());
126        assert_eq!(server_name.host(), "1.1.1.1");
127    }
128
129    #[test]
130    fn parse_ipv6() {
131        let server_name = <&ServerName>::try_from("[::1]").unwrap();
132        assert!(server_name.is_ip_literal());
133        assert_eq!(server_name.host(), "[::1]");
134    }
135
136    #[test]
137    fn parse_ipv6_with_port() {
138        let server_name = <&ServerName>::try_from("[1234:5678::abcd]:5678").unwrap();
139        assert!(server_name.is_ip_literal());
140        assert_eq!(server_name.host(), "[1234:5678::abcd]");
141    }
142
143    #[test]
144    fn parse_dns_name() {
145        let server_name = <&ServerName>::try_from("example.com").unwrap();
146        assert!(!server_name.is_ip_literal());
147        assert_eq!(server_name.host(), "example.com");
148    }
149
150    #[test]
151    fn parse_dns_name_with_port() {
152        let server_name = <&ServerName>::try_from("ruma.io:8080").unwrap();
153        assert!(!server_name.is_ip_literal());
154        assert_eq!(server_name.host(), "ruma.io");
155    }
156}