diff --git a/crates/ruma-identifiers/CHANGELOG.md b/crates/ruma-identifiers/CHANGELOG.md index 6fced00e..828320a2 100644 --- a/crates/ruma-identifiers/CHANGELOG.md +++ b/crates/ruma-identifiers/CHANGELOG.md @@ -12,6 +12,10 @@ Breaking changes: * Rename `RoomVersionId::Version{X}` variants to `RoomVersionId::V{X}` * Rename `RoomIdOrAliasId` to `RoomOrAliasId` +Improvements: + +* Add `host` and `is_ip_literal` methods to `ServerName` + Bug fixes: * Properly validate localpart when building a `UserId` via `parse_with_server_name` (and its diff --git a/crates/ruma-identifiers/src/server_name.rs b/crates/ruma-identifiers/src/server_name.rs index 434fc20c..834e6814 100644 --- a/crates/ruma-identifiers/src/server_name.rs +++ b/crates/ruma-identifiers/src/server_name.rs @@ -1,12 +1,37 @@ //! Matrix-spec compliant server names. +use std::net::Ipv4Addr; + /// A Matrix-spec compliant server name. +/// +/// It consists of a host and an optional port (separated by a colon if present). #[repr(transparent)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ServerName(str); opaque_identifier_validated!(ServerName, ruma_identifiers_validation::server_name::validate); +impl ServerName { + /// Returns the host of the server name. + /// + /// That is: Return the part of the server name before `:` or the full server name if + /// there is no port. + pub fn host(&self) -> &str { + if let Some(end_of_ipv6) = self.0.find(']') { + &self.0[..=end_of_ipv6] + } else { + // It's not ipv6, so ':' means the port starts + let end_of_host = self.0.find(':').unwrap_or(self.0.len()); + &self.0[..end_of_host] + } + } + + /// Returns true if and only if the server name is an IPv4 or IPv6 address. + pub fn is_ip_literal(&self) -> bool { + self.host().parse::().is_ok() || self.0.starts_with('[') + } +} + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -68,4 +93,46 @@ mod tests { fn dns_name_with_invalid_port() { assert!(<&ServerName>::try_from("matrix.org:hello").is_err()); } + + #[test] + fn parse_ipv4_host() { + let server_name = <&ServerName>::try_from("127.0.0.1").unwrap(); + assert!(server_name.is_ip_literal()); + assert_eq!(server_name.host(), "127.0.0.1"); + } + + #[test] + fn parse_ipv4_host_and_port() { + let server_name = <&ServerName>::try_from("1.1.1.1:12000").unwrap(); + assert!(server_name.is_ip_literal()); + assert_eq!(server_name.host(), "1.1.1.1"); + } + + #[test] + fn parse_ipv6() { + let server_name = <&ServerName>::try_from("[::1]").unwrap(); + assert!(server_name.is_ip_literal()); + assert_eq!(server_name.host(), "[::1]"); + } + + #[test] + fn parse_ipv6_with_port() { + let server_name = <&ServerName>::try_from("[1234:5678::abcd]:5678").unwrap(); + assert!(server_name.is_ip_literal()); + assert_eq!(server_name.host(), "[1234:5678::abcd]"); + } + + #[test] + fn parse_dns_name() { + let server_name = <&ServerName>::try_from("example.com").unwrap(); + assert!(!server_name.is_ip_literal()); + assert_eq!(server_name.host(), "example.com"); + } + + #[test] + fn parse_dns_name_with_port() { + let server_name = <&ServerName>::try_from("ruma.io:8080").unwrap(); + assert!(!server_name.is_ip_literal()); + assert_eq!(server_name.host(), "ruma.io"); + } }