use ammonia::Builder;
use rumtk_core::dependencies::maplit::{hashmap, hashset};
use rumtk_core::strings::RUMString;
use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use std::iter::IntoIterator;
use std::sync::LazyLock;
type AllowedSet = LazyLock<HashSet<&'static str>>;
type AllowedMap = LazyLock<HashMap<&'static str, HashSet<&'static str>>>;
static CLEAN_CONTENT_TAGS: AllowedSet = LazyLock::new(|| hashset!["script", "style"]);
static CLEAN_CONTENT_TAGS_RELAXED: AllowedSet = LazyLock::new(|| hashset![]);
static ALLOWED_URL_SCHEMES: AllowedSet = LazyLock::new(|| hashset![
"https", "mailto", "data"
]);
static ALLOWED_URL_SCHEMES_RELAXED: AllowedSet = LazyLock::new(|| hashset![
"http", "https", "mailto", "data"
]);
const ALLOWED_LINK_POLICY: Option<&str> = Some("noopener noreferrer");
static ALLOWED_TAGS: AllowedSet = LazyLock::new(|| hashset![
"animate", "video", "object", "img", "noscript", "meta", "link",
"title", "form", "input", "select", "option", "textarea",
"button", "label", "fieldset", "legend",
"svg", "circle", "path", "polygon"
]);
static ALLOWED_TAGS_RELAXED: AllowedSet = LazyLock::new(|| hashset![
"script", "html", "head", "body", "header", "main", "footer", "style"
]);
static ALLOWED_GENERIC_ATTR: AllowedSet = LazyLock::new(|| hashset![
"class", "open", "hidden", "alt", "type", "height", "width", "href", "id", "data",
"action", "formaction",
"name", "for", "value", "max", "placeholder", "accept", "alt", "pattern",
"maxlength", "minlength", "autocapitalize",
"autocomplete", "autocorrect", "autofocus", "disabled", "hidden", "required",
"content"
]);
static ALLOWED_GENERIC_ATTR_RELAXED: AllowedSet = LazyLock::new(|| hashset![
"onload", "onerror", "style", "src", "srcset", "sizes", "width", "height",
"fetchpriority", "defer", "role", "as", "rel", "lang",
"onload", "onerror", "onclick", "ondblclick", "onmouseover", "onmouseout",
"onmousedown", "onmouseup", "onwheel", "onkeydown", "onkeyup", "onkeypress",
"onchange", "onfocus", "onblur", "oninput", "onsubmit", "onreset", "onunload",
"onresize", "onhashchange", "onplay", "onpause", "onended", "onvolumechange",
"ontimeupdate"
]);
static ALLOWED_HTMX_ATTR: AllowedSet = LazyLock::new(|| hashset![
"hx-get", "hx-post", "hx-put", "hx-patch", "hx-delete", "hx-on", "hx-push-url",
"hx-select", "hx-select-oob", "hx-swap", "hx-swap-oob", "hx-target", "hx-trigger",
"hx-vals",
"hx-boost", "hx-confirm", "hx-disable", "hx-disable-elt", "hx-disinherit",
"hx-encoding", "hx-ext", "hx-headers", "hx-history", "hx-history-elt", "hx-include",
"hx-indicator", "hx-inherit", "hx-params", "hx-preserve", "hx-prompt", "hx-replace-url",
"hx-request", "hx-sync", "hx-validate", "hx-vars",
]);
static mut ALLOWED_ATTRS: AllowedMap = LazyLock::new(|| hashmap![
"a" => hashset![
"href", "hreflang"
],
"bdo" => hashset![
"dir"
],
"blockquote" => hashset![
"cite"
],
"col" => hashset![
"align", "char", "charoff", "span"
],
"colgroup" => hashset![
"align", "char", "charoff", "span"
],
"del" => hashset![
"cite", "datetime"
],
"hr" => hashset![
"align", "size", "width"
],
"img" => hashset![
"align", "alt", "height", "src", "width"
],
"ins" => hashset![
"cite", "datetime"
],
"ol" => hashset![
"start"
],
"q" => hashset![
"cite"
],
"table" => hashset![
"align", "char", "charoff", "summary"
],
"tbody" => hashset![
"align", "char", "charoff"
],
"td" => hashset![
"align", "char", "charoff", "colspan", "headers", "rowspan"
],
"tfoot" => hashset![
"align", "char", "charoff"
],
"th" => hashset![
"align", "char", "charoff", "colspan", "headers", "rowspan", "scope"
],
"thead" => hashset![
"align", "char", "charoff"
],
"tr" => hashset![
"align", "char", "charoff"
],
"object" => hashset![
"data", "type", "img"
],
"path" => hashset![
"d", "stroke-linecap", "stroke-linejoin"
],
"svg" => hashset![
"fill", "height", "opacity", "stroke", "stroke-width", "viewbox",
"width", "version", "baseProfile"
],
"polygon" => hashset![
"points", "fill", "stroke", "stroke-width"
],
"circle" => hashset![
"cx", "cy", "r", "fill", "stroke", "stroke-width"
],
"video" => hashset![
"src", "controls", "autoplay", "loop", "muted", "width", "height"
],
"animate" => hashset![
"attributeName", "from", "to", "dur", "fill", "begin", "repeatCount"
],
]);
static mut ALLOWED_ATTRS_RELAXED: AllowedMap = LazyLock::new(|| hashmap![
"a" => hashset![
"href", "hreflang"
],
"bdo" => hashset![
"dir"
],
"blockquote" => hashset![
"cite"
],
"col" => hashset![
"align", "char", "charoff", "span"
],
"colgroup" => hashset![
"align", "char", "charoff", "span"
],
"del" => hashset![
"cite", "datetime"
],
"hr" => hashset![
"align", "size", "width"
],
"img" => hashset![
"align", "alt", "height", "src", "width"
],
"ins" => hashset![
"cite", "datetime"
],
"ol" => hashset![
"start"
],
"q" => hashset![
"cite"
],
"table" => hashset![
"align", "char", "charoff", "summary"
],
"tbody" => hashset![
"align", "char", "charoff"
],
"td" => hashset![
"align", "char", "charoff", "colspan", "headers", "rowspan"
],
"tfoot" => hashset![
"align", "char", "charoff"
],
"th" => hashset![
"align", "char", "charoff", "colspan", "headers", "rowspan", "scope"
],
"thead" => hashset![
"align", "char", "charoff"
],
"tr" => hashset![
"align", "char", "charoff"
],
"object" => hashset![
"data", "type", "img"
],
"style" => hashset![
],
"script" => hashset![
"src", "integrity", "crossorigin"
],
"input" => hashset![
"id", "name", "for", "type", "value", "max", "placeholder",
"accept", "alt", "pattern", "maxlength", "minlength", "autocapitalize",
"autocomplete", "autocorrect", "autofocus", "disabled", "hidden", "required",
"onload", "onerror", "onclick", "ondblclick", "onmouseover", "onmouseout",
"onmousedown", "onmouseup", "onwheel", "onkeydown", "onkeyup", "onkeypress",
"onchange", "onfocus", "onblur", "oninput", "onsubmit", "onreset", "onunload",
"onresize", "onhashchange", "onplay", "onpause", "onended", "onvolumechange",
"ontimeupdate",
],
"select" => hashset![
"id", "name", "for", "type", "value", "max", "placeholder",
"accept", "alt", "pattern", "maxlength", "minlength", "autocapitalize",
"autocomplete", "autocorrect", "autofocus", "disabled", "hidden", "required",
"onload", "onerror", "onclick", "ondblclick", "onmouseover", "onmouseout",
"onmousedown", "onmouseup", "onwheel", "onkeydown", "onkeyup", "onkeypress",
"onchange", "onfocus", "onblur", "oninput", "onsubmit", "onreset", "onunload",
"onresize", "onhashchange", "onplay", "onpause", "onended", "onvolumechange",
"ontimeupdate",
],
"path" => hashset![
"d", "stroke-linecap", "stroke-linejoin"
],
"svg" => hashset![
"fill", "height", "opacity", "stroke", "stroke-width", "viewbox",
"width", "version", "baseProfile"
],
"polygon" => hashset![
"points", "fill", "stroke", "stroke-width"
],
"circle" => hashset![
"cx", "cy", "r", "fill", "stroke", "stroke-width"
],
"video" => hashset![
"src", "controls", "autoplay", "loop", "muted", "width", "height"
],
"animate" => hashset![
"attributeName", "from", "to", "dur", "fill", "begin", "repeatCount"
],
]);
static mut STRICT_SANITIZER: LazyLock<Builder> = LazyLock::new(|| unsafe {
let mut sanitizer = Builder::default();
default_init_sanitizer(&mut sanitizer, |sanitizer| { });
sanitizer
}
);
static mut RELAXED_SANITIZER: LazyLock<Builder> = LazyLock::new(|| unsafe {
let mut sanitizer = Builder::default();
default_init_sanitizer(&mut sanitizer, |sanitizer| {
sanitizer
.link_rel(None)
.add_tags((*ALLOWED_TAGS_RELAXED).clone())
.url_schemes((*ALLOWED_URL_SCHEMES_RELAXED).clone())
.add_generic_attributes((*ALLOWED_GENERIC_ATTR_RELAXED).clone())
.clean_content_tags(CLEAN_CONTENT_TAGS_RELAXED.clone())
;
}
);
sanitizer
}
);
#[inline]
unsafe fn default_init_sanitizer(builder: &mut Builder, init_closure: impl FnOnce(&mut Builder)) {
builder
.link_rel(ALLOWED_LINK_POLICY)
.url_schemes(ALLOWED_URL_SCHEMES.clone())
.add_tags((*ALLOWED_TAGS).clone())
.tag_attributes((*ALLOWED_ATTRS).clone())
.add_generic_attributes((*ALLOWED_GENERIC_ATTR).clone())
.add_generic_attributes((*ALLOWED_HTMX_ATTR).clone())
.clean_content_tags(CLEAN_CONTENT_TAGS.clone())
.strip_comments(true);
init_closure(builder);
}
#[inline]
pub fn select_sanitizer<'a>(relaxed: bool) -> &'a Builder<'static> {
match relaxed {
true => unsafe {&*RELAXED_SANITIZER},
false => unsafe {&*STRICT_SANITIZER},
}
}
#[inline]
pub fn select_sanitizer_mut<'a>(relaxed: bool) -> &'a mut Builder<'static> {
match relaxed {
true => unsafe {&mut *RELAXED_SANITIZER},
false => unsafe {&mut *STRICT_SANITIZER},
}
}
#[inline]
pub fn sanitizer_update_attributes<T: 'static + ?Sized + Borrow<str>, I: IntoIterator<Item = &'static T>>(it: I, relaxed: bool) {
select_sanitizer_mut(relaxed).add_generic_attributes(it);
}
#[inline]
pub fn sanitizer_update_tag_attributes<T: 'static + ?Sized + Borrow<str>, U: 'static + ?Sized + Borrow<str>, I: IntoIterator<Item = &'static T>>(tag: &'static U, it: I, relaxed: bool) {
select_sanitizer_mut(relaxed).add_tag_attributes(tag, it);
}
#[inline]
pub fn sanitizers_update_attributes<T: 'static + ?Sized + Borrow<str>, I: IntoIterator<Item = &'static T> + Clone>(it: I) {
select_sanitizer_mut(false).add_generic_attributes(it.clone());
select_sanitizer_mut(true).add_generic_attributes(it);
}
#[inline]
pub fn sanitizers_update_tag_attributes<T: 'static + ?Sized + Borrow<str>, I: IntoIterator<Item = &'static T> + Clone>(tag: &'static str, it: I) {
select_sanitizer_mut(false).add_tag_attributes(tag, it.clone());
select_sanitizer_mut(false).rm_clean_content_tags(&[tag]);
select_sanitizer_mut(true).add_tag_attributes(tag, it);
select_sanitizer_mut(true).rm_clean_content_tags(&[tag]);
}
#[inline]
pub fn sanitize_html_strict(html: &str) -> RUMString {
select_sanitizer(false).clean(html).into()
}
#[inline]
pub fn sanitize_html_relaxed(html: &str) -> RUMString {
select_sanitizer(true).clean(html).into()
}
#[inline]
pub fn sanitize_html(html: &str, relaxed: bool) -> RUMString {
select_sanitizer(relaxed).clean(html).into()
}