hyaenidae/content/src/lib.rs

139 lines
3.6 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");
div_hs.insert("toolkit-code");
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");
pre_hs.insert("toolkit-code--pre");
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");
tags.insert("hr");
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
}