use std::path::{Path, PathBuf};
#[cfg(windows)]
fn is_absolute_windows_drive_path_text(text: &str, separator: u8) -> bool {
let bytes = text.as_bytes();
let mixed_separator = match separator {
b'\\' => text.contains('/'),
b'/' => text.contains('\\'),
_ => true,
};
bytes.len() >= 3
&& bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& bytes[2] == separator
&& !mixed_separator
}
#[cfg(windows)]
fn is_windows_unc_path_suffix(text: &str, separator: char) -> bool {
let mixed_separator = match separator {
'\\' => text.contains('/'),
'/' => text.contains('\\'),
_ => true,
};
if mixed_separator {
return false;
}
let body = text.strip_suffix(separator).unwrap_or(text);
let components = body.split(separator).collect::<Vec<_>>();
components.len() >= 2 && components.iter().all(|component| !component.is_empty())
}
pub(crate) fn normalize_host_visible_path_text(rendered: &str) -> String {
#[cfg(windows)]
{
if rendered
.get(..8)
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(r"\\?\UNC\"))
{
let stripped = &rendered[8..];
if is_windows_unc_path_suffix(stripped, '\\') {
return format!(r"\\{}", stripped);
}
}
if rendered
.get(..8)
.is_some_and(|prefix| prefix.eq_ignore_ascii_case("//?/UNC/"))
{
let stripped = &rendered[8..];
if is_windows_unc_path_suffix(stripped, '/') {
return format!(r"\\{}", stripped.replace('/', r"\"));
}
}
if let Some(stripped) = rendered.strip_prefix(r"\\?\")
&& is_absolute_windows_drive_path_text(stripped, b'\\')
{
return stripped.to_string();
}
if let Some(stripped) = rendered.strip_prefix("//?/")
&& is_absolute_windows_drive_path_text(stripped, b'/')
{
return stripped.to_string();
}
}
rendered.to_string()
}
pub(crate) fn normalize_host_input_path_text(rendered: &str) -> Result<String, String> {
let normalized = normalize_host_visible_path_text(rendered);
#[cfg(windows)]
if normalized.starts_with(r"\\?\") || normalized.starts_with("//?/") {
return Err("unsupported Windows verbatim path namespace".to_string());
}
Ok(normalized)
}
pub fn render_host_visible_path(path: &Path) -> String {
normalize_host_visible_path_text(&path.to_string_lossy())
}
#[cfg(windows)]
pub(crate) fn normalize_windows_verbatim_path(path: &Path) -> Result<PathBuf, String> {
use std::ffi::OsString;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
let encoded = path.as_os_str().encode_wide().collect::<Vec<_>>();
const VERBATIM_PREFIX: [u16; 4] = [92, 92, 63, 92];
if !encoded.starts_with(&VERBATIM_PREFIX) {
return Ok(path.to_path_buf());
}
let is_unc = encoded.len() >= 8
&& matches!(encoded[4], 85 | 117)
&& matches!(encoded[5], 78 | 110)
&& matches!(encoded[6], 67 | 99)
&& encoded[7] == 92;
if is_unc {
let suffix = &encoded[8..];
let mut components = suffix.split(|unit| *unit == 92).collect::<Vec<_>>();
if components
.last()
.is_some_and(|component| component.is_empty())
{
components.pop();
}
let valid_unc = !suffix.contains(&47)
&& components.len() >= 2
&& components.iter().all(|component| !component.is_empty());
if valid_unc {
let mut normalized = Vec::with_capacity(encoded.len().saturating_sub(6));
normalized.extend_from_slice(&[92, 92]);
normalized.extend_from_slice(suffix);
return Ok(PathBuf::from(OsString::from_wide(&normalized)));
}
return Err("unsupported Windows verbatim path namespace".to_string());
}
let remainder = &encoded[VERBATIM_PREFIX.len()..];
let drive_qualified = remainder.len() >= 3
&& (remainder[0] >= u16::from(b'A') && remainder[0] <= u16::from(b'Z')
|| remainder[0] >= u16::from(b'a') && remainder[0] <= u16::from(b'z'))
&& remainder[1] == u16::from(b':')
&& remainder[2] == u16::from(b'\\')
&& !remainder.contains(&u16::from(b'/'));
if drive_qualified {
return Ok(PathBuf::from(OsString::from_wide(remainder)));
}
Err("unsupported Windows verbatim path namespace".to_string())
}
pub(crate) fn host_process_path_argument(path: &Path) -> PathBuf {
#[cfg(windows)]
{
normalize_windows_verbatim_path(path).unwrap_or_else(|_| path.to_path_buf())
}
#[cfg(not(windows))]
{
path.to_path_buf()
}
}
#[cfg(test)]
mod tests {
use super::host_process_path_argument;
#[cfg(windows)]
use super::{
normalize_host_input_path_text, normalize_host_visible_path_text,
normalize_windows_verbatim_path,
};
use std::path::Path;
#[cfg(windows)]
#[test]
fn normalize_host_visible_path_text_handles_case_and_separator_variants() {
assert_eq!(
normalize_host_visible_path_text(r"\\?\unc\server\share\module.lua"),
r"\\server\share\module.lua"
);
assert_eq!(
normalize_host_visible_path_text("//?/UNC/server/share/module.lua"),
r"\\server\share\module.lua"
);
assert_eq!(
normalize_host_visible_path_text("//?/C:/runtime/module.lua"),
"C:/runtime/module.lua"
);
assert_eq!(
normalize_host_visible_path_text(
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\module.lua"
),
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\module.lua"
);
assert_eq!(
normalize_host_visible_path_text(r"\\?\C:/runtime/module.lua"),
r"\\?\C:/runtime/module.lua"
);
assert_eq!(
normalize_host_visible_path_text(r"\\?\UNC\server/share\module.lua"),
r"\\?\UNC\server/share\module.lua"
);
}
#[cfg(windows)]
#[test]
fn normalize_host_input_path_text_rejects_unsupported_verbatim_namespaces() {
let error = normalize_host_input_path_text(
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\module.lua",
)
.expect_err("volume GUID namespace must be rejected");
assert_eq!(error, "unsupported Windows verbatim path namespace");
let mixed_error = normalize_host_input_path_text(r"\\?\UNC/server/share/module.lua")
.expect_err("mixed-separator UNC namespace must be rejected");
assert_eq!(mixed_error, "unsupported Windows verbatim path namespace");
let mixed_suffix_error = normalize_host_input_path_text(r"\\?\UNC\server/share\module.lua")
.expect_err("mixed-separator UNC suffix must be rejected");
assert_eq!(
mixed_suffix_error,
"unsupported Windows verbatim path namespace"
);
let mixed_drive_error = normalize_host_input_path_text(r"\\?\C:/runtime/module.lua")
.expect_err("mixed-separator drive path must be rejected");
assert_eq!(
mixed_drive_error,
"unsupported Windows verbatim path namespace"
);
}
#[cfg(windows)]
#[test]
fn normalize_windows_verbatim_path_is_non_lossy_and_strict() {
let unc = normalize_windows_verbatim_path(Path::new(r"\\?\unc\server\share\module.lua"))
.expect("normalize native UNC path");
assert_eq!(unc, Path::new(r"\\server\share\module.lua"));
let mixed_error = normalize_windows_verbatim_path(Path::new(r"\\?\C:/runtime\module.lua"))
.expect_err("mixed native drive path must fail");
assert_eq!(mixed_error, "unsupported Windows verbatim path namespace");
}
#[test]
fn host_process_path_argument_uses_runtime_compatible_path() {
#[cfg(windows)]
assert_eq!(
host_process_path_argument(Path::new(r"\\?\C:\runtime\pnpm.cjs")),
Path::new(r"C:\runtime\pnpm.cjs")
);
#[cfg(not(windows))]
assert_eq!(
host_process_path_argument(Path::new("/runtime/pnpm.cjs")),
Path::new("/runtime/pnpm.cjs")
);
#[cfg(windows)]
assert_eq!(
host_process_path_argument(Path::new(
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\pnpm.cjs"
)),
Path::new(r"\\?\Volume{00000000-0000-0000-0000-000000000000}\pnpm.cjs")
);
}
}