use crate::{
error::Error,
header::{
NsisVersionHint,
blockheader::{BLOCKS_NUM, BlockHeader, BlockType, EMPTY_BLOCK},
},
util::{read_i32_le, read_u32_le},
};
pub const CH_FLAGS_DETAILS_SHOWDETAILS: u32 = 1;
pub const CH_FLAGS_DETAILS_NEVERSHOW: u32 = 2;
pub const CH_FLAGS_PROGRESS_COLORED: u32 = 4;
pub const CH_FLAGS_SILENT: u32 = 8;
pub const CH_FLAGS_SILENT_LOG: u32 = 16;
pub const CH_FLAGS_AUTO_CLOSE: u32 = 32;
pub const CH_FLAGS_DIR_NO_SHOW: u32 = 64;
pub const CH_FLAGS_NO_ROOT_DIR: u32 = 128;
pub const CH_FLAGS_COMP_ONLY_ON_CUSTOM: u32 = 256;
pub const CH_FLAGS_NO_CUSTOM: u32 = 512;
pub const NSIS_MAX_INST_TYPES: usize = 32;
pub const COMMON_HEADER_MIN_SIZE: usize = 4 + (BLOCKS_NUM * BlockHeader::SIZE);
#[derive(Debug)]
pub struct CommonHeader<'a> {
bytes: &'a [u8],
blocks: [BlockHeader<'a>; BLOCKS_NUM],
version: NsisVersionHint,
}
impl<'a> CommonHeader<'a> {
pub fn parse(data: &'a [u8], version_hint: NsisVersionHint) -> Result<Self, Error> {
if data.len() < COMMON_HEADER_MIN_SIZE {
return Err(Error::TooShort {
expected: COMMON_HEADER_MIN_SIZE,
actual: data.len(),
context: "CommonHeader",
});
}
let zero_block = BlockHeader::parse(&[0u8; 8])?;
let mut blocks: [BlockHeader<'a>; BLOCKS_NUM] = [zero_block; BLOCKS_NUM];
for (i, block) in blocks.iter_mut().enumerate() {
let block_offset = i
.checked_mul(BlockHeader::SIZE)
.and_then(|n| n.checked_add(4))
.ok_or(Error::TooShort {
expected: COMMON_HEADER_MIN_SIZE,
actual: data.len(),
context: "CommonHeader",
})?;
let slice = data.get(block_offset..).ok_or(Error::TooShort {
expected: COMMON_HEADER_MIN_SIZE,
actual: data.len(),
context: "CommonHeader",
})?;
*block = BlockHeader::parse(slice)?;
}
for (i, block) in blocks.iter().enumerate() {
if i == BlockType::Data as usize {
continue;
}
let off = block.offset() as usize;
if off > data.len() && block.num() > 0 {
let bt = BlockType::from_index(i).unwrap_or(BlockType::Pages);
return Err(Error::InvalidBlockOffset {
block: bt.name(),
offset: block.offset(),
});
}
}
Ok(Self {
bytes: data,
blocks,
version: version_hint,
})
}
#[inline]
pub fn flags(&self) -> u32 {
read_u32_le(self.bytes, 0)
}
#[inline]
pub fn block(&self, bt: BlockType) -> &BlockHeader<'a> {
self.blocks
.get(bt as usize)
.or_else(|| self.blocks.first())
.unwrap_or(&EMPTY_BLOCK)
}
#[inline]
pub fn blocks(&self) -> &[BlockHeader<'a>; BLOCKS_NUM] {
&self.blocks
}
#[inline]
pub fn version(&self) -> NsisVersionHint {
self.version
}
#[inline]
pub fn install_reg_rootkey(&self) -> i32 {
if self.bytes.len() >= 72 {
read_i32_le(self.bytes, 68)
} else {
0
}
}
pub fn langtable_size(&self) -> i32 {
if self.bytes.len() >= 104 {
read_i32_le(self.bytes, 100)
} else {
0
}
}
pub fn code_on_init(&self) -> i32 {
if self.bytes.len() >= 112 {
read_i32_le(self.bytes, 108)
} else {
-1
}
}
pub fn code_on_inst_success(&self) -> i32 {
if self.bytes.len() >= 116 {
read_i32_le(self.bytes, 112)
} else {
-1
}
}
pub fn code_on_inst_failed(&self) -> i32 {
if self.bytes.len() >= 120 {
read_i32_le(self.bytes, 116)
} else {
-1
}
}
pub fn code_on_user_abort(&self) -> i32 {
if self.bytes.len() >= 124 {
read_i32_le(self.bytes, 120)
} else {
-1
}
}
pub fn code_on_gui_init(&self) -> i32 {
if self.bytes.len() >= 128 {
read_i32_le(self.bytes, 124)
} else {
-1
}
}
pub fn code_on_gui_end(&self) -> i32 {
if self.bytes.len() >= 132 {
read_i32_le(self.bytes, 128)
} else {
-1
}
}
pub fn code_on_mouse_over_section(&self) -> i32 {
if self.bytes.len() >= 136 {
read_i32_le(self.bytes, 132)
} else {
-1
}
}
pub fn code_on_verify_inst_dir(&self) -> i32 {
if self.bytes.len() >= 140 {
read_i32_le(self.bytes, 136)
} else {
-1
}
}
pub fn code_on_sel_change(&self) -> i32 {
if self.bytes.len() >= 144 {
read_i32_le(self.bytes, 140)
} else {
-1
}
}
pub fn code_on_reboot_failed(&self) -> i32 {
if self.bytes.len() >= 148 {
read_i32_le(self.bytes, 144)
} else {
-1
}
}
pub fn block_data(&self, bt: BlockType, header_data: &'a [u8]) -> Result<&'a [u8], Error> {
let bh = self.block(bt);
let offset = bh.offset() as usize;
if offset > header_data.len() {
return Err(Error::InvalidBlockOffset {
block: bt.name(),
offset: bh.offset(),
});
}
header_data.get(offset..).ok_or(Error::InvalidBlockOffset {
block: bt.name(),
offset: bh.offset(),
})
}
#[inline]
pub fn is_silent(&self) -> bool {
self.flags() & CH_FLAGS_SILENT != 0
}
#[inline]
pub fn is_auto_close(&self) -> bool {
self.flags() & CH_FLAGS_AUTO_CLOSE != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_common_header(flags: u32, blocks: &[(u32, i32); BLOCKS_NUM]) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&flags.to_le_bytes());
for &(offset, num) in blocks {
buf.extend_from_slice(&offset.to_le_bytes());
buf.extend_from_slice(&num.to_le_bytes());
}
buf.resize(256, 0);
buf
}
#[test]
fn parse_minimal_valid() {
let blocks = [(0, 0); BLOCKS_NUM];
let data = make_common_header(0, &blocks);
let ch = CommonHeader::parse(&data, NsisVersionHint::Unknown).unwrap();
assert_eq!(ch.flags(), 0);
assert_eq!(ch.version(), NsisVersionHint::Unknown);
}
#[test]
fn parse_with_flags() {
let blocks = [(0, 0); BLOCKS_NUM];
let data = make_common_header(CH_FLAGS_SILENT | CH_FLAGS_AUTO_CLOSE, &blocks);
let ch = CommonHeader::parse(&data, NsisVersionHint::Nsis3x).unwrap();
assert!(ch.is_silent());
assert!(ch.is_auto_close());
assert_eq!(ch.version(), NsisVersionHint::Nsis3x);
}
#[test]
fn parse_too_short() {
let data = [0u8; COMMON_HEADER_MIN_SIZE - 1];
assert!(CommonHeader::parse(&data, NsisVersionHint::Unknown).is_err());
}
#[test]
fn block_data_returns_slice() {
let mut blocks = [(0u32, 0i32); BLOCKS_NUM];
blocks[BlockType::Sections as usize] = (100, 3);
let mut data = make_common_header(0, &blocks);
data.resize(200, 0xAA);
let ch = CommonHeader::parse(&data, NsisVersionHint::Unknown).unwrap();
let section_data = ch.block_data(BlockType::Sections, &data).unwrap();
assert_eq!(section_data.len(), 100); }
#[test]
fn block_data_out_of_range() {
let mut blocks = [(0u32, 0i32); BLOCKS_NUM];
blocks[BlockType::Entries as usize] = (9999, 1);
let data = make_common_header(0, &blocks);
let ch = CommonHeader::parse(&data, NsisVersionHint::Unknown);
assert!(ch.is_err());
}
#[test]
fn callbacks_default_to_minus_one_if_short() {
let blocks = [(0, 0); BLOCKS_NUM];
let data = vec![0u8; COMMON_HEADER_MIN_SIZE];
let mut data = data;
data[0..4].copy_from_slice(&0u32.to_le_bytes());
let ch = CommonHeader::parse(&data, NsisVersionHint::Unknown).unwrap();
let _ = blocks; assert_eq!(ch.code_on_init(), -1);
assert_eq!(ch.code_on_inst_success(), -1);
}
}