Skip to content

Commit 4cbdf0f

Browse files
fix(core): escape glob characters in drop/dialogs , closes #5234 (#5237)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
1 parent f756cd5 commit 4cbdf0f

File tree

2 files changed

+96
-17
lines changed

2 files changed

+96
-17
lines changed

Diff for: .changes/escape-pattern.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": "patch"
3+
---
4+
5+
Escape glob special characters in files/directories when dropping files or using the open/save dialogs.

Diff for: core/tauri/src/scope/fs.rs

+91-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::{
66
collections::{HashMap, HashSet},
77
fmt,
8-
path::{Path, PathBuf},
8+
path::{Path, PathBuf, MAIN_SEPARATOR},
99
sync::{Arc, Mutex},
1010
};
1111

@@ -64,15 +64,19 @@ impl fmt::Debug for Scope {
6464
}
6565
}
6666

67-
fn push_pattern<P: AsRef<Path>>(list: &mut HashSet<Pattern>, pattern: P) -> crate::Result<()> {
67+
fn push_pattern<P: AsRef<Path>, F: Fn(&str) -> Result<Pattern, glob::PatternError>>(
68+
list: &mut HashSet<Pattern>,
69+
pattern: P,
70+
f: F,
71+
) -> crate::Result<()> {
6872
let path: PathBuf = pattern.as_ref().components().collect();
69-
list.insert(Pattern::new(&path.to_string_lossy())?);
73+
list.insert(f(&path.to_string_lossy())?);
7074
#[cfg(windows)]
7175
{
7276
if let Ok(p) = std::fs::canonicalize(&path) {
73-
list.insert(Pattern::new(&p.to_string_lossy())?);
77+
list.insert(f(&p.to_string_lossy())?);
7478
} else {
75-
list.insert(Pattern::new(&format!("\\\\?\\{}", path.display()))?);
79+
list.insert(f(&format!("\\\\?\\{}", path.display()))?);
7680
}
7781
}
7882
Ok(())
@@ -89,15 +93,15 @@ impl Scope {
8993
let mut allowed_patterns = HashSet::new();
9094
for path in scope.allowed_paths() {
9195
if let Ok(path) = parse_path(config, package_info, env, path) {
92-
push_pattern(&mut allowed_patterns, path)?;
96+
push_pattern(&mut allowed_patterns, path, Pattern::new)?;
9397
}
9498
}
9599

96100
let mut forbidden_patterns = HashSet::new();
97101
if let Some(forbidden_paths) = scope.forbidden_paths() {
98102
for path in forbidden_paths {
99103
if let Ok(path) = parse_path(config, package_info, env, path) {
100-
push_pattern(&mut forbidden_patterns, path)?;
104+
push_pattern(&mut forbidden_patterns, path, Pattern::new)?;
101105
}
102106
}
103107
}
@@ -139,16 +143,18 @@ impl Scope {
139143
/// After this function has been called, the frontend will be able to use the Tauri API to read
140144
/// the directory and all of its files and subdirectories.
141145
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {
142-
let path = path.as_ref().to_path_buf();
146+
let path = path.as_ref();
143147
{
144148
let mut list = self.allowed_patterns.lock().unwrap();
145149

146150
// allow the directory to be read
147-
push_pattern(&mut list, &path)?;
151+
push_pattern(&mut list, &path, escaped_pattern)?;
148152
// allow its files and subdirectories to be read
149-
push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }))?;
153+
push_pattern(&mut list, &path, |p| {
154+
escaped_pattern_with(p, if recursive { "**" } else { "*" })
155+
})?;
150156
}
151-
self.trigger(Event::PathAllowed(path));
157+
self.trigger(Event::PathAllowed(path.to_path_buf()));
152158
Ok(())
153159
}
154160

@@ -157,7 +163,11 @@ impl Scope {
157163
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
158164
pub fn allow_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
159165
let path = path.as_ref();
160-
push_pattern(&mut self.allowed_patterns.lock().unwrap(), &path)?;
166+
push_pattern(
167+
&mut self.allowed_patterns.lock().unwrap(),
168+
&path,
169+
escaped_pattern,
170+
)?;
161171
self.trigger(Event::PathAllowed(path.to_path_buf()));
162172
Ok(())
163173
}
@@ -166,16 +176,18 @@ impl Scope {
166176
///
167177
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
168178
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {
169-
let path = path.as_ref().to_path_buf();
179+
let path = path.as_ref();
170180
{
171181
let mut list = self.forbidden_patterns.lock().unwrap();
172182

173183
// allow the directory to be read
174-
push_pattern(&mut list, &path)?;
184+
push_pattern(&mut list, &path, escaped_pattern)?;
175185
// allow its files and subdirectories to be read
176-
push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }))?;
186+
push_pattern(&mut list, &path, |p| {
187+
escaped_pattern_with(p, if recursive { "**" } else { "*" })
188+
})?;
177189
}
178-
self.trigger(Event::PathForbidden(path));
190+
self.trigger(Event::PathForbidden(path.to_path_buf()));
179191
Ok(())
180192
}
181193

