From bcd9dc7f859fa7e65fea5de835fa938ca1368eaf Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 8 Nov 2022 10:15:51 -0300 Subject: [PATCH] fix(core): escape glob characters in drop/dialogs , closes #5234 (#5237) Co-authored-by: Lucas Nogueira --- .changes/escape-pattern.md | 5 ++ core/tauri/src/scope/fs.rs | 108 +++++++++++++++++++++++++++++++------ 2 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 .changes/escape-pattern.md diff --git a/.changes/escape-pattern.md b/.changes/escape-pattern.md new file mode 100644 index 00000000000..4fe1907f091 --- /dev/null +++ b/.changes/escape-pattern.md @@ -0,0 +1,5 @@ +--- +"tauri": "patch" +--- + +Escape glob special characters in files/directories when dropping files or using the open/save dialogs. diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index cc1c37c532b..9de7e1e50ba 100644 --- a/core/tauri/src/scope/fs.rs +++ b/core/tauri/src/scope/fs.rs @@ -5,7 +5,7 @@ use std::{ collections::{HashMap, HashSet}, fmt, - path::{Path, PathBuf}, + path::{Path, PathBuf, MAIN_SEPARATOR}, sync::{Arc, Mutex}, }; @@ -64,15 +64,19 @@ impl fmt::Debug for Scope { } } -fn push_pattern>(list: &mut HashSet, pattern: P) -> crate::Result<()> { +fn push_pattern, F: Fn(&str) -> Result>( + list: &mut HashSet, + pattern: P, + f: F, +) -> crate::Result<()> { let path: PathBuf = pattern.as_ref().components().collect(); - list.insert(Pattern::new(&path.to_string_lossy())?); + list.insert(f(&path.to_string_lossy())?); #[cfg(windows)] { if let Ok(p) = std::fs::canonicalize(&path) { - list.insert(Pattern::new(&p.to_string_lossy())?); + list.insert(f(&p.to_string_lossy())?); } else { - list.insert(Pattern::new(&format!("\\\\?\\{}", path.display()))?); + list.insert(f(&format!("\\\\?\\{}", path.display()))?); } } Ok(()) @@ -89,7 +93,7 @@ impl Scope { let mut alllowed_patterns = HashSet::new(); for path in scope.allowed_paths() { if let Ok(path) = parse_path(config, package_info, env, path) { - push_pattern(&mut alllowed_patterns, path)?; + push_pattern(&mut alllowed_patterns, path, Pattern::new)?; } } @@ -97,7 +101,7 @@ impl Scope { if let Some(forbidden_paths) = scope.forbidden_paths() { for path in forbidden_paths { if let Ok(path) = parse_path(config, package_info, env, path) { - push_pattern(&mut forbidden_patterns, path)?; + push_pattern(&mut forbidden_patterns, path, Pattern::new)?; } } } @@ -139,16 +143,18 @@ impl Scope { /// After this function has been called, the frontend will be able to use the Tauri API to read /// the directory and all of its files and subdirectories. pub fn allow_directory>(&self, path: P, recursive: bool) -> crate::Result<()> { - let path = path.as_ref().to_path_buf(); + let path = path.as_ref(); { let mut list = self.alllowed_patterns.lock().unwrap(); // allow the directory to be read - push_pattern(&mut list, &path)?; + push_pattern(&mut list, &path, escaped_pattern)?; // allow its files and subdirectories to be read - push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }))?; + push_pattern(&mut list, &path, |p| { + escaped_pattern_with(p, if recursive { "**" } else { "*" }) + })?; } - self.trigger(Event::PathAllowed(path)); + self.trigger(Event::PathAllowed(path.to_path_buf())); Ok(()) } @@ -157,7 +163,11 @@ impl Scope { /// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file. pub fn allow_file>(&self, path: P) -> crate::Result<()> { let path = path.as_ref(); - push_pattern(&mut self.alllowed_patterns.lock().unwrap(), &path)?; + push_pattern( + &mut self.alllowed_patterns.lock().unwrap(), + &path, + escaped_pattern, + )?; self.trigger(Event::PathAllowed(path.to_path_buf())); Ok(()) } @@ -166,16 +176,18 @@ impl Scope { /// /// **Note:** this takes precedence over allowed paths, so its access gets denied **always**. pub fn forbid_directory>(&self, path: P, recursive: bool) -> crate::Result<()> { - let path = path.as_ref().to_path_buf(); + let path = path.as_ref(); { let mut list = self.forbidden_patterns.lock().unwrap(); // allow the directory to be read - push_pattern(&mut list, &path)?; + push_pattern(&mut list, &path, escaped_pattern)?; // allow its files and subdirectories to be read - push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }))?; + push_pattern(&mut list, &path, |p| { + escaped_pattern_with(p, if recursive { "**" } else { "*" }) + })?; } - self.trigger(Event::PathForbidden(path)); + self.trigger(Event::PathForbidden(path.to_path_buf())); Ok(()) } @@ -184,7 +196,11 @@ impl Scope { /// **Note:** this takes precedence over allowed paths, so its access gets denied **always**. pub fn forbid_file>(&self, path: P) -> crate::Result<()> { let path = path.as_ref(); - push_pattern(&mut self.forbidden_patterns.lock().unwrap(), &path)?; + push_pattern( + &mut self.forbidden_patterns.lock().unwrap(), + &path, + escaped_pattern, + )?; self.trigger(Event::PathForbidden(path.to_path_buf())); Ok(()) } @@ -224,3 +240,61 @@ impl Scope { } } } + +fn escaped_pattern(p: &str) -> Result { + Pattern::new(&glob::Pattern::escape(p)) +} + +fn escaped_pattern_with(p: &str, append: &str) -> Result { + Pattern::new(&format!( + "{}{}{}", + glob::Pattern::escape(p), + MAIN_SEPARATOR, + append + )) +} + +#[cfg(test)] +mod tests { + use super::Scope; + + fn new_scope() -> Scope { + Scope { + allowed_patterns: Default::default(), + forbidden_patterns: Default::default(), + event_listeners: Default::default(), + } + } + + #[test] + fn path_is_escaped() { + let scope = new_scope(); + scope.allow_directory("/home/tauri/**", false).unwrap(); + assert!(scope.is_allowed("/home/tauri/**")); + assert!(scope.is_allowed("/home/tauri/**/file")); + assert!(!scope.is_allowed("/home/tauri/anyfile")); + + let scope = new_scope(); + scope.allow_file("/home/tauri/**").unwrap(); + assert!(scope.is_allowed("/home/tauri/**")); + assert!(!scope.is_allowed("/home/tauri/**/file")); + assert!(!scope.is_allowed("/home/tauri/anyfile")); + + let scope = new_scope(); + scope.allow_directory("/home/tauri", true).unwrap(); + scope.forbid_directory("/home/tauri/**", false).unwrap(); + assert!(!scope.is_allowed("/home/tauri/**")); + assert!(!scope.is_allowed("/home/tauri/**/file")); + assert!(!scope.is_allowed("/home/tauri/**/inner/file")); + assert!(scope.is_allowed("/home/tauri/inner/folder/anyfile")); + assert!(scope.is_allowed("/home/tauri/anyfile")); + + let scope = new_scope(); + scope.allow_directory("/home/tauri", true).unwrap(); + scope.forbid_file("/home/tauri/**").unwrap(); + assert!(!scope.is_allowed("/home/tauri/**")); + assert!(scope.is_allowed("/home/tauri/**/file")); + assert!(scope.is_allowed("/home/tauri/**/inner/file")); + assert!(scope.is_allowed("/home/tauri/anyfile")); + } +}