command_vault/exec/
mod.rs1use std::io::{self, Write};
2use std::process::Command as ProcessCommand;
3use std::env;
4use std::path::{Path, PathBuf};
5use anyhow::Result;
6use crossterm::terminal;
7use dialoguer::{theme::ColorfulTheme, Input};
8use crate::db::models::Command;
9use crate::shell::hooks::detect_current_shell;
10
11pub struct ExecutionContext {
12 pub command: String,
13 pub directory: String,
14 pub test_mode: bool,
15 pub debug_mode: bool,
16}
17
18pub fn wrap_command(command: &str, test_mode: bool) -> String {
19 if test_mode {
20 command.to_string()
21 } else {
22 let shell_type = detect_current_shell();
24 let clean_command = command.trim_matches('"').to_string();
25
26 match shell_type.as_str() {
27 "zsh" => format!(
28 r#"setopt no_global_rcs; if [ -f ~/.zshrc ]; then ZDOTDIR=~ source ~/.zshrc; fi; {}"#,
29 clean_command
30 ),
31 "fish" => format!(
32 r#"if test -f ~/.config/fish/config.fish; source ~/.config/fish/config.fish 2>/dev/null; end; {}"#,
33 clean_command
34 ),
35 "bash" | _ => format!(
36 r#"if [ -f ~/.bashrc ]; then . ~/.bashrc >/dev/null 2>&1; fi; if [ -f ~/.bash_profile ]; then . ~/.bash_profile >/dev/null 2>&1; fi; {}"#,
37 clean_command
38 ),
39 }
40 }
41}
42
43fn is_path_traversal_attempt(command: &str, working_dir: &Path) -> bool {
44 if command.contains("..") {
46 if let Ok(working_dir) = working_dir.canonicalize() {
48 let potential_path = working_dir.join(command);
50 if let Ok(resolved_path) = potential_path.canonicalize() {
51 return !resolved_path.starts_with(working_dir);
53 }
54 }
55 return true;
57 }
58 false
59}
60
61pub fn execute_shell_command(ctx: &ExecutionContext) -> Result<()> {
62 let shell = if cfg!(windows) {
64 String::from("cmd.exe")
65 } else {
66 env::var("SHELL").unwrap_or_else(|_| String::from("/bin/sh"))
67 };
68
69 let wrapped_command = wrap_command(&ctx.command, ctx.test_mode);
71
72 if is_path_traversal_attempt(&wrapped_command, Path::new(&ctx.directory)) {
74 return Err(anyhow::anyhow!("Directory traversal attempt detected"));
75 }
76
77 let mut command = ProcessCommand::new(&shell);
79
80 if ctx.test_mode {
82 command.args(&["-c", &wrapped_command]);
83 } else {
84 command.args(&["-i", "-c", &wrapped_command]);
86 }
87
88 command.current_dir(&ctx.directory);
90
91 if ctx.debug_mode {
92 println!("Full command: {:?}", command);
93 }
94
95 if !ctx.test_mode {
97 let _ = terminal::disable_raw_mode();
98 let mut stdout = io::stdout();
100 let _ = crossterm::execute!(
101 stdout,
102 crossterm::cursor::MoveTo(0, crossterm::cursor::position()?.1)
103 );
104 println!(); }
106
107 let output = command.output()?;
109
110 if !output.status.success() {
112 let stderr = String::from_utf8_lossy(&output.stderr);
113 return Err(anyhow::anyhow!(
114 "Command failed with status: {}. stderr: {}",
115 output.status,
116 stderr
117 ));
118 }
119
120 if !output.stdout.is_empty() {
122 let stdout_str = String::from_utf8_lossy(&output.stdout);
123 print!("{}", stdout_str);
124 }
125
126 if !output.stderr.is_empty() {
128 let stderr_str = String::from_utf8_lossy(&output.stderr);
129 eprint!("{}", stderr_str);
130 }
131
132 Ok(())
133}
134
135pub fn execute_command(command: &Command) -> Result<()> {
136 let test_mode = std::env::var("COMMAND_VAULT_TEST").is_ok();
137 let debug_mode = std::env::var("COMMAND_VAULT_DEBUG").is_ok();
138 let mut final_command = command.command.clone();
139
140 if !command.parameters.is_empty() {
142 for param in &command.parameters {
143 println!("Parameter: {}", param.name);
144 println!();
145
146 let value = if test_mode {
147 let value = std::env::var("COMMAND_VAULT_TEST_INPUT")
148 .unwrap_or_else(|_| "test_value".to_string());
149 println!("Enter value: {}", value);
150 println!();
151 value
152 } else {
153 let input: String = Input::with_theme(&ColorfulTheme::default())
154 .with_prompt("Enter value")
155 .allow_empty(true)
156 .interact_text()?;
157 println!();
158
159 if input.contains(' ') {
160 format!("'{}'", input.replace("'", "'\\''"))
161 } else {
162 input
163 }
164 };
165
166 final_command = final_command.replace(&format!("@{}", param.name), &value);
167 }
168 }
169
170 let ctx = ExecutionContext {
171 command: final_command,
172 directory: command.directory.clone(),
173 test_mode,
174 debug_mode,
175 };
176
177 println!("─────────────────────────────────────────────");
179 println!();
180 println!("Command to execute: {}", ctx.command);
181 println!("Working directory: {}", ctx.directory);
182 println!();
183
184 execute_shell_command(&ctx)
185}