1use crate::{Error, Result};
4use std::io::Read;
5use thiserror::Error;
6
7#[derive(Error, Debug, Clone)]
9pub enum CompressionBombError {
10 #[error("Compression ratio exceeded: {ratio:.2}x > {max_ratio:.2}x")]
11 RatioExceeded { ratio: f64, max_ratio: f64 },
12
13 #[error("Decompressed size exceeded: {size} bytes > {max_size} bytes")]
14 SizeExceeded { size: usize, max_size: usize },
15
16 #[error("Compression depth exceeded: {depth} > {max_depth}")]
17 DepthExceeded { depth: usize, max_depth: usize },
18}
19
20#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
27pub struct CompressionBombConfig {
28 pub max_ratio: f64,
31 pub max_decompressed_size: usize,
33 pub max_compressed_size: usize,
37 pub max_compression_depth: usize,
39 pub check_interval_bytes: usize,
41}
42
43impl Default for CompressionBombConfig {
44 fn default() -> Self {
45 Self {
46 max_ratio: 300.0,
49 max_decompressed_size: 100 * 1024 * 1024, max_compressed_size: 100 * 1024 * 1024, max_compression_depth: 3,
52 check_interval_bytes: 64 * 1024, }
54 }
55}
56
57impl CompressionBombConfig {
58 pub fn high_security() -> Self {
60 Self {
61 max_ratio: 20.0,
62 max_decompressed_size: 10 * 1024 * 1024, max_compressed_size: 10 * 1024 * 1024, max_compression_depth: 2,
65 check_interval_bytes: 32 * 1024, }
67 }
68
69 pub fn low_memory() -> Self {
71 Self {
72 max_ratio: 50.0,
73 max_decompressed_size: 5 * 1024 * 1024, max_compressed_size: 5 * 1024 * 1024, max_compression_depth: 2,
76 check_interval_bytes: 16 * 1024, }
78 }
79
80 pub fn high_throughput() -> Self {
82 Self {
83 max_ratio: 1000.0,
84 max_decompressed_size: 500 * 1024 * 1024, max_compressed_size: 500 * 1024 * 1024, max_compression_depth: 5,
87 check_interval_bytes: 128 * 1024, }
89 }
90}
91
92#[derive(Debug)]
94pub struct CompressionBombProtector<R: Read> {
95 inner: R,
96 config: CompressionBombConfig,
97 compressed_size: usize,
98 decompressed_size: usize,
99 bytes_since_check: usize,
100 compression_depth: usize,
101}
102
103impl<R: Read> CompressionBombProtector<R> {
104 pub fn new(inner: R, config: CompressionBombConfig, compressed_size: usize) -> Self {
106 Self {
107 inner,
108 config,
109 compressed_size,
110 decompressed_size: 0,
111 bytes_since_check: 0,
112 compression_depth: 0,
113 }
114 }
115
116 pub fn with_depth(
118 inner: R,
119 config: CompressionBombConfig,
120 compressed_size: usize,
121 depth: usize,
122 ) -> Result<Self> {
123 if depth > config.max_compression_depth {
124 return Err(Error::SecurityError(
125 CompressionBombError::DepthExceeded {
126 depth,
127 max_depth: config.max_compression_depth,
128 }
129 .to_string(),
130 ));
131 }
132
133 Ok(Self {
134 inner,
135 config,
136 compressed_size,
137 decompressed_size: 0,
138 bytes_since_check: 0,
139 compression_depth: depth,
140 })
141 }
142
143 fn check_limits(&self) -> Result<()> {
145 if self.decompressed_size > self.config.max_decompressed_size {
147 return Err(Error::SecurityError(
148 CompressionBombError::SizeExceeded {
149 size: self.decompressed_size,
150 max_size: self.config.max_decompressed_size,
151 }
152 .to_string(),
153 ));
154 }
155
156 if self.compressed_size > 0 && self.decompressed_size > 0 {
158 let ratio = self.decompressed_size as f64 / self.compressed_size as f64;
159 if ratio > self.config.max_ratio {
160 return Err(Error::SecurityError(
161 CompressionBombError::RatioExceeded {
162 ratio,
163 max_ratio: self.config.max_ratio,
164 }
165 .to_string(),
166 ));
167 }
168 }
169
170 Ok(())
171 }
172
173 pub fn stats(&self) -> CompressionStats {
175 let ratio = if self.compressed_size > 0 {
176 self.decompressed_size as f64 / self.compressed_size as f64
177 } else {
178 0.0
179 };
180
181 CompressionStats {
182 compressed_size: self.compressed_size,
183 decompressed_size: self.decompressed_size,
184 ratio,
185 compression_depth: self.compression_depth,
186 }
187 }
188
189 pub fn into_inner(self) -> R {
191 self.inner
192 }
193}
194
195impl<R: Read> Read for CompressionBombProtector<R> {
196 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
197 let bytes_read = self.inner.read(buf)?;
198
199 self.decompressed_size += bytes_read;
200 self.bytes_since_check += bytes_read;
201
202 if self.bytes_since_check >= self.config.check_interval_bytes {
204 if let Err(e) = self.check_limits() {
205 return Err(std::io::Error::new(
206 std::io::ErrorKind::InvalidData,
207 e.to_string(),
208 ));
209 }
210 self.bytes_since_check = 0;
211 }
212
213 Ok(bytes_read)
214 }
215}
216
217#[derive(Debug, Clone)]
219pub struct CompressionStats {
220 pub compressed_size: usize,
221 pub decompressed_size: usize,
222 pub ratio: f64,
223 pub compression_depth: usize,
224}
225
226pub struct CompressionBombDetector {
228 config: CompressionBombConfig,
229}
230
231impl Default for CompressionBombDetector {
232 fn default() -> Self {
233 Self::new(CompressionBombConfig::default())
234 }
235}
236
237impl CompressionBombDetector {
238 pub fn new(config: CompressionBombConfig) -> Self {
240 Self { config }
241 }
242
243 pub fn validate_pre_decompression(&self, compressed_size: usize) -> Result<()> {
250 if compressed_size > self.config.max_compressed_size {
251 return Err(Error::SecurityError(format!(
252 "Compressed data size {} exceeds maximum allowed compressed size {}",
253 compressed_size, self.config.max_compressed_size
254 )));
255 }
256 Ok(())
257 }
258
259 pub fn protect_reader<R: Read>(
261 &self,
262 reader: R,
263 compressed_size: usize,
264 ) -> CompressionBombProtector<R> {
265 CompressionBombProtector::new(reader, self.config.clone(), compressed_size)
266 }
267
268 pub fn protect_nested_reader<R: Read>(
270 &self,
271 reader: R,
272 compressed_size: usize,
273 depth: usize,
274 ) -> Result<CompressionBombProtector<R>> {
275 CompressionBombProtector::with_depth(reader, self.config.clone(), compressed_size, depth)
276 }
277
278 pub fn validate_result(&self, compressed_size: usize, decompressed_size: usize) -> Result<()> {
280 if decompressed_size > self.config.max_decompressed_size {
281 return Err(Error::SecurityError(
282 CompressionBombError::SizeExceeded {
283 size: decompressed_size,
284 max_size: self.config.max_decompressed_size,
285 }
286 .to_string(),
287 ));
288 }
289
290 if compressed_size > 0 {
291 let ratio = decompressed_size as f64 / compressed_size as f64;
292 if ratio > self.config.max_ratio {
293 return Err(Error::SecurityError(
294 CompressionBombError::RatioExceeded {
295 ratio,
296 max_ratio: self.config.max_ratio,
297 }
298 .to_string(),
299 ));
300 }
301 }
302
303 Ok(())
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use std::io::Cursor;
311
312 #[test]
313 fn test_compression_bomb_config() {
314 let config = CompressionBombConfig::default();
315 assert!(config.max_ratio > 0.0);
316 assert!(config.max_decompressed_size > 0);
317
318 let high_sec = CompressionBombConfig::high_security();
319 assert!(high_sec.max_ratio < config.max_ratio);
320
321 let low_mem = CompressionBombConfig::low_memory();
322 assert!(low_mem.max_decompressed_size < config.max_decompressed_size);
323
324 let high_throughput = CompressionBombConfig::high_throughput();
325 assert!(high_throughput.max_decompressed_size > config.max_decompressed_size);
326 }
327
328 #[test]
329 fn test_compression_bomb_detector() {
330 let detector = CompressionBombDetector::default();
331
332 assert!(detector.validate_pre_decompression(1024).is_ok());
334 assert!(detector.validate_result(1024, 10 * 1024).is_ok());
335 }
336
337 #[test]
338 fn test_size_limit_exceeded() {
339 let config = CompressionBombConfig {
340 max_decompressed_size: 1024,
341 ..Default::default()
342 };
343 let detector = CompressionBombDetector::new(config);
344
345 let result = detector.validate_result(100, 2048);
347 assert!(result.is_err());
348 let error_msg = result.unwrap_err().to_string();
349 assert!(error_msg.contains("Size exceeded") || error_msg.contains("Security error"));
350 }
351
352 #[test]
353 fn test_ratio_limit_exceeded() {
354 let config = CompressionBombConfig {
355 max_ratio: 10.0,
356 ..Default::default()
357 };
358 let detector = CompressionBombDetector::new(config);
359
360 let result = detector.validate_result(100, 2000);
362 assert!(result.is_err());
363 assert!(
364 result
365 .unwrap_err()
366 .to_string()
367 .contains("Compression ratio exceeded")
368 );
369 }
370
371 #[test]
372 fn test_protected_reader() {
373 let data = b"Hello, world! This is test data for compression testing.";
374 let cursor = Cursor::new(data.as_slice());
375
376 let config = CompressionBombConfig::default();
377 let mut protector = CompressionBombProtector::new(cursor, config, data.len());
378
379 let mut buffer = Vec::new();
380 let bytes_read = protector.read_to_end(&mut buffer).unwrap();
381
382 assert_eq!(bytes_read, data.len());
383 assert_eq!(buffer.as_slice(), data);
384
385 let stats = protector.stats();
386 assert_eq!(stats.compressed_size, data.len());
387 assert_eq!(stats.decompressed_size, data.len());
388 assert!((stats.ratio - 1.0).abs() < 0.01); }
390
391 #[test]
392 fn test_protected_reader_size_limit() {
393 let data = vec![0u8; 2048]; let cursor = Cursor::new(data);
395
396 let config = CompressionBombConfig {
397 max_decompressed_size: 1024, check_interval_bytes: 512, ..Default::default()
400 };
401
402 let mut protector = CompressionBombProtector::new(cursor, config, 100); let mut buffer = vec![0u8; 2048];
405 let result = protector.read(&mut buffer);
406
407 if result.is_ok() {
409 let result2 = protector.read(&mut buffer[512..]);
411 assert!(result2.is_err());
412 } else {
413 assert!(result.is_err());
415 }
416 }
417
418 #[test]
419 fn test_compression_depth_limit() {
420 let data = b"test data";
421 let cursor = Cursor::new(data.as_slice());
422
423 let config = CompressionBombConfig {
424 max_compression_depth: 2,
425 ..Default::default()
426 };
427
428 let protector = CompressionBombProtector::with_depth(cursor, config.clone(), data.len(), 2);
430 assert!(protector.is_ok());
431
432 let cursor2 = Cursor::new(data.as_slice());
434 let result = CompressionBombProtector::with_depth(cursor2, config, data.len(), 3);
435 assert!(result.is_err());
436 }
437
438 #[test]
439 fn test_zero_compressed_size_handling() {
440 let detector = CompressionBombDetector::default();
441
442 assert!(detector.validate_result(0, 1024).is_ok());
444 }
445
446 #[test]
447 fn test_stats_calculation() {
448 let data = b"test";
449 let cursor = Cursor::new(data.as_slice());
450
451 let protector = CompressionBombProtector::new(cursor, CompressionBombConfig::default(), 2);
452 let stats = protector.stats();
453
454 assert_eq!(stats.compressed_size, 2);
455 assert_eq!(stats.decompressed_size, 0); assert_eq!(stats.ratio, 0.0);
457 assert_eq!(stats.compression_depth, 0);
458 }
459
460 #[test]
461 fn test_stats_with_zero_compressed_size() {
462 let data = b"test";
463 let cursor = Cursor::new(data.as_slice());
464
465 let protector = CompressionBombProtector::new(cursor, CompressionBombConfig::default(), 0);
467 let stats = protector.stats();
468
469 assert_eq!(stats.compressed_size, 0);
470 assert_eq!(stats.ratio, 0.0); }
472
473 #[test]
474 fn test_into_inner() {
475 let data = b"test data";
476 let cursor = Cursor::new(data.as_slice());
477 let original_position = cursor.position();
478
479 let protector =
480 CompressionBombProtector::new(cursor, CompressionBombConfig::default(), data.len());
481
482 let inner = protector.into_inner();
484 assert_eq!(inner.position(), original_position);
485 }
486
487 #[test]
488 fn test_protect_nested_reader_success() {
489 let detector = CompressionBombDetector::new(CompressionBombConfig {
490 max_compression_depth: 3,
491 ..Default::default()
492 });
493
494 let data = b"nested compression test";
495 let cursor = Cursor::new(data.as_slice());
496
497 let result = detector.protect_nested_reader(cursor, data.len(), 1);
499 assert!(result.is_ok());
500
501 let protector = result.unwrap();
502 let stats = protector.stats();
503 assert_eq!(stats.compression_depth, 1);
504 }
505
506 #[test]
507 fn test_protect_nested_reader_depth_exceeded() {
508 let detector = CompressionBombDetector::new(CompressionBombConfig {
509 max_compression_depth: 2,
510 ..Default::default()
511 });
512
513 let data = b"nested compression test";
514 let cursor = Cursor::new(data.as_slice());
515
516 let result = detector.protect_nested_reader(cursor, data.len(), 3);
518 assert!(result.is_err());
519
520 let error_msg = result.unwrap_err().to_string();
521 assert!(
522 error_msg.contains("Compression depth exceeded")
523 || error_msg.contains("Security error")
524 );
525 }
526
527 #[test]
528 fn test_validate_pre_decompression_size_exceeded() {
529 let config = CompressionBombConfig {
530 max_compressed_size: 1024,
531 ..Default::default()
532 };
533 let detector = CompressionBombDetector::new(config);
534
535 let result = detector.validate_pre_decompression(2048);
537 assert!(result.is_err());
538
539 let error_msg = result.unwrap_err().to_string();
540 assert!(error_msg.contains("exceeds maximum allowed"));
541 }
542
543 #[test]
544 fn test_validate_pre_decompression_success() {
545 let detector = CompressionBombDetector::default();
546
547 let result = detector.validate_pre_decompression(1024);
549 assert!(result.is_ok());
550 }
551
552 #[test]
553 fn test_protected_reader_stats_after_read() {
554 let data = b"Hello, world!";
555 let cursor = Cursor::new(data.as_slice());
556
557 let compressed_size = 5; let mut protector = CompressionBombProtector::new(
559 cursor,
560 CompressionBombConfig::default(),
561 compressed_size,
562 );
563
564 let mut buffer = Vec::new();
565 protector.read_to_end(&mut buffer).unwrap();
566
567 let stats = protector.stats();
568 assert_eq!(stats.compressed_size, compressed_size);
569 assert_eq!(stats.decompressed_size, data.len());
570
571 let expected_ratio = data.len() as f64 / compressed_size as f64;
572 assert!((stats.ratio - expected_ratio).abs() < 0.01);
573 }
574
575 #[test]
576 fn test_compression_bomb_error_display() {
577 let ratio_err = CompressionBombError::RatioExceeded {
578 ratio: 150.5,
579 max_ratio: 100.0,
580 };
581 assert!(ratio_err.to_string().contains("150.5"));
582 assert!(ratio_err.to_string().contains("100.0"));
583
584 let size_err = CompressionBombError::SizeExceeded {
585 size: 2048,
586 max_size: 1024,
587 };
588 assert!(size_err.to_string().contains("2048"));
589 assert!(size_err.to_string().contains("1024"));
590
591 let depth_err = CompressionBombError::DepthExceeded {
592 depth: 5,
593 max_depth: 3,
594 };
595 assert!(depth_err.to_string().contains("5"));
596 assert!(depth_err.to_string().contains("3"));
597 }
598
599 #[test]
600 fn test_detector_default() {
601 let detector1 = CompressionBombDetector::default();
602 let detector2 = CompressionBombDetector::new(CompressionBombConfig::default());
603
604 assert_eq!(detector1.config.max_ratio, detector2.config.max_ratio);
606 assert_eq!(
607 detector1.config.max_decompressed_size,
608 detector2.config.max_decompressed_size
609 );
610 }
611
612 #[test]
613 fn test_slow_drip_decompression_bomb() {
614 let config = CompressionBombConfig {
616 max_decompressed_size: 10_000,
617 check_interval_bytes: 1000, ..Default::default()
619 };
620
621 let data = vec![0u8; 15_000];
623 let cursor = Cursor::new(data);
624
625 let mut protector = CompressionBombProtector::new(cursor, config, 100);
626
627 let mut buffer = [0u8; 1024];
628 let mut total_read = 0;
629 let mut detected = false;
630
631 loop {
633 match protector.read(&mut buffer) {
634 Ok(0) => break, Ok(n) => {
636 total_read += n;
637 }
638 Err(e) => {
639 let err_str = e.to_string();
642 assert!(
643 err_str.contains("Size exceeded") || err_str.contains("Security"),
644 "Expected size limit error, got: {}",
645 err_str
646 );
647 detected = true;
648 break;
649 }
650 }
651 }
652
653 assert!(detected, "Slow-drip bomb should be detected");
654 assert!(total_read < 15_000, "Should not read all data");
655 }
656
657 #[test]
658 fn test_integer_overflow_protection_in_ratio() {
659 let detector = CompressionBombDetector::default();
660
661 let result = detector.validate_result(1, usize::MAX);
663 assert!(result.is_err());
664 }
665
666 #[test]
667 fn test_integer_overflow_protection_in_size() {
668 let config = CompressionBombConfig {
669 max_decompressed_size: usize::MAX - 1,
670 ..Default::default()
671 };
672 let detector = CompressionBombDetector::new(config);
673
674 let result = detector.validate_result(100, usize::MAX);
676 assert!(result.is_err());
677 }
678
679 #[test]
680 fn test_boundary_max_decompressed_size() {
681 let max_size = 10_000;
682 let config = CompressionBombConfig {
683 max_decompressed_size: max_size,
684 ..Default::default()
685 };
686 let detector = CompressionBombDetector::new(config);
687
688 assert!(detector.validate_result(100, max_size).is_ok());
690
691 assert!(detector.validate_result(100, max_size + 1).is_err());
693 }
694
695 #[test]
696 fn test_boundary_max_ratio() {
697 let max_ratio = 50.0;
698 let config = CompressionBombConfig {
699 max_ratio,
700 ..Default::default()
701 };
702 let detector = CompressionBombDetector::new(config);
703
704 let compressed = 100;
705 let at_limit = (compressed as f64 * max_ratio) as usize;
706
707 assert!(detector.validate_result(compressed, at_limit).is_ok());
709
710 assert!(
712 detector
713 .validate_result(compressed, at_limit + 100)
714 .is_err()
715 );
716 }
717
718 #[test]
719 fn test_boundary_max_compression_depth() {
720 let max_depth = 5;
721 let config = CompressionBombConfig {
722 max_compression_depth: max_depth,
723 ..Default::default()
724 };
725
726 let data = b"test";
727 let cursor = Cursor::new(data.as_slice());
728
729 let result =
731 CompressionBombProtector::with_depth(cursor, config.clone(), data.len(), max_depth);
732 assert!(result.is_ok());
733
734 let cursor2 = Cursor::new(data.as_slice());
736 let result2 =
737 CompressionBombProtector::with_depth(cursor2, config, data.len(), max_depth + 1);
738 assert!(result2.is_err());
739 }
740
741 #[test]
742 fn test_nested_compression_attack_simulation() {
743 let detector = CompressionBombDetector::new(CompressionBombConfig {
745 max_compression_depth: 2,
746 max_decompressed_size: 10_000,
747 ..Default::default()
748 });
749
750 let layer1_data = vec![0u8; 1000]; let cursor1 = Cursor::new(layer1_data.clone());
753
754 let protector1 = detector.protect_nested_reader(cursor1, 100, 1);
755 assert!(protector1.is_ok());
756
757 let cursor2 = Cursor::new(layer1_data.clone());
759 let protector2 = detector.protect_nested_reader(cursor2, 100, 2);
760 assert!(protector2.is_ok());
761
762 let cursor3 = Cursor::new(layer1_data);
764 let protector3 = detector.protect_nested_reader(cursor3, 100, 3);
765 assert!(protector3.is_err());
766 }
767
768 #[test]
769 fn test_check_limits_called_at_intervals() {
770 let check_interval = 100;
771 let config = CompressionBombConfig {
772 max_decompressed_size: 500,
773 check_interval_bytes: check_interval,
774 max_ratio: 10.0,
775 ..Default::default()
776 };
777
778 let data = vec![0u8; 600];
780 let cursor = Cursor::new(data);
781
782 let mut protector = CompressionBombProtector::new(cursor, config, 10); let mut buffer = [0u8; 50]; let mut total_read = 0;
786 let mut error_occurred = false;
787
788 loop {
789 match protector.read(&mut buffer) {
790 Ok(0) => break,
791 Ok(n) => {
792 total_read += n;
793 if total_read > 500 {
795 break;
797 }
798 }
799 Err(_) => {
800 error_occurred = true;
801 break;
802 }
803 }
804 }
805
806 assert!(error_occurred, "Should detect bomb during periodic checks");
807 }
808
809 #[test]
810 fn test_ratio_calculation_with_large_numbers() {
811 let detector = CompressionBombDetector::new(CompressionBombConfig {
812 max_ratio: 100.0,
813 ..Default::default()
814 });
815
816 let compressed = 1_000_000;
818 let decompressed = 50_000_000; assert!(detector.validate_result(compressed, decompressed).is_ok());
821
822 let decompressed_bad = 150_000_000;
824 assert!(
825 detector
826 .validate_result(compressed, decompressed_bad)
827 .is_err()
828 );
829 }
830
831 #[test]
832 fn test_protected_reader_multiple_small_reads() {
833 let data = vec![1u8; 5000];
835 let cursor = Cursor::new(data);
836
837 let config = CompressionBombConfig {
838 max_decompressed_size: 10_000,
839 check_interval_bytes: 1000,
840 ..Default::default()
841 };
842
843 let mut protector = CompressionBombProtector::new(cursor, config, 5000);
844
845 let mut buffer = [0u8; 10];
847 let mut total = 0;
848
849 while let Ok(n) = protector.read(&mut buffer) {
850 if n == 0 {
851 break;
852 }
853 total += n;
854 }
855
856 assert_eq!(total, 5000);
857 let stats = protector.stats();
858 assert_eq!(stats.decompressed_size, 5000);
859 }
860
861 #[test]
862 fn test_error_on_exact_check_interval_boundary() {
863 let check_interval = 1000;
864 let config = CompressionBombConfig {
865 max_decompressed_size: 1500,
866 check_interval_bytes: check_interval,
867 ..Default::default()
868 };
869
870 let data = vec![0u8; 2000];
872 let cursor = Cursor::new(data);
873
874 let mut protector = CompressionBombProtector::new(cursor, config, 100);
875
876 let mut buffer = [0u8; 1000]; let mut detected = false;
878
879 loop {
880 match protector.read(&mut buffer) {
881 Ok(0) => break,
882 Ok(_) => {}
883 Err(_) => {
884 detected = true;
885 break;
886 }
887 }
888 }
889
890 assert!(detected);
891 }
892
893 #[test]
894 fn test_config_serialization_roundtrip() {
895 let config = CompressionBombConfig {
896 max_ratio: 123.45,
897 max_decompressed_size: 999_888,
898 max_compressed_size: 512_000,
899 max_compression_depth: 7,
900 check_interval_bytes: 16_384,
901 };
902
903 let json = serde_json::to_string(&config).unwrap();
905
906 let deserialized: CompressionBombConfig = serde_json::from_str(&json).unwrap();
908
909 assert_eq!(config.max_ratio, deserialized.max_ratio);
910 assert_eq!(
911 config.max_decompressed_size,
912 deserialized.max_decompressed_size
913 );
914 assert_eq!(
915 config.max_compression_depth,
916 deserialized.max_compression_depth
917 );
918 assert_eq!(
919 config.check_interval_bytes,
920 deserialized.check_interval_bytes
921 );
922 }
923
924 #[test]
925 fn test_all_preset_configs() {
926 let default_cfg = CompressionBombConfig::default();
928 let high_sec = CompressionBombConfig::high_security();
929 let low_mem = CompressionBombConfig::low_memory();
930 let high_throughput = CompressionBombConfig::high_throughput();
931
932 assert!(high_sec.max_ratio < default_cfg.max_ratio);
934 assert!(high_sec.max_decompressed_size < default_cfg.max_decompressed_size);
935
936 assert!(low_mem.max_decompressed_size < default_cfg.max_decompressed_size);
938
939 assert!(high_throughput.max_ratio > default_cfg.max_ratio);
941 assert!(high_throughput.max_decompressed_size > default_cfg.max_decompressed_size);
942 }
943
944 #[test]
945 fn test_protect_reader_basic_usage() {
946 let detector = CompressionBombDetector::default();
947 let data = b"test data for protect_reader";
948 let cursor = Cursor::new(data.as_slice());
949
950 let mut protector = detector.protect_reader(cursor, data.len());
951
952 let mut buffer = Vec::new();
953 let bytes_read = protector.read_to_end(&mut buffer).unwrap();
954
955 assert_eq!(bytes_read, data.len());
956 assert_eq!(buffer.as_slice(), data);
957
958 let stats = protector.stats();
959 assert_eq!(stats.compressed_size, data.len());
960 assert_eq!(stats.decompressed_size, data.len());
961 }
962
963 #[test]
964 fn test_protect_reader_with_size_limit() {
965 let config = CompressionBombConfig {
966 max_decompressed_size: 500,
967 check_interval_bytes: 100,
968 ..Default::default()
969 };
970 let detector = CompressionBombDetector::new(config);
971
972 let data = vec![0u8; 1000];
973 let cursor = Cursor::new(data);
974
975 let mut protector = detector.protect_reader(cursor, 50);
976
977 let mut buffer = [0u8; 200];
978 let mut error_occurred = false;
979
980 loop {
981 match protector.read(&mut buffer) {
982 Ok(0) => break,
983 Ok(_) => {}
984 Err(_) => {
985 error_occurred = true;
986 break;
987 }
988 }
989 }
990
991 assert!(error_occurred, "protect_reader should detect size limit");
992 }
993
994 struct FailingReader {
995 fail_after: usize,
996 bytes_read: usize,
997 }
998
999 impl FailingReader {
1000 fn new(fail_after: usize) -> Self {
1001 Self {
1002 fail_after,
1003 bytes_read: 0,
1004 }
1005 }
1006 }
1007
1008 impl Read for FailingReader {
1009 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1010 if self.bytes_read >= self.fail_after {
1011 return Err(std::io::Error::new(
1012 std::io::ErrorKind::BrokenPipe,
1013 "simulated read failure",
1014 ));
1015 }
1016 let to_read = std::cmp::min(buf.len(), self.fail_after - self.bytes_read);
1017 for b in buf.iter_mut().take(to_read) {
1018 *b = 0;
1019 }
1020 self.bytes_read += to_read;
1021 Ok(to_read)
1022 }
1023 }
1024
1025 #[test]
1026 fn test_inner_reader_error_propagation() {
1027 let failing_reader = FailingReader::new(50);
1028 let config = CompressionBombConfig::default();
1029 let mut protector = CompressionBombProtector::new(failing_reader, config, 100);
1030
1031 let mut buffer = [0u8; 100];
1032
1033 let result1 = protector.read(&mut buffer);
1034 assert!(result1.is_ok());
1035 assert_eq!(result1.unwrap(), 50);
1036
1037 let result2 = protector.read(&mut buffer);
1038 assert!(result2.is_err());
1039 let err = result2.unwrap_err();
1040 assert_eq!(err.kind(), std::io::ErrorKind::BrokenPipe);
1041 assert!(err.to_string().contains("simulated read failure"));
1042 }
1043
1044 #[test]
1045 fn test_check_limits_with_zero_compressed_size_and_data_read() {
1046 let config = CompressionBombConfig {
1047 max_decompressed_size: 1000,
1048 check_interval_bytes: 50,
1049 ..Default::default()
1050 };
1051
1052 let data = vec![0u8; 100];
1053 let cursor = Cursor::new(data);
1054
1055 let mut protector = CompressionBombProtector::new(cursor, config, 0);
1056
1057 let mut buffer = [0u8; 60];
1058
1059 let result = protector.read(&mut buffer);
1060 assert!(result.is_ok());
1061 assert_eq!(result.unwrap(), 60);
1062
1063 let stats = protector.stats();
1064 assert_eq!(stats.compressed_size, 0);
1065 assert_eq!(stats.decompressed_size, 60);
1066 assert_eq!(stats.ratio, 0.0);
1067 }
1068
1069 #[test]
1070 fn test_check_limits_ratio_ok_branch() {
1071 let config = CompressionBombConfig {
1072 max_ratio: 100.0,
1073 max_decompressed_size: 10_000,
1074 check_interval_bytes: 50,
1075 ..Default::default()
1076 };
1077
1078 let data = vec![0u8; 100];
1079 let cursor = Cursor::new(data);
1080
1081 let mut protector = CompressionBombProtector::new(cursor, config, 50);
1082
1083 let mut buffer = [0u8; 60];
1084
1085 let result = protector.read(&mut buffer);
1086 assert!(result.is_ok());
1087 assert_eq!(result.unwrap(), 60);
1088
1089 let stats = protector.stats();
1090 assert_eq!(stats.decompressed_size, 60);
1091 assert!((stats.ratio - 1.2).abs() < 0.01);
1092 }
1093}