use std::path::{Component, Path};
use crate::error::{Error, Result};
pub fn reject_traversal(rel_path: &Path) -> Result<()> {
if rel_path.is_absolute() {
return Err(Error::PathEscapesRoot {
path: rel_path.to_path_buf(),
});
}
for component in rel_path.components() {
match component {
Component::ParentDir | Component::RootDir | Component::Prefix(_) => {
return Err(Error::PathEscapesRoot {
path: rel_path.to_path_buf(),
});
}
Component::Normal(_) | Component::CurDir => {}
}
}
Ok(())
}
pub fn is_symlink(abs_path: &Path) -> bool {
std::fs::symlink_metadata(abs_path)
.map(|m| m.file_type().is_symlink())
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn rejects_parent_dir() {
assert!(reject_traversal(&PathBuf::from("../evil.md")).is_err());
assert!(reject_traversal(&PathBuf::from("docs/../../evil.md")).is_err());
}
#[test]
fn rejects_absolute() {
assert!(reject_traversal(&PathBuf::from("/etc/passwd")).is_err());
}
#[test]
fn accepts_legitimate() {
assert!(reject_traversal(&PathBuf::from("docs/a.md")).is_ok());
assert!(reject_traversal(&PathBuf::from("./docs/a.md")).is_ok());
assert!(reject_traversal(&PathBuf::from("a.md")).is_ok());
}
}