cli_testing_specialist/utils/
safe_deserialize.rs1use crate::error::{CliTestError, Result};
2use serde::de::DeserializeOwned;
3use std::io::Read;
4
5const MAX_DESERIALIZE_SIZE: usize = 10 * 1024 * 1024;
7
8const MAX_RECURSION_DEPTH: usize = 16;
10
11pub fn deserialize_json_safe<T: DeserializeOwned>(input: &str) -> Result<T> {
40 if input.len() > MAX_DESERIALIZE_SIZE {
42 return Err(CliTestError::Validation(format!(
43 "JSON payload too large: {} bytes (max: {} bytes)",
44 input.len(),
45 MAX_DESERIALIZE_SIZE
46 )));
47 }
48
49 if input.trim().is_empty() {
51 return Err(CliTestError::Validation(
52 "JSON payload is empty".to_string(),
53 ));
54 }
55
56 let value: T = serde_json::from_str(input)
58 .map_err(|e| CliTestError::Validation(format!("JSON deserialization failed: {}", e)))?;
59
60 let json_value: serde_json::Value = serde_json::from_str(input)?;
62 let depth = calculate_json_depth(&json_value);
63
64 if depth > MAX_RECURSION_DEPTH {
65 return Err(CliTestError::Validation(format!(
66 "JSON depth too deep: {} levels (max: {} levels)",
67 depth, MAX_RECURSION_DEPTH
68 )));
69 }
70
71 Ok(value)
72}
73
74pub fn deserialize_json_safe_from_reader<R: Read, T: DeserializeOwned>(reader: R) -> Result<T> {
79 let mut buffer = Vec::new();
80
81 reader
83 .take(MAX_DESERIALIZE_SIZE as u64 + 1)
84 .read_to_end(&mut buffer)?;
85
86 if buffer.len() > MAX_DESERIALIZE_SIZE {
87 return Err(CliTestError::Validation(format!(
88 "JSON payload too large: exceeds {} bytes",
89 MAX_DESERIALIZE_SIZE
90 )));
91 }
92
93 let input = String::from_utf8(buffer)
94 .map_err(|e| CliTestError::Validation(format!("Invalid UTF-8 in JSON payload: {}", e)))?;
95
96 deserialize_json_safe(&input)
97}
98
99pub fn deserialize_yaml_safe<T: DeserializeOwned>(input: &str) -> Result<T> {
129 if input.len() > MAX_DESERIALIZE_SIZE {
131 return Err(CliTestError::Validation(format!(
132 "YAML payload too large: {} bytes (max: {} bytes)",
133 input.len(),
134 MAX_DESERIALIZE_SIZE
135 )));
136 }
137
138 if input.trim().is_empty() {
140 return Err(CliTestError::Validation(
141 "YAML payload is empty".to_string(),
142 ));
143 }
144
145 let value: T = serde_yaml::from_str(input)
147 .map_err(|e| CliTestError::Validation(format!("YAML deserialization failed: {}", e)))?;
148
149 let yaml_value: serde_yaml::Value = serde_yaml::from_str(input)?;
151 let json_value = yaml_to_json_value(&yaml_value)?;
152 let depth = calculate_json_depth(&json_value);
153
154 if depth > MAX_RECURSION_DEPTH {
155 return Err(CliTestError::Validation(format!(
156 "YAML depth too deep: {} levels (max: {} levels)",
157 depth, MAX_RECURSION_DEPTH
158 )));
159 }
160
161 Ok(value)
162}
163
164pub fn deserialize_yaml_safe_from_reader<R: Read, T: DeserializeOwned>(reader: R) -> Result<T> {
166 let mut buffer = Vec::new();
167
168 reader
170 .take(MAX_DESERIALIZE_SIZE as u64 + 1)
171 .read_to_end(&mut buffer)?;
172
173 if buffer.len() > MAX_DESERIALIZE_SIZE {
174 return Err(CliTestError::Validation(format!(
175 "YAML payload too large: exceeds {} bytes",
176 MAX_DESERIALIZE_SIZE
177 )));
178 }
179
180 let input = String::from_utf8(buffer)
181 .map_err(|e| CliTestError::Validation(format!("Invalid UTF-8 in YAML payload: {}", e)))?;
182
183 deserialize_yaml_safe(&input)
184}
185
186fn calculate_json_depth(value: &serde_json::Value) -> usize {
188 match value {
189 serde_json::Value::Object(map) => {
190 1 + map.values().map(calculate_json_depth).max().unwrap_or(0)
191 }
192 serde_json::Value::Array(arr) => {
193 1 + arr.iter().map(calculate_json_depth).max().unwrap_or(0)
194 }
195 _ => 1,
196 }
197}
198
199fn yaml_to_json_value(yaml: &serde_yaml::Value) -> Result<serde_json::Value> {
201 match yaml {
202 serde_yaml::Value::Null => Ok(serde_json::Value::Null),
203 serde_yaml::Value::Bool(b) => Ok(serde_json::Value::Bool(*b)),
204 serde_yaml::Value::Number(n) => {
205 if let Some(i) = n.as_i64() {
206 Ok(serde_json::Value::Number(i.into()))
207 } else if let Some(f) = n.as_f64() {
208 serde_json::Number::from_f64(f)
209 .map(serde_json::Value::Number)
210 .ok_or_else(|| CliTestError::Validation("Invalid YAML number".to_string()))
211 } else {
212 Err(CliTestError::Validation("Invalid YAML number".to_string()))
213 }
214 }
215 serde_yaml::Value::String(s) => Ok(serde_json::Value::String(s.clone())),
216 serde_yaml::Value::Sequence(arr) => {
217 let json_arr: Result<Vec<_>> = arr.iter().map(yaml_to_json_value).collect();
218 Ok(serde_json::Value::Array(json_arr?))
219 }
220 serde_yaml::Value::Mapping(map) => {
221 let mut json_map = serde_json::Map::new();
222 for (k, v) in map {
223 let key = match k {
224 serde_yaml::Value::String(s) => s.clone(),
225 _ => {
226 return Err(CliTestError::Validation(
227 "YAML map key must be string".to_string(),
228 ))
229 }
230 };
231 json_map.insert(key, yaml_to_json_value(v)?);
232 }
233 Ok(serde_json::Value::Object(json_map))
234 }
235 serde_yaml::Value::Tagged(tagged) => yaml_to_json_value(&tagged.value),
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use serde::{Deserialize, Serialize};
243
244 #[derive(Debug, Serialize, Deserialize, PartialEq)]
245 struct TestStruct {
246 name: String,
247 value: i32,
248 }
249
250 #[test]
253 fn test_json_deserialize_safe_success() {
254 let json = r#"{"name": "test", "value": 42}"#;
255 let result: Result<TestStruct> = deserialize_json_safe(json);
256
257 assert!(result.is_ok());
258 let data = result.unwrap();
259 assert_eq!(data.name, "test");
260 assert_eq!(data.value, 42);
261 }
262
263 #[test]
264 fn test_json_deserialize_safe_empty_input() {
265 let json = "";
266 let result: Result<TestStruct> = deserialize_json_safe(json);
267
268 assert!(result.is_err());
269 assert!(result
270 .unwrap_err()
271 .to_string()
272 .contains("JSON payload is empty"));
273 }
274
275 #[test]
276 fn test_json_deserialize_safe_size_limit() {
277 let large_json = format!(r#"{{"data": "{}"}}"#, "x".repeat(MAX_DESERIALIZE_SIZE + 1));
279
280 let result: Result<serde_json::Value> = deserialize_json_safe(&large_json);
281
282 assert!(result.is_err());
283 assert!(result
284 .unwrap_err()
285 .to_string()
286 .contains("JSON payload too large"));
287 }
288
289 #[test]
290 fn test_json_deserialize_safe_depth_limit() {
291 let mut nested_json = String::from(r#"{"a":"value"}"#);
293 for _ in 0..17 {
294 nested_json = format!(r#"{{"nested":{}}}"#, nested_json);
295 }
296
297 let result: Result<serde_json::Value> = deserialize_json_safe(&nested_json);
298
299 assert!(result.is_err());
300 let err_msg = result.unwrap_err().to_string();
301 assert!(err_msg.contains("depth too deep") || err_msg.contains("17 levels"));
302 }
303
304 #[test]
305 fn test_json_deserialize_safe_valid_depth() {
306 let mut nested_json = String::from(r#"{"a":"value"}"#);
308 for _ in 0..10 {
309 nested_json = format!(r#"{{"nested":{}}}"#, nested_json);
310 }
311
312 let result: Result<serde_json::Value> = deserialize_json_safe(&nested_json);
313
314 assert!(result.is_ok());
315 }
316
317 #[test]
318 fn test_json_deserialize_safe_invalid_syntax() {
319 let json = r#"{"name": "test", "value": }"#; let result: Result<TestStruct> = deserialize_json_safe(json);
322
323 assert!(result.is_err());
324 assert!(result
325 .unwrap_err()
326 .to_string()
327 .contains("JSON deserialization failed"));
328 }
329
330 #[test]
331 fn test_json_deserialize_from_reader() {
332 let json = r#"{"name": "test", "value": 42}"#;
333 let reader = json.as_bytes();
334
335 let result: Result<TestStruct> = deserialize_json_safe_from_reader(reader);
336
337 assert!(result.is_ok());
338 let data = result.unwrap();
339 assert_eq!(data.name, "test");
340 assert_eq!(data.value, 42);
341 }
342
343 #[test]
346 fn test_yaml_deserialize_safe_success() {
347 let yaml = "name: test\nvalue: 42";
348 let result: Result<TestStruct> = deserialize_yaml_safe(yaml);
349
350 assert!(result.is_ok());
351 let data = result.unwrap();
352 assert_eq!(data.name, "test");
353 assert_eq!(data.value, 42);
354 }
355
356 #[test]
357 fn test_yaml_deserialize_safe_empty_input() {
358 let yaml = "";
359 let result: Result<TestStruct> = deserialize_yaml_safe(yaml);
360
361 assert!(result.is_err());
362 assert!(result
363 .unwrap_err()
364 .to_string()
365 .contains("YAML payload is empty"));
366 }
367
368 #[test]
369 fn test_yaml_deserialize_safe_size_limit() {
370 let large_yaml = format!("data: {}", "x".repeat(MAX_DESERIALIZE_SIZE + 1));
372
373 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&large_yaml);
374
375 assert!(result.is_err());
376 assert!(result
377 .unwrap_err()
378 .to_string()
379 .contains("YAML payload too large"));
380 }
381
382 #[test]
383 fn test_yaml_deserialize_safe_depth_limit() {
384 let mut nested_yaml = String::from("a: value");
386 for i in 0..17 {
387 nested_yaml = format!("level{}:\n {}", i, nested_yaml.replace('\n', "\n "));
388 }
389
390 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&nested_yaml);
391
392 assert!(result.is_err());
393 assert!(result.unwrap_err().to_string().contains("depth too deep"));
394 }
395
396 #[test]
397 fn test_yaml_deserialize_safe_valid_depth() {
398 let mut nested_yaml = String::from("a: value");
400 for i in 0..10 {
401 nested_yaml = format!("level{}:\n {}", i, nested_yaml.replace('\n', "\n "));
402 }
403
404 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&nested_yaml);
405
406 assert!(result.is_ok());
407 }
408
409 #[test]
410 fn test_yaml_deserialize_from_reader() {
411 let yaml = "name: test\nvalue: 42";
412 let reader = yaml.as_bytes();
413
414 let result: Result<TestStruct> = deserialize_yaml_safe_from_reader(reader);
415
416 assert!(result.is_ok());
417 let data = result.unwrap();
418 assert_eq!(data.name, "test");
419 assert_eq!(data.value, 42);
420 }
421
422 #[test]
425 fn test_calculate_json_depth_simple() {
426 let json = serde_json::json!({"a": 1});
427 assert_eq!(calculate_json_depth(&json), 2); }
429
430 #[test]
431 fn test_calculate_json_depth_nested() {
432 let json = serde_json::json!({
433 "level1": {
434 "level2": {
435 "level3": "value"
436 }
437 }
438 });
439 assert_eq!(calculate_json_depth(&json), 4); }
441
442 #[test]
443 fn test_calculate_json_depth_array() {
444 let json = serde_json::json!([1, 2, [3, 4, [5]]]);
445 assert_eq!(calculate_json_depth(&json), 4); }
447
448 #[test]
451 fn test_json_bomb_protection() {
452 let payload = r#"{"a":["x","x","x","x","x"]}"#.repeat(1000);
455
456 let result: Result<serde_json::Value> = deserialize_json_safe(&payload);
457
458 assert!(result.is_err());
460 }
461
462 #[test]
463 fn test_yaml_bomb_protection() {
464 let yaml_bomb = format!("a: &anchor {}\nb: *anchor\n", "x".repeat(1000));
466 let repeated = yaml_bomb.repeat(1000);
467
468 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&repeated);
469
470 assert!(result.is_err());
472 }
473
474 #[test]
477 fn test_json_size_exactly_at_limit() {
478 let data_size = MAX_DESERIALIZE_SIZE - 11; let json = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size));
482
483 assert_eq!(json.len(), MAX_DESERIALIZE_SIZE);
484
485 let result: Result<serde_json::Value> = deserialize_json_safe(&json);
486 assert!(result.is_ok());
487 }
488
489 #[test]
490 fn test_json_size_one_byte_over_limit() {
491 let data_size = MAX_DESERIALIZE_SIZE - 10; let json = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size));
494
495 assert_eq!(json.len(), MAX_DESERIALIZE_SIZE + 1);
496
497 let result: Result<serde_json::Value> = deserialize_json_safe(&json);
498 assert!(result.is_err());
499 assert!(result
500 .unwrap_err()
501 .to_string()
502 .contains("JSON payload too large"));
503 }
504
505 #[test]
506 fn test_json_size_one_byte_under_limit() {
507 let data_size = MAX_DESERIALIZE_SIZE - 12; let json = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size));
510
511 assert_eq!(json.len(), MAX_DESERIALIZE_SIZE - 1);
512
513 let result: Result<serde_json::Value> = deserialize_json_safe(&json);
514 assert!(result.is_ok());
515 }
516
517 #[test]
518 fn test_yaml_size_exactly_at_limit() {
519 let data_size = MAX_DESERIALIZE_SIZE - 6; let yaml = format!("data: {}", "x".repeat(data_size));
522
523 assert_eq!(yaml.len(), MAX_DESERIALIZE_SIZE);
524
525 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&yaml);
526 assert!(result.is_ok());
527 }
528
529 #[test]
530 fn test_yaml_size_one_byte_over_limit() {
531 let data_size = MAX_DESERIALIZE_SIZE - 5; let yaml = format!("data: {}", "x".repeat(data_size));
534
535 assert_eq!(yaml.len(), MAX_DESERIALIZE_SIZE + 1);
536
537 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&yaml);
538 assert!(result.is_err());
539 assert!(result
540 .unwrap_err()
541 .to_string()
542 .contains("YAML payload too large"));
543 }
544
545 #[test]
548 fn test_json_depth_exactly_16_levels() {
549 let mut nested_json = String::from(r#""value""#); for i in 0..15 {
553 nested_json = format!(r#"{{"level{}":{}}}"#, i, nested_json);
554 }
555
556 let result: Result<serde_json::Value> = deserialize_json_safe(&nested_json);
557 let value = result.expect("16 levels should be allowed");
558 assert_eq!(calculate_json_depth(&value), 16);
559 }
560
561 #[test]
562 fn test_json_depth_exactly_17_levels() {
563 let mut nested_json = String::from(r#""value""#); for i in 0..16 {
566 nested_json = format!(r#"{{"level{}":{}}}"#, i, nested_json);
567 }
568
569 let result: Result<serde_json::Value> = deserialize_json_safe(&nested_json);
570 assert!(result.is_err());
571 let err_msg = result.unwrap_err().to_string();
572 assert!(err_msg.contains("17 levels") || err_msg.contains("depth too deep"));
573 }
574
575 #[test]
576 fn test_yaml_depth_exactly_16_levels() {
577 let mut nested_yaml = String::from("value"); for i in 0..15 {
581 nested_yaml = format!("level{}:\n {}", i, nested_yaml.replace('\n', "\n "));
582 }
583
584 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&nested_yaml);
585 assert!(result.is_ok());
586 }
587
588 #[test]
589 fn test_yaml_depth_exactly_17_levels() {
590 let mut nested_yaml = String::from("value"); for i in 0..16 {
593 nested_yaml = format!("level{}:\n {}", i, nested_yaml.replace('\n', "\n "));
594 }
595
596 let result: Result<serde_yaml::Value> = deserialize_yaml_safe(&nested_yaml);
597 assert!(result.is_err());
598 assert!(result.unwrap_err().to_string().contains("depth too deep"));
599 }
600
601 #[test]
604 fn test_json_reader_size_arithmetic_plus() {
605 let data_size = MAX_DESERIALIZE_SIZE - 11; let exact_limit_json = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size));
610 let reader = exact_limit_json.as_bytes();
611
612 let result: Result<serde_json::Value> = deserialize_json_safe_from_reader(reader);
613 assert!(result.is_ok());
614 }
615
616 #[test]
617 fn test_yaml_reader_size_arithmetic_plus() {
618 let data_size = MAX_DESERIALIZE_SIZE - 6; let exact_limit_yaml = format!("data: {}", "x".repeat(data_size));
622 let reader = exact_limit_yaml.as_bytes();
623
624 let result: Result<serde_yaml::Value> = deserialize_yaml_safe_from_reader(reader);
625 assert!(result.is_ok());
626 }
627
628 #[test]
629 fn test_depth_calculation_arithmetic() {
630 let json = serde_json::json!({
633 "a": {
634 "b": {
635 "c": "value"
636 }
637 }
638 });
639
640 let depth = calculate_json_depth(&json);
641 assert_eq!(depth, 4); }
643
644 #[test]
647 fn test_json_size_comparison_greater_than() {
648 let data_size = MAX_DESERIALIZE_SIZE - 11; let json_at_limit = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size));
652 assert_eq!(json_at_limit.len(), MAX_DESERIALIZE_SIZE);
653
654 let result: Result<serde_json::Value> = deserialize_json_safe(&json_at_limit);
655 assert!(result.is_ok(), "Size at limit should succeed");
656
657 let data_size_over = MAX_DESERIALIZE_SIZE - 10;
659 let json_over_limit = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size_over));
660 assert_eq!(json_over_limit.len(), MAX_DESERIALIZE_SIZE + 1);
661
662 let result: Result<serde_json::Value> = deserialize_json_safe(&json_over_limit);
663 assert!(result.is_err(), "Size over limit should fail");
664 }
665
666 #[test]
667 fn test_json_depth_comparison_greater_than() {
668 let mut at_limit = String::from(r#"1"#); for _ in 0..15 {
672 at_limit = format!(r#"{{"n":{}}}"#, at_limit);
673 }
674
675 let result: Result<serde_json::Value> = deserialize_json_safe(&at_limit);
676 assert!(result.is_ok(), "Depth at limit should succeed");
677
678 let mut over_limit = String::from(r#"1"#); for _ in 0..16 {
681 over_limit = format!(r#"{{"n":{}}}"#, over_limit);
682 }
683
684 let result: Result<serde_json::Value> = deserialize_json_safe(&over_limit);
685 assert!(result.is_err(), "Depth over limit should fail");
686 }
687
688 #[test]
689 fn test_reader_buffer_length_comparison() {
690 let data_size = MAX_DESERIALIZE_SIZE - 11;
693 let at_limit = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size));
694 let result: Result<serde_json::Value> =
695 deserialize_json_safe_from_reader(at_limit.as_bytes());
696 assert!(result.is_ok());
697
698 let data_size_over = MAX_DESERIALIZE_SIZE - 10;
700 let over_limit = format!(r#"{{"data":"{}"}}"#, "x".repeat(data_size_over));
701 let result: Result<serde_json::Value> =
702 deserialize_json_safe_from_reader(over_limit.as_bytes());
703 assert!(result.is_err());
704 assert!(result.unwrap_err().to_string().contains("too large"));
705 }
706
707 #[test]
710 fn test_max_deserialize_size_constant_value() {
711 assert_eq!(MAX_DESERIALIZE_SIZE, 10485760); assert_ne!(MAX_DESERIALIZE_SIZE, 1048586);
717
718 assert_ne!(MAX_DESERIALIZE_SIZE, 11264);
720 }
721
722 #[test]
723 fn test_max_deserialize_size_boundary_enforcement() {
724 let exact_10mb_data = "x".repeat(MAX_DESERIALIZE_SIZE - 11);
729 let json_10mb = format!(r#"{{"data":"{}"}}"#, exact_10mb_data);
730 assert_eq!(json_10mb.len(), 10485760); let result: Result<serde_json::Value> = deserialize_json_safe(&json_10mb);
733 assert!(result.is_ok(), "10MB exact should be accepted");
734
735 let over_10mb_data = "x".repeat(MAX_DESERIALIZE_SIZE - 10);
737 let json_over_10mb = format!(r#"{{"data":"{}"}}"#, over_10mb_data);
738 assert_eq!(json_over_10mb.len(), 10485761); let result: Result<serde_json::Value> = deserialize_json_safe(&json_over_10mb);
741 assert!(result.is_err(), "10MB + 1 should be rejected");
742 }
743
744 #[test]
747 fn test_yaml_reader_take_plus_one() {
748 let data_size = MAX_DESERIALIZE_SIZE - 6;
753 let exact_limit_yaml = format!("data: {}", "x".repeat(data_size));
754 assert_eq!(exact_limit_yaml.len(), MAX_DESERIALIZE_SIZE);
755
756 let result: Result<serde_yaml::Value> =
757 deserialize_yaml_safe_from_reader(exact_limit_yaml.as_bytes());
758 assert!(
759 result.is_ok(),
760 "Exactly MAX_DESERIALIZE_SIZE should succeed"
761 );
762
763 let data_size_over = MAX_DESERIALIZE_SIZE - 5;
765 let over_limit_yaml = format!("data: {}", "x".repeat(data_size_over));
766 assert_eq!(over_limit_yaml.len(), MAX_DESERIALIZE_SIZE + 1);
767
768 let result: Result<serde_yaml::Value> =
769 deserialize_yaml_safe_from_reader(over_limit_yaml.as_bytes());
770 assert!(result.is_err(), "MAX_DESERIALIZE_SIZE + 1 should fail");
771 assert!(result.unwrap_err().to_string().contains("too large"));
772 }
773
774 #[test]
775 fn test_yaml_reader_buffer_overflow_detection() {
776 let oversized_data = "x".repeat(MAX_DESERIALIZE_SIZE + 1);
782 let reader = oversized_data.as_bytes();
783
784 let result: Result<serde_yaml::Value> = deserialize_yaml_safe_from_reader(reader);
785
786 assert!(result.is_err());
788 let err_msg = result.unwrap_err().to_string();
789 assert!(
790 err_msg.contains("too large") || err_msg.contains("Invalid UTF-8"),
791 "Should detect size overflow, got: {}",
792 err_msg
793 );
794 }
795
796 #[test]
797 #[cfg_attr(
798 all(target_os = "linux", not(target_env = "musl")),
799 ignore = "Requires >20MB memory allocation, fails in CI environments"
800 )]
801 fn test_json_reader_buffer_overflow_detection() {
802 let oversized_data = "x".repeat(MAX_DESERIALIZE_SIZE + 1);
804 let reader = oversized_data.as_bytes();
805
806 let result: Result<serde_json::Value> = deserialize_json_safe_from_reader(reader);
807
808 assert!(result.is_err());
809 let err_msg = result.unwrap_err().to_string();
810 assert!(
811 err_msg.contains("too large") || err_msg.contains("Invalid UTF-8"),
812 "Should detect size overflow, got: {}",
813 err_msg
814 );
815 }
816}