@@ -285,9 +285,9 @@ struct DiskFileSystemInner {
285285
286286 #[ turbo_tasks( debug_ignore, trace_ignore) ]
287287 watcher : DiskWatcher ,
288- /// A root path that we do not allow access to from this filesystem.
288+ /// Root paths that we do not allow access to from this filesystem.
289289 /// Useful for things like output directories to prevent accidental ouroboros situations.
290- denied_path : Option < RcStr > ,
290+ denied_paths : Vec < RcStr > ,
291291}
292292
293293impl DiskFileSystemInner {
@@ -300,24 +300,19 @@ impl DiskFileSystemInner {
300300 /// Checks if a path is within the denied path
301301 /// Returns true if the path should be treated as non-existent
302302 ///
303- /// Since denied_path is guaranteed to be:
303+ /// Since denied_paths are guaranteed to be:
304304 /// - normalized (no ../ traversals)
305305 /// - using unix separators (/)
306306 /// - relative to the fs root
307307 ///
308308 /// We can efficiently check using string operations
309309 fn is_path_denied ( & self , path : & FileSystemPath ) -> bool {
310- let Some ( denied_path) = & self . denied_path else {
311- return false ;
312- } ;
313- // If the path starts with the denied path then there are three cases
314- // * they are equal => denied
315- // * root relative path is a descendant which means the next character is a / => denied
316- // * anything else => not denied (covers denying `.next` but allowing `.next2`)
317310 let path = & path. path ;
318- path. starts_with ( denied_path. as_str ( ) )
319- && ( path. len ( ) == denied_path. len ( )
320- || path. as_bytes ( ) . get ( denied_path. len ( ) ) == Some ( & b'/' ) )
311+ self . denied_paths . iter ( ) . any ( |denied_path| {
312+ path. starts_with ( denied_path. as_str ( ) )
313+ && ( path. len ( ) == denied_path. len ( )
314+ || path. as_bytes ( ) . get ( denied_path. len ( ) ) == Some ( & b'/' ) )
315+ } )
321316 }
322317
323318 /// registers the path as an invalidator for the current task,
@@ -612,7 +607,7 @@ impl DiskFileSystem {
612607 /// * `root` - Path to the given filesystem's root. Should be
613608 /// [canonicalized][std::fs::canonicalize].
614609 pub fn new ( name : RcStr , root : RcStr ) -> Vc < Self > {
615- Self :: new_internal ( name, root, None )
610+ Self :: new_internal ( name, root, Vec :: new ( ) )
616611 }
617612
618613 /// Create a new instance of `DiskFileSystem`.
@@ -621,22 +616,24 @@ impl DiskFileSystem {
621616 /// * `name` - Name of the filesystem.
622617 /// * `root` - Path to the given filesystem's root. Should be
623618 /// [canonicalized][std::fs::canonicalize].
624- /// * `denied_path` - A path within this filesystem that is not allowed to be accessed or
625- /// navigated into. This must be normalized, non-empty and relative to the fs root.
626- pub fn new_with_denied_path ( name : RcStr , root : RcStr , denied_path : RcStr ) -> Vc < Self > {
627- debug_assert ! ( !denied_path. is_empty( ) , "denied_path must not be empty" ) ;
628- debug_assert ! (
629- normalize_path( & denied_path) . as_deref( ) == Some ( & * denied_path) ,
630- "denied_path must be normalized: {denied_path:?}"
631- ) ;
632- Self :: new_internal ( name, root, Some ( denied_path) )
619+ /// * `denied_paths` - Paths within this filesystem that are not allowed to be accessed or
620+ /// navigated into. These must be normalized, non-empty and relative to the fs root.
621+ pub fn new_with_denied_paths ( name : RcStr , root : RcStr , denied_paths : Vec < RcStr > ) -> Vc < Self > {
622+ for denied_path in & denied_paths {
623+ debug_assert ! ( !denied_path. is_empty( ) , "denied_path must not be empty" ) ;
624+ debug_assert ! (
625+ normalize_path( denied_path) . as_deref( ) == Some ( & * * denied_path) ,
626+ "denied_path must be normalized: {denied_path:?}"
627+ ) ;
628+ }
629+ Self :: new_internal ( name, root, denied_paths)
633630 }
634631}
635632
636633#[ turbo_tasks:: value_impl]
637634impl DiskFileSystem {
638635 #[ turbo_tasks:: function]
639- fn new_internal ( name : RcStr , root : RcStr , denied_path : Option < RcStr > ) -> Vc < Self > {
636+ fn new_internal ( name : RcStr , root : RcStr , denied_paths : Vec < RcStr > ) -> Vc < Self > {
640637 mark_stateful ( ) ;
641638
642639 let instance = DiskFileSystem {
@@ -650,7 +647,7 @@ impl DiskFileSystem {
650647 read_semaphore : create_read_semaphore ( ) ,
651648 write_semaphore : create_write_semaphore ( ) ,
652649 watcher : DiskWatcher :: new ( ) ,
653- denied_path ,
650+ denied_paths ,
654651 } ) ,
655652 } ;
656653
@@ -732,15 +729,18 @@ impl FileSystem for DiskFileSystem {
732729 bail ! ( anyhow!( e) . context( format!( "reading dir {}" , full_path. display( ) ) ) )
733730 }
734731 } ;
735- let denied_entry = match self . inner . denied_path . as_ref ( ) {
736- Some ( denied_path) => {
732+ let dir_path = fs_path. path . as_str ( ) ;
733+ let denied_entries: FxHashSet < & str > = self
734+ . inner
735+ . denied_paths
736+ . iter ( )
737+ . filter_map ( |denied_path| {
737738 // If we have a denied path, we need to see if the current directory is a prefix of
738739 // the denied path meaning that it is possible that some directory entry needs to be
739740 // filtered. we do this first to avoid string manipulation on every
740741 // iteration of the directory entries. So while expanding `foo/bar`,
741742 // if `foo/bar/baz` is denied, we filter out `baz`.
742743 // But if foo/bar/baz/qux is denied we don't filter anything from this level.
743- let dir_path = fs_path. path . as_str ( ) ;
744744 if denied_path. starts_with ( dir_path) {
745745 let denied_path_suffix =
746746 if denied_path. as_bytes ( ) . get ( dir_path. len ( ) ) == Some ( & b'/' ) {
@@ -755,9 +755,8 @@ impl FileSystem for DiskFileSystem {
755755 } else {
756756 None
757757 }
758- }
759- None => None ,
760- } ;
758+ } )
759+ . collect ( ) ;
761760
762761 let entries = read_dir
763762 . filter_map ( |r| {
@@ -769,9 +768,7 @@ impl FileSystem for DiskFileSystem {
769768 // we filter out any non unicode names
770769 let file_name: RcStr = e. file_name ( ) . to_str ( ) ?. into ( ) ;
771770 // Filter out denied entries
772- if let Some ( denied_name) = denied_entry
773- && denied_name == file_name. as_str ( )
774- {
771+ if denied_entries. contains ( file_name. as_str ( ) ) {
775772 return None ;
776773 }
777774
@@ -3121,7 +3118,8 @@ mod tests {
31213118 ) ) ;
31223119
31233120 tt. run_once ( async {
3124- let fs = DiskFileSystem :: new_with_denied_path ( rcstr ! ( "test" ) , root, denied_path) ;
3121+ let fs =
3122+ DiskFileSystem :: new_with_denied_paths ( rcstr ! ( "test" ) , root, vec ! [ denied_path] ) ;
31253123 let root_path = fs. root ( ) . await ?;
31263124
31273125 // Test 1: Reading allowed file should work
@@ -3172,7 +3170,8 @@ mod tests {
31723170 ) ) ;
31733171
31743172 tt. run_once ( async {
3175- let fs = DiskFileSystem :: new_with_denied_path ( rcstr ! ( "test" ) , root, denied_path) ;
3173+ let fs =
3174+ DiskFileSystem :: new_with_denied_paths ( rcstr ! ( "test" ) , root, vec ! [ denied_path] ) ;
31763175 let root_path = fs. root ( ) . await ?;
31773176
31783177 // Test: read_dir on root should not include denied_dir
@@ -3222,7 +3221,8 @@ mod tests {
32223221 ) ) ;
32233222
32243223 tt. run_once ( async {
3225- let fs = DiskFileSystem :: new_with_denied_path ( rcstr ! ( "test" ) , root, denied_path) ;
3224+ let fs =
3225+ DiskFileSystem :: new_with_denied_paths ( rcstr ! ( "test" ) , root, vec ! [ denied_path] ) ;
32263226 let root_path = fs. root ( ) . await ?;
32273227
32283228 // Test: read_glob with ** should not reveal denied files
@@ -3288,7 +3288,8 @@ mod tests {
32883288 ) ) ;
32893289
32903290 tt. run_once ( async {
3291- let fs = DiskFileSystem :: new_with_denied_path ( rcstr ! ( "test" ) , root, denied_path) ;
3291+ let fs =
3292+ DiskFileSystem :: new_with_denied_paths ( rcstr ! ( "test" ) , root, vec ! [ denied_path] ) ;
32923293 let root_path = fs. root ( ) . await ?;
32933294
32943295 // Test 1: Writing to allowed directory should work
0 commit comments