Scripts: the project-folder check compares Windows paths in one form

normalize() canonicalizes whatever part of a path exists, which on Windows adds
the verbatim prefix to some paths and not others, so a file under a folder
that isn't on disk failed the project-folder check against an existing
project folder. The check failed closed (access refused); it now drops the
prefix from canonical drive paths so both sides compare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-16 18:53:59 +07:00
co-authored by Claude Opus 5.5
parent 19d19a339e
commit 4d8f88302d
+20
View File
@@ -336,9 +336,19 @@ pub(crate) fn normalize(p: &str) -> std::path::PathBuf {
// Resolve symlinks where the file (or its folder) exists, so links can't escape the project. // Resolve symlinks where the file (or its folder) exists, so links can't escape the project.
std::fs::canonicalize(&out) std::fs::canonicalize(&out)
.or_else(|_| out.parent().map(std::fs::canonicalize).unwrap_or(Ok(out.clone())).map(|d| d.join(out.file_name().unwrap_or_default()))) .or_else(|_| out.parent().map(std::fs::canonicalize).unwrap_or(Ok(out.clone())).map(|d| d.join(out.file_name().unwrap_or_default())))
.map(without_verbatim_prefix)
.unwrap_or(out) .unwrap_or(out)
} }
/// A canonical Windows drive path without its `\\?\` prefix, so it compares with a lexical one
/// (a path whose folder doesn't exist on disk stays lexical).
fn without_verbatim_prefix(p: std::path::PathBuf) -> std::path::PathBuf {
match p.to_str().and_then(|s| s.strip_prefix(r"\\?\")) {
Some(plain) if plain.as_bytes().get(1) == Some(&b':') => plain.into(),
_ => p,
}
}
pub(crate) const GATE: &str = "Preferences ▸ Scripting & Expressions ▸ Allow Scripts to Write Files and Access Network"; pub(crate) const GATE: &str = "Preferences ▸ Scripting & Expressions ▸ Allow Scripts to Write Files and Access Network";
/// The file-access gate: reads under the project's folder are always allowed; other reads, all /// The file-access gate: reads under the project's folder are always allowed; other reads, all
@@ -834,4 +844,14 @@ mod nesting_tests {
assert_eq!(out.result, serde_json::json!(2)); assert_eq!(out.result, serde_json::json!(2));
assert!(check_nesting(&("(".repeat(MAX_NESTING) + &")".repeat(MAX_NESTING))).is_ok()); assert!(check_nesting(&("(".repeat(MAX_NESTING) + &")".repeat(MAX_NESTING))).is_ok());
} }
/// A canonical drive path and a lexical one compare once the `\\?\` prefix is gone; a UNC
/// share and a Unix path are left as they are.
#[test]
fn only_a_verbatim_drive_prefix_is_dropped() {
use std::path::PathBuf;
assert_eq!(without_verbatim_prefix(r"\\?\C:\proj\a.txt".into()), PathBuf::from(r"C:\proj\a.txt"));
assert_eq!(without_verbatim_prefix(r"\\?\UNC\server\share\a.txt".into()), PathBuf::from(r"\\?\UNC\server\share\a.txt"));
assert_eq!(without_verbatim_prefix("/proj/a.txt".into()), PathBuf::from("/proj/a.txt"));
}
} }