use std::io::{Read, Seek, SeekFrom, Write};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SfxFormat {
WindowsPe,
LinuxElf,
MacOsMachO,
Generic,
}
impl SfxFormat {
pub fn detect(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
if data.len() >= 2 && &data[0..2] == b"MZ" {
return Some(Self::WindowsPe);
}
if data.len() >= 4 && &data[0..4] == b"\x7FELF" {
return Some(Self::LinuxElf);
}
if data.len() >= 4 {
let magic = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
match magic {
0xFEEDFACE | 0xFEEDFACF | 0xCAFEBABE | 0xBEBAFECA => {
return Some(Self::MacOsMachO);
}
_ => {}
}
let magic_le = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic_le == 0xFEEDFACE || magic_le == 0xFEEDFACF {
return Some(Self::MacOsMachO);
}
}
None
}
pub fn extension(&self) -> &'static str {
match self {
Self::WindowsPe => "exe",
Self::LinuxElf => "",
Self::MacOsMachO => "",
Self::Generic => "",
}
}
pub fn name(&self) -> &'static str {
match self {
Self::WindowsPe => "Windows PE",
Self::LinuxElf => "Linux ELF",
Self::MacOsMachO => "macOS Mach-O",
Self::Generic => "Generic",
}
}
}
#[derive(Debug, Clone)]
pub struct SfxStub {
pub format: SfxFormat,
pub data: Vec<u8>,
}
impl SfxStub {
pub fn new(data: Vec<u8>) -> Result<Self> {
let format = SfxFormat::detect(&data).unwrap_or(SfxFormat::Generic);
Ok(Self { format, data })
}
pub fn with_format(data: Vec<u8>, format: SfxFormat) -> Self {
Self { format, data }
}
pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
let data = std::fs::read(path.as_ref()).map_err(Error::Io)?;
Self::new(data)
}
pub fn from_reader<R: Read>(mut reader: R) -> Result<Self> {
let mut data = Vec::new();
reader.read_to_end(&mut data).map_err(Error::Io)?;
Self::new(data)
}
pub fn size(&self) -> usize {
self.data.len()
}
pub fn validate(&self) -> Result<()> {
match self.format {
SfxFormat::WindowsPe => self.validate_pe(),
SfxFormat::LinuxElf => self.validate_elf(),
SfxFormat::MacOsMachO => self.validate_macho(),
SfxFormat::Generic => Ok(()), }
}
fn validate_pe(&self) -> Result<()> {
if self.data.len() < 64 {
return Err(Error::InvalidFormat("PE stub too small".into()));
}
if &self.data[0..2] != b"MZ" {
return Err(Error::InvalidFormat("invalid PE signature".into()));
}
let pe_offset = u32::from_le_bytes([
self.data[0x3C],
self.data[0x3D],
self.data[0x3E],
self.data[0x3F],
]) as usize;
if pe_offset + 4 > self.data.len() {
return Err(Error::InvalidFormat(
"PE header offset out of bounds".into(),
));
}
if &self.data[pe_offset..pe_offset + 4] != b"PE\0\0" {
return Err(Error::InvalidFormat("invalid PE header signature".into()));
}
Ok(())
}
fn validate_elf(&self) -> Result<()> {
if self.data.len() < 52 {
return Err(Error::InvalidFormat("ELF stub too small".into()));
}
if &self.data[0..4] != b"\x7FELF" {
return Err(Error::InvalidFormat("invalid ELF magic".into()));
}
let class = self.data[4];
if class != 1 && class != 2 {
return Err(Error::InvalidFormat("invalid ELF class".into()));
}
let elf_type = u16::from_le_bytes([self.data[16], self.data[17]]);
if elf_type != 2 && elf_type != 3 {
return Err(Error::InvalidFormat(
"ELF is not an executable or shared object".into(),
));
}
Ok(())
}
fn validate_macho(&self) -> Result<()> {
if self.data.len() < 28 {
return Err(Error::InvalidFormat("Mach-O stub too small".into()));
}
let magic = u32::from_be_bytes([self.data[0], self.data[1], self.data[2], self.data[3]]);
let magic_le = u32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]]);
let is_valid_magic = matches!(magic, 0xFEEDFACE | 0xFEEDFACF | 0xCAFEBABE | 0xBEBAFECA)
|| matches!(magic_le, 0xFEEDFACE | 0xFEEDFACF);
if !is_valid_magic {
return Err(Error::InvalidFormat("invalid Mach-O magic".into()));
}
Ok(())
}
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<u64> {
writer.write_all(&self.data).map_err(Error::Io)?;
Ok(self.data.len() as u64)
}
}
pub fn get_archive_offset<R: Read + Seek>(reader: &mut R, format: SfxFormat) -> Result<u64> {
match format {
SfxFormat::WindowsPe => get_pe_archive_offset(reader),
SfxFormat::LinuxElf => get_elf_archive_offset(reader),
SfxFormat::MacOsMachO => get_macho_archive_offset(reader),
SfxFormat::Generic => {
crate::format::header::find_signature(reader, None)?
.ok_or_else(|| Error::InvalidFormat("no 7z signature found".into()))
}
}
}
fn get_pe_archive_offset<R: Read + Seek>(reader: &mut R) -> Result<u64> {
reader.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
crate::format::header::find_signature(reader, None)?
.ok_or_else(|| Error::InvalidFormat("no 7z signature found in PE".into()))
}
fn get_elf_archive_offset<R: Read + Seek>(reader: &mut R) -> Result<u64> {
reader.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
crate::format::header::find_signature(reader, None)?
.ok_or_else(|| Error::InvalidFormat("no 7z signature found in ELF".into()))
}
fn get_macho_archive_offset<R: Read + Seek>(reader: &mut R) -> Result<u64> {
reader.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
crate::format::header::find_signature(reader, None)?
.ok_or_else(|| Error::InvalidFormat("no 7z signature found in Mach-O".into()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_detection_pe() {
let mut pe_data = vec![0u8; 128];
pe_data[0] = b'M';
pe_data[1] = b'Z';
pe_data[0x3C] = 64; pe_data[64] = b'P';
pe_data[65] = b'E';
pe_data[66] = 0;
pe_data[67] = 0;
assert_eq!(SfxFormat::detect(&pe_data), Some(SfxFormat::WindowsPe));
}
#[test]
fn test_format_detection_elf() {
let elf_data = b"\x7FELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00";
assert_eq!(SfxFormat::detect(elf_data), Some(SfxFormat::LinuxElf));
}
#[test]
fn test_format_detection_macho_64() {
let macho_data = [0xFE, 0xED, 0xFA, 0xCF]; assert_eq!(SfxFormat::detect(&macho_data), Some(SfxFormat::MacOsMachO));
}
#[test]
fn test_format_detection_macho_fat() {
let macho_data = [0xCA, 0xFE, 0xBA, 0xBE]; assert_eq!(SfxFormat::detect(&macho_data), Some(SfxFormat::MacOsMachO));
}
#[test]
fn test_format_detection_unknown() {
let random_data = [0x00, 0x01, 0x02, 0x03];
assert_eq!(SfxFormat::detect(&random_data), None);
}
#[test]
fn test_stub_new() {
let elf_data = b"\x7FELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00".to_vec();
let stub = SfxStub::new(elf_data).unwrap();
assert_eq!(stub.format, SfxFormat::LinuxElf);
}
#[test]
fn test_stub_with_format() {
let data = vec![0u8; 100];
let stub = SfxStub::with_format(data.clone(), SfxFormat::Generic);
assert_eq!(stub.format, SfxFormat::Generic);
assert_eq!(stub.size(), 100);
}
#[test]
fn test_format_extensions() {
assert_eq!(SfxFormat::WindowsPe.extension(), "exe");
assert_eq!(SfxFormat::LinuxElf.extension(), "");
assert_eq!(SfxFormat::MacOsMachO.extension(), "");
}
#[test]
fn test_pe_validation_too_small() {
let stub = SfxStub::with_format(vec![b'M', b'Z'], SfxFormat::WindowsPe);
assert!(stub.validate().is_err());
}
#[test]
fn test_elf_validation() {
let mut elf_data = vec![0u8; 64];
elf_data[0..4].copy_from_slice(b"\x7FELF");
elf_data[4] = 2; elf_data[5] = 1; elf_data[16] = 2; elf_data[17] = 0;
let stub = SfxStub::with_format(elf_data, SfxFormat::LinuxElf);
assert!(stub.validate().is_ok());
}
#[test]
fn test_write_to() {
let data = vec![1, 2, 3, 4, 5];
let stub = SfxStub::with_format(data.clone(), SfxFormat::Generic);
let mut output = Vec::new();
let written = stub.write_to(&mut output).unwrap();
assert_eq!(written, 5);
assert_eq!(output, data);
}
}