command_vault/exec/
mod.rsuse std::io::{self, Write};
use std::process::Command as ProcessCommand;
use std::env;
use std::path::{Path, PathBuf};
use anyhow::Result;
use crossterm::terminal;
use dialoguer::{theme::ColorfulTheme, Input};
use crate::db::models::Command;
use crate::shell::hooks::detect_current_shell;
pub struct ExecutionContext {
pub command: String,
pub directory: String,
pub test_mode: bool,
pub debug_mode: bool,
}
pub fn wrap_command(command: &str, test_mode: bool) -> String {
if test_mode {
command.to_string()
} else {
let shell_type = detect_current_shell();
let clean_command = command.trim_matches('"').to_string();
match shell_type.as_str() {
"zsh" => format!(
r#"setopt no_global_rcs; if [ -f ~/.zshrc ]; then ZDOTDIR=~ source ~/.zshrc; fi; {}"#,
clean_command
),
"fish" => format!(
r#"if test -f ~/.config/fish/config.fish; source ~/.config/fish/config.fish 2>/dev/null; end; {}"#,
clean_command
),
"bash" | _ => format!(
r#"if [ -f ~/.bashrc ]; then . ~/.bashrc >/dev/null 2>&1; fi; if [ -f ~/.bash_profile ]; then . ~/.bash_profile >/dev/null 2>&1; fi; {}"#,
clean_command
),
}
}
}
fn is_path_traversal_attempt(command: &str, working_dir: &Path) -> bool {
if command.contains("..") {
if let Ok(working_dir) = working_dir.canonicalize() {
let potential_path = working_dir.join(command);
if let Ok(resolved_path) = potential_path.canonicalize() {
return !resolved_path.starts_with(working_dir);
}
}
return true;
}
false
}
pub fn execute_shell_command(ctx: &ExecutionContext) -> Result<()> {
let shell = if cfg!(windows) {
String::from("cmd.exe")
} else {
env::var("SHELL").unwrap_or_else(|_| String::from("/bin/sh"))
};
let wrapped_command = wrap_command(&ctx.command, ctx.test_mode);
if is_path_traversal_attempt(&wrapped_command, Path::new(&ctx.directory)) {
return Err(anyhow::anyhow!("Directory traversal attempt detected"));
}
let mut command = ProcessCommand::new(&shell);
if ctx.test_mode {
command.args(&["-c", &wrapped_command]);
} else {
command.args(&["-i", "-c", &wrapped_command]);
}
command.current_dir(&ctx.directory);
if ctx.debug_mode {
println!("Full command: {:?}", command);
}
if !ctx.test_mode {
let _ = terminal::disable_raw_mode();
let mut stdout = io::stdout();
let _ = crossterm::execute!(
stdout,
crossterm::cursor::MoveTo(0, crossterm::cursor::position()?.1)
);
println!(); }
let output = command.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!(
"Command failed with status: {}. stderr: {}",
output.status,
stderr
));
}
if !output.stdout.is_empty() {
let stdout_str = String::from_utf8_lossy(&output.stdout);
print!("{}", stdout_str);
}
if !output.stderr.is_empty() {
let stderr_str = String::from_utf8_lossy(&output.stderr);
eprint!("{}", stderr_str);
}
Ok(())
}
pub fn execute_command(command: &Command) -> Result<()> {
let test_mode = std::env::var("COMMAND_VAULT_TEST").is_ok();
let debug_mode = std::env::var("COMMAND_VAULT_DEBUG").is_ok();
let mut final_command = command.command.clone();
if !command.parameters.is_empty() {
for param in &command.parameters {
println!("Parameter: {}", param.name);
println!();
let value = if test_mode {
let value = std::env::var("COMMAND_VAULT_TEST_INPUT")
.unwrap_or_else(|_| "test_value".to_string());
println!("Enter value: {}", value);
println!();
value
} else {
let input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter value")
.allow_empty(true)
.interact_text()?;
println!();
if input.contains(' ') {
format!("'{}'", input.replace("'", "'\\''"))
} else {
input
}
};
final_command = final_command.replace(&format!("@{}", param.name), &value);
}
}
let ctx = ExecutionContext {
command: final_command,
directory: command.directory.clone(),
test_mode,
debug_mode,
};
println!("─────────────────────────────────────────────");
println!();
println!("Command to execute: {}", ctx.command);
println!("Working directory: {}", ctx.directory);
println!();
execute_shell_command(&ctx)
}