#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![warn(missing_docs)]
#![warn(clippy::undocumented_unsafe_blocks)]
extern crate alloc;
#[cfg(all(feature = "std", not(test)))]
extern crate std;
use alloc::string::String;
use core::ffi::c_int;
pub mod error;
pub mod int;
mod macros;
pub mod raw;
pub mod rt;
mod stack;
mod sys;
mod tchar;
mod vars;
#[cfg(any(test, feature = "testing"))]
pub mod testing;
pub use crate::error::{Error, Result};
pub use crate::raw::{
ExecFlags, ExtraParameters, Hmodule, Hwnd, NSISPIAPIVER_1_0, NSISPIAPIVER_CURR,
NSPIM_GUIUNLOAD, NSPIM_UNLOAD, NsisPluginCallback, StackNode,
};
pub use crate::stack::Stack;
pub use crate::tchar::Tchar;
pub use crate::vars::{VAR_COUNT, Var, Variables};
pub struct Nsis {
pub stack: Stack,
pub vars: Variables,
pub hwnd: Hwnd,
extra: *mut ExtraParameters,
}
impl Nsis {
#[must_use]
pub unsafe fn from_raw(
hwnd: Hwnd,
string_size: c_int,
variables: *mut Tchar,
stacktop: *mut *mut StackNode,
extra: *mut ExtraParameters,
) -> Self {
let string_size = string_size.max(0) as usize;
Self {
stack: unsafe { Stack::from_raw(stacktop, string_size) },
vars: unsafe { Variables::from_raw(variables, string_size) },
hwnd,
extra,
}
}
#[must_use]
pub fn string_size(&self) -> usize {
self.stack.string_size()
}
#[must_use]
pub fn extra(&self) -> Option<&ExtraParameters> {
unsafe { self.extra.as_ref() }
}
#[must_use]
pub fn exec_flags(&self) -> Option<&ExecFlags> {
unsafe { self.extra()?.exec_flags.as_ref() }
}
#[must_use]
pub fn exec_flags_mut(&mut self) -> Option<&mut ExecFlags> {
unsafe { self.extra.as_ref()?.exec_flags.as_mut() }
}
#[must_use]
pub fn plugin_api_version(&self) -> c_int {
self.exec_flags().map_or(0, |f| f.plugin_api_version)
}
pub fn require_api_version(&self, required: c_int) -> Result<()> {
let found = self.plugin_api_version();
if found >= required {
Ok(())
} else {
Err(Error::UnsupportedApiVersion { found, required })
}
}
pub unsafe fn execute_code_segment(&mut self, position: c_int) -> Result<c_int> {
let f = self
.extra()
.and_then(|e| e.execute_code_segment)
.ok_or(Error::Unavailable("ExecuteCodeSegment"))?;
Ok(unsafe { f(position, self.hwnd) })
}
pub fn validate_filename(&mut self, name: &str) -> Result<String> {
let f = self
.extra()
.and_then(|e| e.validate_filename)
.ok_or(Error::Unavailable("validate_filename"))?;
let mut buf = tchar::encode(name);
buf.push(0);
unsafe { f(buf.as_mut_ptr()) };
Ok(unsafe { tchar::read_bounded(buf.as_ptr(), buf.len()) })
}
pub fn register_callback(&mut self, callback: NsisPluginCallback) -> Result<()> {
self.require_api_version(NSISPIAPIVER_1_0)?;
let f = self
.extra()
.and_then(|e| e.register_plugin_callback)
.ok_or(Error::Unavailable("RegisterPluginCallback"))?;
let module = rt::hinstance();
if module.is_null() {
return Err(Error::Unavailable("HINSTANCE"));
}
match unsafe { f(module, callback) } {
0 | 1 => Ok(()),
_ => Err(Error::Failed),
}
}
}
macro_rules! flag_accessors {
($(
$(#[$attr:meta])*
$get:ident / $set:ident => $field:ident : bool
),* $(,)?) => {
impl Nsis {$(
$(#[$attr])*
#[must_use]
pub fn $get(&self) -> bool {
self.exec_flags().is_some_and(|f| f.$field != 0)
}
$(#[$attr])*
pub fn $set(&mut self, value: bool) {
if let Some(f) = self.exec_flags_mut() {
f.$field = c_int::from(value);
}
}
)*}
};
($(
$(#[$attr:meta])*
$get:ident / $set:ident => $field:ident : int
),* $(,)?) => {
impl Nsis {$(
$(#[$attr])*
#[must_use]
pub fn $get(&self) -> c_int {
self.exec_flags().map_or(0, |f| f.$field)
}
$(#[$attr])*
pub fn $set(&mut self, value: c_int) {
if let Some(f) = self.exec_flags_mut() {
f.$field = value;
}
}
)*}
};
}
flag_accessors! {
error / set_error => exec_error: bool,
silent / set_silent => silent: bool,
abort / set_abort => abort: bool,
reboot_flag / set_reboot_flag => exec_reboot: bool,
autoclose / set_autoclose => autoclose: bool,
rtl / set_rtl => rtl: bool,
all_users / set_all_users => all_user_var: bool,
}
flag_accessors! {
errlvl / set_errlvl => errlvl: int,
alter_reg_view / set_alter_reg_view => alter_reg_view: int,
status_update / set_status_update => status_update: int,
instdir_error / set_instdir_error => instdir_error: int,
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use crate::tchar::Tchar;
use crate::testing::TestInstaller;
unsafe extern "system" fn strip_colons(s: *mut Tchar) {
let (mut read, mut write) = (s, s);
unsafe {
while *read != 0 {
if *read != b':' as Tchar {
*write = *read;
write = write.add(1);
}
read = read.add(1);
}
*write = 0;
}
}
#[test]
fn validate_filename_is_not_bounded_by_string_size() {
let mut inst = TestInstaller::stock();
inst.extra_mut().validate_filename = Some(strip_colons);
let name: String = "a:".repeat(2000);
let clean = inst.nsis().validate_filename(&name).unwrap();
assert_eq!(clean, "a".repeat(2000));
}
}