diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 2 | ||||
-rw-r--r-- | src/file_upload.rs | 87 | ||||
-rw-r--r-- | src/file_utils.rs | 84 | ||||
-rw-r--r-- | src/main.rs | 1 |
4 files changed, 88 insertions, 86 deletions
diff --git a/src/config.rs b/src/config.rs index 8976d35..d52b231 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,7 @@ use rustls_pemfile as pemfile; use crate::{ args::{CliArgs, MediaType}, auth::RequiredAuth, - file_upload::sanitize_path, + file_utils::sanitize_path, renderer::ThemeSlug, }; diff --git a/src/file_upload.rs b/src/file_upload.rs index 2275c73..b9974aa 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -6,8 +6,8 @@ use std::{ use actix_web::{http::header, HttpRequest, HttpResponse}; use futures::TryStreamExt; -use crate::errors::ContextualError; -use crate::listing; +use crate::{errors::ContextualError, file_utils::sanitize_path}; +use crate::{file_utils::contains_symlink, listing}; /// Saves file data from a multipart form field (`field`) to `file_path`, optionally overwriting /// existing file. @@ -214,86 +214,3 @@ pub async fn upload_file( .append_header((header::LOCATION, return_path)) .finish()) } - -/// 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<PathBuf> { - 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 symolic link -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); - } -} 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<PathBuf> { + 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); + } +} diff --git a/src/main.rs b/src/main.rs index 2f81baa..a492f4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ mod config; mod consts; mod errors; mod file_upload; +mod file_utils; mod listing; mod pipe; mod renderer; |