mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-19 00:31:24 +01:00
implement rooms and not_rooms filters on /message
I really doubt anybody is sending /message requests with a filter that rejects the entire request, but it's the first step in the filter implementation.
This commit is contained in:
parent
a5e7ce6c33
commit
404d5fae6c
3 changed files with 116 additions and 1 deletions
|
|
@ -14,7 +14,9 @@ use ruma::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::{pdu::PduBuilder, rooms::timeline::PduCount},
|
service::{pdu::PduBuilder, rooms::timeline::PduCount},
|
||||||
services, utils, Ar, Error, Ra, Result,
|
services, utils,
|
||||||
|
utils::filter::CompiledRoomEventFilter,
|
||||||
|
Ar, Error, Ra, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}`
|
/// # `PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}`
|
||||||
|
|
@ -136,6 +138,13 @@ pub(crate) async fn get_message_events_route(
|
||||||
let sender_device =
|
let sender_device =
|
||||||
body.sender_device.as_ref().expect("user is authenticated");
|
body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
let Ok(filter) = CompiledRoomEventFilter::try_from(&body.filter) else {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"invalid 'filter' parameter",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
let from = match body.from.clone() {
|
let from = match body.from.clone() {
|
||||||
Some(from) => PduCount::try_from_string(&from)?,
|
Some(from) => PduCount::try_from_string(&from)?,
|
||||||
None => match body.dir {
|
None => match body.dir {
|
||||||
|
|
@ -144,6 +153,15 @@ pub(crate) async fn get_message_events_route(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !filter.room_allowed(&body.room_id) {
|
||||||
|
return Ok(Ra(get_message_events::v3::Response {
|
||||||
|
start: from.stringify(),
|
||||||
|
end: None,
|
||||||
|
chunk: vec![],
|
||||||
|
state: vec![],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let to = body.to.as_ref().and_then(|t| PduCount::try_from_string(t).ok());
|
let to = body.to.as_ref().and_then(|t| PduCount::try_from_string(t).ok());
|
||||||
|
|
||||||
services()
|
services()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
pub(crate) mod filter;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
|
|
||||||
96
src/utils/filter.rs
Normal file
96
src/utils/filter.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
//! Helper tools for implementing filtering in the `/client/v3/sync` and
|
||||||
|
//! `/client/v3/rooms/:roomId/messages` endpoints.
|
||||||
|
//!
|
||||||
|
//! The default strategy for filtering is to generate all events, check them
|
||||||
|
//! against the filter, and drop events that were rejected. When significant
|
||||||
|
//! fraction of events are rejected, this results in a large amount of wasted
|
||||||
|
//! work computing events that will be dropped. In most cases, the structure of
|
||||||
|
//! our database doesn't allow for anything fancier, with only a few exceptions.
|
||||||
|
//!
|
||||||
|
//! The first exception is room filters (`room`/`not_room` pairs in
|
||||||
|
//! `filter.rooms` and `filter.rooms.{account_data,timeline,ephemeral,state}`).
|
||||||
|
//! In `/messages`, if the room is rejected by the filter, we can skip the
|
||||||
|
//! entire request.
|
||||||
|
|
||||||
|
use std::{collections::HashSet, hash::Hash};
|
||||||
|
|
||||||
|
use ruma::{api::client::filter::RoomEventFilter, RoomId};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// Structure for testing against an allowlist and a denylist with a single
|
||||||
|
/// `HashSet` lookup.
|
||||||
|
///
|
||||||
|
/// The denylist takes precedence (an item included in both the allowlist and
|
||||||
|
/// the denylist is denied).
|
||||||
|
pub(crate) enum AllowDenyList<'a, T: ?Sized> {
|
||||||
|
/// TODO: fast-paths for allow-all and deny-all?
|
||||||
|
Allow(HashSet<&'a T>),
|
||||||
|
Deny(HashSet<&'a T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized + Hash + PartialEq + Eq> AllowDenyList<'a, T> {
|
||||||
|
fn new<A, D>(allow: Option<A>, deny: D) -> AllowDenyList<'a, T>
|
||||||
|
where
|
||||||
|
A: Iterator<Item = &'a T>,
|
||||||
|
D: Iterator<Item = &'a T>,
|
||||||
|
{
|
||||||
|
let deny_set = deny.collect::<HashSet<_>>();
|
||||||
|
if let Some(allow) = allow {
|
||||||
|
AllowDenyList::Allow(
|
||||||
|
allow.filter(|x| !deny_set.contains(x)).collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AllowDenyList::Deny(deny_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_slices<O: AsRef<T>>(
|
||||||
|
allow: Option<&'a [O]>,
|
||||||
|
deny: &'a [O],
|
||||||
|
) -> AllowDenyList<'a, T> {
|
||||||
|
AllowDenyList::new(
|
||||||
|
allow.map(|allow| allow.iter().map(AsRef::as_ref)),
|
||||||
|
deny.iter().map(AsRef::as_ref),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn allowed(&self, value: &T) -> bool {
|
||||||
|
match self {
|
||||||
|
AllowDenyList::Allow(allow) => allow.contains(value),
|
||||||
|
AllowDenyList::Deny(deny) => !deny.contains(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CompiledRoomEventFilter<'a> {
|
||||||
|
rooms: AllowDenyList<'a, RoomId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a RoomEventFilter> for CompiledRoomEventFilter<'a> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
source: &'a RoomEventFilter,
|
||||||
|
) -> Result<CompiledRoomEventFilter<'a>, Error> {
|
||||||
|
Ok(CompiledRoomEventFilter {
|
||||||
|
rooms: AllowDenyList::from_slices(
|
||||||
|
source.rooms.as_deref(),
|
||||||
|
&source.not_rooms,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompiledRoomEventFilter<'_> {
|
||||||
|
/// Returns `true` if a room is allowed by the `rooms` and `not_rooms`
|
||||||
|
/// fields.
|
||||||
|
///
|
||||||
|
/// This does *not* test the room against the top-level `rooms` filter.
|
||||||
|
/// It is expected that callers have already filtered rooms that are
|
||||||
|
/// rejected by the top-level filter using [`CompiledRoomFilter::rooms`], if
|
||||||
|
/// applicable.
|
||||||
|
pub(crate) fn room_allowed(&self, room_id: &RoomId) -> bool {
|
||||||
|
self.rooms.allowed(room_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue