Skip to content

Commit fab5b98

Browse files
committed
feat(turbo-tasks-fs): support multi denied_paths
1 parent 5cddef4 commit fab5b98

File tree

3 files changed

+46
-42
lines changed

3 files changed

+46
-42
lines changed

crates/next-api/src/project.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,10 +725,10 @@ impl Project {
725725
}
726726
};
727727

728-
Ok(DiskFileSystem::new_with_denied_path(
728+
Ok(DiskFileSystem::new_with_denied_paths(
729729
rcstr!(PROJECT_FILESYSTEM_NAME),
730730
self.root_path.clone(),
731-
denied_path,
731+
vec![denied_path],
732732
))
733733
}
734734

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

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

293293
impl 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]
637634
impl 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

turbopack/crates/turbopack-cli/src/util.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,11 @@ pub async fn project_fs(
6565
watch: bool,
6666
denied_root_path: RcStr,
6767
) -> Result<Vc<Box<dyn FileSystem>>> {
68-
let disk_fs =
69-
DiskFileSystem::new_with_denied_path(rcstr!("project"), project_dir, denied_root_path);
68+
let disk_fs = DiskFileSystem::new_with_denied_paths(
69+
rcstr!("project"),
70+
project_dir,
71+
vec![denied_root_path],
72+
);
7073
if watch {
7174
disk_fs.await?.start_watching(None).await?;
7275
}

0 commit comments

Comments
 (0)