@@ -9,17 +9,47 @@ use crate::{
99} ;
1010
1111use super :: InvokeContext ;
12- use serde:: { Deserialize , Serialize } ;
12+ use serde:: {
13+ de:: { Deserializer , Error as DeError } ,
14+ Deserialize , Serialize ,
15+ } ;
1316use tauri_macros:: { module_command_handler, CommandModule } ;
1417
1518use std:: {
1619 fs,
1720 fs:: File ,
1821 io:: Write ,
19- path:: { Path , PathBuf } ,
22+ path:: { Component , Path } ,
2023 sync:: Arc ,
2124} ;
2225
26+ pub struct SafePathBuf ( std:: path:: PathBuf ) ;
27+
28+ impl AsRef < Path > for SafePathBuf {
29+ fn as_ref ( & self ) -> & Path {
30+ self . 0 . as_ref ( )
31+ }
32+ }
33+
34+ impl < ' de > Deserialize < ' de > for SafePathBuf {
35+ fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
36+ where
37+ D : Deserializer < ' de > ,
38+ {
39+ let path = std:: path:: PathBuf :: deserialize ( deserializer) ?;
40+ if path. components ( ) . any ( |x| {
41+ matches ! (
42+ x,
43+ Component :: ParentDir | Component :: RootDir | Component :: Prefix ( _)
44+ )
45+ } ) {
46+ Err ( DeError :: custom ( "cannot traverse directory" ) )
47+ } else {
48+ Ok ( SafePathBuf ( path) )
49+ }
50+ }
51+ }
52+
2353/// The options for the directory functions on the file system API.
2454#[ derive( Debug , Clone , Deserialize ) ]
2555pub struct DirOperationOptions {
@@ -45,46 +75,46 @@ pub struct FileOperationOptions {
4575pub enum Cmd {
4676 /// The read text file API.
4777 ReadFile {
48- path : PathBuf ,
78+ path : SafePathBuf ,
4979 options : Option < FileOperationOptions > ,
5080 } ,
5181 /// The write file API.
5282 WriteFile {
53- path : PathBuf ,
83+ path : SafePathBuf ,
5484 contents : Vec < u8 > ,
5585 options : Option < FileOperationOptions > ,
5686 } ,
5787 /// The read dir API.
5888 ReadDir {
59- path : PathBuf ,
89+ path : SafePathBuf ,
6090 options : Option < DirOperationOptions > ,
6191 } ,
6292 /// The copy file API.
6393 CopyFile {
64- source : PathBuf ,
65- destination : PathBuf ,
94+ source : SafePathBuf ,
95+ destination : SafePathBuf ,
6696 options : Option < FileOperationOptions > ,
6797 } ,
6898 /// The create dir API.
6999 CreateDir {
70- path : PathBuf ,
100+ path : SafePathBuf ,
71101 options : Option < DirOperationOptions > ,
72102 } ,
73103 /// The remove dir API.
74104 RemoveDir {
75- path : PathBuf ,
105+ path : SafePathBuf ,
76106 options : Option < DirOperationOptions > ,
77107 } ,
78108 /// The remove file API.
79109 RemoveFile {
80- path : PathBuf ,
110+ path : SafePathBuf ,
81111 options : Option < FileOperationOptions > ,
82112 } ,
83113 /// The rename file API.
84114 #[ serde( rename_all = "camelCase" ) ]
85115 RenameFile {
86- old_path : PathBuf ,
87- new_path : PathBuf ,
116+ old_path : SafePathBuf ,
117+ new_path : SafePathBuf ,
88118 options : Option < FileOperationOptions > ,
89119 } ,
90120}
@@ -93,7 +123,7 @@ impl Cmd {
93123 #[ module_command_handler( fs_read_file, "fs > readFile" ) ]
94124 fn read_file < R : Runtime > (
95125 context : InvokeContext < R > ,
96- path : PathBuf ,
126+ path : SafePathBuf ,
97127 options : Option < FileOperationOptions > ,
98128 ) -> crate :: Result < Vec < u8 > > {
99129 file:: read_binary ( resolve_path (
@@ -109,7 +139,7 @@ impl Cmd {
109139 #[ module_command_handler( fs_write_file, "fs > writeFile" ) ]
110140 fn write_file < R : Runtime > (
111141 context : InvokeContext < R > ,
112- path : PathBuf ,
142+ path : SafePathBuf ,
113143 contents : Vec < u8 > ,
114144 options : Option < FileOperationOptions > ,
115145 ) -> crate :: Result < ( ) > {
@@ -127,7 +157,7 @@ impl Cmd {
127157 #[ module_command_handler( fs_read_dir, "fs > readDir" ) ]
128158 fn read_dir < R : Runtime > (
129159 context : InvokeContext < R > ,
130- path : PathBuf ,
160+ path : SafePathBuf ,
131161 options : Option < DirOperationOptions > ,
132162 ) -> crate :: Result < Vec < dir:: DiskEntry > > {
133163 let ( recursive, dir) = if let Some ( options_value) = options {
@@ -151,8 +181,8 @@ impl Cmd {
151181 #[ module_command_handler( fs_copy_file, "fs > copyFile" ) ]
152182 fn copy_file < R : Runtime > (
153183 context : InvokeContext < R > ,
154- source : PathBuf ,
155- destination : PathBuf ,
184+ source : SafePathBuf ,
185+ destination : SafePathBuf ,
156186 options : Option < FileOperationOptions > ,
157187 ) -> crate :: Result < ( ) > {
158188 let ( src, dest) = match options. and_then ( |o| o. dir ) {
@@ -181,7 +211,7 @@ impl Cmd {
181211 #[ module_command_handler( fs_create_dir, "fs > createDir" ) ]
182212 fn create_dir < R : Runtime > (
183213 context : InvokeContext < R > ,
184- path : PathBuf ,
214+ path : SafePathBuf ,
185215 options : Option < DirOperationOptions > ,
186216 ) -> crate :: Result < ( ) > {
187217 let ( recursive, dir) = if let Some ( options_value) = options {
@@ -208,7 +238,7 @@ impl Cmd {
208238 #[ module_command_handler( fs_remove_dir, "fs > removeDir" ) ]
209239 fn remove_dir < R : Runtime > (
210240 context : InvokeContext < R > ,
211- path : PathBuf ,
241+ path : SafePathBuf ,
212242 options : Option < DirOperationOptions > ,
213243 ) -> crate :: Result < ( ) > {
214244 let ( recursive, dir) = if let Some ( options_value) = options {
@@ -235,7 +265,7 @@ impl Cmd {
235265 #[ module_command_handler( fs_remove_file, "fs > removeFile" ) ]
236266 fn remove_file < R : Runtime > (
237267 context : InvokeContext < R > ,
238- path : PathBuf ,
268+ path : SafePathBuf ,
239269 options : Option < FileOperationOptions > ,
240270 ) -> crate :: Result < ( ) > {
241271 let resolved_path = resolve_path (
@@ -252,8 +282,8 @@ impl Cmd {
252282 #[ module_command_handler( fs_rename_file, "fs > renameFile" ) ]
253283 fn rename_file < R : Runtime > (
254284 context : InvokeContext < R > ,
255- old_path : PathBuf ,
256- new_path : PathBuf ,
285+ old_path : SafePathBuf ,
286+ new_path : SafePathBuf ,
257287 options : Option < FileOperationOptions > ,
258288 ) -> crate :: Result < ( ) > {
259289 let ( old, new) = match options. and_then ( |o| o. dir ) {
@@ -280,18 +310,18 @@ impl Cmd {
280310}
281311
282312#[ allow( dead_code) ]
283- fn resolve_path < R : Runtime , P : AsRef < Path > > (
313+ fn resolve_path < R : Runtime > (
284314 config : & Config ,
285315 package_info : & PackageInfo ,
286316 window : & Window < R > ,
287- path : P ,
317+ path : SafePathBuf ,
288318 dir : Option < BaseDirectory > ,
289- ) -> crate :: Result < PathBuf > {
319+ ) -> crate :: Result < SafePathBuf > {
290320 let env = window. state :: < Env > ( ) . inner ( ) ;
291321 match crate :: api:: path:: resolve_path ( config, package_info, env, path, dir) {
292322 Ok ( path) => {
293323 if window. state :: < Scopes > ( ) . fs . is_allowed ( & path) {
294- Ok ( path)
324+ Ok ( SafePathBuf ( path) )
295325 } else {
296326 Err ( crate :: Error :: PathNotAllowed ( path) )
297327 }
@@ -302,7 +332,7 @@ fn resolve_path<R: Runtime, P: AsRef<Path>>(
302332
303333#[ cfg( test) ]
304334mod tests {
305- use std:: path:: PathBuf ;
335+ use std:: path:: SafePathBuf ;
306336
307337 use super :: { BaseDirectory , DirOperationOptions , FileOperationOptions } ;
308338 use quickcheck:: { Arbitrary , Gen } ;
@@ -336,28 +366,32 @@ mod tests {
336366
337367 #[ tauri_macros:: module_command_test( fs_read_file, "fs > readFile" ) ]
338368 #[ quickcheck_macros:: quickcheck]
339- fn read_file ( path : PathBuf , options : Option < FileOperationOptions > ) {
369+ fn read_file ( path : SafePathBuf , options : Option < FileOperationOptions > ) {
340370 let res = super :: Cmd :: read_text_file ( crate :: test:: mock_invoke_context ( ) , path, options) ;
341371 assert ! ( !matches!( res, Err ( crate :: Error :: ApiNotAllowlisted ( _) ) ) ) ;
342372 }
343373
344374 #[ tauri_macros:: module_command_test( fs_write_file, "fs > writeFile" ) ]
345375 #[ quickcheck_macros:: quickcheck]
346- fn write_file ( path : PathBuf , contents : Vec < u8 > , options : Option < FileOperationOptions > ) {
376+ fn write_file ( path : SafePathBuf , contents : Vec < u8 > , options : Option < FileOperationOptions > ) {
347377 let res = super :: Cmd :: write_file ( crate :: test:: mock_invoke_context ( ) , path, contents, options) ;
348378 assert ! ( !matches!( res, Err ( crate :: Error :: ApiNotAllowlisted ( _) ) ) ) ;
349379 }
350380
351381 #[ tauri_macros:: module_command_test( fs_read_dir, "fs > readDir" ) ]
352382 #[ quickcheck_macros:: quickcheck]
353- fn read_dir ( path : PathBuf , options : Option < DirOperationOptions > ) {
383+ fn read_dir ( path : SafePathBuf , options : Option < DirOperationOptions > ) {
354384 let res = super :: Cmd :: read_dir ( crate :: test:: mock_invoke_context ( ) , path, options) ;
355385 assert ! ( !matches!( res, Err ( crate :: Error :: ApiNotAllowlisted ( _) ) ) ) ;
356386 }
357387
358388 #[ tauri_macros:: module_command_test( fs_copy_file, "fs > copyFile" ) ]
359389 #[ quickcheck_macros:: quickcheck]
360- fn copy_file ( source : PathBuf , destination : PathBuf , options : Option < FileOperationOptions > ) {
390+ fn copy_file (
391+ source : SafePathBuf ,
392+ destination : SafePathBuf ,
393+ options : Option < FileOperationOptions > ,
394+ ) {
361395 let res = super :: Cmd :: copy_file (
362396 crate :: test:: mock_invoke_context ( ) ,
363397 source,
@@ -369,28 +403,32 @@ mod tests {
369403
370404 #[ tauri_macros:: module_command_test( fs_create_dir, "fs > createDir" ) ]
371405 #[ quickcheck_macros:: quickcheck]
372- fn create_dir ( path : PathBuf , options : Option < DirOperationOptions > ) {
406+ fn create_dir ( path : SafePathBuf , options : Option < DirOperationOptions > ) {
373407 let res = super :: Cmd :: create_dir ( crate :: test:: mock_invoke_context ( ) , path, options) ;
374408 assert ! ( !matches!( res, Err ( crate :: Error :: ApiNotAllowlisted ( _) ) ) ) ;
375409 }
376410
377411 #[ tauri_macros:: module_command_test( fs_remove_dir, "fs > removeDir" ) ]
378412 #[ quickcheck_macros:: quickcheck]
379- fn remove_dir ( path : PathBuf , options : Option < DirOperationOptions > ) {
413+ fn remove_dir ( path : SafePathBuf , options : Option < DirOperationOptions > ) {
380414 let res = super :: Cmd :: remove_dir ( crate :: test:: mock_invoke_context ( ) , path, options) ;
381415 assert ! ( !matches!( res, Err ( crate :: Error :: ApiNotAllowlisted ( _) ) ) ) ;
382416 }
383417
384418 #[ tauri_macros:: module_command_test( fs_remove_file, "fs > removeFile" ) ]
385419 #[ quickcheck_macros:: quickcheck]
386- fn remove_file ( path : PathBuf , options : Option < FileOperationOptions > ) {
420+ fn remove_file ( path : SafePathBuf , options : Option < FileOperationOptions > ) {
387421 let res = super :: Cmd :: remove_file ( crate :: test:: mock_invoke_context ( ) , path, options) ;
388422 assert ! ( !matches!( res, Err ( crate :: Error :: ApiNotAllowlisted ( _) ) ) ) ;
389423 }
390424
391425 #[ tauri_macros:: module_command_test( fs_rename_file, "fs > renameFile" ) ]
392426 #[ quickcheck_macros:: quickcheck]
393- fn rename_file ( old_path : PathBuf , new_path : PathBuf , options : Option < FileOperationOptions > ) {
427+ fn rename_file (
428+ old_path : SafePathBuf ,
429+ new_path : SafePathBuf ,
430+ options : Option < FileOperationOptions > ,
431+ ) {
394432 let res = super :: Cmd :: rename_file (
395433 crate :: test:: mock_invoke_context ( ) ,
396434 old_path,
0 commit comments