events: Avoid unnecessary copying in reply generation

This commit is contained in:
Jonas Platte 2022-11-03 13:44:59 +01:00
parent ae26730e29
commit d15fc3f5ec
No known key found for this signature in database
GPG Key ID: AAA7A61F696C3E0C
2 changed files with 46 additions and 41 deletions

View File

@ -1,11 +1,11 @@
use std::fmt; use std::fmt::{self, Write};
use super::{ use super::{
sanitize::remove_plain_reply_fallback, FormattedBody, MessageType, OriginalRoomMessageEvent, sanitize::remove_plain_reply_fallback, FormattedBody, MessageType, OriginalRoomMessageEvent,
Relation, Relation,
}; };
#[cfg(feature = "unstable-sanitize")] #[cfg(feature = "unstable-sanitize")]
use super::{sanitize_html, HtmlSanitizerMode, RemoveReplyFallback}; use super::{sanitize::HtmlSanitizer, HtmlSanitizerMode, RemoveReplyFallback};
fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (String, String) { fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (String, String) {
let get_quotes = |body: &str, formatted: Option<&FormattedBody>, is_emote: bool| { let get_quotes = |body: &str, formatted: Option<&FormattedBody>, is_emote: bool| {
@ -14,9 +14,9 @@ fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (
let emote_sign = is_emote.then_some("* ").unwrap_or_default(); let emote_sign = is_emote.then_some("* ").unwrap_or_default();
let body = is_reply.then(|| remove_plain_reply_fallback(body)).unwrap_or(body); let body = is_reply.then(|| remove_plain_reply_fallback(body)).unwrap_or(body);
#[cfg(feature = "unstable-sanitize")] #[cfg(feature = "unstable-sanitize")]
let html_body = formatted_or_plain_body(formatted, body, is_reply); let html_body = FormattedOrPlainBody { formatted, body, is_reply };
#[cfg(not(feature = "unstable-sanitize"))] #[cfg(not(feature = "unstable-sanitize"))]
let html_body = formatted_or_plain_body(formatted, body); let html_body = FormattedOrPlainBody { formatted, body };
( (
format!("> {emote_sign}<{sender}> {body}").replace('\n', "\n> "), format!("> {emote_sign}<{sender}> {body}").replace('\n', "\n> "),
@ -48,46 +48,51 @@ fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (
} }
} }
/// Converts a plaintext body to HTML, escaping any characters that would cause problems. struct EscapeHtmlEntities<'a>(&'a str);
fn escape_html_entities(body: &str) -> String {
let mut escaped_body = String::with_capacity(body.len()); impl fmt::Display for EscapeHtmlEntities<'_> {
for c in body.chars() { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.0.chars() {
// Escape reserved HTML entities and new lines. // Escape reserved HTML entities and new lines.
// <https://developer.mozilla.org/en-US/docs/Glossary/Entity#reserved_characters> // <https://developer.mozilla.org/en-US/docs/Glossary/Entity#reserved_characters>
let s = match c { match c {
'&' => Some("&amp;"), '&' => f.write_str("&amp;")?,
'<' => Some("&lt;"), '<' => f.write_str("&lt;")?,
'>' => Some("&gt;"), '>' => f.write_str("&gt;")?,
'"' => Some("&quot;"), '"' => f.write_str("&quot;")?,
'\n' => Some("<br>"), '\n' => f.write_str("<br>")?,
_ => None, _ => f.write_char(c)?,
};
if let Some(s) = s {
escaped_body.push_str(s);
} else {
escaped_body.push(c);
} }
} }
escaped_body
Ok(())
}
} }
fn formatted_or_plain_body( struct FormattedOrPlainBody<'a> {
formatted: Option<&FormattedBody>, formatted: Option<&'a FormattedBody>,
body: &str, body: &'a str,
#[cfg(feature = "unstable-sanitize")] is_reply: bool,
) -> String {
if let Some(formatted_body) = formatted {
#[cfg(feature = "unstable-sanitize")] #[cfg(feature = "unstable-sanitize")]
if is_reply { is_reply: bool,
sanitize_html(&formatted_body.body, HtmlSanitizerMode::Strict, RemoveReplyFallback::Yes) }
impl fmt::Display for FormattedOrPlainBody<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(formatted_body) = self.formatted {
#[cfg(feature = "unstable-sanitize")]
if self.is_reply {
let sanitizer =
HtmlSanitizer::new(HtmlSanitizerMode::Strict, RemoveReplyFallback::Yes);
write!(f, "{}", sanitizer.clean(&formatted_body.body))
} else { } else {
formatted_body.body.clone() f.write_str(&formatted_body.body)
} }
#[cfg(not(feature = "unstable-sanitize"))] #[cfg(not(feature = "unstable-sanitize"))]
formatted_body.body.clone() f.write_str(&formatted_body.body)
} else { } else {
escape_html_entities(body) write!(f, "{}", EscapeHtmlEntities(self.body))
}
} }
} }
@ -111,7 +116,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}{}", escape_html_entities(body)), None => format!("{quoted_html}{}", EscapeHtmlEntities(body)),
}; };
(plain, html) (plain, html)

View File

@ -6,7 +6,7 @@ mod html_fragment;
mod html_sanitizer; mod html_sanitizer;
#[cfg(feature = "unstable-sanitize")] #[cfg(feature = "unstable-sanitize")]
use html_sanitizer::HtmlSanitizer; pub(super) use html_sanitizer::HtmlSanitizer;
/// Sanitize the given HTML string. /// Sanitize the given HTML string.
/// ///