palpo_identifiers_validation/user_id.rs
1use crate::{Error, parse_id};
2
3pub fn validate(s: &str) -> Result<(), Error> {
4 let colon_idx = parse_id(s, b'@')?;
5 let localpart = &s[1..colon_idx];
6 let _ = localpart_is_fully_conforming(localpart)?;
7
8 Ok(())
9}
10
11/// Check whether the given user id localpart is valid and fully conforming
12///
13/// Returns an `Err` for invalid user ID localparts, `Ok(false)` for historical user ID localparts
14/// and `Ok(true)` for fully conforming user ID localparts.
15///
16/// With the `compat` feature enabled, this will also return `Ok(false)` for invalid user ID
17/// localparts. User IDs that don't even meet the historical user ID restrictions exist in the wild
18/// due to Synapse allowing them over federation. This will likely be fixed in an upcoming room
19/// version; see [MSC2828](https://github.com/matrix-org/matrix-spec-proposals/pull/2828).
20pub fn localpart_is_fully_conforming(localpart: &str) -> Result<bool, Error> {
21 // See https://spec.matrix.org/latest/appendices/#user-identifiers
22 let is_fully_conforming = !localpart.is_empty()
23 && localpart
24 .bytes()
25 .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.' | b'=' | b'_' | b'/' | b'+'));
26
27 if !is_fully_conforming {
28 // If it's not fully conforming, check if it contains characters that are also disallowed
29 // for historical user IDs, or is empty. If that's the case, return an error.
30 // See https://spec.matrix.org/latest/appendices/#historical-user-ids
31 // #[cfg(not(feature = "compat-user-id"))]
32 // let is_invalid = localpart.is_empty() || localpart.bytes().any(|b| b < 0x21 || b == b':' || b > 0x7E);
33
34 // In compat mode, allow anything except `:` to match Synapse. The `:` check is only needed
35 // because this function can be called through `UserId::parse_with_servername`, otherwise
36 // it would be impossible for the input to contain a `:`.
37 // #[cfg(feature = "compat-user-id")]
38 let is_invalid = localpart.as_bytes().contains(&b':');
39
40 if is_invalid {
41 return Err(Error::InvalidCharacters);
42 }
43 }
44
45 Ok(is_fully_conforming)
46}