diff --git a/Cargo.lock b/Cargo.lock index 55da611..d7d47d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "alsa" version = "0.9.1" @@ -990,6 +996,23 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 1.0.11", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + [[package]] name = "cssparser-macros" version = "0.6.1" @@ -1772,6 +1795,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2375,6 +2404,11 @@ name = "hashbrown" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "headers" @@ -2460,7 +2494,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29a4ec546a9a7e86b30ddc62c55f78055d7533cb617139ece68881e35cbc56e8" dependencies = [ - "lol_html", + "lol_html 1.2.1", ] [[package]] @@ -2918,11 +2952,11 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" dependencies = [ - "cssparser", + "cssparser 0.27.2", "html5ever", "indexmap 1.9.3", "matches", - "selectors", + "selectors 0.22.0", ] [[package]] @@ -3067,17 +3101,34 @@ checksum = "a4629ff9c2deeb7aad9b2d0f379fc41937a02f3b739f007732c46af40339dee5" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cssparser", + "cssparser 0.27.2", "encoding_rs", "hashbrown 0.13.2", "lazy_static", "lazycell", "memchr", "mime", - "selectors", + "selectors 0.22.0", "thiserror 1.0.69", ] +[[package]] +name = "lol_html" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1058123f6262982b891dccc395cff0144d9439de366460b47fab719258b96e" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cssparser 0.29.6", + "encoding_rs", + "hashbrown 0.15.1", + "memchr", + "mime", + "selectors 0.24.0", + "thiserror 2.0.3", +] + [[package]] name = "longest-increasing-subsequence" version = "0.1.0" @@ -3380,6 +3431,7 @@ dependencies = [ "gloo-timers", "html-purifier", "js-sys", + "lol_html 2.2.0", "markdown", "merge-io", "mumble-protocol-2x", @@ -3998,7 +4050,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros", + "phf_macros 0.8.0", "phf_shared 0.8.0", "proc-macro-hack", ] @@ -4009,7 +4061,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros 0.10.0", "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] @@ -4066,6 +4120,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -5021,7 +5089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" dependencies = [ "bitflags 1.3.2", - "cssparser", + "cssparser 0.27.2", "derive_more", "fxhash", "log", @@ -5029,11 +5097,29 @@ dependencies = [ "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", - "servo_arc", + "servo_arc 0.1.1", "smallvec", "thin-slice", ] +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser 0.29.6", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc 0.2.0", + "smallvec", +] + [[package]] name = "semver" version = "1.0.23" @@ -5221,6 +5307,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 25dcd2e..4da226d 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -89,6 +89,7 @@ tracing-subscriber = { version = "0.3.18", features = ["ansi"] } tracing = "0.1.40" color-eyre = "0.6.3" crossbeam-queue = "0.3.11" +lol_html = "2.2.0" [features] web = [ diff --git a/gui/src/app.rs b/gui/src/app.rs index f4fa355..9ee11ad 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -212,9 +212,9 @@ pub fn Channel(id: ChannelId) -> Element { summary { span { role: "button", - prevent_default: "onclick", ondoubleclick: move |evt| { evt.stop_propagation(); + evt.prevent_default(); net.send(EnterChannel { channel: id, user }) }, "{state.name}" diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 89fe3aa..5114d2f 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -12,6 +12,7 @@ use futures::SinkExt as _; use futures::StreamExt as _; use futures_channel::mpsc::UnboundedSender; pub use imp::spawn; +use msghtml::process_message_html; use mumble_protocol::control::msgs; use mumble_protocol::control::ControlCodec; use mumble_protocol::control::ControlPacket; @@ -30,6 +31,7 @@ use tracing::info; pub mod app; pub mod imp; +mod msghtml; pub static CONFIG: Lazy = Lazy::new(|| imp::load_config().unwrap_or_default()); @@ -375,7 +377,7 @@ fn accept_packet( } else { None }, - dangerous_html: html_purifier::purifier(&text, Default::default()), + dangerous_html: process_message_html(&text), raw: text, }); } @@ -387,7 +389,7 @@ fn accept_packet( let text = u.get_welcome_text().to_string(); server.chat.push(Chat { sender: None, - dangerous_html: html_purifier::purifier(&text, Default::default()), + dangerous_html: process_message_html(&text), raw: text, }); } diff --git a/gui/src/msghtml.rs b/gui/src/msghtml.rs new file mode 100644 index 0000000..3494ddc --- /dev/null +++ b/gui/src/msghtml.rs @@ -0,0 +1,110 @@ +// This is a fork of https://github.com/mehmetcansahin/html-purifier + +use lol_html::html_content::{Comment, Element}; +use lol_html::{comments, element, rewrite_str, RewriteStrSettings}; + +pub struct AllowedElement { + pub name: &'static str, + pub attributes: &'static [&'static str], +} + +const ALLOWED: &'static [AllowedElement] = &[ + AllowedElement { + name: "div", + attributes: &[], + }, + AllowedElement { + name: "b", + attributes: &[], + }, + AllowedElement { + name: "strong", + attributes: &[], + }, + AllowedElement { + name: "i", + attributes: &[], + }, + AllowedElement { + name: "em", + attributes: &[], + }, + AllowedElement { + name: "u", + attributes: &[], + }, + AllowedElement { + name: "a", + attributes: &["href", "title"], + }, + AllowedElement { + name: "ul", + attributes: &[], + }, + AllowedElement { + name: "ol", + attributes: &[], + }, + AllowedElement { + name: "li", + attributes: &[], + }, + AllowedElement { + name: "p", + attributes: &["style"], + }, + AllowedElement { + name: "br", + attributes: &[], + }, + AllowedElement { + name: "span", + attributes: &["style"], + }, + AllowedElement { + name: "img", + attributes: &["width", "height", "alt", "src"], + }, +]; + +pub fn process_message_html(input: &str) -> String { + let element_handler = |el: &mut Element| { + let find = ALLOWED.iter().find(|e| e.name.eq(&el.tag_name())); + match find { + Some(find) => { + let remove_attributes = el + .attributes() + .iter() + .filter(|e| find.attributes.iter().any(|a| a.eq(&e.name())) == false) + .map(|m| m.name()) + .collect::>(); + for attr in remove_attributes { + el.remove_attribute(&attr); + } + } + None => { + el.remove_and_keep_content(); + } + } + if el.tag_name() == "a" { + el.set_attribute("target", "_blank"); + } + Ok(()) + }; + let comment_handler = |c: &mut Comment| { + c.remove(); + Ok(()) + }; + let output = rewrite_str( + input, + RewriteStrSettings { + element_content_handlers: vec![ + element!("*", element_handler), + comments!("*", comment_handler), + ], + ..RewriteStrSettings::default() + }, + ) + .unwrap(); + return output; +}