@@ -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) ]
184206mod 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