Skip to content

Commit 3276bc8

Browse files
zecakehHywan
authored andcommitted
refactor(base): Don't drop whole m.room.create if predecessor is invalid
The `m.room.create` event contains at lot of important information for a room, like its type (i.e. whether it is a space), its version and its creator(s) (which are important in room version 12). So ignoring the event completely might break a room. Instead what we do here is simply ignore the `predecessor` field if it is considered invalid, allowing us to access the other fields. Signed-off-by: Kévin Commaille <[email protected]>
1 parent a4da6ba commit 3276bc8

File tree

1 file changed

+38
-26
lines changed

1 file changed

+38
-26
lines changed

crates/matrix-sdk-base/src/response_processors/state_events.rs

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -107,24 +107,16 @@ pub mod sync {
107107
}
108108

109109
AnySyncStateEvent::RoomCreate(create) => {
110-
if super::is_create_event_valid(
110+
let edited_create = super::validate_create_event_predecessor(
111111
context,
112112
room_info.room_id(),
113113
create,
114114
state_store,
115-
) {
116-
room_info.handle_state_event(event);
117-
} else {
118-
error!(
119-
room_id = ?room_info.room_id(),
120-
?create,
121-
"`m.create.tombstone` event is invalid, it creates a loop"
122-
);
115+
);
123116

124-
// Do not add the event to `room_info`.
125-
// Do not add the event to `context.state_changes.state`.
126-
continue;
127-
}
117+
room_info.handle_state_event(
118+
edited_create.map(Into::into).as_ref().unwrap_or(event),
119+
);
128120
}
129121

130122
AnySyncStateEvent::RoomTombstone(tombstone) => {
@@ -317,31 +309,47 @@ where
317309
.unzip()
318310
}
319311

320-
/// Check if `m.room.create` isn't creating a loop of rooms.
321-
pub fn is_create_event_valid(
312+
/// Check if the `predecessor` in `m.room.create` isn't creating a loop of
313+
/// rooms.
314+
///
315+
/// If it is, we return a clone of the event with the predecessor removed.
316+
pub fn validate_create_event_predecessor(
322317
context: &mut Context,
323318
room_id: &RoomId,
324319
event: &SyncStateEvent<RoomCreateEventContent>,
325320
state_store: &BaseStateStore,
326-
) -> bool {
321+
) -> Option<SyncStateEvent<RoomCreateEventContent>> {
327322
let mut already_seen = BTreeSet::new();
328323
already_seen.insert(room_id.to_owned());
329324

330-
let Some(mut predecessor_room_id) = event
331-
.as_original()
332-
.and_then(|event| Some(event.content.predecessor.as_ref()?.room_id.clone()))
325+
// Redacted and non-redacted create events use the same content type.
326+
let content = match event {
327+
SyncStateEvent::Original(event) => &event.content,
328+
SyncStateEvent::Redacted(event) => &event.content,
329+
};
330+
331+
let Some(mut predecessor_room_id) =
332+
content.predecessor.as_ref().map(|predecessor| predecessor.room_id.clone())
333333
else {
334-
// `true` means no problem. No predecessor = no problem here.
335-
return true;
334+
// No predecessor = no problem here.
335+
return None;
336336
};
337337

338338
loop {
339339
// We must check immediately if the `predecessor_room_id` is in `already_seen`
340340
// in case of a room is created and marks itself as its predecessor in a single
341341
// sync.
342-
if already_seen.contains(AsRef::<RoomId>::as_ref(&predecessor_room_id)) {
342+
if already_seen.contains(&predecessor_room_id) {
343343
// Ahhh, there is a loop with `m.room.create` events!
344-
return false;
344+
// We remove the predecessor so that we don't process it later.
345+
let mut event = event.clone();
346+
347+
match &mut event {
348+
SyncStateEvent::Original(event) => event.content.predecessor.take(),
349+
SyncStateEvent::Redacted(event) => event.content.predecessor.take(),
350+
};
351+
352+
return Some(event);
345353
}
346354

347355
already_seen.insert(predecessor_room_id.clone());
@@ -366,7 +374,7 @@ pub fn is_create_event_valid(
366374
predecessor_room_id = next_predecessor_room_id;
367375
}
368376

369-
true
377+
None
370378
}
371379

372380
/// Check if `m.room.tombstone` isn't creating a loop of rooms.
@@ -420,6 +428,7 @@ pub fn is_tombstone_event_valid(
420428

421429
#[cfg(test)]
422430
mod tests {
431+
use assert_matches2::assert_matches;
423432
use matrix_sdk_test::{
424433
DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, async_test,
425434
event_factory::EventFactory,
@@ -920,11 +929,12 @@ mod tests {
920929
// The sync doesn't fail but…
921930
assert!(client.receive_sync_response(response).await.is_ok());
922931

923-
// … the state event has not been saved.
932+
// … the predecessor has not been saved.
924933
let room_0 = client.get_room(room_id_0).unwrap();
925934

926935
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
927936
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
937+
assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
928938
}
929939
}
930940

@@ -961,11 +971,12 @@ mod tests {
961971
// The sync doesn't fail but…
962972
assert!(client.receive_sync_response(response).await.is_ok());
963973

964-
// … the state event has not been saved.
974+
// … the tombstone event and the predecessor have not been saved.
965975
let room_0 = client.get_room(room_id_0).unwrap();
966976

967977
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
968978
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
979+
assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
969980
}
970981
}
971982

@@ -1070,6 +1081,7 @@ mod tests {
10701081
// this state event is missing because it creates a loop
10711082
assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
10721083
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
1084+
assert_matches!(room_2.create_content(), Some(_), "room 2 must have a create content");
10731085
}
10741086
}
10751087

0 commit comments

Comments
 (0)