events: Escape plain bodies in replies
Replies generate an HTML body even if the reply itself only consists of plain text. In order to convert the plain text to HTML, it has to be escaped, which did not happen previously.
This commit is contained in:
parent
8d0f817f48
commit
69c807bdc1
@ -1,5 +1,10 @@
|
||||
# [unreleased]
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* HTML-relevant characters (`<`, `>`, etc) in plaintext replies are now escaped
|
||||
during creation of the rich reply
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* Remove deprecated `EventType` enum
|
||||
|
@ -48,22 +48,8 @@ fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (
|
||||
}
|
||||
}
|
||||
|
||||
fn formatted_or_plain_body(
|
||||
formatted: Option<&FormattedBody>,
|
||||
body: &str,
|
||||
#[cfg(feature = "unstable-sanitize")] is_reply: bool,
|
||||
) -> String {
|
||||
if let Some(formatted_body) = formatted {
|
||||
#[cfg(feature = "unstable-sanitize")]
|
||||
if is_reply {
|
||||
sanitize_html(&formatted_body.body, HtmlSanitizerMode::Strict, RemoveReplyFallback::Yes)
|
||||
} else {
|
||||
formatted_body.body.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unstable-sanitize"))]
|
||||
formatted_body.body.clone()
|
||||
} else {
|
||||
/// Converts a plaintext body to HTML, escaping any characters that would cause problems.
|
||||
fn escape_html_entities(body: &str) -> String {
|
||||
let mut escaped_body = String::with_capacity(body.len());
|
||||
for c in body.chars() {
|
||||
// Escape reserved HTML entities and new lines.
|
||||
@ -83,6 +69,25 @@ fn formatted_or_plain_body(
|
||||
}
|
||||
}
|
||||
escaped_body
|
||||
}
|
||||
|
||||
fn formatted_or_plain_body(
|
||||
formatted: Option<&FormattedBody>,
|
||||
body: &str,
|
||||
#[cfg(feature = "unstable-sanitize")] is_reply: bool,
|
||||
) -> String {
|
||||
if let Some(formatted_body) = formatted {
|
||||
#[cfg(feature = "unstable-sanitize")]
|
||||
if is_reply {
|
||||
sanitize_html(&formatted_body.body, HtmlSanitizerMode::Strict, RemoveReplyFallback::Yes)
|
||||
} else {
|
||||
formatted_body.body.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unstable-sanitize"))]
|
||||
formatted_body.body.clone()
|
||||
} else {
|
||||
escape_html_entities(body)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +102,7 @@ fn formatted_or_plain_body(
|
||||
/// [HTML tags and attributes]: https://spec.matrix.org/v1.4/client-server-api/#mroommessage-msgtypes
|
||||
/// [rich reply fallbacks]: https://spec.matrix.org/v1.4/client-server-api/#fallbacks-for-rich-replies
|
||||
pub fn plain_and_formatted_reply_body(
|
||||
body: impl fmt::Display,
|
||||
body: &str,
|
||||
formatted: Option<impl fmt::Display>,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
) -> (String, String) {
|
||||
@ -106,7 +111,7 @@ pub fn plain_and_formatted_reply_body(
|
||||
let plain = format!("{quoted}\n{body}");
|
||||
let html = match formatted {
|
||||
Some(formatted) => format!("{quoted_html}{formatted}"),
|
||||
None => format!("{quoted_html}{body}"),
|
||||
None => format!("{quoted_html}{}", escape_html_entities(body)),
|
||||
};
|
||||
|
||||
(plain, html)
|
||||
|
@ -8,8 +8,9 @@ use ruma_common::{
|
||||
key::verification::VerificationMethod,
|
||||
room::{
|
||||
message::{
|
||||
AudioMessageEventContent, KeyVerificationRequestEventContent, MessageType,
|
||||
OriginalRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent,
|
||||
AudioMessageEventContent, ForwardThread, KeyVerificationRequestEventContent,
|
||||
MessageType, OriginalRoomMessageEvent, RoomMessageEventContent,
|
||||
TextMessageEventContent,
|
||||
},
|
||||
MediaSource,
|
||||
},
|
||||
@ -434,6 +435,53 @@ fn content_deserialization_failure() {
|
||||
assert_matches!(from_json_value::<RoomMessageEventContent>(json_data), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_tags_in_plain_reply_body() {
|
||||
let first_message = OriginalRoomMessageEvent {
|
||||
content: RoomMessageEventContent::text_plain("Usage: cp <source> <destination>"),
|
||||
event_id: event_id!("$143273582443PhrSn:example.org").to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
|
||||
room_id: room_id!("!testroomid:example.org").to_owned(),
|
||||
sender: user_id!("@user:example.org").to_owned(),
|
||||
unsigned: MessageLikeUnsigned::default(),
|
||||
};
|
||||
let second_message = RoomMessageEventContent::text_plain("Usage: rm <path>")
|
||||
.make_reply_to(&first_message, ForwardThread::Yes);
|
||||
|
||||
let body = assert_matches!(
|
||||
first_message.content.msgtype,
|
||||
MessageType::Text(TextMessageEventContent { body, formatted: None, .. }) => body
|
||||
);
|
||||
assert_eq!(body, "Usage: cp <source> <destination>");
|
||||
|
||||
let (body, formatted) = assert_matches!(
|
||||
second_message.msgtype,
|
||||
MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted)
|
||||
);
|
||||
assert_eq!(
|
||||
body,
|
||||
"\
|
||||
> <@user:example.org> Usage: cp <source> <destination>\n\
|
||||
Usage: rm <path>\
|
||||
"
|
||||
);
|
||||
let formatted = formatted.unwrap();
|
||||
assert_eq!(
|
||||
formatted.body,
|
||||
"\
|
||||
<mx-reply>\
|
||||
<blockquote>\
|
||||
<a href=\"https://matrix.to/#/!testroomid:example.org/$143273582443PhrSn:example.org\">In reply to</a> \
|
||||
<a href=\"https://matrix.to/#/@user:example.org\">@user:example.org</a>\
|
||||
<br>\
|
||||
Usage: cp <source> <destination>\
|
||||
</blockquote>\
|
||||
</mx-reply>\
|
||||
Usage: rm <path>\
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-sanitize")]
|
||||
fn reply_sanitize() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user