use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct VirtualFs {
mappings: Vec<(PathBuf, PathBuf)>,
}
impl VirtualFs {
pub fn new(workspace_root: &Path) -> Self {
let mappings = vec![
(PathBuf::from("/work"), workspace_root.join("work")),
(PathBuf::from("/tmp"), workspace_root.join("tmp")),
(PathBuf::from("/home"), workspace_root.join("home")),
];
Self { mappings }
}
pub fn empty() -> Self {
Self {
mappings: Vec::new(),
}
}
pub fn add_mapping(&mut self, virtual_path: impl Into<PathBuf>, real_path: impl Into<PathBuf>) {
self.mappings.push((virtual_path.into(), real_path.into()));
}
pub fn translate(&self, path: &str) -> Option<PathBuf> {
let path = Path::new(path);
for (virtual_prefix, real_prefix) in &self.mappings {
if let Ok(suffix) = path.strip_prefix(virtual_prefix) {
return Some(real_prefix.join(suffix));
}
}
None
}
pub fn is_allowed(&self, path: &str) -> bool {
let path = Path::new(path);
for (virtual_prefix, _) in &self.mappings {
if path.starts_with(virtual_prefix) {
return true;
}
}
let system_prefixes = ["/usr", "/bin", "/lib", "/lib64", "/etc", "/proc", "/dev"];
for prefix in &system_prefixes {
if path.starts_with(prefix) {
return true;
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_mappings() {
let vfs = VirtualFs::new(Path::new("/tmp/evalbox-abc123"));
assert_eq!(
vfs.translate("/work/main.py"),
Some(PathBuf::from("/tmp/evalbox-abc123/work/main.py"))
);
assert_eq!(
vfs.translate("/tmp/output.txt"),
Some(PathBuf::from("/tmp/evalbox-abc123/tmp/output.txt"))
);
assert_eq!(
vfs.translate("/home/.bashrc"),
Some(PathBuf::from("/tmp/evalbox-abc123/home/.bashrc"))
);
}
#[test]
fn no_translation_for_system_paths() {
let vfs = VirtualFs::new(Path::new("/tmp/evalbox-abc123"));
assert_eq!(vfs.translate("/usr/bin/python3"), None);
assert_eq!(vfs.translate("/etc/passwd"), None);
}
#[test]
fn is_allowed_checks() {
let vfs = VirtualFs::new(Path::new("/tmp/evalbox-abc123"));
assert!(vfs.is_allowed("/work/test.py"));
assert!(vfs.is_allowed("/tmp/output"));
assert!(vfs.is_allowed("/usr/bin/python3"));
assert!(vfs.is_allowed("/etc/passwd"));
assert!(vfs.is_allowed("/proc/self/status"));
assert!(!vfs.is_allowed("/root/.ssh/id_rsa"));
assert!(!vfs.is_allowed("/var/log/syslog"));
}
#[test]
fn custom_mapping() {
let mut vfs = VirtualFs::empty();
vfs.add_mapping("/data", "/mnt/shared/data");
assert_eq!(
vfs.translate("/data/file.csv"),
Some(PathBuf::from("/mnt/shared/data/file.csv"))
);
assert_eq!(vfs.translate("/work/test"), None);
}
}