Compare commits

...

3 commits

Author SHA1 Message Date
d58e46d65e test(fs): add tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-04-28 15:24:09 +01:00
b32c10f080 refactor(fs/tests): group into modules 2024-04-28 15:24:08 +01:00
a9057a8831 feat(fs): properly validates paths are within base directory 2024-04-28 15:24:06 +01:00
3 changed files with 239 additions and 37 deletions

View file

@ -31,6 +31,7 @@ thiserror = "1.0"
# fs
tempfile = "3.10"
path-clean = "1.0"
# error handling
derive_more = { version = "1.0.0-beta.6", features = ["from", "display"] }

View file

@ -57,12 +57,25 @@ impl super::FileSystemLike for RealFileSystem {
impl RealFileSystem {
fn validate(&self, path: &Path) -> super::Result<()> {
let path = self.clean_path(path)?;
if !path.starts_with(&self.base) {
return Err(super::Error::PathTraversal {
base: self.base.clone(),
path: path.to_path_buf(),
path,
});
}
Ok(())
}
fn clean_path(&self, path: &Path) -> super::Result<PathBuf> {
// let path = path.as_ref();
use path_clean::PathClean;
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
}
.clean();
Ok(abs_path)
}
}

View file

@ -1,55 +1,243 @@
use std::path::PathBuf;
use assert2::let_assert;
use crate::fs;
type TestResult = Result<(), crate::fs::Error>;
type TestResult = Result<(), fs::Error>;
#[test]
fn write_read_file_exists() -> TestResult {
let fs = fs::temp()?;
let pathbuf: PathBuf = fs.path_of("foo".into())?;
mod path_of {
use super::*;
#[test]
fn validate_fails_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
fs.file_write(&pathbuf, "content")?;
let c = fs.file_read_to_string(&pathbuf)?;
assert_eq!(c, "content");
let_assert!(Err(fs::Error::PathTraversal { base, path: _path }) = fs.path_of("..".into()));
assert_eq!(base, fs.base());
let exists = fs.path_exists(&pathbuf)?;
assert!(exists);
let is_file = fs.path_is_file(&pathbuf)?;
assert!(is_file);
Ok(())
Ok(())
}
}
#[test]
fn create_dir_should_create_a_dir() -> TestResult {
let fs = fs::temp()?;
let pathbuf = fs.path_of("subdir".into())?;
mod file {
use super::*;
#[test]
/// Write to a file, read it, verify it exists, is a file and has the expected contents
fn write_read_file_exists() -> TestResult {
let fs = fs::temp()?;
let pathbuf = fs.base().join("foo");
fs.dir_create(&pathbuf)?;
let_assert!(Ok(_) = fs.file_write(&pathbuf, "content"));
let_assert!(
Ok(c) = fs.file_read_to_string(&pathbuf),
"file_read_to_string"
);
assert_eq!(c, "content");
let exists = fs.path_exists(&pathbuf)?;
assert!(exists);
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
assert!(exists);
let is_dir = fs.path_is_dir(&pathbuf)?;
assert!(is_dir);
let_assert!(Ok(is_file) = fs.path_is_file(&pathbuf));
assert!(is_file);
Ok(())
Ok(())
}
}
#[test]
fn create_dir_all_should_create_a_dir() -> TestResult {
let fs = fs::temp()?;
let pathbuf = fs.base().join("subdir").join("child");
mod dir_create {
use super::*;
#[test]
fn should_create_a_dir() -> TestResult {
let fs = fs::temp()?;
let pathbuf = fs.base().join("subdir");
fs.dir_create_all(&pathbuf)?;
let_assert!(Ok(_) = fs.dir_create(&pathbuf));
let exists = fs.path_exists(&pathbuf)?;
assert!(exists, "path exists");
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
assert!(exists);
let is_dir = fs.path_is_dir(&pathbuf)?;
assert!(is_dir, "path is a directory");
let_assert!(Ok(is_dir) = fs.path_is_dir(&pathbuf));
assert!(is_dir);
Ok(())
Ok(())
}
#[test]
fn should_fail_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("..").join("foo");
let_assert!(
Err(fs::Error::PathTraversal {
base: _base,
path: _path
}) = fs.dir_create(&path)
);
Ok(())
}
}
mod dir_create_all {
use super::*;
#[test]
fn should_create_a_dir() -> TestResult {
let fs = fs::temp()?;
let pathbuf = fs.base().join("subdir").join("child");
let_assert!(Ok(_) = fs.dir_create_all(&pathbuf));
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
assert!(exists, "path exists");
let_assert!(Ok(is_dir) = fs.path_is_dir(&pathbuf));
assert!(is_dir, "path is a directory");
Ok(())
}
#[test]
fn should_fail_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("..").join("foo");
let_assert!(
Err(fs::Error::PathTraversal {
base: _base,
path: _path
}) = fs.dir_create_all(&path)
);
Ok(())
}
}
mod path_exists {
use super::*;
#[test]
fn should_be_true_when_it_exists() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
let_assert!(Ok(_) = fs.file_write(&path, "bar"));
let_assert!(Ok(exists) = fs.path_exists(&path));
assert!(exists);
Ok(())
}
#[test]
fn should_be_false_when_it_does_not_exist() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
let_assert!(Ok(exists) = fs.path_exists(&path));
assert!(!exists);
Ok(())
}
#[test]
fn should_fail_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("..").join("foo");
let_assert!(
Err(fs::Error::PathTraversal {
base: _base,
path: _path
}) = fs.path_exists(&path)
);
Ok(())
}
}
mod path_is_dir {
use super::*;
#[test]
fn should_be_true_when_is_a_dir() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
let_assert!(Ok(_) = fs.dir_create(&path));
let_assert!(Ok(is_dir) = fs.path_is_dir(&path));
assert!(is_dir);
Ok(())
}
#[test]
fn should_be_false_when_is_a_file() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
let_assert!(Ok(_) = fs.file_write(&path, "bar"));
let_assert!(Ok(is_dir) = fs.path_is_dir(&path));
assert!(!is_dir);
Ok(())
}
#[test]
#[ignore]
fn should_be_false_when_is_a_link() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
// TODO create a link
// let_assert!(Ok(_) = fs.file_write(&path, "bar"));
let_assert!(Ok(is_dir) = fs.path_is_dir(&path));
assert!(!is_dir);
Ok(())
}
#[test]
fn should_fail_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("..").join("foo");
let_assert!(
Err(fs::Error::PathTraversal {
base: _base,
path: _path
}) = fs.path_is_dir(&path)
);
Ok(())
}
}
mod path_is_file {
use super::*;
#[test]
fn should_be_true_when_is_a_file() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
let_assert!(Ok(_) = fs.file_write(&path, "bar"));
let_assert!(Ok(is_file) = fs.path_is_file(&path));
assert!(is_file);
Ok(())
}
#[test]
fn should_be_false_when_is_a_dir() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
let_assert!(Ok(_) = fs.dir_create(&path));
let_assert!(Ok(is_file) = fs.path_is_file(&path));
assert!(!is_file);
Ok(())
}
#[test]
#[ignore]
fn should_be_false_when_is_a_link() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("foo");
// TODO create a link
// let_assert!(Ok(_) = fs.file_write(&path, "bar"));
let_assert!(Ok(is_file) = fs.path_is_file(&path));
assert!(!is_file);
Ok(())
}
#[test]
fn should_fail_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
let path = fs.base().join("..").join("foo");
let_assert!(
Err(fs::Error::PathTraversal {
base: _base,
path: _path
}) = fs.path_is_file(&path)
);
Ok(())
}
}