use crate::context::tera::form::FormFilter;
use crate::context::tera::url::LinkFunction;
use crate::middleware::CsrfTokenFunction;
use crate::utils::aliases::ARlockmap;
use chrono::NaiveDateTime;
use pulldown_cmark::{Options, Parser, html};
use tera::{Filter, Kwargs, State, Tera, TeraResult, Value, escape_html};
fn mask_filter(value: &str, _: Kwargs, _: &State) -> String {
"•".repeat(value.chars().count())
}
pub struct CsrfFieldFilter;
impl Filter<&str, TeraResult<Value>> for CsrfFieldFilter {
fn is_safe(&self) -> bool {
true
}
fn call(&self, token: &str, _: Kwargs, _: &State) -> TeraResult<Value> {
let mut escaped = Vec::new();
escape_html(token, &mut escaped)
.map_err(|e| tera::Error::chain("csrf_field: failed to escape the token", e))?;
let token = String::from_utf8(escaped)?;
Ok(Value::safe_string(&format!(
r#"<input type="hidden" name="csrf_token" value="{token}">"#
)))
}
}
fn format_date_filter(value: &str, _: Kwargs, _: &State) -> String {
NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S%.f")
.or_else(|_| NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S"))
.map(|dt| dt.format("%d/%m/%Y %H:%M").to_string())
.unwrap_or_else(|_| value.to_string())
}
pub struct MarkdownFilter;
impl Filter<&str, Value> for MarkdownFilter {
fn is_safe(&self) -> bool {
true
}
fn call(&self, md: &str, _: Kwargs, _: &State) -> Value {
let mut opts = Options::empty();
opts.insert(Options::ENABLE_TABLES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
let parser = Parser::new_ext(md, opts);
let mut output = String::new();
html::push_html(&mut output, parser);
Value::safe_string(&crate::utils::sanitizer::sanitize_markdown(&output))
}
}
pub struct SanitizeFilter;
impl Filter<&str, Value> for SanitizeFilter {
fn is_safe(&self) -> bool {
true
}
fn call(&self, raw: &str, _: Kwargs, _: &State) -> Value {
Value::safe_string(&crate::utils::sanitizer::sanitize_rich(raw))
}
}
fn plaintext_filter(raw: &str, _: Kwargs, _: &State) -> String {
crate::utils::sanitizer::sanitize_strict(raw)
}
fn humanize_filter(value: &Value, _: Kwargs, _: &State) -> Value {
let Some(raw) = value.as_str() else {
return value.clone();
};
let humanized = raw
.split(['_', '-'])
.filter(|word| !word.is_empty())
.map(|word| {
let mut chars = word.chars();
match chars.next() {
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ");
Value::from(humanized)
}
fn register_filter(base_url: String, version: String) -> impl Fn(&str, Kwargs, &State) -> String {
move |file: &str, _: Kwargs, _: &State| {
let full_url = format!(
"{}/{}",
base_url.trim_end_matches('/'),
file.trim_start_matches('/')
);
if version.is_empty() {
full_url
} else {
format!(r#"{}?v={}"#, full_url, version)
}
}
}
pub fn register_asset_filters(
tera: &mut Tera,
static_url: String,
media_url: String,
runique_static_url: String,
runique_media_url: String,
url_registry: ARlockmap,
) {
let version = crate::utils::env::css_token();
crate::context::tera::contrib::register_contrib(tera);
tera.register_filter("mask", mask_filter);
tera.register_filter("static", register_filter(static_url, version.clone()));
tera.register_filter("media", register_filter(media_url, String::new()));
tera.register_filter(
"runique_static",
register_filter(runique_static_url, version),
);
tera.register_filter(
"runique_media",
register_filter(runique_media_url, String::new()),
);
tera.register_filter("form", FormFilter);
tera.register_filter("csrf_field", CsrfFieldFilter);
tera.register_filter("markdown", MarkdownFilter);
tera.register_filter("sanitize", SanitizeFilter);
tera.register_filter("plaintext", plaintext_filter);
tera.register_filter("format_date", format_date_filter);
tera.register_filter("humanize", humanize_filter);
tera.register_function("csrf_token", CsrfTokenFunction);
tera.register_function("link", LinkFunction { url_registry });
}