use ammonia::Builder; use once_cell::sync::Lazy; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; fn allow_styles<'u>(allowed: &[&str], value: &'u str) -> Option> { let mut altered = false; let rules: Vec<_> = value .split(';') .filter_map(|rule| { let name = rule.split(':').next()?.trim(); log::debug!("Checking '{}' against {:?}", name, allowed); if allowed.contains(&name) { Some(rule) } else { altered = true; None } }) .collect(); if altered { if rules.is_empty() { None } else { Some(Cow::Owned(rules.join(";"))) } } else { Some(Cow::Borrowed(value)) } } fn attribute_filter<'u>(element: &str, attribute: &str, value: &'u str) -> Option> { match (element, attribute) { ("ul", "style") | ("ol", "style") => allow_styles(&["list-style-type", "type"], value), ("span", "style") => allow_styles(&["color", "opacity"], value), ("div", "class") | ("span", "class") | ("figure", "class") | ("pre", "class") | ("pre", "data-language") | ("span", "data-symbol") | ("blockquote", "data-author") | ("a", "rel") | ("a", "title") | ("a", "href") | ("img", "src") | ("img", "title") | ("img", "alt") => Some(Cow::Borrowed(value)), _ => None, } } // Classes based on bbclash BBCode spec: // https://github.com/EndaHallahan/BBClash/blob/master/Spec.md static AMMONIA_CONFIG: Lazy = Lazy::new(|| { let mut classes = HashMap::new(); let div_hs = classes.entry("div").or_insert(HashSet::new()); div_hs.insert("center"); div_hs.insert("right"); div_hs.insert("math_container"); // div_hs.insert("embed"); for now, no embeds div_hs.insert("indent-1"); div_hs.insert("indent-2"); div_hs.insert("indent-3"); div_hs.insert("indent-4"); let span_hs = classes.entry("span").or_insert(HashSet::new()); span_hs.insert("underline"); span_hs.insert("smallcaps"); span_hs.insert("monospace"); span_hs.insert("spoiler"); span_hs.insert("math_container"); let pre_hs = classes.entry("pre").or_insert(HashSet::new()); pre_hs.insert("codeblock"); let figure_hs = classes.entry("figure").or_insert(HashSet::new()); figure_hs.insert("figure-right"); figure_hs.insert("figure-left"); let mut schemes = HashSet::new(); schemes.insert("http"); schemes.insert("https"); schemes.insert("mailto"); let mut builder = Builder::new(); builder .allowed_classes(classes) .url_schemes(schemes) .attribute_filter(attribute_filter) .add_tag_attributes("span", &["style"]) .add_tag_attributes("div", &["style"]); builder }); pub fn html(source: &str) -> String { let h = AMMONIA_CONFIG.clean(source).to_string(); log::debug!("{}", h); h } pub fn bbcode(source: &str) -> String { let bb = bbclash::bbcode_to_html(source); log::debug!("{}", bb); bb }