diff options
| author | JJ Liebig <jonathan.liebig@gmail.com> | 2026-09-05 21:56:17 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-05 21:56:17 +0200 |
| commit | 6f57f2f62c5d2591647262caf15bc3c962b9c9f8 (patch) | |
| tree | aeece6f488a6ec55e15369151fed74f0137a69bb /src/platform/windows.rs | |
| parent | 50ce4ec2ddb8ec621a77d8773aa6df6d9e7441ff (diff) | |
fix: resolve Windows plugin pane launch paths (#3666)
* fix: resolve Windows plugin pane launch paths
refs #3024
* test: compare resolved Windows plugin probe paths
refs #3024
* test: compare resolved Windows plugin working directories
refs #3024
* test: wait for Windows plugin fixture completion
refs #3024
Diffstat (limited to 'src/platform/windows.rs')
| -rw-r--r-- | src/platform/windows.rs | 107 |
1 files changed, 106 insertions, 1 deletions
diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 10fbb6f0..1c5c4afb 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -30,7 +30,7 @@ use windows_sys::{ Win32::{ Foundation::{ CloseHandle, GlobalFree, LocalFree, FILETIME, HANDLE, HWND, INVALID_HANDLE_VALUE, - NTSTATUS, STATUS_SUCCESS, UNICODE_STRING, + MAX_PATH, NTSTATUS, STATUS_SUCCESS, UNICODE_STRING, }, Globalization::{CompareStringOrdinal, CSTR_EQUAL, CSTR_GREATER_THAN, CSTR_LESS_THAN}, Security::SECURITY_ATTRIBUTES, @@ -105,6 +105,44 @@ pub(crate) fn prepare_paste_text_for_pty_platform(text: String) -> String { text.replace("\r\n", "\n").replace('\n', "\r\n") } +pub(crate) fn plugin_runtime_path_platform(path: &std::path::Path) -> PathBuf { + use std::os::windows::ffi::OsStrExt; + + let Some(candidate) = standard_windows_path(path) else { + return path.to_path_buf(); + }; + // Rust can canonicalize a long standard path by adding its own verbatim prefix, but native + // process consumers still need the original prefix when the plugin root exceeds MAX_PATH. + if candidate.join("").as_os_str().encode_wide().count() >= MAX_PATH as usize { + return path.to_path_buf(); + } + match candidate.canonicalize() { + Ok(canonical) if canonical == path => candidate, + _ => path.to_path_buf(), + } +} + +fn standard_windows_path(path: &std::path::Path) -> Option<PathBuf> { + use std::path::{Component, Prefix}; + + let mut components = path.components(); + let Component::Prefix(prefix) = components.next()? else { + return None; + }; + let mut candidate = match prefix.kind() { + Prefix::VerbatimDisk(drive) => PathBuf::from(format!("{}:", char::from(drive))), + Prefix::VerbatimUNC(server, share) => { + let mut candidate = PathBuf::from(r"\\"); + candidate.push(server); + candidate.push(share); + candidate + } + _ => return None, + }; + candidate.push(components.as_path()); + Some(candidate) +} + /// Resolves against the current foreground layout because asynchronous console /// records do not retain the layout that was active when the key was pressed. pub(crate) fn resolve_base_printable_key(vk: u16, scan: u16) -> Option<char> { @@ -2570,6 +2608,73 @@ mod tests { }; #[test] + fn windows_standard_plugin_runtime_paths_drop_only_disk_and_unc_verbatim_prefixes() { + assert_eq!( + super::standard_windows_path(std::path::Path::new(r"\\?\C:\plugins\example")), + Some(std::path::PathBuf::from(r"C:\plugins\example")) + ); + assert_eq!( + super::standard_windows_path(std::path::Path::new( + r"\\?\UNC\server\share\plugins\example" + )), + Some(std::path::PathBuf::from(r"\\server\share\plugins\example")) + ); + assert_eq!( + super::standard_windows_path(std::path::Path::new( + r"\\?\Volume{01234567-89ab-cdef-0123-456789abcdef}\plugins" + )), + None + ); + } + + #[test] + fn windows_plugin_runtime_path_keeps_extended_path_when_normal_form_is_not_equivalent() { + let path = std::path::PathBuf::from(format!( + r"\\?\C:\herdr-missing-plugin-runtime-path-{}", + std::process::id() + )); + assert_eq!(super::plugin_runtime_path_platform(&path), path); + } + + #[test] + fn windows_plugin_runtime_path_keeps_verbatim_root_beyond_max_path() { + use std::os::windows::ffi::OsStrExt; + + let base = std::env::temp_dir().join(format!( + "herdr-plugin-runtime-path-limit-test-{}", + std::process::id() + )); + fs::create_dir_all(&base).expect("create test base"); + let extended_base = base.canonicalize().expect("canonicalize test base"); + let normal_base = super::standard_windows_path(&extended_base) + .expect("test base has a standard drive path"); + let normal_base_len = normal_base.as_os_str().encode_wide().count(); + let root_at_length = |length| { + let component_len = length - normal_base_len - 1; + let path = extended_base.join("é".repeat(component_len)); + fs::create_dir(&path).expect("create length-boundary test root"); + path.canonicalize() + .expect("canonicalize length-boundary test root") + }; + + let at_limit = root_at_length(windows_sys::Win32::Foundation::MAX_PATH as usize - 2); + let at_limit_normal = + super::standard_windows_path(&at_limit).expect("convert root at MAX_PATH boundary"); + assert_eq!( + super::plugin_runtime_path_platform(&at_limit), + at_limit_normal + ); + + let beyond_limit = root_at_length(windows_sys::Win32::Foundation::MAX_PATH as usize - 1); + assert_eq!( + super::plugin_runtime_path_platform(&beyond_limit), + beyond_limit + ); + + fs::remove_dir_all(base).expect("remove test directory"); + } + + #[test] fn paste_text_uses_windows_line_endings() { assert_eq!( super::prepare_paste_text_for_pty_platform("one\ntwo\r\nthree\rfour".to_owned()), |
