use crate::{Error, Result, Runtime};
use path_clean::PathClean;
use std::path::{Component, Path, PathBuf};
pub async fn validate_asset_security(
canonical: &Path,
cwd: &Path,
runtime: &dyn Runtime,
) -> Result<()> {
let cwd_canonical =
super::canonicalize_path(cwd, runtime)
.await
.map_err(|e| Error::IoError {
message: "Failed to canonicalize working directory".to_string(),
source: std::io::Error::other(format!("{e}")),
})?;
if canonical.starts_with(&cwd_canonical) {
return Ok(());
}
let has_node_modules = canonical
.components()
.any(|c| matches!(c, Component::Normal(s) if s == "node_modules"));
if has_node_modules {
let mut current = canonical;
while let Some(parent) = current.parent() {
if parent.file_name().and_then(|s| s.to_str()) == Some("node_modules") {
if let Some(nm_parent) = parent.parent() {
if nm_parent.starts_with(&cwd_canonical) {
return Ok(());
}
}
}
current = parent;
}
}
if let Some(monorepo_root) = find_monorepo_root(&cwd_canonical, runtime).await {
if canonical.starts_with(&monorepo_root) {
return Ok(());
}
}
Err(Error::AssetSecurityViolation {
path: canonical.display().to_string(),
reason: "Asset path is outside project directory and node_modules".to_string(),
})
}
pub async fn find_monorepo_root(cwd: &Path, runtime: &dyn Runtime) -> Option<PathBuf> {
let mut current = cwd;
loop {
if runtime.exists(¤t.join("pnpm-workspace.yaml")) {
return Some(current.to_path_buf());
}
if runtime.exists(¤t.join("lerna.json")) {
return Some(current.to_path_buf());
}
if let Ok(content) = runtime.read_file(¤t.join("package.json")).await {
if let Ok(pkg_json) = String::from_utf8(content) {
if pkg_json.contains("\"workspaces\"") {
return Some(current.to_path_buf());
}
}
}
current = current.parent()?;
}
}
pub(crate) async fn canonicalize_path(path: &Path, runtime: &dyn Runtime) -> Result<PathBuf> {
if !runtime.exists(path) {
return Err(Error::AssetNotFound {
specifier: path.display().to_string(),
searched_from: "".to_string(),
});
}
#[cfg(not(target_family = "wasm"))]
{
if let Ok(canonical) = path.canonicalize() {
return Ok(canonical);
}
}
Ok(path.to_path_buf().clean())
}