|
1 |
| -use std::borrow::Cow; |
2 |
| -use std::sync::OnceLock; |
3 |
| -use ammonia; |
4 |
| - |
5 |
| -static DEFAULT_AMMONIA_BUILDER: OnceLock<ammonia::Builder> = OnceLock::new(); |
6 |
| - |
7 |
| -/// Sanitizes incoming HTML using the [Ammonia](https://docs.rs/ammonia/1.0.0/ammonia/) crate. |
8 |
| -/// |
9 |
| -/// ```rust |
10 |
| -/// use walrs_inputfilter::filter::StripTags; |
11 |
| -/// use std::borrow::Cow; |
12 |
| -/// |
13 |
| -/// let filter = StripTags::new(); |
14 |
| -/// |
15 |
| -/// for (i, (incoming_src, expected_src)) in [ |
16 |
| -/// ("", ""), |
17 |
| -/// ("Socrates'", "Socrates'"), |
18 |
| -/// ("\"Hello\"", "\"Hello\""), |
19 |
| -/// ("Hello", "Hello"), |
20 |
| -/// ("<script>alert(\"Hello World\");</script>", ""), // Removes `script` tags, by default |
21 |
| -/// ("<p>The quick brown fox</p><style>p { font-weight: bold; }</style>", |
22 |
| -/// "<p>The quick brown fox</p>"), // Removes `style` tags, by default |
23 |
| -/// ("<p>The quick brown fox", "<p>The quick brown fox</p>") // Fixes erroneous markup, by default |
24 |
| -/// ] |
25 |
| -/// .into_iter().enumerate() { |
26 |
| -/// println!("Filter test {}: filter({}) == {}", i, incoming_src, expected_src); |
27 |
| -/// let result = filter.filter(incoming_src.into()); |
28 |
| -/// |
29 |
| -/// assert_eq!(result, expected_src.to_string()); |
30 |
| -/// assert_eq!(filter(incoming_src.into()), result); |
31 |
| -/// } |
32 |
| -/// ``` |
33 |
| -/// |
34 |
| -pub struct StripTags<'a> { |
35 |
| - /// Ammonia builder used to sanitize incoming HTML. |
36 |
| - /// |
37 |
| - /// If `None`, a default builder is used when `filter`/instance is called. |
38 |
| - pub ammonia: Option<ammonia::Builder<'a>>, |
39 |
| -} |
40 |
| - |
41 |
| -impl<'a> StripTags<'a> { |
42 |
| - /// Constructs a new `StripTags` instance. |
43 |
| - pub fn new() -> Self { |
44 |
| - Self { |
45 |
| - ammonia: None, |
46 |
| - } |
47 |
| - } |
48 |
| - |
49 |
| - /// Filters incoming HTML using the contained `ammonia::Builder` instance. |
50 |
| - /// If no instance is set gets/(and/or) initializes a new (default, and singleton) instance. |
51 |
| - /// |
52 |
| - /// ```rust |
53 |
| - /// use std::borrow::Cow; |
54 |
| - /// use std::sync::OnceLock; |
55 |
| - /// use ammonia::Builder as AmmoniaBuilder; |
56 |
| - /// use walrs_inputfilter::filter::StripTags; |
57 |
| - /// |
58 |
| - /// // Using default settings: |
59 |
| - /// let filter = StripTags::new(); |
60 |
| - /// |
61 |
| - /// let subject = r#"<p>Hello</p><script>alert('hello');</script> |
62 |
| - /// <style>p { font-weight: bold; }</style>"#; |
63 |
| - /// |
64 |
| - /// // Ammonia removes `script`, and `style` tags by default. |
65 |
| - /// assert_eq!(filter.filter(subject.into()).trim(), |
66 |
| - /// "<p>Hello</p>" |
67 |
| - /// ); |
68 |
| - /// |
69 |
| - /// // Using custom settings: |
70 |
| - /// // Instantiate a custom sanitizer instance. |
71 |
| - /// let mut sanitizer = AmmoniaBuilder::default(); |
72 |
| - /// let additional_allowed_tags = vec!["style"]; |
73 |
| - /// |
74 |
| - /// sanitizer |
75 |
| - /// .add_tags(&additional_allowed_tags) // Add 'style' tag to "tags-whitelist" |
76 |
| - /// |
77 |
| - /// // Remove 'style' tag from "tags-blacklist" |
78 |
| - /// .rm_clean_content_tags(&additional_allowed_tags); |
79 |
| - /// |
80 |
| - /// let filter = StripTags { |
81 |
| - /// ammonia: Some(sanitizer) |
82 |
| - /// }; |
83 |
| - /// |
84 |
| - /// // Notice `style` tags are no longer removed. |
85 |
| - /// assert_eq!(filter.filter( |
86 |
| - /// "<script>alert('hello');</script><style>p { font-weight: bold; }</style>".into() |
87 |
| - /// ), |
88 |
| - /// "<style>p { font-weight: bold; }</style>" |
89 |
| - /// ); |
90 |
| - /// ``` |
91 |
| - /// |
92 |
| - pub fn filter<'b>(&self, input: Cow<'b, str>) -> Cow<'b, str> { |
93 |
| - match self.ammonia { |
94 |
| - None => Cow::Owned( |
95 |
| - DEFAULT_AMMONIA_BUILDER.get_or_init(|| ammonia::Builder::default()) |
96 |
| - .clean(&input).to_string() |
97 |
| - ), |
98 |
| - Some(ref sanitizer) => Cow::Owned( |
99 |
| - sanitizer.clean(&input).to_string() |
100 |
| - ), |
101 |
| - } |
102 |
| - } |
103 |
| -} |
104 |
| - |
105 |
| -impl<'a, 'b> FnOnce<(Cow<'b, str>, )> for StripTags<'a> { |
106 |
| - type Output = Cow<'b, str>; |
107 |
| - |
108 |
| - extern "rust-call" fn call_once(self, args: (Cow<'b, str>, )) -> Self::Output { |
109 |
| - self.filter(args.0) |
110 |
| - } |
111 |
| -} |
112 |
| - |
113 |
| -impl<'a, 'b> FnMut<(Cow<'b, str>, )> for StripTags<'a> { |
114 |
| - extern "rust-call" fn call_mut(&mut self, args: (Cow<'b, str>, )) -> Self::Output { |
115 |
| - self.filter(args.0) |
116 |
| - } |
117 |
| -} |
118 |
| - |
119 |
| -impl<'a, 'b> Fn<(Cow<'b, str>, )> for StripTags<'a> { |
120 |
| - extern "rust-call" fn call(&self, args: (Cow<'b, str>, )) -> Self::Output { |
121 |
| - self.filter(args.0) |
122 |
| - } |
123 |
| -} |
124 |
| - |
125 |
| -#[cfg(test)] |
126 |
| -mod test { |
127 |
| - use super::*; |
128 |
| - |
129 |
| - #[test] |
130 |
| - fn test_construction() { |
131 |
| - let _ = StripTags::new(); |
132 |
| - let _ = StripTags { |
133 |
| - ammonia: Some(ammonia::Builder::default()), |
134 |
| - }; |
135 |
| - } |
136 |
| - |
137 |
| - #[test] |
138 |
| - fn test_filter() { |
139 |
| - let filter = StripTags::new(); |
140 |
| - |
141 |
| - for (i, (incoming_src, expected_src)) in [ |
142 |
| - ("", ""), |
143 |
| - ("Socrates'", "Socrates'"), |
144 |
| - ("\"Hello\"", "\"Hello\""), |
145 |
| - ("Hello", "Hello"), |
146 |
| - ("<script>alert(\"Hello World\");</script>", ""), // Removes `script` tags, by default |
147 |
| - ("<p>The quick brown fox</p><style>p { font-weight: bold; }</style>", |
148 |
| - "<p>The quick brown fox</p>"), // Removes `style` tags, by default |
149 |
| - ("<p>The quick brown fox", "<p>The quick brown fox</p>") // Fixes erroneous markup |
150 |
| - ] |
151 |
| - .into_iter().enumerate() { |
152 |
| - println!("Filter test {}: filter({}) == {}", i, incoming_src, expected_src); |
153 |
| - |
154 |
| - let result = filter.filter(incoming_src.into()); |
155 |
| - |
156 |
| - assert_eq!(result, expected_src.to_string()); |
157 |
| - assert_eq!(filter(incoming_src.into()), result); |
158 |
| - } |
159 |
| - } |
160 |
| -} |
| 1 | +use std::borrow::Cow; |
| 2 | +use std::sync::OnceLock; |
| 3 | +use ammonia; |
| 4 | + |
| 5 | +static DEFAULT_AMMONIA_BUILDER: OnceLock<ammonia::Builder> = OnceLock::new(); |
| 6 | + |
| 7 | +/// Sanitizes incoming HTML using the [Ammonia](https://docs.rs/ammonia/1.0.0/ammonia/) crate. |
| 8 | +/// |
| 9 | +/// ```rust |
| 10 | +/// use walrs_inputfilter::filter::StripTags; |
| 11 | +/// use std::borrow::Cow; |
| 12 | +/// |
| 13 | +/// let filter = StripTags::new(); |
| 14 | +/// |
| 15 | +/// for (i, (incoming_src, expected_src)) in [ |
| 16 | +/// ("", ""), |
| 17 | +/// ("Socrates'", "Socrates'"), |
| 18 | +/// ("\"Hello\"", "\"Hello\""), |
| 19 | +/// ("Hello", "Hello"), |
| 20 | +/// ("<script>alert(\"Hello World\");</script>", ""), // Removes `script` tags, by default |
| 21 | +/// ("<p>The quick brown fox</p><style>p { font-weight: bold; }</style>", |
| 22 | +/// "<p>The quick brown fox</p>"), // Removes `style` tags, by default |
| 23 | +/// ("<p>The quick brown fox", "<p>The quick brown fox</p>") // Fixes erroneous markup, by default |
| 24 | +/// ] |
| 25 | +/// .into_iter().enumerate() { |
| 26 | +/// println!("Filter test {}: filter({}) == {}", i, incoming_src, expected_src); |
| 27 | +/// let result = filter.filter(incoming_src.into()); |
| 28 | +/// |
| 29 | +/// assert_eq!(result, expected_src.to_string()); |
| 30 | +/// assert_eq!(filter(incoming_src.into()), result); |
| 31 | +/// } |
| 32 | +/// ``` |
| 33 | +/// |
| 34 | +pub struct StripTags<'a> { |
| 35 | + /// Ammonia builder used to sanitize incoming HTML. |
| 36 | + /// |
| 37 | + /// If `None`, a default builder is used when `filter`/instance is called. |
| 38 | + pub ammonia: Option<ammonia::Builder<'a>>, |
| 39 | +} |
| 40 | + |
| 41 | +impl<'a> StripTags<'a> { |
| 42 | + /// Constructs a new `StripTags` instance. |
| 43 | + pub fn new() -> Self { |
| 44 | + Self { |
| 45 | + ammonia: None, |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + /// Filters incoming HTML using the contained `ammonia::Builder` instance. |
| 50 | + /// If no instance is set gets/(and/or) initializes a new (default, and singleton) instance. |
| 51 | + /// |
| 52 | + /// ```rust |
| 53 | + /// use std::borrow::Cow; |
| 54 | + /// use std::sync::OnceLock; |
| 55 | + /// use ammonia::Builder as AmmoniaBuilder; |
| 56 | + /// use walrs_inputfilter::filter::StripTags; |
| 57 | + /// |
| 58 | + /// // Using default settings: |
| 59 | + /// let filter = StripTags::new(); |
| 60 | + /// |
| 61 | + /// let subject = r#"<p>Hello</p><script>alert('hello');</script> |
| 62 | + /// <style>p { font-weight: bold; }</style>"#; |
| 63 | + /// |
| 64 | + /// // Ammonia removes `script`, and `style` tags by default. |
| 65 | + /// assert_eq!(filter.filter(subject.into()).trim(), |
| 66 | + /// "<p>Hello</p>" |
| 67 | + /// ); |
| 68 | + /// |
| 69 | + /// // Using custom settings: |
| 70 | + /// // Instantiate a custom sanitizer instance. |
| 71 | + /// let mut sanitizer = AmmoniaBuilder::default(); |
| 72 | + /// let additional_allowed_tags = vec!["style"]; |
| 73 | + /// |
| 74 | + /// sanitizer |
| 75 | + /// .add_tags(&additional_allowed_tags) // Add 'style' tag to "tags-whitelist" |
| 76 | + /// |
| 77 | + /// // Remove 'style' tag from "tags-blacklist" |
| 78 | + /// .rm_clean_content_tags(&additional_allowed_tags); |
| 79 | + /// |
| 80 | + /// let filter = StripTags { |
| 81 | + /// ammonia: Some(sanitizer) |
| 82 | + /// }; |
| 83 | + /// |
| 84 | + /// // Notice `style` tags are no longer removed. |
| 85 | + /// assert_eq!(filter.filter( |
| 86 | + /// "<script>alert('hello');</script><style>p { font-weight: bold; }</style>".into() |
| 87 | + /// ), |
| 88 | + /// "<style>p { font-weight: bold; }</style>" |
| 89 | + /// ); |
| 90 | + /// ``` |
| 91 | + /// |
| 92 | + pub fn filter<'b>(&self, input: Cow<'b, str>) -> Cow<'b, str> { |
| 93 | + match self.ammonia { |
| 94 | + None => Cow::Owned( |
| 95 | + DEFAULT_AMMONIA_BUILDER.get_or_init(ammonia::Builder::default) |
| 96 | + .clean(&input).to_string() |
| 97 | + ), |
| 98 | + Some(ref sanitizer) => Cow::Owned( |
| 99 | + sanitizer.clean(&input).to_string() |
| 100 | + ), |
| 101 | + } |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +impl<'a> Default for StripTags<'a> { |
| 106 | + fn default() -> Self { |
| 107 | + Self::new() |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +impl<'a, 'b> FnOnce<(Cow<'b, str>, )> for StripTags<'a> { |
| 112 | + type Output = Cow<'b, str>; |
| 113 | + |
| 114 | + extern "rust-call" fn call_once(self, args: (Cow<'b, str>, )) -> Self::Output { |
| 115 | + self.filter(args.0) |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +impl<'a, 'b> FnMut<(Cow<'b, str>, )> for StripTags<'a> { |
| 120 | + extern "rust-call" fn call_mut(&mut self, args: (Cow<'b, str>, )) -> Self::Output { |
| 121 | + self.filter(args.0) |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +impl<'a, 'b> Fn<(Cow<'b, str>, )> for StripTags<'a> { |
| 126 | + extern "rust-call" fn call(&self, args: (Cow<'b, str>, )) -> Self::Output { |
| 127 | + self.filter(args.0) |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +#[cfg(test)] |
| 132 | +mod test { |
| 133 | + use super::*; |
| 134 | + |
| 135 | + #[test] |
| 136 | + fn test_construction() { |
| 137 | + let _ = StripTags::new(); |
| 138 | + let _ = StripTags { |
| 139 | + ammonia: Some(ammonia::Builder::default()), |
| 140 | + }; |
| 141 | + } |
| 142 | + |
| 143 | + #[test] |
| 144 | + fn test_filter() { |
| 145 | + let filter = StripTags::new(); |
| 146 | + |
| 147 | + for (i, (incoming_src, expected_src)) in [ |
| 148 | + ("", ""), |
| 149 | + ("Socrates'", "Socrates'"), |
| 150 | + ("\"Hello\"", "\"Hello\""), |
| 151 | + ("Hello", "Hello"), |
| 152 | + ("<script>alert(\"Hello World\");</script>", ""), // Removes `script` tags, by default |
| 153 | + ("<p>The quick brown fox</p><style>p { font-weight: bold; }</style>", |
| 154 | + "<p>The quick brown fox</p>"), // Removes `style` tags, by default |
| 155 | + ("<p>The quick brown fox", "<p>The quick brown fox</p>") // Fixes erroneous markup |
| 156 | + ] |
| 157 | + .into_iter().enumerate() { |
| 158 | + println!("Filter test {}: filter({}) == {}", i, incoming_src, expected_src); |
| 159 | + |
| 160 | + let result = filter.filter(incoming_src.into()); |
| 161 | + |
| 162 | + assert_eq!(result, expected_src.to_string()); |
| 163 | + assert_eq!(filter(incoming_src.into()), result); |
| 164 | + } |
| 165 | + } |
| 166 | +} |
0 commit comments