Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 32 additions & 26 deletions turbopack/crates/turbo-tasks-fs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ struct DiskFileSystemInner {

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

impl DiskFileSystemInner {
Expand All @@ -303,24 +303,19 @@ impl DiskFileSystemInner {
/// Checks if a path is within the denied path
/// Returns true if the path should be treated as non-existent
///
/// Since denied_path is guaranteed to be:
/// Since denied_paths are guaranteed to be:
/// - normalized (no ../ traversals)
/// - using unix separators (/)
/// - relative to the fs root
///
/// We can efficiently check using string operations
fn is_path_denied(&self, path: &FileSystemPath) -> bool {
let Some(denied_path) = &self.denied_path else {
return false;
};
// If the path starts with the denied path then there are three cases
// * they are equal => denied
// * root relative path is a descendant which means the next character is a / => denied
// * anything else => not denied (covers denying `.next` but allowing `.next2`)
let path = &path.path;
path.starts_with(denied_path.as_str())
&& (path.len() == denied_path.len()
|| path.as_bytes().get(denied_path.len()) == Some(&b'/'))
self.denied_paths.iter().any(|denied_path| {
path.starts_with(denied_path.as_str())
&& (path.len() == denied_path.len()
|| path.as_bytes().get(denied_path.len()) == Some(&b'/'))
})
}

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

/// Create a new instance of `DiskFileSystem`.
Expand All @@ -656,14 +651,25 @@ impl DiskFileSystem {
normalize_path(&denied_path).as_deref() == Some(&*denied_path),
"denied_path must be normalized: {denied_path:?}"
);
Self::new_internal(name, root, Some(denied_path))
Self::new_internal(name, root, vec![denied_path])
}

pub fn new_with_denied_paths(name: RcStr, root: RcStr, denied_paths: Vec<RcStr>) -> Vc<Self> {
for denied_path in &denied_paths {
debug_assert!(!denied_path.is_empty(), "denied_path must not be empty");
debug_assert!(
normalize_path(denied_path).as_deref() == Some(&**denied_path),
"denied_path must be normalized: {denied_path:?}"
);
}
Self::new_internal(name, root, denied_paths)
}
}

#[turbo_tasks::value_impl]
impl DiskFileSystem {
#[turbo_tasks::function]
fn new_internal(name: RcStr, root: RcStr, denied_path: Option<RcStr>) -> Vc<Self> {
fn new_internal(name: RcStr, root: RcStr, denied_paths: Vec<RcStr>) -> Vc<Self> {
mark_stateful();

let instance = DiskFileSystem {
Expand All @@ -677,7 +683,7 @@ impl DiskFileSystem {
read_semaphore: create_read_semaphore(),
write_semaphore: create_write_semaphore(),
watcher: DiskWatcher::new(),
denied_path,
denied_paths,
}),
};

Expand Down Expand Up @@ -785,15 +791,18 @@ impl FileSystem for DiskFileSystem {
bail!(anyhow!(e).context(format!("reading dir {}", full_path.display())))
}
};
let denied_entry = match self.inner.denied_path.as_ref() {
Some(denied_path) => {
let dir_path = fs_path.path.as_str();
let denied_entries: FxHashSet<&str> = self
.inner
.denied_paths
.iter()
.filter_map(|denied_path| {
// If we have a denied path, we need to see if the current directory is a prefix of
// the denied path meaning that it is possible that some directory entry needs to be
// filtered. we do this first to avoid string manipulation on every
// iteration of the directory entries. So while expanding `foo/bar`,
// if `foo/bar/baz` is denied, we filter out `baz`.
// But if foo/bar/baz/qux is denied we don't filter anything from this level.
let dir_path = fs_path.path.as_str();
if denied_path.starts_with(dir_path) {
let denied_path_suffix =
if denied_path.as_bytes().get(dir_path.len()) == Some(&b'/') {
Expand All @@ -808,9 +817,8 @@ impl FileSystem for DiskFileSystem {
} else {
None
}
}
None => None,
};
})
.collect();

let entries = read_dir
.filter_map(|r| {
Expand All @@ -822,9 +830,7 @@ impl FileSystem for DiskFileSystem {
// we filter out any non unicode names
let file_name: RcStr = e.file_name().to_str()?.into();
// Filter out denied entries
if let Some(denied_name) = denied_entry
&& denied_name == file_name.as_str()
{
if denied_entries.contains(file_name.as_str()) {
return None;
}

Expand Down
Loading