askama/filters/
escape.rs

1use core::convert::Infallible;
2use core::fmt::{self, Formatter, Write};
3use core::pin::Pin;
4use core::str;
5
6use crate::{FastWritable, Values};
7
8/// Marks a string (or other `Display` type) as safe
9///
10/// Use this if you want to allow markup in an expression, or if you know
11/// that the expression's contents don't need to be escaped.
12///
13/// Askama will automatically insert the first (`Escaper`) argument,
14/// so this filter only takes a single argument of any type that implements
15/// `Display`.
16///
17/// ```
18/// # #[cfg(feature = "code-in-doc")] {
19/// # use askama::Template;
20/// /// ```jinja
21/// /// <div>{{ example|safe }}</div>
22/// /// ```
23/// #[derive(Template)]
24/// #[template(ext = "html", in_doc = true)]
25/// struct Example<'a> {
26///     example: &'a str,
27/// }
28///
29/// assert_eq!(
30///     Example { example: "<p>I'm Safe</p>" }.to_string(),
31///     "<div><p>I'm Safe</p></div>"
32/// );
33/// # }
34/// ```
35#[inline]
36pub fn safe<T, E>(text: T, escaper: E) -> Result<Safe<T>, Infallible> {
37    let _ = escaper; // it should not be part of the interface that the `escaper` is unused
38    Ok(Safe(text))
39}
40
41/// Escapes strings according to the escape mode.
42///
43/// Askama will automatically insert the first (`Escaper`) argument,
44/// so this filter only takes a single argument of any type that implements
45/// `Display`.
46///
47/// It is possible to optionally specify an escaper other than the default for
48/// the template's extension, like `{{ val|escape("txt") }}`.
49///
50/// ```
51/// # #[cfg(feature = "code-in-doc")] {
52/// # use askama::Template;
53/// /// ```jinja
54/// /// <div>{{ example|escape }}</div>
55/// /// ```
56/// #[derive(Template)]
57/// #[template(ext = "html", in_doc = true)]
58/// struct Example<'a> {
59///     example: &'a str,
60/// }
61///
62/// assert_eq!(
63///     Example { example: "Escape <>&" }.to_string(),
64///     "<div>Escape &#60;&#62;&#38;</div>"
65/// );
66/// # }
67/// ```
68#[inline]
69pub fn escape<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
70    Ok(Safe(EscapeDisplay(text, escaper)))
71}
72
73pub struct EscapeDisplay<T, E>(T, E);
74
75impl<T: fmt::Display, E: Escaper> fmt::Display for EscapeDisplay<T, E> {
76    #[inline]
77    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
78        write!(EscapeWriter(fmt, self.1), "{}", &self.0)
79    }
80}
81
82impl<T: FastWritable, E: Escaper> FastWritable for EscapeDisplay<T, E> {
83    #[inline]
84    fn write_into<W: fmt::Write + ?Sized>(
85        &self,
86        dest: &mut W,
87        values: &dyn Values,
88    ) -> crate::Result<()> {
89        self.0.write_into(&mut EscapeWriter(dest, self.1), values)
90    }
91}
92
93struct EscapeWriter<W, E>(W, E);
94
95impl<W: Write, E: Escaper> Write for EscapeWriter<W, E> {
96    #[inline]
97    fn write_str(&mut self, s: &str) -> fmt::Result {
98        self.1.write_escaped_str(&mut self.0, s)
99    }
100
101    #[inline]
102    fn write_char(&mut self, c: char) -> fmt::Result {
103        self.1.write_escaped_char(&mut self.0, c)
104    }
105}
106
107/// Alias for [`escape()`]
108///
109/// ```
110/// # #[cfg(feature = "code-in-doc")] {
111/// # use askama::Template;
112/// /// ```jinja
113/// /// <div>{{ example|e }}</div>
114/// /// ```
115/// #[derive(Template)]
116/// #[template(ext = "html", in_doc = true)]
117/// struct Example<'a> {
118///     example: &'a str,
119/// }
120///
121/// assert_eq!(
122///     Example { example: "Escape <>&" }.to_string(),
123///     "<div>Escape &#60;&#62;&#38;</div>"
124/// );
125/// # }
126/// ```
127#[inline]
128pub fn e<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
129    escape(text, escaper)
130}
131
132/// Escape characters in a safe way for HTML texts and attributes
133///
134/// * `"` => `&#34;`
135/// * `&` => `&#38;`
136/// * `'` => `&#39;`
137/// * `<` => `&#60;`
138/// * `>` => `&#62;`
139#[derive(Debug, Clone, Copy, Default)]
140pub struct Html;
141
142impl Escaper for Html {
143    #[inline]
144    fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result {
145        crate::html::write_escaped_str(dest, string)
146    }
147
148    #[inline]
149    fn write_escaped_char<W: Write>(&self, dest: W, c: char) -> fmt::Result {
150        crate::html::write_escaped_char(dest, c)
151    }
152}
153
154/// Don't escape the input but return in verbatim
155#[derive(Debug, Clone, Copy, Default)]
156pub struct Text;
157
158impl Escaper for Text {
159    #[inline]
160    fn write_escaped_str<W: Write>(&self, mut dest: W, string: &str) -> fmt::Result {
161        dest.write_str(string)
162    }
163
164    #[inline]
165    fn write_escaped_char<W: Write>(&self, mut dest: W, c: char) -> fmt::Result {
166        dest.write_char(c)
167    }
168}
169
170/// Escapers are used to make generated text safe for printing in some context.
171///
172/// E.g. in an [`Html`] context, any and all generated text can be used in HTML/XML text nodes and
173/// attributes, without for for maliciously injected data.
174pub trait Escaper: Copy {
175    /// Escaped the input string `string` into `dest`
176    fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result;
177
178    /// Escaped the input char `c` into `dest`
179    #[inline]
180    fn write_escaped_char<W: Write>(&self, dest: W, c: char) -> fmt::Result {
181        self.write_escaped_str(dest, c.encode_utf8(&mut [0; 4]))
182    }
183}
184
185/// Used internally by askama to select the appropriate escaper
186pub trait AutoEscape {
187    /// The wrapped or converted result type
188    type Escaped: fmt::Display;
189    /// Early error testing for the input value, usually [`Infallible`]
190    type Error: Into<crate::Error>;
191
192    /// Used internally by askama to select the appropriate escaper
193    fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error>;
194}
195
196/// Used internally by askama to select the appropriate escaper
197#[derive(Debug, Clone)]
198pub struct AutoEscaper<'a, T: ?Sized, E> {
199    text: &'a T,
200    escaper: E,
201}
202
203impl<'a, T: ?Sized, E> AutoEscaper<'a, T, E> {
204    /// Used internally by askama to select the appropriate escaper
205    #[inline]
206    pub fn new(text: &'a T, escaper: E) -> Self {
207        Self { text, escaper }
208    }
209}
210
211/// Use the provided escaper
212impl<'a, T: fmt::Display + ?Sized, E: Escaper> AutoEscape for &&AutoEscaper<'a, T, E> {
213    type Escaped = EscapeDisplay<&'a T, E>;
214    type Error = Infallible;
215
216    #[inline]
217    fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
218        Ok(EscapeDisplay(self.text, self.escaper))
219    }
220}
221
222/// Types that implement this marker trait don't need to be HTML escaped
223///
224/// Please note that this trait is only meant as speed-up helper. In some odd circumcises askama
225/// might still decide to HTML escape the input, so if this must not happen, then you need to use
226/// the [`|safe`](super::safe) filter to prevent the auto escaping.
227///
228/// If you are unsure if your type generates HTML safe output in all cases, then DON'T mark it.
229/// Better safe than sorry!
230pub trait HtmlSafe: fmt::Display {}
231
232/// Don't escape HTML safe types
233impl<'a, T: HtmlSafe + ?Sized> AutoEscape for &AutoEscaper<'a, T, Html> {
234    type Escaped = &'a T;
235    type Error = Infallible;
236
237    #[inline]
238    fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
239        Ok(self.text)
240    }
241}
242
243/// Mark the output of a filter as "maybe safe"
244///
245/// This enum can be used as a transparent return type of custom filters that want to mark
246/// their output as "safe" depending on some circumstances, i.e. that their output maybe does not
247/// need to be escaped.
248///
249/// If the filter is not used as the last element in the filter chain, then any assumption is void.
250/// Let the next filter decide if the output is safe or not.
251///
252/// ## Example
253///
254/// ```rust
255/// mod filters {
256///     use askama::{filters::MaybeSafe, Result, Values};
257///
258///     // Do not actually use this filter! It's an intentionally bad example.
259///     pub fn backdoor<T: std::fmt::Display>(
260///         s: T,
261///         _: &dyn Values,
262///         enable: &bool,
263///     ) -> Result<MaybeSafe<T>> {
264///         Ok(match *enable {
265///             true => MaybeSafe::Safe(s),
266///             false => MaybeSafe::NeedsEscaping(s),
267///         })
268///     }
269/// }
270///
271/// #[derive(askama::Template)]
272/// #[template(
273///     source = "<div class='{{ klass|backdoor(enable_backdoor) }}'></div>",
274///     ext = "html"
275/// )]
276/// struct DivWithBackdoor<'a> {
277///     klass: &'a str,
278///     enable_backdoor: bool,
279/// }
280///
281/// assert_eq!(
282///     DivWithBackdoor { klass: "<script>", enable_backdoor: false }.to_string(),
283///     "<div class='&#60;script&#62;'></div>",
284/// );
285/// assert_eq!(
286///     DivWithBackdoor { klass: "<script>", enable_backdoor: true }.to_string(),
287///     "<div class='<script>'></div>",
288/// );
289/// ```
290pub enum MaybeSafe<T> {
291    /// The contained value does not need escaping
292    Safe(T),
293    /// The contained value needs to be escaped
294    NeedsEscaping(T),
295}
296
297const _: () = {
298    // This is the fallback. The filter is not the last element of the filter chain.
299    impl<T: fmt::Display> fmt::Display for MaybeSafe<T> {
300        #[inline]
301        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
302            let inner = match self {
303                MaybeSafe::Safe(inner) => inner,
304                MaybeSafe::NeedsEscaping(inner) => inner,
305            };
306            write!(f, "{inner}")
307        }
308    }
309
310    // This is the fallback. The filter is not the last element of the filter chain.
311    impl<T: FastWritable> FastWritable for MaybeSafe<T> {
312        #[inline]
313        fn write_into<W: fmt::Write + ?Sized>(
314            &self,
315            dest: &mut W,
316            values: &dyn Values,
317        ) -> crate::Result<()> {
318            let inner = match self {
319                MaybeSafe::Safe(inner) => inner,
320                MaybeSafe::NeedsEscaping(inner) => inner,
321            };
322            inner.write_into(dest, values)
323        }
324    }
325
326    macro_rules! add_ref {
327        ($([$($tt:tt)*])*) => { $(
328            impl<'a, T: fmt::Display, E: Escaper> AutoEscape
329            for &AutoEscaper<'a, $($tt)* MaybeSafe<T>, E> {
330                type Escaped = Wrapped<'a, T, E>;
331                type Error = Infallible;
332
333                #[inline]
334                fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
335                    match self.text {
336                        MaybeSafe::Safe(t) => Ok(Wrapped::Safe(t)),
337                        MaybeSafe::NeedsEscaping(t) => Ok(Wrapped::NeedsEscaping(t, self.escaper)),
338                    }
339                }
340            }
341        )* };
342    }
343
344    add_ref!([] [&] [&&] [&&&]);
345
346    pub enum Wrapped<'a, T: ?Sized, E> {
347        Safe(&'a T),
348        NeedsEscaping(&'a T, E),
349    }
350
351    impl<T: FastWritable + ?Sized, E: Escaper> FastWritable for Wrapped<'_, T, E> {
352        fn write_into<W: fmt::Write + ?Sized>(
353            &self,
354            dest: &mut W,
355            values: &dyn Values,
356        ) -> crate::Result<()> {
357            match *self {
358                Wrapped::Safe(t) => t.write_into(dest, values),
359                Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest, values),
360            }
361        }
362    }
363
364    impl<T: fmt::Display + ?Sized, E: Escaper> fmt::Display for Wrapped<'_, T, E> {
365        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
366            match *self {
367                Wrapped::Safe(t) => write!(f, "{t}"),
368                Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).fmt(f),
369            }
370        }
371    }
372};
373
374/// Mark the output of a filter as "safe"
375///
376/// This struct can be used as a transparent return type of custom filters that want to mark their
377/// output as "safe" no matter what, i.e. that their output does not need to be escaped.
378///
379/// If the filter is not used as the last element in the filter chain, then any assumption is void.
380/// Let the next filter decide if the output is safe or not.
381///
382/// ## Example
383///
384/// ```rust
385/// mod filters {
386///     use askama::{filters::Safe, Result, Values};
387///
388///     // Do not actually use this filter! It's an intentionally bad example.
389///     pub fn strip_except_apos(s: impl ToString, _: &dyn Values) -> Result<Safe<String>> {
390///         Ok(Safe(s
391///             .to_string()
392///             .chars()
393///             .filter(|c| !matches!(c, '<' | '>' | '"' | '&'))
394///             .collect()
395///         ))
396///     }
397/// }
398///
399/// #[derive(askama::Template)]
400/// #[template(
401///     source = "<div class='{{ klass|strip_except_apos }}'></div>",
402///     ext = "html"
403/// )]
404/// struct DivWithClass<'a> {
405///     klass: &'a str,
406/// }
407///
408/// assert_eq!(
409///     DivWithClass { klass: "<&'lifetime X>" }.to_string(),
410///     "<div class=''lifetime X'></div>",
411/// );
412/// ```
413pub struct Safe<T>(pub T);
414
415const _: () = {
416    // This is the fallback. The filter is not the last element of the filter chain.
417    impl<T: fmt::Display> fmt::Display for Safe<T> {
418        #[inline]
419        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
420            write!(f, "{}", self.0)
421        }
422    }
423
424    // This is the fallback. The filter is not the last element of the filter chain.
425    impl<T: FastWritable> FastWritable for Safe<T> {
426        #[inline]
427        fn write_into<W: fmt::Write + ?Sized>(
428            &self,
429            dest: &mut W,
430            values: &dyn Values,
431        ) -> crate::Result<()> {
432            self.0.write_into(dest, values)
433        }
434    }
435
436    macro_rules! add_ref {
437        ($([$($tt:tt)*])*) => { $(
438            impl<'a, T: fmt::Display, E> AutoEscape for &AutoEscaper<'a, $($tt)* Safe<T>, E> {
439                type Escaped = &'a T;
440                type Error = Infallible;
441
442                #[inline]
443                fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
444                    Ok(&self.text.0)
445                }
446            }
447        )* };
448    }
449
450    add_ref!([] [&] [&&] [&&&]);
451};
452
453/// There is not need to mark the output of a custom filter as "unsafe"; this is simply the default
454pub struct Unsafe<T>(pub T);
455
456impl<T: fmt::Display> fmt::Display for Unsafe<T> {
457    #[inline]
458    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
459        write!(f, "{}", self.0)
460    }
461}
462
463/// Like [`Safe`], but only for HTML output
464pub struct HtmlSafeOutput<T>(pub T);
465
466impl<T: fmt::Display> fmt::Display for HtmlSafeOutput<T> {
467    #[inline]
468    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
469        write!(f, "{}", self.0)
470    }
471}
472
473macro_rules! mark_html_safe {
474    ($($ty:ty),* $(,)?) => {$(
475        impl HtmlSafe for $ty {}
476    )*};
477}
478
479mark_html_safe! {
480    bool,
481    f32, f64,
482    i8, i16, i32, i64, i128, isize,
483    u8, u16, u32, u64, u128, usize,
484    core::num::NonZeroI8, core::num::NonZeroI16, core::num::NonZeroI32,
485    core::num::NonZeroI64, core::num::NonZeroI128, core::num::NonZeroIsize,
486    core::num::NonZeroU8, core::num::NonZeroU16, core::num::NonZeroU32,
487    core::num::NonZeroU64, core::num::NonZeroU128, core::num::NonZeroUsize,
488}
489
490impl<T: HtmlSafe> HtmlSafe for core::num::Wrapping<T> {}
491impl<T: fmt::Display> HtmlSafe for HtmlSafeOutput<T> {}
492
493#[cfg(feature = "alloc")]
494impl<T> HtmlSafe for alloc::borrow::Cow<'_, T>
495where
496    T: HtmlSafe + alloc::borrow::ToOwned + ?Sized,
497    T::Owned: HtmlSafe,
498{
499}
500
501crate::impl_for_ref! {
502    impl HtmlSafe for T {}
503}
504
505impl<T: HtmlSafe> HtmlSafe for Pin<T> {}
506
507/// Used internally by askama to select the appropriate [`write!()`] mechanism
508pub struct Writable<'a, S: ?Sized>(pub &'a S);
509
510/// Used internally by askama to select the appropriate [`write!()`] mechanism
511pub trait WriteWritable {
512    /// Used internally by askama to select the appropriate [`write!()`] mechanism
513    fn askama_write<W: fmt::Write + ?Sized>(
514        &self,
515        dest: &mut W,
516        values: &dyn Values,
517    ) -> crate::Result<()>;
518}
519
520#[test]
521#[cfg(feature = "alloc")]
522fn test_escape() {
523    use alloc::string::ToString;
524
525    assert_eq!(escape("", Html).unwrap().to_string(), "");
526    assert_eq!(escape("<&>", Html).unwrap().to_string(), "&#60;&#38;&#62;");
527    assert_eq!(escape("bla&", Html).unwrap().to_string(), "bla&#38;");
528    assert_eq!(escape("<foo", Html).unwrap().to_string(), "&#60;foo");
529    assert_eq!(escape("bla&h", Html).unwrap().to_string(), "bla&#38;h");
530
531    assert_eq!(escape("", Text).unwrap().to_string(), "");
532    assert_eq!(escape("<&>", Text).unwrap().to_string(), "<&>");
533    assert_eq!(escape("bla&", Text).unwrap().to_string(), "bla&");
534    assert_eq!(escape("<foo", Text).unwrap().to_string(), "<foo");
535    assert_eq!(escape("bla&h", Text).unwrap().to_string(), "bla&h");
536}
537
538#[test]
539#[cfg(feature = "alloc")]
540fn test_html_safe_marker() {
541    use alloc::string::ToString;
542
543    struct Script1;
544    struct Script2;
545
546    impl fmt::Display for Script1 {
547        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
548            f.write_str("<script>")
549        }
550    }
551
552    impl fmt::Display for Script2 {
553        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
554            f.write_str("<script>")
555        }
556    }
557
558    impl HtmlSafe for Script2 {}
559
560    assert_eq!(
561        (&&AutoEscaper::new(&Script1, Html))
562            .askama_auto_escape()
563            .unwrap()
564            .to_string(),
565        "&#60;script&#62;",
566    );
567    assert_eq!(
568        (&&AutoEscaper::new(&Script2, Html))
569            .askama_auto_escape()
570            .unwrap()
571            .to_string(),
572        "<script>",
573    );
574
575    assert_eq!(
576        (&&AutoEscaper::new(&Script1, Text))
577            .askama_auto_escape()
578            .unwrap()
579            .to_string(),
580        "<script>",
581    );
582    assert_eq!(
583        (&&AutoEscaper::new(&Script2, Text))
584            .askama_auto_escape()
585            .unwrap()
586            .to_string(),
587        "<script>",
588    );
589
590    assert_eq!(
591        (&&AutoEscaper::new(&Safe(Script1), Html))
592            .askama_auto_escape()
593            .unwrap()
594            .to_string(),
595        "<script>",
596    );
597    assert_eq!(
598        (&&AutoEscaper::new(&Safe(Script2), Html))
599            .askama_auto_escape()
600            .unwrap()
601            .to_string(),
602        "<script>",
603    );
604
605    assert_eq!(
606        (&&AutoEscaper::new(&Unsafe(Script1), Html))
607            .askama_auto_escape()
608            .unwrap()
609            .to_string(),
610        "&#60;script&#62;",
611    );
612    assert_eq!(
613        (&&AutoEscaper::new(&Unsafe(Script2), Html))
614            .askama_auto_escape()
615            .unwrap()
616            .to_string(),
617        "&#60;script&#62;",
618    );
619
620    assert_eq!(
621        (&&AutoEscaper::new(&MaybeSafe::Safe(Script1), Html))
622            .askama_auto_escape()
623            .unwrap()
624            .to_string(),
625        "<script>",
626    );
627    assert_eq!(
628        (&&AutoEscaper::new(&MaybeSafe::Safe(Script2), Html))
629            .askama_auto_escape()
630            .unwrap()
631            .to_string(),
632        "<script>",
633    );
634    assert_eq!(
635        (&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script1), Html))
636            .askama_auto_escape()
637            .unwrap()
638            .to_string(),
639        "&#60;script&#62;",
640    );
641    assert_eq!(
642        (&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script2), Html))
643            .askama_auto_escape()
644            .unwrap()
645            .to_string(),
646        "&#60;script&#62;",
647    );
648
649    assert_eq!(
650        (&&AutoEscaper::new(&Safe(std::pin::Pin::new(&Script1)), Html))
651            .askama_auto_escape()
652            .unwrap()
653            .to_string(),
654        "<script>",
655    );
656    assert_eq!(
657        (&&AutoEscaper::new(&Safe(std::pin::Pin::new(&Script2)), Html))
658            .askama_auto_escape()
659            .unwrap()
660            .to_string(),
661        "<script>",
662    );
663}