fix(runtime): normalize Windows workspace paths
Keep extended-length and regular Windows paths comparable when enforcing workspace boundaries, including first-run roots that cannot yet be canonicalized. Add regression coverage for DOS and UNC path forms. Signed-off-by: Suliman Abdulrazzaq <suliman9000a@gmail.com>
This commit is contained in:
parent
b71afddae1
commit
9e7fd601a5
|
|
@ -40,7 +40,9 @@ fn is_binary_file(path: &Path) -> io::Result<bool> {
|
||||||
/// the workspace boundary (e.g. via `../` traversal or symlink).
|
/// the workspace boundary (e.g. via `../` traversal or symlink).
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn validate_workspace_boundary(resolved: &Path, workspace_root: &Path) -> io::Result<()> {
|
fn validate_workspace_boundary(resolved: &Path, workspace_root: &Path) -> io::Result<()> {
|
||||||
if !resolved.starts_with(workspace_root) {
|
let resolved = normalize_for_comparison(resolved);
|
||||||
|
let workspace_root = normalize_for_comparison(workspace_root);
|
||||||
|
if !resolved.starts_with(&workspace_root) {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::PermissionDenied,
|
io::ErrorKind::PermissionDenied,
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -527,9 +529,29 @@ fn build_grep_content_output(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize_workspace_root(workspace_root: &Path) -> PathBuf {
|
fn canonicalize_workspace_root(workspace_root: &Path) -> PathBuf {
|
||||||
workspace_root
|
let canonical = workspace_root
|
||||||
.canonicalize()
|
.canonicalize()
|
||||||
.unwrap_or_else(|_| workspace_root.to_path_buf())
|
.unwrap_or_else(|_| workspace_root.to_path_buf());
|
||||||
|
normalize_for_comparison(&canonical)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalize Windows' extended-length path prefix before comparing paths.
|
||||||
|
///
|
||||||
|
/// `std::fs::canonicalize` returns paths with a `\\?\` prefix on Windows,
|
||||||
|
/// while a workspace root may still be represented as a regular path when it
|
||||||
|
/// cannot be canonicalized (for example, while a workspace is bootstrapping).
|
||||||
|
/// These paths refer to the same location but do not compare equal with
|
||||||
|
/// [`Path::starts_with`]. Keep the comparison representation stable without
|
||||||
|
/// changing the paths returned to callers.
|
||||||
|
fn normalize_for_comparison(path: &Path) -> PathBuf {
|
||||||
|
let raw = path.to_string_lossy();
|
||||||
|
if let Some(unc_path) = raw.strip_prefix(r"\\?\UNC\") {
|
||||||
|
return PathBuf::from(format!(r"\\{unc_path}"));
|
||||||
|
}
|
||||||
|
if let Some(dos_path) = raw.strip_prefix(r"\\?\") {
|
||||||
|
return PathBuf::from(dos_path);
|
||||||
|
}
|
||||||
|
path.to_path_buf()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_skip_glob_dir(entry: &DirEntry) -> bool {
|
fn should_skip_glob_dir(entry: &DirEntry) -> bool {
|
||||||
|
|
@ -777,8 +799,9 @@ mod tests {
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
component_contains_glob, derive_glob_walk_root, edit_file, expand_braces, glob_search,
|
component_contains_glob, derive_glob_walk_root, edit_file, expand_braces, glob_search,
|
||||||
grep_search, is_symlink_escape, read_file, read_file_in_workspace, write_file,
|
grep_search, is_symlink_escape, normalize_for_comparison, read_file,
|
||||||
write_file_in_workspace, GrepSearchInput, MAX_WRITE_SIZE,
|
read_file_in_workspace, validate_workspace_boundary, write_file, write_file_in_workspace,
|
||||||
|
GrepSearchInput, MAX_WRITE_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn temp_path(name: &str) -> std::path::PathBuf {
|
fn temp_path(name: &str) -> std::path::PathBuf {
|
||||||
|
|
@ -906,6 +929,33 @@ mod tests {
|
||||||
let _ = std::fs::remove_dir_all(&outside);
|
let _ = std::fs::remove_dir_all(&outside);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalizes_windows_extended_length_prefixes_for_boundary_checks() {
|
||||||
|
let dos_path = PathBuf::from(r"C:\workspace\file.txt");
|
||||||
|
let extended_dos_path = PathBuf::from(r"\\?\C:\workspace\file.txt");
|
||||||
|
assert_eq!(
|
||||||
|
normalize_for_comparison(&extended_dos_path),
|
||||||
|
dos_path,
|
||||||
|
"extended DOS paths should compare like their regular form"
|
||||||
|
);
|
||||||
|
|
||||||
|
let unc_path = PathBuf::from(r"\\server\share\workspace\file.txt");
|
||||||
|
let extended_unc_path = PathBuf::from(r"\\?\UNC\server\share\workspace\file.txt");
|
||||||
|
assert_eq!(
|
||||||
|
normalize_for_comparison(&extended_unc_path),
|
||||||
|
unc_path,
|
||||||
|
"extended UNC paths should compare like their regular form"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_equivalent_extended_path_at_workspace_boundary() {
|
||||||
|
let root = PathBuf::from(r"C:\workspace");
|
||||||
|
let resolved = PathBuf::from(r"\\?\C:\workspace\src\main.rs");
|
||||||
|
validate_workspace_boundary(&resolved, &root)
|
||||||
|
.expect("equivalent Windows path representations should be accepted");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn workspace_write_rejects_parent_symlink_escape_regression_3007_class() {
|
fn workspace_write_rejects_parent_symlink_escape_regression_3007_class() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue