Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as vscode from "vscode"
import * as ws from "ws"
import { errToStr } from "./api-helper"
import { CertificateError } from "./error"
import { getHeaderArgs } from "./headers"
import { getProxyForUrl } from "./proxy"
import { Storage } from "./storage"
import { expandPath } from "./util"
Expand Down Expand Up @@ -168,6 +169,7 @@ export async function startWorkspaceIfStoppedOrFailed(
const startArgs = [
"--global-config",
globalConfigDir,
...getHeaderArgs(vscode.workspace.getConfiguration()),
"start",
"--yes",
workspace.owner_name + "/" + workspace.name,
Expand Down
22 changes: 20 additions & 2 deletions src/headers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as cp from "child_process"
import * as os from "os"
import * as util from "util"

import { WorkspaceConfiguration } from "vscode"
import type { WorkspaceConfiguration } from "vscode"
import { escapeCommandArg } from "./util"

export interface Logger {
writeToCoderOutputChannel(message: string): void
Expand All @@ -25,6 +26,23 @@ export function getHeaderCommand(config: WorkspaceConfiguration): string | undef
return cmd
}

export function getHeaderArgs(config: WorkspaceConfiguration): string[] {
// Escape a command line to be executed by the Coder binary, so ssh doesn't substitute variables.
const escapeSubcommand: (str: string) => string =
os.platform() === "win32"
? // On Windows variables are %VAR%, and we need to use double quotes.
(str) => escapeCommandArg(str).replace(/%/g, "%%")
: // On *nix we can use single quotes to escape $VARS.
// Note single quotes cannot be escaped inside single quotes.
(str) => `'${str.replace(/'/g, "'\\''")}'`

const command = getHeaderCommand(config)
if (!command) {
return []
}
return ["--header-command", escapeSubcommand(command)]
}

// TODO: getHeaders might make more sense to directly implement on Storage
// but it is difficult to test Storage right now since we use vitest instead of
// the standard extension testing framework which would give us access to vscode
Expand Down
30 changes: 8 additions & 22 deletions src/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import { extractAgents } from "./api-helper"
import * as cli from "./cliManager"
import { Commands } from "./commands"
import { featureSetForVersion, FeatureSet } from "./featureSet"
import { getHeaderCommand } from "./headers"
import { getHeaderArgs } from "./headers"
import { Inbox } from "./inbox"
import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig"
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
import { Storage } from "./storage"
import { AuthorityPrefix, expandPath, parseRemoteAuthority } from "./util"
import { AuthorityPrefix, escapeCommandArg, expandPath, parseRemoteAuthority } from "./util"
import { WorkspaceMonitor } from "./workspaceMonitor"

export interface RemoteDetails extends vscode.Disposable {
Expand Down Expand Up @@ -611,32 +611,18 @@ export class Remote {
const sshConfig = new SSHConfig(sshConfigFile)
await sshConfig.load()

const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"`
// Escape a command line to be executed by the Coder binary, so ssh doesn't substitute variables.
const escapeSubcommand: (str: string) => string =
os.platform() === "win32"
? // On Windows variables are %VAR%, and we need to use double quotes.
(str) => escape(str).replace(/%/g, "%%")
: // On *nix we can use single quotes to escape $VARS.
// Note single quotes cannot be escaped inside single quotes.
(str) => `'${str.replace(/'/g, "'\\''")}'`

// Add headers from the header command.
let headerArg = ""
const headerCommand = getHeaderCommand(vscode.workspace.getConfiguration())
if (typeof headerCommand === "string" && headerCommand.trim().length > 0) {
headerArg = ` --header-command ${escapeSubcommand(headerCommand)}`
}
const headerArgs = getHeaderArgs(vscode.workspace.getConfiguration())
const headerArgList = headerArgs.length > 0 ? ` ${headerArgs.join(" ")}` : ""

const hostPrefix = label ? `${AuthorityPrefix}.${label}--` : `${AuthorityPrefix}--`

const proxyCommand = featureSet.wildcardSSH
? `${escape(binaryPath)}${headerArg} --global-config ${escape(
? `${escapeCommandArg(binaryPath)}${headerArgList} --global-config ${escapeCommandArg(
path.dirname(this.storage.getSessionTokenPath(label)),
)} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escape(this.storage.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
: `${escape(binaryPath)}${headerArg} vscodessh --network-info-dir ${escape(
)} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escapeCommandArg(this.storage.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
: `${escapeCommandArg(binaryPath)}${headerArgList} vscodessh --network-info-dir ${escapeCommandArg(
this.storage.getNetworkInfoPath(),
)}${await this.formatLogArg(logDir)} --session-token-file ${escape(this.storage.getSessionTokenPath(label))} --url-file ${escape(
)}${await this.formatLogArg(logDir)} --session-token-file ${escapeCommandArg(this.storage.getSessionTokenPath(label))} --url-file ${escapeCommandArg(
this.storage.getUrlPath(label),
)} %h`

Expand Down
4 changes: 4 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,7 @@ export function expandPath(input: string): string {
const userHome = os.homedir()
return input.replace(/\${userHome}/g, userHome)
}

export function escapeCommandArg(arg: string): string {
return `"${arg.replace(/"/g, '\\"')}"`
}