Skip to content

refactor(bash_completion): refactor { => _comp_}{dequote,quote} #736

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

Merged
merged 11 commits into from
Jun 1, 2022
Merged
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
99 changes: 88 additions & 11 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ _comp_readline_variable_on()
_comp_deprecate_func _rl_enabled _comp_readline_variable_on

# This function shell-quotes the argument
# @param $1 String to be quoted
# @var[out] ret Resulting string
_comp_quote()
{
ret=\'${1//\'/\'\\\'\'}\'
}

# This function shell-quotes the argument
# @deprecated Use `_comp_quote` instead. Note that `_comp_quote` stores
# the results in the variable `ret` instead of writing them to stdout.
quote()
{
local quoted=${1//\'/\'\\\'\'}
Expand All @@ -162,10 +172,77 @@ quote_readline()
printf %s "$ret"
} # quote_readline()

# shellcheck disable=SC1003
_comp_dequote__initialize()
{
unset -f "$FUNCNAME"
local regex_param='\$([_a-zA-Z][_a-zA-Z0-9]*|[-*@#?$!0-9_])|\$\{[!#]?([_a-zA-Z][_a-zA-Z0-9]*(\[([0-9]+|[*@])\])?|[-*@#?$!0-9_])\}'
local regex_quoted='\\.|'\''[^'\'']*'\''|\$?"([^\"$`!]|'$regex_param'|\\.)*"|\$'\''([^\'\'']|\\.)*'\'''
_comp_dequote__regex_safe_word='^([^\'\''"$`;&|<>()!]|'$regex_quoted'|'$regex_param')*$'
}
_comp_dequote__initialize

# This function expands a word using `eval` in a safe way. This function can
# be typically used to get the expanded value of `${word[i]}` as
# `_comp_dequote "${word[i]}"`. When the word contains unquoted shell special
# characters, command substitutions, and other unsafe strings, the function
# call fails before applying `eval`. Otherwise, `eval` is applied to the
# string to generate the result.
#
# @param $1 String to be expanded. A safe word consists of the following
# sequence of substrings:
#
# - Shell non-special characaters: [^\'"$`;&|<>()!].
# - Parameter expansions of the forms $PARAM, ${!PARAM},
# ${#PARAM}, ${NAME[INDEX]}, ${!NAME[INDEX]}, ${#NAME[INDEX]}
# where INDEX is an integer, `*` or `@`, NAME is a valid
# variable name [_a-zA-Z][_a-zA-Z0-9]*, and PARAM is NAME or a
# parameter [-*@#?$!0-9_].
# - Quotes \?, '...', "...", $'...', and $"...". In the double
# quotations, parameter expansions are allowed.
#
# @var[out] ret Array that contains the expanded results. Multiple words or no
# words may be generated through pathname expansions.
#
# Note: This function allows parameter expansions as safe strings, which might
# cause unexpected results:
#
# * This allows execution of arbitrary commands through extra expansions of
# array subscripts in name references. For example,
#
# declare -n v='dummy[$(echo xxx >/dev/tty)]'
# echo "$v" # This line executes the command 'echo xxx'.
# _comp_dequote '"$v"' # This line also executes it.
#
# * This may change the internal state of the variable that has side effects.
# For example, the state of the random number generator of RANDOM can change:
#
# RANDOM=1234 # Set seed
# echo "$RANDOM" # This produces 30658.
# RANDOM=1234 # Reset seed
# _comp_dequote '"$RANDOM"' # This line changes the internal state.
# echo "$RANDOM" # This fails to reproduce 30658.
#
# We allow these parameter expansions as a part of safe strings assuming the
# referential transparency of the simple parameter expansions and the sane
# setup of the variables by the user or other frameworks that the user loads.
_comp_dequote()
{
ret=() # fallback value for unsafe word and failglob
[[ $1 =~ $_comp_dequote__regex_safe_word ]] || return 1
eval "ret=($1)" 2>/dev/null # may produce failglob
}

# This function shell-dequotes the argument
# @deprecated Use `_comp_dequote' instead. Note that `_comp_dequote` stores
# the results in the array `ret` instead of writing them to stdout.
dequote()
{
eval printf %s "$1" 2>/dev/null
local ret
_comp_dequote "$1"
local rc=$?
printf %s "$ret"
return $rc
}

# Unset the given variables across a scope boundary. Useful for unshadowing
Expand Down Expand Up @@ -1030,14 +1107,14 @@ _parse_help()
shopt -s lastpipe
set -o noglob

eval local cmd="$(quote "$1")"
local cmd=$1
local line rc=1
{
(
case $cmd in
-) cat ;;
*) LC_ALL=C "$(dequote "$cmd")" ${2:---help} 2>&1 ;;
-) exec cat ;;
*) _comp_dequote "$cmd" && LC_ALL=C "$ret" ${2:---help} 2>&1 ;;
esac
} |
) |
while read -r line; do

