From 489c7d04c824339faf085418c48bd44c75a6da80 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 23 Mar 2023 07:35:31 +0800 Subject: Create shared file utiity module --- src/file_utils.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/file_utils.rs (limited to 'src/file_utils.rs') diff --git a/src/file_utils.rs b/src/file_utils.rs new file mode 100644 index 0000000..fdd68ba --- /dev/null +++ b/src/file_utils.rs @@ -0,0 +1,84 @@ +use std::path::{Component, Path, PathBuf}; + +/// Guarantee that the path is relative and cannot traverse back to parent directories +/// and optionally prevent traversing hidden directories. +/// +/// See the unit tests tests::test_sanitize_path* for examples +pub fn sanitize_path(path: &Path, traverse_hidden: bool) -> Option { + let mut buf = PathBuf::new(); + + for comp in path.components() { + match comp { + Component::Normal(name) => buf.push(name), + Component::ParentDir => { + buf.pop(); + } + _ => (), + } + } + + // Double-check that all components are Normal and check for hidden dirs + for comp in buf.components() { + match comp { + Component::Normal(_) if traverse_hidden => (), + Component::Normal(name) if !name.to_str()?.starts_with('.') => (), + _ => return None, + } + } + + Some(buf) +} + +/// Returns if a path goes through a symbolic link +pub fn contains_symlink(path: &PathBuf) -> bool { + let mut joined_path = PathBuf::new(); + for path_slice in path { + joined_path = joined_path.join(path_slice); + if !joined_path.exists() { + // On Windows, `\\?\` won't exist even though it's the root + // So, we can't just return here + // But we don't need to check if it's a symlink since it won't be + continue; + } + if joined_path + .symlink_metadata() + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) + { + return true; + } + } + false +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use rstest::rstest; + + #[rstest] + #[case("/foo", "foo")] + #[case("////foo", "foo")] + #[case("C:/foo", if cfg!(windows) { "foo" } else { "C:/foo" })] + #[case("../foo", "foo")] + #[case("../foo/../bar/abc", "bar/abc")] + fn test_sanitize_path(#[case] input: &str, #[case] output: &str) { + assert_eq!( + sanitize_path(Path::new(input), true).unwrap(), + Path::new(output) + ); + assert_eq!( + sanitize_path(Path::new(input), false).unwrap(), + Path::new(output) + ); + } + + #[rstest] + #[case(".foo")] + #[case("/.foo")] + #[case("foo/.bar/foo")] + fn test_sanitize_path_no_hidden_files(#[case] input: &str) { + assert_eq!(sanitize_path(Path::new(input), false), None); + } +} -- cgit v1.2.3 From 03915c3d33a6a053a1455a2ba48ad57e0477365a Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 23 Mar 2023 07:39:00 +0800 Subject: Make file util functions generic --- src/file_utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/file_utils.rs') diff --git a/src/file_utils.rs b/src/file_utils.rs index fdd68ba..ba976de 100644 --- a/src/file_utils.rs +++ b/src/file_utils.rs @@ -4,10 +4,10 @@ use std::path::{Component, Path, PathBuf}; /// and optionally prevent traversing hidden directories. /// /// See the unit tests tests::test_sanitize_path* for examples -pub fn sanitize_path(path: &Path, traverse_hidden: bool) -> Option { +pub fn sanitize_path(path: impl AsRef, traverse_hidden: bool) -> Option { let mut buf = PathBuf::new(); - for comp in path.components() { + for comp in path.as_ref().components() { match comp { Component::Normal(name) => buf.push(name), Component::ParentDir => { @@ -30,9 +30,9 @@ pub fn sanitize_path(path: &Path, traverse_hidden: bool) -> Option { } /// Returns if a path goes through a symbolic link -pub fn contains_symlink(path: &PathBuf) -> bool { +pub fn contains_symlink(path: impl AsRef) -> bool { let mut joined_path = PathBuf::new(); - for path_slice in path { + for path_slice in path.as_ref().iter() { joined_path = joined_path.join(path_slice); if !joined_path.exists() { // On Windows, `\\?\` won't exist even though it's the root -- cgit v1.2.3 From cea3eabae7c369ec658745ba3f8e4328a4de975f Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:37:18 +0800 Subject: Rewrite `contains_symlink` --- src/file_utils.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'src/file_utils.rs') diff --git a/src/file_utils.rs b/src/file_utils.rs index ba976de..114a08f 100644 --- a/src/file_utils.rs +++ b/src/file_utils.rs @@ -1,4 +1,7 @@ -use std::path::{Component, Path, PathBuf}; +use std::{ + io, + path::{Component, Path, PathBuf}, +}; /// Guarantee that the path is relative and cannot traverse back to parent directories /// and optionally prevent traversing hidden directories. @@ -29,26 +32,23 @@ pub fn sanitize_path(path: impl AsRef, traverse_hidden: bool) -> Option) -> bool { - let mut joined_path = PathBuf::new(); - for path_slice in path.as_ref().iter() { - joined_path = joined_path.join(path_slice); - if !joined_path.exists() { - // On Windows, `\\?\` won't exist even though it's the root - // So, we can't just return here - // But we don't need to check if it's a symlink since it won't be - continue; - } - if joined_path - .symlink_metadata() - .map(|m| m.file_type().is_symlink()) - .unwrap_or(false) - { - return true; - } - } - false +/// Checks if any segment of the path is a symlink. +/// +/// This function fails if [`std::fs::symlink_metadata`] fails, which usually +/// means user has no permission to access the path. +pub fn contains_symlink(path: impl AsRef) -> io::Result { + let contains_symlink = path + .as_ref() + .ancestors() + // On Windows, `\\?\` won't exist even though it's the root, but there's no need to check it + // So we filter it out + .filter(|p| p.exists()) + .map(|p| p.symlink_metadata()) + .collect::, _>>()? + .into_iter() + .any(|p| p.file_type().is_symlink()); + + Ok(contains_symlink) } #[cfg(test)] -- cgit v1.2.3