Skip to content

Commit e3aae79

Browse files
stillfast梦有三千州khsrali
authored
Fix: Remove glob character escaping for SCP RCP protocol (#7335)
--------- Co-authored-by: 梦有三千州 <9842930+mengyou3000z@user.noreply.gitee.com> Co-authored-by: Ali Khosravi <khsrali@gmail.com>
1 parent 2068a7d commit e3aae79

2 files changed

Lines changed: 19 additions & 12 deletions

File tree

src/aiida/transports/plugins/async_backend.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -502,17 +502,20 @@ async def openssh_execute(self, commands, stdin: Optional[str] = None, timeout:
502502
return process.returncode, stdout.decode(), stderr.decode()
503503

504504
def _escape_for_rcp(self, path: str) -> str:
505-
"""Escape special characters for scp RCP mode using backslashes.
505+
"""Backslash-escape shell metacharacters for scp's RCP protocol.
506506
507-
Note: escape_for_bash() does NOT work here because scp's RCP protocol
508-
expects backslash-escaped paths, not quote-wrapped strings.
507+
Glob characters (``*``, ``?``, ``[``, ``]``) are deliberately left
508+
unescaped so the remote shell expands them; the trade-off is that
509+
filenames containing literal glob characters cannot be addressed
510+
in RCP mode. :func:`escape_for_bash` is unsuitable here because it
511+
quote-wraps rather than backslash-escapes.
509512
510-
:param path: The path to escape
511-
:return: The escaped path with backslashes before special characters
513+
:param path: The path to escape.
514+
:return: The escaped path.
512515
"""
513516

514517
result = []
515-
special = set(' \t\n"\'`$\\!#&*?;<>|(){}[]')
518+
special = set(' \t\n"\'`$\\!#&;<>|(){}')
516519
for char in path:
517520
if char in special:
518521
result.append('\\')
@@ -522,11 +525,13 @@ def _escape_for_rcp(self, path: str) -> str:
522525
def _escape_for_scp(self, path: str) -> str:
523526
"""Prepare a path for use in scp commands.
524527
525-
In OpenSSH < 9.0, scp uses the RCP protocol which passes paths through
526-
the remote shell. We must escape shell metacharacters with backslashes
527-
to prevent shell expansion/injection.
528-
529-
OpenSSH 9.0+, however, uses SFTP mode by default - paths are binary data, no shell quoting needed.
528+
OpenSSH >= 9.0 uses SFTP mode: paths are sent as binary, no
529+
escaping needed. OpenSSH < 9.0 uses RCP mode: paths are interpreted
530+
by a shell on both ends (scp itself shells out internally for local
531+
paths), so shell metacharacters are backslash-escaped via
532+
:meth:`_escape_for_rcp` while glob characters are deliberately
533+
preserved for pattern matching. Mode is selected via
534+
:attr:`is_openssh_9_or_higher`.
530535
"""
531536

532537
if self.is_openssh_9_or_higher:

tests/transports/test_asyncssh_plugin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,11 @@ def test_escape_for_glob_preserves_wildcards_escapes_dangerous_chars():
218218

219219

220220
def test_escape_for_rcp():
221-
"""Test RCP mode escapes shell metacharacters."""
221+
"""Test RCP mode escapes shell metacharacters but preserves globs."""
222222
backend = _TestOpenSSH()
223223
assert backend._escape_for_rcp('/path/with spaces/$VAR;cmd') == '/path/with\\ spaces/\\$VAR\\;cmd'
224+
# Glob characters pass through so the remote shell can expand them.
225+
assert backend._escape_for_rcp('/dir/*.[ch]') == '/dir/*.[ch]'
224226

225227

226228
def test_escape_for_scp_version_aware():

0 commit comments

Comments
 (0)