Fix #1001: Enable controlling shell expansion via "argsCanBeInterpretedByShell"

Fix #357: "argsExpansion" does not do what it says in VSCode

Treat non-array "args" in debug config as a request to prevent shell argument escaping and allow shell expansion.

Remove "argsExpansion".
This commit is contained in:
Pavel Minaev 2022-08-03 17:29:19 -07:00 committed by Pavel Minaev
parent 6b276e339c
commit 4f6638b0a6
8 changed files with 68 additions and 46 deletions

View file

@ -49,7 +49,6 @@ class DebugConfig(MutableMapping):
"type": (),
# Launch
"args": [],
"argsExpansion": "shell",
"code": (),
"console": "internal",
"cwd": (),

View file

@ -325,9 +325,12 @@ debugpy.connect({(host, port)!r})
attach_listen.host = "127.0.0.1"
attach_listen.port = net.get_test_server_port(5478, 5600)
all_launch_terminal = [launch["integratedTerminal"], launch["externalTerminal"]]
all_launch_terminal = [
launch.with_options(console="integratedTerminal"),
launch.with_options(console="externalTerminal"),
]
all_launch = [launch["internalConsole"]] + all_launch_terminal
all_launch = [launch.with_options(console="internalConsole")] + all_launch_terminal
all_attach_listen = [attach_listen["api"], attach_listen["cli"]]

View file

@ -490,7 +490,11 @@ class Session(object):
def _process_request(self, request):
self.timeline.record_request(request, block=False)
if request.command == "runInTerminal":
args = request("args", json.array(str))
args = request("args", json.array(str, vectorize=True))
if len(args) > 0 and request("argsCanBeInterpretedByShell", False):
# The final arg is a string that contains multiple actual arguments.
last_arg = args.pop()
args += last_arg.split()
cwd = request("cwd", ".")
env = request("env", json.object(str))
try:
@ -557,6 +561,7 @@ class Session(object):
"columnsStartAt1": True,
"supportsVariableType": True,
"supportsRunInTerminalRequest": True,
"supportsArgsCanBeInterpretedByShell": True,
},
)

View file

@ -45,6 +45,14 @@ class Target(object):
"""
raise NotImplementedError
@property
def argslist(self):
args = self.args
if isinstance(args, str):
return [args]
else:
return list(args)
@property
def co_filename(self):
"""co_filename of code objects created at runtime from the source that this
@ -121,9 +129,11 @@ class Program(Target):
def cli(self, env):
if self._cwd:
return [self._get_relative_program()] + list(self.args)
cli = [self._get_relative_program()]
else:
return [self.filename.strpath] + list(self.args)
cli = [self.filename.strpath]
cli += self.argslist
return cli
class Module(Target):
@ -150,7 +160,7 @@ class Module(Target):
def cli(self, env):
if self.filename is not None:
env.prepend_to("PYTHONPATH", self.filename.dirname)
return ["-m", self.name] + list(self.args)
return ["-m", self.name] + self.argslist
class Code(Target):
@ -176,7 +186,7 @@ class Code(Target):
session.config["args"] = self.args
def cli(self, env):
return ["-c", self.code] + list(self.args)
return ["-c", self.code] + self.argslist
@property
def co_filename(self):

View file

@ -34,8 +34,11 @@ def test_args(pyfile, target, run):
@pytest.mark.parametrize("target", targets.all)
@pytest.mark.parametrize("run", runners.all_launch)
@pytest.mark.parametrize("expansion", ["", "none", "shell"])
@pytest.mark.parametrize("expansion", ["preserve", "expand"])
def test_shell_expansion(pyfile, target, run, expansion):
if expansion == "expand" and run.console == "internalConsole":
pytest.skip('Shell expansion is not supported for "internalConsole"')
@pyfile
def code_to_debug():
import sys
@ -46,6 +49,8 @@ def test_shell_expansion(pyfile, target, run, expansion):
backchannel.send(sys.argv)
def expand(args):
if expansion != "expand":
return
log.info("Before expansion: {0}", args)
for i, arg in enumerate(args):
if arg.startswith("$"):
@ -57,17 +62,14 @@ def test_shell_expansion(pyfile, target, run, expansion):
expand(args)
return super().run_in_terminal(args, cwd, env)
args = ["0", "$1", "2"]
argslist = ["0", "$1", "2"]
args = argslist if expansion == "preserve" else " ".join(argslist)
with Session() as session:
if expansion:
session.config["argsExpansion"] = expansion
backchannel = session.open_backchannel()
with run(session, target(code_to_debug, args=args)):
pass
argv = backchannel.receive()
if session.config["console"] != "internalConsole" and expansion != "none":
expand(args)
assert argv == [some.str] + args
expand(argslist)
assert argv == [some.str] + argslist