use crate::{Error, Result};
use std::io::{Read, Seek, SeekFrom};
use super::reader::{read_u8, read_u32_le};
use super::{SIGNATURE, VERSION_MAJOR, VERSION_MINOR};
pub const SFX_SEARCH_LIMIT: usize = 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StartHeader {
pub version_major: u8,
pub version_minor: u8,
pub start_header_crc: u32,
pub next_header_offset: u64,
pub next_header_size: u64,
pub next_header_crc: u32,
pub sfx_offset: u64,
}
impl StartHeader {
pub fn parse<R: Read>(r: &mut R) -> Result<Self> {
let mut sig = [0u8; 6];
r.read_exact(&mut sig).map_err(Error::from)?;
if sig != *SIGNATURE {
return Err(Error::InvalidFormat("invalid 7z signature".into()));
}
let version_major = read_u8(r)?;
let version_minor = read_u8(r)?;
if version_major > VERSION_MAJOR
|| (version_major == VERSION_MAJOR && version_minor > VERSION_MINOR)
{
return Err(Error::UnsupportedFeature {
feature: "unsupported archive version",
});
}
let start_header_crc = read_u32_le(r)?;
let mut header_data = [0u8; 20];
r.read_exact(&mut header_data).map_err(Error::from)?;
let calculated_crc = crc32fast::hash(&header_data);
if calculated_crc != start_header_crc {
return Err(Error::CorruptHeader {
offset: 12,
reason: format!(
"start header CRC mismatch: expected {:#x}, got {:#x}",
start_header_crc, calculated_crc
),
});
}
let next_header_offset = u64::from_le_bytes(header_data[0..8].try_into().unwrap());
let next_header_size = u64::from_le_bytes(header_data[8..16].try_into().unwrap());
let next_header_crc = u32::from_le_bytes(header_data[16..20].try_into().unwrap());
Ok(Self {
version_major,
version_minor,
start_header_crc,
next_header_offset,
next_header_size,
next_header_crc,
sfx_offset: 0, })
}
pub fn next_header_position(&self) -> u64 {
self.sfx_offset + super::SIGNATURE_HEADER_SIZE + self.next_header_offset
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetectedSfxOffset {
pub archive_offset: u64,
pub stub_size: u64,
}
pub fn find_signature<R: Read + Seek>(
reader: &mut R,
search_limit: Option<usize>,
) -> Result<Option<u64>> {
let limit = search_limit.unwrap_or(SFX_SEARCH_LIMIT);
let start_pos = reader.stream_position().map_err(Error::Io)?;
let mut buffer = vec![0u8; limit];
let bytes_read = reader.read(&mut buffer).map_err(Error::Io)?;
buffer.truncate(bytes_read);
let mut search_start = 0;
while search_start + 8 <= buffer.len() {
if let Some(rel_pos) = buffer[search_start..]
.windows(6)
.position(|w| w == SIGNATURE)
{
let pos = search_start + rel_pos;
if pos + 8 <= buffer.len() {
let version_major = buffer[pos + 6];
let version_minor = buffer[pos + 7];
if version_major == VERSION_MAJOR && version_minor <= 10 {
let absolute_offset = start_pos + pos as u64;
return Ok(Some(absolute_offset));
}
}
search_start = pos + 1;
} else {
break;
}
}
Ok(None)
}
pub fn detect_sfx<R: Read + Seek>(reader: &mut R) -> Result<Option<DetectedSfxOffset>> {
let start_pos = reader.stream_position().map_err(Error::Io)?;
reader.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
let offset = find_signature(reader, None)?;
reader.seek(SeekFrom::Start(start_pos)).map_err(Error::Io)?;
match offset {
Some(0) => Ok(None), Some(offset) => Ok(Some(DetectedSfxOffset {
archive_offset: offset,
stub_size: offset,
})),
None => Err(Error::InvalidFormat(
"no 7z signature found (not a 7z or SFX archive)".into(),
)),
}
}
pub fn parse_sfx_header<R: Read + Seek>(reader: &mut R, sfx_offset: u64) -> Result<StartHeader> {
reader
.seek(SeekFrom::Start(sfx_offset))
.map_err(Error::Io)?;
StartHeader::parse(reader)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn create_valid_header(offset: u64, size: u64, next_crc: u32) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]);
data.push(0x00); data.push(0x04);
let mut header_data = Vec::new();
header_data.extend_from_slice(&offset.to_le_bytes());
header_data.extend_from_slice(&size.to_le_bytes());
header_data.extend_from_slice(&next_crc.to_le_bytes());
let crc = crc32fast::hash(&header_data);
data.extend_from_slice(&crc.to_le_bytes());
data.extend_from_slice(&header_data);
data
}
#[test]
fn test_valid_start_header() {
let data = create_valid_header(100, 50, 0xDEADBEEF);
let mut cursor = Cursor::new(&data);
let header = StartHeader::parse(&mut cursor).unwrap();
assert_eq!(header.version_major, 0);
assert_eq!(header.version_minor, 4);
assert_eq!(header.next_header_offset, 100);
assert_eq!(header.next_header_size, 50);
assert_eq!(header.next_header_crc, 0xDEADBEEF);
}
#[test]
fn test_invalid_signature() {
let mut data = create_valid_header(100, 50, 0);
data[0] = 0x00;
let mut cursor = Cursor::new(&data);
let err = StartHeader::parse(&mut cursor).unwrap_err();
assert!(matches!(err, Error::InvalidFormat(_)));
}
#[test]
fn test_crc_mismatch() {
let mut data = create_valid_header(100, 50, 0);
data[12] = 0xFF;
let mut cursor = Cursor::new(&data);
let err = StartHeader::parse(&mut cursor).unwrap_err();
assert!(matches!(err, Error::CorruptHeader { .. }));
}
#[test]
fn test_truncated_header() {
let data = [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, 0x00];
let mut cursor = Cursor::new(&data);
let err = StartHeader::parse(&mut cursor).unwrap_err();
assert!(matches!(err, Error::Io(_)));
}
#[test]
fn test_next_header_position() {
let data = create_valid_header(100, 50, 0);
let mut cursor = Cursor::new(&data);
let header = StartHeader::parse(&mut cursor).unwrap();
assert_eq!(header.next_header_position(), 32 + 100);
}
#[test]
fn test_empty_archive() {
let data = create_valid_header(0, 0, 0);
let mut cursor = Cursor::new(&data);
let header = StartHeader::parse(&mut cursor).unwrap();
assert_eq!(header.next_header_size, 0);
}
#[test]
fn test_find_signature_at_start() {
let data = create_valid_header(0, 0, 0);
let mut cursor = Cursor::new(&data);
let offset = find_signature(&mut cursor, None).unwrap();
assert_eq!(offset, Some(0));
}
#[test]
fn test_find_signature_with_offset() {
let mut data = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00]; data.extend_from_slice(&create_valid_header(0, 0, 0));
let mut cursor = Cursor::new(&data);
let offset = find_signature(&mut cursor, None).unwrap();
assert_eq!(offset, Some(8)); }
#[test]
fn test_find_signature_not_found() {
let data = vec![0xDE, 0xAD, 0xBE, 0xEF]; let mut cursor = Cursor::new(&data);
let offset = find_signature(&mut cursor, None).unwrap();
assert_eq!(offset, None);
}
#[test]
fn test_detect_sfx_regular_archive() {
let data = create_valid_header(0, 0, 0);
let mut cursor = Cursor::new(&data);
let sfx_info = detect_sfx(&mut cursor).unwrap();
assert!(sfx_info.is_none()); }
#[test]
fn test_detect_sfx_archive() {
let mut data = vec![0u8; 100]; data.extend_from_slice(&create_valid_header(0, 0, 0));
let mut cursor = Cursor::new(&data);
let sfx_info = detect_sfx(&mut cursor).unwrap();
assert!(sfx_info.is_some());
let info = sfx_info.unwrap();
assert_eq!(info.archive_offset, 100);
assert_eq!(info.stub_size, 100);
}
#[test]
fn test_detect_sfx_no_signature() {
let data = vec![0xDE, 0xAD, 0xBE, 0xEF]; let mut cursor = Cursor::new(&data);
let err = detect_sfx(&mut cursor).unwrap_err();
assert!(matches!(err, Error::InvalidFormat(_)));
}
#[test]
fn test_parse_sfx_header() {
let mut data = vec![0u8; 100]; data.extend_from_slice(&create_valid_header(50, 30, 0xCAFE));
let mut cursor = Cursor::new(&data);
let header = parse_sfx_header(&mut cursor, 100).unwrap();
assert_eq!(header.next_header_offset, 50);
assert_eq!(header.next_header_size, 30);
}
}