Przeglądaj źródła

fix(core): escape glob characters in drop/dialogs , closes #5234 (#5237)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Lucas Nogueira 2 lat temu
rodzic
commit
bcd9dc7f85
2 zmienionych plików z 96 dodań i 17 usunięć
  1. 5 0
      .changes/escape-pattern.md
  2. 91 17
      core/tauri/src/scope/fs.rs

+ 5 - 0
.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.

+ 91 - 17
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<P: AsRef<Path>>(list: &mut HashSet<Pattern>, pattern: P) -> crate::Result<()> {
+fn push_pattern<P: AsRef<Path>, F: Fn(&str) -> Result<Pattern, glob::PatternError>>(
+  list: &mut HashSet<Pattern>,
+  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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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, glob::PatternError> {
+  Pattern::new(&glob::Pattern::escape(p))
+}
+
+fn escaped_pattern_with(p: &str, append: &str) -> Result<Pattern, glob::PatternError> {
+  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"));
+  }
+}