Skip to main content

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"&lt;",
58}
59
60escape_impl! {
61    escape_text;
62    b'&' => b"&amp;",
63    b'<' => b"&lt;",
64    b'>' => b"&gt;",
65}
66
67escape_impl! {
68    escape_double_quote;
69    b'&' => b"&amp;",
70    b'<' => b"&lt;",
71    b'>' => b"&gt;",
72    b'"' => b"&quot;",
73}
74
75escape_impl! {
76    escape_single_quote;
77    b'&' => b"&amp;",
78    b'<' => b"&lt;",
79    b'>' => b"&gt;",
80    b'\'' => b"&#x27;",
81}
82
83escape_impl! {
84    escape_quote;
85    b'&' => b"&amp;",
86    b'<' => b"&lt;",
87    b'>' => b"&gt;",
88    b'"' => b"&quot;",
89    b'\'' => b"&#x27;",
90}
91
92escape_impl! {
93    escape_safe;
94    b'&' => b"&amp;",
95    b'<' => b"&lt;",
96    b'>' => b"&gt;",
97    b'"' => b"&quot;",
98    b'\'' => b"&#x27;",
99    b'/' => b"&#x2F;",
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        $(#[$attr])*
143        #[inline]
144        pub fn $encode_to_string_name<S: AsRef<str>>(text: S, output: &mut String) -> &str {
145            // SAFETY: the encoded data is valid UTF-8, so `output` remains a valid `String`.
146            unsafe { from_utf8_unchecked($encode_to_vec_name(text, output.as_mut_vec())) }
147        }
148
149        $(#[$encode_to_vec_attr])*
150        ///
151        $(#[$attr])*
152        #[inline]
153        pub fn $encode_to_vec_name<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
154            let text = text.as_ref();
155            let text_bytes = text.as_bytes();
156            let text_length = text_bytes.len();
157
158            output.reserve(text_length);
159
160            let current_length = output.len();
161
162            let mut start = 0;
163            let mut end = 0;
164
165            for e in text_bytes.iter().copied() {
166                $escape_macro!(vec e, output, text_bytes, start, end);
167            }
168
169            output.extend_from_slice(&text_bytes[start..end]);
170
171            &output[current_length..]
172        }
173
174        #[cfg(feature = "std")]
175        $(#[$encode_to_writer_attr])*
176        ///
177        $(#[$attr])*
178        #[inline]
179        pub fn $encode_to_writer_name<S: AsRef<str>, W: Write>(text: S, output: &mut W) -> Result<(), io::Error> {
180            let text = text.as_ref();
181            let text_bytes = text.as_bytes();
182
183            let mut start = 0;
184            let mut end = 0;
185
186            for e in text_bytes.iter().copied() {
187                $escape_macro!(writer e, output, text_bytes, start, end);
188            }
189
190            output.write_all(&text_bytes[start..end])
191        }
192    };
193}
194
195encode_impl! {
196    /// The following characters are escaped:
197    ///
198    /// * `&` => `&amp;`
199    /// * `<` => `&lt;`
200    escape_text_minimal;
201    /// Encode text used as regular HTML text.
202    encode_text_minimal;
203    /// Write text used as regular HTML text to a mutable `String` reference and return the encoded string slice.
204    encode_text_minimal_to_string;
205    /// Write text used as regular HTML text to a mutable `Vec<u8>` reference and return the encoded data slice.
206    encode_text_minimal_to_vec;
207    /// Write text used as regular HTML text to a writer.
208    encode_text_minimal_to_writer;
209}
210
211encode_impl! {
212    /// The following characters are escaped:
213    ///
214    /// * `&` => `&amp;`
215    /// * `<` => `&lt;`
216    /// * `>` => `&gt;`
217    escape_text;
218    /// Encode text used as regular HTML text.
219    encode_text;
220    /// Write text used as regular HTML text to a mutable `String` reference and return the encoded string slice.
221    encode_text_to_string;
222    /// Write text used as regular HTML text to a mutable `Vec<u8>` reference and return the encoded data slice.
223    encode_text_to_vec;
224    /// Write text used as regular HTML text to a writer.
225    encode_text_to_writer;
226}
227
228encode_impl! {
229    /// The following characters are escaped:
230    ///
231    /// * `&` => `&amp;`
232    /// * `<` => `&lt;`
233    /// * `>` => `&gt;`
234    /// * `"` => `&quot;`
235    escape_double_quote;
236    /// Encode text used in a double-quoted attribute.
237    encode_double_quoted_attribute;
238    /// Write text used in a double-quoted attribute to a mutable `String` reference and return the encoded string slice.
239    encode_double_quoted_attribute_to_string;
240    /// Write text used in a double-quoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice.
241    encode_double_quoted_attribute_to_vec;
242    /// Write text used in a double-quoted attribute to a writer.
243    encode_double_quoted_attribute_to_writer;
244}
245
246encode_impl! {
247    /// The following characters are escaped:
248    ///
249    /// * `&` => `&amp;`
250    /// * `<` => `&lt;`
251    /// * `>` => `&gt;`
252    /// * `'` => `&#x27;`
253    escape_single_quote;
254    /// Encode text used in a single-quoted attribute.
255    encode_single_quoted_attribute;
256    /// Write text used in a single-quoted attribute to a mutable `String` reference and return the encoded string slice.
257    encode_single_quoted_attribute_to_string;
258    /// Write text used in a single-quoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice.
259    encode_single_quoted_attribute_to_vec;
260    /// Write text used in a single-quoted attribute to a writer.
261    encode_single_quoted_attribute_to_writer;
262}
263
264encode_impl! {
265    /// The following characters (HTML reserved characters)  are escaped:
266    ///
267    /// * `&` => `&amp;`
268    /// * `<` => `&lt;`
269    /// * `>` => `&gt;`
270    /// * `"` => `&quot;`
271    /// * `'` => `&#x27;`
272    escape_quote;
273    /// Encode text used in a quoted attribute.
274    encode_quoted_attribute;
275    /// Write text used in a quoted attribute to a mutable `String` reference and return the encoded string slice.
276    encode_quoted_attribute_to_string;
277    /// Write text used in a quoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice.
278    encode_quoted_attribute_to_vec;
279    /// Write text used in a quoted attribute to a writer.
280    encode_quoted_attribute_to_writer;
281}
282
283encode_impl! {
284    /// The following characters are escaped:
285    ///
286    /// * `&` => `&amp;`
287    /// * `<` => `&lt;`
288    /// * `>` => `&gt;`
289    /// * `"` => `&quot;`
290    /// * `'` => `&#x27;`
291    /// * `/` => `&#x2F;`
292    escape_safe;
293    /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities.
294    ///
295    /// This function is not a context-independent sanitizer.
296    /// Use a context-specific encoder for text, attributes, scripts, and styles.
297    encode_safe;
298    /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities, write the result to a mutable `String` reference, and return the encoded string slice.
299    ///
300    /// This function is not a context-independent sanitizer.
301    /// Use a context-specific encoder for text, attributes, scripts, and styles.
302    encode_safe_to_string;
303    /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities, write the result to a mutable `Vec<u8>` reference, and return the encoded data slice.
304    ///
305    /// This function is not a context-independent sanitizer.
306    /// Use a context-specific encoder for text, attributes, scripts, and styles.
307    encode_safe_to_vec;
308    /// Encode `&`, `<`, `>`, `"`, `'`, and `/` as HTML entities and write the result to a writer.
309    ///
310    /// This function is not a context-independent sanitizer.
311    /// Use a context-specific encoder for text, attributes, scripts, and styles.
312    encode_safe_to_writer;
313}