Skip to main content

tuwunel_core/matrix/pdu/format/
check.rs

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
10/// Verifies that a parsed PDU belongs to the expected room.
11///
12/// The comparison uses the room ID retained in the stored representation. A
13/// mismatch is reported as an invalid request parameter.
14pub 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
27/// Check that the given canonicalized PDU respects the event format of the room
28/// version and the [size limits] from the Matrix specification.
29///
30/// This is part of the [checks performed on receipt of a PDU].
31///
32/// This checks the following and enforces their size limits:
33///
34/// * Full PDU
35/// * `sender`
36/// * `room_id`
37/// * `type`
38/// * `event_id`
39/// * `state_key`
40/// * `prev_events`
41/// * `auth_events`
42/// * `depth`
43///
44/// Returns an `Err(_)` if the JSON is malformed or if the PDU doesn't pass the
45/// checks.
46///
47/// [size limits]: https://spec.matrix.org/latest/client-server-api/#size-limits
48/// [checks performed on receipt of a PDU]: https://spec.matrix.org/latest/server-server-api/#checks-performed-on-receipt-of-a-pdu
49pub fn check_rules(pdu: &CanonicalJsonObject, rules: &EventFormatRules) -> Result {
50	// Check the PDU size, it must occur on the full PDU with signatures.
51	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	// Check the presence, type and length of the `type` field.
59	let event_type = extract_required_string_field(pdu, "type")?;
60
61	// Check the presence, type and length of the `sender` field.
62	extract_required_string_field(pdu, "sender")?;
63
64	// Check the presence, type and length of the `room_id` field.
65	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	// Check the presence, type and length of the `event_id` field.
70	if rules.require_event_id {
71		extract_required_string_field(pdu, "event_id")?;
72	}
73
74	// Check the type and length of the `state_key` field.
75	extract_optional_string_field(pdu, "state_key")?;
76
77	// Check the presence, type and length of the `prev_events` field.
78	extract_required_array_field(pdu, "prev_events", MAX_PREV_EVENTS)?;
79
80	// Check the presence, type and length of the `auth_events` field.
81	let auth_events = extract_required_array_field(pdu, "auth_events", MAX_AUTH_EVENTS)?;
82
83	if !rules.allow_room_create_in_auth_events {
84		// The only case where the room ID should be missing is for m.room.create which
85		// shouldn't have any auth_events.
86		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	// Check the presence, type and value of the `depth` field.
118	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
136/// Extract the optional string field with the given name from the given
137/// canonical JSON object.
138///
139/// Returns `Ok(Some(value))` if the field is present and a valid string,
140/// `Ok(None)` if the field is missing and `Err(_)` if the field is not a string
141/// or its length is bigger than [`ID_MAX_BYTES`].
142fn 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
165/// Extract the required string field with the given name from the given
166/// canonical JSON object.
167///
168/// Returns `Ok(value)` if the field is present and a valid string and `Err(_)`
169/// if the field is missing, not a string or its length is bigger than
170/// [`ID_MAX_BYTES`].
171fn 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
179/// Extract the required array field with the given name from the given
180/// canonical JSON object.
181///
182/// Returns `Ok(value)` if the field is present and a valid array or `Err(_)` if
183/// the field is missing, not an array or its length is bigger than the given
184/// value.
185fn 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	/// Construct a PDU valid for the event format of room v1.
221	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	/// Construct a PDU valid for the event format of room v3.
261	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	/// Construct an `m.room.create` PDU valid for the event format of
297	/// `org.matrix.hydra.11`.
298	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	/// Construct a PDU valid for the event format of `org.matrix.hydra.11`.
326	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		// Add a lot of data in the content to reach MAX_PDU_SIZE.
372		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}