#![cfg(feature = "lzma")]
mod common;
use std::io::Cursor;
use zesven::Archive;
use zesven::Error;
use zesven::format::streams::ResourceLimits;
#[test]
fn test_open_with_limits_accepts_default_limits() {
let entries = [("test.txt", b"hello world" as &[u8])];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::default();
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, limits).unwrap();
assert_eq!(archive.entries().len(), 1);
}
#[test]
fn test_open_with_limits_accepts_unlimited() {
let entries = [("test.txt", b"hello world" as &[u8])];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::unlimited();
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, limits).unwrap();
assert_eq!(archive.entries().len(), 1);
}
#[test]
fn test_open_with_limits_max_entries_enforced() {
let entries = [
("file1.txt", b"content1" as &[u8]),
("file2.txt", b"content2" as &[u8]),
("file3.txt", b"content3" as &[u8]),
];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::new().max_entries(1);
let cursor = Cursor::new(&archive_bytes);
let result = Archive::open_with_limits(cursor, limits);
assert!(
result.is_err(),
"Should reject archive exceeding entry limit"
);
match result {
Err(Error::ResourceLimitExceeded(msg)) => {
assert!(
msg.contains("stream") || msg.contains("entries"),
"Error should mention streams or entries: {}",
msg
);
}
Err(e) => panic!("Expected ResourceLimitExceeded, got: {:?}", e),
Ok(_) => panic!("Should have failed"),
}
}
#[test]
fn test_open_with_limits_passes_through_to_parser() {
let entries = [
("file1.txt", b"content1" as &[u8]),
("file2.txt", b"content2" as &[u8]),
];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::new().max_entries(10);
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, limits).unwrap();
assert_eq!(archive.entries().len(), 2);
}
#[test]
fn test_open_with_limits_custom_header_bytes() {
let entries = [("test.txt", b"hello" as &[u8])];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::new().max_header_bytes(64 * 1024);
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, limits).unwrap();
assert_eq!(archive.entries().len(), 1);
}
#[test]
fn test_open_preserves_default_behavior() {
let entries = [("test.txt", b"hello world" as &[u8])];
let archive_bytes = common::create_archive(&entries).unwrap();
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open(cursor).unwrap();
assert_eq!(archive.entries().len(), 1);
}
#[test]
fn test_max_entries_exact_boundary() {
let entries = [
("file1.txt", b"content1" as &[u8]),
("file2.txt", b"content2" as &[u8]),
("file3.txt", b"content3" as &[u8]),
];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::new().max_entries(2);
let cursor = Cursor::new(&archive_bytes);
let result = Archive::open_with_limits(cursor, limits);
assert!(
result.is_err(),
"Should reject when entries (3) > limit (2)"
);
let limits = ResourceLimits::new().max_entries(3);
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, limits)
.expect("Should accept when entries (3) == limit (3)");
assert_eq!(archive.entries().len(), 3);
let limits = ResourceLimits::new().max_entries(4);
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, limits)
.expect("Should accept when entries (3) < limit (4)");
assert_eq!(archive.entries().len(), 3);
}
#[test]
fn test_max_entries_single_entry_boundary() {
let entries = [("single.txt", b"content" as &[u8])];
let archive_bytes = common::create_archive(&entries).unwrap();
let limits = ResourceLimits::new().max_entries(0);
let cursor = Cursor::new(&archive_bytes);
let result = Archive::open_with_limits(cursor, limits);
assert!(
result.is_err(),
"limit=0 should reject archive with 1 entry"
);
let limits = ResourceLimits::new().max_entries(1);
let cursor = Cursor::new(&archive_bytes);
let archive =
Archive::open_with_limits(cursor, limits).expect("limit=1 should accept 1-entry archive");
assert_eq!(archive.entries().len(), 1);
}
#[cfg(feature = "lzma2")]
#[test]
fn test_ratio_limit_enforced_during_open() {
use std::io::Cursor as StdCursor;
use zesven::format::streams::RatioLimit;
use zesven::{ArchivePath, WriteOptions, Writer};
let compressible_data = vec![0u8; 100_000];
let mut archive_bytes = Vec::new();
{
let cursor = StdCursor::new(&mut archive_bytes);
let mut writer = Writer::create(cursor)
.expect("Failed to create writer")
.options(WriteOptions::new());
let path = ArchivePath::new("zeros.bin").expect("Invalid path");
writer
.add_bytes(path, &compressible_data)
.expect("Failed to add entry");
let _ = writer.finish().expect("Failed to finish archive");
}
assert!(!archive_bytes.is_empty());
let strict_limits = ResourceLimits::new().ratio_limit(Some(RatioLimit::new(10)));
let cursor = Cursor::new(&archive_bytes);
let result = Archive::open_with_limits(cursor, strict_limits);
match result {
Ok(_archive) => {
}
Err(Error::ResourceLimitExceeded(msg)) => {
assert!(
msg.contains("ratio") || msg.contains("compression"),
"Error should mention ratio/compression: {}",
msg
);
}
Err(other) => {
panic!("Unexpected error with strict ratio limit: {:?}", other);
}
}
let permissive_limits = ResourceLimits::new().ratio_limit(Some(RatioLimit::new(100_000)));
let cursor = Cursor::new(&archive_bytes);
let archive = Archive::open_with_limits(cursor, permissive_limits)
.expect("Should open with permissive ratio limit");
assert_eq!(archive.entries().len(), 1);
}
#[cfg(feature = "lzma2")]
mod extraction_enforcement_tests {
use super::*;
use std::io::Cursor as StdCursor;
use zesven::read::ExtractOptions;
use zesven::{ArchivePath, Writer};
fn create_test_archive(data: &[u8], filename: &str) -> Vec<u8> {
let mut archive_bytes = Vec::new();
{
let cursor = StdCursor::new(&mut archive_bytes);
let mut writer = Writer::create(cursor).expect("Failed to create writer");
let path = ArchivePath::new(filename).expect("Invalid path");
writer.add_bytes(path, data).expect("Failed to add entry");
let _ = writer.finish().expect("Failed to finish archive");
}
archive_bytes
}
#[test]
fn test_extraction_entry_size_limit_in_options() {
let data = vec![0x42u8; 10_000];
let archive_bytes = create_test_archive(&data, "large.bin");
let cursor = Cursor::new(&archive_bytes);
let mut archive = Archive::open(cursor).expect("Should open");
let limits = ResourceLimits::new().max_entry_unpacked(5_000);
let options = ExtractOptions::new().limits(limits);
let temp_dir = tempfile::tempdir().unwrap();
let result = archive.extract(temp_dir.path(), (), &options);
let extract_result = result.expect("Batch extraction should not return Err");
assert_eq!(
extract_result.entries_extracted, 0,
"Entry exceeding limit should not be extracted"
);
assert_eq!(
extract_result.entries_failed, 1,
"Entry exceeding limit should be recorded as failed"
);
assert!(
extract_result
.failures
.iter()
.any(|(_, msg)| msg.contains("exceeds limit")),
"Failure message should indicate limit exceeded: {:?}",
extract_result.failures
);
}
#[test]
fn test_extraction_total_size_limit_in_options() {
let entries = [
("file1.bin", vec![0x41u8; 10_000]),
("file2.bin", vec![0x42u8; 10_000]),
("file3.bin", vec![0x43u8; 10_000]),
];
let mut archive_bytes = Vec::new();
{
let cursor = StdCursor::new(&mut archive_bytes);
let mut writer = Writer::create(cursor).expect("Failed to create writer");
for (name, data) in &entries {
let path = ArchivePath::new(name).expect("Invalid path");
writer.add_bytes(path, data).expect("Failed to add entry");
}
let _ = writer.finish().expect("Failed to finish archive");
}
let cursor = Cursor::new(&archive_bytes);
let mut archive = Archive::open(cursor).expect("Should open");
let limits = ResourceLimits::new().max_total_unpacked(20_000);
let options = ExtractOptions::new().limits(limits);
let temp_dir = tempfile::tempdir().unwrap();
let result = archive.extract(temp_dir.path(), (), &options);
let extract_result = result.expect("Batch extraction should not return Err");
assert!(
extract_result.entries_extracted >= 1 && extract_result.entries_extracted <= 2,
"Expected 1-2 entries extracted before hitting total limit, got {}",
extract_result.entries_extracted
);
assert!(
extract_result.entries_failed >= 1,
"At least one entry should fail due to total limit exceeded"
);
assert!(
extract_result
.failures
.iter()
.any(|(_, msg)| msg.contains("exceeds limit")),
"Failure message should indicate limit exceeded: {:?}",
extract_result.failures
);
}
#[test]
fn test_extraction_ratio_limit_in_options() {
use zesven::format::streams::RatioLimit;
let compressible_data = vec![0u8; 50_000];
let archive_bytes = create_test_archive(&compressible_data, "zeros.bin");
let cursor = Cursor::new(&archive_bytes);
let mut archive = Archive::open(cursor).expect("Should open");
let limits = ResourceLimits::new().ratio_limit(Some(RatioLimit::new(5)));
let options = ExtractOptions::new().limits(limits);
let temp_dir = tempfile::tempdir().unwrap();
let result = archive.extract(temp_dir.path(), (), &options);
let extract_result = result.expect("Batch extraction should not return Err");
assert_eq!(
extract_result.entries_extracted, 0,
"Entry exceeding ratio limit should not be extracted"
);
assert_eq!(
extract_result.entries_failed, 1,
"Entry exceeding ratio limit should be recorded as failed"
);
assert!(
extract_result
.failures
.iter()
.any(|(_, msg)| { msg.contains("ratio") || msg.contains("Compression") }),
"Failure message should indicate ratio exceeded: {:?}",
extract_result.failures
);
}
#[test]
fn test_open_limits_vs_extract_limits() {
let data = vec![0x42u8; 10_000];
let archive_bytes = create_test_archive(&data, "large.bin");
let open_limits = ResourceLimits::new().max_entry_unpacked(1_000);
let cursor = Cursor::new(&archive_bytes);
let mut archive = Archive::open_with_limits(cursor, open_limits).expect("Should open");
let result = archive.extract_to_vec("large.bin");
assert!(
result.is_ok(),
"extract_to_vec ignores open_with_limits extraction settings"
);
assert_eq!(result.unwrap().len(), 10_000);
}
}