From 55a01e711320d49353af309c02beeb92d76c386e Mon Sep 17 00:00:00 2001 From: Olivia Lee Date: Sat, 19 Jul 2025 23:19:41 -0700 Subject: [PATCH] don't strip unknown keys when copying redacts property to/from content Servers are required to preserve unknown properties in event content, since they may be added by a future version of the spec. Round-tripping through RoomRedactionEventContent results in dropping all unknown properties. --- book/changelog.md | 3 +++ src/service/pdu.rs | 52 +++++++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/book/changelog.md b/book/changelog.md index 23ae40d6..cadf4c25 100644 --- a/book/changelog.md +++ b/book/changelog.md @@ -245,6 +245,9 @@ This will be the first release of Grapevine since it was forked from Conduit 29. Fix bug where ban reasons would be ignored when the banned user already had a member event in the room. ([!185](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/185)) +30. Stop stripping unknown properties from redaction events before sending them + to clients. + ([!191](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/191)) ### Added diff --git a/src/service/pdu.rs b/src/service/pdu.rs index 1131a5b8..fe13f1d2 100644 --- a/src/service/pdu.rs +++ b/src/service/pdu.rs @@ -1,16 +1,13 @@ -use std::{cmp::Ordering, collections::BTreeMap, sync::Arc}; +use std::{borrow::Cow, 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, + room::member::RoomMemberEventContent, + space::child::HierarchySpaceChildEvent, AnyEphemeralRoomEvent, + AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, + AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, + TimelineEventType, }, serde::Raw, state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, @@ -161,24 +158,41 @@ impl PduEvent { pub(crate) fn copy_redacts( &self, ) -> (Option>, Box) { + #[derive(Deserialize)] + struct ExtractRedacts<'a> { + #[serde(borrow)] + redacts: Option>, + } + if self.kind != TimelineEventType::RoomRedaction { return (self.redacts.clone(), self.content.clone()); } - let Ok(mut content) = serde_json::from_str::( - self.content.get(), - ) else { + let Ok(extract) = + serde_json::from_str::>(self.content.get()) + else { return (self.redacts.clone(), self.content.clone()); }; - if let Some(redacts) = content.redacts { + if let Some(redacts) = extract.redacts { (Some(redacts.into()), self.content.clone()) } else if let Some(redacts) = self.redacts.clone() { - content.redacts = Some(redacts.into()); - ( - self.redacts.clone(), - to_raw_value(&content) - .expect("Must be valid, we only added redacts field"), - ) + let content = serde_json::from_str::>( + self.content.get(), + ); + let mut content = match content { + Ok(content) => content, + Err(error) => { + warn!(%error, "PDU is not a valid json object"); + return (self.redacts.clone(), self.content.clone()); + } + }; + + let redacts_json = to_raw_value(&redacts) + .expect("all strings should be representable as json"); + content.insert("redacts", &redacts_json); + let content_json = to_raw_value(&content) + .expect("Must be valid, we only added redacts field"); + (self.redacts.clone(), content_json) } else { (self.redacts.clone(), self.content.clone()) }