Skip to content

Commit 4d74294

Browse files
committed
Tighten _escape_for_rcp/_escape_for_scp docstrings and pin glob preservation
1 parent 64a918c commit 4d74294

2 files changed

Lines changed: 18 additions & 24 deletions

File tree

src/aiida/transports/plugins/async_backend.py

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -502,17 +502,16 @@ 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-
Important: We do NOT escape glob characters (*, ?, []) because the RCP
511-
protocol passes these to the remote shell for glob expansion. Escaping
512-
them would prevent proper file pattern matching.
513-
514-
:param path: The path to escape
515-
:return: The escaped path with backslashes before special characters
513+
:param path: The path to escape.
514+
:return: The escaped path.
516515
"""
517516

518517
result = []
@@ -526,20 +525,13 @@ def _escape_for_rcp(self, path: str) -> str:
526525
def _escape_for_scp(self, path: str) -> str:
527526
"""Prepare a path for use in scp commands.
528527
529-
Version-specific behavior:
530-
531-
- OpenSSH 9.0+ (SFTP mode):
532-
Uses SFTP protocol where paths are treated as binary data.
533-
No escaping needed - glob characters are not expanded.
534-
Direct return of the path.
535-
536-
- OpenSSH < 9.0 (RCP mode):
537-
Uses RCP protocol where paths are passed to remote shell.
538-
Must escape shell metacharacters to prevent injection,
539-
but MUST preserve glob characters (*, ?, []) for proper
540-
file pattern matching.
541-
542-
Version detection is based on `is_openssh_9_or_higher` flag set in __init__.
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`.
543535
"""
544536

545537
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)