html_escape/encode/html_entity/mod.rs
1mod unquoted_attribute;
2
3use alloc::{borrow::Cow, string::String, vec::Vec};
4use core::str::from_utf8_unchecked;
5#[cfg(feature = "std")]
6use std::io::{self, Write};
7
8pub use unquoted_attribute::*;
9
10macro_rules! escape_impl {
11 (@inner [$dollar:tt] $name:ident; $($l:expr => $r:expr),+ $(,)*) => {
12 macro_rules! $name {
13 ($dollar e:expr) => {
14 match $dollar e {
15 $($l => break $r,)+
16 _ => (),
17 }
18 };
19 (vec $dollar e:expr, $dollar v:ident, $dollar b:ident, $dollar start:ident, $dollar end:ident) => {
20 match $dollar e {
21 $($l => {
22 $dollar v.extend_from_slice(&$dollar b[$dollar start..$dollar end]);
23 $dollar start = $dollar end + 1;
24 $dollar v.extend_from_slice($r);
25 })+
26 _ => (),
27 }
28
29 $dollar end += 1;
30 };
31 (writer $dollar e:expr, $dollar w:ident, $dollar b:ident, $dollar start:ident, $dollar end:ident) => {
32 match $dollar e {
33 $($l => {
34 $dollar w.write_all(&$dollar b[$dollar start..$dollar end])?;
35 $dollar start = $dollar end + 1;
36 $dollar w.write_all($r)?;
37 })+
38 _ => (),
39 }
40
41 $dollar end += 1;
42 };
43 }
44 };
45 ($name:ident; $($l:expr => $r:expr),+ $(,)*) => {
46 escape_impl! {
47 @inner [$]
48 $name;
49 $($l => $r.as_ref(),)*
50 }
51 };
52}
53
54escape_impl! {
55 escape_text_minimal;
56 b'&' => b"&",
57 b'<' => b"<",
58}
59
60escape_impl! {
61 escape_text;
62 b'&' => b"&",
63 b'<' => b"<",
64 b'>' => b">",
65}
66
67escape_impl! {
68 escape_double_quote;
69 b'&' => b"&",
70 b'<' => b"<",
71 b'>' => b">",
72 b'"' => b""",
73}
74
75escape_impl! {
76 escape_single_quote;
77 b'&' => b"&",
78 b'<' => b"<",
79 b'>' => b">",
80 b'\'' => b"'",
81}
82
83escape_impl! {
84 escape_quote;
85 b'&' => b"&",
86 b'<' => b"<",
87 b'>' => b">",
88 b'"' => b""",
89 b'\'' => b"'",
90}
91
92escape_impl! {
93 escape_safe;
94 b'&' => b"&",
95 b'<' => b"<",
96 b'>' => b">",
97 b'"' => b""",
98 b'\'' => b"'",
99 b'/' => b"/",
100}
101
102macro_rules! encode_impl {
103 ($(#[$attr: meta])* $escape_macro:ident; $(#[$encode_attr: meta])* $encode_name: ident; $(#[$encode_to_string_attr: meta])* $encode_to_string_name: ident; $(#[$encode_to_vec_attr: meta])* $encode_to_vec_name: ident; $(#[$encode_to_writer_attr: meta])* $encode_to_writer_name: ident $(;)*) => {
104 $(#[$encode_attr])*
105 ///
106 $(#[$attr])*
107 #[inline]
108 pub fn $encode_name<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
109 let text = text.as_ref();
110 let text_bytes = text.as_bytes();
111 let text_length = text_bytes.len();
112
113 let mut p = 0;
114 let mut e;
115
116 let first = loop {
117 if p == text_length {
118 return Cow::from(text);
119 }
120
121 e = text_bytes[p];
122
123 $escape_macro!(e);
124
125 p += 1;
126 };
127
128 let mut v = Vec::with_capacity(text_length + 5);
129
130 v.extend_from_slice(&text_bytes[..p]);
131 v.extend_from_slice(first);
132
133 // SAFETY: `text_bytes[p]` is the ASCII byte which has just been escaped, so the rest is valid UTF-8.
134 $encode_to_vec_name(unsafe { from_utf8_unchecked(&text_bytes[(p + 1)..]) }, &mut v);
135
136 // SAFETY: `v` only contains slices of `text`, split at ASCII bytes, plus ASCII escape sequences, so it is valid UTF-8.
137 Cow::from(unsafe { String::from_utf8_unchecked(v) })
138 }
139
140 $(#[$encode_to_string_attr])*
141 ///
142 /// This appends to `output` and returns only the new string slice.
143 ///
144 $(#[$attr])*
145 #[inline]
146 pub fn $encode_to_string_name<S: AsRef<str>>(text: S, output: &mut String) -> &str {
147 // SAFETY: the encoded data is valid UTF-8, so `output` remains a valid `String`.
148 unsafe { from_utf8_unchecked($encode_to_vec_name(text, output.as_mut_vec())) }
149 }
150
151 $(#[$encode_to_vec_attr])*
152 ///
153 /// This appends to `output` and returns only the new data slice.
154 ///
155 $(#[$attr])*
156 #[inline]
157 pub fn $encode_to_vec_name<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
158 let text = text.as_ref();
159 let text_bytes = text.as_bytes();
160 let text_length = text_bytes.len();
161
162 output.reserve(text_length);
163
164 let current_length = output.len();
165
166 let mut start = 0;
167 let mut end = 0;
168
169 for e in text_bytes.iter().copied() {
170 $escape_macro!(vec e, output, text_bytes, start, end);
171 }
172
173 output.extend_from_slice(&text_bytes[start..end]);
174
175 &output[current_length..]
176 }
177
178 #[cfg(feature = "std")]
179 $(#[$encode_to_writer_attr])*
180 ///
181 $(#[$attr])*
182 #[inline]
183 pub fn $encode_to_writer_name<S: AsRef<str>, W: Write + ?Sized>(text: S, output: &mut W) -> Result<(), io::Error> {
184 let text = text.as_ref();
185 let text_bytes = text.as_bytes();
186
187 let mut start = 0;
188 let mut end = 0;
189
190 for e in text_bytes.iter().copied() {
191 $escape_macro!(writer e, output, text_bytes, start, end);
192 }
193
194 output.write_all(&text_bytes[start..end])
195 }
196 };
197}
198
199encode_impl! {
200 /// The following characters are escaped:
201 ///
202 /// * `&` => `&`
203 /// * `<` => `<`
204 escape_text_minimal;
205 /// Encode text used as regular HTML text.
206 encode_text_minimal;
207 /// Write text used as regular HTML text to a mutable `String` reference and return the encoded string slice.
208 encode_text_minimal_to_string;
209 /// Write text used as regular HTML text to a mutable `Vec<u8>` reference and return the encoded data slice.
210 encode_text_minimal_to_vec;
211 /// Write text used as regular HTML text to a writer.
212 encode_text_minimal_to_writer;
213}
214
215encode_impl! {
216 /// The following characters are escaped:
217 ///
218 /// * `&` => `&`
219 /// * `<` => `<`
220 /// * `>` => `>`
221 escape_text;
222 /// Encode text used as regular HTML text.
223 encode_text;
224 /// Write text used as regular HTML text to a mutable `String` reference and return the encoded string slice.
225 encode_text_to_string;
226 /// Write text used as regular HTML text to a mutable `Vec<u8>` reference and return the encoded data slice.
227 encode_text_to_vec;
228 /// Write text used as regular HTML text to a writer.
229 encode_text_to_writer;
230}
231
232encode_impl! {
233 /// The following characters are escaped:
234 ///
235 /// * `&` => `&`
236 /// * `<` => `<`
237 /// * `>` => `>`
238 /// * `"` => `"`
239 escape_double_quote;
240 /// Encode text used in a double-quoted attribute.
241 encode_double_quoted_attribute;
242 /// Write text used in a double-quoted attribute to a mutable `String` reference and return the encoded string slice.
243 encode_double_quoted_attribute_to_string;
244 /// Write text used in a double-quoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice.
245 encode_double_quoted_attribute_to_vec;
246 /// Write text used in a double-quoted attribute to a writer.
247 encode_double_quoted_attribute_to_writer;
248}
249
250encode_impl! {
251 /// The following characters are escaped:
252 ///
253 /// * `&` => `&`
254 /// * `<` => `<`
255 /// * `>` => `>`
256 /// * `'` => `'`
257 escape_single_quote;
258 /// Encode text used in a single-quoted attribute.
259 encode_single_quoted_attribute;
260 /// Write text used in a single-quoted attribute to a mutable `String` reference and return the encoded string slice.
261 encode_single_quoted_attribute_to_string;
262 /// Write text used in a single-quoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice.
263 encode_single_quoted_attribute_to_vec;
264 /// Write text used in a single-quoted attribute to a writer.
265 encode_single_quoted_attribute_to_writer;
266}
267
268encode_impl! {
269 /// The following characters (HTML reserved characters) are escaped:
270 ///
271 /// * `&` => `&`
272 /// * `<` => `<`
273 /// * `>` => `>`
274 /// * `"` => `"`
275 /// * `'` => `'`
276 escape_quote;
277 /// Encode text used in a quoted attribute.
278 encode_quoted_attribute;
279 /// Write text used in a quoted attribute to a mutable `String` reference and return the encoded string slice.
280 encode_quoted_attribute_to_string;
281 /// Write text used in a quoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice.
282 encode_quoted_attribute_to_vec;
283 /// Write text used in a quoted attribute to a writer.
284 encode_quoted_attribute_to_writer;
285}
286
287encode_impl! {
288 /// The following characters are escaped:
289 ///
290 /// * `&` => `&`
291 /// * `<` => `<`
292 /// * `>` => `>`
293 /// * `"` => `"`
294 /// * `'` => `'`
295 /// * `/` => `/`
296 escape_safe;
297 /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities.
298 ///
299 /// This function is not a context-independent sanitizer.
300 /// Use a context-specific encoder for text, attributes, scripts, and styles.
301 encode_safe;
302 /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities, write the result to a mutable `String` reference, and return the encoded string slice.
303 ///
304 /// This function is not a context-independent sanitizer.
305 /// Use a context-specific encoder for text, attributes, scripts, and styles.
306 encode_safe_to_string;
307 /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities, write the result to a mutable `Vec<u8>` reference, and return the encoded data slice.
308 ///
309 /// This function is not a context-independent sanitizer.
310 /// Use a context-specific encoder for text, attributes, scripts, and styles.
311 encode_safe_to_vec;
312 /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities and write the result to a writer.
313 ///
314 /// This function is not a context-independent sanitizer.
315 /// Use a context-specific encoder for text, attributes, scripts, and styles.
316 encode_safe_to_writer;
317}