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
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

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::{
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<Arc<EventId>>, Box<RawJsonValue>) {
#[derive(Deserialize)]
struct ExtractRedacts<'a> {
#[serde(borrow)]
redacts: Option<Cow<'a, EventId>>,
}
if self.kind != TimelineEventType::RoomRedaction {
return (self.redacts.clone(), self.content.clone());
}
let Ok(mut content) = serde_json::from_str::<RoomRedactionEventContent>(
self.content.get(),
) else {
let Ok(extract) =
serde_json::from_str::<ExtractRedacts<'_>>(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::<BTreeMap<&str, &RawJsonValue>>(
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())
}