Skip to content

Commit dc880a3

Browse files
fix: escape ampersand and question mark in ProxyCommand (#480)
* fix: escape ampersand and question mark in ProxyCommand Also add some notes on characters double quotes does not handle, in case we need to do something about them in the future. Fixes #479. * Update platform version Looks like the one we were using disappeared again. * Add tests for escaping URLs * Add changelog entry --------- Co-authored-by: Asher <[email protected]>
1 parent b493bb0 commit dc880a3

File tree

6 files changed

+45
-5
lines changed

6 files changed

+45
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
## Unreleased
66

7+
### Fixed
8+
9+
- When a proxy command argument (such as the URL) contains `?` and `&`, escape
10+
it in the SSH config by using double quotes, as these characters have special
11+
meanings in shells.
12+
713
## 2.14.0 - 2024-08-30
814

915
### Fixed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pluginUntilBuild=242.*
2626
# that exists, ideally the most recent one, for example
2727
# 233.15325-EAP-CANDIDATE-SNAPSHOT).
2828
platformType=GW
29-
platformVersion=233.15325-EAP-CANDIDATE-SNAPSHOT
29+
platformVersion=233.15619-EAP-CANDIDATE-SNAPSHOT
3030
instrumentationCompiler=242.19533-EAP-CANDIDATE-SNAPSHOT
3131
# Gateway does not have open sources.
3232
platformDownloadSources=true

src/main/kotlin/com/coder/gateway/util/Escape.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@ package com.coder.gateway.util
33
/**
44
* Escape an argument to be used in the ProxyCommand of an SSH config.
55
*
6-
* Escaping happens by surrounding with double quotes if the argument contains
7-
* whitespace and escaping any existing double quotes regardless of whitespace.
6+
* Escaping happens by:
7+
* 1. Surrounding with double quotes if the argument contains whitespace, ?, or
8+
* & (to handle query parameters in URLs) as these characters have special
9+
* meaning in shells.
10+
* 2. Always escaping existing double quotes.
11+
*
12+
* Double quotes does not preserve the literal values of $, `, \, *, @, and !
13+
* (when history expansion is enabled); these are not currently handled.
814
*
915
* Throws if the argument is invalid.
1016
*/
1117
fun escape(s: String): String {
1218
if (s.contains("\n")) {
1319
throw Exception("argument cannot contain newlines")
1420
}
15-
if (s.contains(" ") || s.contains("\t")) {
21+
if (s.contains(" ") || s.contains("\t") || s.contains("&") || s.contains("?")) {
1622
return "\"" + s.replace("\"", "\\\"") + "\""
1723
}
1824
return s.replace("\"", "\\\"")

src/test/fixtures/outputs/url.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# --- START CODER JETBRAINS test.coder.invalid
2+
Host coder-jetbrains--url--test.coder.invalid
3+
ProxyCommand /tmp/coder-gateway/test.coder.invalid/coder-linux-amd64 --global-config /tmp/coder-gateway/test.coder.invalid/config --url "https://test.coder.invalid?foo=bar&baz=qux" ssh --stdio --usage-app=jetbrains url
4+
ConnectTimeout 0
5+
StrictHostKeyChecking no
6+
UserKnownHostsFile /dev/null
7+
LogLevel ERROR
8+
SetEnv CODER_SSH_SESSION_TYPE=JetBrains
9+
Host coder-jetbrains--url--test.coder.invalid--bg
10+
ProxyCommand /tmp/coder-gateway/test.coder.invalid/coder-linux-amd64 --global-config /tmp/coder-gateway/test.coder.invalid/config --url "https://test.coder.invalid?foo=bar&baz=qux" ssh --stdio --usage-app=disable url
11+
ConnectTimeout 0
12+
StrictHostKeyChecking no
13+
UserKnownHostsFile /dev/null
14+
LogLevel ERROR
15+
SetEnv CODER_SSH_SESSION_TYPE=JetBrains
16+
# --- END CODER JETBRAINS test.coder.invalid

src/test/kotlin/com/coder/gateway/cli/CoderCLIManagerTest.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ internal class CoderCLIManagerTest {
303303
val extraConfig: String = "",
304304
val env: Environment = Environment(),
305305
val sshLogDirectory: Path? = null,
306+
val url: URL? = null
306307
)
307308

308309
@Test
@@ -390,6 +391,13 @@ internal class CoderCLIManagerTest {
390391
"blank",
391392
sshLogDirectory = tmpdir.resolve("ssh-logs"),
392393
),
394+
SSHTest(
395+
listOf("url"),
396+
input = null,
397+
output = "url",
398+
remove = "blank",
399+
url = URL("https://test.coder.invalid?foo=bar&baz=qux"),
400+
),
393401
)
394402

395403
val newlineRe = "\r?\n".toRegex()
@@ -408,7 +416,7 @@ internal class CoderCLIManagerTest {
408416
env = it.env,
409417
)
410418

411-
val ccm = CoderCLIManager(URL("https://test.coder.invalid"), settings)
419+
val ccm = CoderCLIManager(it.url ?: URL("https://test.coder.invalid"), settings)
412420

413421
// Input is the configuration that we start with, if any.
414422
if (it.input != null) {

src/test/kotlin/com/coder/gateway/util/EscapeTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ internal class EscapeTest {
1515
"""C:\echo "hello world"""" to """"C:\echo \"hello world\""""",
1616
"""C:\"no"\"spaces"""" to """C:\\"no\"\\"spaces\"""",
1717
""""C:\Program Files\HeaderCommand.exe" --flag""" to """"\"C:\Program Files\HeaderCommand.exe\" --flag"""",
18+
"https://coder.com" to """https://coder.com""",
19+
"https://coder.com/?question" to """"https://coder.com/?question"""",
20+
"https://coder.com/&ampersand" to """"https://coder.com/&ampersand"""",
21+
"https://coder.com/?with&both" to """"https://coder.com/?with&both"""",
1822
)
1923
tests.forEach {
2024
assertEquals(it.value, escape(it.key))

0 commit comments

Comments
 (0)