use core::convert::Infallible;
use core::fmt::{self, Formatter, Write};
use core::pin::Pin;
use core::str;
use crate::{FastWritable, Values};
#[inline]
pub fn safe<T, E>(text: T, escaper: E) -> Result<Safe<T>, Infallible> {
let _ = escaper; Ok(Safe(text))
}
#[inline]
pub fn escape<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
Ok(Safe(EscapeDisplay(text, escaper)))
}
pub struct EscapeDisplay<T, E>(T, E);
impl<T: fmt::Display, E: Escaper> fmt::Display for EscapeDisplay<T, E> {
#[inline]
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
write!(EscapeWriter(fmt, self.1), "{}", &self.0)
}
}
impl<T: FastWritable, E: Escaper> FastWritable for EscapeDisplay<T, E> {
#[inline]
fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
self.0.write_into(&mut EscapeWriter(dest, self.1), values)
}
}
struct EscapeWriter<W, E>(W, E);
impl<W: Write, E: Escaper> Write for EscapeWriter<W, E> {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.1.write_escaped_str(&mut self.0, s)
}
#[inline]
fn write_char(&mut self, c: char) -> fmt::Result {
self.1.write_escaped_char(&mut self.0, c)
}
}
#[inline]
pub fn e<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
escape(text, escaper)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Html;
impl Escaper for Html {
#[inline]
fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result {
crate::html::write_escaped_str(dest, string)
}
#[inline]
fn write_escaped_char<W: Write>(&self, dest: W, c: char) -> fmt::Result {
crate::html::write_escaped_char(dest, c)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Text;
impl Escaper for Text {
#[inline]
fn write_escaped_str<W: Write>(&self, mut dest: W, string: &str) -> fmt::Result {
dest.write_str(string)
}
#[inline]
fn write_escaped_char<W: Write>(&self, mut dest: W, c: char) -> fmt::Result {
dest.write_char(c)
}
}
pub trait Escaper: Copy {
fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result;
#[inline]
fn write_escaped_char<W: Write>(&self, dest: W, c: char) -> fmt::Result {
self.write_escaped_str(dest, c.encode_utf8(&mut [0; 4]))
}
}
pub trait AutoEscape {
type Escaped: fmt::Display;
type Error: Into<crate::Error>;
fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error>;
}
#[derive(Debug, Clone)]
pub struct AutoEscaper<'a, T: ?Sized, E> {
text: &'a T,
escaper: E,
}
impl<'a, T: ?Sized, E> AutoEscaper<'a, T, E> {
#[inline]
pub fn new(text: &'a T, escaper: E) -> Self {
Self { text, escaper }
}
}
impl<'a, T: fmt::Display + ?Sized, E: Escaper> AutoEscape for &&AutoEscaper<'a, T, E> {
type Escaped = EscapeDisplay<&'a T, E>;
type Error = Infallible;
#[inline]
fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
Ok(EscapeDisplay(self.text, self.escaper))
}
}
pub trait HtmlSafe: fmt::Display {}
impl<'a, T: HtmlSafe + ?Sized> AutoEscape for &AutoEscaper<'a, T, Html> {
type Escaped = &'a T;
type Error = Infallible;
#[inline]
fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
Ok(self.text)
}
}
pub enum MaybeSafe<T> {
Safe(T),
NeedsEscaping(T),
}
const _: () = {
impl<T: fmt::Display> fmt::Display for MaybeSafe<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let inner = match self {
MaybeSafe::Safe(inner) => inner,
MaybeSafe::NeedsEscaping(inner) => inner,
};
write!(f, "{inner}")
}
}
impl<T: FastWritable> FastWritable for MaybeSafe<T> {
#[inline]
fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
let inner = match self {
MaybeSafe::Safe(inner) => inner,
MaybeSafe::NeedsEscaping(inner) => inner,
};
inner.write_into(dest, values)
}
}
macro_rules! add_ref {
($([$($tt:tt)*])*) => { $(
impl<'a, T: fmt::Display, E: Escaper> AutoEscape
for &AutoEscaper<'a, $($tt)* MaybeSafe<T>, E> {
type Escaped = Wrapped<'a, T, E>;
type Error = Infallible;
#[inline]
fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
match self.text {
MaybeSafe::Safe(t) => Ok(Wrapped::Safe(t)),
MaybeSafe::NeedsEscaping(t) => Ok(Wrapped::NeedsEscaping(t, self.escaper)),
}
}
}
)* };
}
add_ref!([] [&] [&&] [&&&]);
pub enum Wrapped<'a, T: ?Sized, E> {
Safe(&'a T),
NeedsEscaping(&'a T, E),
}
impl<T: FastWritable + ?Sized, E: Escaper> FastWritable for Wrapped<'_, T, E> {
fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
match *self {
Wrapped::Safe(t) => t.write_into(dest, values),
Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest, values),
}
}
}
impl<T: fmt::Display + ?Sized, E: Escaper> fmt::Display for Wrapped<'_, T, E> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Wrapped::Safe(t) => write!(f, "{t}"),
Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).fmt(f),
}
}
}
};
pub struct Safe<T>(pub T);
const _: () = {
impl<T: fmt::Display> fmt::Display for Safe<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<T: FastWritable> FastWritable for Safe<T> {
#[inline]
fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
self.0.write_into(dest, values)
}
}
macro_rules! add_ref {
($([$($tt:tt)*])*) => { $(
impl<'a, T: fmt::Display, E> AutoEscape for &AutoEscaper<'a, $($tt)* Safe<T>, E> {
type Escaped = &'a T;
type Error = Infallible;
#[inline]
fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
Ok(&self.text.0)
}
}
)* };
}
add_ref!([] [&] [&&] [&&&]);
};
pub struct Unsafe<T>(pub T);
impl<T: fmt::Display> fmt::Display for Unsafe<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub struct HtmlSafeOutput<T>(pub T);
impl<T: fmt::Display> fmt::Display for HtmlSafeOutput<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
macro_rules! mark_html_safe {
($($ty:ty),* $(,)?) => {$(
impl HtmlSafe for $ty {}
)*};
}
mark_html_safe! {
bool,
f32, f64,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
core::num::NonZeroI8, core::num::NonZeroI16, core::num::NonZeroI32,
core::num::NonZeroI64, core::num::NonZeroI128, core::num::NonZeroIsize,
core::num::NonZeroU8, core::num::NonZeroU16, core::num::NonZeroU32,
core::num::NonZeroU64, core::num::NonZeroU128, core::num::NonZeroUsize,
}
impl<T: HtmlSafe> HtmlSafe for core::num::Wrapping<T> {}
impl<T: fmt::Display> HtmlSafe for HtmlSafeOutput<T> {}
#[cfg(feature = "alloc")]
impl<T> HtmlSafe for alloc::borrow::Cow<'_, T>
where
T: HtmlSafe + alloc::borrow::ToOwned + ?Sized,
T::Owned: HtmlSafe,
{
}
crate::impl_for_ref! {
impl HtmlSafe for T {}
}
impl<T: HtmlSafe> HtmlSafe for Pin<T> {}
pub struct Writable<'a, S: ?Sized>(pub &'a S);
pub trait WriteWritable {
fn askama_write(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()>;
}
#[test]
#[cfg(feature = "alloc")]
fn test_escape() {
use alloc::string::ToString;
assert_eq!(escape("", Html).unwrap().to_string(), "");
assert_eq!(escape("<&>", Html).unwrap().to_string(), "<&>");
assert_eq!(escape("bla&", Html).unwrap().to_string(), "bla&");
assert_eq!(escape("<foo", Html).unwrap().to_string(), "<foo");
assert_eq!(escape("bla&h", Html).unwrap().to_string(), "bla&h");
assert_eq!(escape("", Text).unwrap().to_string(), "");
assert_eq!(escape("<&>", Text).unwrap().to_string(), "<&>");
assert_eq!(escape("bla&", Text).unwrap().to_string(), "bla&");
assert_eq!(escape("<foo", Text).unwrap().to_string(), "<foo");
assert_eq!(escape("bla&h", Text).unwrap().to_string(), "bla&h");
}
#[test]
#[cfg(feature = "alloc")]
fn test_html_safe_marker() {
use alloc::string::ToString;
struct Script1;
struct Script2;
impl fmt::Display for Script1 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("<script>")
}
}
impl fmt::Display for Script2 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("<script>")
}
}
impl HtmlSafe for Script2 {}
assert_eq!(
(&&AutoEscaper::new(&Script1, Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Script2, Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Script1, Text))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Script2, Text))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Safe(Script1), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Safe(Script2), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Unsafe(Script1), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Unsafe(Script2), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&MaybeSafe::Safe(Script1), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&MaybeSafe::Safe(Script2), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script1), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script2), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Safe(std::pin::Pin::new(&Script1)), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
assert_eq!(
(&&AutoEscaper::new(&Safe(std::pin::Pin::new(&Script2)), Html))
.askama_auto_escape()
.unwrap()
.to_string(),
"<script>",
);
}