132 lines
3.5 KiB
Rust
132 lines
3.5 KiB
Rust
//! Crate **ruma_identifiers** contains types for [Matrix](https://matrix.org/) identifiers
|
|
//! for events, rooms, room aliases, room versions, and users.
|
|
|
|
#![deny(
|
|
missing_copy_implementations,
|
|
missing_debug_implementations,
|
|
missing_docs,
|
|
warnings
|
|
)]
|
|
#![warn(
|
|
clippy::empty_line_after_outer_attr,
|
|
clippy::expl_impl_clone_on_copy,
|
|
clippy::if_not_else,
|
|
clippy::items_after_statements,
|
|
clippy::match_same_arms,
|
|
clippy::mem_forget,
|
|
clippy::missing_docs_in_private_items,
|
|
clippy::multiple_inherent_impl,
|
|
clippy::mut_mut,
|
|
clippy::needless_borrow,
|
|
clippy::needless_continue,
|
|
clippy::single_match_else,
|
|
clippy::unicode_not_nfc,
|
|
clippy::use_self,
|
|
clippy::used_underscore_binding,
|
|
clippy::wrong_pub_self_convention,
|
|
clippy::wrong_self_convention
|
|
)]
|
|
|
|
#[cfg(feature = "diesel")]
|
|
#[cfg_attr(feature = "diesel", macro_use)]
|
|
extern crate diesel;
|
|
|
|
use std::fmt::{Formatter, Result as FmtResult};
|
|
|
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
|
use url::Url;
|
|
|
|
pub use url::Host;
|
|
|
|
#[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, user_id::UserId,
|
|
};
|
|
|
|
pub mod device_id;
|
|
#[cfg(feature = "diesel")]
|
|
mod diesel_integration;
|
|
mod error;
|
|
mod event_id;
|
|
mod room_alias_id;
|
|
mod room_id;
|
|
mod room_id_or_room_alias_id;
|
|
mod room_version_id;
|
|
mod user_id;
|
|
|
|
/// All identifiers must be 255 bytes or less.
|
|
const MAX_BYTES: usize = 255;
|
|
/// The minimum number of characters an ID can be.
|
|
///
|
|
/// This is an optimization and not required by the spec. The shortest possible valid ID is a sigil
|
|
/// + a single character local ID + a colon + a single character hostname.
|
|
const MIN_CHARS: usize = 4;
|
|
/// The number of bytes in a valid sigil.
|
|
const SIGIL_BYTES: usize = 1;
|
|
|
|
/// `Display` implementation shared by identifier types.
|
|
fn display(
|
|
f: &mut Formatter<'_>,
|
|
sigil: char,
|
|
localpart: &str,
|
|
hostname: &Host,
|
|
port: u16,
|
|
) -> FmtResult {
|
|
if port == 443 {
|
|
write!(f, "{}{}:{}", sigil, localpart, hostname)
|
|
} else {
|
|
write!(f, "{}{}:{}:{}", sigil, localpart, hostname, port)
|
|
}
|
|
}
|
|
|
|
/// Generates a random identifier localpart.
|
|
fn generate_localpart(length: usize) -> String {
|
|
thread_rng()
|
|
.sample_iter(&Alphanumeric)
|
|
.take(length)
|
|
.collect()
|
|
}
|
|
|
|
/// Checks if an identifier is within the acceptable byte lengths.
|
|
fn validate_id(id: &str) -> Result<(), Error> {
|
|
if id.len() > MAX_BYTES {
|
|
return Err(Error::MaximumLengthExceeded);
|
|
}
|
|
|
|
if id.len() < MIN_CHARS {
|
|
return Err(Error::MinimumLengthNotSatisfied);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses the localpart, host, and port from a string identifier.
|
|
fn parse_id(required_sigil: char, id: &str) -> Result<(&str, Host, u16), Error> {
|
|
validate_id(id)?;
|
|
|
|
if !id.starts_with(required_sigil) {
|
|
return Err(Error::MissingSigil);
|
|
}
|
|
|
|
let delimiter_index = match id.find(':') {
|
|
Some(index) => index,
|
|
None => return Err(Error::MissingDelimiter),
|
|
};
|
|
|
|
let localpart = &id[1..delimiter_index];
|
|
let raw_host = &id[delimiter_index + SIGIL_BYTES..];
|
|
let url_string = format!("https://{}", raw_host);
|
|
let url = Url::parse(&url_string)?;
|
|
|
|
let host = match url.host() {
|
|
Some(host) => host.to_owned(),
|
|
None => return Err(Error::InvalidHost),
|
|
};
|
|
|
|
let port = url.port().unwrap_or(443);
|
|
|
|
Ok((localpart, host, port))
|
|
}
|