1use std::collections::HashMap;
14
15
16pub fn parse_url_search_params(params: &str) -> HashMap<String, String> {
44 let mut params_map : HashMap<String, String> = HashMap::new();
45
46 if params.trim().is_empty() {
47 return params_map
48 }
49
50 let split_iter = params.split("&").into_iter();
51 for param in split_iter {
52 let mut key = "";
53 let mut value = "";
54
55 let mut key_value = param.split("=").into_iter();
56 let boxed_key = key_value.next();
57 if boxed_key.is_some() {
58 key = boxed_key.unwrap();
59 }
60
61 let boxed_value = key_value.next();
62 if boxed_value.is_some() {
63 value = boxed_value.unwrap();
64 }
65
66 if !key.is_empty() {
67 params_map.insert(decode_uri_component(key), decode_uri_component(value));
68 }
69
70 }
71 params_map
72}
73
74
75pub fn build_url_search_params(params: HashMap<String, String>) -> String {
108
109 let mut key_value_list : Vec<String> = vec![];
110 for (key, value) in params {
111 let param = [encode_uri_component(key.as_str()), "=".to_string(), encode_uri_component(value.as_str())].join("");
112 key_value_list.push(param);
113 }
114
115 key_value_list.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
116 let url_search_params : String = key_value_list.join("&");
117
118 url_search_params
119}
120
121pub fn encode_uri_component(component: &str) -> String {
122 let mut _result = component.replace(SYMBOL.percent, "%25");
123 _result = _result.replace(SYMBOL.whitespace, "%20");
124 _result = _result.replace(SYMBOL.carriage_return, "%0D");
125 _result = _result.replace(SYMBOL.new_line, "%0A");
126 _result = _result.replace(SYMBOL.exclamation_mark, "%21");
127 _result = _result.replace(SYMBOL.quotation_mark, "%22");
128 _result = _result.replace(SYMBOL.number_sign, "%23");
129 _result = _result.replace(SYMBOL.dollar, "%24");
130 _result = _result.replace(SYMBOL.ampersand, "%26");
131 _result = _result.replace(SYMBOL.single_quote, "%27");
132 _result = _result.replace(SYMBOL.opening_bracket, "%28");
133 _result = _result.replace(SYMBOL.closing_bracket, "%29");
134 _result = _result.replace(SYMBOL.asterisk, "%2A");
135 _result = _result.replace(SYMBOL.plus, "%2B");
136 _result = _result.replace(SYMBOL.comma, "%2C");
137 _result = _result.replace(SYMBOL.slash, "%2F");
138 _result = _result.replace(SYMBOL.colon, "%3A");
139 _result = _result.replace(SYMBOL.semicolon, "%3B");
140 _result = _result.replace(SYMBOL.equals, "%3D");
141 _result = _result.replace(SYMBOL.at, "%40");
142 _result = _result.replace(SYMBOL.opening_square_bracket, "%5B");
143 _result = _result.replace(SYMBOL.closing_square_bracket, "%5D");
144
145
146 return _result
147}
148
149pub fn decode_uri_component(component: &str) -> String {
150 let mut _result = component.replace( "%20", SYMBOL.whitespace);
151 _result = _result.replace("%0A", SYMBOL.new_line);
152 _result = _result.replace ("%0D", SYMBOL.carriage_return);
153 _result = _result.replace ("%21", SYMBOL.exclamation_mark);
154 _result = _result.replace ("%22", SYMBOL.quotation_mark);
155 _result = _result.replace ("%23", SYMBOL.number_sign);
156 _result = _result.replace ("%24", SYMBOL.dollar);
157 _result = _result.replace ("%25", SYMBOL.percent);
158 _result = _result.replace ("%26", SYMBOL.ampersand);
159 _result = _result.replace ("%27", SYMBOL.single_quote);
160 _result = _result.replace ("%28", SYMBOL.opening_bracket);
161 _result = _result.replace ("%29", SYMBOL.closing_bracket);
162 _result = _result.replace ("%2A", SYMBOL.asterisk);
163 _result = _result.replace ("%2B", SYMBOL.plus);
164 _result = _result.replace ("%2C", SYMBOL.comma);
165 _result = _result.replace ("%2F", SYMBOL.slash);
166 _result = _result.replace ("%3A", SYMBOL.colon);
167 _result = _result.replace ("%3B", SYMBOL.semicolon);
168 _result = _result.replace ("%3D", SYMBOL.equals);
169 _result = _result.replace ("%3F", SYMBOL.question_mark);
170 _result = _result.replace ("%40", SYMBOL.at);
171 _result = _result.replace ("%5B", SYMBOL.opening_square_bracket);
172 _result = _result.replace ("%5D", SYMBOL.closing_square_bracket);
173
174 return _result
175}
176
177pub struct Symbol {
178 pub new_line_carriage_return: &'static str,
179 pub new_line: &'static str,
180 pub carriage_return: &'static str,
181 pub empty_string: &'static str,
182 pub whitespace: &'static str,
183 pub equals: &'static str,
184 pub comma: &'static str,
185 pub hyphen: &'static str,
186 pub slash: &'static str,
187 pub semicolon: &'static str,
188 pub colon: &'static str,
189 pub number_sign: &'static str,
190 pub opening_square_bracket: &'static str,
191 pub closing_square_bracket: &'static str,
192 pub opening_curly_bracket: &'static str,
193 pub closing_curly_bracket: &'static str,
194 pub quotation_mark: &'static str,
195 pub underscore: &'static str,
196 pub single_quote: &'static str,
197 pub percent: &'static str,
198 pub exclamation_mark: &'static str,
199 pub dollar: &'static str,
200 pub ampersand: &'static str,
201 pub opening_bracket: &'static str,
202 pub closing_bracket: &'static str,
203 pub asterisk: &'static str,
204 pub plus: &'static str,
205 pub question_mark: &'static str,
206 pub at: &'static str,
207}
208
209pub const SYMBOL: Symbol = Symbol {
210 new_line: "\n",
211 carriage_return: "\r",
212 new_line_carriage_return: "\r\n",
213 empty_string: "",
214 whitespace: " ",
215 equals: "=",
216 comma: ",",
217 hyphen: "-",
218 slash: "/",
219 semicolon: ";",
220 colon: ":",
221 number_sign: "#",
222 opening_square_bracket: "[",
223 closing_square_bracket: "]",
224 opening_curly_bracket: "{",
225 closing_curly_bracket: "}",
226 quotation_mark: "\"",
227 underscore: "_",
228 single_quote: "'",
229 percent: "%",
230 exclamation_mark: "!",
231 dollar: "$",
232 ampersand: "&",
233 opening_bracket: "(",
234 closing_bracket: ")",
235 asterisk: "*",
236 plus: "+",
237 question_mark: "?",
238 at: "@",
239};
240
241#[cfg(test)]
242mod tests {
243 use std::collections::HashMap;
244 use crate::{build_url_search_params, decode_uri_component, encode_uri_component, parse_url_search_params};
245
246 #[test]
247 fn build_url_search_params_test() {
248 let mut params_map: HashMap<String, String> = HashMap::new();
249 params_map.insert("key1".to_string(), "test1".to_string());
250 params_map.insert("key2".to_string(), "test2".to_string());
251 params_map.insert("".to_string(), "empty_key".to_string());
252 params_map.insert("empty_value".to_string(), "".to_string());
253
254 let search_params = build_url_search_params(params_map);
255 let parsed_search_params = parse_url_search_params(&search_params);
256
257 let boxed_get = parsed_search_params.get("key1");
258 assert!(boxed_get.is_some());
259
260 let actual_param_value = boxed_get.unwrap();
261 assert_eq!(actual_param_value, "test1");
262
263 let boxed_get = parsed_search_params.get("key2");
264 assert!(boxed_get.is_some());
265
266 let actual_param_value = boxed_get.unwrap();
267 assert_eq!(actual_param_value, "test2");
268
269 let boxed_get = parsed_search_params.get("");
270 assert!(boxed_get.is_none());
271
272 let boxed_get = parsed_search_params.get("empty_value");
273 assert!(boxed_get.is_some());
274
275 let actual_param_value = boxed_get.unwrap();
276 assert_eq!(actual_param_value, "");
277 }
278
279 #[test]
280 fn build_url_search_params_ampersand() {
281 let mut params_map: HashMap<String, String> = HashMap::new();
282 params_map.insert("key1".to_string(), "test1".to_string());
283 params_map.insert("key2".to_string(), "test2".to_string());
284 params_map.insert("".to_string(), "empty_key".to_string());
285 params_map.insert("empty_value".to_string(), "".to_string());
286 params_map.insert("&".to_string(), "unescaped_ampersand_as_key".to_string());
287
288 let search_params = build_url_search_params(params_map);
289 let parsed_search_params = parse_url_search_params(&search_params);
290
291 let boxed_get = parsed_search_params.get("key1");
292 assert!(boxed_get.is_some());
293
294 let actual_param_value = boxed_get.unwrap();
295 assert_eq!(actual_param_value, "test1");
296
297 let boxed_get = parsed_search_params.get("key2");
298 assert!(boxed_get.is_some());
299
300 let actual_param_value = boxed_get.unwrap();
301 assert_eq!(actual_param_value, "test2");
302
303 let boxed_get = parsed_search_params.get("");
304 assert!(boxed_get.is_none());
305
306
307 let boxed_get = parsed_search_params.get("empty_value");
308 assert!(boxed_get.is_some());
309
310 let actual_param_value = boxed_get.unwrap();
311 assert_eq!(actual_param_value, "");
312
313 let boxed_get = parsed_search_params.get("empty_value");
314 assert!(boxed_get.is_some());
315
316 }
317
318 #[test]
319 fn parse_empty_url_search_params() {
320 let search_params = "";
321 let params = parse_url_search_params(search_params);
322 assert_eq!(0, params.len());
323 }
324
325 #[test]
326 fn parse_empty_equals_ampersand_search_params() {
327 let search_params = "=&key2=value2";
328 let params = parse_url_search_params(search_params);
329 assert_eq!(1, params.len());
330
331 let boxed_get = params.get("key2");
332 assert!(boxed_get.is_some());
333
334 let actual_param_value = boxed_get.unwrap();
335 assert_eq!(actual_param_value, "value2");
336 }
337
338 #[test]
339 fn encode_decode() {
340 let component = "\r\n \"%!#$&'()*+,/:;=?@[]][@?=;:/,+*)('&$#!%\" \r\n";
341 let mut _result = encode_uri_component(component);
342 assert_eq!("%0D%0A%20%22%25%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D?%40%5B%5D%5D%5B%40?%3D%3B%3A%2F%2C%2B%2A%29%28%27%26%24%23%21%25%22%20%0D%0A", _result);
343 _result = decode_uri_component(_result.as_str());
344 assert_eq!(component, _result);
345 }
346}