1use lazy_static::lazy_static;
9use regex::Regex;
10use rustc_hash::FxHashMap;
11use serde_json::{Value, from_str};
12use std::borrow::Cow;
13use std::convert::Infallible;
14
15lazy_static! {
16 static ref PARENTHESES_RE: Regex = Regex::new(r"(^\[.*\]$|^\{.*\}$)").unwrap();
17}
18
19#[inline]
26fn url_decode_optimized(input: &[u8]) -> Cow<'_, str> {
27 let has_encoded = input.iter().any(|&b| b == b'+' || b == b'%');
28
29 if !has_encoded {
30 return match std::str::from_utf8(input) {
31 Ok(s) => Cow::Borrowed(s),
32 Err(_) => Cow::Owned(String::from_utf8_lossy(input).into_owned()),
33 };
34 }
35
36 let mut result = Vec::with_capacity(input.len());
37 let mut i = 0;
38
39 while i < input.len() {
40 match input[i] {
41 b'+' => {
42 result.push(b' ');
43 i += 1;
44 }
45 b'%' if i + 2 < input.len() => {
46 if let (Some(hi), Some(lo)) = (
47 char::from(input[i + 1]).to_digit(16),
48 char::from(input[i + 2]).to_digit(16),
49 ) {
50 result.push((hi * 16 + lo) as u8);
51 i += 3;
52 } else {
53 result.push(input[i]);
54 i += 1;
55 }
56 }
57 b => {
58 result.push(b);
59 i += 1;
60 }
61 }
62 }
63
64 Cow::Owned(String::from_utf8_lossy(&result).into_owned())
65}
66
67#[inline]
87pub fn parse_query_string(qs: &[u8], separator: char) -> Vec<(String, String)> {
88 if qs.is_empty() {
89 return Vec::new();
90 }
91
92 let separator_byte = separator as u8;
93 let mut result = Vec::with_capacity(8);
94
95 let mut start = 0;
96 let mut i = 0;
97
98 while i <= qs.len() {
99 if i == qs.len() || qs[i] == separator_byte {
100 if i > start {
101 let pair = &qs[start..i];
102
103 if let Some(eq_pos) = pair.iter().position(|&b| b == b'=') {
104 let key = url_decode_optimized(&pair[..eq_pos]);
105 let value = url_decode_optimized(&pair[eq_pos + 1..]);
106 result.push((key.into_owned(), value.into_owned()));
107 } else {
108 let key = url_decode_optimized(pair);
109 result.push((key.into_owned(), String::new()));
110 }
111 }
112
113 start = i + 1;
114 }
115
116 i += 1;
117 }
118
119 result
120}
121
122#[inline]
131fn decode_value(json_str: String, parse_numbers: bool) -> Value {
132 if PARENTHESES_RE.is_match(json_str.as_str()) {
133 let result: Value = match from_str(json_str.as_str()) {
134 Ok(value) => value,
135 Err(_) => match from_str(json_str.replace('\'', "\"").as_str()) {
136 Ok(normalized) => normalized,
137 Err(_) => Value::Null,
138 },
139 };
140 return result;
141 }
142
143 let normalized = json_str.replace('"', "");
144
145 let json_boolean = parse_boolean(&normalized);
146 let json_null = Ok::<_, Infallible>(normalized == "null");
147
148 if parse_numbers {
149 let json_integer = normalized.parse::<i64>();
150 let json_float = normalized.parse::<f64>();
151 return match (json_integer, json_float, json_boolean, json_null) {
152 (Ok(json_integer), _, _, _) => Value::from(json_integer),
153 (_, Ok(json_float), _, _) => Value::from(json_float),
154 (_, _, Ok(json_boolean), _) => Value::from(json_boolean),
155 (_, _, _, Ok(true)) => Value::Null,
156 _ => Value::from(normalized),
157 };
158 }
159
160 match (json_boolean, json_null) {
161 (Ok(json_boolean), _) => Value::from(json_boolean),
162 (_, Ok(true)) => Value::Null,
163 _ => Value::from(normalized),
164 }
165}
166
167#[inline]
176fn parse_boolean(s: &str) -> Result<bool, ()> {
177 let lower = s.to_lowercase();
178 if lower == "true" || s == "1" {
179 Ok(true)
180 } else if lower == "false" || s == "0" {
181 Ok(false)
182 } else {
183 Err(())
184 }
185}
186
187#[inline]
204pub fn parse_query_string_to_json(qs: &[u8], parse_numbers: bool) -> Value {
205 let mut array_map: FxHashMap<String, Vec<Value>> = FxHashMap::default();
206
207 for (key, value) in parse_query_string(qs, '&') {
208 match array_map.get_mut(&key) {
209 Some(entry) => {
210 entry.push(decode_value(value, parse_numbers));
211 }
212 None => {
213 array_map.insert(key, vec![decode_value(value, parse_numbers)]);
214 }
215 }
216 }
217
218 array_map
219 .iter()
220 .map(|(key, value)| {
221 if value.len() == 1 {
222 (key, value[0].to_owned())
223 } else {
224 (key, Value::Array(value.to_owned()))
225 }
226 })
227 .collect::<Value>()
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use serde_json::{json, to_string};
234
235 fn eq_str(value: Value, string: &str) {
236 assert_eq!(&to_string(&value).unwrap_or_default(), string)
237 }
238
239 #[test]
240 fn test_ampersand_separator() {
241 assert_eq!(
242 parse_query_string(b"key=1&key=2&anotherKey=a&yetAnother=z", '&'),
243 vec![
244 (String::from("key"), String::from("1")),
245 (String::from("key"), String::from("2")),
246 (String::from("anotherKey"), String::from("a")),
247 (String::from("yetAnother"), String::from("z")),
248 ]
249 );
250 }
251
252 #[test]
253 fn test_handles_url_encoded_ampersand() {
254 assert_eq!(
255 parse_query_string(b"first=%26%40A.ac&second=aaa", '&'),
256 vec![
257 (String::from("first"), String::from("&@A.ac")),
258 (String::from("second"), String::from("aaa")),
259 ]
260 );
261 }
262
263 #[test]
264 fn parse_query_string_to_json_parses_simple_string() {
265 eq_str(parse_query_string_to_json(b"0=foo", true), r#"{"0":"foo"}"#);
266 }
267
268 #[test]
269 fn parse_query_string_to_json_parses_numbers() {
270 assert_eq!(parse_query_string_to_json(b"a=1", true), json!({"a": 1}));
271 assert_eq!(parse_query_string_to_json(b"a=1.1", true), json!({"a": 1.1}));
272 }
273
274 #[test]
275 fn parse_query_string_to_json_parses_booleans() {
276 assert_eq!(parse_query_string_to_json(b"a=true", false), json!({"a": true}));
277 assert_eq!(parse_query_string_to_json(b"a=false", false), json!({"a": false}));
278 }
279
280 #[test]
281 fn parse_query_string_to_json_parses_booleans_from_numbers() {
282 assert_eq!(parse_query_string_to_json(b"a=1", false), json!({"a": true}));
283 assert_eq!(parse_query_string_to_json(b"a=0", false), json!({"a": false}));
284 }
285
286 #[test]
287 fn parse_query_string_to_json_parses_case_insensitive_booleans() {
288 assert_eq!(parse_query_string_to_json(b"a=True", false), json!({"a": true}));
289 assert_eq!(parse_query_string_to_json(b"a=TRUE", false), json!({"a": true}));
290 assert_eq!(parse_query_string_to_json(b"a=False", false), json!({"a": false}));
291 assert_eq!(parse_query_string_to_json(b"a=FALSE", false), json!({"a": false}));
292 }
293
294 #[test]
295 fn parse_query_string_to_json_parses_multiple_values() {
296 assert_eq!(
297 parse_query_string_to_json(b"a=1&a=2&a=3", true),
298 json!({ "a": [1,2,3] })
299 );
300 }
301
302 #[test]
303 fn parse_query_string_to_json_parses_null() {
304 assert_eq!(parse_query_string_to_json(b"a=null", true), json!({ "a": null }));
305 }
306
307 #[test]
308 fn parse_query_string_to_json_parses_empty_string() {
309 assert_eq!(parse_query_string_to_json(b"a=", true), json!({ "a": "" }));
310 }
311
312 #[test]
313 fn parse_query_string_to_json_parses_empty_string_without_number_parsing() {
314 assert_eq!(parse_query_string_to_json(b"a=", false), json!({ "a": "" }));
315 }
316
317 #[test]
318 fn parse_query_string_to_json_parses_multiple_string_values() {
319 assert_eq!(
320 parse_query_string_to_json(b"q=foo&q=bar", true),
321 json!({ "q": ["foo", "bar"] })
322 );
323 }
324
325 #[test]
326 fn parse_query_string_to_json_parses_multiple_string_values_with_parse_numbers_false() {
327 assert_eq!(
328 parse_query_string_to_json(b"q=foo&q=bar", false),
329 json!({ "q": ["foo", "bar"] })
330 );
331 }
332
333 #[test]
334 fn parse_query_string_to_json_preserves_order_and_duplicates() {
335 assert_eq!(
336 parse_query_string_to_json(b"q=foo&q=bar&q=baz", true),
337 json!({ "q": ["foo", "bar", "baz"] })
338 );
339
340 assert_eq!(
341 parse_query_string_to_json(b"q=foo&q=foo&q=bar", true),
342 json!({ "q": ["foo", "foo", "bar"] })
343 );
344 }
345
346 #[test]
347 fn test_url_encoded_special_chars_in_values() {
348 let result = parse_query_string_to_json(b"email=x%40test.com&special=%26%40A.ac", false);
349 assert_eq!(
350 result,
351 json!({
352 "email": "x@test.com",
353 "special": "&@A.ac"
354 })
355 );
356 }
357
358 #[test]
359 fn test_url_encoded_space() {
360 let result = parse_query_string_to_json(b"name=hello%20world", false);
361 assert_eq!(result, json!({ "name": "hello world" }));
362 }
363
364 #[test]
365 fn test_url_encoded_complex_chars() {
366 let result = parse_query_string_to_json(b"name=test%26value%3D123", false);
367 assert_eq!(result, json!({ "name": "test&value=123" }));
368 }
369
370 #[test]
371 fn test_malformed_percent_single_char() {
372 let result = parse_query_string(b"key=%", '&');
373 assert_eq!(result, vec![(String::from("key"), String::from("%"))]);
374 }
375
376 #[test]
377 fn test_malformed_percent_single_hex_only() {
378 let result = parse_query_string(b"key=%2", '&');
379 assert_eq!(result, vec![(String::from("key"), String::from("%2"))]);
380 }
381
382 #[test]
383 fn test_malformed_percent_invalid_hex_chars() {
384 let result = parse_query_string(b"key=%GG&other=value", '&');
385 assert_eq!(
386 result,
387 vec![
388 (String::from("key"), String::from("%GG")),
389 (String::from("other"), String::from("value")),
390 ]
391 );
392 }
393
394 #[test]
395 fn test_malformed_percent_mixed_invalid_hex() {
396 let result = parse_query_string(b"key=%2G&other=value", '&');
397 assert_eq!(
398 result,
399 vec![
400 (String::from("key"), String::from("%2G")),
401 (String::from("other"), String::from("value")),
402 ]
403 );
404 }
405
406 #[test]
407 fn test_percent_encoding_lowercase_hex() {
408 let result = parse_query_string(b"key=%2f&other=test", '&');
409 assert_eq!(
410 result,
411 vec![
412 (String::from("key"), String::from("/")),
413 (String::from("other"), String::from("test")),
414 ]
415 );
416 }
417
418 #[test]
419 fn test_percent_encoding_uppercase_hex() {
420 let result = parse_query_string(b"key=%2F&other=test", '&');
421 assert_eq!(
422 result,
423 vec![
424 (String::from("key"), String::from("/")),
425 (String::from("other"), String::from("test")),
426 ]
427 );
428 }
429
430 #[test]
431 fn test_percent_encoding_mixed_case_hex() {
432 let result = parse_query_string(b"key=%2f%3D%4A", '&');
433 assert_eq!(result, vec![(String::from("key"), String::from("/=J"))]);
434 }
435
436 #[test]
437 fn test_plus_as_space_in_value() {
438 let result = parse_query_string(b"message=hello+world", '&');
439 assert_eq!(result, vec![(String::from("message"), String::from("hello world"))]);
440 }
441
442 #[test]
443 fn test_plus_as_space_multiple_plus() {
444 let result = parse_query_string(b"message=a+b+c+d", '&');
445 assert_eq!(result, vec![(String::from("message"), String::from("a b c d"))]);
446 }
447
448 #[test]
449 fn test_percent_encoded_space_vs_plus() {
450 let result = parse_query_string(b"a=%20space&b=+plus", '&');
451 assert_eq!(
452 result,
453 vec![
454 (String::from("a"), String::from(" space")),
455 (String::from("b"), String::from(" plus")),
456 ]
457 );
458 }
459
460 #[test]
461 fn test_mixed_plus_and_percent_encoded_space() {
462 let result = parse_query_string(b"text=hello+%20world", '&');
463 assert_eq!(result, vec![(String::from("text"), String::from("hello world"))]);
464 }
465
466 #[test]
467 fn test_ampersand_in_value_encoded() {
468 let result = parse_query_string(b"text=foo%26bar", '&');
469 assert_eq!(result, vec![(String::from("text"), String::from("foo&bar"))]);
470 }
471
472 #[test]
473 fn test_equals_in_value_encoded() {
474 let result = parse_query_string(b"text=a%3Db", '&');
475 assert_eq!(result, vec![(String::from("text"), String::from("a=b"))]);
476 }
477
478 #[test]
479 fn test_question_mark_in_value_encoded() {
480 let result = parse_query_string(b"text=what%3F", '&');
481 assert_eq!(result, vec![(String::from("text"), String::from("what?"))]);
482 }
483
484 #[test]
485 fn test_hash_in_value_encoded() {
486 let result = parse_query_string(b"text=anchor%23top", '&');
487 assert_eq!(result, vec![(String::from("text"), String::from("anchor#top"))]);
488 }
489
490 #[test]
491 fn test_multiple_encoded_special_chars() {
492 let result = parse_query_string(b"text=%26%3D%3F%23", '&');
493 assert_eq!(result, vec![(String::from("text"), String::from("&=?#"))]);
494 }
495
496 #[test]
497 fn test_empty_query_string() {
498 let result = parse_query_string(b"", '&');
499 assert_eq!(result, vec![]);
500 }
501
502 #[test]
503 fn test_multiple_consecutive_separators() {
504 let result = parse_query_string(b"a=1&&&b=2", '&');
505 assert_eq!(
506 result,
507 vec![
508 (String::from("a"), String::from("1")),
509 (String::from("b"), String::from("2")),
510 ]
511 );
512 }
513
514 #[test]
515 fn test_key_without_value() {
516 let result = parse_query_string(b"key=", '&');
517 assert_eq!(result, vec![(String::from("key"), String::from(""))]);
518 }
519
520 #[test]
521 fn test_key_without_equals() {
522 let result = parse_query_string(b"key", '&');
523 assert_eq!(result, vec![(String::from("key"), String::from(""))]);
524 }
525
526 #[test]
527 fn test_value_without_key() {
528 let result = parse_query_string(b"=value", '&');
529 assert_eq!(result, vec![(String::from(""), String::from("value"))]);
530 }
531
532 #[test]
533 fn test_multiple_equals_in_pair() {
534 let result = parse_query_string(b"key=val=more", '&');
535 assert_eq!(result, vec![(String::from("key"), String::from("val=more"))]);
536 }
537
538 #[test]
539 fn test_separator_at_start() {
540 let result = parse_query_string(b"&key=value", '&');
541 assert_eq!(result, vec![(String::from("key"), String::from("value"))]);
542 }
543
544 #[test]
545 fn test_separator_at_end() {
546 let result = parse_query_string(b"key=value&", '&');
547 assert_eq!(result, vec![(String::from("key"), String::from("value"))]);
548 }
549
550 #[test]
551 fn test_separator_at_both_ends() {
552 let result = parse_query_string(b"&key=value&", '&');
553 assert_eq!(result, vec![(String::from("key"), String::from("value"))]);
554 }
555
556 #[test]
557 fn test_multiple_values_same_key() {
558 let result = parse_query_string(b"tag=foo&tag=bar&tag=baz", '&');
559 assert_eq!(
560 result,
561 vec![
562 (String::from("tag"), String::from("foo")),
563 (String::from("tag"), String::from("bar")),
564 (String::from("tag"), String::from("baz")),
565 ]
566 );
567 }
568
569 #[test]
570 fn test_multiple_values_mixed_keys() {
571 let result = parse_query_string(b"tag=foo&id=1&tag=bar&id=2", '&');
572 assert_eq!(
573 result,
574 vec![
575 (String::from("tag"), String::from("foo")),
576 (String::from("id"), String::from("1")),
577 (String::from("tag"), String::from("bar")),
578 (String::from("id"), String::from("2")),
579 ]
580 );
581 }
582
583 #[test]
584 fn test_json_conversion_empty_key() {
585 let result = parse_query_string_to_json(b"=value", false);
586 assert_eq!(result, json!({ "": "value" }));
587 }
588
589 #[test]
590 fn test_json_conversion_all_empty_values() {
591 let result = parse_query_string_to_json(b"a=&b=&c=", false);
592 assert_eq!(result, json!({ "a": "", "b": "", "c": "" }));
593 }
594
595 #[test]
596 fn test_json_conversion_malformed_json_object() {
597 let result = parse_query_string_to_json(b"data={invalid", false);
598 assert_eq!(result, json!({ "data": "{invalid" }));
599 }
600
601 #[test]
602 fn test_json_conversion_malformed_json_array() {
603 let result = parse_query_string_to_json(b"items=[1,2,", false);
604 assert_eq!(result, json!({ "items": "[1,2," }));
605 }
606
607 #[test]
608 fn test_json_conversion_with_quotes_in_value() {
609 let result = parse_query_string_to_json(b"text=\"hello\"", false);
610 assert_eq!(result, json!({ "text": "hello" }));
611 }
612
613 #[test]
614 fn test_json_conversion_single_quotes_in_object() {
615 let result = parse_query_string_to_json(b"obj={'key':'value'}", false);
616 let value = result.get("obj");
617 assert!(value.is_some());
618 }
619
620 #[test]
621 fn test_boolean_case_insensitive_variations() {
622 assert_eq!(parse_query_string_to_json(b"a=tRuE", false), json!({"a": true}));
623 assert_eq!(parse_query_string_to_json(b"a=FaLsE", false), json!({"a": false}));
624 assert_eq!(
625 parse_query_string_to_json(b"a=tRuE&b=FaLsE", false),
626 json!({"a": true, "b": false})
627 );
628 }
629
630 #[test]
631 fn test_boolean_with_numbers_no_parse() {
632 assert_eq!(parse_query_string_to_json(b"a=1", false), json!({"a": true}));
633 assert_eq!(parse_query_string_to_json(b"a=0", false), json!({"a": false}));
634 }
635
636 #[test]
637 fn test_number_parsing_negative() {
638 assert_eq!(parse_query_string_to_json(b"a=-123", true), json!({"a": -123}));
639 assert_eq!(parse_query_string_to_json(b"a=-1.5", true), json!({"a": -1.5}));
640 }
641
642 #[test]
643 fn test_number_parsing_zero() {
644 assert_eq!(parse_query_string_to_json(b"a=0", true), json!({"a": 0}));
645 assert_eq!(parse_query_string_to_json(b"a=0.0", true), json!({"a": 0.0}));
646 }
647
648 #[test]
649 fn test_number_parsing_scientific_notation() {
650 assert_eq!(parse_query_string_to_json(b"a=1e10", true), json!({"a": 1e10}));
651 assert_eq!(parse_query_string_to_json(b"a=1.5e-3", true), json!({"a": 1.5e-3}));
652 }
653
654 #[test]
655 fn test_array_mixed_types_with_number_parsing() {
656 assert_eq!(
657 parse_query_string_to_json(b"vals=1&vals=2.5&vals=true&vals=test", true),
658 json!({"vals": [1, 2.5, true, "test"]})
659 );
660 }
661
662 #[test]
663 fn test_array_mixed_types_without_number_parsing() {
664 assert_eq!(
665 parse_query_string_to_json(b"vals=1&vals=2.5&vals=true&vals=test", false),
666 json!({"vals": [true, "2.5", true, "test"]})
667 );
668 }
669
670 #[test]
671 fn test_utf8_chinese_characters() {
672 let result = parse_query_string("name=中文".as_bytes(), '&');
673 assert_eq!(result, vec![(String::from("name"), String::from("中文"))]);
674 }
675
676 #[test]
677 fn test_utf8_emoji() {
678 let result = parse_query_string("emoji=🚀".as_bytes(), '&');
679 assert_eq!(result, vec![(String::from("emoji"), String::from("🚀"))]);
680 }
681
682 #[test]
683 fn test_utf8_mixed_with_encoding() {
684 let result = parse_query_string("text=hello%20中文".as_bytes(), '&');
685 assert_eq!(result, vec![(String::from("text"), String::from("hello 中文"))]);
686 }
687
688 #[test]
689 fn test_custom_separator_semicolon() {
690 let result = parse_query_string(b"a=1;b=2;c=3", ';');
691 assert_eq!(
692 result,
693 vec![
694 (String::from("a"), String::from("1")),
695 (String::from("b"), String::from("2")),
696 (String::from("c"), String::from("3")),
697 ]
698 );
699 }
700
701 #[test]
702 fn test_custom_separator_comma() {
703 let result = parse_query_string(b"a=1,b=2,c=3", ',');
704 assert_eq!(
705 result,
706 vec![
707 (String::from("a"), String::from("1")),
708 (String::from("b"), String::from("2")),
709 (String::from("c"), String::from("3")),
710 ]
711 );
712 }
713
714 #[test]
715 fn test_percent_encoding_all_byte_values() {
716 let result = parse_query_string(b"space=%20&at=%40&hash=%23&dollar=%24", '&');
717 assert_eq!(
718 result,
719 vec![
720 (String::from("space"), String::from(" ")),
721 (String::from("at"), String::from("@")),
722 (String::from("hash"), String::from("#")),
723 (String::from("dollar"), String::from("$")),
724 ]
725 );
726 }
727
728 #[test]
729 fn test_high_byte_values_in_percent_encoding() {
730 let result = parse_query_string(b"high=%ff%fe%fd", '&');
731 assert_eq!(result.len(), 1);
732 assert_eq!(result[0].0, "high");
733 }
734
735 #[test]
736 fn test_very_long_query_string() {
737 let mut long_query = String::from("key=");
738 long_query.push_str(&"a".repeat(10000));
739 let result = parse_query_string(long_query.as_bytes(), '&');
740 assert_eq!(result.len(), 1);
741 assert_eq!(result[0].0, "key");
742 assert_eq!(result[0].1.len(), 10000);
743 }
744
745 #[test]
746 fn test_very_large_number_of_parameters() {
747 let mut query = String::new();
748 for i in 0..100 {
749 if i > 0 {
750 query.push('&');
751 }
752 query.push_str(&format!("param{}=value{}", i, i));
753 }
754 let result = parse_query_string(query.as_bytes(), '&');
755 assert_eq!(result.len(), 100);
756 assert_eq!(result[0].0, "param0");
757 assert_eq!(result[99].0, "param99");
758 }
759
760 #[test]
761 fn test_literal_space_in_value() {
762 let result = parse_query_string(b"name=hello world", '&');
763 assert_eq!(result, vec![(String::from("name"), String::from("hello world"))]);
764 }
765
766 #[test]
767 fn test_tab_in_value() {
768 let result = parse_query_string(b"name=hello\tworld", '&');
769 assert_eq!(result, vec![(String::from("name"), String::from("hello\tworld"))]);
770 }
771
772 #[test]
773 fn test_newline_in_value() {
774 let result = parse_query_string(b"name=hello\nworld", '&');
775 assert_eq!(result, vec![(String::from("name"), String::from("hello\nworld"))]);
776 }
777}