diff --git a/rust/src/events/filter.rs b/rust/src/events/filter.rs
new file mode 100644
index 0000000000..7e39972c62
--- /dev/null
+++ b/rust/src/events/filter.rs
@@ -0,0 +1,107 @@
+/*
+ * This file is licensed under the Affero General Public License (AGPL) version 3.
+ *
+ * Copyright (C) 2024 New Vector, Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * See the GNU Affero General Public License for more details:
+ * <https://www.gnu.org/licenses/agpl-3.0.html>.
+ */
+
+use std::collections::HashMap;
+
+use pyo3::{exceptions::PyValueError, pyfunction, PyResult};
+
+use crate::{
+ identifier::UserID,
+ matrix_const::{
+ HISTORY_VISIBILITY_INVITED, HISTORY_VISIBILITY_JOINED, MEMBERSHIP_INVITE, MEMBERSHIP_JOIN,
+ },
+};
+
+#[pyfunction(name = "event_visible_to_server")]
+pub fn event_visible_to_server_py(
+ sender: String,
+ target_server_name: String,
+ history_visibility: String,
+ erased_senders: HashMap<String, bool>,
+ partial_state_invisible: bool,
+ memberships: Vec<(String, String)>, // (state_key, membership)
+) -> PyResult<bool> {
+ event_visible_to_server(
+ sender,
+ target_server_name,
+ history_visibility,
+ erased_senders,
+ partial_state_invisible,
+ memberships,
+ )
+ .map_err(|e| PyValueError::new_err(format!("{e}")))
+}
+
+/// Return whether the target server is allowed to see the event.
+///
+/// For a fully stated room, the target server is allowed to see an event E if:
+/// - the state at E has world readable or shared history vis, OR
+/// - the state at E says that the target server is in the room.
+///
+/// For a partially stated room, the target server is allowed to see E if:
+/// - E was created by this homeserver, AND:
+/// - the partial state at E has world readable or shared history vis, OR
+/// - the partial state at E says that the target server is in the room.
+pub fn event_visible_to_server(
+ sender: String,
+ target_server_name: String,
+ history_visibility: String,
+ erased_senders: HashMap<String, bool>,
+ partial_state_invisible: bool,
+ memberships: Vec<(String, String)>, // (state_key, membership)
+) -> anyhow::Result<bool> {
+ if let Some(&erased) = erased_senders.get(&sender) {
+ if erased {
+ return Ok(false);
+ }
+ }
+
+ if partial_state_invisible {
+ return Ok(false);
+ }
+
+ if history_visibility != HISTORY_VISIBILITY_INVITED
+ && history_visibility != HISTORY_VISIBILITY_JOINED
+ {
+ return Ok(true);
+ }
+
+ let mut visible = false;
+ for (state_key, membership) in memberships {
+ let state_key = UserID::try_from(state_key.as_ref())
+ .map_err(|e| anyhow::anyhow!(format!("invalid user_id ({state_key}): {e}")))?;
+ if state_key.server_name() != target_server_name {
+ return Err(anyhow::anyhow!(
+ "state_key.server_name ({}) does not match target_server_name ({target_server_name})",
+ state_key.server_name()
+ ));
+ }
+
+ match membership.as_str() {
+ MEMBERSHIP_INVITE => {
+ if history_visibility == HISTORY_VISIBILITY_INVITED {
+ visible = true;
+ break;
+ }
+ }
+ MEMBERSHIP_JOIN => {
+ visible = true;
+ break;
+ }
+ _ => continue,
+ }
+ }
+
+ Ok(visible)
+}
diff --git a/rust/src/events/mod.rs b/rust/src/events/mod.rs
index a4ade1a178..0bb6cdb181 100644
--- a/rust/src/events/mod.rs
+++ b/rust/src/events/mod.rs
@@ -22,15 +22,17 @@
use pyo3::{
types::{PyAnyMethods, PyModule, PyModuleMethods},
- Bound, PyResult, Python,
+ wrap_pyfunction, Bound, PyResult, Python,
};
+pub mod filter;
mod internal_metadata;
/// Called when registering modules with python.
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
let child_module = PyModule::new_bound(py, "events")?;
child_module.add_class::<internal_metadata::EventInternalMetadata>()?;
+ child_module.add_function(wrap_pyfunction!(filter::event_visible_to_server_py, m)?)?;
m.add_submodule(&child_module)?;
diff --git a/rust/src/identifier.rs b/rust/src/identifier.rs
new file mode 100644
index 0000000000..b199c5838e
--- /dev/null
+++ b/rust/src/identifier.rs
@@ -0,0 +1,86 @@
+/*
+ * This file is licensed under the Affero General Public License (AGPL) version 3.
+ *
+ * Copyright (C) 2024 New Vector, Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * See the GNU Affero General Public License for more details:
+ * <https://www.gnu.org/licenses/agpl-3.0.html>.
+ */
+
+//! # Matrix Identifiers
+//!
+//! This module contains definitions and utilities for working with matrix identifiers.
+
+use std::{fmt, ops::Deref};
+
+/// Errors that can occur when parsing a matrix identifier.
+#[derive(Clone, Debug, PartialEq)]
+pub enum IdentifierError {
+ IncorrectSigil,
+ MissingColon,
+}
+
+impl fmt::Display for IdentifierError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+/// A Matrix user_id.
+#[derive(Clone, Debug, PartialEq)]
+pub struct UserID(String);
+
+impl UserID {
+ /// Returns the `localpart` of the user_id.
+ pub fn localpart(&self) -> &str {
+ &self[1..self.colon_pos()]
+ }
+
+ /// Returns the `server_name` / `domain` of the user_id.
+ pub fn server_name(&self) -> &str {
+ &self[self.colon_pos() + 1..]
+ }
+
+ /// Returns the position of the ':' inside of the user_id.
+ /// Used when splitting the user_id into it's respective parts.
+ fn colon_pos(&self) -> usize {
+ self.find(':').unwrap()
+ }
+}
+
+impl TryFrom<&str> for UserID {
+ type Error = IdentifierError;
+
+ /// Will try creating a `UserID` from the provided `&str`.
+ /// Can fail if the user_id is incorrectly formatted.
+ fn try_from(s: &str) -> Result<Self, Self::Error> {
+ if !s.starts_with('@') {
+ return Err(IdentifierError::IncorrectSigil);
+ }
+
+ if s.find(':').is_none() {
+ return Err(IdentifierError::MissingColon);
+ }
+
+ Ok(UserID(s.to_string()))
+ }
+}
+
+impl Deref for UserID {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl fmt::Display for UserID {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 06477880b9..5de9238326 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -6,6 +6,8 @@ pub mod acl;
pub mod errors;
pub mod events;
pub mod http;
+pub mod identifier;
+pub mod matrix_const;
pub mod push;
pub mod rendezvous;
diff --git a/rust/src/matrix_const.rs b/rust/src/matrix_const.rs
new file mode 100644
index 0000000000..f75f3bd7c3
--- /dev/null
+++ b/rust/src/matrix_const.rs
@@ -0,0 +1,28 @@
+/*
+ * This file is licensed under the Affero General Public License (AGPL) version 3.
+ *
+ * Copyright (C) 2024 New Vector, Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * See the GNU Affero General Public License for more details:
+ * <https://www.gnu.org/licenses/agpl-3.0.html>.
+ */
+
+//! # Matrix Constants
+//!
+//! This module contains definitions for constant values described by the matrix specification.
+
+pub const HISTORY_VISIBILITY_WORLD_READABLE: &str = "world_readable";
+pub const HISTORY_VISIBILITY_SHARED: &str = "shared";
+pub const HISTORY_VISIBILITY_INVITED: &str = "invited";
+pub const HISTORY_VISIBILITY_JOINED: &str = "joined";
+
+pub const MEMBERSHIP_BAN: &str = "ban";
+pub const MEMBERSHIP_LEAVE: &str = "leave";
+pub const MEMBERSHIP_KNOCK: &str = "knock";
+pub const MEMBERSHIP_INVITE: &str = "invite";
+pub const MEMBERSHIP_JOIN: &str = "join";
diff --git a/rust/src/push/utils.rs b/rust/src/push/utils.rs
index 28ebed62c8..59536c9954 100644
--- a/rust/src/push/utils.rs
+++ b/rust/src/push/utils.rs
@@ -23,7 +23,6 @@ use anyhow::bail;
use anyhow::Context;
use anyhow::Error;
use lazy_static::lazy_static;
-use regex;
use regex::Regex;
use regex::RegexBuilder;
|