fix(ssh): avoid unnecessary quotes around login addresses

This commit is contained in:
Kaede Akino committed 2026-09-28 01:08:00 +08:00
1 parent 93338ed22b
commit d30388381a
2 files changed
+72 -17

No files matched your search

@@ -1864,9 +1864,21 @@ public final class HostLibraryModel: RemoteSessionHostStore {
/// POSIX-shell quoting for a single argument. Common SSH punctuation stays
/// readable; everything else uses single quotes without losing apostrophes.
private static func shellQuoted(_ value: String) -> String {
let safe = CharacterSet(
charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@%+=:,./-")
if !value.isEmpty, value.unicodeScalars.allSatisfy(safe.contains) { return value }
// Keep this an explicit byte loop: the Release build has miscompiled
// the CharacterSet.contains method reference passed to allSatisfy.
var requiresQuoting = value.isEmpty
for byte in value.utf8 {
switch byte {
case 0x30...0x39, 0x41...0x5A, 0x61...0x7A: // 0-9, A-Z, a-z
continue
case 0x5F, 0x40, 0x25, 0x2B, 0x3D, 0x3A, 0x2C, 0x2E, 0x2F, 0x2D: // _@%+=:,./-
continue
default:
requiresQuoting = true
}
break
}
if !requiresQuoting { return value }
return "'\(value.replacingOccurrences(of: "'", with: "'\"'\"'"))'"
}
+57 -14
View File
@@ -200,9 +200,10 @@ struct PortForwardingFeatureTests {
hosts: [jump, destination], identities: [identity, destinationIdentity]
)
let command = store.sshCommand(for: rule)
#expect(command?.contains("-J deploy@jump.example") == true)
#expect(command?.contains("-p 2222") == true)
#expect(command?.contains("-L 127.0.0.1:15432:db.internal:5432") == true)
#expect(
command
== "ssh -N -L 127.0.0.1:15432:db.internal:5432 -J deploy@jump.example -p 2222 root@destination.example"
)
jump.proxy = HostProxy(kind: .http, hostname: "proxy.example", port: 8080)
let proxiedStore = HostLibraryModel(
@@ -228,32 +229,74 @@ struct PortForwardingFeatureTests {
)
let command = store.sshCommand(for: destination)
#expect(command?.contains("-J 'deploy@[2001:db8::10]:2200'") == true)
#expect(command?.contains("-p 2222") == true)
#expect(command?.hasSuffix("'root@destination host'") == true)
#expect(command == "ssh -J 'deploy@[2001:db8::10]:2200' -p 2222 'root@destination host'")
let dynamic = PortForwardingRule(
hostID: destination.id, kind: .dynamic,
bindHost: "::1", bindPort: 1080
)
#expect(
store.sshCommand(for: dynamic)?.contains("-D '[::1]:1080'") == true
store.sshCommand(for: dynamic)
== "ssh -N -D '[::1]:1080' -J 'deploy@[2001:db8::10]:2200' -p 2222 'root@destination host'"
)
}
@Test
func sshCommandDoesNotQuoteExecutableOrOrdinaryLoginAddress() {
let identity = Identity(name: "Deploy", username: "deploy-user")
@Test(arguments: [
("root", "192.236.229.190", 22, "ssh root@192.236.229.190"),
("deploy-user", "192.0.2.10", 22, "ssh deploy-user@192.0.2.10"),
("deploy-user", "server.example", 2222, "ssh -p 2222 deploy-user@server.example"),
("root", "2001:db8::10", 22, "ssh root@2001:db8::10"),
("", "server.example", 22, "ssh server.example"),
("USER_1%+=:,./-", "server.example", 22, "ssh USER_1%+=:,./-@server.example"),
])
func sshCommandDoesNotQuoteExecutableOrOrdinaryLoginAddress(
username: String, address: String, port: Int, expected: String
) {
let identity = Identity(name: "Login", username: username)
let host = Host(
name: "Documentation Server", host: "192.0.2.10",
name: "Server", host: address, port: port,
identityID: identity.id
)
let store = HostLibraryModel(hosts: [host], identities: [identity])
#expect(
store.sshCommand(for: host)
== "ssh deploy-user@192.0.2.10"
#expect(store.sshCommand(for: host) == expected)
}
@Test(arguments: [
("root", "server host", "ssh 'root@server host'"),
("o'brien", "server.example", "ssh 'o'\"'\"'brien@server.example'"),
("root", "$HOME", "ssh 'root@$HOME'"),
("root", "server;whoami", "ssh 'root@server;whoami'"),
("root", "[2001:db8::10]", "ssh 'root@[2001:db8::10]'"),
("用户", "server.example", "ssh '用户@server.example'"),
("", ";", "ssh ';'"),
("", "$", "ssh '$'"),
("", "*", "ssh '*'"),
("", "'", "ssh ''\"'\"''"),
("", "", "ssh ''"),
])
func sshCommandPreservesShellQuoting(username: String, address: String, expected: String) {
let identity = Identity(name: "Login", username: username)
let host = Host(name: "Server", host: address, identityID: identity.id)
let store = HostLibraryModel(hosts: [host], identities: [identity])
#expect(store.sshCommand(for: host) == expected)
}
@Test(arguments: [
("operator", "console.example", "telnet -l operator console.example 2323"),
("o'brien", "console host", "telnet -l 'o'\"'\"'brien' 'console host' 2323"),
("$", ";", "telnet -l '$' ';' 2323"),
])
func telnetCommandUsesTheSameShellQuoting(username: String, address: String, expected: String) {
let identity = Identity(name: "Login", username: username)
let host = Host(
name: "Console", host: address, sshEnabled: false, telnetEnabled: true,
telnetPort: 2323, telnetIdentityID: identity.id
)
let store = HostLibraryModel(hosts: [host], identities: [identity])
#expect(store.telnetCommand(for: host) == expected)
}
@Test