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(&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#[inline]
124pub fn e<T, E>(text: T, escaper: E) -> Result<Safe<EscapeDisplay<T, E>>, Infallible> {
125 escape(text, escaper)
126}
127
128#[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#[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
166pub trait Escaper: Copy {
171 fn write_escaped_str<W: Write>(&self, dest: W, string: &str) -> fmt::Result;
173
174 #[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
181pub trait AutoEscape {
183 type Escaped: fmt::Display;
185 type Error: Into<crate::Error>;
187
188 fn askama_auto_escape(&self) -> Result<Self::Escaped, Self::Error>;
190}
191
192#[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 #[inline]
202 pub fn new(text: &'a T, escaper: E) -> Self {
203 Self { text, escaper }
204 }
205}
206
207impl<'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
218pub trait HtmlSafe: fmt::Display {}
227
228impl<'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
239pub enum MaybeSafe<T> {
288 Safe(T),
290 NeedsEscaping(T),
292}
293
294const _: () = {
295 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 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
363pub struct Safe<T>(pub T);
404
405const _: () = {
406 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 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
439pub 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
449pub 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
493pub struct Writable<'a, S: ?Sized>(pub &'a S);
495
496pub trait WriteWritable {
498 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(), "<&>");
509 assert_eq!(escape("bla&", Html).unwrap().to_string(), "bla&");
510 assert_eq!(escape("<foo", Html).unwrap().to_string(), "<foo");
511 assert_eq!(escape("bla&h", Html).unwrap().to_string(), "bla&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 "<script>",
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 "<script>",
593 );
594 assert_eq!(
595 (&&AutoEscaper::new(&Unsafe(Script2), Html))
596 .askama_auto_escape()
597 .unwrap()
598 .to_string(),
599 "<script>",
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 "<script>",
622 );
623 assert_eq!(
624 (&&AutoEscaper::new(&MaybeSafe::NeedsEscaping(Script2), Html))
625 .askama_auto_escape()
626 .unwrap()
627 .to_string(),
628 "<script>",
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}