#![allow(deprecated)]
use crate::utils::config::Config;
use crate::utils::validation::ProfileName;
use anyhow::{Context as _, Result};
use colored::Colorize as _;
fn shell_escape_bash(value: &str) -> String {
let escaped = value.replace('\'', r"'\''");
format!("'{escaped}'")
}
fn shell_escape_powershell(value: &str) -> String {
let escaped = value.replace('\'', "''");
format!("'{escaped}'")
}
fn shell_escape_cmd(value: &str) -> String {
let mut out = String::with_capacity(value.len() + 4);
for ch in value.chars() {
match ch {
'%' => out.push_str("%%"),
'"' => out.push_str("\"\""),
_ => out.push(ch),
}
}
out
}
fn print_shell_env(shell: &str, profile: &str, profile_dir: &str) {
match shell {
"fish" => {
println!("set -x CODEXCTL {};", shell_escape_bash(profile));
println!("set -x CODEXCTL_DIR {};", shell_escape_bash(profile_dir));
println!("# Use with: codex");
println!(
"# Or run: eval (codexctl env {} --unset) to clear",
shell_escape_bash(profile)
);
}
"powershell" | "pwsh" => {
println!("$env:CODEXCTL = {};", shell_escape_powershell(profile));
println!(
"$env:CODEXCTL_DIR = {};",
shell_escape_powershell(profile_dir)
);
println!("# Use with: codex");
println!(
"# Or run: codexctl env {} --unset | Invoke-Expression to clear",
shell_escape_powershell(profile)
);
}
"cmd" | "batch" => {
println!("set \"CODEXCTL={}\"", shell_escape_cmd(profile));
println!("set \"CODEXCTL_DIR={}\"", shell_escape_cmd(profile_dir));
println!("REM Use with: codex");
println!("REM Clear with: codexctl env {} --unset", profile);
}
_ => {
println!("export CODEXCTL={};", shell_escape_bash(profile));
println!("export CODEXCTL_DIR={};", shell_escape_bash(profile_dir));
println!("# Use with: codex");
println!(
"# Or run: eval $(codexctl env {} --unset) to clear",
shell_escape_bash(profile)
);
}
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn execute(
config: Config,
profile: String,
shell: String,
unset: bool,
quiet: bool,
) -> Result<()> {
let profile_name = ProfileName::try_from(profile.as_str())
.with_context(|| format!("Invalid profile name '{profile}'"))?;
let profile_dir = config.profile_path_validated(&profile_name)?;
if !profile_dir.exists() {
anyhow::bail!(
"Profile '{profile}' not found. Use 'codexctl list' to see available profiles."
);
}
if unset {
match shell.as_str() {
"fish" => {
println!("set -e CODEXCTL;");
println!("set -e CODEXCTL_DIR;");
}
"powershell" | "pwsh" => {
println!("Remove-Item Env:CODEXCTL -ErrorAction SilentlyContinue;");
println!("Remove-Item Env:CODEXCTL_DIR -ErrorAction SilentlyContinue;");
}
"cmd" | "batch" => {
println!("set CODEXCTL=");
println!("set CODEXCTL_DIR=");
}
_ => {
println!("unset CODEXCTL;");
println!("unset CODEXCTL_DIR;");
}
}
if !quiet {
eprintln!(
"{} Environment cleared. Using default `Codex` auth.",
"✓".green()
);
}
return Ok(());
}
print_shell_env(&shell, &profile, &profile_dir.to_string_lossy());
if !quiet {
eprintln!();
eprintln!(
"{} Profile '{}' environment configured.",
"✓".green(),
profile.cyan()
);
eprintln!("{} Run the commands above to use this profile.", "ℹ".blue());
eprintln!("{} This won't affect other terminals!", "ℹ".blue());
eprintln!();
eprintln!("Example usage:");
eprintln!(" {}", format!("eval $(codexctl env {profile})").yellow());
eprintln!(" {} # Uses '{}' profile", "codex".yellow(), profile);
eprintln!();
eprintln!("To switch back to default:");
eprintln!(
" {}",
format!("eval $(codexctl env {profile} --unset)").yellow()
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bash_plain_value() {
assert_eq!(shell_escape_bash("myprofile"), "'myprofile'");
}
#[test]
fn bash_spaces() {
assert_eq!(shell_escape_bash("my profile"), "'my profile'");
}
#[test]
fn bash_dollar_sign() {
assert_eq!(shell_escape_bash("$HOME"), "'$HOME'");
}
#[test]
fn bash_backtick() {
assert_eq!(shell_escape_bash("`id`"), "'`id`'");
}
#[test]
fn bash_subshell_parens() {
assert_eq!(shell_escape_bash("$(id)"), "'$(id)'");
}
#[test]
fn bash_embedded_single_quote() {
assert_eq!(shell_escape_bash("O'Brien"), r"'O'\''Brien'");
}
#[test]
fn bash_double_quote() {
assert_eq!(shell_escape_bash(r#"say "hi""#), r#"'say "hi"'"#);
}
#[test]
fn bash_path_with_spaces() {
assert_eq!(
shell_escape_bash("/home/user/my docs"),
"'/home/user/my docs'"
);
}
#[test]
fn bash_hostile_injection() {
let hostile = "'; rm -rf /; echo '";
let escaped = shell_escape_bash(hostile);
assert_eq!(escaped, r"''\''; rm -rf /; echo '\'''");
}
#[test]
fn bash_newline() {
assert_eq!(shell_escape_bash("line1\nline2"), "'line1\nline2'");
}
#[test]
fn bash_exclamation() {
assert_eq!(shell_escape_bash("hello!world"), "'hello!world'");
}
#[test]
fn powershell_plain_value() {
assert_eq!(shell_escape_powershell("myprofile"), "'myprofile'");
}
#[test]
fn powershell_spaces() {
assert_eq!(shell_escape_powershell("my profile"), "'my profile'");
}
#[test]
fn powershell_dollar_sign() {
assert_eq!(shell_escape_powershell("$env:PATH"), "'$env:PATH'");
}
#[test]
fn powershell_backtick() {
assert_eq!(shell_escape_powershell("`whoami`"), "'`whoami`'");
}
#[test]
fn powershell_embedded_single_quote() {
assert_eq!(shell_escape_powershell("it's"), "'it''s'");
}
#[test]
fn powershell_windows_path() {
assert_eq!(
shell_escape_powershell(r"C:\Users\Alice\My Documents"),
r"'C:\Users\Alice\My Documents'"
);
}
#[test]
fn powershell_hostile_injection() {
let hostile = "'; Remove-Item -Recurse C:\\; $x='";
let escaped = shell_escape_powershell(hostile);
assert_eq!(escaped, "'''; Remove-Item -Recurse C:\\; $x='''");
}
#[test]
fn powershell_parens() {
assert_eq!(shell_escape_powershell("a(b)c"), "'a(b)c'");
}
#[test]
fn cmd_plain_value() {
assert_eq!(shell_escape_cmd("myprofile"), "myprofile");
}
#[test]
fn cmd_spaces() {
assert_eq!(shell_escape_cmd("my profile"), "my profile");
}
#[test]
fn cmd_percent_sign() {
assert_eq!(shell_escape_cmd("%PATH%"), "%%PATH%%");
}
#[test]
fn cmd_double_quote() {
assert_eq!(shell_escape_cmd(r#"say "hi""#), r#"say ""hi"""#);
}
#[test]
fn cmd_windows_path_with_spaces() {
assert_eq!(
shell_escape_cmd(r"C:\Program Files\codex"),
r"C:\Program Files\codex"
);
}
#[test]
fn cmd_hostile_percent_injection() {
let hostile = "%COMSPEC% /c calc.exe";
assert_eq!(shell_escape_cmd(hostile), "%%COMSPEC%% /c calc.exe");
}
#[test]
fn cmd_hostile_quote_injection() {
let hostile = r#"foo" & calc.exe & set "X=bar"#;
let escaped = shell_escape_cmd(hostile);
assert!(escaped.contains("\"\""));
assert!(!escaped.starts_with('\"'));
}
#[test]
fn cmd_empty_value() {
assert_eq!(shell_escape_cmd(""), "");
}
}