tuwunel_matrix/event/
filter.rs1use std::mem::take;
2
3use ruma::{
4 RoomId, UserId,
5 api::client::filter::{Filter, RoomEventFilter, RoomFilter, UrlFilter},
6 serde::Raw,
7};
8use serde_json::{Map, Value};
9use smallvec::SmallVec;
10use tuwunel_core::is_equal_to;
11
12use super::Event;
13
14type FieldPath = SmallVec<[String; 2]>;
16
17pub trait Matches<T> {
23 fn matches(&self, t: T) -> bool;
29}
30
31impl<E: Event> Matches<&E> for RoomEventFilter {
32 #[inline]
33 fn matches(&self, event: &E) -> bool {
34 if !matches_sender(event, self) {
35 return false;
36 }
37
38 if !matches_room(event, self) {
39 return false;
40 }
41
42 if !matches_type(event, self) {
43 return false;
44 }
45
46 if !matches_url(event, self) {
47 return false;
48 }
49
50 true
51 }
52}
53
54impl Matches<&RoomId> for RoomFilter {
55 #[inline]
56 fn matches(&self, room_id: &RoomId) -> bool { matches_room_id(room_id, self) }
57}
58
59impl Matches<&UserId> for Filter {
60 #[inline]
61 fn matches(&self, user_id: &UserId) -> bool { matches_user_id(user_id, self) }
62}
63
64fn matches_user_id(user_id: &UserId, filter: &Filter) -> bool {
65 if filter
66 .not_senders
67 .iter()
68 .any(is_equal_to!(user_id))
69 {
70 return false;
71 }
72
73 if let Some(senders) = filter.senders.as_ref()
74 && !senders.iter().any(is_equal_to!(user_id))
75 {
76 return false;
77 }
78
79 true
80}
81
82fn matches_room_id(room_id: &RoomId, filter: &RoomFilter) -> bool {
83 if filter.not_rooms.iter().any(is_equal_to!(room_id)) {
84 return false;
85 }
86
87 if let Some(rooms) = filter.rooms.as_ref()
88 && !rooms.iter().any(is_equal_to!(room_id))
89 {
90 return false;
91 }
92
93 true
94}
95
96fn matches_room<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
97 if filter
98 .not_rooms
99 .iter()
100 .any(is_equal_to!(event.room_id()))
101 {
102 return false;
103 }
104
105 if let Some(rooms) = filter.rooms.as_ref()
106 && !rooms.iter().any(is_equal_to!(event.room_id()))
107 {
108 return false;
109 }
110
111 true
112}
113
114fn matches_sender<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
115 if filter
116 .not_senders
117 .iter()
118 .any(is_equal_to!(event.sender()))
119 {
120 return false;
121 }
122
123 if let Some(senders) = filter.senders.as_ref()
124 && !senders.iter().any(is_equal_to!(event.sender()))
125 {
126 return false;
127 }
128
129 true
130}
131
132fn matches_type<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
133 let kind = event.kind().to_cow_str();
134
135 if filter.not_types.iter().any(is_equal_to!(&kind)) {
136 return false;
137 }
138
139 if let Some(types) = filter.types.as_ref()
140 && !types.iter().any(is_equal_to!(&kind))
141 {
142 return false;
143 }
144
145 true
146}
147
148fn matches_url<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
149 let Some(url_filter) = filter.url_filter.as_ref() else {
150 return true;
151 };
152
153 let url = event
155 .get_content_as_value()
156 .get("url")
157 .is_some_and(Value::is_string);
158
159 match url_filter {
160 | UrlFilter::EventsWithUrl => url,
161 | UrlFilter::EventsWithoutUrl => !url,
162 }
163}
164
165#[must_use]
171pub fn trim_event_fields<T>(raw: Raw<T>, event_fields: Option<&[String]>) -> Raw<T> {
172 const ALWAYS_KEEP: [&str; 6] =
174 ["event_id", "type", "sender", "origin_server_ts", "state_key", "room_id"];
175
176 let Some(fields) = event_fields else {
177 return raw;
178 };
179
180 let Ok(Value::Object(source)) = raw.deserialize_as::<Value>() else {
181 return raw;
182 };
183
184 let mut out: Map<String, Value> = ALWAYS_KEEP
185 .into_iter()
186 .filter_map(|key| {
187 source
188 .get(key)
189 .map(|value| (key.to_owned(), value.clone()))
190 })
191 .collect();
192
193 for field in fields {
194 copy_field_path(&source, &mut out, &split_field(field));
195 }
196
197 Raw::from_json_value(&Value::Object(out))
198}
199
200fn copy_field_path(
204 source: &Map<String, Value>,
205 out: &mut Map<String, Value>,
206 segments: &[String],
207) {
208 let Some((head, rest)) = segments.split_first() else {
209 return;
210 };
211
212 let Some(value) = source.get(head) else {
213 return;
214 };
215
216 if rest.is_empty() {
217 out.insert(head.clone(), value.clone());
218 return;
219 }
220
221 let Value::Object(child) = value else {
222 return;
223 };
224
225 if let Value::Object(child_out) = out
226 .entry(head.clone())
227 .or_insert_with(|| Value::Object(Map::new()))
228 {
229 copy_field_path(child, child_out, rest);
230 }
231}
232
233fn split_field(field: &str) -> FieldPath {
238 let mut segments = FieldPath::new();
239 let mut current = String::new();
240 let mut chars = field.chars();
241 while let Some(c) = chars.next() {
242 match c {
243 | '\\' => match chars.next() {
244 | Some('.') => current.push('.'),
245 | Some('\\') | None => current.push('\\'),
246 | Some(other) => {
247 current.push('\\');
248 current.push(other);
249 },
250 },
251 | '.' => {
252 segments.push(take(&mut current));
253 },
254 | other => current.push(other),
255 }
256 }
257
258 segments.push(current);
259 segments
260}
261
262#[cfg(test)]
263mod tests {
264 use super::split_field;
265
266 fn segments(field: &str) -> Vec<String> { split_field(field).into_vec() }
267
268 #[test]
269 fn split_simple_path() {
270 assert_eq!(segments("content.body"), ["content", "body"]);
271 }
272
273 #[test]
274 fn split_escaped_dot() {
275 assert_eq!(segments(r"content.m\.relates_to"), ["content", "m.relates_to"]);
276 }
277
278 #[test]
279 fn split_escaped_backslash_then_dot() {
280 assert_eq!(segments(r"a\\.b"), [r"a\", "b"]);
281 }
282
283 #[test]
284 fn split_reserved_escape_kept_verbatim() {
285 assert_eq!(segments(r"weird\x"), [r"weird\x"]);
286 }
287
288 #[test]
289 fn split_trailing_backslash() {
290 assert_eq!(segments(r"foo\"), [r"foo\"]);
291 }
292}