ruma_common/identifiers/
server_name.rs

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