1use ruma::{
2 CanonicalJsonObject, CanonicalJsonValue, ID_MAX_BYTES, RoomId, int,
3 room_version_rules::EventFormatRules,
4};
5use serde_json::to_string as to_json_string;
6
7use super::super::{MAX_AUTH_EVENTS, MAX_PDU_BYTES, MAX_PREV_EVENTS, Pdu};
8use crate::{Err, Result, err};
9
10pub fn check_room_id(pdu: &Pdu, room_id: &RoomId) -> Result {
11 if pdu.room_id != room_id {
12 return Err!(Request(InvalidParam(error!(
13 pdu_event_id = ?pdu.event_id,
14 pdu_room_id = ?pdu.room_id,
15 ?room_id,
16 "Event in wrong room",
17 ))));
18 }
19
20 Ok(())
21}
22
23pub fn check_rules(pdu: &CanonicalJsonObject, rules: &EventFormatRules) -> Result {
46 let json = to_json_string(&pdu)
48 .map_err(|e| err!(Request(BadJson("Failed to serialize canonical JSON: {e}"))))?;
49
50 if json.len() > MAX_PDU_BYTES {
51 return Err!(Request(TooLarge("PDU is larger than maximum of {MAX_PDU_BYTES} bytes")));
52 }
53
54 let event_type = extract_required_string_field(pdu, "type")?;
56
57 extract_required_string_field(pdu, "sender")?;
59
60 let room_id = (event_type != "m.room.create" || rules.require_room_create_room_id)
62 .then(|| extract_required_string_field(pdu, "room_id"))
63 .transpose()?;
64
65 if rules.require_event_id {
67 extract_required_string_field(pdu, "event_id")?;
68 }
69
70 extract_optional_string_field(pdu, "state_key")?;
72
73 extract_required_array_field(pdu, "prev_events", MAX_PREV_EVENTS)?;
75
76 let auth_events = extract_required_array_field(pdu, "auth_events", MAX_AUTH_EVENTS)?;
78
79 if !rules.allow_room_create_in_auth_events {
80 if let Some(room_id) = room_id {
83 let room_create_event_reference_hash = <&RoomId>::try_from(room_id.as_str())
84 .map_err(|e| err!("invalid `room_id` field in PDU: {e}"))?
85 .strip_sigil();
86
87 for event_id in auth_events {
88 let CanonicalJsonValue::String(event_id) = event_id else {
89 return Err!(Request(InvalidParam(
90 "unexpected format of array item in `auth_events` field in PDU: \
91 expected string, got {event_id:?}"
92 )));
93 };
94
95 let reference_hash =
96 event_id
97 .strip_prefix('$')
98 .ok_or(err!(Request(InvalidParam(
99 "unexpected format of array item in `auth_events` field in PDU: \
100 string not beginning with the `$` sigil"
101 ))))?;
102
103 if reference_hash == room_create_event_reference_hash {
104 return Err!(Request(InvalidParam(
105 "invalid `auth_events` field in PDU: cannot contain the `m.room.create` \
106 event ID"
107 )));
108 }
109 }
110 }
111 }
112
113 match pdu.get("depth") {
115 | Some(CanonicalJsonValue::Integer(value)) =>
116 if *value < int!(0) {
117 return Err!(Request(InvalidParam(
118 "invalid `depth` field in PDU: cannot be a negative integer"
119 )));
120 },
121 | Some(value) => {
122 return Err!(Request(InvalidParam(
123 "unexpected format of `depth` field in PDU: expected integer, got {value:?}"
124 )));
125 },
126 | None => return Err!(Request(InvalidParam("missing `depth` field in PDU"))),
127 }
128
129 Ok(())
130}
131
132fn extract_optional_string_field<'a>(
139 object: &'a CanonicalJsonObject,
140 field: &'a str,
141) -> Result<Option<&'a String>> {
142 match object.get(field) {
143 | Some(CanonicalJsonValue::String(value)) =>
144 if value.len() > ID_MAX_BYTES {
145 Err!(Request(TooLarge(
146 "invalid `{field}` field in PDU: string length is larger than maximum of \
147 {ID_MAX_BYTES} bytes"
148 )))
149 } else {
150 Ok(Some(value))
151 },
152
153 | Some(value) => Err!(Request(InvalidParam(
154 "unexpected format of `{field}` field in PDU: expected string, got {value:?}"
155 ))),
156
157 | None => Ok(None),
158 }
159}
160
161fn extract_required_string_field<'a>(
168 object: &'a CanonicalJsonObject,
169 field: &'a str,
170) -> Result<&'a String> {
171 extract_optional_string_field(object, field)?
172 .ok_or_else(|| err!(Request(InvalidParam("missing `{field}` field in PDU"))))
173}
174
175fn extract_required_array_field<'a>(
182 object: &'a CanonicalJsonObject,
183 field: &'a str,
184 max_len: usize,
185) -> Result<&'a [CanonicalJsonValue]> {
186 match object.get(field) {
187 | Some(CanonicalJsonValue::Array(value)) =>
188 if value.len() > max_len {
189 Err!(Request(TooLarge(
190 "invalid `{field}` field in PDU: array length is larger than maximum of \
191 {max_len}"
192 )))
193 } else {
194 Ok(value)
195 },
196
197 | Some(value) => Err!(Request(InvalidParam(
198 "unexpected format of `{field}` field in PDU: expected array, got {value:?}"
199 ))),
200
201 | None => Err!(Request(InvalidParam("missing `{field}` field in PDU"))),
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use std::iter::repeat_n;
208
209 use ruma::{
210 CanonicalJsonObject, CanonicalJsonValue, int, room_version_rules::EventFormatRules,
211 };
212 use serde_json::{from_value as from_json_value, json};
213
214 use super::check_rules as check_pdu_format;
215
216 fn pdu_v1() -> CanonicalJsonObject {
218 let pdu = json!({
219 "auth_events": [
220 [
221 "$af232176:example.org",
222 { "sha256": "abase64encodedsha256hashshouldbe43byteslong" },
223 ],
224 ],
225 "content": {
226 "key": "value",
227 },
228 "depth": 12,
229 "event_id": "$a4ecee13e2accdadf56c1025:example.com",
230 "hashes": {
231 "sha256": "thishashcoversallfieldsincasethisisredacted"
232 },
233 "origin_server_ts": 1_838_188_000,
234 "prev_events": [
235 [
236 "$af232176:example.org",
237 { "sha256": "abase64encodedsha256hashshouldbe43byteslong" }
238 ],
239 ],
240 "room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
241 "sender": "@alice:example.com",
242 "signatures": {
243 "example.com": {
244 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
245 },
246 },
247 "type": "m.room.message",
248 "unsigned": {
249 "age": 4612,
250 },
251 });
252
253 from_json_value(pdu).unwrap()
254 }
255
256 fn pdu_v3() -> CanonicalJsonObject {
258 let pdu = json!({
259 "auth_events": [
260 "$base64encodedeventid",
261 "$adifferenteventid",
262 ],
263 "content": {
264 "key": "value",
265 },
266 "depth": 12,
267 "hashes": {
268 "sha256": "thishashcoversallfieldsincasethisisredacted",
269 },
270 "origin_server_ts": 1_838_188_000,
271 "prev_events": [
272 "$base64encodedeventid",
273 "$adifferenteventid",
274 ],
275 "redacts": "$some/old+event",
276 "room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
277 "sender": "@alice:example.com",
278 "signatures": {
279 "example.com": {
280 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
281 },
282 },
283 "type": "m.room.message",
284 "unsigned": {
285 "age": 4612,
286 }
287 });
288
289 from_json_value(pdu).unwrap()
290 }
291
292 fn room_create_hydra() -> CanonicalJsonObject {
295 let pdu = json!({
296 "auth_events": [],
297 "content": {
298 "room_version": "org.matrix.hydra.11",
299 },
300 "depth": 1,
301 "hashes": {
302 "sha256": "thishashcoversallfieldsincasethisisredacted",
303 },
304 "origin_server_ts": 1_838_188_000,
305 "prev_events": [],
306 "sender": "@alice:example.com",
307 "signatures": {
308 "example.com": {
309 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
310 },
311 },
312 "type": "m.room.create",
313 "unsigned": {
314 "age": 4612,
315 }
316 });
317
318 from_json_value(pdu).unwrap()
319 }
320
321 fn pdu_hydra() -> CanonicalJsonObject {
323 let pdu = json!({
324 "auth_events": [
325 "$base64encodedeventid",
326 "$adifferenteventid",
327 ],
328 "content": {
329 "key": "value",
330 },
331 "depth": 12,
332 "hashes": {
333 "sha256": "thishashcoversallfieldsincasethisisredacted",
334 },
335 "origin_server_ts": 1_838_188_000,
336 "prev_events": [
337 "$base64encodedeventid",
338 ],
339 "room_id": "!roomcreatereferencehash",
340 "sender": "@alice:example.com",
341 "signatures": {
342 "example.com": {
343 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
344 },
345 },
346 "type": "m.room.message",
347 "unsigned": {
348 "age": 4612,
349 }
350 });
351
352 from_json_value(pdu).unwrap()
353 }
354
355 #[test]
356 fn check_pdu_format_valid_v1() {
357 check_pdu_format(&pdu_v1(), &EventFormatRules::V1).unwrap();
358 }
359
360 #[test]
361 fn check_pdu_format_valid_v3() {
362 check_pdu_format(&pdu_v3(), &EventFormatRules::V3).unwrap();
363 }
364
365 #[test]
366 fn check_pdu_format_pdu_too_big() {
367 let mut pdu = pdu_v3();
369 let content = pdu
370 .get_mut("content")
371 .unwrap()
372 .as_object_mut()
373 .unwrap();
374
375 let long_string = repeat_n('a', 66_000).collect::<String>();
376 content.insert("big_data".into(), long_string.into());
377 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
378 }
379
380 #[test]
381 fn check_pdu_format_fields_missing() {
382 for field in
383 &["event_id", "sender", "room_id", "type", "prev_events", "auth_events", "depth"]
384 {
385 let mut pdu = pdu_v1();
386 pdu.remove(*field).unwrap();
387 check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
388 }
389 }
390
391 #[test]
392 fn check_pdu_format_strings_too_big() {
393 for field in &["event_id", "sender", "room_id", "type", "state_key"] {
394 let mut pdu = pdu_v1();
395 let value = repeat_n('a', 300).collect::<String>();
396 pdu.insert((*field).into(), value.into());
397 check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
398 }
399 }
400
401 #[test]
402 fn check_pdu_format_strings_wrong_format() {
403 for field in &["event_id", "sender", "room_id", "type", "state_key"] {
404 let mut pdu = pdu_v1();
405 pdu.insert((*field).into(), true.into());
406 check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
407 }
408 }
409
410 #[test]
411 fn check_pdu_format_arrays_too_big() {
412 for field in &["prev_events", "auth_events"] {
413 let mut pdu = pdu_v3();
414 let value: Vec<_> =
415 repeat_n(CanonicalJsonValue::from("$eventid".to_owned()), 30).collect();
416
417 pdu.insert((*field).into(), value.into());
418 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
419 }
420 }
421
422 #[test]
423 fn check_pdu_format_arrays_wrong_format() {
424 for field in &["prev_events", "auth_events"] {
425 let mut pdu = pdu_v3();
426 pdu.insert((*field).into(), true.into());
427 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
428 }
429 }
430
431 #[test]
432 fn check_pdu_format_negative_depth() {
433 let mut pdu = pdu_v3();
434 pdu.insert("depth".into(), int!(-1).into())
435 .unwrap();
436
437 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
438 }
439
440 #[test]
441 fn check_pdu_format_depth_wrong_format() {
442 let mut pdu = pdu_v3();
443 pdu.insert("depth".into(), true.into());
444 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
445 }
446
447 #[test]
448 fn check_pdu_format_valid_room_create_hydra() {
449 let pdu = room_create_hydra();
450 check_pdu_format(&pdu, &EventFormatRules::V12).unwrap();
451 }
452
453 #[test]
454 fn check_pdu_format_valid_hydra() {
455 let pdu = pdu_hydra();
456 check_pdu_format(&pdu, &EventFormatRules::V12).unwrap();
457 }
458
459 #[test]
460 fn check_pdu_format_hydra_with_room_create() {
461 let mut pdu = pdu_hydra();
462 pdu.get_mut("auth_events")
463 .unwrap()
464 .as_array_mut()
465 .unwrap()
466 .push("$roomcreatereferencehash".to_owned().into());
467
468 check_pdu_format(&pdu, &EventFormatRules::V12).unwrap_err();
469 }
470}