From 85c6a28dce4bcdcae2ce11da44b9b4c44751a5b7 Mon Sep 17 00:00:00 2001 From: Hampus Date: Tue, 11 Aug 2026 00:06:18 +0200 Subject: [PATCH] fix(desktop): read authenticode sigs with a working pwsh host (#1549) --- tools/ci/src/desktop.rs | 89 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/tools/ci/src/desktop.rs b/tools/ci/src/desktop.rs index c74fabcf1..d5ad34af7 100644 --- a/tools/ci/src/desktop.rs +++ b/tools/ci/src/desktop.rs @@ -1912,6 +1912,29 @@ fn relative_display(root: &Path, path: &Path) -> String { .replace('\\', "/") } +fn percent_decode_archive_name(name: &str) -> String { + if !name.contains('%') { + return name.to_string(); + } + let bytes = name.as_bytes(); + let mut decoded = Vec::with_capacity(bytes.len()); + let mut index = 0; + while index < bytes.len() { + if bytes[index] == b'%' && index + 2 < bytes.len() { + let high = (bytes[index + 1] as char).to_digit(16); + let low = (bytes[index + 2] as char).to_digit(16); + if let (Some(high), Some(low)) = (high, low) { + decoded.push((high * 16 + low) as u8); + index += 3; + continue; + } + } + decoded.push(bytes[index]); + index += 1; + } + String::from_utf8(decoded).unwrap_or_else(|_| name.to_string()) +} + fn assert_expected_windows_pe_inventory( root: &Path, files: &[PathBuf], @@ -1921,7 +1944,7 @@ fn assert_expected_windows_pe_inventory( let present = files .iter() .filter_map(|path| path.file_name().and_then(OsStr::to_str)) - .map(ToOwned::to_owned) + .map(percent_decode_archive_name) .collect::>(); let expected = expected_windows_pe_inventory(arch, main_exe); let missing = expected @@ -2011,14 +2034,18 @@ fn authenticode_report(files: &[PathBuf]) -> Result> { fs::write(&script_path, authenticode_report_script(&list_path)) .with_context(|| format!("Failed to write {}", script_path.display()))?; - let output = capture(CommandSpec::new("powershell").args([ - "-NoProfile", - "-NonInteractive", - "-ExecutionPolicy", - "Bypass", - "-File", - script_path.to_string_lossy().as_ref(), - ]))?; + let output = capture( + CommandSpec::new(powershell_host()) + .args([ + "-NoProfile", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-File", + script_path.to_string_lossy().as_ref(), + ]) + .env_remove("PSModulePath"), + )?; ensure!( output.status == 0, "Get-AuthenticodeSignature failed with exit code {}", @@ -2029,6 +2056,20 @@ fn authenticode_report(files: &[PathBuf]) -> Result> { parse_authenticode_report(stdout.trim()) } +fn powershell_host() -> &'static str { + if which_in_path("pwsh.exe").is_some() || which_in_path("pwsh").is_some() { + return "pwsh"; + } + "powershell" +} + +fn which_in_path(program: &str) -> Option { + let path = env::var_os("PATH")?; + env::split_paths(&path) + .map(|directory| directory.join(program)) + .find(|candidate| candidate.is_file()) +} + fn authenticode_report_script(list_path: &Path) -> String { format!( "$ErrorActionPreference = 'Stop'\n\ @@ -3772,6 +3813,36 @@ export const CHANNEL_DISPLAY_NAME = BUILD_CHANNEL;\n" assert!(zip.by_name("resources/app.asar").is_ok()); } + #[test] + fn percent_encoded_archive_names_match_their_decoded_inventory_entry() { + assert_eq!( + percent_decode_archive_name("Fluxer%20Canary.exe"), + "Fluxer Canary.exe" + ); + assert_eq!(percent_decode_archive_name("Fluxer.exe"), "Fluxer.exe"); + assert_eq!( + percent_decode_archive_name("win-game-capture.win32-arm64-msvc.node"), + "win-game-capture.win32-arm64-msvc.node" + ); + assert_eq!(percent_decode_archive_name("100%.dll"), "100%.dll"); + assert_eq!(percent_decode_archive_name("a%zz.dll"), "a%zz.dll"); + } + + #[test] + fn canary_nupkg_inventory_accepts_percent_encoded_main_executable() { + let root = Path::new("lib").join("app"); + let files = expected_windows_pe_inventory("arm64", "Fluxer Canary.exe") + .into_iter() + .map(|name| { + if name == "Fluxer Canary.exe" { + return root.join("Fluxer%20Canary.exe"); + } + root.join(name) + }) + .collect::>(); + assert_expected_windows_pe_inventory(&root, &files, "arm64", "Fluxer Canary.exe").unwrap(); + } + #[test] fn known_optional_windows_pe_inventory_never_repeats_a_required_binary() { for arch in ["x64", "arm64"] {