From 36e6f04ab9ec49bfc1632e22a5eb98e80cc91996 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 19 Sep 2022 16:19:34 +0200 Subject: [PATCH 1/4] fix(core): escape glob characters in drop/dialogs , closes #5234 --- .changes/escape-pattern.md | 5 +++++ core/tauri/src/scope/mod.rs | 15 +++++++++++---- 2 files changed, 16 insertions(+), 4 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/mod.rs b/core/tauri/src/scope/mod.rs index d1055e71250..3f8e4f626d5 100644 --- a/core/tauri/src/scope/mod.rs +++ b/core/tauri/src/scope/mod.rs @@ -30,17 +30,24 @@ pub(crate) struct Scopes { impl Scopes { #[allow(dead_code)] pub(crate) fn allow_directory(&self, path: &Path, recursive: bool) -> crate::Result<()> { - self.fs.allow_directory(path, recursive)?; + let path = path.to_string_lossy(); + let escaped_path = glob::Pattern::escape(&path); + + self.fs.allow_directory(&escaped_path, recursive)?; #[cfg(protocol_asset)] - self.asset_protocol.allow_directory(path, recursive)?; + self + .asset_protocol + .allow_directory(&escaped_path, recursive)?; Ok(()) } #[allow(dead_code)] pub(crate) fn allow_file(&self, path: &Path) -> crate::Result<()> { - self.fs.allow_file(path)?; + let path = path.to_string_lossy(); + let escaped_path = glob::Pattern::escape(&path); + self.fs.allow_file(&escaped_path)?; #[cfg(protocol_asset)] - self.asset_protocol.allow_file(path)?; + self.asset_protocol.allow_file(&escaped_path)?; Ok(()) } } From e1b0c9a1ad0436919d2c38fe0a234a9405016dc7 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 19 Sep 2022 16:33:58 +0200 Subject: [PATCH 2/4] also fix the public api --- core/tauri/src/scope/fs.rs | 13 +++++++++---- core/tauri/src/scope/mod.rs | 15 ++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index 44f4e2e25b0..6cec83c32a4 100644 --- a/core/tauri/src/scope/fs.rs +++ b/core/tauri/src/scope/fs.rs @@ -139,7 +139,7 @@ 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 = escape_pattern_in_path(path); { let mut list = self.allowed_patterns.lock().unwrap(); @@ -156,7 +156,7 @@ 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(); + let path = escape_pattern_in_path(path); push_pattern(&mut self.allowed_patterns.lock().unwrap(), &path)?; self.trigger(Event::PathAllowed(path.to_path_buf())); Ok(()) @@ -166,7 +166,7 @@ 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 = escape_pattern_in_path(path); { let mut list = self.forbidden_patterns.lock().unwrap(); @@ -183,7 +183,7 @@ 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(); + let path = escape_pattern_in_path(path); push_pattern(&mut self.forbidden_patterns.lock().unwrap(), &path)?; self.trigger(Event::PathForbidden(path.to_path_buf())); Ok(()) @@ -224,3 +224,8 @@ impl Scope { } } } + +fn escape_pattern_in_path>(p: P) -> PathBuf { + let p = p.as_ref().to_string_lossy(); + PathBuf::from(glob::Pattern::escape(&p)) +} diff --git a/core/tauri/src/scope/mod.rs b/core/tauri/src/scope/mod.rs index 3f8e4f626d5..d1055e71250 100644 --- a/core/tauri/src/scope/mod.rs +++ b/core/tauri/src/scope/mod.rs @@ -30,24 +30,17 @@ pub(crate) struct Scopes { impl Scopes { #[allow(dead_code)] pub(crate) fn allow_directory(&self, path: &Path, recursive: bool) -> crate::Result<()> { - let path = path.to_string_lossy(); - let escaped_path = glob::Pattern::escape(&path); - - self.fs.allow_directory(&escaped_path, recursive)?; + self.fs.allow_directory(path, recursive)?; #[cfg(protocol_asset)] - self - .asset_protocol - .allow_directory(&escaped_path, recursive)?; + self.asset_protocol.allow_directory(path, recursive)?; Ok(()) } #[allow(dead_code)] pub(crate) fn allow_file(&self, path: &Path) -> crate::Result<()> { - let path = path.to_string_lossy(); - let escaped_path = glob::Pattern::escape(&path); - self.fs.allow_file(&escaped_path)?; + self.fs.allow_file(path)?; #[cfg(protocol_asset)] - self.asset_protocol.allow_file(&escaped_path)?; + self.asset_protocol.allow_file(path)?; Ok(()) } } From 2405c082abd74c0a304bc7d329c281ce3ba0cf00 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 19 Sep 2022 16:56:52 +0200 Subject: [PATCH 3/4] clippy --- core/tauri/src/scope/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index 6cec83c32a4..2f4e9f71fa9 100644 --- a/core/tauri/src/scope/fs.rs +++ b/core/tauri/src/scope/fs.rs @@ -158,7 +158,7 @@ impl Scope { pub fn allow_file>(&self, path: P) -> crate::Result<()> { let path = escape_pattern_in_path(path); push_pattern(&mut self.allowed_patterns.lock().unwrap(), &path)?; - self.trigger(Event::PathAllowed(path.to_path_buf())); + self.trigger(Event::PathAllowed(path)); Ok(()) } @@ -185,7 +185,7 @@ impl Scope { pub fn forbid_file>(&self, path: P) -> crate::Result<()> { let path = escape_pattern_in_path(path); push_pattern(&mut self.forbidden_patterns.lock().unwrap(), &path)?; - self.trigger(Event::PathForbidden(path.to_path_buf())); + self.trigger(Event::PathForbidden(path)); Ok(()) } From 63e8ac908bc6451d54fc8e27447f48d54360c9b9 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 4 Oct 2022 17:30:49 -0300 Subject: [PATCH 4/4] make canonicalize work, add tests --- core/tauri/src/scope/fs.rs | 117 +++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index 2f4e9f71fa9..66d8188c748 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 allowed_patterns = HashSet::new(); for path in scope.allowed_paths() { if let Ok(path) = parse_path(config, package_info, env, path) { - push_pattern(&mut allowed_patterns, path)?; + push_pattern(&mut allowed_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 = escape_pattern_in_path(path); + let path = path.as_ref(); { let mut list = self.allowed_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(()) } @@ -156,9 +162,13 @@ 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 = escape_pattern_in_path(path); - push_pattern(&mut self.allowed_patterns.lock().unwrap(), &path)?; - self.trigger(Event::PathAllowed(path)); + let path = path.as_ref(); + push_pattern( + &mut self.allowed_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 = escape_pattern_in_path(path); + 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(()) } @@ -183,9 +195,13 @@ 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 = escape_pattern_in_path(path); - push_pattern(&mut self.forbidden_patterns.lock().unwrap(), &path)?; - self.trigger(Event::PathForbidden(path)); + let path = path.as_ref(); + push_pattern( + &mut self.forbidden_patterns.lock().unwrap(), + &path, + escaped_pattern, + )?; + self.trigger(Event::PathForbidden(path.to_path_buf())); Ok(()) } @@ -225,7 +241,60 @@ impl Scope { } } -fn escape_pattern_in_path>(p: P) -> PathBuf { - let p = p.as_ref().to_string_lossy(); - PathBuf::from(glob::Pattern::escape(&p)) +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")); + } }