use std::fs::{self, File};
use std::io;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use goblin::{self, Hint};
use crate::file_utils;
use crate::unpacker::Unpacker;
pub struct ExeUnpacker {
archive_path: PathBuf,
}
impl ExeUnpacker {
pub fn new(archive: &Path) -> ExeUnpacker {
ExeUnpacker {
archive_path: archive.to_path_buf(),
}
}
pub fn supports(archive_path: &Path) -> bool {
let mut file = match File::open(archive_path) {
Ok(x) => x,
Err(_) => {
return false;
}
};
let hint = match goblin::peek(&mut file) {
Ok(x) => x,
Err(_) => {
return false;
}
};
#[cfg(all(unix, not(target_os = "macos")))]
return matches!(hint, Hint::Elf(_));
#[cfg(target_os = "macos")]
return matches!(hint, Hint::Mach(_) | Hint::MachFat(_));
#[cfg(windows)]
matches!(hint, Hint::PE)
}
}
impl Unpacker for ExeUnpacker {
fn unpack(&self, dst_dir: &Path, _strip: u32) -> Result<Option<String>> {
let exe_file_name = self.archive_path.file_name().unwrap();
let dst_path = dst_dir.join(exe_file_name);
fs::create_dir_all(dst_path.parent().unwrap())?;
let mut src_file = File::open(&self.archive_path)
.with_context(|| format!("Error with {:?}", self.archive_path))?;
let mut dst_file =
File::create(&dst_path).with_context(|| format!("Can't create {dst_path:?}"))?;
io::copy(&mut src_file, &mut dst_file)?;
#[cfg(unix)]
file_utils::set_file_executable(&dst_path)?;
let name = file_utils::get_file_name(&dst_path)?;
Ok(Some(name.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_file_utils::get_fixture_path;
const EXECUTABLE_NAME: &str = if cfg!(unix) {
"/bin/ls"
} else if cfg!(windows) {
"c:\\windows\\explorer.exe"
} else {
"not-going-to-work"
};
fn get_test_executable_path() -> PathBuf {
let path = PathBuf::from(EXECUTABLE_NAME);
assert!(path.exists());
path
}
#[test]
fn supports_should_accept_executable() {
let exe_path = get_test_executable_path();
assert!(ExeUnpacker::supports(&exe_path));
}
#[test]
fn supports_should_not_accept_zip_files() {
let zip_path = get_fixture_path("test_archive.zip");
assert!(!ExeUnpacker::supports(&zip_path));
}
#[test]
fn unpack_should_copy_file() {
let src_exe_path = get_test_executable_path();
let exe_file_name = src_exe_path.file_name().unwrap();
let dir = assert_fs::TempDir::new().unwrap();
let dst_exe_path = dir.join(&exe_file_name);
io::copy(
&mut File::open(&src_exe_path).unwrap(),
&mut File::create(&dst_exe_path).unwrap(),
)
.unwrap();
let unpacker = ExeUnpacker {
archive_path: dst_exe_path,
};
let dst_dir = dir.join("sub");
unpacker.unpack(&dst_dir, 0).unwrap();
let dst_path = dst_dir.join(exe_file_name);
assert!(dst_path.exists());
#[cfg(unix)]
{
use crate::test_file_utils::is_file_executable;
assert!(is_file_executable(&dst_path));
}
}
}