Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

622 file list tome doesnt support relative paths #671

Merged
merged 6 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 125 additions & 17 deletions implants/lib/eldritch/src/file/list_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,66 @@ pub fn list(starlark_heap: &Heap, path: String) -> Result<Vec<Dict>> {

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use crate::runtime::Message;

use super::*;
use tempfile::tempdir;
use pb::eldritch::Tome;
use tempfile::{tempdir, NamedTempFile};

fn init_logging() {
hulto marked this conversation as resolved.
Show resolved Hide resolved
pretty_env_logger::formatted_timed_builder()
.filter_level(log::LevelFilter::Info)
.parse_env("IMIX_LOG")
.init();
}

#[tokio::test]
async fn test_file_list_file() -> anyhow::Result<()> {
init_logging();
let tmp_file = NamedTempFile::new()?;
let path = String::from(tmp_file.path().to_str().unwrap());
let expected_file_name =
String::from(tmp_file.path().file_name().unwrap().to_str().unwrap());

// Run Eldritch (until finished)
let mut runtime = crate::start(
123,
Tome {
eldritch: String::from(r#"print(file.list(input_params['path'])[0]['file_name'])"#),
parameters: HashMap::from([(String::from("path"), path.clone())]),
file_names: Vec::new(),
},
)
.await;
runtime.finish().await;

// Read Messages
let expected_output = format!("{}\n", path);
let mut found = false;
for msg in runtime.messages() {
if let Message::ReportText(m) = msg {
assert_eq!(123, m.id);
assert!(m.text.contains(&expected_file_name));
log::debug!("text: {:?}", m.text);
found = true;
}
}
assert!(found);
Ok(())
}

#[tokio::test]
async fn test_file_list_dir() -> anyhow::Result<()> {
init_logging();

#[test]
fn test_file_list() -> anyhow::Result<()> {
let test_dir = tempdir()?;
let path = test_dir
.path()
.to_str()
.context("Failed to convert string")?
.to_string();
let expected_dirs = ["never gonna", "give you up", ".never gonna"];
let expected_files = ["let_you_down", ".or desert you"];

Expand All @@ -230,14 +284,36 @@ mod tests {
std::fs::File::create(test_dir_to_create)?;
}

let binding = Heap::new();
let list_res = list(&binding, test_dir.path().to_str().unwrap().to_string())?;
assert_eq!(list_res.len(), (expected_dirs.len() + expected_files.len()));
// Run Eldritch (until finished)
let mut runtime = crate::start(
123,
Tome {
eldritch: String::from(
r#"
for f in file.list(input_params['path']):
print(f['file_name'])"#,
),
parameters: HashMap::from([(String::from("path"), path.clone())]),
file_names: Vec::new(),
},
)
.await;
runtime.finish().await;

let mut counter = 0;
for msg in runtime.messages() {
if let Message::ReportText(m) = msg {
counter += 1;
log::debug!("text: {:?}", m.text);
}
}
assert_eq!(counter, (expected_dirs.len() + expected_files.len()));

Ok(())
}
#[test]
fn test_file_list_glob() -> anyhow::Result<()> {

#[tokio::test]
async fn test_file_list_glob() -> anyhow::Result<()> {
let test_dir = tempdir()?;
let expected_dir = "down the";
let nested_dir = "rabbit hole";
Expand All @@ -254,19 +330,51 @@ mod tests {
.join(file);
std::fs::File::create(test_file)?;

// /tmpdir/down the/*
let binding = Heap::new();
let list_res = list(
&binding,
// Run Eldritch (until finished)
let mut runtime = crate::start(
123,
Tome {
eldritch: String::from(
r#"
for f in file.list(input_params['path']):
print(f['file_name'])"#,
),
parameters: HashMap::from([(
String::from("path"),
test_dir
.path()
.join("*")
.join("win")
.to_str()
.unwrap()
.to_string(),
)]),
file_names: Vec::new(),
},
)
.await;
runtime.finish().await;

let expected_output = format!(
"{}\n",
test_dir
.path()
.join("*")
.join("win")
.join(expected_dir)
.join(nested_dir)
.join(file)
.to_str()
.unwrap()
.to_string(),
)?;
println!("{:?}", list_res);
);
let mut found = false;
for msg in runtime.messages() {
if let Message::ReportText(m) = msg {
assert_eq!(123, m.id);
assert_eq!(expected_output, m.text);
log::debug!("text: {:?}", m.text);
found = true;
}
}

Ok(())
}
}
119 changes: 16 additions & 103 deletions tavern/tomes/file_list/main.eldritch
Original file line number Diff line number Diff line change
Expand Up @@ -6,111 +6,24 @@ name = {
"Link": "Link"
}

SEP = "/"
if sys.get_os().get("platform", "") == "PLATFORM_WINDOWS":
SEP = "\\"

def can_read(f):
"""Return true if the user can read this dir/file
"""

# Until we get permissions on windows, just go ahead and try to read
if SEP == "\\":
return True
PERM_READ = 4
f_user = int(f["permissions"][-3]) # User byte
f_group = int(f["permissions"][-2]) # Group byte

# Check world byte first so it hopefully is fast
if int(f["permissions"][-1]) & PERM_READ:
return True

# Are we root?
root = usernfo["euid"]["uid"] == 0

# If the user isnt root and the user doesnt own the file, clear the user byte
if not root and f["owner"] not in (usernfo["euid"]["name"], usernfo["uid"]["name"]):
f_user = 0

# TODO: https://github.com/spellshift/realm/issues/570
# Will NOT match any group other than primary until #570 is fixed

# If the user isnt root and the group doesnt own the file, clear the group byte
if not root and f["group"] not in (str(usernfo["egid"]), str(usernfo["gid"])):
f_group = 0

if (f_group & PERM_READ) | (f_user & PERM_READ):
return True
return False

def glob(s, pattern):
"""Basic glob functionality"""
p = pattern.split("*")
# Check the first chunk
chunk = p.pop(0)
if not s.startswith(chunk):
return False
s = s[len(chunk):]
# Check the last chunk
if p:
chunk = p.pop()
if chunk:
if not s.endswith(chunk):
return False
s = s[:-len(chunk)]

# Check all the middle chunks
for part in p:
if part not in s:
return False
s = s[s.index(part)+1:]
return True

def print_file(path, f):
def print_file(f):
"""Pretty Print a file"""
full = path.rstrip("/") + "/" + f["file_name"]
if f["type"] == "Directory":
full += "/"
print(f['permissions']+"\t"+f['owner']+"\t"+f['group']+"\t"+str(f['size'])+"\t"+f['modified']+"\t"+name.get(f['type'], f['type'])+"\t"+full+"\n")
perms = f['permissions']
owner = f['owner']
group = f['group']
size = str(f['size'])
modified = f['modified']
ftype = name.get(f['type'], f['type'])
absolute_path = f['absolute_path']

print(f"{perms}\t{owner}\t{group}\t{size}\t{modified}\t{ftype}\t{absolute_path}")

def file_list(path):
"""List all files in the given path"""
parts = path.strip(SEP).split(SEP)
base = [] # The base of the path that doesnt have a glob
pattern = ""
for p in parts:
if '*' in p:
pattern = p
break
base.append(p)

base = SEP.join(base)
if SEP == "/":
base = "/" + base

# Safety checking
if not file.exists(base):
print("Error Path '"+path+"' does not exist\n")
return
elif file.is_file(base):
print("Error Path '"+path+"' is a file\n")
return

# TODO: No way to check if we can read base
for f in file.list(base):
if pattern == "*":
# List each file/dir in this folder
if f["type"] == "Directory" and can_read(f):
d = base+SEP+f["file_name"]
for f in file.list(d):
print_file(d, f)
elif not pattern:
# Just list each file/folder
print_file(base, f)
elif glob(f["file_name"], pattern):
# Only print files/folders that match the glob
print_file(base, f)
res = file.list(path)
if len(res) > 0:
for f in res:
print_file(f)
else:
eprint(f"No files found at '{path}'")

file_list(input_params['path'])
print("\n")
print("\n")
Loading