1use log::warn;
7use serde_json::Value;
8
9pub const MAX_JSON_DEPTH: usize = 32;
19
20pub fn validate_json_depth(json_str: &str, max_depth: usize) -> Result<(), String> {
60 let value: Value = serde_json::from_str(json_str).map_err(|e| {
62 warn!("JSON parse error: {}", e);
64 "Invalid JSON format".to_string()
66 })?;
67
68 let depth = calculate_depth(&value);
70
71 if depth > max_depth {
72 warn!(
74 "JSON depth validation failed: depth {} exceeds maximum {}",
75 depth, max_depth
76 );
77 return Err("JSON structure too deeply nested".to_string());
79 }
80
81 Ok(())
82}
83
84fn calculate_depth(value: &Value) -> usize {
98 calculate_depth_limited(value, 0)
99}
100
101fn calculate_depth_limited(value: &Value, current_depth: usize) -> usize {
106 if current_depth > MAX_JSON_DEPTH {
109 return current_depth;
110 }
111
112 match value {
113 Value::Array(arr) => {
114 if arr.is_empty() {
115 1
116 } else {
117 let max_child = arr
118 .iter()
119 .map(|v| calculate_depth_limited(v, current_depth.saturating_add(1)))
120 .max()
121 .unwrap_or(0);
122 1_usize.saturating_add(max_child)
123 }
124 }
125 Value::Object(obj) => {
126 if obj.is_empty() {
127 1
128 } else {
129 let max_child = obj
130 .values()
131 .map(|v| calculate_depth_limited(v, current_depth.saturating_add(1)))
132 .max()
133 .unwrap_or(0);
134 1_usize.saturating_add(max_child)
135 }
136 }
137 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => 0,
139 }
140}
141
142#[cfg(test)]
143#[allow(clippy::expect_used)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_calculate_depth_scalar() {
149 let value = serde_json::json!("test");
150 assert_eq!(calculate_depth(&value), 0);
151
152 let value = serde_json::json!(42);
153 assert_eq!(calculate_depth(&value), 0);
154
155 let value = serde_json::json!(true);
156 assert_eq!(calculate_depth(&value), 0);
157
158 let value = serde_json::json!(null);
159 assert_eq!(calculate_depth(&value), 0);
160 }
161
162 #[test]
163 fn test_calculate_depth_simple_object() {
164 let value = serde_json::json!({"key": "value"});
165 assert_eq!(calculate_depth(&value), 1);
166 }
167
168 #[test]
169 fn test_calculate_depth_simple_array() {
170 let value = serde_json::json!([1, 2, 3]);
171 assert_eq!(calculate_depth(&value), 1);
172 }
173
174 #[test]
175 fn test_calculate_depth_nested_object() {
176 let value = serde_json::json!({
177 "user": {
178 "profile": {
179 "settings": {
180 "theme": "dark"
181 }
182 }
183 }
184 });
185 assert_eq!(calculate_depth(&value), 4);
186 }
187
188 #[test]
189 fn test_calculate_depth_nested_array() {
190 let value = serde_json::json!([[[1, 2], [3, 4]]]);
191 assert_eq!(calculate_depth(&value), 3);
192 }
193
194 #[test]
195 fn test_calculate_depth_mixed() {
196 let value = serde_json::json!({
197 "data": [
198 {"nested": [1, 2, 3]},
199 {"nested": [4, 5, 6]}
200 ]
201 });
202 assert_eq!(calculate_depth(&value), 4);
204 }
205
206 #[test]
207 fn test_calculate_depth_empty_structures() {
208 let value = serde_json::json!({});
209 assert_eq!(calculate_depth(&value), 1);
210
211 let value = serde_json::json!([]);
212 assert_eq!(calculate_depth(&value), 1);
213 }
214
215 #[test]
216 fn test_validate_json_depth_valid() {
217 let json = r#"{"user": {"profile": {"name": "test"}}}"#;
218 assert!(validate_json_depth(json, MAX_JSON_DEPTH).is_ok());
219 }
220
221 #[test]
222 fn test_validate_json_depth_at_limit() {
223 let mut json = String::from("{");
225 for i in 0..MAX_JSON_DEPTH - 1 {
226 json.push_str(&format!("\"level{}\":{{", i));
227 }
228 json.push_str("\"value\":42");
229 json.push_str(&"}".repeat(MAX_JSON_DEPTH));
230
231 assert!(validate_json_depth(&json, MAX_JSON_DEPTH).is_ok());
232 }
233
234 #[test]
235 fn test_validate_json_depth_exceeds_limit() {
236 let mut json = String::from("{");
238 for i in 0..MAX_JSON_DEPTH + 5 {
239 json.push_str(&format!("\"level{}\":{{", i));
240 }
241 json.push_str("\"value\":42");
242 json.push_str(&"}".repeat(MAX_JSON_DEPTH + 6));
243
244 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
245 assert!(result.is_err());
246 assert!(
247 result
248 .expect_err("should fail on deeply nested json")
249 .contains("too deeply nested")
250 );
251 }
252
253 #[test]
254 fn test_validate_json_depth_invalid_json() {
255 let json = r#"{"invalid": json}"#;
256 let result = validate_json_depth(json, MAX_JSON_DEPTH);
257 assert!(result.is_err());
258 assert!(
259 result
260 .expect_err("should fail on invalid json")
261 .contains("Invalid JSON format")
262 );
263 }
264
265 #[test]
266 fn test_validate_json_depth_deeply_nested_array() {
267 let mut json = String::new();
269 for _ in 0..50 {
270 json.push('[');
271 }
272 json.push_str("42");
273 for _ in 0..50 {
274 json.push(']');
275 }
276
277 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
278 assert!(result.is_err());
279 assert!(
280 result
281 .expect_err("should fail on deeply nested json")
282 .contains("too deeply nested")
283 );
284 }
285
286 #[test]
287 fn test_validate_json_depth_custom_limit() {
288 let json = r#"{"a": {"b": {"c": {"d": "value"}}}}"#;
289
290 assert!(validate_json_depth(json, 5).is_ok());
292
293 let result = validate_json_depth(json, 3);
295 assert!(result.is_err());
296 }
297
298 #[test]
299 fn test_realistic_api_response() {
300 let json = r#"{
302 "_embedded": {
303 "applications": [
304 {
305 "id": 123,
306 "profile": {
307 "name": "TestApp",
308 "settings": {
309 "scan": {
310 "enabled": true
311 }
312 }
313 }
314 }
315 ]
316 }
317 }"#;
318
319 assert!(validate_json_depth(json, MAX_JSON_DEPTH).is_ok());
320 }
321
322 #[test]
323 fn test_dos_payload_detection() {
324 let depth = 100;
327 let mut json = String::new();
328
329 for i in 0..depth {
331 json.push_str(&format!("{{\"level_{}\":", i));
332 }
333 json.push_str("null");
334 for _ in 0..depth {
335 json.push('}');
336 }
337
338 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
339 assert!(result.is_err());
340 assert!(
341 result
342 .expect_err("should fail on deeply nested json")
343 .contains("too deeply nested")
344 );
345 }
346}
347
348#[cfg(test)]
356#[allow(clippy::expect_used)]
357mod proptest_security {
358 use super::*;
359 use proptest::prelude::*;
360
361 proptest! {
366 #![proptest_config(ProptestConfig {
367 cases: if cfg!(miri) { 5 } else { 1000 },
368 failure_persistence: None,
369 .. ProptestConfig::default()
370 })]
371
372 #[test]
375 fn proptest_valid_json_within_limits_succeeds(
376 depth in 1usize..=MAX_JSON_DEPTH,
377 ) {
378 let mut json = String::new();
380 for i in 0..depth {
381 json.push_str(&format!("{{\"level{}\":", i));
382 }
383 json.push_str("\"value\"");
384 json.push_str(&"}".repeat(depth));
385
386 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
387 prop_assert!(result.is_ok(), "Valid JSON at depth {} should succeed", depth);
388 }
389
390 #[test]
393 fn proptest_deeply_nested_json_rejected(
394 excess_depth in 1usize..=50,
395 ) {
396 let depth = MAX_JSON_DEPTH.saturating_add(excess_depth);
397
398 let mut json = String::new();
400 for i in 0..depth {
401 json.push_str(&format!("{{\"level{}\":", i));
402 }
403 json.push_str("null");
404 json.push_str(&"}".repeat(depth));
405
406 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
407 prop_assert!(result.is_err(), "JSON at depth {} should be rejected", depth);
408
409 if let Err(msg) = result {
411 prop_assert!(
412 msg.contains("too deeply nested") || msg == "Invalid JSON format",
413 "Error message should indicate rejection: {}", msg
414 );
415 }
416 }
417
418 #[test]
421 fn proptest_invalid_json_returns_error(
422 garbage in ".*{0,200}",
423 ) {
424 let result = validate_json_depth(&garbage, MAX_JSON_DEPTH);
426
427 match result {
429 Ok(_) => {
430 prop_assert!(serde_json::from_str::<Value>(&garbage).is_ok());
432 },
433 Err(msg) => {
434 prop_assert!(
436 msg == "Invalid JSON format" || msg.contains("too deeply nested"),
437 "Error message should be sanitized"
438 );
439 }
440 }
441 }
442
443 #[test]
446 fn proptest_empty_and_whitespace_json(
447 whitespace in "\\s{0,100}",
448 ) {
449 let result = validate_json_depth(&whitespace, MAX_JSON_DEPTH);
450
451 match result {
453 Ok(_) => {
454 prop_assert!(serde_json::from_str::<Value>(&whitespace).is_ok());
456 },
457 Err(msg) => {
458 prop_assert_eq!(msg, "Invalid JSON format");
459 }
460 }
461 }
462
463 #[test]
466 fn proptest_custom_depth_limit_enforced(
467 max_depth in 5usize..=MAX_JSON_DEPTH,
468 test_depth in 1usize..=MAX_JSON_DEPTH,
469 ) {
470 let mut json = String::new();
473 for i in 0..test_depth {
474 json.push_str(&format!("{{\"d{}\":", i));
475 }
476 json.push('0');
477 json.push_str(&"}".repeat(test_depth));
478
479 let result = validate_json_depth(&json, max_depth);
480
481 if test_depth <= max_depth {
483 prop_assert!(result.is_ok(),
484 "JSON depth {} should pass with limit {}", test_depth, max_depth);
485 } else {
486 prop_assert!(result.is_err(),
487 "JSON depth {} should fail with limit {}", test_depth, max_depth);
488 }
489 }
490 }
491
492 proptest! {
497 #![proptest_config(ProptestConfig {
498 cases: if cfg!(miri) { 5 } else { 1000 },
499 failure_persistence: None,
500 .. ProptestConfig::default()
501 })]
502
503 #[test]
506 fn proptest_special_characters_in_strings(
507 special_chars in r#"[<>'"&\x00-\x1f\x7f\\]{0,100}"#,
508 ) {
509 let json = serde_json::json!({
510 "payload": special_chars,
511 "nested": {
512 "value": special_chars
513 }
514 }).to_string();
515
516 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
518 prop_assert!(result.is_ok());
519
520 let parsed: Value = serde_json::from_str(&json)
522 .expect("serde_json should handle its own output");
523 prop_assert_eq!(parsed.get("payload").and_then(|v| v.as_str()), Some(special_chars.as_str()));
524 }
525
526 #[test]
529 fn proptest_control_characters_safe(
530 control_char in prop::sample::select(vec![
532 '\0', '\t', '\n', '\r', '\x01', '\x02', '\x08', '\x0c', '\x1f', '\x7f'
533 ]),
534 ) {
535 let payload = format!("test{}value", control_char);
536 let json = serde_json::json!({
537 "data": payload
538 }).to_string();
539
540 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
542 prop_assert!(result.is_ok());
543 }
544
545 #[test]
548 fn proptest_large_strings_safe(
549 length in 0usize..=10000,
550 ) {
551 let large_string = "A".repeat(length);
552 let json = serde_json::json!({
553 "large_field": large_string
554 }).to_string();
555
556 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
558 prop_assert!(result.is_ok());
559
560 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
562 prop_assert_eq!(calculate_depth(&parsed), 1);
563 }
564
565 #[test]
568 fn proptest_unicode_handling(
569 unicode_str in "[\\p{L}\\p{N}\\p{S}\\p{M}]{0,200}",
571 ) {
572 let json = serde_json::json!({
573 "unicode": unicode_str,
574 "nested": {
575 "more_unicode": unicode_str
576 }
577 }).to_string();
578
579 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
581 prop_assert!(result.is_ok());
582
583 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
585 prop_assert_eq!(parsed.get("unicode").and_then(|v| v.as_str()), Some(unicode_str.as_str()));
586 }
587
588 #[test]
591 fn proptest_path_traversal_sequences_safe(
592 traversal in prop::sample::select(vec![
594 "../", "..\\", "../../", "../../../etc/passwd",
595 "....//", "..\\..\\", "/etc/passwd", "C:\\Windows\\System32",
596 "%2e%2e%2f", "%2e%2e/", "..%2f", "..%5c"
597 ]),
598 ) {
599 let json = serde_json::json!({
600 "filename": traversal,
601 "path": traversal
602 }).to_string();
603
604 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
607 prop_assert!(result.is_ok());
608
609 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
611 prop_assert_eq!(parsed.get("filename").and_then(|v| v.as_str()), Some(traversal));
612 }
613
614 #[test]
617 fn proptest_null_byte_injection_safe(
618 prefix in "[a-zA-Z0-9]{0,50}",
619 suffix in "[a-zA-Z0-9]{0,50}",
620 ) {
621 let payload = format!("{}\0{}", prefix, suffix);
623 let json = serde_json::json!({
624 "payload": payload
625 }).to_string();
626
627 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
629 prop_assert!(result.is_ok());
630
631 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
633 if let Some(s) = parsed.get("payload").and_then(|v| v.as_str()) {
634 let expected_without_null = format!("{}{}", prefix, suffix);
636 prop_assert!(s.contains('\0') || s == expected_without_null);
637 }
638 }
639 }
640
641 proptest! {
646 #![proptest_config(ProptestConfig {
647 cases: if cfg!(miri) { 5 } else { 1000 },
648 failure_persistence: None,
649 .. ProptestConfig::default()
650 })]
651
652 #[test]
655 fn proptest_large_arrays_safe(
656 size in 0usize..=1000,
657 ) {
658 let array: Vec<i32> = (0..size).map(|i| i32::try_from(i).unwrap_or(0)).collect();
659 let json = serde_json::json!({
660 "large_array": array
661 }).to_string();
662
663 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
665 prop_assert!(result.is_ok());
666
667 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
669 prop_assert_eq!(calculate_depth(&parsed), 2);
670 }
671
672 #[test]
675 fn proptest_large_objects_safe(
676 key_count in 0usize..=500,
677 ) {
678 let mut obj = serde_json::Map::new();
680 for i in 0..key_count {
681 obj.insert(format!("key_{}", i), Value::from(i));
682 }
683 let json = Value::Object(obj).to_string();
684
685 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
687 prop_assert!(result.is_ok());
688
689 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
691 prop_assert_eq!(calculate_depth(&parsed), 1);
692 }
693
694 #[test]
697 fn proptest_empty_structures_depth(
698 nest_level in 0usize..=10,
699 ) {
700 let mut json = String::new();
702 for _ in 0..nest_level {
703 json.push_str("{\"empty\":");
704 }
705 json.push_str("{}");
706 json.push_str(&"}".repeat(nest_level));
707
708 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
709 prop_assert!(result.is_ok());
710
711 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
712 let depth = calculate_depth(&parsed);
713 prop_assert_eq!(depth, nest_level.saturating_add(1));
715 }
716
717 #[test]
720 fn proptest_mixed_nesting_depth_calculation(
721 depth in 1usize..=10,
722 ) {
723 let mut json = String::new();
725 for _ in 0..depth {
726 json.push_str("[{\"x\":");
727 }
728 json.push_str("null");
729 json.push_str(&"}]".repeat(depth));
730
731 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
732 prop_assert!(result.is_ok(), "JSON construction should be valid");
733
734 let parsed: Value = serde_json::from_str(&json)
735 .expect("JSON should parse correctly");
736 let calculated_depth = calculate_depth(&parsed);
737 prop_assert!(calculated_depth >= depth,
739 "Calculated depth {} should be >= nesting levels {}",
740 calculated_depth, depth);
741 }
742 }
743
744 proptest! {
749 #![proptest_config(ProptestConfig {
750 cases: if cfg!(miri) { 5 } else { 1000 },
751 failure_persistence: None,
752 .. ProptestConfig::default()
753 })]
754
755 #[test]
758 fn proptest_depth_calculation_no_overflow(
759 depth in 0usize..=200,
761 ) {
762 let mut json = String::new();
764 for i in 0..depth {
765 json.push_str(&format!("{{\"{}\":", i));
766 }
767 json.push_str("42");
768 json.push_str(&"}".repeat(depth));
769
770 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
773
774 if depth <= MAX_JSON_DEPTH {
775 prop_assert!(result.is_ok());
776 } else {
777 prop_assert!(result.is_err());
778 }
779 }
780
781 #[test]
784 fn proptest_extreme_numeric_values(
785 value in prop::num::i64::ANY,
786 ) {
787 let json = serde_json::json!({
788 "number": value,
789 "nested": {
790 "another_number": value
791 }
792 }).to_string();
793
794 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
796 prop_assert!(result.is_ok());
797
798 let parsed: Value = serde_json::from_str(&json).expect("JSON parsing should succeed");
799 prop_assert_eq!(calculate_depth(&parsed), 2);
800 }
801
802 #[test]
805 fn proptest_scalar_values_depth_zero(
806 bool_val in any::<bool>(),
807 ) {
808 let null_value = Value::Null;
810 let bool_value = Value::Bool(bool_val);
811 let num_value = Value::from(42);
812 let str_value = Value::from("test");
813
814 prop_assert_eq!(calculate_depth(&null_value), 0);
815 prop_assert_eq!(calculate_depth(&bool_value), 0);
816 prop_assert_eq!(calculate_depth(&num_value), 0);
817 prop_assert_eq!(calculate_depth(&str_value), 0);
818 }
819
820 #[test]
823 fn proptest_recursion_bounded(
824 depth in (MAX_JSON_DEPTH + 1)..=60,
825 ) {
826 let mut json = String::new();
828 for i in 0..depth {
829 json.push_str(&format!("[{{\"{}\":", i));
830 }
831 json.push('0');
832 json.push_str(&"}]".repeat(depth));
833
834 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
837 prop_assert!(result.is_err());
838
839 if let Ok(parsed) = serde_json::from_str::<Value>(&json) {
843 let calculated = calculate_depth(&parsed);
844 prop_assert!(calculated > MAX_JSON_DEPTH);
846 }
847 }
848 }
849
850 proptest! {
855 #![proptest_config(ProptestConfig {
856 cases: if cfg!(miri) { 5 } else { 500 }, failure_persistence: None,
858 .. ProptestConfig::default()
859 })]
860
861 #[test]
864 fn proptest_dos_exponential_width(
865 width in 1usize..=20,
866 depth in 1usize..=4,
867 ) {
868 fn create_wide_json(width: usize, depth: usize) -> String {
871 if depth == 0 {
872 return "42".to_string();
873 }
874
875 let mut json = String::from("[");
876 for i in 0..width {
877 if i > 0 {
878 json.push(',');
879 }
880 json.push_str(&create_wide_json(width, depth.saturating_sub(1)));
881 }
882 json.push(']');
883 json
884 }
885
886 let json = create_wide_json(width, depth);
887
888 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
890
891 if depth <= MAX_JSON_DEPTH {
892 prop_assert!(result.is_ok() || result.is_err()); } else {
894 prop_assert!(result.is_err());
895 }
896 }
897
898 #[test]
901 fn proptest_dos_varied_nesting_patterns(
902 depth in 30usize..=MAX_JSON_DEPTH + 20,
903 pattern in prop::sample::select(vec!["array", "object", "mixed"]),
904 ) {
905 let json = match pattern {
906 "array" => {
907 let mut s = String::new();
908 for _ in 0..depth {
909 s.push('[');
910 }
911 s.push_str("null");
912 s.push_str(&"]".repeat(depth));
913 s
914 },
915 "object" => {
916 let mut s = String::new();
917 for i in 0..depth {
918 s.push_str(&format!("{{\"k{}\":", i));
919 }
920 s.push_str("null");
921 s.push_str(&"}".repeat(depth));
922 s
923 },
924 _ => { let mut s = String::new();
926 for i in 0..depth {
927 if i % 2 == 0 {
928 s.push('[');
929 } else {
930 s.push_str("{\"x\":");
931 }
932 }
933 s.push_str("null");
934 for i in (0..depth).rev() {
935 if i % 2 == 0 {
936 s.push(']');
937 } else {
938 s.push('}');
939 }
940 }
941 s
942 }
943 };
944
945 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
946
947 if depth <= MAX_JSON_DEPTH {
948 prop_assert!(result.is_ok());
949 } else {
950 prop_assert!(result.is_err());
951 }
952 }
953
954 #[test]
957 fn proptest_malformed_json_graceful_failure(
958 open_brackets in 0usize..=50,
959 close_brackets in 0usize..=50,
960 ) {
961 let mut json = String::new();
963 json.push_str(&"[".repeat(open_brackets));
964 json.push_str("null");
965 json.push_str(&"]".repeat(close_brackets));
966
967 let result = validate_json_depth(&json, MAX_JSON_DEPTH);
968
969 match result {
971 Ok(_) => {
972 prop_assert_eq!(open_brackets, close_brackets);
974 },
975 Err(msg) => {
976 prop_assert!(
978 msg == "Invalid JSON format" || msg.contains("too deeply nested")
979 );
980 }
981 }
982 }
983 }
984}