1use core::convert::Infallible;
2use core::fmt::{self, Formatter, Write};
3use core::ops::Deref;
4use core::pin::Pin;
5use core::str;
6
7use crate::Values;
8
9#[inline]
37pub fn safe<T, E>(text: T, escaper: E) -> Result<Safe<T>, Infallible> {
38 let _ = escaper; Ok(Safe(text))
40}
41
42#[inline]
70pub fn escape<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
71 Ok(Safe(EscapeDisplay(text, escaper)))
72}
73
74pub struct EscapeDisplay<T, E>(T, E);
75
76impl<T: fmt::Display, E: Escaper> fmt::Display for EscapeDisplay<T, E> {
77 #[inline]
78 fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
79 write!(EscapeWriter(fmt, self.1), "{}", &self.0)
80 }
81}
82
83impl<T: FastWritable, E: Escaper> FastWritable for EscapeDisplay<T, E> {
84 #[inline]
85 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
86 self.0.write_into(&mut EscapeWriter(dest, self.1))
87 }
88}
89
90struct EscapeWriter<W, E>(W, E);
91
92impl<W: Write, E: Escaper> Write for EscapeWriter<W, E> {
93 #[inline]
94 fn write_str(&mut self, s: &str) -> fmt::Result {
95 self.1.write_escaped_str(&mut self.0, s)
96 }
97
98 #[inline]
99 fn write_char(&mut self, c: char) -> fmt::Result {
100 self.1.write_escaped_char(&mut self.0, c)
101 }
102}
103
104#[inline]
125pub fn e<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
126 escape(text, escaper)
127}
128
129#[derive(Debug, Clone, Copy, Default)]
137pub struct Html;
138
139impl Escaper for Html {
140 #[inline]
141 fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result {
142 crate::html::write_escaped_str(dest, string)
143 }
144
145 #[inline]
146 fn write_escaped_char<W: Write>(&self, dest: W, c: char) -> fmt::Result {
147 crate::html::write_escaped_char(dest, c)
148 }
149}
150
151#[derive(Debug, Clone, Copy, Default)]
153pub struct Text;
154
155impl Escaper for Text {
156 #[inline]
157 fn write_escaped_str<W: Write>(&self, mut dest: W, string: &str) -> fmt::Result {
158 dest.write_str(string)
159 }
160
161 #[inline]
162 fn write_escaped_char<W: Write>(&self, mut dest: W, c: char) -> fmt::Result {
163 dest.write_char(c)
164 }
165}
166
167pub trait Escaper: Copy {
172 fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result;
174
175 #[inline]
177 fn write_escaped_char<W: Write>(&self, dest: W, c: char) -> fmt::Result {
178 self.write_escaped_str(dest, c.encode_utf8(&mut [0; 4]))
179 }
180}
181
182pub trait AutoEscape {
184 type Escaped: fmt::Display;
186 type Error: Into<crate::Error>;
188
189 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error>;
191}
192
193#[derive(Debug, Clone)]
195pub struct AutoEscaper<'a, T: ?Sized, E> {
196 text: &'a T,
197 escaper: E,
198}
199
200impl<'a, T: ?Sized, E> AutoEscaper<'a, T, E> {
201 #[inline]
203 pub fn new(text: &'a T, escaper: E) -> Self {
204 Self { text, escaper }
205 }
206}
207
208impl<'a, T: fmt::Display + ?Sized, E: Escaper> AutoEscape for &&AutoEscaper<'a, T, E> {
210 type Escaped = EscapeDisplay<&'a T, E>;
211 type Error = Infallible;
212
213 #[inline]
214 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
215 Ok(EscapeDisplay(self.text, self.escaper))
216 }
217}
218
219pub trait HtmlSafe: fmt::Display {}
228
229impl<'a, T: HtmlSafe + ?Sized> AutoEscape for &AutoEscaper<'a, T, Html> {
231 type Escaped = &'a T;
232 type Error = Infallible;
233
234 #[inline]
235 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
236 Ok(self.text)
237 }
238}
239
240pub enum MaybeSafe<T> {
284 Safe(T),
286 NeedsEscaping(T),
288}
289
290const _: () = {
291 impl<T: fmt::Display> fmt::Display for MaybeSafe<T> {
293 #[inline]
294 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
295 let inner = match self {
296 MaybeSafe::Safe(inner) => inner,
297 MaybeSafe::NeedsEscaping(inner) => inner,
298 };
299 write!(f, "{inner}")
300 }
301 }
302
303 impl<T: FastWritable> FastWritable for MaybeSafe<T> {
305 #[inline]
306 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
307 let inner = match self {
308 MaybeSafe::Safe(inner) => inner,
309 MaybeSafe::NeedsEscaping(inner) => inner,
310 };
311 inner.write_into(dest)
312 }
313 }
314
315 macro_rules! add_ref {
316 ($([$($tt:tt)*])*) => { $(
317 impl<'a, T: fmt::Display, E: Escaper> AutoEscape
318 for &AutoEscaper<'a, $($tt)* MaybeSafe<T>, E> {
319 type Escaped = Wrapped<'a, T, E>;
320 type Error = Infallible;
321
322 #[inline]
323 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
324 match self.text {
325 MaybeSafe::Safe(t) => Ok(Wrapped::Safe(t)),
326 MaybeSafe::NeedsEscaping(t) => Ok(Wrapped::NeedsEscaping(t, self.escaper)),
327 }
328 }
329 }
330 )* };
331 }
332
333 add_ref!([] [&] [&&] [&&&]);
334
335 pub enum Wrapped<'a, T: ?Sized, E> {
336 Safe(&'a T),
337 NeedsEscaping(&'a T, E),
338 }
339
340 impl<T: FastWritable + ?Sized, E: Escaper> FastWritable for Wrapped<'_, T, E> {
341 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
342 match *self {
343 Wrapped::Safe(t) => t.write_into(dest),
344 Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest),
345 }
346 }
347 }
348
349 impl<T: fmt::Display + ?Sized, E: Escaper> fmt::Display for Wrapped<'_, T, E> {
350 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
351 match *self {
352 Wrapped::Safe(t) => write!(f, "{t}"),
353 Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).fmt(f),
354 }
355 }
356 }
357};
358
359pub struct Safe<T>(pub T);
399
400const _: () = {
401 impl<T: fmt::Display> fmt::Display for Safe<T> {
403 #[inline]
404 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
405 write!(f, "{}", self.0)
406 }
407 }
408
409 impl<T: FastWritable> FastWritable for Safe<T> {
411 #[inline]
412 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
413 self.0.write_into(dest)
414 }
415 }
416
417 macro_rules! add_ref {
418 ($([$($tt:tt)*])*) => { $(
419 impl<'a, T: fmt::Display, E> AutoEscape for &AutoEscaper<'a, $($tt)* Safe<T>, E> {
420 type Escaped = &'a T;
421 type Error = Infallible;
422
423 #[inline]
424 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error> {
425 Ok(&self.text.0)
426 }
427 }
428 )* };
429 }
430
431 add_ref!([] [&] [&&] [&&&]);
432};
433
434pub struct Unsafe<T>(pub T);
436
437impl<T: fmt::Display> fmt::Display for Unsafe<T> {
438 #[inline]
439 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
440 write!(f, "{}", self.0)
441 }
442}
443
444pub struct HtmlSafeOutput<T>(pub T);
446
447impl<T: fmt::Display> fmt::Display for HtmlSafeOutput<T> {
448 #[inline]
449 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
450 write!(f, "{}", self.0)
451 }
452}
453
454macro_rules! mark_html_safe {
455 ($($ty:ty),* $(,)?) => {$(
456 impl HtmlSafe for $ty {}
457 )*};
458}
459
460mark_html_safe! {
461 bool,
462 f32, f64,
463 i8, i16, i32, i64, i128, isize,
464 u8, u16, u32, u64, u128, usize,
465 core::num::NonZeroI8, core::num::NonZeroI16, core::num::NonZeroI32,
466 core::num::NonZeroI64, core::num::NonZeroI128, core::num::NonZeroIsize,
467 core::num::NonZeroU8, core::num::NonZeroU16, core::num::NonZeroU32,
468 core::num::NonZeroU64, core::num::NonZeroU128, core::num::NonZeroUsize,
469}
470
471impl<T: HtmlSafe> HtmlSafe for core::num::Wrapping<T> {}
472impl<T: fmt::Display> HtmlSafe for HtmlSafeOutput<T> {}
473
474#[cfg(feature = "alloc")]
475impl<T> HtmlSafe for alloc::borrow::Cow<'_, T>
476where
477 T: HtmlSafe + alloc::borrow::ToOwned + ?Sized,
478 T::Owned: HtmlSafe,
479{
480}
481
482crate::impl_for_ref! {
483 impl HtmlSafe for T {}
484}
485
486impl<T: HtmlSafe> HtmlSafe for Pin<T> {}
487
488pub struct Writable<'a, S: ?Sized>(pub &'a S);
490
491pub trait WriteWritable {
493 fn askama_write<W: fmt::Write + ?Sized>(
495 &self,
496 dest: &mut W,
497 values: &dyn Values,
498 ) -> crate::Result<()>;
499}
500
501pub trait FastWritable {
505 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()>;
507}
508
509const _: () = {
510 crate::impl_for_ref! {
511 impl FastWritable for T {
512 #[inline]
513 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
514 <T>::write_into(self, dest)
515 }
516 }
517 }
518
519 impl<T> FastWritable for Pin<T>
520 where
521 T: Deref,
522 <T as Deref>::Target: FastWritable,
523 {
524 #[inline]
525 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
526 self.as_ref().get_ref().write_into(dest)
527 }
528 }
529
530 #[cfg(feature = "alloc")]
531 impl<T: FastWritable + alloc::borrow::ToOwned> FastWritable for alloc::borrow::Cow<'_, T> {
532 #[inline]
533 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
534 T::write_into(self.as_ref(), dest)
535 }
536 }
537
538 macro_rules! impl_for_int {
540 ($($ty:ty)*) => { $(
541 impl FastWritable for $ty {
542 #[inline]
543 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
544 itoa::Buffer::new().format(*self).write_into(dest)
545 }
546 }
547 )* };
548 }
549
550 impl_for_int!(
551 u8 u16 u32 u64 u128 usize
552 i8 i16 i32 i64 i128 isize
553 );
554
555 macro_rules! impl_for_nz_int {
557 ($($id:ident)*) => { $(
558 impl FastWritable for core::num::$id {
559 #[inline]
560 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
561 self.get().write_into(dest)
562 }
563 }
564 )* };
565 }
566
567 impl_for_nz_int!(
568 NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize
569 NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize
570 );
571
572 impl FastWritable for str {
573 #[inline]
574 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
575 Ok(dest.write_str(self)?)
576 }
577 }
578
579 #[cfg(feature = "alloc")]
580 impl FastWritable for alloc::string::String {
581 #[inline]
582 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
583 self.as_str().write_into(dest)
584 }
585 }
586
587 impl FastWritable for bool {
588 #[inline]
589 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
590 Ok(dest.write_str(match self {
591 true => "true",
592 false => "false",
593 })?)
594 }
595 }
596
597 impl FastWritable for char {
598 #[inline]
599 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
600 Ok(dest.write_char(*self)?)
601 }
602 }
603
604 impl FastWritable for fmt::Arguments<'_> {
605 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
606 Ok(match self.as_str() {
607 Some(s) => dest.write_str(s),
608 None => dest.write_fmt(*self),
609 }?)
610 }
611 }
612
613 impl<S: crate::Template + ?Sized> WriteWritable for &Writable<'_, S> {
614 #[inline]
615 fn askama_write<W: fmt::Write + ?Sized>(
616 &self,
617 dest: &mut W,
618 values: &dyn Values,
619 ) -> crate::Result<()> {
620 self.0.render_into_with_values(dest, values)
621 }
622 }
623
624 impl<S: FastWritable + ?Sized> WriteWritable for &&Writable<'_, S> {
625 #[inline]
626 fn askama_write<W: fmt::Write + ?Sized>(
627 &self,
628 dest: &mut W,
629 _: &dyn Values,
630 ) -> crate::Result<()> {
631 self.0.write_into(dest)
632 }
633 }
634
635 impl<S: fmt::Display + ?Sized> WriteWritable for &&&Writable<'_, S> {
636 #[inline]
637 fn askama_write<W: fmt::Write + ?Sized>(
638 &self,
639 dest: &mut W,
640 _: &dyn Values,
641 ) -> crate::Result<()> {
642 Ok(write!(dest, "{}", self.0)?)
643 }
644 }
645};
646
647#[test]
648#[cfg(feature = "alloc")]
649fn test_escape() {
650 use alloc::string::ToString;
651
652 assert_eq!(escape("", Html).unwrap().to_string(), "");
653 assert_eq!(escape("<&>", Html).unwrap().to_string(), "<&>");
654 assert_eq!(escape("bla&", Html).unwrap().to_string(), "bla&");
655 assert_eq!(escape("<foo", Html).unwrap().to_string(), "<foo");
656 assert_eq!(escape("bla&h", Html).unwrap().to_string(), "bla&h");
657
658 assert_eq!(escape("", Text).unwrap().to_string(), "");
659 assert_eq!(escape("<&>", Text).unwrap().to_string(), "<&>");
660 assert_eq!(escape("bla&", Text).unwrap().to_string(), "bla&");
661 assert_eq!(escape("<foo", Text).unwrap().to_string(), "<foo");
662 assert_eq!(escape("bla&h", Text).unwrap().to_string(), "bla&h");
663}
664
665#[test]
666#[cfg(feature = "alloc")]
667fn test_html_safe_marker() {
668 use alloc::string::ToString;
669
670 struct Script1;
671 struct Script2;
672
673 impl fmt::Display for Script1 {
674 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
675 f.write_str("<script>")
676 }
677 }
678
679 impl fmt::Display for Script2 {
680 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
681 f.write_str("<script>")
682 }
683 }
684
685 impl HtmlSafe for Script2 {}
686
687 assert_eq!(
688 (&&AutoEscaper::new(&Script1, Html))
689 .askama_auto_escape()
690 .unwrap()
691 .to_string(),
692 "<script>",
693 );
694 assert_eq!(
695 (&&AutoEscaper::new(&Script2, Html))
696 .askama_auto_escape()
697 .unwrap()
698 .to_string(),
699 "<script>",
700 );
701
702 assert_eq!(
703 (&&AutoEscaper::new(&Script1, Text))
704 .askama_auto_escape()
705 .unwrap()
706 .to_string(),
707 "<script>",
708 );
709 assert_eq!(
710 (&&AutoEscaper::new(&Script2, Text))
711 .askama_auto_escape()
712 .unwrap()
713 .to_string(),
714 "<script>",
715 );
716
717 assert_eq!(
718 (&&AutoEscaper::new(&Safe(Script1), Html))
719 .askama_auto_escape()
720 .unwrap()
721 .to_string(),
722 "<script>",
723 );
724 assert_eq!(
725 (&&AutoEscaper::new(&Safe(Script2), Html))
726 .askama_auto_escape()
727 .unwrap()
728 .to_string(),
729 "<script>",
730 );
731
732 assert_eq!(
733 (&&AutoEscaper::new(&Unsafe(Script1), Html))
734 .askama_auto_escape()
735 .unwrap()
736 .to_string(),
737 "<script>",
738 );
739 assert_eq!(
740 (&&AutoEscaper::new(&Unsafe(Script2), Html))
741 .askama_auto_escape()
742 .unwrap()
743 .to_string(),
744 "<script>",
745 );
746
747 assert_eq!(
748 (&&AutoEscaper::new(&MaybeSafe::Safe(Script1), Html))
749 .askama_auto_escape()
750 .unwrap()
751 .to_string(),
752 "<script>",
753 );
754 assert_eq!(
755 (&&AutoEscaper::new(&MaybeSafe::Safe(Script2), Html))
756 .askama_auto_escape()
757 .unwrap()
758 .to_string(),
759 "<script>",
760 );
761 assert_eq!(
762 (&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script1), Html))
763 .askama_auto_escape()
764 .unwrap()
765 .to_string(),
766 "<script>",
767 );
768 assert_eq!(
769 (&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script2), Html))
770 .askama_auto_escape()
771 .unwrap()
772 .to_string(),
773 "<script>",
774 );
775
776 assert_eq!(
777 (&&AutoEscaper::new(&Safe(std::pin::Pin::new(&Script1)), Html))
778 .askama_auto_escape()
779 .unwrap()
780 .to_string(),
781 "<script>",
782 );
783 assert_eq!(
784 (&&AutoEscaper::new(&Safe(std::pin::Pin::new(&Script2)), Html))
785 .askama_auto_escape()
786 .unwrap()
787 .to_string(),
788 "<script>",
789 );
790}