use super::options::Kind;
const SCRIPTS: &[&str] = &[
"bat", "cmd", "ps1", "psm1", "vbs", "vbe", "js", "jse", "wsf",
];
pub(super) fn opaque(line: &str, kind: Kind) -> bool {
let programs: Vec<String> = line
.split(|c: char| c.is_whitespace() || "&|;\"'".contains(c))
.map(super::super::path::program)
.collect();
let starts = |names: &[&str]| programs.iter().any(|p| names.contains(&p.as_str()));
let powershell =
matches!(kind, Kind::PowerShell | Kind::StartProcess) || starts(&["powershell", "pwsh"]);
let cmd = matches!(kind, Kind::Cmd | Kind::Start | Kind::ForFiles) || starts(&["cmd"]);
writes_script(line)
|| forfiles(line)
|| (powershell && !super::simple::powershell(line))
|| (cmd && !super::simple::cmd(line))
}
fn is_script(word: &str) -> bool {
let path = super::super::path::read(word);
path.parts
.last()
.and_then(|name| name.rsplit_once('.'))
.is_some_and(|(_, ext)| SCRIPTS.contains(&ext))
}
fn writes_script(line: &str) -> bool {
line.match_indices('>').any(|(at, _)| {
let target = line[at..].trim_start_matches('>').trim_start();
let end = target
.find(|c: char| c.is_whitespace() || "&|;)".contains(c))
.unwrap_or(target.len());
is_script(target[..end].trim_matches(['"', '\'']))
})
}
fn forfiles(line: &str) -> bool {
let lower = line.to_ascii_lowercase();
let words: Vec<&str> = lower
.split_whitespace()
.map(|w| w.trim_matches(['"', '\'']))
.collect();
let runs = words
.iter()
.any(|w| super::super::path::program(w) == "forfiles");
runs && (words.contains(&"/m")
|| ["@path", "@file", "@fname", "@relpath"]
.iter()
.any(|name| lower.contains(name)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_line_that_writes_a_script_is_opaque() {
assert!(writes_script("echo w^sl -e id> a.bat & a.bat"));
assert!(writes_script("echo x >> \"C:\\t\\run.PS1\""));
assert!(!writes_script("echo x > out.txt"));
assert!(is_script(r".\x.ps1"));
assert!(!is_script("x.exe"));
}
#[test]
fn forfiles_with_a_mask_or_a_file_name_is_opaque() {
assert!(forfiles(
r#"forfiles /p C:\Windows\System32 /m ws?.exe /c "@path -e id""#
));
assert!(forfiles(r#"FORFILES /c "cmd /c echo @file""#));
assert!(!forfiles("forfiles /p C:/logs /d -30"));
assert!(!forfiles("echo /m @path"));
}
}