summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs63
1 files changed, 54 insertions, 9 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 6470322..581928e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,7 @@ use std::{env, path::{Component, Path, PathBuf}};
pub enum NormalizeError {
EmptyInput,
CwdNotAbsolute,
+ InputOutsideFileSystemRoot,
}
pub fn normalize_path(root: &Path, origin: &Path, input: &Path) -> Result<PathBuf, NormalizeError> {
@@ -35,20 +36,54 @@ fn normalize_path_with_preset_cwd(
} else {
cwd.join(root)
};
- let mut stack = Vec::new();
- for component in input.components() {
+ let normalized_input = normalize_lexically(&input)?;
+ Ok(normalize_to_root(normalized_input, &root))
+}
+
+fn normalize_lexically(path: &Path) -> Result<PathBuf, NormalizeError> {
+ let mut lexical = PathBuf::new();
+ let mut iter = path.components().peekable();
+
+ // Find the root, if any, and add it to the lexical path.
+ // Here we treat the Windows path "C:\" as a single "root" even though
+ // `components` splits it into two: (Prefix, RootDir).
+ let root = match iter.peek() {
+ Some(Component::ParentDir) => return Err(NormalizeError::InputOutsideFileSystemRoot),
+ Some(p @ Component::RootDir) | Some(p @ Component::CurDir) => {
+ lexical.push(p);
+ iter.next();
+ lexical.as_os_str().len()
+ }
+ Some(Component::Prefix(prefix)) => {
+ lexical.push(prefix.as_os_str());
+ iter.next();
+ if let Some(p @ Component::RootDir) = iter.peek() {
+ lexical.push(p);
+ iter.next();
+ }
+ lexical.as_os_str().len()
+ }
+ None => return Ok(PathBuf::new()),
+ Some(Component::Normal(_)) => 0,
+ };
+
+ for component in iter {
match component {
- Component::CurDir => (),
+ Component::RootDir => unreachable!(),
+ Component::Prefix(_) => return Err(NormalizeError::InputOutsideFileSystemRoot),
+ Component::CurDir => continue,
Component::ParentDir => {
- stack.pop();
+ // It's an error if ParentDir causes us to go above the "root".
+ if lexical.as_os_str().len() == root {
+ return Err(NormalizeError::InputOutsideFileSystemRoot);
+ } else {
+ lexical.pop();
+ }
}
- Component::Prefix(_) => stack.push(component),
- Component::Normal(_) => stack.push(component),
- Component::RootDir => stack.push(component),
+ Component::Normal(path) => lexical.push(path),
}
}
- let normalized_input = PathBuf::from_iter(stack);
- Ok(normalize_to_root(normalized_input, &root))
+ Ok(lexical)
}
fn normalize_to_root(target: PathBuf, mut root: &Path) -> PathBuf {
@@ -153,4 +188,14 @@ mod tests {
let result = normalize_path_with_preset_cwd(root, origin_dir, input, fake_cwd);
assert_eq!(result.unwrap(), Path::new("../outside/main.rs"));
}
+
+ #[test]
+ fn input_cannot_go_above_root() {
+ let fake_cwd = Path::new("/sandbox");
+ let root = Path::new("project");
+ let origin_dir = Path::new("outside");
+ let input = Path::new("../../../main.rs");
+ let result = normalize_path_with_preset_cwd(root, origin_dir, input, fake_cwd);
+ assert!(matches!(result, Err(NormalizeError::InputOutsideFileSystemRoot)));
+ }
}