#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Format {
Gzip,
Bzip2,
TarGz,
TarBz2,
Tar,
Zip,
SevenZip,
Unknown,
}
impl Format {
#[must_use]
pub fn is_compression_wrapper(self) -> bool {
matches!(self, Format::Gzip | Format::Bzip2)
}
#[must_use]
pub fn is_archive(self) -> bool {
matches!(
self,
Format::Tar | Format::TarGz | Format::TarBz2 | Format::Zip | Format::SevenZip
)
}
}
fn is_tar_bz2_name(ends: &impl Fn(&str) -> bool) -> bool {
ends(".tar.bz2") || ends(".tbz2") || ends(".tbz") || ends(".tb2")
}
#[must_use]
pub fn sniff(name: Option<&str>, head: &[u8]) -> Format {
if head.starts_with(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]) {
return Format::SevenZip;
}
if head.starts_with(b"PK\x03\x04") {
return Format::Zip;
}
if head.len() >= 262 && &head[257..262] == b"ustar" {
return Format::Tar;
}
let lower = name.map(str::to_ascii_lowercase);
let ends = |suf: &str| lower.as_deref().is_some_and(|n| n.ends_with(suf));
if head.starts_with(&[0x1F, 0x8B]) {
return if ends(".tgz") || ends(".tar.gz") {
Format::TarGz
} else {
Format::Gzip
};
}
if head.starts_with(b"BZh") {
return if is_tar_bz2_name(&ends) {
Format::TarBz2
} else {
Format::Bzip2
};
}
if ends(".7z") {
return Format::SevenZip;
}
if ends(".zip") || ends(".clbx") {
return Format::Zip;
}
if ends(".tgz") || ends(".tar.gz") {
return Format::TarGz;
}
if is_tar_bz2_name(&ends) {
return Format::TarBz2;
}
if ends(".tar") {
return Format::Tar;
}
if ends(".gz") {
return Format::Gzip;
}
if ends(".bz2") {
return Format::Bzip2;
}
Format::Unknown
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tbz_and_tb2_aliases_are_tar_bzip2() {
for name in ["evidence.tb2", "evidence.tbz", "EVIDENCE.TB2"] {
assert_eq!(sniff(Some(name), b"BZh9"), Format::TarBz2, "{name}");
}
assert_eq!(sniff(Some("notes.txt.bz2"), b"BZh9"), Format::Bzip2);
assert_eq!(sniff(Some("disk.dd.bz2"), b"BZh9"), Format::Bzip2);
}
#[test]
fn tbz_and_tb2_aliases_resolve_by_name_when_magic_silent() {
for name in ["evidence.tb2", "evidence.tbz"] {
assert_eq!(sniff(Some(name), b"\x00\x01\x02"), Format::TarBz2, "{name}");
}
}
}