Compare commits

..

No commits in common. "984724a465a16159c4410944560ddc4c9bc9ed47" and "396d4b77bc79d4bd54c727ffcd2d77e4811b73d2" have entirely different histories.

5 changed files with 36 additions and 302 deletions

View file

@ -9,7 +9,6 @@ repository = "https://git.kemitix.net/kemitix/kxio"
exclude = [".cargo_home"] exclude = [".cargo_home"]
[features] [features]
default = ["fs", "network"]
fs = [] fs = []
network = [] network = []
@ -31,7 +30,6 @@ thiserror = "1.0"
# fs # fs
tempfile = "3.10" tempfile = "3.10"
path-clean = "1.0"
# error handling # error handling
derive_more = { version = "1.0.0-beta.6", features = ["from", "display"] } derive_more = { version = "1.0.0-beta.6", features = ["from", "display"] }

View file

@ -1,18 +0,0 @@
use super::Result;
use std::path::{Path, PathBuf};
pub trait FileSystemLike {
fn base(&self) -> &Path;
fn dir_create(&self, path: &Path) -> Result<()>;
fn dir_create_all(&self, path: &Path) -> Result<()>;
fn file_read_to_string(&self, path: &Path) -> Result<String>;
fn file_write(&self, path: &Path, contents: &str) -> Result<()>;
fn path_exists(&self, path: &Path) -> Result<bool>;
fn path_is_dir(&self, path: &Path) -> Result<bool>;
fn path_is_file(&self, path: &Path) -> Result<bool>;
fn path_of(&self, path: PathBuf) -> Result<PathBuf>;
}

View file

@ -1,10 +1,7 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use derive_more::From; use derive_more::From;
use crate::fs::like::FileSystemLike;
mod like;
mod real; mod real;
mod temp; mod temp;
@ -32,6 +29,7 @@ pub enum FileSystem {
Real(real::RealFileSystem), Real(real::RealFileSystem),
Temp(temp::TempFileSystem), Temp(temp::TempFileSystem),
} }
impl std::ops::Deref for FileSystem { impl std::ops::Deref for FileSystem {
type Target = dyn FileSystemLike; type Target = dyn FileSystemLike;
@ -42,3 +40,11 @@ impl std::ops::Deref for FileSystem {
} }
} }
} }
pub trait FileSystemLike {
fn base(&self) -> &Path;
fn path_of(&self, path: PathBuf) -> Result<PathBuf>;
fn file_write(&self, path: &Path, contents: &str) -> Result<()>;
fn file_read_to_string(&self, path: &Path) -> Result<String>;
fn path_exists(&self, path: &Path) -> Result<bool>;
fn path_is_file(&self, path: &Path) -> Result<bool>;
}

View file

@ -12,20 +12,10 @@ impl super::FileSystemLike for RealFileSystem {
fn base(&self) -> &Path { fn base(&self) -> &Path {
&self.base &self.base
} }
fn path_of(&self, path: PathBuf) -> super::Result<PathBuf> {
fn dir_create(&self, path: &Path) -> super::Result<()> { let path_of = self.base.as_path().join(path);
self.validate(path)?; self.validate(&path_of)?;
std::fs::create_dir(path).map_err(Into::into) Ok(path_of)
}
fn dir_create_all(&self, path: &Path) -> super::Result<()> {
self.validate(path)?;
std::fs::create_dir_all(path).map_err(Into::into)
}
fn file_read_to_string(&self, path: &Path) -> super::Result<String> {
self.validate(path)?;
std::fs::read_to_string(path).map_err(Into::into)
} }
fn file_write(&self, path: &Path, contents: &str) -> super::Result<()> { fn file_write(&self, path: &Path, contents: &str) -> super::Result<()> {
@ -33,14 +23,9 @@ impl super::FileSystemLike for RealFileSystem {
std::fs::write(path, contents).map_err(Into::into) std::fs::write(path, contents).map_err(Into::into)
} }
fn path_exists(&self, path: &Path) -> super::Result<bool> { fn file_read_to_string(&self, path: &Path) -> super::Result<String> {
self.validate(path)?; self.validate(path)?;
Ok(path.exists()) std::fs::read_to_string(path).map_err(Into::into)
}
fn path_is_dir(&self, path: &Path) -> super::Result<bool> {
self.validate(path)?;
Ok(path.is_dir())
} }
fn path_is_file(&self, path: &Path) -> super::Result<bool> { fn path_is_file(&self, path: &Path) -> super::Result<bool> {
@ -48,34 +33,20 @@ impl super::FileSystemLike for RealFileSystem {
Ok(path.is_file()) Ok(path.is_file())
} }
fn path_of(&self, path: PathBuf) -> super::Result<PathBuf> { fn path_exists(&self, path: &Path) -> super::Result<bool> {
let path_of = self.base.as_path().join(path); self.validate(path)?;
self.validate(&path_of)?; Ok(path.exists())
Ok(path_of)
} }
} }
impl RealFileSystem { impl RealFileSystem {
fn validate(&self, path: &Path) -> super::Result<()> { fn validate(&self, path: &std::path::Path) -> super::Result<()> {
let canon = self.clean_path(path)?; if !path.starts_with(&self.base) {
if !canon.starts_with(&self.base) {
return Err(super::Error::PathTraversal { return Err(super::Error::PathTraversal {
base: self.base.clone(), base: self.base.clone(),
path: canon.to_path_buf(), path: path.to_path_buf(),
}); });
} }
Ok(()) 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,243 +1,20 @@
use assert2::let_assert; use std::path::PathBuf;
use crate::fs; use crate::fs;
type TestResult = Result<(), fs::Error>; type TestResult = Result<(), crate::fs::Error>;
mod path_of {
use super::*;
#[test] #[test]
fn validate_fails_on_path_traversal() -> TestResult {
let fs = fs::temp()?;
let_assert!(Err(fs::Error::PathTraversal { base, path: _path }) = fs.path_of("..".into()));
assert_eq!(base, fs.base());
Ok(())
}
}
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 { fn write_read_file_exists() -> TestResult {
let fs = fs::temp()?; let temp_fs = fs::temp()?;
let pathbuf = fs.base().join("foo"); let name: PathBuf = temp_fs.path_of("foo".into())?;
temp_fs.file_write(&name, "content")?;
let_assert!(Ok(_) = fs.file_write(&pathbuf, "content")); let c = temp_fs.file_read_to_string(&name)?;
let_assert!(
Ok(c) = fs.file_read_to_string(&pathbuf),
"file_read_to_string"
);
assert_eq!(c, "content"); assert_eq!(c, "content");
let exists = temp_fs.path_exists(&name)?;
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
assert!(exists); assert!(exists);
let is_file = temp_fs.path_is_file(&name)?;
let_assert!(Ok(is_file) = fs.path_is_file(&pathbuf));
assert!(is_file); assert!(is_file);
Ok(()) Ok(())
} }
}
mod dir_create {
use super::*;
#[test]
fn should_create_a_dir() -> TestResult {
let fs = fs::temp()?;
let pathbuf = fs.base().join("subdir");
let_assert!(Ok(_) = fs.dir_create(&pathbuf));
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
assert!(exists);
let_assert!(Ok(is_dir) = fs.path_is_dir(&pathbuf));
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.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(())
}
}