url_escape/encode/mod.rs
1// Ref: https://url.spec.whatwg.org/
2
3use std::{
4 borrow::Cow,
5 io::{self, Write},
6 str::from_utf8_unchecked,
7};
8
9/// The C0 control percent-encode set are the C0 controls and U+007F (DEL).
10pub use percent_encoding::CONTROLS;
11/// Not an ASCII letter or digit.
12pub use percent_encoding::NON_ALPHANUMERIC;
13
14use crate::percent_encoding::{utf8_percent_encode, AsciiSet};
15
16/// The fragment percent-encode set is the C0 control percent-encode set and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`).
17pub const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
18
19/// The query percent-encode set is the C0 control percent-encode set and U+0020 SPACE, U+0022 ("), U+0023 (#), U+003C (<), and U+003E (>).
20///
21/// The query percent-encode set cannot be defined in terms of the fragment percent-encode set due to the omission of U+0060 (`).
22pub const QUERY: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>');
23
24/// The special-query percent-encode set is the query percent-encode set and U+0027 (').
25pub const SPECIAL_QUERY: &AsciiSet = &QUERY.add(b'\'');
26
27/// The path percent-encode set is the query percent-encode set and U+003F (?), U+005E (^), U+0060 (`), U+007B ({), and U+007D (}).
28pub const PATH: &AsciiSet = &QUERY.add(b'?').add(b'^').add(b'`').add(b'{').add(b'}');
29
30/// The userinfo percent-encode set is the path percent-encode set and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@), U+005B ([) to U+005D (]), inclusive, and U+007C (|).
31pub const USERINFO: &AsciiSet = &PATH
32 .add(b'/')
33 .add(b':')
34 .add(b';')
35 .add(b'=')
36 .add(b'@')
37 .add(b'[')
38 .add(b'\\')
39 .add(b']')
40 .add(b'|');
41
42/// The component percent-encode set is the userinfo percent-encode set and U+0024 ($) to U+0026 (&), inclusive, U+002B (+), and U+002C (,).
43pub const COMPONENT: &AsciiSet = &USERINFO.add(b'$').add(b'%').add(b'&').add(b'+').add(b',');
44
45/// The application/x-www-form-urlencoded percent-encode set is the component percent-encode set and U+0021 (!), U+0027 (') to U+0029 RIGHT PARENTHESIS, inclusive, and U+007E (~).
46pub const X_WWW_FORM_URLENCODED: &AsciiSet =
47 &COMPONENT.add(b'!').add(b'\'').add(b'(').add(b')').add(b'~');
48
49/// Encode text.
50#[inline]
51pub fn encode<'a, S: ?Sized + AsRef<str>>(
52 text: &'a S,
53 ascii_set: &'static AsciiSet,
54) -> Cow<'a, str> {
55 Cow::from(utf8_percent_encode(text.as_ref(), ascii_set))
56}
57
58/// Write text to a mutable `String` reference and return the encoded string slice.
59#[inline]
60pub fn encode_to_string<'a, S: AsRef<str>>(
61 text: S,
62 ascii_set: &'static AsciiSet,
63 output: &'a mut String,
64) -> &'a str {
65 unsafe { from_utf8_unchecked(encode_to_vec(text, ascii_set, output.as_mut_vec())) }
66}
67
68/// Write text to a mutable `Vec<u8>` reference and return the encoded data slice.
69pub fn encode_to_vec<'a, S: AsRef<str>>(
70 text: S,
71 ascii_set: &'static AsciiSet,
72 output: &'a mut Vec<u8>,
73) -> &'a [u8] {
74 let text = text.as_ref();
75 let text_bytes = text.as_bytes();
76 let text_length = text_bytes.len();
77
78 output.reserve(text_length);
79
80 let current_length = output.len();
81
82 let pe = utf8_percent_encode(text, ascii_set);
83
84 for s in pe {
85 output.extend_from_slice(s.as_bytes());
86 }
87
88 &output[current_length..]
89}
90
91/// Write text to a writer.
92#[inline]
93pub fn encode_to_writer<S: AsRef<str>, W: Write>(
94 text: S,
95 ascii_set: &'static AsciiSet,
96 output: &mut W,
97) -> Result<(), io::Error> {
98 let pe = utf8_percent_encode(text.as_ref(), ascii_set);
99
100 for s in pe {
101 output.write_all(s.as_bytes())?;
102 }
103
104 Ok(())
105}
106
107macro_rules! encode_impl {
108 ($(#[$attr: meta])* $escape_set: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 $(;)*) => {
109 $(#[$encode_attr])*
110 ///
111 $(#[$attr])*
112 #[inline]
113 pub fn $encode_name<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
114 encode(text, $escape_set)
115 }
116
117 $(#[$encode_to_string_attr])*
118 ///
119 $(#[$attr])*
120 #[inline]
121 pub fn $encode_to_string_name<S: AsRef<str>>(text: S, output: &mut String) -> &str {
122 encode_to_string(text, $escape_set, output)
123 }
124
125 $(#[$encode_to_vec_attr])*
126 ///
127 $(#[$attr])*
128 #[inline]
129 pub fn $encode_to_vec_name<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
130 encode_to_vec(text, $escape_set, output)
131 }
132
133 $(#[$encode_to_writer_attr])*
134 ///
135 $(#[$attr])*
136 #[inline]
137 pub fn $encode_to_writer_name<S: AsRef<str>, W: Write>(text: S, output: &mut W) -> Result<(), io::Error> {
138 encode_to_writer(text, $escape_set, output)
139 }
140 };
141}
142
143encode_impl! {
144 /// The following characters are escaped:
145 ///
146 /// C0 controls and,
147 ///
148 /// * SPACE
149 /// * `"`
150 /// * `<`
151 /// * `>`
152 /// * <code>`</code>
153 ///
154 /// and all code points greater than `~` (U+007E) are escaped.
155 FRAGMENT;
156 /// Encode text used in a fragment part.
157 encode_fragment;
158 /// Write text used in a fragment part to a mutable `String` reference and return the encoded string slice.
159 encode_fragment_to_string;
160 /// Write text used in a fragment part to a mutable `Vec<u8>` reference and return the encoded data slice.
161 encode_fragment_to_vec;
162 /// Write text used in a fragment part to a writer.
163 encode_fragment_to_writer;
164}
165
166encode_impl! {
167 /// The following characters are escaped:
168 ///
169 /// C0 controls and,
170 ///
171 /// * SPACE
172 /// * `"`
173 /// * `#`
174 /// * `<`
175 /// * `>`
176 ///
177 /// and all code points greater than `~` (U+007E) are escaped.
178 QUERY;
179 /// Encode text used in the query part.
180 encode_query;
181 /// Write text used in the query part to a mutable `String` reference and return the encoded string slice.
182 encode_query_to_string;
183 /// Write text used in the query part to a mutable `Vec<u8>` reference and return the encoded data slice.
184 encode_query_to_vec;
185 /// Write text used in the query part to a writer.
186 encode_query_to_writer;
187}
188
189encode_impl! {
190 /// The following characters are escaped:
191 ///
192 /// C0 controls and,
193 ///
194 /// * SPACE
195 /// * `"`
196 /// * `#`
197 /// * `'`
198 /// * `<`
199 /// * `>`
200 ///
201 /// and all code points greater than `~` (U+007E) are escaped.
202 ///
203 /// The term "special" means whether a URL is special. A URL is special is the scheme of that URL is **ftp**, **file** , **http**, **https**, **ws**, or **wss**.
204 SPECIAL_QUERY;
205 /// Encode text used in the query part.
206 encode_special_query;
207 /// Write text used in the query part to a mutable `String` reference and return the encoded string slice.
208 encode_special_query_to_string;
209 /// Write text used in the query part to a mutable `Vec<u8>` reference and return the encoded data slice.
210 encode_special_query_to_vec;
211 /// Write text used in the query part to a writer.
212 encode_special_query_to_writer;
213}
214
215encode_impl! {
216 /// The following characters are escaped:
217 ///
218 /// C0 controls and,
219 ///
220 /// * SPACE
221 /// * `"`
222 /// * `#`
223 /// * `<`
224 /// * `>`
225 /// * `?`
226 /// * `^`
227 /// * <code>`</code>
228 /// * `{`
229 /// * `}`
230 ///
231 /// and all code points greater than `~` (U+007E) are escaped.
232 PATH;
233 /// Encode text used in the path part.
234 encode_path;
235 /// Write text used in the path part to a mutable `String` reference and return the encoded string slice.
236 encode_path_to_string;
237 /// Write text used in the path part to a mutable `Vec<u8>` reference and return the encoded data slice.
238 encode_path_to_vec;
239 /// Write text used in the path part to a writer.
240 encode_path_to_writer;
241}
242
243encode_impl! {
244 /// The following characters are escaped:
245 ///
246 /// C0 controls and,
247 ///
248 /// * SPACE
249 /// * `"`
250 /// * `#`
251 /// * `/`
252 /// * `:`
253 /// * `;`
254 /// * `<`
255 /// * `=`
256 /// * `>`
257 /// * `?`
258 /// * `@`
259 /// * `[`
260 /// * `\`
261 /// * `]`
262 /// * `^`
263 /// * <code>`</code>
264 /// * `{`
265 /// * `}`
266 /// * `|`
267 ///
268 /// and all code points greater than `~` (U+007E) are escaped.
269 USERINFO;
270 /// Encode text used in the userinfo part.
271 encode_userinfo;
272 /// Write text used in the userinfo part to a mutable `String` reference and return the encoded string slice.
273 encode_userinfo_to_string;
274 /// Write text used in the userinfo part to a mutable `Vec<u8>` reference and return the encoded data slice.
275 encode_userinfo_to_vec;
276 /// Write text used in the userinfo part to a writer.
277 encode_userinfo_to_writer;
278}
279
280encode_impl! {
281 /// The following characters are escaped:
282 ///
283 /// C0 controls and,
284 ///
285 /// * SPACE
286 /// * `"`
287 /// * `#`
288 /// * `$`
289 /// * `%`
290 /// * `&`
291 /// * `+`
292 /// * `,`
293 /// * `/`
294 /// * `:`
295 /// * `;`
296 /// * `<`
297 /// * `=`
298 /// * `>`
299 /// * `?`
300 /// * `@`
301 /// * `[`
302 /// * `\`
303 /// * `]`
304 /// * `^`
305 /// * <code>`</code>
306 /// * `{`
307 /// * `}`
308 /// * `|`
309 ///
310 /// and all code points greater than `~` (U+007E) are escaped.
311 ///
312 /// It gives identical results to JavaScript's `encodeURIComponent()`.
313 COMPONENT;
314 /// Encode text used in a component.
315 encode_component;
316 /// Write text used in a component to a mutable `String` reference and return the encoded string slice.
317 encode_component_to_string;
318 /// Write text used in a component to a mutable `Vec<u8>` reference and return the encoded data slice.
319 encode_component_to_vec;
320 /// Write text used in a component to a writer.
321 encode_component_to_writer;
322}
323
324/// Encode text as a www-form-urlencoded text.
325///
326/// Decode the output with `decode_www_form_urlencoded` because `+` represents a space in this format.
327///
328/// The following characters are escaped:
329///
330/// C0 controls and,
331///
332/// * SPACE
333/// * `!`
334/// * `"`
335/// * `#`
336/// * `$`
337/// * `%`
338/// * `&`
339/// * `'`
340/// * `(`
341/// * `)`
342/// * `+`
343/// * `,`
344/// * `/`
345/// * `:`
346/// * `;`
347/// * `<`
348/// * `=`
349/// * `>`
350/// * `?`
351/// * `@`
352/// * `[`
353/// * `\`
354/// * `]`
355/// * `^`
356/// * <code>`</code>
357/// * `{`
358/// * `}`
359/// * `|`
360/// * `~`
361///
362/// and all code points greater than `~` (U+007E) are escaped.
363#[inline]
364pub fn encode_www_form_urlencoded<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
365 let text = text.as_ref();
366
367 if text.as_bytes().contains(&b' ') {
368 let mut output = String::new();
369
370 encode_www_form_urlencoded_to_string(text, &mut output);
371
372 Cow::Owned(output)
373 } else {
374 encode(text, X_WWW_FORM_URLENCODED)
375 }
376}
377
378/// Write text as a urlencoded text to a mutable `String` reference and return the encoded string slice.
379///
380/// Decode the output with `decode_www_form_urlencoded_to_string` because `+` represents a space in this format.
381#[inline]
382pub fn encode_www_form_urlencoded_to_string<S: AsRef<str>>(text: S, output: &mut String) -> &str {
383 unsafe { from_utf8_unchecked(encode_www_form_urlencoded_to_vec(text, output.as_mut_vec())) }
384}
385
386/// Write text as a www-form-urlencoded text to a mutable `Vec<u8>` reference and return the encoded data slice.
387///
388/// Decode the output with `decode_www_form_urlencoded_to_vec` because `+` represents a space in this format.
389pub fn encode_www_form_urlencoded_to_vec<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
390 let text = text.as_ref();
391
392 output.reserve(text.len());
393
394 let current_length = output.len();
395
396 for (index, part) in text.split(' ').enumerate() {
397 if index > 0 {
398 output.push(b'+');
399 }
400
401 let pe = utf8_percent_encode(part, X_WWW_FORM_URLENCODED);
402
403 for s in pe {
404 output.extend_from_slice(s.as_bytes());
405 }
406 }
407
408 &output[current_length..]
409}
410
411/// Write text as a www-form-urlencoded text to a writer.
412///
413/// Decode the output with `decode_www_form_urlencoded_to_writer` because `+` represents a space in this format.
414#[inline]
415pub fn encode_www_form_urlencoded_to_writer<S: AsRef<str>, W: Write>(
416 text: S,
417 output: &mut W,
418) -> Result<(), io::Error> {
419 for (index, part) in text.as_ref().split(' ').enumerate() {
420 if index > 0 {
421 output.write_all(b"+")?;
422 }
423
424 let pe = utf8_percent_encode(part, X_WWW_FORM_URLENCODED);
425
426 for s in pe {
427 output.write_all(s.as_bytes())?;
428 }
429 }
430
431 Ok(())
432}