lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0

//! Archive type detection

use super::types::ArchiveType;

/// Detect archive type from file path (by extension)
pub fn detect_archive_type(path: &str) -> Option<ArchiveType> {
    let lower = path.to_lowercase();

    if lower.ends_with(".zip") {
        Some(ArchiveType::Zip)
    } else if lower.ends_with(".tar.gz") || lower.ends_with(".tgz") {
        Some(ArchiveType::TarGz)
    } else if lower.ends_with(".tar.bz2") || lower.ends_with(".tbz2") {
        Some(ArchiveType::TarBz2)
    } else if lower.ends_with(".tar.xz") || lower.ends_with(".txz") {
        Some(ArchiveType::TarXz)
    } else if lower.ends_with(".tar") {
        Some(ArchiveType::Tar)
    } else if lower.ends_with(".7z") {
        Some(ArchiveType::SevenZip)
    } else if lower.ends_with(".rar") {
        Some(ArchiveType::Rar)
    } else {
        None
    }
}

/// Detect archive type from magic bytes
pub fn detect_by_magic(data: &[u8]) -> Option<ArchiveType> {
    if data.len() < 8 {
        return None;
    }

    match &data[..4] {
        // ZIP: PK\x03\x04
        [0x50, 0x4B, 0x03, 0x04] => Some(ArchiveType::Zip),
        // GZIP: \x1f\x8b
        [0x1F, 0x8B, _, _] => Some(ArchiveType::TarGz),
        // BZ2: BZ
        [0x42, 0x5A, _, _] => Some(ArchiveType::TarBz2),
        // XZ: \xfd7zXZ
        [0xFD, 0x37, 0x7A, 0x58] => Some(ArchiveType::TarXz),
        // 7z: 7z\xbc\xaf\x27\x1c
        [0x37, 0x7A, 0xBC, 0xAF] => Some(ArchiveType::SevenZip),
        // RAR: Rar!
        [0x52, 0x61, 0x72, 0x21] => Some(ArchiveType::Rar),
        _ => {
            // TAR: check for ustar at offset 257
            if data.len() >= 263 && &data[257..262] == b"ustar" {
                Some(ArchiveType::Tar)
            } else {
                None
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_detect_by_extension() {
        assert_eq!(detect_archive_type("file.zip"), Some(ArchiveType::Zip));
        assert_eq!(detect_archive_type("file.tar.gz"), Some(ArchiveType::TarGz));
        assert_eq!(detect_archive_type("file.tgz"), Some(ArchiveType::TarGz));
        assert_eq!(detect_archive_type("file.tar"), Some(ArchiveType::Tar));
        assert_eq!(detect_archive_type("file.7z"), Some(ArchiveType::SevenZip));
        assert_eq!(detect_archive_type("file.txt"), None);
    }

    #[test]
    fn test_detect_by_magic() {
        assert_eq!(
            detect_by_magic(&[0x50, 0x4B, 0x03, 0x04, 0, 0, 0, 0]),
            Some(ArchiveType::Zip)
        );
        assert_eq!(
            detect_by_magic(&[0x1F, 0x8B, 0x08, 0x00, 0, 0, 0, 0]),
            Some(ArchiveType::TarGz)
        );
    }
}