universal_archiver/format/
mod.rs

1mod gzip;
2mod tar;
3mod xz;
4mod zip;
5
6use crate::format::gzip::GZipFormat;
7use crate::format::tar::TarFormat;
8use crate::format::xz::XZFormat;
9use crate::format::zip::ZipFormat;
10use anyhow::{bail, Result};
11use std::fs::File;
12use std::io::Read;
13use std::path::Path;
14
15pub enum Format {
16    Zip(ZipFormat),
17    Xz(XZFormat),
18    Gz(GZipFormat),
19    Tar(TarFormat),
20}
21
22pub struct FileObject {
23    pub ext: String,
24    pub header: [u8; 32],
25}
26
27pub trait FileFormat: Sized {
28    fn parse(file: &FileObject) -> Result<Self>;
29    fn extract(&self, file: &Path, output: &Path) -> Result<()>;
30}
31
32impl FileFormat for Format {
33    fn parse(file: &FileObject) -> Result<Self> {
34        if let Ok(zip) = ZipFormat::parse(file) {
35            tracing::info!("Detected zip format");
36            Ok(Self::Zip(zip))
37        } else if let Ok(tar) = TarFormat::parse(file) {
38            tracing::info!("Detected tar format");
39            Ok(Self::Tar(tar))
40        } else if let Ok(xz) = XZFormat::parse(file) {
41            tracing::info!("Detected xz format");
42            Ok(Self::Xz(xz))
43        } else if let Ok(gz) = GZipFormat::parse(file) {
44            tracing::info!("Detected gzip format");
45            Ok(Self::Gz(gz))
46        } else {
47            bail!("Unknown file format");
48        }
49    }
50
51    fn extract(&self, file: &Path, output: &Path) -> Result<()> {
52        match self {
53            Format::Zip(zip) => zip.extract(file, output),
54            Format::Xz(xz) => xz.extract(file, output),
55            Format::Gz(gz) => gz.extract(file, output),
56            Format::Tar(tar) => tar.extract(file, output),
57        }
58    }
59}
60
61/// Parses the format of the file
62pub fn parse_format(file: &Path) -> Result<Format> {
63    let obj = FileObject {
64        ext: get_file_extensions(file).unwrap_or_default(),
65        header: get_file_header(file)?,
66    };
67
68    Format::parse(&obj)
69}
70
71/// Returns the extensions for a given file.
72/// This works different to the extension format of the standard library
73/// as it recognizes everything after the first dot as an extension. As we're
74/// just using the extensions for format detection that behaviour isn't a problem.
75fn get_file_extensions(path: &Path) -> Option<String> {
76    let name = path.file_name()?.to_string_lossy();
77    let extensions: String = name
78        .split('.')
79        .skip(1)
80        .fold(String::new(), |acc, val| format!("{acc}.{val}"));
81
82    Some(extensions)
83}
84
85/// Returns the first 32 bytes of the file that can be used to detect
86/// the signature from the magic number
87fn get_file_header(path: &Path) -> Result<[u8; 32]> {
88    let mut file = File::open(path)?;
89    let mut header_buf = [0u8; 32];
90    file.read_exact(&mut header_buf)?;
91
92    Ok(header_buf)
93}