1use core::fmt::{self, Display, Formatter, Write};
2
3pub trait Escape {
4 fn escape(&self, f: &mut Formatter, newline: bool) -> fmt::Result;
7}
8
9pub struct Trusted<T: Display>(pub T);
11
12impl<T: Display> Escape for T {
13 fn escape(&self, f: &mut Formatter, newline: bool) -> fmt::Result {
14 if newline {
15 writeln!(HtmlEscaper(f), "{}", self)
16 } else {
17 write!(HtmlEscaper(f), "{}", self)
18 }
19 }
20}
21
22impl<T: Display> Escape for Trusted<T> {
23 fn escape(&self, f: &mut Formatter, newline: bool) -> fmt::Result {
24 if newline {
25 writeln!(f, "{}", self.0)
26 } else {
27 write!(f, "{}", self.0)
28 }
29 }
30}
31
32pub struct HtmlEscaper<'a, 'b>(pub &'a mut Formatter<'b>);
34
35impl Write for HtmlEscaper<'_, '_> {
36 fn write_str(&mut self, s: &str) -> core::fmt::Result {
37 let mut i = 0;
38 for (j, c) in s.char_indices() {
39 let replacement = match c {
40 '"' => Some("""),
41 '&' => Some("&"),
42 '<' => Some("<"),
43 '>' => Some(">"),
44 '\'' => Some("'"),
45 _ => None,
46 };
47 if let Some(replacement) = replacement {
48 if i < j {
49 self.0.write_str(&s[i..j])?;
50 }
51 self.0.write_str(replacement)?;
52 i = j + c.len_utf8();
53 }
54 }
55
56 if i < s.len() {
57 self.0.write_str(&s[i..])?;
58 }
59
60 Ok(())
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use {
67 super::*,
68 core::fmt::{self, Display},
69 };
70
71 struct Wrapper(&'static str);
72
73 impl Display for Wrapper {
74 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
75 write!(HtmlEscaper(f), "{}", self.0)
76 }
77 }
78
79 #[test]
80 fn unescaped_characters() {
81 assert_eq!(Wrapper("hello").to_string(), "hello");
82 }
83
84 #[test]
85 fn escaped_characters() {
86 assert_eq!(Wrapper("\"").to_string(), """);
87 assert_eq!(Wrapper("&").to_string(), "&");
88 assert_eq!(Wrapper("'").to_string(), "'");
89 assert_eq!(Wrapper("<").to_string(), "<");
90 assert_eq!(Wrapper(">").to_string(), ">");
91 }
92
93 #[test]
94 fn mixed_characters() {
95 assert_eq!(Wrapper("foo&bar&baz").to_string(), "foo&bar&baz");
96 }
97}