Skip to main content

plf_contrib/
urlencode.rs

1use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, percent_encode};
2use plf::{Kwargs, State};
3
4/// https://url.spec.whatwg.org/#fragment-percent-encode-set
5const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
6    .add(b' ')
7    .add(b'"')
8    .add(b'<')
9    .add(b'>')
10    .add(b'`');
11
12/// https://url.spec.whatwg.org/#path-percent-encode-set
13const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
14
15/// https://url.spec.whatwg.org/#userinfo-percent-encode-set
16const USERINFO_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET
17    .add(b'/')
18    .add(b':')
19    .add(b';')
20    .add(b'=')
21    .add(b'@')
22    .add(b'[')
23    .add(b'\\')
24    .add(b']')
25    .add(b'^')
26    .add(b'|');
27
28/// Same as Python quote
29/// https://github.com/python/cpython/blob/da27d9b9dc44913ffee8f28d9638985eaaa03755/Lib/urllib/parse.py#L787
30/// with `/` not escaped
31const PYTHON_ENCODE_SET: &AsciiSet = &USERINFO_ENCODE_SET
32    .remove(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    .add(b'$')
42    .add(b'&')
43    .add(b'\'')
44    .add(b'(')
45    .add(b')')
46    .add(b'*')
47    .add(b'+')
48    .add(b',')
49    .add(b';')
50    .add(b'=');
51
52/// Percent-encodes reserved URI characters.
53/// Matches Python's `urllib.parse.quote` behavior with `/` not escaped.
54///
55/// ```text
56/// {{ value | urlencode }}
57/// ```
58pub fn urlencode(val: &str, _: Kwargs, _: &State) -> String {
59    percent_encode(val.as_bytes(), PYTHON_ENCODE_SET).to_string()
60}
61
62/// Percent-encodes all non-alphanumeric characters.
63/// Stricter than `urlencode` - also encodes `/` and other typically safe characters.
64///
65/// ```text
66/// {{ value | urlencode_strict }}
67/// ```
68pub fn urlencode_strict(val: &str, _: Kwargs, _: &State) -> String {
69    percent_encode(val.as_bytes(), NON_ALPHANUMERIC).to_string()
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use plf::{Context, Kwargs, State};
76
77    #[test]
78    fn test_urlencode() {
79        let ctx = Context::new();
80        let state = State::new(&ctx);
81        assert_eq!(
82            urlencode("hello world", Kwargs::default(), &state),
83            "hello%20world"
84        );
85        assert_eq!(
86            urlencode("foo/bar?baz=1", Kwargs::default(), &state),
87            "foo/bar%3Fbaz%3D1"
88        );
89    }
90
91    #[test]
92    fn test_urlencode_strict() {
93        let ctx = Context::new();
94        let state = State::new(&ctx);
95        assert_eq!(
96            urlencode_strict("hello", Kwargs::default(), &state),
97            "hello"
98        );
99        assert_eq!(urlencode_strict("a/b", Kwargs::default(), &state), "a%2Fb");
100    }
101
102    #[test]
103    fn test_register() {
104        let mut tera = plf::Tera::default();
105        tera.register_filter("urlencode", urlencode);
106        tera.register_filter("urlencode_strict", urlencode_strict);
107    }
108}