Restore server name validation
This commit is contained in:
parent
b48235f496
commit
e8f7bd9f6d
@ -16,6 +16,8 @@ Breaking changes:
|
|||||||
* Note that hashes are generally only guaranteed consistent in the lifetime of the program
|
* Note that hashes are generally only guaranteed consistent in the lifetime of the program
|
||||||
though, so do not persist them!
|
though, so do not persist them!
|
||||||
* The `hostname` methods have been updated to return string slices instead of `&url::Host`
|
* The `hostname` methods have been updated to return string slices instead of `&url::Host`
|
||||||
|
* `Error::InvalidHost` has been renamed to `Error::InvalidServerName`, because it also covers errors
|
||||||
|
in the port, not just the host part section of the server name
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ pub enum Error {
|
|||||||
InvalidCharacters,
|
InvalidCharacters,
|
||||||
/// The localpart of the ID string is not valid (because it is empty).
|
/// The localpart of the ID string is not valid (because it is empty).
|
||||||
InvalidLocalPart,
|
InvalidLocalPart,
|
||||||
/// The domain part of the the ID string is not a valid IP address or DNS name.
|
/// The server name part of the the ID string is not a valid server name.
|
||||||
InvalidHost,
|
InvalidServerName,
|
||||||
/// The ID exceeds 255 bytes (or 32 codepoints for a room version ID.)
|
/// The ID exceeds 255 bytes (or 32 codepoints for a room version ID.)
|
||||||
MaximumLengthExceeded,
|
MaximumLengthExceeded,
|
||||||
/// The ID is less than 4 characters (or is an empty room version ID.)
|
/// The ID is less than 4 characters (or is an empty room version ID.)
|
||||||
@ -28,7 +28,7 @@ impl Display for Error {
|
|||||||
let message = match self {
|
let message = match self {
|
||||||
Error::InvalidCharacters => "localpart contains invalid characters",
|
Error::InvalidCharacters => "localpart contains invalid characters",
|
||||||
Error::InvalidLocalPart => "localpart is empty",
|
Error::InvalidLocalPart => "localpart is empty",
|
||||||
Error::InvalidHost => "server name is not a valid IP address or domain name",
|
Error::InvalidServerName => "server name is not a valid IP address or domain name",
|
||||||
Error::MaximumLengthExceeded => "ID exceeds 255 bytes",
|
Error::MaximumLengthExceeded => "ID exceeds 255 bytes",
|
||||||
Error::MinimumLengthNotSatisfied => "ID must be at least 4 characters",
|
Error::MinimumLengthNotSatisfied => "ID must be at least 4 characters",
|
||||||
Error::MissingDelimiter => "colon is required between localpart and server name",
|
Error::MissingDelimiter => "colon is required between localpart and server name",
|
||||||
|
@ -162,10 +162,10 @@ mod tests {
|
|||||||
assert_eq!(id_str.len(), 31);
|
assert_eq!(id_str.len(), 31);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn generate_random_invalid_event_id() {
|
fn generate_random_invalid_event_id() {
|
||||||
assert!(EventId::new("").is_err());
|
assert!(EventId::new("").is_err());
|
||||||
}*/
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_valid_original_event_id() {
|
fn serialize_valid_original_event_id() {
|
||||||
@ -275,11 +275,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn invalid_event_id_host() {
|
fn invalid_event_id_host() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EventId::try_from("$39hvsi03hlne:/").unwrap_err(),
|
EventId::try_from("$39hvsi03hlne:/").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ mod tests {
|
|||||||
fn invalid_event_id_port() {
|
fn invalid_event_id_port() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EventId::try_from("$39hvsi03hlne:example.com:notaport").unwrap_err(),
|
EventId::try_from("$39hvsi03hlne:example.com:notaport").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ use serde::de::{self, Deserialize as _, Deserializer, Unexpected};
|
|||||||
pub use crate::device_id::DeviceId;
|
pub use crate::device_id::DeviceId;
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
error::Error, event_id::EventId, room_alias_id::RoomAliasId, room_id::RoomId,
|
error::Error, event_id::EventId, room_alias_id::RoomAliasId, room_id::RoomId,
|
||||||
room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId, user_id::UserId,
|
room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId,
|
||||||
|
server_name::is_valid_server_name, user_id::UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -38,6 +39,7 @@ mod room_alias_id;
|
|||||||
mod room_id;
|
mod room_id;
|
||||||
mod room_id_or_room_alias_id;
|
mod room_id_or_room_alias_id;
|
||||||
mod room_version_id;
|
mod room_version_id;
|
||||||
|
mod server_name;
|
||||||
mod user_id;
|
mod user_id;
|
||||||
|
|
||||||
/// All identifiers must be 255 bytes or less.
|
/// All identifiers must be 255 bytes or less.
|
||||||
@ -79,8 +81,8 @@ fn parse_id(id: &str, valid_sigils: &[char]) -> Result<NonZeroU8, Error> {
|
|||||||
validate_id(id, valid_sigils)?;
|
validate_id(id, valid_sigils)?;
|
||||||
|
|
||||||
let colon_idx = id.find(':').ok_or(Error::MissingDelimiter)?;
|
let colon_idx = id.find(':').ok_or(Error::MissingDelimiter)?;
|
||||||
if colon_idx == id.len() - 1 {
|
if !is_valid_server_name(&id[colon_idx + 1..]) {
|
||||||
return Err(Error::InvalidHost);
|
return Err(Error::InvalidServerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
match NonZeroU8::new(colon_idx as u8) {
|
match NonZeroU8::new(colon_idx as u8) {
|
||||||
|
@ -144,11 +144,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn invalid_room_alias_id_host() {
|
fn invalid_room_alias_id_host() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RoomAliasId::try_from("#ruma:/").unwrap_err(),
|
RoomAliasId::try_from("#ruma:/").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ mod tests {
|
|||||||
fn invalid_room_alias_id_port() {
|
fn invalid_room_alias_id_port() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RoomAliasId::try_from("#ruma:example.com:notaport").unwrap_err(),
|
RoomAliasId::try_from("#ruma:example.com:notaport").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,10 +101,10 @@ mod tests {
|
|||||||
assert_eq!(id_str.len(), 31);
|
assert_eq!(id_str.len(), 31);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn generate_random_invalid_room_id() {
|
fn generate_random_invalid_room_id() {
|
||||||
assert!(RoomId::new("").is_err());
|
assert!(RoomId::new("").is_err());
|
||||||
}*/
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_valid_room_id() {
|
fn serialize_valid_room_id() {
|
||||||
@ -162,11 +162,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn invalid_room_id_host() {
|
fn invalid_room_id_host() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RoomId::try_from("!29fhd83h92h0:/").unwrap_err(),
|
RoomId::try_from("!29fhd83h92h0:/").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ mod tests {
|
|||||||
fn invalid_room_id_port() {
|
fn invalid_room_id_port() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RoomId::try_from("!29fhd83h92h0:example.com:notaport").unwrap_err(),
|
RoomId::try_from("!29fhd83h92h0:example.com:notaport").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
96
src/server_name.rs
Normal file
96
src/server_name.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/// Check whether a given string is a valid server name according to [the specification][].
|
||||||
|
///
|
||||||
|
/// [the specification]: https://matrix.org/docs/spec/appendices#server-name
|
||||||
|
pub fn is_valid_server_name(name: &str) -> bool {
|
||||||
|
use std::net::Ipv6Addr;
|
||||||
|
|
||||||
|
let end_of_host = if name.starts_with('[') {
|
||||||
|
let end_of_ipv6 = match name.find(']') {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if name[1..end_of_ipv6].parse::<Ipv6Addr>().is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_of_ipv6 + 1
|
||||||
|
} else {
|
||||||
|
let end_of_host = name.find(':').unwrap_or_else(|| name.len());
|
||||||
|
|
||||||
|
if name[..end_of_host]
|
||||||
|
.bytes()
|
||||||
|
.any(|byte| !(byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'.'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_of_host
|
||||||
|
};
|
||||||
|
|
||||||
|
if name.len() == end_of_host {
|
||||||
|
true
|
||||||
|
} else if name.as_bytes()[end_of_host] != b':' {
|
||||||
|
// hostname is followed by something other than ":port"
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
// are the remaining characters after ':' a valid port?
|
||||||
|
name[end_of_host + 1..].parse::<u16>().is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::is_valid_server_name;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv4_host() {
|
||||||
|
assert!(is_valid_server_name("127.0.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv4_host_and_port() {
|
||||||
|
assert!(is_valid_server_name("1.1.1.1:12000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv6() {
|
||||||
|
assert!(is_valid_server_name("[::1]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv6_with_port() {
|
||||||
|
assert!(is_valid_server_name("[1234:5678::abcd]:5678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dns_name() {
|
||||||
|
assert!(is_valid_server_name("example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dns_name_with_port() {
|
||||||
|
assert!(is_valid_server_name("ruma.io:8080"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_ipv6() {
|
||||||
|
assert!(!is_valid_server_name("[test::1]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv4_with_invalid_port() {
|
||||||
|
assert!(!is_valid_server_name("127.0.0.1:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv6_with_invalid_port() {
|
||||||
|
assert!(!is_valid_server_name("[fe80::1]:100000"));
|
||||||
|
assert!(!is_valid_server_name("[fe80::1]!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dns_name_with_invalid_port() {
|
||||||
|
assert!(!is_valid_server_name("matrix.org:hello"));
|
||||||
|
}
|
||||||
|
}
|
@ -209,16 +209,19 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn invalid_user_id_host() {
|
fn invalid_user_id_host() {
|
||||||
assert_eq!(UserId::try_from("@carl:/").unwrap_err(), Error::InvalidHost);
|
assert_eq!(
|
||||||
|
UserId::try_from("@carl:/").unwrap_err(),
|
||||||
|
Error::InvalidServerName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_user_id_port() {
|
fn invalid_user_id_port() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
UserId::try_from("@carl:example.com:notaport").unwrap_err(),
|
UserId::try_from("@carl:example.com:notaport").unwrap_err(),
|
||||||
Error::InvalidHost
|
Error::InvalidServerName
|
||||||
);
|
);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user