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.
This commit is contained in:
Olivia Lee 2025-07-19 23:19:41 -07:00
parent 9a142c7557
commit 55a01e7113
No known key found for this signature in database
GPG key ID: 54D568A15B9CD1F9
2 changed files with 36 additions and 19 deletions

View file

@ -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 29. Fix bug where ban reasons would be ignored when the banned user already had
a member event in the room. a member event in the room.
([!185](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/185)) ([!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 ### Added

View file

@ -1,16 +1,13 @@
use std::{cmp::Ordering, collections::BTreeMap, sync::Arc}; use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, sync::Arc};
use ruma::{ use ruma::{
canonical_json::redact_content_in_place, canonical_json::redact_content_in_place,
events::{ events::{
room::{ room::member::RoomMemberEventContent,
member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent, AnyEphemeralRoomEvent,
redaction::RoomRedactionEventContent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
}, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent,
space::child::HierarchySpaceChildEvent, TimelineEventType,
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent,
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
AnyTimelineEvent, StateEvent, TimelineEventType,
}, },
serde::Raw, serde::Raw,
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, state_res, CanonicalJsonObject, CanonicalJsonValue, EventId,
@ -161,24 +158,41 @@ impl PduEvent {
pub(crate) fn copy_redacts( pub(crate) fn copy_redacts(
&self, &self,
) -> (Option<Arc<EventId>>, Box<RawJsonValue>) { ) -> (Option<Arc<EventId>>, Box<RawJsonValue>) {
#[derive(Deserialize)]
struct ExtractRedacts<'a> {
#[serde(borrow)]
redacts: Option<Cow<'a, EventId>>,
}
if self.kind != TimelineEventType::RoomRedaction { if self.kind != TimelineEventType::RoomRedaction {
return (self.redacts.clone(), self.content.clone()); return (self.redacts.clone(), self.content.clone());
} }
let Ok(mut content) = serde_json::from_str::<RoomRedactionEventContent>( let Ok(extract) =
self.content.get(), serde_json::from_str::<ExtractRedacts<'_>>(self.content.get())
) else { else {
return (self.redacts.clone(), self.content.clone()); 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()) (Some(redacts.into()), self.content.clone())
} else if let Some(redacts) = self.redacts.clone() { } else if let Some(redacts) = self.redacts.clone() {
content.redacts = Some(redacts.into()); let content = serde_json::from_str::<BTreeMap<&str, &RawJsonValue>>(
( self.content.get(),
self.redacts.clone(), );
to_raw_value(&content) let mut content = match content {
.expect("Must be valid, we only added redacts field"), 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 { } else {
(self.redacts.clone(), self.content.clone()) (self.redacts.clone(), self.content.clone())
} }