use std::path::{Component, Path, PathBuf};
pub(crate) fn lexical_normalize(path: &Path) -> PathBuf {
let mut normalized = PathBuf::new();
for component in path.components() {
match component {
Component::CurDir => {}
Component::ParentDir => {
normalized.pop();
}
other => normalized.push(other.as_os_str()),
}
}
normalized
}
#[cfg(test)]
mod tests {
use super::lexical_normalize;
use std::path::{Path, PathBuf};
#[test]
fn normalizes_absolute_path() {
let root = Path::new(std::path::MAIN_SEPARATOR_STR);
let path = root
.join("alpha")
.join(".")
.join("beta")
.join("..")
.join("gamma");
assert_eq!(lexical_normalize(&path), root.join("alpha").join("gamma"));
}
#[test]
fn clamps_relative_parent_traversal() {
assert_eq!(
lexical_normalize(Path::new("../../alpha/../beta")),
PathBuf::from("beta")
);
}
#[test]
fn clamps_rooted_parent_traversal_at_root() {
let root = Path::new(std::path::MAIN_SEPARATOR_STR);
assert_eq!(lexical_normalize(&root.join("..")), root.to_path_buf());
assert_eq!(
lexical_normalize(&root.join("alpha").join("..").join("..")),
root.to_path_buf()
);
}
#[test]
fn removes_dots_without_changing_components() {
assert_eq!(
lexical_normalize(Path::new("./alpha/./beta")),
PathBuf::from("alpha/beta")
);
}
#[test]
fn preserves_normal_components() {
let path = Path::new("alpha").join("with space").join("café");
assert_eq!(lexical_normalize(&path), path);
}
}