use std::{cmp::Ordering, collections::BTreeMap, sync::Arc}; use ruma::{ canonical_json::redact_content_in_place, events::{ room::{member::RoomMemberEventContent, redaction::RoomRedactionEventContent}, space::child::HierarchySpaceChildEvent, AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, TimelineEventType, }, serde::Raw, state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UInt, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::{ json, value::{to_raw_value, RawValue as RawJsonValue}, }; use tracing::warn; use crate::{services, Error}; /// Content hashes of a PDU. #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct EventHash { /// The SHA-256 hash. pub(crate) sha256: String, } #[derive(Clone, Deserialize, Serialize, Debug)] pub(crate) struct PduEvent { pub(crate) event_id: Arc, pub(crate) room_id: OwnedRoomId, pub(crate) sender: OwnedUserId, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) origin: Option, pub(crate) origin_server_ts: UInt, #[serde(rename = "type")] pub(crate) kind: TimelineEventType, pub(crate) content: Box, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) state_key: Option, pub(crate) prev_events: Vec>, pub(crate) depth: UInt, pub(crate) auth_events: Vec>, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) redacts: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) unsigned: Option>, pub(crate) hashes: EventHash, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) signatures: Option>, /* BTreeMap, BTreeMap> */ } impl PduEvent { #[tracing::instrument(skip(self))] pub(crate) fn redact(&mut self, room_version_id: RoomVersionId, reason: &PduEvent) -> crate::Result<()> { self.unsigned = None; let mut content = serde_json::from_str(self.content.get()) .map_err(|_| Error::bad_database("PDU in db has invalid content."))?; redact_content_in_place(&mut content, &room_version_id, self.kind.to_string()) .map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; self.unsigned = Some( to_raw_value(&json!({ "redacted_because": serde_json::to_value(reason).expect("to_value(PduEvent) always works") })) .expect("to string always works"), ); self.content = to_raw_value(&content).expect("to string always works"); Ok(()) } pub(crate) fn remove_transaction_id(&mut self) -> crate::Result<()> { if let Some(unsigned) = &self.unsigned { let mut unsigned: BTreeMap> = serde_json::from_str(unsigned.get()) .map_err(|_| Error::bad_database("Invalid unsigned in pdu event"))?; unsigned.remove("transaction_id"); self.unsigned = Some(to_raw_value(&unsigned).expect("unsigned is valid")); } Ok(()) } pub(crate) fn add_age(&mut self) -> crate::Result<()> { let mut unsigned: BTreeMap> = self .unsigned .as_ref() .map_or_else(|| Ok(BTreeMap::new()), |u| serde_json::from_str(u.get())) .map_err(|_| Error::bad_database("Invalid unsigned in pdu event"))?; unsigned.insert("age".to_owned(), to_raw_value(&1).unwrap()); self.unsigned = Some(to_raw_value(&unsigned).expect("unsigned is valid")); Ok(()) } /// Copies the `redacts` property of the event to the `content` dict and /// vice-versa. /// /// This follows the specification's /// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property): /// /// > For backwards-compatibility with older clients, servers should add a /// > redacts /// > property to the top level of m.room.redaction events in when serving /// > such events /// > over the Client-Server API. /// /// > For improved compatibility with newer clients, servers should add a /// > redacts property /// > to the content of m.room.redaction events in older room versions when /// > serving /// > such events over the Client-Server API. #[must_use] pub(crate) fn copy_redacts(&self) -> (Option>, Box) { if self.kind == TimelineEventType::RoomRedaction { if let Ok(mut content) = serde_json::from_str::(self.content.get()) { if let Some(redacts) = content.redacts { return (Some(redacts.into()), self.content.clone()); } else if let Some(redacts) = self.redacts.clone() { content.redacts = Some(redacts.into()); return ( self.redacts.clone(), to_raw_value(&content).expect("Must be valid, we only added redacts field"), ); } } } (self.redacts.clone(), self.content.clone()) } #[tracing::instrument(skip(self))] pub(crate) fn to_sync_room_event(&self) -> Raw { let (redacts, content) = self.copy_redacts(); let mut json = json!({ "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } serde_json::from_value(json).expect("Raw::from_value always works") } /// This only works for events that are also AnyRoomEvents. #[tracing::instrument(skip(self))] pub(crate) fn to_any_event(&self) -> Raw { let (redacts, content) = self.copy_redacts(); let mut json = json!({ "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, "room_id": self.room_id, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_room_event(&self) -> Raw { let (redacts, content) = self.copy_redacts(); let mut json = json!({ "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, "room_id": self.room_id, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_message_like_event(&self) -> Raw { let (redacts, content) = self.copy_redacts(); let mut json = json!({ "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, "room_id": self.room_id, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_state_event(&self) -> Raw { let mut json = json!({ "content": self.content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, "room_id": self.room_id, "state_key": self.state_key, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_sync_state_event(&self) -> Raw { let mut json = json!({ "content": self.content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, "state_key": self.state_key, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_stripped_state_event(&self) -> Raw { let json = json!({ "content": self.content, "type": self.kind, "sender": self.sender, "state_key": self.state_key, }); serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_stripped_spacechild_state_event(&self) -> Raw { let json = json!({ "content": self.content, "type": self.kind, "sender": self.sender, "state_key": self.state_key, "origin_server_ts": self.origin_server_ts, }); serde_json::from_value(json).expect("Raw::from_value always works") } #[tracing::instrument(skip(self))] pub(crate) fn to_member_event(&self) -> Raw> { let mut json = json!({ "content": self.content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, "origin_server_ts": self.origin_server_ts, "redacts": self.redacts, "room_id": self.room_id, "state_key": self.state_key, }); if let Some(unsigned) = &self.unsigned { json["unsigned"] = json!(unsigned); } serde_json::from_value(json).expect("Raw::from_value always works") } /// This does not return a full `Pdu` it is only to satisfy ruma's types. #[tracing::instrument] pub(crate) fn convert_to_outgoing_federation_event(mut pdu_json: CanonicalJsonObject) -> Box { if let Some(unsigned) = pdu_json .get_mut("unsigned") .and_then(|val| val.as_object_mut()) { unsigned.remove("transaction_id"); } if let Some(room_id) = pdu_json .get("room_id") .and_then(|val| RoomId::parse(val.as_str()?).ok()) { if let Ok(room_version_id) = services().rooms.state.get_room_version(&room_id) { // room v3 and above removed the "event_id" field from remote PDU format match room_version_id { RoomVersionId::V1 | RoomVersionId::V2 => {}, _ => { pdu_json.remove("event_id"); }, }; } else { pdu_json.remove("event_id"); } } else { pdu_json.remove("event_id"); } // TODO: another option would be to convert it to a canonical string to validate // size and return a Result> // serde_json::from_str::>( // ruma::serde::to_canonical_json_string(pdu_json).expect("CanonicalJson is // valid serde_json::Value"), ) // .expect("Raw::from_value always works") to_raw_value(&pdu_json).expect("CanonicalJson is valid serde_json::Value") } pub(crate) fn from_id_val(event_id: &EventId, mut json: CanonicalJsonObject) -> Result { json.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned())); serde_json::from_value(serde_json::to_value(json).expect("valid JSON")) } } impl state_res::Event for PduEvent { type Id = Arc; fn event_id(&self) -> &Self::Id { &self.event_id } fn room_id(&self) -> &RoomId { &self.room_id } fn sender(&self) -> &UserId { &self.sender } fn event_type(&self) -> &TimelineEventType { &self.kind } fn content(&self) -> &RawJsonValue { &self.content } fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { MilliSecondsSinceUnixEpoch(self.origin_server_ts) } fn state_key(&self) -> Option<&str> { self.state_key.as_deref() } fn prev_events(&self) -> Box + '_> { Box::new(self.prev_events.iter()) } fn auth_events(&self) -> Box + '_> { Box::new(self.auth_events.iter()) } fn redacts(&self) -> Option<&Self::Id> { self.redacts.as_ref() } } // These impl's allow us to dedup state snapshots when resolving state // for incoming events (federation/send/{txn}). impl Eq for PduEvent {} impl PartialEq for PduEvent { fn eq(&self, other: &Self) -> bool { self.event_id == other.event_id } } impl PartialOrd for PduEvent { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for PduEvent { fn cmp(&self, other: &Self) -> Ordering { self.event_id.cmp(&other.event_id) } } /// Generates a correct eventId for the incoming pdu. /// /// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap`. pub(crate) fn gen_event_id_canonical_json( pdu: &RawJsonValue, room_version_id: &RoomVersionId, ) -> crate::Result<(OwnedEventId, CanonicalJsonObject)> { let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { warn!("Error parsing incoming event {:?}: {:?}", pdu, e); Error::BadServerResponse("Invalid PDU in server response") })?; let event_id = format!( "${}", // Anything higher than version3 behaves the same ruma::signatures::reference_hash(&value, room_version_id).expect("ruma can calculate reference hashes") ) .try_into() .expect("ruma's reference hashes are valid event ids"); Ok((event_id, value)) } /// Build the start of a PDU in order to add it to the Database. #[derive(Debug, Deserialize)] pub(crate) struct PduBuilder { #[serde(rename = "type")] pub(crate) event_type: TimelineEventType, pub(crate) content: Box, pub(crate) unsigned: Option>, pub(crate) state_key: Option, pub(crate) redacts: Option>, }