From 93f75353a7480237a501a72b53cdce5f311e2880 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 13:12:38 +0200 Subject: [PATCH] Move user ID localpart classification into a public function --- CHANGELOG.md | 3 +++ src/lib.rs | 7 +++---- src/user_id.rs | 40 +++++++++++++++++++++++++++------------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07fe41d8..c50abcc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ Improvements: * Add support for historical uppercase MXIDs * Made all dependencies optional * `serde` is the only one that is enabled by default +* The `user_id` module is now public and contains `fn localpart_is_fully_conforming` + * This function can be used to determine whether a user name (the localpart of a user ID) is valid + without actually constructing a full user ID first # 0.14.1 diff --git a/src/lib.rs b/src/lib.rs index 8ea4364e..c008e968 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,10 +20,9 @@ use std::num::NonZeroU8; use serde::de::{self, Deserialize as _, Deserializer, Unexpected}; #[doc(inline)] -pub use crate::device_id::DeviceId; pub use crate::{ - error::Error, event_id::EventId, room_alias_id::RoomAliasId, room_id::RoomId, - room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId, + device_id::DeviceId, error::Error, event_id::EventId, room_alias_id::RoomAliasId, + room_id::RoomId, room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId, server_name::is_valid_server_name, user_id::UserId, }; @@ -40,7 +39,7 @@ mod room_id; mod room_id_or_room_alias_id; mod room_version_id; mod server_name; -mod user_id; +pub mod user_id; /// All identifiers must be 255 bytes or less. const MAX_BYTES: usize = 255; diff --git a/src/user_id.rs b/src/user_id.rs index 31a67100..96e44a07 100644 --- a/src/user_id.rs +++ b/src/user_id.rs @@ -84,29 +84,43 @@ impl TryFrom> for UserId { let colon_idx = parse_id(&user_id, &['@'])?; let localpart = &user_id[1..colon_idx.get() as usize]; - // See https://matrix.org/docs/spec/appendices#user-identifiers - let is_fully_conforming = localpart.bytes().all(|b| match b { - b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.' | b'=' | b'_' | b'/' => true, - _ => false, - }); - - // If it's not fully conforming, check if it contains characters that are also disallowed - // for historical user IDs. If there are, return an error. - // See https://matrix.org/docs/spec/appendices#historical-user-ids - if !is_fully_conforming && localpart.bytes().any(|b| b < 0x21 || b == b':' || b > 0x7E) { - return Err(Error::InvalidCharacters); - } + let is_historical = localpart_is_fully_comforming(localpart)?; Ok(Self { full_id: user_id.into_owned(), colon_idx, - is_historical: !is_fully_conforming, + is_historical: !is_historical, }) } } common_impls!(UserId, "a Matrix user ID"); +/// Check whether the given user id localpart is valid and fully conforming +/// +/// Returns an `Err` for invalid user ID localparts, `Ok(false)` for historical user ID localparts +/// and `Ok(true)` for fully conforming user ID localparts. +pub fn localpart_is_fully_comforming(localpart: &str) -> Result { + if localpart.is_empty() { + return Err(Error::InvalidLocalPart); + } + + // See https://matrix.org/docs/spec/appendices#user-identifiers + let is_fully_conforming = localpart.bytes().all(|b| match b { + b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.' | b'=' | b'_' | b'/' => true, + _ => false, + }); + + // If it's not fully conforming, check if it contains characters that are also disallowed + // for historical user IDs. If there are, return an error. + // See https://matrix.org/docs/spec/appendices#historical-user-ids + if !is_fully_conforming && localpart.bytes().any(|b| b < 0x21 || b == b':' || b > 0x7E) { + Err(Error::InvalidCharacters) + } else { + Ok(is_fully_conforming) + } +} + #[cfg(test)] mod tests { use std::convert::TryFrom;