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        /// 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    /// * `&` => `&amp;`
203    /// * `<` => `&lt;`
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    /// * `&` => `&amp;`
219    /// * `<` => `&lt;`
220    /// * `>` => `&gt;`
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    /// * `&` => `&amp;`
236    /// * `<` => `&lt;`
237    /// * `>` => `&gt;`
238    /// * `"` => `&quot;`
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    /// * `&` => `&amp;`
254    /// * `<` => `&lt;`
255    /// * `>` => `&gt;`
256    /// * `'` => `&#x27;`
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    /// * `&` => `&amp;`
272    /// * `<` => `&lt;`
273    /// * `>` => `&gt;`
274    /// * `"` => `&quot;`
275    /// * `'` => `&#x27;`
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    /// * `&` => `&amp;`
291    /// * `<` => `&lt;`
292    /// * `>` => `&gt;`
293    /// * `"` => `&quot;`
294    /// * `'` => `&#x27;`
295    /// * `/` => `&#x2F;`
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}