use std::path::{Component, Path};
pub(crate) fn reject_traversal(candidate: &Path) -> Result<(), String> {
for component in candidate.components() {
match component {
Component::Prefix(_) | Component::RootDir => {
return Err(format!(
"{} must be relative to the prompts directory",
candidate.display()
));
}
Component::ParentDir => {
return Err(format!(
"{} must not climb above the prompts directory with `..`",
candidate.display()
));
}
Component::CurDir | Component::Normal(_) => {}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use super::reject_traversal;
#[test]
fn an_absolute_pattern_is_refused() {
let absolute = if cfg!(windows) {
PathBuf::from(r"C:\etc\secrets\*.md")
} else {
PathBuf::from("/etc/secrets/*.md")
};
let err = reject_traversal(&absolute).expect_err("an absolute pattern escapes the root");
assert!(err.contains("relative"), "{err}");
}
#[test]
fn a_parent_dir_component_is_refused() {
let err = reject_traversal(Path::new("../outside/*.md"))
.expect_err("a `..` component climbs above the root");
assert!(err.contains(".."), "{err}");
}
#[test]
fn a_nested_parent_dir_component_is_refused() {
let err = reject_traversal(Path::new("governance/../../escape.md"))
.expect_err("a `..` anywhere climbs above the root");
assert!(err.contains(".."), "{err}");
}
#[test]
fn a_plain_relative_pattern_is_allowed() {
reject_traversal(Path::new("governance/**/*.md")).expect("a relative pattern is fine");
reject_traversal(Path::new("./local.md")).expect("a leading ./ is fine");
}
}