ruma_state_res/
event_format.rs

1use js_int::int;
2use ruma_common::{
3    room_version_rules::EventFormatRules, CanonicalJsonObject, CanonicalJsonValue, RoomId,
4    ID_MAX_BYTES,
5};
6use serde_json::to_string as to_json_string;
7
8/// The [maximum size allowed] for a PDU.
9///
10/// [maximum size allowed]: https://spec.matrix.org/latest/client-server-api/#size-limits
11const MAX_PDU_BYTES: usize = 65_535;
12
13/// The [maximum length allowed] for the `prev_events` array of a PDU.
14///
15/// [maximum length allowed]: https://spec.matrix.org/latest/rooms/v1/#event-format
16const MAX_PREV_EVENTS_LENGTH: usize = 20;
17
18/// The [maximum length allowed] for the `auth_events` array of a PDU.
19///
20/// [maximum length allowed]: https://spec.matrix.org/latest/rooms/v1/#event-format
21const MAX_AUTH_EVENTS_LENGTH: usize = 10;
22
23/// Check that the given canonicalized PDU respects the event format of the room version and the
24/// [size limits] from the Matrix specification.
25///
26/// This is part of the [checks performed on receipt of a PDU].
27///
28/// This checks the following and enforces their size limits:
29///
30/// * Full PDU
31/// * `sender`
32/// * `room_id`
33/// * `type`
34/// * `event_id`
35/// * `state_key`
36/// * `prev_events`
37/// * `auth_events`
38/// * `depth`
39///
40/// Returns an `Err(_)` if the JSON is malformed or if the PDU doesn't pass the checks.
41///
42/// [size limits]: https://spec.matrix.org/latest/client-server-api/#size-limits
43/// [checks performed on receipt of a PDU]: https://spec.matrix.org/latest/server-server-api/#checks-performed-on-receipt-of-a-pdu
44pub fn check_pdu_format(pdu: &CanonicalJsonObject, rules: &EventFormatRules) -> Result<(), String> {
45    // Check the PDU size, it must occur on the full PDU with signatures.
46    let json =
47        to_json_string(&pdu).map_err(|e| format!("Failed to serialize canonical JSON: {e}"))?;
48    if json.len() > MAX_PDU_BYTES {
49        return Err("PDU is larger than maximum of {MAX_PDU_BYTES} bytes".to_owned());
50    }
51
52    // Check the presence, type and length of the `type` field.
53    let event_type = extract_required_string_field(pdu, "type")?;
54
55    // Check the presence, type and length of the `sender` field.
56    extract_required_string_field(pdu, "sender")?;
57
58    // Check the presence, type and length of the `room_id` field.
59    let room_id = (event_type != "m.room.create" || rules.require_room_create_room_id)
60        .then(|| extract_required_string_field(pdu, "room_id"))
61        .transpose()?;
62
63    // Check the presence, type and length of the `event_id` field.
64    if rules.require_event_id {
65        extract_required_string_field(pdu, "event_id")?;
66    }
67
68    // Check the type and length of the `state_key` field.
69    extract_optional_string_field(pdu, "state_key")?;
70
71    // Check the presence, type and length of the `prev_events` field.
72    extract_required_array_field(pdu, "prev_events", MAX_PREV_EVENTS_LENGTH)?;
73
74    // Check the presence, type and length of the `auth_events` field.
75    let auth_events = extract_required_array_field(pdu, "auth_events", MAX_AUTH_EVENTS_LENGTH)?;
76
77    if !rules.allow_room_create_in_auth_events {
78        // The only case where the room ID should be missing is for m.room.create which shouldn't
79        // have any auth_events.
80        if let Some(room_id) = room_id {
81            let room_create_event_reference_hash = <&RoomId>::try_from(room_id.as_str())
82                .map_err(|e| format!("invalid `room_id` field in PDU: {e}"))?
83                .strip_sigil();
84
85            for event_id in auth_events {
86                let CanonicalJsonValue::String(event_id) = event_id else {
87                    return Err(format!(
88                        "unexpected format of array item in `auth_events` field in PDU: \
89                         expected string, got {event_id:?}"
90                    ));
91                };
92
93                let reference_hash = event_id.strip_prefix('$').ok_or(
94                    "unexpected format of array item in `auth_events` field in PDU: \
95                     string not beginning with the `$` sigil",
96                )?;
97
98                if reference_hash == room_create_event_reference_hash {
99                    return Err("invalid `auth_events` field in PDU: \
100                                cannot contain the `m.room.create` event ID"
101                        .to_owned());
102                }
103            }
104        }
105    }
106
107    // Check the presence, type and value of the `depth` field.
108    match pdu.get("depth") {
109        Some(CanonicalJsonValue::Integer(value)) => {
110            if *value < int!(0) {
111                return Err("invalid `depth` field in PDU: cannot be a negative integer".to_owned());
112            }
113        }
114        Some(value) => {
115            return Err(format!(
116                "unexpected format of `depth` field in PDU: \
117                 expected integer, got {value:?}"
118            ));
119        }
120        None => return Err("missing `depth` field in PDU".to_owned()),
121    }
122
123    Ok(())
124}
125
126/// Extract the optional string field with the given name from the given canonical JSON object.
127///
128/// Returns `Ok(Some(value))` if the field is present and a valid string, `Ok(None)` if the field
129/// is missing and `Err(_)` if the field is not a string or its length is bigger than
130/// [`ID_MAX_BYTES`].
131fn extract_optional_string_field<'a>(
132    object: &'a CanonicalJsonObject,
133    field: &'a str,
134) -> Result<Option<&'a String>, String> {
135    match object.get(field) {
136        Some(CanonicalJsonValue::String(value)) => {
137            if value.len() > ID_MAX_BYTES {
138                Err(format!(
139                    "invalid `{field}` field in PDU: \
140                     string length is larger than maximum of {ID_MAX_BYTES} bytes"
141                ))
142            } else {
143                Ok(Some(value))
144            }
145        }
146        Some(value) => Err(format!(
147            "unexpected format of `{field}` field in PDU: \
148             expected string, got {value:?}"
149        )),
150        None => Ok(None),
151    }
152}
153
154/// Extract the required string field with the given name from the given canonical JSON object.
155///
156/// Returns `Ok(value)` if the field is present and a valid string and `Err(_)` if the field is
157/// missing, not a string or its length is bigger than [`ID_MAX_BYTES`].
158fn extract_required_string_field<'a>(
159    object: &'a CanonicalJsonObject,
160    field: &'a str,
161) -> Result<&'a String, String> {
162    extract_optional_string_field(object, field)?
163        .ok_or_else(|| format!("missing `{field}` field in PDU"))
164}
165
166/// Extract the required array field with the given name from the given canonical JSON object.
167///
168/// Returns `Ok(value)` if the field is present and a valid array or `Err(_)` if the field is
169/// missing, not an array or its length is bigger than the given value.
170fn extract_required_array_field<'a>(
171    object: &'a CanonicalJsonObject,
172    field: &'a str,
173    max_len: usize,
174) -> Result<&'a [CanonicalJsonValue], String> {
175    match object.get(field) {
176        Some(CanonicalJsonValue::Array(value)) => {
177            if value.len() > max_len {
178                Err(format!(
179                    "invalid `{field}` field in PDU: \
180                     array length is larger than maximum of {max_len}"
181                ))
182            } else {
183                Ok(value)
184            }
185        }
186        Some(value) => Err(format!(
187            "unexpected format of `{field}` field in PDU: \
188             expected array, got {value:?}"
189        )),
190        None => Err(format!("missing `{field}` field in PDU")),
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use std::iter::repeat_n;
197
198    use js_int::int;
199    use ruma_common::{
200        room_version_rules::EventFormatRules, CanonicalJsonObject, CanonicalJsonValue,
201    };
202    use serde_json::{from_value as from_json_value, json};
203
204    use super::check_pdu_format;
205
206    /// Construct a PDU valid for the event format of room v1.
207    fn pdu_v1() -> CanonicalJsonObject {
208        let pdu = json!({
209            "auth_events": [
210                [
211                    "$af232176:example.org",
212                    { "sha256": "abase64encodedsha256hashshouldbe43byteslong" },
213                ],
214            ],
215            "content": {
216                "key": "value",
217            },
218            "depth": 12,
219            "event_id": "$a4ecee13e2accdadf56c1025:example.com",
220            "hashes": {
221                "sha256": "thishashcoversallfieldsincasethisisredacted"
222            },
223            "origin_server_ts": 1_838_188_000,
224            "prev_events": [
225                [
226                    "$af232176:example.org",
227                    { "sha256": "abase64encodedsha256hashshouldbe43byteslong" }
228                ],
229            ],
230            "room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
231            "sender": "@alice:example.com",
232            "signatures": {
233                "example.com": {
234                    "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
235                },
236            },
237            "type": "m.room.message",
238            "unsigned": {
239                "age": 4612,
240            },
241        });
242        from_json_value(pdu).unwrap()
243    }
244
245    /// Construct a PDU valid for the event format of room v3.
246    fn pdu_v3() -> CanonicalJsonObject {
247        let pdu = json!({
248            "auth_events": [
249                "$base64encodedeventid",
250                "$adifferenteventid",
251            ],
252            "content": {
253                "key": "value",
254            },
255            "depth": 12,
256            "hashes": {
257                "sha256": "thishashcoversallfieldsincasethisisredacted",
258            },
259            "origin_server_ts": 1_838_188_000,
260            "prev_events": [
261                "$base64encodedeventid",
262                "$adifferenteventid",
263            ],
264            "redacts": "$some/old+event",
265            "room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
266            "sender": "@alice:example.com",
267            "signatures": {
268                "example.com": {
269                    "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
270                },
271            },
272            "type": "m.room.message",
273            "unsigned": {
274                "age": 4612,
275            }
276        });
277        from_json_value(pdu).unwrap()
278    }
279
280    /// Construct an `m.room.create` PDU valid for the event format of v12.
281    fn room_create_v12() -> CanonicalJsonObject {
282        let pdu = json!({
283            "auth_events": [],
284            "content": {
285                "room_version": "12",
286            },
287            "depth": 1,
288            "hashes": {
289                "sha256": "thishashcoversallfieldsincasethisisredacted",
290            },
291            "origin_server_ts": 1_838_188_000,
292            "prev_events": [],
293            "sender": "@alice:example.com",
294            "signatures": {
295                "example.com": {
296                    "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
297                },
298            },
299            "type": "m.room.create",
300            "unsigned": {
301                "age": 4612,
302            }
303        });
304        from_json_value(pdu).unwrap()
305    }
306
307    /// Construct a PDU valid for the event format of v12.
308    fn pdu_v12() -> CanonicalJsonObject {
309        let pdu = json!({
310            "auth_events": [
311                "$base64encodedeventid",
312                "$adifferenteventid",
313            ],
314            "content": {
315                "key": "value",
316            },
317            "depth": 12,
318            "hashes": {
319                "sha256": "thishashcoversallfieldsincasethisisredacted",
320            },
321            "origin_server_ts": 1_838_188_000,
322            "prev_events": [
323                "$base64encodedeventid",
324            ],
325            "room_id": "!roomcreatereferencehash",
326            "sender": "@alice:example.com",
327            "signatures": {
328                "example.com": {
329                    "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
330                },
331            },
332            "type": "m.room.message",
333            "unsigned": {
334                "age": 4612,
335            }
336        });
337        from_json_value(pdu).unwrap()
338    }
339
340    #[test]
341    fn check_pdu_format_valid_v1() {
342        check_pdu_format(&pdu_v1(), &EventFormatRules::V1).unwrap();
343    }
344
345    #[test]
346    fn check_pdu_format_valid_v3() {
347        check_pdu_format(&pdu_v3(), &EventFormatRules::V3).unwrap();
348    }
349
350    #[test]
351    fn check_pdu_format_pdu_too_big() {
352        // Add a lot of data in the content to reach MAX_PDU_SIZE.
353        let mut pdu = pdu_v3();
354        let content = pdu.get_mut("content").unwrap().as_object_mut().unwrap();
355        let long_string = repeat_n('a', 66_000).collect::<String>();
356        content.insert("big_data".to_owned(), long_string.into());
357
358        check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
359    }
360
361    #[test]
362    fn check_pdu_format_fields_missing() {
363        for field in
364            &["event_id", "sender", "room_id", "type", "prev_events", "auth_events", "depth"]
365        {
366            let mut pdu = pdu_v1();
367            pdu.remove(*field).unwrap();
368
369            check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
370        }
371    }
372
373    #[test]
374    fn check_pdu_format_strings_too_big() {
375        for field in &["event_id", "sender", "room_id", "type", "state_key"] {
376            let mut pdu = pdu_v1();
377            let value = repeat_n('a', 300).collect::<String>();
378            pdu.insert((*field).to_owned(), value.into());
379            check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
380        }
381    }
382
383    #[test]
384    fn check_pdu_format_strings_wrong_format() {
385        for field in &["event_id", "sender", "room_id", "type", "state_key"] {
386            let mut pdu = pdu_v1();
387            pdu.insert((*field).to_owned(), true.into());
388            check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
389        }
390    }
391
392    #[test]
393    fn check_pdu_format_arrays_too_big() {
394        for field in &["prev_events", "auth_events"] {
395            let mut pdu = pdu_v3();
396            let value =
397                repeat_n(CanonicalJsonValue::from("$eventid".to_owned()), 30).collect::<Vec<_>>();
398            pdu.insert((*field).to_owned(), value.into());
399            check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
400        }
401    }
402
403    #[test]
404    fn check_pdu_format_arrays_wrong_format() {
405        for field in &["prev_events", "auth_events"] {
406            let mut pdu = pdu_v3();
407            pdu.insert((*field).to_owned(), true.into());
408            check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
409        }
410    }
411
412    #[test]
413    fn check_pdu_format_negative_depth() {
414        let mut pdu = pdu_v3();
415        pdu.insert("depth".to_owned(), int!(-1).into()).unwrap();
416        check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
417    }
418
419    #[test]
420    fn check_pdu_format_depth_wrong_format() {
421        let mut pdu = pdu_v3();
422        pdu.insert("depth".to_owned(), true.into());
423        check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
424    }
425
426    #[test]
427    fn check_pdu_format_valid_room_create_v12() {
428        let pdu = room_create_v12();
429        check_pdu_format(&pdu, &EventFormatRules::V12).unwrap();
430    }
431
432    #[test]
433    fn check_pdu_format_valid_v12() {
434        let pdu = pdu_v12();
435        check_pdu_format(&pdu, &EventFormatRules::V12).unwrap();
436    }
437
438    #[test]
439    fn check_pdu_format_v12_with_room_create() {
440        let mut pdu = pdu_v12();
441        pdu.get_mut("auth_events")
442            .unwrap()
443            .as_array_mut()
444            .unwrap()
445            .push("$roomcreatereferencehash".to_owned().into());
446
447        check_pdu_format(&pdu, &EventFormatRules::V12).unwrap_err();
448    }
449}