]>
Commit | Line | Data |
---|---|---|
136023e0 XL |
1 | use crate::errors::Error; |
2 | ||
3 | /// Escape HTML following [OWASP](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet) | |
4 | /// | |
5 | /// Escape the following characters with HTML entity encoding to prevent switching | |
6 | /// into any execution context, such as script, style, or event handlers. Using | |
7 | /// hex entities is recommended in the spec. In addition to the 5 characters | |
8 | /// significant in XML (&, <, >, ", '), the forward slash is included as it helps | |
9 | /// to end an HTML entity. | |
10 | /// | |
11 | /// ```text | |
12 | /// & --> & | |
13 | /// < --> < | |
14 | /// > --> > | |
15 | /// " --> " | |
16 | /// ' --> ' ' is not recommended | |
17 | /// / --> / forward slash is included as it helps end an HTML entity | |
18 | /// ``` | |
19 | #[inline] | |
20 | pub fn escape_html(input: &str) -> String { | |
21 | let mut output = String::with_capacity(input.len() * 2); | |
22 | for c in input.chars() { | |
23 | match c { | |
24 | '&' => output.push_str("&"), | |
25 | '<' => output.push_str("<"), | |
26 | '>' => output.push_str(">"), | |
27 | '"' => output.push_str("""), | |
28 | '\'' => output.push_str("'"), | |
29 | '/' => output.push_str("/"), | |
30 | _ => output.push(c), | |
31 | } | |
32 | } | |
33 | ||
34 | // Not using shrink_to_fit() on purpose | |
35 | output | |
36 | } | |
37 | ||
38 | pub(crate) fn render_to_string<C, F, E>(context: C, render: F) -> Result<String, Error> | |
39 | where | |
40 | C: FnOnce() -> String, | |
41 | F: FnOnce(&mut Vec<u8>) -> Result<(), E>, | |
42 | Error: From<E>, | |
43 | { | |
44 | let mut buffer = Vec::new(); | |
45 | render(&mut buffer).map_err(Error::from)?; | |
46 | buffer_to_string(context, buffer) | |
47 | } | |
48 | ||
49 | pub(crate) fn buffer_to_string<F>(context: F, buffer: Vec<u8>) -> Result<String, Error> | |
50 | where | |
51 | F: FnOnce() -> String, | |
52 | { | |
53 | String::from_utf8(buffer).map_err(|error| Error::utf8_conversion_error(error, context())) | |
54 | } | |
55 | ||
56 | #[cfg(test)] | |
57 | mod tests { | |
58 | use super::escape_html; | |
59 | use super::render_to_string; | |
60 | ||
61 | #[test] | |
62 | fn test_escape_html() { | |
63 | let tests = vec![ | |
64 | (r"", ""), | |
65 | (r"a&b", "a&b"), | |
66 | (r"<a", "<a"), | |
67 | (r">a", ">a"), | |
68 | (r#"""#, """), | |
69 | (r#"'"#, "'"), | |
70 | (r#"大阪"#, "大阪"), | |
71 | ]; | |
72 | for (input, expected) in tests { | |
73 | assert_eq!(escape_html(input), expected); | |
74 | } | |
75 | let empty = String::new(); | |
76 | assert_eq!(escape_html(&empty), empty); | |
77 | } | |
78 | ||
79 | #[test] | |
80 | fn test_render_to_string() { | |
81 | use std::io::Write; | |
82 | let string = render_to_string(|| panic!(), |w| write!(w, "test")).unwrap(); | |
83 | assert_eq!(string, "test".to_owned()); | |
84 | } | |
85 | } |