Skip to content

fix(java,rsync,scp): handle quoted space in filepaths properly #1417

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
wants to merge 2 commits into
base: main
Choose a base branch
from
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
8 changes: 6 additions & 2 deletions completions/java
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,16 @@ _comp_cmd_java__packages()
_comp_cmd_java__find_sourcepath || return 0
local -a sourcepaths=("${REPLY[@]}")

local REPLY
_comp_dequote "$cur" || REPLY=$cur
local cur_val=${REPLY-}

# convert package syntax to path syntax
local cur=${cur//.//}
local cur_val=${cur_val//.//}
# parse each sourcepath element for packages
for i in "${sourcepaths[@]}"; do
if [[ -d $i ]]; then
_comp_expand_glob files '"$i/$cur"*' || continue
_comp_expand_glob files '"$i/$cur_val"*' || continue
_comp_split -la COMPREPLY "$(
command ls -F -d "${files[@]}" 2>/dev/null |
command sed -e 's|^'"$i"'/||'
Expand Down
28 changes: 21 additions & 7 deletions completions/ssh
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,13 @@ _comp_cmd_scp__path_esc='[][(){}<>"'"'"',:;^&!$=?`\\|[:space:]]'
# "compopt +o nospace" instead, but it would suffix a space to directory names
# unexpectedly.
#
# FIXME: With the current strategy of using "ls -FL", we cannot distinguish the
# filenames that end with one of the type-classifier characters. For example,
# a regular file "pipe|" and a named pipe "pipe" would both produce the
# identical result "pipe|" with "ls -1FL". As a consequence, those characters
# at the end of the filename are removed unexpectedly. To solve this problem,
# we need to give up relying on "ls -1FL".
#
# Options:
# -d Only directory names are selected.
# @param $1 escape_replacement - If a non-empty value is specified, special
Expand Down Expand Up @@ -530,13 +537,16 @@ _comp_xfunc_scp_compgen_remote_files()
done

# remove backslash escape from the first colon
local cur=${cur/\\:/:}

local _userhost=${cur%%?(\\):*}
local _path=${cur#*:}
local REPLY=$cur
if [[ ! $_less_escaping ]]; then
# unescape (3 backslashes to 1 for chars we escaped)
REPLY=$(command sed -e 's/\\\\\\\('"$_comp_cmd_scp__path_esc"'\)/\\\1/g' <<<"$REPLY")
fi
_comp_dequote "$REPLY"
local cur_val=${REPLY-}

# unescape (3 backslashes to 1 for chars we escaped)
_path=$(command sed -e 's/\\\\\\\('"$_comp_cmd_scp__path_esc"'\)/\\\1/g' <<<"$_path")
local _userhost=${cur_val%%:*}
local _path=${cur_val#*:}

# default to home dir of specified user on remote host
if [[ ! $_path ]]; then
Expand Down Expand Up @@ -575,8 +585,12 @@ _comp_xfunc_scp_compgen_local_files()
shift
fi

local REPLY
_comp_dequote "$cur" || REPLY=$cur
local cur_val=${REPLY-}

local files
_comp_expand_glob files '"$cur"*' || return 0
_comp_expand_glob files '"$cur_val"*' || return 0
_comp_compgen -RU files split -l ${1:+-P "$1"} -- "$(
command ls -aF1dL "${files[@]}" 2>/dev/null |
_comp_cmd_scp__escape_path ${_dirs_only:+'-d'}
Expand Down
12 changes: 12 additions & 0 deletions test/t/test_rsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ def test_remote_path_with_spaces(self, bash):
completion == r"\ in\ filename.txt"
or completion == r"\\\ in\\\ filename.txt"
)

@pytest.mark.complete(r"rsync -na spaced\ ", cwd="scp")
def test_local_path_with_spaces(self, completion):
"""This function tests xfunc _comp_xfunc_scp_compgen_local_files, which
is defined in completions/ssh, through the rsync interface. We reuse
the fixture directory for the test of the scp completion.

The expected result depends on the rsync version, so we check the
result if it matches either one of two possible expected results.

"""
assert completion == r"\ conf" or completion == r"\\\ conf"
23 changes: 22 additions & 1 deletion test/t/test_scp.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@ def test_remote_path_ending_with_backslash(self, bash):

@pytest.fixture
def tmpdir_mkfifo(self, request, bash):
tmpdir, _, _ = prepare_fixture_dir(request, files=[], dirs=[])
# We prepare two files: 1) a named pipe and 2) a regular file ending
# with the same name but an extra special character "|".
tmpdir, _, _ = prepare_fixture_dir(
request,
files=["local_path_2-pipe|"],
dirs=[],
)

# If the system allows creating a named pipe, we create it in a
# temporary directory and returns the path. We cannot check the
Expand All @@ -205,3 +211,18 @@ def test_local_path_mark_1(self, bash, tmpdir_mkfifo):
bash, "scp local_path_1-", cwd=tmpdir_mkfifo
)
assert completion == "pipe"

# FIXME: This test currently fails.
# def test_local_path_mark_2(self, bash, tmpdir_mkfifo):
# completion = assert_complete(
# bash, "scp local_path_2-", cwd=tmpdir_mkfifo
# )
# assert completion == "pipe\\|"

@pytest.mark.complete("scp spa", cwd="scp")
def test_local_path_with_spaces_1(self, completion):
assert completion == r"ced\ \ conf"

@pytest.mark.complete(r"scp spaced\ ", cwd="scp")
def test_local_path_with_spaces_2(self, completion):
assert completion == r"\ conf"