diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md
index 2132a69be..dd4779872 100644
--- a/docs/_docs/user-guide/eldritch.md
+++ b/docs/_docs/user-guide/eldritch.md
@@ -346,6 +346,12 @@ The file.mkdir method will make a new directory at `path`. If the parent
The file.moveto method moves a file or directory from `src` to `dst`. If the `dst` directory or file exists it will be deleted before being replaced to ensure consistency across systems.
+### file.parent_dir
+
+`file.parent_dir(path: str) -> str`
+
+The file.parent_dir method returns the parent directory of a give path. Eg `/etc/ssh/sshd_config` -> `/etc/ssh`
+
### file.read
`file.read(path: str) -> str`
diff --git a/implants/lib/eldritch/src/file/mod.rs b/implants/lib/eldritch/src/file/mod.rs
index c7b1a6d99..a02cfbe8e 100644
--- a/implants/lib/eldritch/src/file/mod.rs
+++ b/implants/lib/eldritch/src/file/mod.rs
@@ -9,6 +9,7 @@ mod is_file_impl;
mod list_impl;
mod mkdir_impl;
mod moveto_impl;
+mod parent_dir_impl;
mod read_impl;
mod remove_impl;
mod replace_all_impl;
@@ -130,6 +131,10 @@ fn methods(builder: &mut MethodsBuilder) {
moveto_impl::moveto(old, new)?;
Ok(NoneType{})
}
+ #[allow(unused_variables)]
+ fn parent_dir(this: &FileLibrary, path: String) -> anyhow::Result {
+ parent_dir_impl::parent_dir(path)
+ }
#[allow(unused_variables)]
fn replace_all(this: &FileLibrary, path: String, pattern: String, value: String) -> anyhow::Result {
diff --git a/implants/lib/eldritch/src/file/parent_dir_impl.rs b/implants/lib/eldritch/src/file/parent_dir_impl.rs
new file mode 100644
index 000000000..8a15db5a0
--- /dev/null
+++ b/implants/lib/eldritch/src/file/parent_dir_impl.rs
@@ -0,0 +1,69 @@
+use anyhow::{Context, Result};
+use std::path::PathBuf;
+
+pub fn parent_dir(path: String) -> Result {
+ let mut res = PathBuf::from(&path);
+ res.pop();
+ Ok(res
+ .to_str()
+ .context("Failed to convert to str")?
+ .to_string())
+}
+
+#[cfg(test)]
+mod test {
+ use crate::runtime::Message;
+ use pb::eldritch::Tome;
+ use std::collections::HashMap;
+
+ macro_rules! test_cases {
+ ($($name:ident: $value:expr,)*) => {
+ $(
+ #[tokio::test]
+ async fn $name() {
+ let tc: TestCase = $value;
+
+ // Run Eldritch (until finished)
+ let mut runtime = crate::start(tc.id, tc.tome).await;
+ runtime.finish().await;
+
+ // Read Messages
+ let mut found = false;
+ for msg in runtime.messages() {
+ if let Message::ReportText(m) = msg {
+ assert_eq!(tc.id, m.id);
+ assert_eq!(tc.want_text, m.text);
+ found = true;
+ }
+ }
+ assert!(found);
+ }
+ )*
+ }
+ }
+
+ struct TestCase {
+ pub id: i64,
+ pub tome: Tome,
+ pub want_text: String,
+ }
+
+
+ test_cases! {
+ simple_ssh: TestCase{
+ id: 123,
+ tome: Tome{
+ eldritch: String::from(r#"print(file.parent_dir(input_params['path']))"#),
+ #[cfg(not(target_os="windows"))]
+ parameters: HashMap::from([(String::from("path"),String::from("/etc/ssh/sshd_config"))]),
+ #[cfg(target_os="windows")]
+ parameters: HashMap::from([(String::from("path"),String::from("C:\\ProgramData\\ssh\\sshd_config"))]),
+ file_names: Vec::new(),
+ },
+ #[cfg(not(target_os="windows"))]
+ want_text: String::from("/etc/ssh\n"),
+ #[cfg(target_os="windows")]
+ want_text: String::from("C:\\ProgramData\\ssh\n"),
+ },
+ }
+}
diff --git a/implants/lib/eldritch/src/runtime/mod.rs b/implants/lib/eldritch/src/runtime/mod.rs
index 1a8d7b449..1c6bd7b1a 100644
--- a/implants/lib/eldritch/src/runtime/mod.rs
+++ b/implants/lib/eldritch/src/runtime/mod.rs
@@ -89,7 +89,7 @@ mod tests {
parameters: HashMap::new(),
file_names: Vec::new(),
},
- want_text: format!("{}\n", r#"["append", "compress", "copy", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#),
+ want_text: format!("{}\n", r#"["append", "compress", "copy", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "parent_dir", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#),
want_error: None,
},
process_bindings: TestCase {