use crate::{
error::Error,
records::windows::WindowsVersionRange,
util::{encoding::read_setup_string, read::Reader},
version::Version,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ItemConditions {
pub components: String,
pub tasks: String,
pub languages: String,
pub check: String,
pub after_install: String,
pub before_install: String,
}
#[allow(dead_code)]
impl ItemConditions {
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
let mut out = Self::default();
if version.at_least(2, 0, 0) || (version.is_isx() && version.at_least(1, 3, 8)) {
out.components = read_setup_string(reader, version, "Item.Components")?;
}
if version.at_least(2, 0, 0) || (version.is_isx() && version.at_least(1, 3, 17)) {
out.tasks = read_setup_string(reader, version, "Item.Tasks")?;
}
if version.at_least(4, 0, 1) {
out.languages = read_setup_string(reader, version, "Item.Languages")?;
}
if version.at_least(4, 0, 0) || (version.is_isx() && version.at_least(1, 3, 24)) {
out.check = read_setup_string(reader, version, "Item.Check")?;
}
if version.at_least(4, 1, 0) {
out.after_install = read_setup_string(reader, version, "Item.AfterInstall")?;
out.before_install = read_setup_string(reader, version, "Item.BeforeInstall")?;
}
Ok(out)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ItemBase {
pub conditions: ItemConditions,
pub winver: WindowsVersionRange,
}
#[allow(dead_code)]
impl ItemBase {
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
let conditions = ItemConditions::read(reader, version)?;
let winver = WindowsVersionRange::read(reader, version)?;
Ok(Self { conditions, winver })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::version::{Version, VersionFlags};
fn unicode_v(a: u8, b: u8, c: u8) -> Version {
Version {
a,
b,
c,
d: 0,
flags: VersionFlags::UNICODE,
raw_marker: [0u8; 64],
}
}
fn put_str(buf: &mut Vec<u8>, s: &str) {
let utf16: Vec<u16> = s.encode_utf16().collect();
let byte_len = u32::try_from(utf16.len() * 2).unwrap();
buf.extend_from_slice(&byte_len.to_le_bytes());
for u in utf16 {
buf.extend_from_slice(&u.to_le_bytes());
}
}
#[test]
fn item_conditions_reads_6_strings_at_v6_4() {
let v = unicode_v(6, 4, 0);
let mut bytes = Vec::new();
put_str(&mut bytes, "comp_a");
put_str(&mut bytes, "task_a");
put_str(&mut bytes, "en");
put_str(&mut bytes, "IsAdmin");
put_str(&mut bytes, "Post"); put_str(&mut bytes, "Pre"); let mut r = Reader::new(&bytes);
let cond = ItemConditions::read(&mut r, &v).unwrap();
assert_eq!(cond.components, "comp_a");
assert_eq!(cond.tasks, "task_a");
assert_eq!(cond.languages, "en");
assert_eq!(cond.check, "IsAdmin");
assert_eq!(cond.after_install, "Post");
assert_eq!(cond.before_install, "Pre");
assert_eq!(r.pos(), bytes.len(), "no leftover bytes");
}
#[test]
fn item_conditions_pre_4_1_skips_install_hooks() {
let v = unicode_v(4, 0, 5);
let mut bytes = Vec::new();
put_str(&mut bytes, "c");
put_str(&mut bytes, "t");
put_str(&mut bytes, "l");
put_str(&mut bytes, "k");
let mut r = Reader::new(&bytes);
let cond = ItemConditions::read(&mut r, &v).unwrap();
assert_eq!(cond.components, "c");
assert_eq!(cond.check, "k");
assert_eq!(cond.after_install, "");
assert_eq!(cond.before_install, "");
assert_eq!(r.pos(), bytes.len());
}
#[test]
fn item_conditions_pre_4_0_skips_languages_and_check() {
let v = unicode_v(3, 0, 0);
let mut bytes = Vec::new();
put_str(&mut bytes, "c");
put_str(&mut bytes, "t");
let mut r = Reader::new(&bytes);
let cond = ItemConditions::read(&mut r, &v).unwrap();
assert_eq!(cond.components, "c");
assert_eq!(cond.tasks, "t");
assert_eq!(cond.languages, "");
assert_eq!(cond.check, "");
assert_eq!(r.pos(), bytes.len());
}
#[test]
fn item_conditions_empty_strings_round_trip() {
let v = unicode_v(6, 0, 0);
let mut bytes = Vec::new();
for _ in 0..6 {
bytes.extend_from_slice(&0u32.to_le_bytes());
}
let mut r = Reader::new(&bytes);
let cond = ItemConditions::read(&mut r, &v).unwrap();
assert_eq!(cond, ItemConditions::default());
assert_eq!(r.pos(), 24);
}
#[test]
fn item_base_reads_conditions_then_winver() {
let v = unicode_v(6, 4, 0);
let mut bytes = Vec::new();
for _ in 0..6 {
bytes.extend_from_slice(&0u32.to_le_bytes());
}
bytes.extend_from_slice(&[0u8; 20]);
let mut r = Reader::new(&bytes);
let base = ItemBase::read(&mut r, &v).unwrap();
assert_eq!(base.conditions, ItemConditions::default());
assert_eq!(base.winver, WindowsVersionRange::default());
assert_eq!(r.pos(), bytes.len());
}
#[test]
fn item_conditions_propagates_truncation() {
let v = unicode_v(6, 0, 0);
let bytes = [4u8, 0, 0, 0, b'A', 0];
let mut r = Reader::new(&bytes);
let err = ItemConditions::read(&mut r, &v).unwrap_err();
assert!(matches!(err, Error::Truncated { .. }));
}
}