use std::path::{Component, Path, PathBuf};
pub fn normalize_absolute(path: &Path) -> PathBuf {
let mut out = PathBuf::from("/");
for component in path.components() {
match component {
Component::RootDir | Component::Prefix(_) => {}
Component::CurDir => {}
Component::ParentDir => {
out.pop();
}
Component::Normal(part) => out.push(part),
}
}
out
}
pub fn join_under(base: &Path, logical: &Path) -> PathBuf {
let normalized = normalize_absolute(logical);
let relative = normalized
.strip_prefix("/")
.expect("normalized logical paths are absolute");
let joined = base.join(relative);
assert!(joined.starts_with(base));
joined
}
pub fn logical_parent(path: &Path) -> PathBuf {
assert!(path.is_absolute());
path.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("/"))
}
pub fn ancestor_dirs(path: &Path) -> Vec<PathBuf> {
assert!(path.is_absolute());
let mut dirs = Vec::new();
let mut current = PathBuf::from("/");
let parent = logical_parent(path);
for component in parent.components() {
if let Component::Normal(part) = component {
current.push(part);
dirs.push(current.clone());
}
}
dirs
}
pub fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for byte in bytes {
s.push(char::from_digit(u32::from(byte >> 4), 16).expect("a nibble is one hex digit"));
s.push(char::from_digit(u32::from(byte & 0xf), 16).expect("a nibble is one hex digit"));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalizes_dot_and_parent_components() {
assert_eq!(
normalize_absolute(Path::new("/a/./b/../c")),
Path::new("/a/c")
);
assert_eq!(
normalize_absolute(Path::new("/../../etc")),
Path::new("/etc")
);
assert_eq!(normalize_absolute(Path::new("a/b")), Path::new("/a/b"));
}
#[test]
fn join_under_cannot_escape_the_base() {
let base = Path::new("/out");
assert_eq!(
join_under(base, Path::new("/etc/passwd")),
Path::new("/out/etc/passwd")
);
assert_eq!(
join_under(base, Path::new("/../../etc")),
Path::new("/out/etc")
);
assert_eq!(join_under(base, Path::new("/")), Path::new("/out"));
}
#[test]
fn ancestors_are_listed_shallowest_first() {
assert_eq!(
ancestor_dirs(Path::new("/usr/lib/x86_64-linux-gnu/libc.so.6")),
vec![
PathBuf::from("/usr"),
PathBuf::from("/usr/lib"),
PathBuf::from("/usr/lib/x86_64-linux-gnu"),
]
);
assert!(ancestor_dirs(Path::new("/libc.so.6")).is_empty());
}
#[test]
fn hex_is_lowercase_and_padded() {
assert_eq!(hex(&[0x00, 0x0f, 0xff]), "000fff");
}
}