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 {
// Escape reserved HTML entities and new lines. for c in self.0.chars() {
// <https://developer.mozilla.org/en-US/docs/Glossary/Entity#reserved_characters> // Escape reserved HTML entities and new lines.
let s = match c { // <https://developer.mozilla.org/en-US/docs/Glossary/Entity#reserved_characters>
'&' => Some("&amp;"), match c {
'<' => Some("&lt;"), '&' => f.write_str("&amp;")?,
'>' => Some("&gt;"), '<' => f.write_str("&lt;")?,
'"' => Some("&quot;"), '>' => f.write_str("&gt;")?,
'\n' => Some("<br>"), '"' => f.write_str("&quot;")?,
_ => None, '\n' => f.write_str("<br>")?,
}; _ => f.write_char(c)?,
if let Some(s) = s { }
escaped_body.push_str(s);
} else {
escaped_body.push(c);
} }
Ok(())
} }
escaped_body
} }
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, #[cfg(feature = "unstable-sanitize")]
) -> String { is_reply: bool,
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"))] impl fmt::Display for FormattedOrPlainBody<'_> {
formatted_body.body.clone() fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
} else { if let Some(formatted_body) = self.formatted {
escape_html_entities(body) #[cfg(feature = "unstable-sanitize")]
if self.is_reply {
let sanitizer =
HtmlSanitizer::new(HtmlSanitizerMode::Strict, RemoveReplyFallback::Yes);
write!(f, "{}", sanitizer.clean(&formatted_body.body))
} else {
f.write_str(&formatted_body.body)
}
#[cfg(not(feature = "unstable-sanitize"))]
f.write_str(&formatted_body.body)
} else {
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.
/// ///