use anyhow::{Context, Result};
use std::fs;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
pub const BUILTIN_HOST_COMMANDS: &[&str] = &["afplay", "wl-paste", "xclip"];
const CLIPBOARD_SHIMS: &[(&str, &str)] = &[
(
"wl-paste",
r#"#!/bin/sh
mime=""
list_types=0
while [ $# -gt 0 ]; do
case "$1" in
-l|--list-types) list_types=1; shift ;;
-t|--type) [ $# -ge 2 ] || exit 1; mime="$2"; shift 2 ;;
-n|--no-newline) shift ;;
*) shift ;;
esac
done
if [ "$list_types" -eq 1 ]; then
printf '%s\n' image/png
exit 0
fi
[ -n "$mime" ] || exit 1
exec workmux clipboard-read "$mime"
"#,
),
(
"xclip",
r#"#!/bin/sh
mime=""
output=0
while [ $# -gt 0 ]; do
case "$1" in
-o) output=1; shift ;;
-selection) shift; shift ;;
-t) [ $# -ge 2 ] || exit 1; mime="$2"; shift 2 ;;
-i) echo "workmux: xclip write not supported in sandbox" >&2; exit 1 ;;
*) shift ;;
esac
done
[ "$output" -eq 1 ] || { echo "workmux: xclip write not supported in sandbox" >&2; exit 1; }
[ -n "$mime" ] || exit 1
exec workmux clipboard-read "$mime"
"#,
),
];
fn clipboard_shim_script(cmd: &str) -> Option<&'static str> {
CLIPBOARD_SHIMS
.iter()
.find(|(name, _)| *name == cmd)
.map(|(_, script)| *script)
}
pub fn is_clipboard_shim(cmd: &str) -> bool {
CLIPBOARD_SHIMS.iter().any(|(name, _)| *name == cmd)
}
pub fn validate_command_name(cmd: &str) -> bool {
if cmd.is_empty() || cmd.len() > 64 {
return false;
}
if !cmd.as_bytes()[0].is_ascii_alphanumeric() {
return false;
}
if !cmd
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.'))
{
return false;
}
if matches!(cmd, "." | ".." | "_shim") {
return false;
}
true
}
pub fn effective_host_commands(user_commands: &[String]) -> Vec<String> {
let mut commands: Vec<String> = BUILTIN_HOST_COMMANDS
.iter()
.map(|s| s.to_string())
.collect();
for cmd in user_commands {
if !commands.iter().any(|c| c == cmd) {
commands.push(cmd.clone());
}
}
commands
}
pub fn create_shim_directory(state_dir: &Path, commands: &[String]) -> Result<PathBuf> {
let shim_bin = state_dir.join("shims/bin");
fs::create_dir_all(&shim_bin)
.with_context(|| format!("Failed to create shim dir: {}", shim_bin.display()))?;
let dispatcher = shim_bin.join("_shim");
fs::write(
&dispatcher,
"#!/bin/sh\nexec workmux host-exec \"$(basename \"$0\")\" \"$@\"\n",
)
.context("Failed to write shim dispatcher")?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&dispatcher, fs::Permissions::from_mode(0o755))?;
}
for cmd in commands {
if !validate_command_name(cmd) {
tracing::warn!(command = cmd, "skipping invalid host_command name");
continue;
}
let link = shim_bin.join(cmd);
let tmp = shim_bin.join(format!(".{}.tmp", cmd));
let _ = fs::remove_file(&tmp);
if let Some(script) = clipboard_shim_script(cmd) {
fs::write(&tmp, script)
.with_context(|| format!("Failed to write clipboard shim for: {}", cmd))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&tmp, fs::Permissions::from_mode(0o755))?;
}
} else {
symlink("_shim", &tmp)
.with_context(|| format!("Failed to create temp shim symlink for: {}", cmd))?;
}
fs::rename(&tmp, &link).with_context(|| format!("Failed to rename shim for: {}", cmd))?;
}
Ok(shim_bin)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_shim_directory() {
let tmp = tempfile::tempdir().unwrap();
let commands = vec!["just".to_string(), "cargo".to_string(), "npm".to_string()];
let shim_bin = create_shim_directory(tmp.path(), &commands).unwrap();
let dispatcher = shim_bin.join("_shim");
assert!(dispatcher.exists());
let content = std::fs::read_to_string(&dispatcher).unwrap();
assert!(content.contains("workmux host-exec"));
for cmd in &commands {
let link = shim_bin.join(cmd);
assert!(link.symlink_metadata().unwrap().file_type().is_symlink());
assert_eq!(std::fs::read_link(&link).unwrap(), PathBuf::from("_shim"));
}
}
#[test]
fn test_create_shim_directory_skips_invalid() {
let tmp = tempfile::tempdir().unwrap();
let commands = vec!["valid".to_string(), "/bin/evil".to_string(), "".to_string()];
let shim_bin = create_shim_directory(tmp.path(), &commands).unwrap();
assert!(shim_bin.join("valid").exists());
assert!(!shim_bin.join("/bin/evil").exists());
}
#[test]
fn test_effective_host_commands_includes_builtins() {
let result = effective_host_commands(&[]);
assert!(result.contains(&"afplay".to_string()));
assert!(result.contains(&"wl-paste".to_string()));
assert!(result.contains(&"xclip".to_string()));
}
#[test]
fn test_effective_host_commands_merges_user() {
let result = effective_host_commands(&["just".to_string(), "cargo".to_string()]);
assert!(result.contains(&"afplay".to_string()));
assert!(result.contains(&"wl-paste".to_string()));
assert!(result.contains(&"xclip".to_string()));
assert!(result.contains(&"just".to_string()));
assert!(result.contains(&"cargo".to_string()));
}
#[test]
fn test_effective_host_commands_deduplicates() {
let result = effective_host_commands(&["afplay".to_string(), "just".to_string()]);
let afplay_count = result.iter().filter(|c| *c == "afplay").count();
assert_eq!(afplay_count, 1);
assert!(result.contains(&"just".to_string()));
}
#[test]
fn test_create_shim_directory_idempotent() {
let tmp = tempfile::tempdir().unwrap();
let commands = vec!["just".to_string()];
create_shim_directory(tmp.path(), &commands).unwrap();
create_shim_directory(tmp.path(), &commands).unwrap();
assert!(tmp.path().join("shims/bin/just").exists());
}
#[test]
fn test_validate_command_name_valid() {
assert!(validate_command_name("just"));
assert!(validate_command_name("cargo"));
assert!(validate_command_name("npm"));
assert!(validate_command_name("node-v20"));
assert!(validate_command_name("my_tool"));
assert!(validate_command_name("go1.21"));
assert!(validate_command_name("afplay"));
}
#[test]
fn test_validate_command_name_rejects_empty() {
assert!(!validate_command_name(""));
}
#[test]
fn test_validate_command_name_rejects_path_separators() {
assert!(!validate_command_name("/bin/rm"));
assert!(!validate_command_name("..\\cmd"));
assert!(!validate_command_name("foo/bar"));
}
#[test]
fn test_validate_command_name_rejects_shell_metacharacters() {
assert!(!validate_command_name("cmd;rm -rf /"));
assert!(!validate_command_name("cmd$(whoami)"));
assert!(!validate_command_name("cmd`whoami`"));
assert!(!validate_command_name("cmd&bg"));
assert!(!validate_command_name("cmd|pipe"));
assert!(!validate_command_name("cmd>out"));
assert!(!validate_command_name("cmd<in"));
assert!(!validate_command_name("cmd name"));
assert!(!validate_command_name("cmd\ttab"));
assert!(!validate_command_name("cmd\nnewline"));
}
#[test]
fn test_validate_command_name_rejects_reserved() {
assert!(!validate_command_name("."));
assert!(!validate_command_name(".."));
assert!(!validate_command_name("_shim"));
}
#[test]
fn test_validate_command_name_rejects_leading_special() {
assert!(!validate_command_name("-flag"));
assert!(!validate_command_name("_underscore"));
assert!(!validate_command_name(".dotfile"));
}
#[test]
fn test_validate_command_name_rejects_too_long() {
let long = "a".repeat(65);
assert!(!validate_command_name(&long));
let ok = "a".repeat(64);
assert!(validate_command_name(&ok));
}
#[test]
fn test_clipboard_shims_are_regular_files() {
let tmp = tempfile::tempdir().unwrap();
let commands = vec![
"just".to_string(),
"wl-paste".to_string(),
"xclip".to_string(),
];
let shim_bin = create_shim_directory(tmp.path(), &commands).unwrap();
for cmd in &["wl-paste", "xclip"] {
let path = shim_bin.join(cmd);
let meta = path.symlink_metadata().unwrap();
assert!(
meta.file_type().is_file(),
"{} should be a regular file, not a symlink",
cmd
);
let content = std::fs::read_to_string(&path).unwrap();
assert!(
content.contains("workmux clipboard-read"),
"{} shim should call workmux clipboard-read",
cmd
);
}
let just_meta = shim_bin.join("just").symlink_metadata().unwrap();
assert!(just_meta.file_type().is_symlink());
}
#[test]
fn test_is_clipboard_shim() {
assert!(is_clipboard_shim("wl-paste"));
assert!(is_clipboard_shim("xclip"));
assert!(!is_clipboard_shim("afplay"));
assert!(!is_clipboard_shim("just"));
}
#[test]
fn test_wl_paste_shim_content() {
let script = clipboard_shim_script("wl-paste").unwrap();
assert!(script.starts_with("#!/bin/sh"));
assert!(script.contains("-t|--type"));
assert!(script.contains("--list-types"));
assert!(script.contains("image/png"));
assert!(script.contains("workmux clipboard-read"));
}
#[test]
fn test_xclip_shim_content() {
let script = clipboard_shim_script("xclip").unwrap();
assert!(script.starts_with("#!/bin/sh"));
assert!(script.contains("-o) output=1"));
assert!(script.contains("xclip write not supported"));
assert!(script.contains("workmux clipboard-read"));
}
}