Skip to content

Commit e1bb697

Browse files
eugenesvkByron
andauthored
fix: Escape quoted paths when deleting with AppleScript
Co-authored-by: Sebastian Thiel <[email protected]>
1 parent 6f0b737 commit e1bb697

File tree

1 file changed

+52
-11
lines changed

1 file changed

+52
-11
lines changed

src/macos.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ fn delete_using_finder<P: AsRef<Path>>(full_paths: &[P]) -> Result<(), Error> {
119119
.map(|p| {
120120
let path_b = p.as_ref().as_os_str().as_encoded_bytes();
121121
match std::str::from_utf8(path_b) {
122-
Ok(path_utf8) => format!("POSIX file \"{path_utf8}\""), // utf-8 path, use as is
123-
Err(_) => format!("POSIX file \"{}\"", &percent_encode(path_b)), // binary path, %-encode it
122+
Ok(path_utf8) => format!(r#"POSIX file "{}""#, esc_quote(path_utf8)), // utf-8 path, escape \"
123+
Err(_) => format!(r#"POSIX file "{}""#, esc_quote(&percent_encode(path_b))), // binary path, %-encode it and escape \"
124124
}
125125
})
126126
.collect::<Vec<String>>()
@@ -180,6 +180,28 @@ fn percent_encode(input: &[u8]) -> Cow<'_, str> {
180180
Cow::Owned(res)
181181
}
182182

183+
/// Escapes `"` or `\` with `\` for use in AppleScript text
184+
fn esc_quote(s: &str) -> Cow<'_, str> {
185+
if s.contains(['"', '\\']) {
186+
let mut r = String::with_capacity(s.len());
187+
let chars = s.chars();
188+
for c in chars {
189+
match c {
190+
'"' | '\\' => {
191+
r.push('\\');
192+
r.push(c);
193+
} // escapes quote/escape char
194+
_ => {
195+
r.push(c);
196+
} // no escape required
197+
}
198+
}
199+
Cow::Owned(r)
200+
} else {
201+
Cow::Borrowed(s)
202+
}
203+
}
204+
183205
#[cfg(test)]
184206
mod tests {
185207
use crate::{
@@ -194,6 +216,24 @@ mod tests {
194216
use std::path::PathBuf;
195217
use std::process::Command;
196218

219+
#[test]
220+
#[serial]
221+
fn test_delete_with_finder_quoted_paths() {
222+
init_logging();
223+
let mut trash_ctx = TrashContext::default();
224+
trash_ctx.set_delete_method(DeleteMethod::Finder);
225+
226+
let mut path1 = PathBuf::from(get_unique_name());
227+
let mut path2 = PathBuf::from(get_unique_name());
228+
path1.set_extension(r#"a"b,"#);
229+
path2.set_extension(r#"x80=%80 slash=\ pc=% quote=" comma=,"#);
230+
File::create_new(&path1).unwrap();
231+
File::create_new(&path2).unwrap();
232+
trash_ctx.delete_all(&[&path1, &path2]).unwrap();
233+
assert!(!path1.exists());
234+
assert!(!path2.exists());
235+
}
236+
197237
#[test]
198238
#[serial]
199239
fn test_delete_with_ns_file_manager() {
@@ -214,18 +254,19 @@ mod tests {
214254
let parent_fs_supports_binary = tmp.path();
215255

216256
init_logging();
217-
let mut trash_ctx = TrashContext::default();
218-
trash_ctx.set_delete_method(DeleteMethod::NsFileManager);
257+
for method in [DeleteMethod::NsFileManager, DeleteMethod::Finder] {
258+
let mut trash_ctx = TrashContext::default();
259+
trash_ctx.set_delete_method(method);
219260

220-
let invalid_utf8 = b"\x80"; // lone continuation byte (128) (invalid utf8)
221-
let mut path_invalid = parent_fs_supports_binary.join(get_unique_name());
222-
path_invalid.set_extension(OsStr::from_bytes(invalid_utf8)); //...trash-test-111-0.\x80 (not push to avoid fail unexisting dir)
261+
let mut path_invalid = parent_fs_supports_binary.join(get_unique_name());
262+
path_invalid.set_extension(OsStr::from_bytes(b"\x80\"\\")); //...trash-test-111-0.\x80 (not push to avoid fail unexisting dir)
223263

224-
File::create_new(&path_invalid).unwrap();
264+
File::create_new(&path_invalid).unwrap();
225265

226-
assert!(path_invalid.exists());
227-
trash_ctx.delete(&path_invalid).unwrap();
228-
assert!(!path_invalid.exists());
266+
assert!(path_invalid.exists());
267+
trash_ctx.delete(&path_invalid).unwrap();
268+
assert!(!path_invalid.exists());
269+
}
229270
}
230271

231272
#[test]

0 commit comments

Comments
 (0)