diff --git a/Cargo.lock b/Cargo.lock index 64aab0137a3be79ab3f21c70d0fd0853accdafce..2e493c9afda2dff62ae49048a12a3e1d106b35df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3080,6 +3080,7 @@ name = "cli" version = "0.1.0" dependencies = [ "anyhow", + "askpass", "clap", "collections", "core-foundation 0.10.0", @@ -20215,7 +20216,6 @@ dependencies = [ "agent_ui", "anyhow", "ashpd 0.11.0", - "askpass", "assets", "assistant_tools", "audio", diff --git a/crates/askpass/src/askpass.rs b/crates/askpass/src/askpass.rs index 6405be5966ba512d68a9ab638d1216d678200968..df4ac19a93504cca4bb3d6234da33c3474ab5bba 100644 --- a/crates/askpass/src/askpass.rs +++ b/crates/askpass/src/askpass.rs @@ -85,11 +85,8 @@ impl AskPassSession { let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME); let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>(); let listener = UnixListener::bind(&askpass_socket).context("creating askpass socket")?; - #[cfg(not(target_os = "windows"))] - let zed_path = util::get_shell_safe_zed_path()?; - #[cfg(target_os = "windows")] - let zed_path = std::env::current_exe() - .context("finding current executable path for use in askpass")?; + let zed_cli_path = + util::get_shell_safe_zed_cli_path().context("getting zed-cli path for askpass")?; let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>(); let mut kill_tx = Some(askpass_kill_master_tx); @@ -137,7 +134,7 @@ impl AskPassSession { }); // Create an askpass script that communicates back to this process. - let askpass_script = generate_askpass_script(&zed_path, &askpass_socket); + let askpass_script = generate_askpass_script(&zed_cli_path, &askpass_socket); fs::write(&askpass_script_path, askpass_script) .await .with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?; @@ -254,10 +251,10 @@ pub fn main(socket: &str) { #[inline] #[cfg(not(target_os = "windows"))] -fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) -> String { +fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String { format!( - "{shebang}\n{print_args} | {zed_exe} --askpass={askpass_socket} 2> /dev/null \n", - zed_exe = zed_path, + "{shebang}\n{print_args} | {zed_cli} --askpass={askpass_socket} 2> /dev/null \n", + zed_cli = zed_cli_path, askpass_socket = askpass_socket.display(), print_args = "printf '%s\\0' \"$@\"", shebang = "#!/bin/sh", @@ -266,13 +263,13 @@ fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) -> #[inline] #[cfg(target_os = "windows")] -fn generate_askpass_script(zed_path: &std::path::Path, askpass_socket: &std::path::Path) -> String { +fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String { format!( r#" $ErrorActionPreference = 'Stop'; - ($args -join [char]0) | & "{zed_exe}" --askpass={askpass_socket} 2> $null + ($args -join [char]0) | & "{zed_cli}" --askpass={askpass_socket} 2> $null "#, - zed_exe = zed_path.display(), + zed_cli = zed_cli_path, askpass_socket = askpass_socket.display(), ) } diff --git a/crates/askpass/src/encrypted_password.rs b/crates/askpass/src/encrypted_password.rs index 2684f76e8992fa5f33bbeb07996e834fccbf04fa..b5495df6e43cab8efe7a3e505602df11d03a1092 100644 --- a/crates/askpass/src/encrypted_password.rs +++ b/crates/askpass/src/encrypted_password.rs @@ -67,7 +67,7 @@ impl TryFrom<&str> for EncryptedPassword { unsafe { CryptProtectMemory( value.as_mut_ptr() as _, - len, + padded_length, CRYPTPROTECTMEMORY_SAME_PROCESS, )?; } @@ -97,7 +97,7 @@ pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result { unsafe { CryptUnprotectMemory( password.0.as_mut_ptr() as _, - password.1, + password.0.len().try_into()?, CRYPTPROTECTMEMORY_SAME_PROCESS, ) .context("while decrypting a SSH password")? diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 28f80237292fcef67934c65d9d867613e6e1544c..812e56d1730b8e2ada34e98aa85a5767eed77997 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -22,6 +22,7 @@ default = [] [dependencies] anyhow.workspace = true +askpass.workspace = true clap.workspace = true collections.workspace = true ipc-channel = "0.19" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index de45ae44fccef1bde25c5d8216261c3847943589..f421ffe76c778d5ff275c146479315f6cd403f61 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -116,6 +116,11 @@ struct Args { ))] #[arg(long)] uninstall: bool, + + /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency, + /// by having Zed act like netcat communicating over a Unix socket. + #[arg(long, hide = true)] + askpass: Option, } fn parse_path_with_position(argument_str: &str) -> anyhow::Result { @@ -203,6 +208,12 @@ fn main() -> Result<()> { } let args = Args::parse(); + // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass + if let Some(socket) = &args.askpass { + askpass::main(socket); + return Ok(()); + } + // Set custom data directory before any path operations let user_data_dir = args.user_data_dir.clone(); if let Some(dir) = &user_data_dir { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index d67aa67f63917f6da42248b98be7a6171ef66892..732a07c982698d917dd80441c7766a769e31613b 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -14,7 +14,7 @@ pub mod size; pub mod test; pub mod time; -use anyhow::Result; +use anyhow::{Context as _, Result}; use futures::Future; use itertools::Either; use regex::Regex; @@ -290,8 +290,6 @@ fn load_shell_from_passwd() -> Result<()> { #[cfg(unix)] /// Returns a shell escaped path for the current zed executable pub fn get_shell_safe_zed_path() -> anyhow::Result { - use anyhow::Context; - let zed_path = std::env::current_exe() .context("Failed to determine current zed executable path.")? .to_string_lossy() @@ -307,6 +305,59 @@ pub fn get_shell_safe_zed_path() -> anyhow::Result { Ok(zed_path_escaped.to_string()) } +/// Returns a shell escaped path for the zed cli executable, this function +/// should be called from the zed executable, not zed-cli. +pub fn get_shell_safe_zed_cli_path() -> Result { + let zed_path = + std::env::current_exe().context("Failed to determine current zed executable path.")?; + let parent = zed_path + .parent() + .context("Failed to determine parent directory of zed executable path.")?; + + let possible_locations: &[&str] = if cfg!(target_os = "macos") { + // On macOS, the zed executable and zed-cli are inside the app bundle, + // so here ./cli is for both installed and development builds. + &["./cli"] + } else if cfg!(target_os = "windows") { + // bin/zed.exe is for installed builds, ./cli.exe is for development builds. + &["bin/zed.exe", "./cli.exe"] + } else if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") { + // bin is the standard, ./cli is for the target directory in development builds. + &["../bin/zed", "./cli"] + } else { + anyhow::bail!("unsupported platform for determining zed-cli path"); + }; + + let zed_cli_path = possible_locations + .iter() + .find_map(|p| { + parent + .join(p) + .canonicalize() + .ok() + .filter(|p| p != &zed_path) + }) + .with_context(|| { + format!( + "could not find zed-cli from any of: {}", + possible_locations.join(", ") + ) + })? + .to_string_lossy() + .to_string(); + + #[cfg(target_os = "windows")] + { + Ok(zed_cli_path) + } + #[cfg(not(target_os = "windows"))] + { + Ok(shlex::try_quote(&zed_cli_path) + .context("Failed to shell-escape Zed executable path.")? + .to_string()) + } +} + #[cfg(unix)] pub async fn load_login_shell_environment() -> Result<()> { load_shell_from_passwd().log_err(); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 9664c147f19f0fe3790ec30f283f2bb9a01f523b..28d3688f5f13946b50c57fb1cbbf678674a75e65 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -25,7 +25,6 @@ agent.workspace = true agent_settings.workspace = true agent_ui.workspace = true anyhow.workspace = true -askpass.workspace = true assets.workspace = true assistant_tools.workspace = true audio.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index dfa1d7ac4b207d66da34fb87c07bb3a32ae122c9..29868be577bac136e97781a0082d77fa35c73e25 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -176,12 +176,6 @@ pub fn main() { return; } - // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass - if let Some(socket) = &args.askpass { - askpass::main(socket); - return; - } - // `zed --nc` Makes zed operate in nc/netcat mode for use with MCP if let Some(socket) = &args.nc { match nc::main(socket) { @@ -1249,11 +1243,6 @@ struct Args { #[arg(long)] system_specs: bool, - /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency, - /// by having Zed act like netcat communicating over a Unix socket. - #[arg(long, hide = true)] - askpass: Option, - /// Used for the MCP Server, to remove the need for netcat as a dependency, /// by having Zed act like netcat communicating over a Unix socket. #[arg(long, hide = true)]