1use core::convert::Infallible;
2use core::fmt::{self, Formatter, Write};
3use core::pin::Pin;
4use core::str;
5
6use crate::{FastWritable, Values};
7
8#[inline]
36pub fn safe<T, E>(text: T, escaper: E) -> Result<Safe<T>, Infallible> {
37 let _ = escaper; Ok(Safe(text))
39}
40
41#[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#[inline]
128pub fn e<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
129 escape(text, escaper)
130}
131
132#[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#[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
170pub trait Escaper: Copy {
175 fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result;
177
178 #[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
185pub trait AutoEscape {
187 type Escaped: fmt::Display;
189 type Error: Into<crate::Error>;
191
192 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error>;
194}
195
196#[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 #[inline]
206 pub fn new(text: &'a T, escaper: E) -> Self {
207 Self { text, escaper }
208 }
209}
210
211impl<'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
222pub trait HtmlSafe: fmt::Display {}
231
232impl<'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
243pub enum MaybeSafe<T> {
291 Safe(T),
293 NeedsEscaping(T),
295}
296
297const _: () = {
298 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 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
374pub struct Safe<T>(pub T);
414
415const _: () = {
416 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 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
453pub 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
463pub 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
507pub struct Writable<'a, S: ?Sized>(pub &'a S);
509
510pub trait WriteWritable {
512 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(), "<&>");
527 assert_eq!(escape("bla&", Html).unwrap().to_string(), "bla&");
528 assert_eq!(escape("<foo", Html).unwrap().to_string(), "<foo");
529 assert_eq!(escape("bla&h", Html).unwrap().to_string(), "bla&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 "<script>",
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 "<script>",
611 );
612 assert_eq!(
613 (&&AutoEscaper::new(&Unsafe(Script2), Html))
614 .askama_auto_escape()
615 .unwrap()
616 .to_string(),
617 "<script>",
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 "<script>",
640 );
641 assert_eq!(
642 (&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script2), Html))
643 .askama_auto_escape()
644 .unwrap()
645 .to_string(),
646 "<script>",
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}