[[ $line == *([[:blank:]])-* ]] || continue
Expand Down Expand Up @@ -1067,14 +1144,14 @@ _parse_usage()
shopt -s lastpipe
set -o noglob

eval local cmd="$(quote "$1")"
local cmd=$1
local line match option i char rc=1
{
(
case $cmd in
-) cat ;;
*) LC_ALL=C "$(dequote "$cmd")" ${2:---usage} 2>&1 ;;
-) exec cat ;;
*) _comp_dequote "$cmd" && LC_ALL=C "$ret" ${2:---usage} 2>&1 ;;
esac
} |
) |
while read -r line; do

while [[ $line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do
Expand Down
6 changes: 4 additions & 2 deletions completions/_umount.linux
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ _reply_compgen_array()
# argument.
local i wlist
for i in ${!COMPREPLY[*]}; do
local q=$(quote "$(printf %q "${COMPREPLY[i]}")")
wlist+=$q$'\n'
local ret
printf -v ret %q "${COMPREPLY[i]}"
_comp_quote "$ret"
wlist+=$ret$'\n'
done

# We also have to add another round of escaping to $cur.
Expand Down
5 changes: 4 additions & 1 deletion completions/export
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ _export()

case $cur in
*=)
local pval=$(quote "$(eval printf %s \"\$\{${cur%=}-\}\")")
local pname=${cur%=}
local ret
_comp_quote "${!pname-}"
local pval=$ret
# Complete previous value if it's not empty.
if [[ $pval != \'\' ]]; then
COMPREPLY=("$pval")
Expand Down
14 changes: 10 additions & 4 deletions completions/make
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@ _make()
# with -C/--directory
for ((i = 1; i < ${#words[@]}; i++)); do
if [[ ${words[i]} == -@(C|-directory) ]]; then
# eval for tilde expansion
eval "makef_dir=( -C \"${words[i + 1]}\" )"
# Expand tilde expansion
local ret
_comp_dequote "${words[i + 1]-}" &&
[[ -d ${ret-} ]] &&
makef_dir=(-C "$ret")
break
fi
done
Expand All @@ -145,8 +148,11 @@ _make()
# specified with -f/--file/--makefile
for ((i = 1; i < ${#words[@]}; i++)); do
if [[ ${words[i]} == -@(f|-?(make)file) ]]; then
# eval for tilde expansion
eval "makef=( -f \"${words[i + 1]}\" )"
# Expand tilde expansion
local ret
_comp_dequote "${words[i + 1]-}" &&
[[ -f ${ret-} ]] &&
makef=(-f "$ret")
break
fi
done
Expand Down
78 changes: 52 additions & 26 deletions completions/mutt
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,24 @@ _muttaddr()
# @output muttrc filename
_muttrc()
{
local muttrc=
# Search COMP_WORDS for '-F muttrc' or '-Fmuttrc' argument
set -- "${words[@]}"
while (($# > 0)); do
if [[ $1 == -F* ]]; then
local ret
if ((${#1} > 2)); then
muttrc="$(dequote "${1:2}")"
_comp_dequote "${1:2}" && muttrc=$ret
else
shift
[[ $1 ]] && muttrc="$(dequote "$1")"
[[ ${1-} ]] && _comp_dequote "$1" && muttrc=$ret
fi
break
fi
shift
done

if [[ ! -v muttrc ]]; then
if [[ ! $muttrc ]]; then
if [[ -f ~/.${muttcmd}rc ]]; then
muttrc=\~/.${muttcmd}rc
elif [[ -f ~/.${muttcmd}/${muttcmd}rc ]]; then
Expand All @@ -42,27 +44,48 @@ _muttrc()
}

# Recursively build list of sourced config files
# @param $1 List of config files found so far
# @param $2 Config file to process
# @output List of config files
_muttconffiles()
# @param $1... Config file to process
# @var[out] ret List of config files
# @return 0 if any conffiles are generated, 1 if none is generated.
_comp_cmd_mutt__get_conffiles()
{
local file sofar
local -a newconffiles

sofar=" $1 "
shift
while [[ ${1-} ]]; do
newconffiles=($(command sed -n 's|^source[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*$|\1|p' "$(eval printf %s $1)"))
for file in ${newconffiles+"${newconffiles[@]}"}; do
__expand_tilde_by_ref file
[[ ! -f $file || $sofar == *\ $file\ * ]] && continue
sofar+=" $file"
sofar=" $(eval _muttconffiles \"$sofar\" $file) "
done
shift
local -a conffiles=()
local -A visited=()
local file
for file; do
_comp_dequote "$file"
_comp_cmd_mutt__get_conffiles__visit "$ret"
done
((${#conffiles[@]})) || return 1
ret=("${conffiles[@]}")
}
# Recursion function for _comp_cmd_mutt__get_conffiles
# @var[ref] conffiles List of config files found so far
# @var[ref] visited Dictionary of config files already visited
_comp_cmd_mutt__get_conffiles__visit()
{
[[ -f $1 && ${visited[$1]-} != yes ]] || return 0
visited[$1]=yes
conffiles+=("$1")

local -a newconffiles=($(command sed -n 's|^source[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*$|\1|p' "$1"))
((${#newconffiles[@]})) || return 0

local file
for file in "${newconffiles[@]}"; do
__expand_tilde_by_ref file
_comp_cmd_mutt__get_conffiles__visit "$file"
done
printf '%s\n' $sofar
}

# Recursively build list of sourced config files
# @param $1... Config file to process
# @output List of config files
_muttconffiles()
{
local ret
_comp_cmd_mutt__get_conffiles "$@" &&
printf '%s\n' "${ret[@]}"
}

# @param $1 (cur) Current word to complete
Expand All @@ -74,11 +97,14 @@ _muttaliases()
muttrc=$(_muttrc)
[[ ! $muttrc ]] && return

conffiles=($(eval _muttconffiles $muttrc $muttrc))
local ret
_comp_cmd_mutt__get_conffiles "$muttrc" || return 0
conffiles=("${ret[@]}")
# shellcheck disable=SC2046
aliases=("$(command sed -n 's|^alias[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*$|\1|p' \
"${conffiles[@]}")")
COMPREPLY+=($(compgen -W "${aliases[*]}" -- "$cur"))
aliases=($(command sed -n 's|^alias[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*$|\1|p' \
"${conffiles[@]}"))
((${#aliases[@]})) &&
COMPREPLY+=($(compgen -W '"${aliases[@]}"' -- "$cur"))
}

# @param $1 (cur) Current word to complete
Expand Down
4 changes: 3 additions & 1 deletion completions/pkgadd
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ _pkgadd()
done
pkginst_list="${tmplist[*]}"
else
pkginst_list="$(strings "$(dequote $device)" |
local ret
_comp_dequote "$device"
pkginst_list="$(strings "$ret" |
command grep ^PKG= | sort -u | cut -d= -f2)"
fi
COMPREPLY=($(compgen -W "$pkginst_list" -- ${cur}))
Expand Down
4 changes: 3 additions & 1 deletion completions/pkgutil
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ _pkgutil()
local catalog=$(_pkgutil_url2catalog "$url")
catalog_files=("$catalog")
elif [[ ${words[i]} == --config ]]; then
configuration_files=("$(dequote ${words[i + 1]})")
local ret
_comp_dequote "${words[i + 1]}"
[[ ${ret-} ]] && configuration_files=("$ret")
elif [[ ${words[i]} == -@([iurdacUS]|-install|-upgrade|-remove|-download|-available|-compare|-catalog|-stream) ]]; then
command="${words[i]}"
fi
Expand Down
5 changes: 3 additions & 2 deletions completions/ssh
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,12 @@ _ssh_configfile()
set -- "${words[@]}"
while (($# > 0)); do
if [[ $1 == -F* ]]; then
local ret
if ((${#1} > 2)); then
configfile="$(dequote "${1:2}")"
_comp_dequote "${1:2}" && configfile=$ret
else
shift
[[ ${1-} ]] && configfile="$(dequote "$1")"
[[ ${1-} ]] && _comp_dequote "$1" && configfile=$ret
fi
break
fi
Expand Down
Loading