Skip to main content

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>&#096;</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>&#096;</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>&#096;</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>&#096;</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>&#096;</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}