zagens_runtime_adapters/tools/
path.rs1use std::path::{Component, Path, PathBuf};
4
5#[must_use]
7pub fn path_has_prefix(path: &Path, prefix: &Path) -> bool {
8 strip_verbatim_prefix(path).starts_with(strip_verbatim_prefix(prefix))
9}
10
11#[must_use]
12fn strip_verbatim_prefix(path: &Path) -> PathBuf {
13 #[cfg(windows)]
14 {
15 let s = path.display().to_string();
16 if let Some(rest) = s.strip_prefix(r"\\?\UNC\") {
17 return PathBuf::from(format!(r"\\{rest}"));
18 }
19 if let Some(rest) = s.strip_prefix(r"\\?\") {
20 return PathBuf::from(rest);
21 }
22 }
23 path.to_path_buf()
24}
25
26#[must_use]
28pub fn normalize_path(path: &Path) -> PathBuf {
29 let mut prefix: Option<std::ffi::OsString> = None;
30 let mut is_root = false;
31 let mut stack: Vec<std::ffi::OsString> = Vec::new();
32
33 for component in path.components() {
34 match component {
35 Component::Prefix(prefix_component) => {
36 prefix = Some(prefix_component.as_os_str().to_owned());
37 }
38 Component::RootDir => {
39 is_root = true;
40 }
41 Component::CurDir => {}
42 Component::ParentDir => {
43 let parent = Component::ParentDir.as_os_str();
44 if let Some(last) = stack.pop() {
45 if last == parent {
46 stack.push(last);
47 stack.push(parent.to_owned());
48 }
49 } else if !is_root {
50 stack.push(parent.to_owned());
51 }
52 }
53 Component::Normal(part) => {
54 stack.push(part.to_owned());
55 }
56 }
57 }
58
59 let mut normalized = PathBuf::new();
60 if let Some(prefix) = prefix {
61 normalized.push(prefix);
62 }
63 if is_root {
64 normalized.push(Path::new(std::path::MAIN_SEPARATOR_STR));
65 }
66 for part in stack {
67 normalized.push(part);
68 }
69 normalized
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn normalize_path_resolves_parent() {
78 let normalized = normalize_path(Path::new("new/../safe.txt"));
79 assert!(normalized.ends_with("safe.txt"));
80 }
81}