2021-01-28 02:57:28 +00:00
|
|
|
use ammonia::Builder;
|
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
2021-01-31 19:50:34 +00:00
|
|
|
mod bbcode;
|
|
|
|
mod color;
|
|
|
|
mod email;
|
|
|
|
mod handle;
|
|
|
|
mod render;
|
|
|
|
mod url;
|
|
|
|
|
|
|
|
pub use bbcode::Tag;
|
|
|
|
pub use render::NodeView;
|
|
|
|
|
2021-01-28 02:57:28 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 19:50:34 +00:00
|
|
|
static STRIP_CONFIG: Lazy<Builder> = Lazy::new(|| {
|
|
|
|
let mut builder = Builder::new();
|
|
|
|
builder.allowed_classes(HashMap::new()).tags(HashSet::new());
|
|
|
|
builder
|
|
|
|
});
|
|
|
|
|
2021-01-28 02:57:28 +00:00
|
|
|
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");
|
|
|
|
|
2021-01-31 19:50:34 +00:00
|
|
|
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");
|
|
|
|
|
2021-01-28 02:57:28 +00:00
|
|
|
let mut builder = Builder::new();
|
|
|
|
builder
|
2021-01-31 19:50:34 +00:00
|
|
|
.tags(tags)
|
2021-01-28 02:57:28 +00:00
|
|
|
.allowed_classes(classes)
|
|
|
|
.url_schemes(schemes)
|
2021-01-31 19:50:34 +00:00
|
|
|
.link_rel(Some("nofollow noopener noreferer"))
|
2021-01-28 02:57:28 +00:00
|
|
|
.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
|
|
|
|
}
|
|
|
|
|
2021-01-31 19:50:34 +00:00
|
|
|
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
|
2021-01-28 02:57:28 +00:00
|
|
|
}
|