Repository navigation
fix: quote file paths with shell metacharacters in generated terminal commands #1197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Bondrewd-K
wants to merge
4
commits into
quarto-dev:main
Choose a base branch
from
Bondrewd-K:fix/vscode-shquote-parens
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+400
−22
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8fc8ce6
fix: quote file paths with shell metacharacters in generated terminal…
Bondrewd-K ca40e5e
Merge remote-tracking branch 'origin/main' into Bondrewd-K-fix/vscode…
juliasilge e0187ae
fix: serialize terminal command arguments per target shell
Bondrewd-K d609047
fix: address review comments on shell-aware terminal quoting
Bondrewd-K File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (C) 2026 Posit Software, PBC. All rights reserved. | ||
| * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| import * as assert from "assert"; | ||
| import * as fs from "node:fs"; | ||
| import * as os from "node:os"; | ||
| import * as path from "node:path"; | ||
|
|
||
| import { pythonIsCondaEnv } from "../core/python"; | ||
|
|
||
| suite("pythonIsCondaEnv", function () { | ||
| test("accepts a raw interpreter path with shell special characters", function () { | ||
| if (process.platform === "win32") { | ||
| // A shebang script does not run on Windows. | ||
| this.skip(); | ||
| } | ||
| // execFileSync does not spawn a shell, so the interpreter path must be | ||
| // passed raw: with the old shell-quoted path the file does not exist and | ||
| // the function returns false (quarto-dev/quarto#1197). A shebang script | ||
| // named `python` stands in for a real interpreter without needing conda. | ||
| const dir = fs.mkdtempSync(path.join(os.tmpdir(), "quarto-test-dir (x) $y-")); | ||
| const script = path.join(dir, "python"); | ||
| try { | ||
| fs.writeFileSync(script, "#!/bin/sh\necho True\n"); | ||
| fs.chmodSync(script, 0o755); | ||
| assert.strictEqual(pythonIsCondaEnv(script), true); | ||
| } finally { | ||
| fs.rmSync(dir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (C) 2026 Posit Software, PBC. All rights reserved. | ||
| * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| import * as assert from "assert"; | ||
| import * as vscode from "vscode"; | ||
| import { QuartoContext } from "quarto-core"; | ||
|
|
||
| import { terminalCommand, terminalShellForPath } from "../core/terminal"; | ||
|
|
||
| suite("terminalCommand", function () { | ||
| test("does not quote the quarto executable name", function () { | ||
| // PowerShell does not run a line that starts with a quoted string as a | ||
| // command (it needs `&`), so the executable must stay a bare word — | ||
| // quoting it broke every Windows preview (quarto-dev/quarto#1197). | ||
| const context = { useCmd: false } as unknown as QuartoContext; | ||
| const cmd = terminalCommand("preview", context); | ||
| assert.deepStrictEqual(cmd, ["quarto", "preview"]); | ||
| assert.ok(!/^['"]/.test(cmd[0]), "executable name must not be quoted"); | ||
| }); | ||
|
|
||
| test("passes the document path as one literal argument", function () { | ||
| // The #1194 fix itself: a path with parentheses must survive as one | ||
| // literal argument. Quoting shape is shell-dependent (and the test | ||
| // host's default shell varies), so assert singularity and literal | ||
| // survival; exact serializer output is covered by the shQuote tests. | ||
| const context = { useCmd: false } as unknown as QuartoContext; | ||
| const target = vscode.Uri.file("C:/Users/me/Article(md)/01_introduction.md"); | ||
| const cmd = terminalCommand("preview", context, target); | ||
| assert.strictEqual(cmd[0], "quarto"); | ||
| assert.strictEqual(cmd[1], "preview"); | ||
| assert.strictEqual(cmd.length, 3, "document path must be a single argument"); | ||
| assert.ok( | ||
| cmd[2].includes("Article(md)"), | ||
| `document path must survive literally, got: ${cmd[2]}` | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| suite("terminalShellForPath", function () { | ||
| test("maps the terminal's actual shell to quoting rules", function () { | ||
| // terminalCommand() quotes for the shell the terminal will actually | ||
| // start (vscode.env.shell), not the OS platform (quarto-dev/quarto#1197). | ||
| const cases: Array<[string | undefined, string | undefined]> = [ | ||
| ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "powershell"], | ||
| ["/usr/local/bin/pwsh", "powershell"], | ||
| ["C:\\Windows\\System32\\cmd.exe", "cmd"], | ||
| ["/bin/bash", "posix"], | ||
| ["/bin/zsh", "posix"], | ||
| ["/bin/sh", "posix"], | ||
| ["/usr/bin/fish", undefined], | ||
| ["", undefined], | ||
| [undefined, undefined], | ||
| ]; | ||
| for (const [shellPath, expected] of cases) { | ||
| assert.strictEqual( | ||
| terminalShellForPath(shellPath), | ||
| expected, | ||
| `shell path: ${shellPath}` | ||
| ); | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this fix! My earlier review also asked for a test of this change, and I don't see one quite yet.
The
vscodeimport stops this module from running under plainnode:test, but the VS Code extension tests inapps/vscode/src/test/run inside the extension host, wherevscodeis available. Can you add a test there withnpm run test-vscode?The test must fail with the old shell-quoted path and pass with the raw path. A
falseresult does not show this, because thecatchreturnsfalsefor both. One way to get atrueresult without conda:dir (x) $y.pythonthat contains#!/bin/shandecho True.pythonIsCondaEnv()returnstruefor the script path.With the old code, the quoted path does not exist, so the function returns
falseand the test fails. A shebang script does not run on Windows, so skip this test onwin32.