use crate::{
decompress::{self, CompressionMode},
error::Error,
installer::NsisInstaller,
nsis::entry::{Entry, EntryIter},
opcode,
strings::NsisString,
};
pub struct ExtractedFile<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> ExtractedFile<'a> {
pub fn name(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(1))
}
#[inline]
pub fn overwrite_flags(&self) -> i32 {
self.entry.offset(0)
}
#[inline]
pub fn data_block_offset(&self) -> u32 {
self.entry.offset(2) as u32
}
pub fn datetime(&self) -> Option<(u32, u32)> {
let lo = self.entry.offset(3);
let hi = self.entry.offset(4);
if lo == 0 && hi == 0 {
None
} else {
Some((lo as u32, hi as u32))
}
}
pub fn is_compressed(&self) -> bool {
let Some((is_compressed, _)) = self.length_prefix() else {
return false;
};
is_compressed
}
pub fn data(&self) -> &[u8] {
let Some((_, size)) = self.length_prefix() else {
return &[];
};
let source = self.data_source();
let Some(offset) = self.source_offset().checked_add(4) else {
return &[];
};
let Some(end) = offset.checked_add(size as usize) else {
return &[];
};
source.get(offset..end).unwrap_or(&[])
}
pub fn decompress(&self) -> Result<Vec<u8>, Error> {
let Some((is_compressed, size)) = self.length_prefix() else {
return Err(Error::TooShort {
expected: 4,
actual: 0,
context: "file data length prefix",
});
};
let source = self.data_source();
let offset = self.source_offset().checked_add(4).ok_or(Error::TooShort {
expected: usize::MAX,
actual: source.len(),
context: "file data offset overflow",
})?;
let end = offset.checked_add(size as usize).ok_or(Error::TooShort {
expected: usize::MAX,
actual: source.len(),
context: "file data end overflow",
})?;
let payload = source.get(offset..end).ok_or(Error::TooShort {
expected: end,
actual: source.len(),
context: "file data payload",
})?;
if !is_compressed {
return Ok(payload.to_vec());
}
decompress::decompress_block(
payload,
self.installer.compression(),
decompress::DecodeLimit::Capped(self.installer.max_decompressed_size()),
)
}
#[inline]
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
fn data_source(&self) -> &[u8] {
if self.installer.compression_mode() == CompressionMode::Solid {
self.installer.solid_data()
} else {
self.installer.file_data()
}
}
fn source_offset(&self) -> usize {
if self.installer.compression_mode() == CompressionMode::Solid {
self.data_block_offset() as usize
} else {
self.installer
.data_block_offset()
.saturating_add(self.data_block_offset() as usize)
}
}
fn length_prefix(&self) -> Option<(bool, u32)> {
let source = self.data_source();
let offset = self.source_offset();
let slice = source.get(offset..)?;
if slice.len() < 4 {
return None;
}
decompress::read_length_prefix(slice).ok()
}
fn validate_data_bounds(&self) -> Result<(), Error> {
let source = self.data_source();
let prefix_offset = self.source_offset();
let prefix_end = prefix_offset.checked_add(4).ok_or(Error::TooShort {
expected: usize::MAX,
actual: source.len(),
context: "file data length prefix",
})?;
let prefix = source
.get(prefix_offset..prefix_end)
.ok_or(Error::TooShort {
expected: prefix_end,
actual: source.len(),
context: "file data length prefix",
})?;
let (_, size) = decompress::read_length_prefix(prefix)?;
let payload_end = prefix_end
.checked_add(size as usize)
.ok_or(Error::TooShort {
expected: usize::MAX,
actual: source.len(),
context: "file data payload",
})?;
source
.get(prefix_end..payload_end)
.map(|_| ())
.ok_or(Error::TooShort {
expected: payload_end,
actual: source.len(),
context: "file data payload",
})
}
}
pub struct FileIter<'a> {
installer: &'a NsisInstaller<'a>,
entries: EntryIter<'a>,
}
impl<'a> FileIter<'a> {
pub(crate) fn new(installer: &'a NsisInstaller<'a>, entries: EntryIter<'a>) -> Self {
Self { installer, entries }
}
}
impl<'a> Iterator for FileIter<'a> {
type Item = Result<ExtractedFile<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry_result = self.entries.next()?;
match entry_result {
Ok(entry) => {
if self.installer.normalize_opcode(entry.which()) == opcode::EW_EXTRACTFILE {
let file = ExtractedFile {
installer: self.installer,
entry,
};
if let Err(e) = file.validate_data_bounds() {
return Some(Err(e));
}
return Some(Ok(file));
}
}
Err(e) => return Some(Err(e)),
}
}
}
}