1#[cfg(feature = "e2e-encryption")]
18use std::sync::{Arc, RwLock};
19
20#[cfg(feature = "e2e-encryption")]
21use futures_core::Stream;
22#[cfg(feature = "e2e-encryption")]
23use futures_util::StreamExt;
24#[cfg(feature = "markdown")]
25use ruma::events::room::message::FormattedBody;
26use ruma::{
27 RoomAliasId,
28 events::{AnyMessageLikeEventContent, AnyStateEventContent},
29 serde::Raw,
30};
31use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
32#[cfg(feature = "e2e-encryption")]
33use tokio::sync::broadcast;
34#[cfg(feature = "e2e-encryption")]
35use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError};
36use url::Url;
37
38#[cfg(feature = "local-server")]
39pub mod local_server;
40
41#[cfg(feature = "local-server")]
42use self::local_server::QueryString;
43#[cfg(doc)]
44use crate::Room;
45
46#[cfg(feature = "e2e-encryption")]
52#[derive(Clone, Debug)]
53pub(crate) struct ChannelObservable<T: Clone + Send> {
54 value: Arc<RwLock<T>>,
55 channel: broadcast::Sender<T>,
56}
57
58#[cfg(feature = "e2e-encryption")]
59impl<T: Default + Clone + Send + 'static> Default for ChannelObservable<T> {
60 fn default() -> Self {
61 let value = Default::default();
62 Self::new(value)
63 }
64}
65
66#[cfg(feature = "e2e-encryption")]
67impl<T: 'static + Send + Clone> ChannelObservable<T> {
68 pub(crate) fn new(value: T) -> Self {
71 let channel = broadcast::Sender::new(100);
72 Self { value: RwLock::new(value).into(), channel }
73 }
74
75 pub(crate) fn subscribe(
80 &self,
81 ) -> impl Stream<Item = Result<T, BroadcastStreamRecvError>> + use<T> {
82 let current_value = self.value.read().unwrap().to_owned();
83 let initial_stream = tokio_stream::once(Ok(current_value));
84 let broadcast_stream = BroadcastStream::new(self.channel.subscribe());
85
86 initial_stream.chain(broadcast_stream)
87 }
88
89 pub(crate) fn set(&self, new_value: T) -> T {
91 let old_value = {
92 let mut guard = self.value.write().unwrap();
93 std::mem::replace(&mut (*guard), new_value.clone())
94 };
95
96 let _ = self.channel.send(new_value);
98
99 old_value
100 }
101
102 pub(crate) fn get(&self) -> T {
104 self.value.read().unwrap().to_owned()
105 }
106}
107
108pub trait IntoRawMessageLikeEventContent {
110 #[doc(hidden)]
111 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent>;
112}
113
114impl IntoRawMessageLikeEventContent for Raw<AnyMessageLikeEventContent> {
115 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
116 self
117 }
118}
119
120impl IntoRawMessageLikeEventContent for &Raw<AnyMessageLikeEventContent> {
121 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
122 self.clone()
123 }
124}
125
126impl IntoRawMessageLikeEventContent for JsonValue {
127 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
128 (&self).into_raw_message_like_event_content()
129 }
130}
131
132impl IntoRawMessageLikeEventContent for &JsonValue {
133 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
134 Raw::new(self).expect("serde_json::Value never fails to serialize").cast_unchecked()
135 }
136}
137
138impl IntoRawMessageLikeEventContent for Box<RawJsonValue> {
139 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
140 Raw::from_json(self)
141 }
142}
143
144impl IntoRawMessageLikeEventContent for &RawJsonValue {
145 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
146 self.to_owned().into_raw_message_like_event_content()
147 }
148}
149
150impl IntoRawMessageLikeEventContent for &Box<RawJsonValue> {
151 fn into_raw_message_like_event_content(self) -> Raw<AnyMessageLikeEventContent> {
152 self.clone().into_raw_message_like_event_content()
153 }
154}
155
156pub trait IntoRawStateEventContent {
158 #[doc(hidden)]
159 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent>;
160}
161
162impl IntoRawStateEventContent for Raw<AnyStateEventContent> {
163 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
164 self
165 }
166}
167
168impl IntoRawStateEventContent for &Raw<AnyStateEventContent> {
169 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
170 self.clone()
171 }
172}
173
174impl IntoRawStateEventContent for JsonValue {
175 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
176 (&self).into_raw_state_event_content()
177 }
178}
179
180impl IntoRawStateEventContent for &JsonValue {
181 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
182 Raw::new(self).expect("serde_json::Value never fails to serialize").cast_unchecked()
183 }
184}
185
186impl IntoRawStateEventContent for Box<RawJsonValue> {
187 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
188 Raw::from_json(self)
189 }
190}
191
192impl IntoRawStateEventContent for &RawJsonValue {
193 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
194 self.to_owned().into_raw_state_event_content()
195 }
196}
197
198impl IntoRawStateEventContent for &Box<RawJsonValue> {
199 fn into_raw_state_event_content(self) -> Raw<AnyStateEventContent> {
200 self.clone().into_raw_state_event_content()
201 }
202}
203
204const INVALID_ROOM_ALIAS_NAME_CHARS: &str = "#,:{}\\";
205
206pub fn is_room_alias_format_valid(alias: String) -> bool {
212 let alias_parts: Vec<&str> = alias.split(':').collect();
213 if alias_parts.len() != 2 {
214 return false;
215 }
216
217 let local_part = alias_parts[0];
218 let has_valid_format = local_part.chars().skip(1).all(|c| {
219 c.is_ascii()
220 && !c.is_whitespace()
221 && !c.is_control()
222 && !INVALID_ROOM_ALIAS_NAME_CHARS.contains(c)
223 });
224
225 let is_lowercase = alias.to_lowercase() == alias;
226
227 has_valid_format && is_lowercase && RoomAliasId::parse(alias).is_ok()
229}
230
231#[cfg(feature = "markdown")]
237pub fn formatted_body_from(
238 body: Option<&str>,
239 formatted_body: Option<FormattedBody>,
240) -> Option<FormattedBody> {
241 if formatted_body.is_some() { formatted_body } else { body.and_then(FormattedBody::markdown) }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
249pub enum UrlOrQuery {
250 Url(Url),
252
253 Query(String),
255}
256
257impl UrlOrQuery {
258 pub fn query(&self) -> Option<&str> {
262 match self {
263 Self::Url(url) => url.query(),
264 Self::Query(query) => Some(query),
265 }
266 }
267}
268
269impl From<Url> for UrlOrQuery {
270 fn from(value: Url) -> Self {
271 Self::Url(value)
272 }
273}
274
275#[cfg(feature = "local-server")]
276impl From<QueryString> for UrlOrQuery {
277 fn from(value: QueryString) -> Self {
278 Self::Query(value.0)
279 }
280}
281
282#[cfg(feature = "unstable-msc4354")]
285pub(crate) fn sticky_duration_ms(
286 duration: std::time::Duration,
287) -> ruma::events::sticky::StickyDurationMs {
288 ruma::events::sticky::StickyDurationMs::new_clamped(
289 u64::try_from(duration.as_millis()).unwrap_or(u64::MAX),
290 )
291}
292
293#[cfg(test)]
294mod test {
295 #[cfg(feature = "markdown")]
296 use ruma::events::room::message::FormattedBody;
297 #[cfg(feature = "markdown")]
298 use strass::assert_let;
299
300 #[cfg(feature = "markdown")]
301 use crate::utils::formatted_body_from;
302 use crate::utils::is_room_alias_format_valid;
303
304 #[cfg(feature = "e2e-encryption")]
305 #[test]
306 fn test_channel_observable_get_set() {
307 let observable = super::ChannelObservable::new(0);
308
309 assert_eq!(observable.get(), 0);
310 assert_eq!(observable.set(1), 0);
311 assert_eq!(observable.set(10), 1);
312 assert_eq!(observable.get(), 10);
313 }
314
315 #[test]
316 fn test_is_room_alias_format_valid_when_it_has_no_leading_hash_char_is_not_valid() {
317 assert!(!is_room_alias_format_valid("alias:domain.org".to_owned()))
318 }
319
320 #[test]
321 fn test_is_room_alias_format_valid_when_it_has_several_colon_chars_is_not_valid() {
322 assert!(!is_room_alias_format_valid("#alias:something:domain.org".to_owned()))
323 }
324
325 #[test]
326 fn test_is_room_alias_format_valid_when_it_has_no_colon_chars_is_not_valid() {
327 assert!(!is_room_alias_format_valid("#alias.domain.org".to_owned()))
328 }
329
330 #[test]
331 fn test_is_room_alias_format_valid_when_server_part_is_not_valid() {
332 assert!(!is_room_alias_format_valid("#alias:".to_owned()))
333 }
334
335 #[test]
336 fn test_is_room_alias_format_valid_when_name_part_has_whitespace_is_not_valid() {
337 assert!(!is_room_alias_format_valid("#alias with whitespace:domain.org".to_owned()))
338 }
339
340 #[test]
341 fn test_is_room_alias_format_valid_when_name_part_has_control_char_is_not_valid() {
342 assert!(!is_room_alias_format_valid("#alias\u{0009}:domain.org".to_owned()))
343 }
344
345 #[test]
346 fn test_is_room_alias_format_valid_when_name_part_has_invalid_char_is_not_valid() {
347 assert!(!is_room_alias_format_valid("#a#lias,{t\\est}:domain.org".to_owned()))
348 }
349
350 #[test]
351 fn test_is_room_alias_format_valid_when_name_part_is_not_lowercase_is_not_valid() {
352 assert!(!is_room_alias_format_valid("#Alias:domain.org".to_owned()))
353 }
354
355 #[test]
356 fn test_is_room_alias_format_valid_when_server_part_is_not_lowercase_is_not_valid() {
357 assert!(!is_room_alias_format_valid("#alias:Domain.org".to_owned()))
358 }
359
360 #[test]
361 fn test_is_room_alias_format_valid_when_has_valid_format() {
362 assert!(is_room_alias_format_valid("#alias.test:domain.org".to_owned()))
363 }
364
365 #[test]
366 #[cfg(feature = "markdown")]
367 fn test_formatted_body_from_nothing_returns_none() {
368 std::assert_matches!(formatted_body_from(None, None), None);
369 }
370
371 #[test]
372 #[cfg(feature = "markdown")]
373 fn test_formatted_body_from_only_formatted_body_returns_the_formatted_body() {
374 let formatted_body = FormattedBody::html(r"<h1>Hello!</h1>");
375
376 assert_let!(
377 Some(result_formatted_body) = formatted_body_from(None, Some(formatted_body.clone()))
378 );
379
380 assert_eq!(formatted_body.body, result_formatted_body.body);
381 assert_eq!(result_formatted_body.format, result_formatted_body.format);
382 }
383
384 #[test]
385 #[cfg(feature = "markdown")]
386 fn test_formatted_body_from_markdown_body_returns_a_processed_formatted_body() {
387 let markdown_body = Some(r"# Parsed");
388
389 assert_let!(Some(result_formatted_body) = formatted_body_from(markdown_body, None));
390
391 let expected_formatted_body = FormattedBody::html("<h1>Parsed</h1>\n".to_owned());
392 assert_eq!(expected_formatted_body.body, result_formatted_body.body);
393 assert_eq!(expected_formatted_body.format, result_formatted_body.format);
394 }
395
396 #[test]
397 #[cfg(feature = "markdown")]
398 fn test_formatted_body_from_body_and_formatted_body_returns_the_formatted_body() {
399 let markdown_body = Some(r"# Markdown");
400 let formatted_body = FormattedBody::html(r"<h1>HTML</h1>");
401
402 assert_let!(
403 Some(result_formatted_body) =
404 formatted_body_from(markdown_body, Some(formatted_body.clone()))
405 );
406
407 assert_eq!(formatted_body.body, result_formatted_body.body);
408 assert_eq!(formatted_body.format, result_formatted_body.format);
409 }
410}