From 40a45b564d07799021d7e1da7c4a37b41600a98f Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Thu, 19 Sep 2024 19:33:55 -0400 Subject: [PATCH] fix(windows): Handle root paths that cannot be canonicalized (#10838) --- crates/tauri/src/scope/fs.rs | 137 ++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 9 deletions(-) diff --git a/crates/tauri/src/scope/fs.rs b/crates/tauri/src/scope/fs.rs index e01235129..a211757cc 100644 --- a/crates/tauri/src/scope/fs.rs +++ b/crates/tauri/src/scope/fs.rs @@ -77,10 +77,23 @@ fn push_pattern, F: Fn(&str) -> Result crate::Result<()> { - let path: PathBuf = pattern.as_ref().components().collect(); + let mut path: PathBuf = dunce::simplified(pattern.as_ref()).components().collect(); + + if cfg!(windows) { + // Canonicalize disk-relative paths before inserting into the list + use std::path::{Component, Prefix}; + let mut components = path.components(); + if let Some(Component::Prefix(prefix)) = components.next() { + if matches!(prefix.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)) + && !matches!(components.next(), Some(Component::RootDir)) + { + path = dunce::simplified(&path.canonicalize()?).to_path_buf(); + } + } + } + list.insert(f(&path.to_string_lossy())?); - let mut path = path; let mut checked_path = None; // attempt to canonicalize parents in case we have a path like `/data/user/0/appid/**` @@ -109,7 +122,9 @@ fn push_pattern, F: Fn(&str) -> Result, F: Fn(&str) -> Result Result { } fn escaped_pattern_with(p: &str, append: &str) -> Result { - Pattern::new(&format!( - "{}{}{append}", - glob::Pattern::escape(p), - MAIN_SEPARATOR - )) + if p.ends_with(MAIN_SEPARATOR) { + Pattern::new(&format!("{}{append}", glob::Pattern::escape(p))) + } else { + Pattern::new(&format!( + "{}{}{append}", + glob::Pattern::escape(p), + MAIN_SEPARATOR + )) + } } #[cfg(test)] @@ -470,4 +489,104 @@ mod tests { assert!(scope.is_allowed("C:\\home\\tauri\\anyfile")); } } + + #[cfg(windows)] + #[test] + fn windows_root_paths() { + let scope = new_scope(); + { + // UNC network path + scope.allow_directory("\\\\localhost\\c$", true).unwrap(); + assert!(scope.is_allowed("\\\\localhost\\c$")); + assert!(scope.is_allowed("\\\\localhost\\c$\\Windows")); + assert!(scope.is_allowed("\\\\localhost\\c$\\NonExistentFile")); + assert!(!scope.is_allowed("\\\\localhost\\d$")); + assert!(!scope.is_allowed("\\\\OtherServer\\Share")); + } + + let scope = new_scope(); + { + // Verbatim UNC network path + scope + .allow_directory("\\\\?\\UNC\\localhost\\c$", true) + .unwrap(); + assert!(scope.is_allowed("\\\\localhost\\c$")); + assert!(scope.is_allowed("\\\\localhost\\c$\\Windows")); + assert!(scope.is_allowed("\\\\?\\UNC\\localhost\\c$\\Windows\\NonExistentFile")); + // A non-existent file cannot be canonicalized to a verbatim UNC path, so this will fail to match + assert!(!scope.is_allowed("\\\\localhost\\c$\\Windows\\NonExistentFile")); + assert!(!scope.is_allowed("\\\\localhost\\d$")); + assert!(!scope.is_allowed("\\\\OtherServer\\Share")); + } + + let scope = new_scope(); + { + // Device namespace + scope.allow_file("\\\\.\\COM1").unwrap(); + assert!(scope.is_allowed("\\\\.\\COM1")); + assert!(!scope.is_allowed("\\\\.\\COM2")); + } + + let scope = new_scope(); + { + // Disk root + scope.allow_directory("C:\\", true).unwrap(); + assert!(scope.is_allowed("C:\\Windows")); + assert!(scope.is_allowed("C:\\Windows\\system.ini")); + assert!(scope.is_allowed("C:\\NonExistentFile")); + assert!(!scope.is_allowed("D:\\home")); + } + + let scope = new_scope(); + { + // Verbatim disk root + scope.allow_directory("\\\\?\\C:\\", true).unwrap(); + assert!(scope.is_allowed("C:\\Windows")); + assert!(scope.is_allowed("C:\\Windows\\system.ini")); + assert!(scope.is_allowed("C:\\NonExistentFile")); + assert!(!scope.is_allowed("D:\\home")); + } + + let scope = new_scope(); + { + // Verbatim path + scope.allow_file("\\\\?\\anyfile").unwrap(); + assert!(scope.is_allowed("\\\\?\\anyfile")); + assert!(!scope.is_allowed("\\\\?\\otherfile")); + } + + let cwd = std::env::current_dir().unwrap(); + let disk = { + let std::path::Component::Prefix(prefix) = cwd.components().next().unwrap() else { + panic!("Expected current dir to start with a prefix"); + }; + assert!( + matches!(prefix.kind(), std::path::Prefix::Disk(_)), + "Expected current dir to be on a disk drive" + ); + prefix.as_os_str().to_string_lossy() + }; + + let scope = new_scope(); + { + // Disk + scope.allow_directory(&*disk, true).unwrap(); + assert!(scope.is_allowed(format!("{}Cargo.toml", disk))); + assert!(scope.is_allowed(cwd.join("Cargo.toml"))); + assert!(!scope.is_allowed("C:\\Windows")); + assert!(!scope.is_allowed("Q:Cargo.toml")); + } + + let scope = new_scope(); + { + // Verbatim disk + scope + .allow_directory(format!("\\\\?\\{}", disk), true) + .unwrap(); + assert!(scope.is_allowed(format!("{}Cargo.toml", disk))); + assert!(scope.is_allowed(cwd.join("Cargo.toml"))); + assert!(!scope.is_allowed("C:\\Windows")); + assert!(!scope.is_allowed("Q:Cargo.toml")); + } + } }