fix: Escape last ; in env var value before passing to tmux (#912)

* fix: Escape last ; in env var value before passing to tmux

* Use shell_quote for more robust quoting

* Use Sh quoting always

* Revert to original escaping, add test

---------

Co-authored-by: LoricAndre <57358788+LoricAndre@users.noreply.github.com>
This commit is contained in:
Mathieu Lemay 2026-01-22 11:49:18 -05:00 committed by GitHub
parent ebab1d70c4
commit 554896db64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 36 additions and 1 deletions

View file

@ -214,6 +214,7 @@ pub fn run_with(opts: &SkimOptions) -> Option<SkimOutput> {
for (name, value) in std::env::vars() {
if name.starts_with("SKIM") || name == "PATH" || name.starts_with("RUST") {
let value = sanitize_value(value);
debug!("adding {name} = {value} to the command's env");
tmux_cmd.args(["-e", &format!("{name}={value}")]);
}
@ -332,6 +333,16 @@ fn push_quoted_arg(args_str: &mut String, arg: &str) {
));
}
fn sanitize_value(value: String) -> String {
if !value.ends_with(';') {
return value;
}
let mut value = value.clone();
value.replace_range(value.len() - 1.., "\\;");
value
}
#[cfg(test)]
mod tests {
use super::*;
@ -392,4 +403,20 @@ mod tests {
check("right,10%,20", "20", "10%", x, y);
check("right,10%,20%", "20%", "10%", x, y);
}
#[test]
fn test_sanitize_value() {
assert_eq!(sanitize_value("some-value".to_string()), "some-value".to_string());
assert_eq!(sanitize_value("some-value;".to_string()), "some-value\\;".to_string());
assert_eq!(sanitize_value("some-value;;".to_string()), "some-value;\\;".to_string());
assert_eq!(
sanitize_value("some-value;;;".to_string()),
"some-value;;\\;".to_string()
);
assert_eq!(sanitize_value("some-value;x".to_string()), "some-value;x".to_string());
assert_eq!(
sanitize_value("some-value;x;".to_string()),
"some-value;x\\;".to_string()
);
}
}

View file

@ -72,12 +72,14 @@ fn tmux_quote_bash() -> Result<()> {
let mut tmux = TmuxController::new()?;
let outfile = setup_tmux_mock(&tmux)?;
tmux.send_keys(&[Str("export SHELL=/bin/bash"), Enter])?;
tmux.send_keys(&[Str("export SKIM_ESCAPED_VAR=';;'"), Enter])?;
tmux.start_sk(None, &["--tmux", "--bind 'ctrl-a:reload(ls /foo*)'"])?;
tmux.until(|_| Path::new(&outfile).exists())?;
let cmd = get_tmux_cmd(&outfile)?;
assert!(cmd.starts_with("display-popup"));
assert!(cmd.contains("-E"));
assert!(cmd.contains("--bind $'ctrl-a:reload(ls /foo*)'"));
assert!(cmd.contains("SKIM_ESCAPED_VAR=;\\;"));
Ok(())
}
@ -86,6 +88,7 @@ fn tmux_quote_zsh() -> Result<()> {
let mut tmux = TmuxController::new()?;
let outfile = setup_tmux_mock(&tmux)?;
tmux.send_keys(&[Str("export SHELL=/bin/zsh"), Enter])?;
tmux.send_keys(&[Str("export SKIM_ESCAPED_VAR=';;'"), Enter])?;
tmux.start_sk(None, &["--tmux", "--bind 'ctrl-a:reload(ls /foo*)'"])?;
tmux.until(|_| Path::new(&outfile).exists())?;
let cmd = get_tmux_cmd(&outfile)?;
@ -93,6 +96,7 @@ fn tmux_quote_zsh() -> Result<()> {
assert!(cmd.starts_with("display-popup"));
assert!(cmd.contains("-E"));
assert!(cmd.contains("sk --bind $'ctrl-a:reload(ls /foo*)' >"));
assert!(cmd.contains("SKIM_ESCAPED_VAR=;\\;"));
Ok(())
}
@ -101,12 +105,14 @@ fn tmux_quote_sh() -> Result<()> {
let mut tmux = TmuxController::new()?;
let outfile = setup_tmux_mock(&tmux)?;
tmux.send_keys(&[Str("export SHELL=/bin/sh"), Enter])?;
tmux.send_keys(&[Str("export SKIM_ESCAPED_VAR=';;'"), Enter])?;
tmux.start_sk(None, &["--tmux", "--bind 'ctrl-a:reload(ls /foo*)'"])?;
tmux.until(|_| Path::new(&outfile).exists())?;
let cmd = get_tmux_cmd(&outfile)?;
assert!(cmd.starts_with("display-popup"));
assert!(cmd.contains("-E"));
assert!(cmd.contains("--bind ctrl-a':reload(ls /foo*)'"));
assert!(cmd.contains("SKIM_ESCAPED_VAR=;\\;"));
Ok(())
}
@ -114,13 +120,15 @@ fn tmux_quote_sh() -> Result<()> {
fn tmux_quote_fish() -> Result<()> {
let mut tmux = TmuxController::new()?;
let outfile = setup_tmux_mock(&tmux)?;
tmux.send_keys(&[Str("export SHELL=/bin/sh"), Enter])?;
tmux.send_keys(&[Str("export SHELL=/bin/fish"), Enter])?;
tmux.send_keys(&[Str("export SKIM_ESCAPED_VAR=';;'"), Enter])?;
tmux.start_sk(None, &["--tmux", "--bind 'ctrl-a:reload(ls /foo*)'"])?;
tmux.until(|_| Path::new(&outfile).exists())?;
let cmd = get_tmux_cmd(&outfile)?;
assert!(cmd.starts_with("display-popup"));
assert!(cmd.contains("-E"));
assert!(cmd.contains("--bind ctrl-a':reload(ls /foo*)'"));
assert!(cmd.contains("SKIM_ESCAPED_VAR=;\\;"));
Ok(())
}