Merge remote-tracking branch 'upstream/main' into conduwuit-changes

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-09-06 21:10:32 -04:00
commit c8a2d06e24
14 changed files with 129 additions and 51 deletions

View File

@ -27,6 +27,9 @@ Improvements:
to send `Future` events and update `Future` events with `future_tokens`.
(`Future` events are scheduled messages that can be controlled
with `future_tokens` to send on demand or restart the timeout)
- Change types of `SyncRequestListFilters::{room_types,not_room_types}` to
`Vec<RoomTypeFilter>` instead of a vector of strings
- This is a breaking change, but only for users of `unstable-msc3575`
Bug fixes:

View File

@ -10,6 +10,7 @@ use js_int::UInt;
use js_option::JsOption;
use ruma_common::{
api::{request, response, Metadata},
directory::RoomTypeFilter,
metadata,
serde::{deserialize_cow_str, duration::opt_ms, Raw},
DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, room::RoomType

View File

@ -223,12 +223,32 @@ where
}
}
impl From<Option<RoomType>> for RoomTypeFilter {
fn from(t: Option<RoomType>) -> Self {
match t {
None => Self::Default,
Some(s) => match s {
RoomType::Space => Self::Space,
_ => Self::from(Some(s.as_str())),
},
}
}
}
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{Filter, RoomNetwork, RoomTypeFilter};
use crate::room::RoomType;
#[test]
fn test_from_room_type() {
let test = RoomType::Space;
let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
assert_eq!(other, RoomTypeFilter::Space);
}
#[test]
fn serialize_matrix_network_only() {

View File

@ -7,6 +7,8 @@ Bug fixes:
- Fix serialization of `room::message::Relation` and `room::encrypted::Relation`
which could cause duplicate `rel_type` keys.
- `Restricted` no longer fails to deserialize when the `allow` field is missing
- Markdown text constructors now also detect markdown syntax like backslash
escapes and entity references to decide if the text should be sent as HTML.
Improvements:

View File

@ -65,7 +65,7 @@ indexmap = { version = "2.0.0", features = ["serde"] }
js_int = { workspace = true, features = ["serde"] }
js_option = "0.1.0"
percent-encoding = "2.1.0"
pulldown-cmark = { version = "0.11.0", optional = true, default-features = false, features = ["html"] }
pulldown-cmark = { version = "0.12.1", optional = true, default-features = false, features = ["html"] }
regex = { version = "1.5.6", default-features = false, features = ["std", "perf"] }
ruma-common = { workspace = true }
ruma-html = { workspace = true, optional = true }

View File

@ -72,6 +72,7 @@ impl CallMemberEventContent {
pub fn new_empty(leave_reason: Option<LeaveReason>) -> Self {
Self::Empty(EmptyMembershipData { leave_reason })
}
/// All non expired memberships in this member event.
///
/// In most cases you want to use this method instead of the public memberships field.
@ -268,7 +269,7 @@ mod tests {
}),
"ABCDE".to_owned(),
ActiveFocus::Livekit(ActiveLivekitFocus {
focus_select: FocusSelection::OldestMembership,
focus_selection: FocusSelection::OldestMembership,
}),
vec![Focus::Livekit(LivekitFocus {
alias: "1".to_owned(),
@ -294,7 +295,7 @@ mod tests {
],
"focus_active":{
"type":"livekit",
"focus_select":"oldest_membership"
"focus_selection":"oldest_membership"
}
});
assert_eq!(
@ -348,7 +349,7 @@ mod tests {
}),
"THIS_DEVICE".to_owned(),
ActiveFocus::Livekit(ActiveLivekitFocus {
focus_select: FocusSelection::OldestMembership,
focus_selection: FocusSelection::OldestMembership,
}),
vec![Focus::Livekit(LivekitFocus {
alias: "room1".to_owned(),
@ -364,7 +365,7 @@ mod tests {
"device_id": "THIS_DEVICE",
"focus_active":{
"type": "livekit",
"focus_select": "oldest_membership"
"focus_selection": "oldest_membership"
},
"foci_preferred": [
{
@ -473,7 +474,7 @@ mod tests {
"device_id": "THIS_DEVICE",
"focus_active":{
"type": "livekit",
"focus_select": "oldest_membership"
"focus_selection": "oldest_membership"
},
"foci_preferred": [
{
@ -509,26 +510,32 @@ mod tests {
assert_eq!(member_event.sender, sender);
assert_eq!(member_event.room_id, room_id);
assert_eq!(member_event.origin_server_ts, TS(js_int::UInt::new(111).unwrap()));
assert_eq!(
member_event.content,
CallMemberEventContent::SessionContent(SessionMembershipData {
let membership = SessionMembershipData {
application: Application::Call(CallApplicationContent {
call_id: "".to_owned(),
scope: CallScope::Room
scope: CallScope::Room,
}),
device_id: "THIS_DEVICE".to_owned(),
foci_preferred: [Focus::Livekit(LivekitFocus {
alias: "room1".to_owned(),
service_url: "https://livekit1.com".to_owned()
service_url: "https://livekit1.com".to_owned(),
})]
.to_vec(),
focus_active: ActiveFocus::Livekit(ActiveLivekitFocus {
focus_select: FocusSelection::OldestMembership
focus_selection: FocusSelection::OldestMembership,
}),
created_ts: None
})
created_ts: None,
};
assert_eq!(
member_event.content,
CallMemberEventContent::SessionContent(membership.clone())
);
// Correctly computes the active_memberships array.
assert_eq!(
member_event.content.active_memberships(None)[0],
vec![MembershipData::Session(&membership)][0]
);
assert_eq!(js_int::Int::new(10), member_event.unsigned.age);
assert_eq!(
CallMemberEventContent::Empty(EmptyMembershipData { leave_reason: None }),
@ -568,7 +575,7 @@ mod tests {
}
#[test]
fn memberships_do_expire() {
fn legacy_memberships_do_expire() {
let content_legacy = create_call_member_legacy_event_content();
let (now, one_second_ago, two_hours_ago) = timestamps();

View File

@ -59,7 +59,7 @@ pub enum ActiveFocus {
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ActiveLivekitFocus {
/// The selection method used to select the LiveKit focus for the rtc session.
pub focus_select: FocusSelection,
pub focus_selection: FocusSelection,
}
impl ActiveLivekitFocus {
@ -67,10 +67,10 @@ impl ActiveLivekitFocus {
///
/// # Arguments
///
/// * `focus_select` - The selection method used to select the LiveKit focus for the rtc
/// * `focus_selection` - The selection method used to select the LiveKit focus for the rtc
/// session.
pub fn new() -> Self {
Self { focus_select: FocusSelection::OldestMembership }
Self { focus_selection: FocusSelection::OldestMembership }
}
}

View File

@ -58,7 +58,7 @@ impl<'a> MembershipData<'a> {
pub fn focus_active(&self) -> &ActiveFocus {
match self {
MembershipData::Legacy(_) => &ActiveFocus::Livekit(ActiveLivekitFocus {
focus_select: super::focus::FocusSelection::OldestMembership,
focus_selection: super::focus::FocusSelection::OldestMembership,
}),
MembershipData::Session(data) => &data.focus_active,
}

View File

@ -858,11 +858,12 @@ pub struct CustomEventContent {
#[cfg(feature = "markdown")]
pub(crate) fn parse_markdown(text: &str) -> Option<String> {
use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd};
use pulldown_cmark::{CowStr, Event, Options, Parser, Tag, TagEnd};
const OPTIONS: Options = Options::ENABLE_TABLES.union(Options::ENABLE_STRIKETHROUGH);
let mut found_first_paragraph = false;
let mut previous_event_was_text = false;
let parser_events: Vec<_> = Parser::new_ext(text, OPTIONS)
.map(|event| match event {
@ -871,8 +872,29 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
})
.collect();
let has_markdown = parser_events.iter().any(|ref event| {
let is_text = matches!(event, Event::Text(_));
// Numeric references should be replaced by their UTF-8 equivalent, so encountering a
// non-borrowed string means that there is markdown syntax.
let is_borrowed_text = matches!(event, Event::Text(CowStr::Borrowed(_)));
if is_borrowed_text {
if previous_event_was_text {
// The text was split, so a character was likely removed, like in the case of
// backslash escapes, or replaced by a static string, like for entity references, so
// there is markdown syntax.
return true;
} else {
previous_event_was_text = true;
}
} else {
previous_event_was_text = false;
}
// A hard break happens when a newline is encountered, which is not necessarily markdown
// syntax.
let is_break = matches!(event, Event::HardBreak);
// The parser always wraps the string into a paragraph, so the first paragraph should be
// ignored, it is not due to markdown syntax.
let is_first_paragraph_start = if matches!(event, Event::Start(Tag::Paragraph)) {
if found_first_paragraph {
false
@ -885,7 +907,7 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
};
let is_paragraph_end = matches!(event, Event::End(TagEnd::Paragraph));
!is_text && !is_break && !is_first_paragraph_start && !is_paragraph_end
!is_borrowed_text && !is_break && !is_first_paragraph_start && !is_paragraph_end
});
if !has_markdown {
@ -897,3 +919,41 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
Some(html_body)
}
#[cfg(all(test, feature = "markdown"))]
mod tests {
use assert_matches2::assert_matches;
use super::parse_markdown;
#[test]
fn detect_markdown() {
// Simple single-line text.
let text = "Hello world.";
assert_matches!(parse_markdown(text), None);
// Simple double-line text.
let text = "Hello\nworld.";
assert_matches!(parse_markdown(text), None);
// With new paragraph.
let text = "Hello\n\nworld.";
assert_matches!(parse_markdown(text), Some(_));
// With tagged element.
let text = "Hello **world**.";
assert_matches!(parse_markdown(text), Some(_));
// With backslash escapes.
let text = r#"Hello \<world\>."#;
assert_matches!(parse_markdown(text), Some(_));
// With entity reference.
let text = r#"Hello &lt;world&gt;."#;
assert_matches!(parse_markdown(text), Some(_));
// With numeric reference.
let text = "Hello w&#8853;rld.";
assert_matches!(parse_markdown(text), Some(_));
}
}

View File

@ -201,9 +201,9 @@ fn markdown_detection() {
let formatted_body = FormattedBody::markdown("A message\nwith\n\nmultiple\n\nparagraphs");
formatted_body.unwrap();
// HTML entities don't trigger markdown.
// "Less than" symbol triggers markdown.
let formatted_body = FormattedBody::markdown("A message with & HTML < entities");
assert_matches!(formatted_body, None);
assert_matches!(formatted_body, Some(_));
// HTML triggers markdown.
let formatted_body = FormattedBody::markdown("<span>An HTML message</span>");

View File

@ -3,6 +3,7 @@
Breaking Changes:
- `MatrixElement::Div` is now a newtype variant.
- `AnchorData`'s `name` field was removed, according to MSC4159.
Improvements:

View File

@ -337,9 +337,6 @@ impl PartialEq<u8> for HeadingLevel {
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AnchorData {
/// The name of the anchor.
pub name: Option<StrTendril>,
/// Where to display the linked URL.
pub target: Option<StrTendril>,
@ -350,7 +347,7 @@ pub struct AnchorData {
impl AnchorData {
/// Construct an empty `AnchorData`.
fn new() -> Self {
Self { name: None, target: None, href: None }
Self { target: None, href: None }
}
/// Parse the given attributes to construct a new `AnchorData`.
@ -368,9 +365,6 @@ impl AnchorData {
}
match attr.name.local.as_bytes() {
b"name" => {
data.name = Some(attr.value.clone());
}
b"target" => {
data.target = Some(attr.value.clone());
}

View File

@ -32,7 +32,7 @@ static ALLOWED_ATTRIBUTES_STRICT: Map<&str, &Set<&str>> = phf_map! {
};
static ALLOWED_ATTRIBUTES_SPAN_STRICT: Set<&str> =
phf_set! { "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "data-mx-maths" };
static ALLOWED_ATTRIBUTES_A_STRICT: Set<&str> = phf_set! { "name", "target", "href" };
static ALLOWED_ATTRIBUTES_A_STRICT: Set<&str> = phf_set! { "target", "href" };
static ALLOWED_ATTRIBUTES_IMG_STRICT: Set<&str> =
phf_set! { "width", "height", "alt", "title", "src" };
static ALLOWED_ATTRIBUTES_OL_STRICT: Set<&str> = phf_set! { "start" };

View File

@ -84,11 +84,7 @@ fn span_attributes() {
#[test]
fn a_attributes() {
let raw_html = "\
<a \
name=\"my_anchor\" \
target=\"_blank\" \
href=\"https://localhost/\"\
>\
<a target=\"_blank\" href=\"https://localhost/\">\
Link with all supported attributes\
</a>\
<a href=\"matrix:r/somewhere:localhost\">Link with valid matrix scheme URI</a>\
@ -105,7 +101,6 @@ fn a_attributes() {
let element = node.as_element().unwrap().to_matrix();
assert_matches!(element.element, MatrixElement::A(anchor));
assert_eq!(anchor.name.unwrap().as_ref(), "my_anchor");
assert_eq!(anchor.target.unwrap().as_ref(), "_blank");
assert_matches!(anchor.href.unwrap(), AnchorUri::Other(uri));
assert_eq!(uri.as_ref(), "https://localhost/");
@ -116,7 +111,6 @@ fn a_attributes() {
let element = node.as_element().unwrap().to_matrix();
assert_matches!(element.element, MatrixElement::A(anchor));
assert!(anchor.name.is_none());
assert!(anchor.target.is_none());
assert_matches!(anchor.href.unwrap(), AnchorUri::Matrix(uri));
assert_eq!(uri.to_string(), "matrix:r/somewhere:localhost");
@ -127,7 +121,6 @@ fn a_attributes() {
let element = node.as_element().unwrap().to_matrix();
assert_matches!(element.element, MatrixElement::A(anchor));
assert!(anchor.name.is_none());
assert!(anchor.target.is_none());
assert!(anchor.href.is_none());
// The `href` attribute is in the unsupported attributes.
@ -138,7 +131,6 @@ fn a_attributes() {
let element = node.as_element().unwrap().to_matrix();
assert_matches!(element.element, MatrixElement::A(anchor));
assert!(anchor.name.is_none());
assert!(anchor.target.is_none());
assert_matches!(anchor.href.unwrap(), AnchorUri::MatrixTo(uri));
assert_eq!(uri.to_string(), "https://matrix.to/#/%23somewhere:example.org");
@ -149,7 +141,6 @@ fn a_attributes() {
let element = node.as_element().unwrap().to_matrix();
assert_matches!(element.element, MatrixElement::A(anchor));
assert!(anchor.name.is_none());
assert!(anchor.target.is_none());
assert!(anchor.href.is_none());
// The `href` attribute is in the unsupported attributes.
@ -160,7 +151,6 @@ fn a_attributes() {
let element = node.as_element().unwrap().to_matrix();
assert_matches!(element.element, MatrixElement::A(anchor));
assert!(anchor.name.is_none());
assert!(anchor.target.is_none());
assert!(anchor.href.is_none());
// The `href` attribute is in the unsupported attributes.