use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use crate::engine::wasm::host_state::HostState;
pub(super) const HOME_SCHEME: &str = "home://";
pub(super) const CWD_SCHEME: &str = "cwd://";
pub(super) const TMP_PREFIX: &str = "/tmp/";
fn make_relative(requested: &str) -> &Path {
let path = Path::new(requested);
let mut components = path.components();
while let Some(c) = components.clone().next() {
if matches!(c, Component::RootDir | Component::Prefix(_)) {
components.next();
} else {
break;
}
}
components.as_path()
}
pub(super) struct ResolvedPhysical {
pub(super) physical: PathBuf,
pub(super) canonical_root: PathBuf,
}
pub(super) fn resolve_physical_absolute(
root: &Path,
requested: &str,
) -> Result<ResolvedPhysical, String> {
let canonical_root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
let relative_requested = make_relative(requested);
let joined = canonical_root.join(relative_requested);
let mut current_check = joined.clone();
let mut unexisting_components = Vec::new();
loop {
if std::fs::symlink_metadata(¤t_check).is_ok() {
let canonical =
std::fs::canonicalize(¤t_check).unwrap_or_else(|_| current_check.clone());
let mut final_path = canonical;
for comp in unexisting_components.into_iter().rev() {
final_path.push(comp);
}
if !final_path.starts_with(&canonical_root) {
return Err(format!(
"path escapes root boundary: {requested} resolves to {}",
final_path.display()
));
}
return Ok(ResolvedPhysical {
physical: final_path,
canonical_root,
});
}
if let Some(parent) = current_check.parent() {
if let Some(file_name) = current_check.file_name() {
unexisting_components.push(file_name.to_os_string());
}
current_check = parent.to_path_buf();
} else {
break;
}
}
if !joined.starts_with(&canonical_root) {
return Err(format!(
"path escapes root boundary: {requested} resolves to {}",
joined.display()
));
}
Ok(ResolvedPhysical {
physical: joined,
canonical_root,
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum VfsTarget {
Workspace,
Home,
Tmp,
}
pub(super) struct ResolvedPath {
pub(super) physical: PathBuf,
pub(super) relative: PathBuf,
pub(super) target: VfsTarget,
}
pub(super) struct ResolvedVfsPath {
pub(super) relative: PathBuf,
pub(super) vfs: Arc<dyn astrid_vfs::Vfs>,
pub(super) handle: astrid_capabilities::DirHandle,
}
pub(super) fn resolve_path(state: &HostState, raw_path: &str) -> Result<ResolvedPath, String> {
if let Some(stripped) = raw_path.strip_prefix(CWD_SCHEME) {
let resolved = resolve_physical_absolute(&state.workspace_root, stripped)?;
let relative = resolved
.physical
.strip_prefix(&resolved.canonical_root)
.map_err(|_| "resolved cwd path escaped canonical root".to_string())?
.to_path_buf();
Ok(ResolvedPath {
physical: resolved.physical,
relative,
target: VfsTarget::Workspace,
})
} else if let Some(stripped) = raw_path.strip_prefix(HOME_SCHEME) {
let home = state
.effective_home()
.ok_or_else(|| "home:// scheme is not available for this principal".to_string())?;
let resolved = resolve_physical_absolute(&home.root, stripped)?;
let relative = resolved
.physical
.strip_prefix(&resolved.canonical_root)
.map_err(|_| "resolved home path escaped canonical root".to_string())?
.to_path_buf();
Ok(ResolvedPath {
physical: resolved.physical,
relative,
target: VfsTarget::Home,
})
} else if raw_path.starts_with(TMP_PREFIX) || raw_path == "/tmp" {
let tmp_mount = state
.effective_tmp()
.ok_or_else(|| "/tmp is not available for this principal".to_string())?;
let stripped = raw_path
.strip_prefix(TMP_PREFIX)
.or_else(|| raw_path.strip_prefix("/tmp"))
.unwrap_or("");
let resolved = resolve_physical_absolute(&tmp_mount.root, stripped)?;
let relative = resolved
.physical
.strip_prefix(&resolved.canonical_root)
.map_err(|_| "resolved /tmp path escaped canonical root".to_string())?
.to_path_buf();
Ok(ResolvedPath {
physical: resolved.physical,
relative,
target: VfsTarget::Tmp,
})
} else {
let resolved = resolve_physical_absolute(&state.workspace_root, raw_path)?;
let relative = resolved
.physical
.strip_prefix(&resolved.canonical_root)
.map_err(|_| "resolved path escaped canonical root".to_string())?
.to_path_buf();
Ok(ResolvedPath {
physical: resolved.physical,
relative,
target: VfsTarget::Workspace,
})
}
}
pub(super) fn resolve_vfs(
state: &HostState,
resolved: &ResolvedPath,
) -> Result<ResolvedVfsPath, String> {
let (vfs, handle) = match resolved.target {
VfsTarget::Home => {
let m = state
.effective_home()
.ok_or_else(|| "home:// VFS is not mounted".to_string())?;
(m.vfs.clone(), m.handle.clone())
},
VfsTarget::Tmp => {
let m = state
.effective_tmp()
.ok_or_else(|| "/tmp VFS is not mounted".to_string())?;
(m.vfs.clone(), m.handle.clone())
},
VfsTarget::Workspace => (state.vfs.clone(), state.vfs_root_handle.clone()),
};
Ok(ResolvedVfsPath {
relative: resolved.relative.clone(),
vfs,
handle,
})
}