use std::collections::HashSet;
use crate::{
error::Error,
records::{
item::{ItemBase, ItemConditions},
windows::{Bitness, WindowsVersionRange},
},
util::{
encoding::{read_ansi_bytes, read_setup_string},
read::Reader,
},
version::Version,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileEntryType {
UserFile,
UninstExe,
RegSvrExe,
}
stable_name_enum!(FileEntryType, {
Self::UserFile => "user_file",
Self::UninstExe => "uninst_exe",
Self::RegSvrExe => "reg_svr_exe",
});
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum FileFlag {
ConfirmOverwrite,
NeverUninstall,
RestartReplace,
DeleteAfterInstall,
RegisterServer,
RegisterTypeLib,
SharedFile,
IsReadmeFile,
CompareTimeStamp,
FontIsNotTrueType,
SkipIfSourceDoesntExist,
OverwriteReadOnly,
OverwriteSameVersion,
CustomDestName,
OnlyIfDestFileExists,
NoRegError,
UninsRestartDelete,
OnlyIfDoesntExist,
IgnoreVersion,
PromptIfOlder,
DontCopy,
UninsRemoveReadOnly,
RecurseSubDirsExternal,
ReplaceSameVersionIfContentsDiffer,
DontVerifyChecksum,
UninsNoSharedFilePrompt,
CreateAllSubDirs,
Bits32,
Bits64,
ExternalSizePreset,
SetNtfsCompression,
UnsetNtfsCompression,
GacInstall,
Download,
ExtractArchive,
}
stable_flag_enum!(FileFlag, {
ConfirmOverwrite => "confirm_overwrite",
NeverUninstall => "never_uninstall",
RestartReplace => "restart_replace",
DeleteAfterInstall => "delete_after_install",
RegisterServer => "register_server",
RegisterTypeLib => "register_type_lib",
SharedFile => "shared_file",
IsReadmeFile => "is_readme_file",
CompareTimeStamp => "compare_time_stamp",
FontIsNotTrueType => "font_is_not_true_type",
SkipIfSourceDoesntExist => "skip_if_source_doesnt_exist",
OverwriteReadOnly => "overwrite_read_only",
OverwriteSameVersion => "overwrite_same_version",
CustomDestName => "custom_dest_name",
OnlyIfDestFileExists => "only_if_dest_file_exists",
NoRegError => "no_reg_error",
UninsRestartDelete => "unins_restart_delete",
OnlyIfDoesntExist => "only_if_doesnt_exist",
IgnoreVersion => "ignore_version",
PromptIfOlder => "prompt_if_older",
DontCopy => "dont_copy",
UninsRemoveReadOnly => "unins_remove_read_only",
RecurseSubDirsExternal => "recurse_subdirs_external",
ReplaceSameVersionIfContentsDiffer => "replace_same_version_if_contents_differ",
DontVerifyChecksum => "dont_verify_checksum",
UninsNoSharedFilePrompt => "unins_no_shared_file_prompt",
CreateAllSubDirs => "create_all_subdirs",
Bits32 => "bits32",
Bits64 => "bits64",
ExternalSizePreset => "external_size_preset",
SetNtfsCompression => "set_ntfs_compression",
UnsetNtfsCompression => "unset_ntfs_compression",
GacInstall => "gac_install",
Download => "download",
ExtractArchive => "extract_archive",
});
#[derive(Clone, Debug)]
pub struct FileEntry {
pub source: String,
pub destination: String,
pub install_font_name: String,
pub strong_assembly_name: String,
pub item: ItemBase,
pub excludes: String,
pub download_iss_sig_source: String,
pub download_user_name: String,
pub download_password: String,
pub extract_archive_password: String,
pub verification: FileVerification,
pub location_index: u32,
pub attributes: u32,
pub external_size: u64,
pub permission_index: i16,
pub bitness: Option<Bitness>,
pub bitness_raw: u8,
pub flags: HashSet<FileFlag>,
pub options_raw: Vec<u8>,
pub file_type: Option<FileEntryType>,
pub file_type_raw: u8,
}
#[derive(Clone, Debug, Default)]
pub struct FileVerification {
pub iss_sig_allowed_keys: Vec<u8>,
pub hash: [u8; 32],
pub kind: Option<FileVerificationKind>,
pub kind_raw: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileVerificationKind {
None,
Hash,
IsSig,
}
stable_name_enum!(FileVerificationKind, {
Self::None => "none",
Self::Hash => "hash",
Self::IsSig => "issig",
});
impl FileEntry {
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
let source = read_setup_string(reader, version, "File.Source")?;
let destination = read_setup_string(reader, version, "File.Destination")?;
let install_font_name = read_setup_string(reader, version, "File.FontName")?;
let strong_assembly_name = if version.at_least(5, 2, 5) {
read_setup_string(reader, version, "File.StrongAssemblyName")?
} else {
String::new()
};
let (
item_conds,
excludes,
download_iss_sig_source,
download_user_name,
download_password,
extract_archive_password,
verification,
) = if version.at_least(6, 5, 0) {
let conditions = ItemConditions::read(reader, version)?;
let excludes = read_setup_string(reader, version, "File.Excludes")?;
let dl_src = read_setup_string(reader, version, "File.DownloadISSigSource")?;
let dl_user = read_setup_string(reader, version, "File.DownloadUserName")?;
let dl_pw = read_setup_string(reader, version, "File.DownloadPassword")?;
let extract_pw = read_setup_string(reader, version, "File.ExtractArchivePassword")?;
let iss_keys = read_ansi_bytes(reader, "File.Verification.ISSigAllowedKeys")?;
let hash = reader.array::<32>("File.Verification.Hash")?;
let kind_raw = reader.u8("File.Verification.Typ")?;
let verification = FileVerification {
iss_sig_allowed_keys: iss_keys,
hash,
kind: decode_verification_kind(kind_raw),
kind_raw,
};
(
conditions,
excludes,
dl_src,
dl_user,
dl_pw,
extract_pw,
verification,
)
} else {
let conditions = ItemConditions::read(reader, version)?;
(
conditions,
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
FileVerification::default(),
)
};
let winver = WindowsVersionRange::read(reader, version)?;
let item = ItemBase {
conditions: item_conds,
winver,
};
let location_index = reader.u32_le("File.LocationIndex")?;
let attributes = reader.u32_le("File.Attributes")?;
let external_size = if version.at_least(4, 0, 0) {
reader.u64_le("File.ExternalSize")?
} else {
u64::from(reader.u32_le("File.ExternalSize")?)
};
let mut synthetic = HashSet::new();
if !version.at_least(3, 0, 5) {
let copy_mode = reader.u8("File.CopyMode")?;
match copy_mode {
0 => {
synthetic.insert(FileFlag::PromptIfOlder);
}
1 => {
synthetic.insert(FileFlag::OnlyIfDoesntExist);
synthetic.insert(FileFlag::PromptIfOlder);
}
2 => {
synthetic.insert(FileFlag::IgnoreVersion);
synthetic.insert(FileFlag::PromptIfOlder);
}
_ => {}
}
}
let permission_index = if version.at_least(4, 1, 0) {
reader
.array::<2>("File.PermissionIndex")
.map(i16::from_le_bytes)?
} else {
-1
};
let (bitness, bitness_raw) = if version.at_least_4(7, 0, 0, 3) {
let raw = reader.u8("File.Bitness")?;
(Bitness::decode(raw), raw)
} else {
(None, 0)
};
let table = file_flag_table(version);
let bit_count = file_flag_bit_count(version, table.len());
let raw = reader.set_bytes(bit_count, true, "File.Options")?;
let mut flags = super::decode_packed_flags(&raw, &table);
flags.extend(synthetic);
let file_type_raw = reader.u8("File.FileType")?;
let file_type = decode_file_type(file_type_raw, version);
Ok(Self {
source,
destination,
install_font_name,
strong_assembly_name,
item,
excludes,
download_iss_sig_source,
download_user_name,
download_password,
extract_archive_password,
verification,
location_index,
attributes,
external_size,
permission_index,
bitness,
bitness_raw,
flags,
options_raw: raw,
file_type,
file_type_raw,
})
}
}
fn decode_verification_kind(b: u8) -> Option<FileVerificationKind> {
match b {
0 => Some(FileVerificationKind::None),
1 => Some(FileVerificationKind::Hash),
2 => Some(FileVerificationKind::IsSig),
_ => None,
}
}
fn decode_file_type(b: u8, version: &Version) -> Option<FileEntryType> {
if version.at_least(5, 0, 0) {
match b {
0 => Some(FileEntryType::UserFile),
1 => Some(FileEntryType::UninstExe),
_ => None,
}
} else {
match b {
0 => Some(FileEntryType::UserFile),
1 => Some(FileEntryType::UninstExe),
2 => Some(FileEntryType::RegSvrExe),
_ => None,
}
}
}
fn file_flag_table(version: &Version) -> Vec<FileFlag> {
let mut t = vec![
FileFlag::ConfirmOverwrite,
FileFlag::NeverUninstall,
FileFlag::RestartReplace,
FileFlag::DeleteAfterInstall,
FileFlag::RegisterServer,
FileFlag::RegisterTypeLib,
FileFlag::SharedFile,
];
if !version.at_least(2, 0, 0) && !version.is_isx() {
t.push(FileFlag::IsReadmeFile);
}
t.push(FileFlag::CompareTimeStamp);
t.push(FileFlag::FontIsNotTrueType);
if version.at_least(1, 2, 5) {
t.push(FileFlag::SkipIfSourceDoesntExist);
}
if version.at_least(1, 2, 6) {
t.push(FileFlag::OverwriteReadOnly);
}
if version.at_least(1, 3, 21) {
t.push(FileFlag::OverwriteSameVersion);
t.push(FileFlag::CustomDestName);
}
if version.at_least(1, 3, 25) {
t.push(FileFlag::OnlyIfDestFileExists);
}
if version.at_least(2, 0, 5) {
t.push(FileFlag::NoRegError);
}
if version.at_least(3, 0, 1) {
t.push(FileFlag::UninsRestartDelete);
}
if version.at_least(3, 0, 5) {
t.push(FileFlag::OnlyIfDoesntExist);
t.push(FileFlag::IgnoreVersion);
t.push(FileFlag::PromptIfOlder);
}
if version.at_least(4, 0, 0) || (version.is_isx() && version.at_least_4(3, 0, 6, 1)) {
t.push(FileFlag::DontCopy);
}
if version.at_least(4, 0, 5) {
t.push(FileFlag::UninsRemoveReadOnly);
}
if version.at_least(4, 1, 8) {
t.push(FileFlag::RecurseSubDirsExternal);
}
if version.at_least(4, 2, 1) {
t.push(FileFlag::ReplaceSameVersionIfContentsDiffer);
}
if version.at_least(4, 2, 5) {
t.push(FileFlag::DontVerifyChecksum);
}
if version.at_least(5, 0, 3) {
t.push(FileFlag::UninsNoSharedFilePrompt);
}
if version.at_least(5, 1, 0) {
t.push(FileFlag::CreateAllSubDirs);
}
if version.at_least(5, 1, 2) && !version.at_least_4(7, 0, 0, 3) {
t.push(FileFlag::Bits32);
t.push(FileFlag::Bits64);
}
if version.at_least(5, 2, 0) {
t.push(FileFlag::ExternalSizePreset);
t.push(FileFlag::SetNtfsCompression);
t.push(FileFlag::UnsetNtfsCompression);
}
if version.at_least(5, 2, 5) {
t.push(FileFlag::GacInstall);
}
if version.at_least(6, 5, 0) {
t.push(FileFlag::Download);
t.push(FileFlag::ExtractArchive);
}
t
}
fn file_flag_bit_count(version: &Version, named: usize) -> usize {
if version.at_least(6, 7, 0) { 57 } else { named }
}