1use anyhow::anyhow;
2
3#[inline]
9pub fn url_encoded(input: &[u8]) -> String {
10 url_encoded_internal(input, true)
11}
12
13#[inline]
14pub(crate) fn method_id_encoded(input: &[u8]) -> String {
16 url_encoded_internal(input, false)
17}
18
19#[inline]
20fn url_encoded_internal(input: &[u8], escape_colon: bool) -> String {
21 let mut ret: Vec<u8> = Vec::new();
22
23 for idx in input {
24 match *idx as char {
25 '0'..='9' | 'A'..='Z' | 'a'..='z' | '.' | '-' | '_' => ret.push(*idx),
26 ':' => {
27 if escape_colon {
28 for i in format!("%{:02X}", idx).bytes() {
29 ret.push(i)
30 }
31 } else {
32 ret.push(*idx)
33 }
34 }
35 _ => {
36 for i in format!("%{:02X}", idx).bytes() {
37 ret.push(i)
38 }
39 }
40 }
41 }
42
43 String::from_utf8(ret).unwrap()
44}
45
46#[inline]
48pub(crate) fn url_decoded(s: &[u8]) -> Vec<u8> {
49 let mut hexval: u8 = 0;
50 let mut hexleft = true;
51 let mut ret = Vec::new();
52 let mut in_pct = false;
53
54 for idx in s {
55 match *idx as char {
56 '%' => in_pct = true,
57 '0'..='9' | 'a'..='f' | 'A'..='F' => {
58 if in_pct {
59 let val: u8 = (*idx as char).to_digit(16).unwrap() as u8;
60
61 hexval |= if hexleft { val << 4 } else { val };
62
63 if hexleft {
64 hexleft = false;
65 } else {
66 ret.push(hexval);
67 in_pct = false;
68 hexleft = true;
69 hexval = 0;
70 }
71 } else {
72 ret.push(*idx)
73 }
74 }
75 _ => ret.push(*idx),
76 }
77 }
78
79 ret
80}
81
82#[inline]
86pub(crate) fn validate_method_name(s: &[u8]) -> Result<(), anyhow::Error> {
87 for idx in s {
88 if !(&0x61..=&0x7a).contains(&idx) && !('0'..='9').contains(&(*idx as char)) {
89 return Err(anyhow!(
90 "Method name has invalid characters (not in 0x61 - 0x7a)"
91 ));
92 }
93 }
94
95 Ok(())
96}
97
98mod tests {
99 #[test]
100 fn test_encode_decode() {
101 let encoded = super::url_encoded("text with spaces".as_bytes());
102 assert_eq!(encoded, String::from("text%20with%20spaces"));
103 assert_eq!(
104 super::url_decoded(encoded.as_bytes()),
105 "text with spaces".as_bytes()
106 );
107 }
108
109 #[test]
110 fn test_battery_encode() {
111 use rand::Fill;
112
113 let mut rng = rand::rng();
114
115 for _ in 1..100000 {
116 let mut array: [u8; 100] = [0; 100];
117 array.fill(&mut rng);
118 let encoded = super::url_encoded(&array);
119 assert_eq!(super::url_decoded(encoded.as_bytes()), array, "{}", encoded);
120 }
121 }
122
123 #[test]
124 fn test_validate_method_name() {
125 assert!(super::validate_method_name("erik".as_bytes()).is_ok());
126 assert!(super::validate_method_name("not valid".as_bytes()).is_err());
127 }
128}