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