asonix
0e1552eeaa
This gives us more control over things, like automatic 'linkifying', and the ability to add custom user tagging logic
136 lines
3.5 KiB
Rust
136 lines
3.5 KiB
Rust
use ammonia::Builder;
|
|
use once_cell::sync::Lazy;
|
|
use std::borrow::Cow;
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
mod bbcode;
|
|
mod color;
|
|
mod email;
|
|
mod handle;
|
|
mod render;
|
|
mod url;
|
|
|
|
pub use bbcode::Tag;
|
|
pub use render::NodeView;
|
|
|
|
fn allow_styles<'u>(allowed: &[&str], value: &'u str) -> Option<Cow<'u, str>> {
|
|
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<Cow<'u, str>> {
|
|
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")
|
|
| ("pre", "class")
|
|
| ("span", "data-symbol")
|
|
| ("blockquote", "data-author")
|
|
| ("a", "rel")
|
|
| ("a", "title")
|
|
| ("a", "href")
|
|
| ("img", "src")
|
|
| ("img", "title")
|
|
| ("img", "alt") => Some(Cow::Borrowed(value)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
static STRIP_CONFIG: Lazy<Builder> = Lazy::new(|| {
|
|
let mut builder = Builder::new();
|
|
builder.allowed_classes(HashMap::new()).tags(HashSet::new());
|
|
builder
|
|
});
|
|
|
|
static AMMONIA_CONFIG: Lazy<Builder> = 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");
|
|
|
|
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");
|
|
|
|
let pre_hs = classes.entry("pre").or_insert(HashSet::new());
|
|
pre_hs.insert("codeblock");
|
|
|
|
let mut schemes = HashSet::new();
|
|
schemes.insert("http");
|
|
schemes.insert("https");
|
|
schemes.insert("mailto");
|
|
|
|
let mut tags = HashSet::new();
|
|
tags.insert("div");
|
|
tags.insert("span");
|
|
tags.insert("pre");
|
|
tags.insert("code");
|
|
tags.insert("i");
|
|
tags.insert("em");
|
|
tags.insert("b");
|
|
tags.insert("strong");
|
|
tags.insert("s");
|
|
tags.insert("sub");
|
|
tags.insert("sup");
|
|
tags.insert("blockquote");
|
|
tags.insert("a");
|
|
tags.insert("img");
|
|
tags.insert("br");
|
|
|
|
let mut builder = Builder::new();
|
|
builder
|
|
.tags(tags)
|
|
.allowed_classes(classes)
|
|
.url_schemes(schemes)
|
|
.link_rel(Some("nofollow noopener noreferer"))
|
|
.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<F>(source: &str, mapper: F) -> String
|
|
where
|
|
for<'a> F: Fn(NodeView<'a>) -> NodeView<'a> + Copy,
|
|
{
|
|
let stripped = STRIP_CONFIG.clean(source).to_string();
|
|
let preprocessed = render::preprocessor(&stripped, mapper);
|
|
log::debug!("{}", preprocessed);
|
|
preprocessed
|
|
}
|