Skip to content

Commit

Permalink
Document and test 'Data::file_exact()'.
Browse files Browse the repository at this point in the history
To do so, adds a 'Jail::change_dir()' method and relaxes the rules for
input paths to jail methods.
  • Loading branch information
SergioBenitez committed Mar 16, 2024
1 parent e7c9c35 commit 70a105f
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 33 deletions.
79 changes: 60 additions & 19 deletions src/jail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,27 @@ impl Jail {
&self.canonical_dir
}

fn safe_jailed_path(&self, path: &Path) -> Result<PathBuf> {
let path = crate::util::dedot(path);
if path.is_absolute() && path.starts_with(self.directory()) {
return Ok(path);
}

if !path.is_relative() {
return Err("Jail: input path is outside of jail directory".to_string().into());
}

Ok(path)
}

/// Creates a file with contents `contents` within the jail's directory. The
/// file is deleted when the jail is dropped.
///
/// # Errors
///
/// An error is returned if `path` is not relative. Any I/O errors
/// encountered while creating the file are returned.
/// An error is returned if `path` is not relative or is outside of the
/// jail's directory. Any I/O errors encountered while creating the
/// subdirectory are returned.
///
/// # Example
///
Expand All @@ -154,12 +168,8 @@ impl Jail {
/// });
/// ```
pub fn create_file<P: AsRef<Path>>(&self, path: P, contents: &str) -> Result<File> {
let path = path.as_ref();
if !path.is_relative() {
return Err("Jail::create_file(): file path is absolute".to_string().into());
}

let file = File::create(self.directory().join(path)).map_err(as_string)?;
let path = self.safe_jailed_path(path.as_ref())?;
let file = File::create(path).map_err(as_string)?;
let mut writer = BufWriter::new(file);
writer.write_all(contents.as_bytes()).map_err(as_string)?;
Ok(writer.into_inner().map_err(as_string)?)
Expand All @@ -174,8 +184,9 @@ impl Jail {
///
/// # Errors
///
/// An error is returned if `path` is not relative. Any I/O errors
/// encountered while creating the subdirectory are returned.
/// An error is returned if `path` is not relative or is outside of the
/// jail's directory. Any I/O errors encountered while creating the
/// subdirectory are returned.
///
/// # Example
///
Expand All @@ -185,24 +196,54 @@ impl Jail {
/// figment::Jail::expect_with(|jail| {
/// let dir = jail.create_dir("subdir")?;
/// jail.create_file(dir.join("config.json"), "{ foo: 123 }")?;
/// # assert_eq!(dir, Path::new("subdir"));
///
/// let dir = jail.create_dir("subdir/1/2")?;
/// jail.create_file(dir.join("secret.toml"), "secret = 1337")?;
/// # assert_eq!(dir, Path::new("subdir/1/2"));
///
/// Ok(())
/// });
/// ```
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
let path = path.as_ref();
if !path.is_relative() {
return Err("Jail::create_dir(): dir path is absolute".to_string().into());
}
let path = self.safe_jailed_path(path.as_ref())?;
fs::create_dir_all(&path).map_err(as_string)?;
Ok(path)
}

let absolute_dir_path = self.directory().join(path);
fs::create_dir_all(&absolute_dir_path).map_err(as_string)?;
Ok(path.into())
/// Sets the jail's current working directory to `path` if `path` is within
/// [`Jail::directory()`]. Otherwise returns an error.
///
/// # Errors
///
/// An error is returned if `path` is not relative or is outside of the
/// jail's directory. Any I/O errors encountered while creating the
/// subdirectory are returned.
///
/// # Example
///
/// ```rust
/// use std::path::Path;
///
/// figment::Jail::expect_with(|jail| {
/// assert_eq!(std::env::current_dir().unwrap(), jail.directory());
///
/// let subdir = jail.create_dir("subdir")?;
/// jail.change_dir(&subdir)?;
/// assert_eq!(std::env::current_dir().unwrap(), jail.directory().join(subdir));
///
/// let file = jail.create_file("foo.txt", "contents")?;
/// assert!(!jail.directory().join("foo.txt").exists());
/// assert!(jail.directory().join("subdir").join("foo.txt").exists());
///
/// jail.change_dir(jail.directory())?;
/// assert_eq!(std::env::current_dir().unwrap(), jail.directory());
///
/// Ok(())
/// });
/// ```
pub fn change_dir<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
let path = self.safe_jailed_path(path.as_ref())?;
std::env::set_current_dir(&path).map_err(as_string)?;
Ok(path)
}

/// Remove all environment variables. All variables will be restored when
Expand Down
49 changes: 35 additions & 14 deletions src/providers/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,10 @@ impl<F: Format> Data<F> {
/// Returns a `Data` provider that sources its values by parsing the file at
/// `path` as format `F`. If `path` is relative, the file is searched for in
/// the current working directory and all parent directories until the root,
/// and the first hit is used.
/// and the first hit is used. If you don't want parent directories to be
/// searched, use [`Data::file_exact()`] instead.
///
/// (If you don't want want to search parent directories,
/// use [`file_exact()`](Data::file_exact) instead.)
///
/// Nesting is not enabled by default; use [`Data::nested()`] to enable
/// nesting.
/// Nesting is disabled by default. Use [`Data::nested()`] to enable it.
///
/// ```rust
/// use serde::Deserialize;
Expand Down Expand Up @@ -137,16 +134,40 @@ impl<F: Format> Data<F> {
}

/// Returns a `Data` provider that sources its values by parsing the file at
/// `path` as format `F`. If `path` is relative, it is located relative
/// to the current working directory—no other directories are searched.
/// `path` as format `F`. If `path` is relative, it is located relative to
/// the current working directory. No other directories are searched.
///
/// If you want to search parent directories for `path`, use
/// [`Data::file()`] instead.
///
/// (If you want to search parent directories, use [`file()`](Data::file)
/// instead.)
/// Nesting is disabled by default. Use [`Data::nested()`] to enable it.
///
/// ```rust
/// use serde::Deserialize;
/// use figment::{Figment, Jail, providers::{Format, Toml}};
///
/// Nesting is not enabled by default; use [`Data::nested()`] to enable
/// nesting.
pub fn file_exact<P:AsRef<Path>>(path: P) -> Self {
Data::new(Source::File(Some(path.as_ref().to_owned())), Some(Profile::Default))
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct Config {
/// foo: usize,
/// }
///
/// Jail::expect_with(|jail| {
/// // Create 'subdir/config.toml' and set `cwd = subdir`.
/// jail.create_file("config.toml", "foo = 123")?;
/// jail.change_dir(jail.create_dir("subdir")?)?;
///
/// // We are in `subdir`. `config.toml` is in `../`. `file()` finds it.
/// let config = Figment::from(Toml::file("config.toml")).extract::<Config>()?;
/// assert_eq!(config.foo, 123);
///
/// // `file_exact()` doesn't search, so it doesn't find it.
/// let config = Figment::from(Toml::file_exact("config.toml")).extract::<Config>();
/// assert!(config.is_err());
/// Ok(())
/// });
/// ```
pub fn file_exact<P: AsRef<Path>>(path: P) -> Self {
Data::new(Source::File(Some(path.as_ref().to_owned())), Some(Profile::Default))
}

/// Returns a `Data` provider that sources its values by parsing the string
Expand Down
24 changes: 24 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,30 @@ pub mod vec_tuple_map {
}
}

fn dedot_components<'c>(components: impl Iterator<Item = Component<'c>>) -> PathBuf {
use std::path::Component::*;

let mut comps = vec![];
for component in components {
match component {
p@Prefix(_) => comps = vec![p],
r@RootDir if comps.iter().all(|c| matches!(c, Prefix(_))) => comps.push(r),
r@RootDir => comps = vec![r],
CurDir => { },
ParentDir if comps.iter().all(|c| matches!(c, Prefix(_) | RootDir)) => { },
ParentDir => { comps.pop(); },
c@Normal(_) => comps.push(c),
}
}

comps.iter().map(|c| c.as_os_str()).collect()
}

/// Remove any dots from the path by popping as needed.
pub(crate) fn dedot(path: &Path) -> PathBuf {
dedot_components(path.components())
}

use crate::value::{Value, Dict};

/// Given a key path `key` of the form `a.b.c`, creates nested dictionaries for
Expand Down

0 comments on commit 70a105f

Please sign in to comment.