Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 24 additions & 0 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,30 @@ def test_special_chars_csh(self):
self.assertTrue(env_name.encode() in lines[0])
self.assertEndsWith(lines[1], env_name.encode())

# gh-152686: '!' triggers csh history expansion even inside single quotes
@unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
@unittest.skipIf(sys.platform.startswith('netbsd'),
"NetBSD csh fails with quoted special chars; see gh-139308")
def test_special_chars_csh_prompt(self):
"""
Test that a '!' in the prompt is quoted properly (csh)
"""
rmtree(self.env_dir)
csh = shutil.which('tcsh') or shutil.which('csh')
if csh is None:
self.skipTest('csh required for this test')
prompt = 'py!env'
builder = venv.EnvBuilder(clear=True, prompt=prompt)
builder.create(self.env_dir)
activate = os.path.join(self.env_dir, self.bindir, 'activate.csh')
test_script = os.path.join(self.env_dir, 'test_special_chars_prompt.csh')
with open(test_script, "w") as f:
f.write(f'source {shlex.quote(activate)}\n'
'python -c \'import os; print(os.environ["VIRTUAL_ENV_PROMPT"])\'\n'
'deactivate\n')
out, err = check_output([csh, test_script])
self.assertEndsWith(out.splitlines()[-1], prompt.encode())

# gh-140006: the fish prompt override must keep working when a user
# function shadows a builtin it relies on.
@unittest.skipIf(os.name == 'nt', 'fish is not available on Windows')
Expand Down
7 changes: 7 additions & 0 deletions Lib/venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,13 +519,20 @@ def quote_ps1(s):
def quote_bat(s):
return s

def quote_csh(s):
# (t)csh history-expands '!' even inside single quotes, so POSIX
# quoting is not enough; escape each '!' explicitly.
return shlex.quote(s).replace('!', "'\\!'")

# gh-124651: need to quote the template strings properly
quote = shlex.quote
script_path = context.script_path
if script_path.endswith('.ps1'):
quote = quote_ps1
elif script_path.endswith('.bat'):
quote = quote_bat
elif script_path.endswith('.csh'):
quote = quote_csh
else:
# fallbacks to POSIX shell compliant quote
quote = shlex.quote
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :mod:`venv` activation under (t)csh for environments whose path or
``--prompt`` contains ``!``: ``activate.csh`` is now quoted so that csh does
not perform history expansion on it.
Loading