Support SSH usernames which contain @ symbols (#25314)

Color Fuzzy and Conrad Irwin created

Closes #25246

Release Notes:

- SSH: Improved handling of multiple `@` in connection strings: e.g.
`ssh jim.lv@es2@10.220.67.57@11.239.1.231` improving support of jump
hosts running JumpServer.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

Cargo.lock                       | 1 +
Cargo.toml                       | 1 +
crates/feedback/Cargo.toml       | 2 +-
crates/remote/Cargo.toml         | 1 +
crates/remote/src/ssh_session.rs | 7 +++++--
crates/zed/Cargo.toml            | 2 +-
6 files changed, 10 insertions(+), 4 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -11253,6 +11253,7 @@ dependencies = [
  "smol",
  "tempfile",
  "thiserror 1.0.69",
+ "urlencoding",
  "util",
 ]
 

Cargo.toml 🔗

@@ -565,6 +565,7 @@ unindent = "0.2.0"
 unicode-segmentation = "1.10"
 unicode-script = "0.5.7"
 url = "2.2"
+urlencoding = "2.1.2"
 uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
 wasmparser = "0.221"
 wasm-encoder = "0.221"

crates/feedback/Cargo.toml 🔗

@@ -36,7 +36,7 @@ serde_json.workspace = true
 smol.workspace = true
 sysinfo.workspace = true
 ui.workspace = true
-urlencoding = "2.1.2"
+urlencoding.workspace = true
 util.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true

crates/remote/Cargo.toml 🔗

@@ -39,6 +39,7 @@ shlex.workspace = true
 smol.workspace = true
 tempfile.workspace = true
 thiserror.workspace = true
+urlencoding.workspace = true
 util.workspace = true
 
 [dev-dependencies]

crates/remote/src/ssh_session.rs 🔗

@@ -203,7 +203,8 @@ impl SshConnectionOptions {
                 anyhow::bail!("unsupported argument: {:?}", arg);
             }
             let mut input = &arg as &str;
-            if let Some((u, rest)) = input.split_once('@') {
+            // Destination might be: username1@username2@ip2@ip1
+            if let Some((u, rest)) = input.rsplit_once('@') {
                 input = rest;
                 username = Some(u.to_string());
             }
@@ -238,7 +239,9 @@ impl SshConnectionOptions {
     pub fn ssh_url(&self) -> String {
         let mut result = String::from("ssh://");
         if let Some(username) = &self.username {
-            result.push_str(username);
+            // Username might be: username1@username2@ip2
+            let username = urlencoding::encode(username);
+            result.push_str(&username);
             result.push('@');
         }
         result.push_str(&self.host);

crates/zed/Cargo.toml 🔗

@@ -124,7 +124,7 @@ time.workspace = true
 toolchain_selector.workspace = true
 ui.workspace = true
 url.workspace = true
-urlencoding = "2.1.2"
+urlencoding.workspace = true
 util.workspace = true
 uuid.workspace = true
 vim.workspace = true