Skip to content

Commit f17cccb

Browse files
committed
feat: support multiple denied paths in DiskFileSystem and use AutoSet
1 parent 3cb6c0f commit f17cccb

File tree

1 file changed

+32
-26
lines changed
  • turbopack/crates/turbo-tasks-fs/src

1 file changed

+32
-26
lines changed

turbopack/crates/turbo-tasks-fs/src/lib.rs

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ struct DiskFileSystemInner {
288288

289289
#[turbo_tasks(debug_ignore, trace_ignore)]
290290
watcher: DiskWatcher,
291-
/// A root path that we do not allow access to from this filesystem.
291+
/// Root paths that we do not allow access to from this filesystem.
292292
/// Useful for things like output directories to prevent accidental ouroboros situations.
293-
denied_path: Option<RcStr>,
293+
denied_paths: Vec<RcStr>,
294294
}
295295

296296
impl DiskFileSystemInner {
@@ -303,24 +303,19 @@ impl DiskFileSystemInner {
303303
/// Checks if a path is within the denied path
304304
/// Returns true if the path should be treated as non-existent
305305
///
306-
/// Since denied_path is guaranteed to be:
306+
/// Since denied_paths are guaranteed to be:
307307
/// - normalized (no ../ traversals)
308308
/// - using unix separators (/)
309309
/// - relative to the fs root
310310
///
311311
/// We can efficiently check using string operations
312312
fn is_path_denied(&self, path: &FileSystemPath) -> bool {
313-
let Some(denied_path) = &self.denied_path else {
314-
return false;
315-
};
316-
// If the path starts with the denied path then there are three cases
317-
// * they are equal => denied
318-
// * root relative path is a descendant which means the next character is a / => denied
319-
// * anything else => not denied (covers denying `.next` but allowing `.next2`)
320313
let path = &path.path;
321-
path.starts_with(denied_path.as_str())
322-
&& (path.len() == denied_path.len()
323-
|| path.as_bytes().get(denied_path.len()) == Some(&b'/'))
314+
self.denied_paths.iter().any(|denied_path| {
315+
path.starts_with(denied_path.as_str())
316+
&& (path.len() == denied_path.len()
317+
|| path.as_bytes().get(denied_path.len()) == Some(&b'/'))
318+
})
324319
}
325320

326321
/// registers the path as an invalidator for the current task,
@@ -639,7 +634,7 @@ impl DiskFileSystem {
639634
/// * `root` - Path to the given filesystem's root. Should be
640635
/// [canonicalized][std::fs::canonicalize].
641636
pub fn new(name: RcStr, root: RcStr) -> Vc<Self> {
642-
Self::new_internal(name, root, None)
637+
Self::new_internal(name, root, Vec::new())
643638
}
644639

645640
/// Create a new instance of `DiskFileSystem`.
@@ -656,14 +651,25 @@ impl DiskFileSystem {
656651
normalize_path(&denied_path).as_deref() == Some(&*denied_path),
657652
"denied_path must be normalized: {denied_path:?}"
658653
);
659-
Self::new_internal(name, root, Some(denied_path))
654+
Self::new_internal(name, root, vec![denied_path])
655+
}
656+
657+
pub fn new_with_denied_paths(name: RcStr, root: RcStr, denied_paths: Vec<RcStr>) -> Vc<Self> {
658+
for denied_path in &denied_paths {
659+
debug_assert!(!denied_path.is_empty(), "denied_path must not be empty");
660+
debug_assert!(
661+
normalize_path(denied_path).as_deref() == Some(&**denied_path),
662+
"denied_path must be normalized: {denied_path:?}"
663+
);
664+
}
665+
Self::new_internal(name, root, denied_paths)
660666
}
661667
}
662668

663669
#[turbo_tasks::value_impl]
664670
impl DiskFileSystem {
665671
#[turbo_tasks::function]
666-
fn new_internal(name: RcStr, root: RcStr, denied_path: Option<RcStr>) -> Vc<Self> {
672+
fn new_internal(name: RcStr, root: RcStr, denied_paths: Vec<RcStr>) -> Vc<Self> {
667673
mark_stateful();
668674

669675
let instance = DiskFileSystem {
@@ -677,7 +683,7 @@ impl DiskFileSystem {
677683
read_semaphore: create_read_semaphore(),
678684
write_semaphore: create_write_semaphore(),
679685
watcher: DiskWatcher::new(),
680-
denied_path,
686+
denied_paths,
681687
}),
682688
};
683689

@@ -785,15 +791,18 @@ impl FileSystem for DiskFileSystem {
785791
bail!(anyhow!(e).context(format!("reading dir {}", full_path.display())))
786792
}
787793
};
788-
let denied_entry = match self.inner.denied_path.as_ref() {
789-
Some(denied_path) => {
794+
let dir_path = fs_path.path.as_str();
795+
let denied_entries: FxHashSet<&str> = self
796+
.inner
797+
.denied_paths
798+
.iter()
799+
.filter_map(|denied_path| {
790800
// If we have a denied path, we need to see if the current directory is a prefix of
791801
// the denied path meaning that it is possible that some directory entry needs to be
792802
// filtered. we do this first to avoid string manipulation on every
793803
// iteration of the directory entries. So while expanding `foo/bar`,
794804
// if `foo/bar/baz` is denied, we filter out `baz`.
795805
// But if foo/bar/baz/qux is denied we don't filter anything from this level.
796-
let dir_path = fs_path.path.as_str();
797806
if denied_path.starts_with(dir_path) {
798807
let denied_path_suffix =
799808
if denied_path.as_bytes().get(dir_path.len()) == Some(&b'/') {
@@ -808,9 +817,8 @@ impl FileSystem for DiskFileSystem {
808817
} else {
809818
None
810819
}
811-
}
812-
None => None,
813-
};
820+
})
821+
.collect();
814822

815823
let entries = read_dir
816824
.filter_map(|r| {
@@ -822,9 +830,7 @@ impl FileSystem for DiskFileSystem {
822830
// we filter out any non unicode names
823831
let file_name: RcStr = e.file_name().to_str()?.into();
824832
// Filter out denied entries
825-
if let Some(denied_name) = denied_entry
826-
&& denied_name == file_name.as_str()
827-
{
833+
if denied_entries.contains(file_name.as_str()) {
828834
return None;
829835
}
830836

0 commit comments

Comments
 (0)