@@ -184,7 +196,11 @@ impl Scope {
184196
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
185197
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
186198
let path = path.as_ref();
187-
push_pattern(&mut self.forbidden_patterns.lock().unwrap(), &path)?;
199+
push_pattern(
200+
&mut self.forbidden_patterns.lock().unwrap(),
201+
&path,
202+
escaped_pattern,
203+
)?;
188204
self.trigger(Event::PathForbidden(path.to_path_buf()));
189205
Ok(())
190206
}
@@ -224,3 +240,61 @@ impl Scope {
224240
}
225241
}
226242
}
243+
244+
fn escaped_pattern(p: &str) -> Result<Pattern, glob::PatternError> {
245+
Pattern::new(&glob::Pattern::escape(p))
246+
}
247+
248+
fn escaped_pattern_with(p: &str, append: &str) -> Result<Pattern, glob::PatternError> {
249+
Pattern::new(&format!(
250+
"{}{}{}",
251+
glob::Pattern::escape(p),
252+
MAIN_SEPARATOR,
253+
append
254+
))
255+
}
256+
257+
#[cfg(test)]
258+
mod tests {
259+
use super::Scope;
260+
261+
fn new_scope() -> Scope {
262+
Scope {
263+
allowed_patterns: Default::default(),
264+
forbidden_patterns: Default::default(),
265+
event_listeners: Default::default(),
266+
}
267+
}
268+
269+
#[test]
270+
fn path_is_escaped() {
271+
let scope = new_scope();
272+
scope.allow_directory("/home/tauri/**", false).unwrap();
273+
assert!(scope.is_allowed("/home/tauri/**"));
274+
assert!(scope.is_allowed("/home/tauri/**/file"));
275+
assert!(!scope.is_allowed("/home/tauri/anyfile"));
276+
277+
let scope = new_scope();
278+
scope.allow_file("/home/tauri/**").unwrap();
279+
assert!(scope.is_allowed("/home/tauri/**"));
280+
assert!(!scope.is_allowed("/home/tauri/**/file"));
281+
assert!(!scope.is_allowed("/home/tauri/anyfile"));
282+
283+
let scope = new_scope();
284+
scope.allow_directory("/home/tauri", true).unwrap();
285+
scope.forbid_directory("/home/tauri/**", false).unwrap();
286+
assert!(!scope.is_allowed("/home/tauri/**"));
287+
assert!(!scope.is_allowed("/home/tauri/**/file"));
288+
assert!(!scope.is_allowed("/home/tauri/**/inner/file"));
289+
assert!(scope.is_allowed("/home/tauri/inner/folder/anyfile"));
290+
assert!(scope.is_allowed("/home/tauri/anyfile"));
291+
292+
let scope = new_scope();
293+
scope.allow_directory("/home/tauri", true).unwrap();
294+
scope.forbid_file("/home/tauri/**").unwrap();
295+
assert!(!scope.is_allowed("/home/tauri/**"));
296+
assert!(scope.is_allowed("/home/tauri/**/file"));
297+
assert!(scope.is_allowed("/home/tauri/**/inner/file"));
298+
assert!(scope.is_allowed("/home/tauri/anyfile"));
299+
}
300+
}

0 commit comments

Comments
 (0)