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