Skip to main content

shadowforge_lib/domain/archive/
mod.rs

1//! ZIP / TAR / TAR.GZ archive handling.
2//!
3//! Pure domain logic — format detection only. All I/O goes through adapters.
4
5use crate::domain::types::ArchiveFormat;
6
7/// Detect archive format by magic bytes.
8///
9/// Returns `None` if the format is unrecognised.
10#[must_use]
11pub fn detect_format(data: &[u8]) -> Option<ArchiveFormat> {
12    if data.first() == Some(&0x50) && data.get(1) == Some(&0x4B) {
13        // PK (ZIP magic)
14        Some(ArchiveFormat::Zip)
15    } else if data.first() == Some(&0x1F) && data.get(1) == Some(&0x8B) {
16        // Gzip magic → TAR.GZ
17        Some(ArchiveFormat::TarGz)
18    } else if data.len() >= 263 && data.get(257..262) == Some(b"ustar") {
19        // POSIX TAR magic at offset 257
20        Some(ArchiveFormat::Tar)
21    } else {
22        None
23    }
24}
25
26/// Maximum recursion depth for nested archive unpacking.
27pub const MAX_NESTING_DEPTH: u8 = 3;
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    type TestResult = Result<(), Box<dyn std::error::Error>>;
34
35    #[test]
36    fn detect_zip_by_magic() {
37        let data = [0x50, 0x4B, 0x03, 0x04, 0, 0, 0, 0];
38        assert_eq!(detect_format(&data), Some(ArchiveFormat::Zip));
39    }
40
41    #[test]
42    fn detect_tar_gz_by_magic() {
43        let data = [0x1F, 0x8B, 0, 0];
44        assert_eq!(detect_format(&data), Some(ArchiveFormat::TarGz));
45    }
46
47    #[test]
48    fn detect_tar_by_ustar() -> TestResult {
49        let mut data = vec![0u8; 300];
50        data.get_mut(257..262)
51            .ok_or("slice out of bounds")?
52            .copy_from_slice(b"ustar");
53        assert_eq!(detect_format(&data), Some(ArchiveFormat::Tar));
54        Ok(())
55    }
56
57    #[test]
58    fn detect_unknown_returns_none() {
59        let data = [0xFF, 0xFE, 0x00, 0x01];
60        assert_eq!(detect_format(&data), None);
61    }
62
63    #[test]
64    fn detect_empty_returns_none() {
65        assert_eq!(detect_format(&[]), None);
66    }
67}