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]
|
# [unreleased]
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* HTML-relevant characters (`<`, `>`, etc) in plaintext replies are now escaped
|
||||||
|
during creation of the rich reply
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
* Remove deprecated `EventType` enum
|
* Remove deprecated `EventType` enum
|
||||||
|
@ -48,22 +48,8 @@ fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn formatted_or_plain_body(
|
/// Converts a plaintext body to HTML, escaping any characters that would cause problems.
|
||||||
formatted: Option<&FormattedBody>,
|
fn escape_html_entities(body: &str) -> String {
|
||||||
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 {
|
|
||||||
let mut escaped_body = String::with_capacity(body.len());
|
let mut escaped_body = String::with_capacity(body.len());
|
||||||
for c in body.chars() {
|
for c in body.chars() {
|
||||||
// Escape reserved HTML entities and new lines.
|
// Escape reserved HTML entities and new lines.
|
||||||
@ -84,6 +70,25 @@ fn formatted_or_plain_body(
|
|||||||
}
|
}
|
||||||
escaped_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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the plain and formatted body for a rich reply.
|
/// Get the plain and formatted body for a rich reply.
|
||||||
@ -97,7 +102,7 @@ fn formatted_or_plain_body(
|
|||||||
/// [HTML tags and attributes]: https://spec.matrix.org/v1.4/client-server-api/#mroommessage-msgtypes
|
/// [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
|
/// [rich reply fallbacks]: https://spec.matrix.org/v1.4/client-server-api/#fallbacks-for-rich-replies
|
||||||
pub fn plain_and_formatted_reply_body(
|
pub fn plain_and_formatted_reply_body(
|
||||||
body: impl fmt::Display,
|
body: &str,
|
||||||
formatted: Option<impl fmt::Display>,
|
formatted: Option<impl fmt::Display>,
|
||||||
original_message: &OriginalRoomMessageEvent,
|
original_message: &OriginalRoomMessageEvent,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
@ -106,7 +111,7 @@ pub fn plain_and_formatted_reply_body(
|
|||||||
let plain = format!("{quoted}\n{body}");
|
let plain = format!("{quoted}\n{body}");
|
||||||
let html = match formatted {
|
let html = match formatted {
|
||||||
Some(formatted) => format!("{quoted_html}{formatted}"),
|
Some(formatted) => format!("{quoted_html}{formatted}"),
|
||||||
None => format!("{quoted_html}{body}"),
|
None => format!("{quoted_html}{}", escape_html_entities(body)),
|
||||||
};
|
};
|
||||||
|
|
||||||
(plain, html)
|
(plain, html)
|
||||||
|
@ -8,8 +8,9 @@ use ruma_common::{
|
|||||||
key::verification::VerificationMethod,
|
key::verification::VerificationMethod,
|
||||||
room::{
|
room::{
|
||||||
message::{
|
message::{
|
||||||
AudioMessageEventContent, KeyVerificationRequestEventContent, MessageType,
|
AudioMessageEventContent, ForwardThread, KeyVerificationRequestEventContent,
|
||||||
OriginalRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent,
|
MessageType, OriginalRoomMessageEvent, RoomMessageEventContent,
|
||||||
|
TextMessageEventContent,
|
||||||
},
|
},
|
||||||
MediaSource,
|
MediaSource,
|
||||||
},
|
},
|
||||||
@ -434,6 +435,53 @@ fn content_deserialization_failure() {
|
|||||||
assert_matches!(from_json_value::<RoomMessageEventContent>(json_data), Err(_));
|
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]
|
#[test]
|
||||||
#[cfg(feature = "unstable-sanitize")]
|
#[cfg(feature = "unstable-sanitize")]
|
||||||
fn reply_sanitize() {
|
fn reply_sanitize() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user