Skip to content

Commit

Permalink
Merge pull request #708 from akinomyoga/fix-failglob-included_ssh_con…
Browse files Browse the repository at this point in the history
…fig_files

feat(_comp_expand_glob): Add a utility to safely perform pathname expansions (Fix #705)
  • Loading branch information
akinomyoga authored Apr 17, 2022
2 parents eb94fb1 + ec907d9 commit 3414cb6
Show file tree
Hide file tree
Showing 18 changed files with 330 additions and 107 deletions.
106 changes: 81 additions & 25 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,55 @@ _upvars()
done
}

# Get the list of filenames that match with the specified glob pattern.
# This function does the globbing in a controlled environment, avoiding
# interference from user's shell options/settings or environment variables.
# @param $1 array_name Array name
# The array name should not start with the double underscores "__". The
# array name should not be "GLOBIGNORE".
# @param $2 pattern Pattern string to be evaluated.
# This pattern string will be evaluated using "eval", so brace expansions,
# parameter expansions, command substitutions, and other expansions will be
# processed. The user-provided strings should not be directly specified to
# this argument.
_comp_expand_glob()
{
if (($# != 2)); then
printf 'bash-completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2
printf 'usage: %s ARRAY_NAME PATTERN\n' "$FUNCNAME" >&2
return 2
elif [[ $1 == @(GLOBIGNORE|__*|*[^_a-zA-Z0-9]*|[0-9]*|'') ]]; then
printf 'bash-completion: %s: invalid array name "%s"\n' "$FUNCNAME" "$1" >&2
return 2
fi

# Save and adjust the settings.
local __original_opts=$SHELLOPTS:$BASHOPTS
set +o noglob
shopt -s nullglob
shopt -u failglob dotglob

# Also the user's GLOBIGNORE may affect the result of pathname expansions.
local GLOBIGNORE=

eval -- "$1=()" # a fallback in case that the next line fails.
eval -- "$1=($2)"

# Restore the settings. Note: Changing GLOBIGNORE affects the state of
# "shopt -q dotglob", so we need to explicitly restore the original state
# of "shopt -q dotglob".
_comp_unlocal GLOBIGNORE
if [[ :$__original_opts: == *:dotglob:* ]]; then
shopt -s dotglob
else
shopt -u dotglob
fi
[[ :$__original_opts: == *:nullglob:* ]] || shopt -u nullglob
[[ :$__original_opts: == *:failglob:* ]] && shopt -s failglob
[[ :$__original_opts: == *:noglob:* ]] && set -o noglob
return 0
}

# Reassemble command line words, excluding specified characters from the
# list of word completion separators (COMP_WORDBREAKS).
# @param $1 chars Characters out of $COMP_WORDBREAKS which should
Expand Down Expand Up @@ -1099,15 +1148,20 @@ _mac_addresses()
#
_configured_interfaces()
{
local -a files
if [[ -f /etc/debian_version ]]; then
# Debian system
_comp_expand_glob files '/etc/network/interfaces /etc/network/interfaces.d/*'
((${#files[@]})) || return 0
COMPREPLY=($(compgen -W "$(command sed -ne 's|^iface \([^ ]\{1,\}\).*$|\1|p' \
/etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null)" \
"${files[@]}" 2>/dev/null)" \
-- "$cur"))
elif [[ -f /etc/SuSE-release ]]; then
# SuSE system
_comp_expand_glob files '/etc/sysconfig/network/ifcfg-*'
((${#files[@]})) || return 0
COMPREPLY=($(compgen -W "$(printf '%s\n' \
/etc/sysconfig/network/ifcfg-* |
"${files[@]}" |
command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
elif [[ -f /etc/pld-release ]]; then
# PLD Linux
Expand All @@ -1116,8 +1170,10 @@ _configured_interfaces()
command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
else
# Assume Red Hat
_comp_expand_glob files '/etc/sysconfig/network-scripts/ifcfg-*'
((${#files[@]})) || return 0
COMPREPLY=($(compgen -W "$(printf '%s\n' \
/etc/sysconfig/network-scripts/ifcfg-* |
"${files[@]}" |
command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
fi
}
Expand Down Expand Up @@ -1373,12 +1429,12 @@ _xinetd_services()
{
local xinetddir=${_comp__test_xinetd_dir:-/etc/xinetd.d}
if [[ -d $xinetddir ]]; then
local IFS=$' \t\n' reset=$(shopt -p nullglob)
shopt -s nullglob
local -a svcs=($xinetddir/!($_comp_backup_glob))
$reset
((!${#svcs[@]})) ||
local -a svcs
_comp_expand_glob svcs '$xinetddir/!($_comp_backup_glob)'
if ((${#svcs[@]})); then
local IFS=$'\n'
COMPREPLY+=($(compgen -W '"${svcs[@]#$xinetddir/}"' -- "${cur-}"))
fi
fi
}

Expand All @@ -1389,12 +1445,9 @@ _services()
local sysvdirs
_sysvdirs

local IFS=$' \t\n' reset=$(shopt -p nullglob)
shopt -s nullglob
COMPREPLY=(
$(printf '%s\n' ${sysvdirs[0]}/!($_comp_backup_glob|functions|README)))
$reset
_comp_expand_glob COMPREPLY '${sysvdirs[0]}/!($_comp_backup_glob|functions|README)'

local IFS=$'\n'
COMPREPLY+=($({
systemctl list-units --full --all ||
systemctl list-unit-files
Expand Down Expand Up @@ -1425,7 +1478,7 @@ _service()
_services
[[ -e /etc/mandrake-release ]] && _xinetd_services
else
local sysvdirs
local IFS=$'\n' sysvdirs
_sysvdirs
COMPREPLY=($(compgen -W '`command sed -e "y/|/ /" \
-ne "s/^.*\(U\|msg_u\)sage.*{\(.*\)}.*$/\2/p" \
Expand Down Expand Up @@ -1677,7 +1730,9 @@ _terms()
{
toe -a || toe
} | awk '{ print $1 }'
find /{etc,lib,usr/lib,usr/share}/terminfo/? -type f -maxdepth 1 |
_comp_expand_glob dirs '/{etc,lib,usr/lib,usr/share}/terminfo/?'
((${#dirs[@]})) &&
find "${dirs[@]}" -type f -maxdepth 1 |
awk -F/ '{ print $NF }'
} 2>/dev/null)" -- "$cur"))
}
Expand Down Expand Up @@ -1737,7 +1792,7 @@ _included_ssh_config_files()
{
(($# < 1)) &&
echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2
local configfile i f
local configfile i files f
configfile=$1

local IFS=$' \t\n' reset=$(shopt -po noglob)
Expand All @@ -1760,15 +1815,16 @@ _included_ssh_config_files()
fi
__expand_tilde_by_ref i
# In case the expanded variable contains multiple paths
set +o noglob
for f in $i; do
if [[ -r $f ]]; then
config+=("$f")
# The Included file is processed to look for Included files in itself
_included_ssh_config_files $f
fi
done
$reset
_comp_expand_glob files '$i'
if ((${#files[@]})); then
for f in "${files[@]}"; do
if [[ -r $f ]]; then
config+=("$f")
# The Included file is processed to look for Included files in itself
_included_ssh_config_files $f
fi
done
fi
done
} # _included_ssh_config_files()

Expand Down
2 changes: 1 addition & 1 deletion completions/_rtcwake
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ _rtcwake()
return
;;
--device | -d)
COMPREPLY=($(command ls -d /dev/rtc?* 2>/dev/null))
_comp_expand_glob COMPREPLY '/dev/rtc?*'
((${#COMPREPLY[@]})) &&
COMPREPLY=($(compgen -W '"${COMPREPLY[@]#/dev/}"' -- "$cur"))
return
Expand Down
5 changes: 4 additions & 1 deletion completions/_yum
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ _yum_repolist()

_yum_plugins()
{
command ls /usr/lib/yum-plugins/*.py{,c,o} 2>/dev/null |
local -a files
_comp_expand_glob files '/usr/lib/yum-plugins/*.py{,c,o}'
((${#files[@]})) &&
printf '%s\n' "${files[@]}" |
command sed -ne 's|.*/\([^./]*\)\.py[co]\{0,1\}$|\1|p' | sort -u
}

Expand Down
2 changes: 1 addition & 1 deletion completions/dpkg
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ _dpkg_reconfigure()

case $prev in
--frontend | -!(-*)f)
opt=(/usr/share/perl5/Debconf/FrontEnd/*)
_comp_expand_glob opt '/usr/share/perl5/Debconf/FrontEnd/*'
if ((${#opt[@]})); then
opt=(${opt[@]##*/})
opt=(${opt[@]%.pm})
Expand Down
2 changes: 1 addition & 1 deletion completions/hcitool
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ _hciattach()
_count_args
case $args in
1)
COMPREPLY=(/dev/tty*)
_comp_expand_glob COMPREPLY '/dev/tty*'
((${#COMPREPLY[@]})) &&
COMPREPLY=($(compgen -W '"${COMPREPLY[@]}"
"${COMPREPLY[@]#/dev/}"' -- "$cur"))
Expand Down
9 changes: 3 additions & 6 deletions completions/hunspell
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ _hunspell()
return
;;
-d)
local IFS=$' \t\n' reset=$(shopt -p nullglob)
shopt -s nullglob
local -a dicts=(/usr/share/hunspell/*.dic
/usr/local/share/hunspell/*.dic)
$reset
local -a dicts
_comp_expand_glob dicts '/usr/share/hunspell/*.dic /usr/local/share/hunspell/*.dic'
if ((${#dicts[@]})); then
dicts=("${dicts[@]##*/}")
dicts=("${dicts[@]%.dic}")
IFS=$'\n'
local IFS=$'\n'
COMPREPLY=($(compgen -W '${dicts[@]}' -- "$cur"))
fi
return
Expand Down
9 changes: 4 additions & 5 deletions completions/ipmitool
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ _ipmitool()
return
;;
-*d)
COMPREPLY=($(compgen -W "$(
command ls -d /dev/ipmi* /dev/ipmi/* /dev/ipmidev/* \
2>/dev/null | command sed -ne 's/^[^0-9]*\([0-9]\{1,\}\)/\1/p'
)" \
-- "$cur"))
local -a files
_comp_expand_glob files '/dev/ipmi* /dev/ipmi/* /dev/ipmidev/*'
((${#files[@]})) &&
COMPREPLY=($(compgen -W '"${files[@]##*([^0-9])}"' -X '![0-9]*' -- "$cur"))
return
;;
-*I)
Expand Down
24 changes: 15 additions & 9 deletions completions/lintian
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

_lintian_tags()
{
local match search tags
local match search tags check_files
_comp_expand_glob check_files '/usr/share/lintian/checks/*.desc'
((${#check_files[@]})) || return 0

tags=$(awk '/^Tag/ { print $2 }' /usr/share/lintian/checks/*.desc)
tags=$(awk '/^Tag/ { print $2 }' "${check_files[@]}")
if [[ $cur == *, ]]; then
search=${cur//,/ }
for item in $search; do
match=$(command grep -nE "^Tag: $item$" \
/usr/share/lintian/checks/*.desc | cut -d: -f1)
"${check_files[@]}" | cut -d: -f1)
tags=$(command sed -e "s/\<$item\>//g" <<<$tags)
done
COMPREPLY+=($(compgen -W "$tags"))
Expand All @@ -22,15 +24,17 @@ _lintian_tags()

_lintian_checks()
{
local match search todisable checks
local match search todisable checks check_files
_comp_expand_glob check_files '/usr/share/lintian/checks/*.desc'
((${#check_files[@]})) || return 0

checks=$(awk '/^(Check-Script|Abbrev)/ { print $2 }' \
/usr/share/lintian/checks/*.desc)
"${check_files[@]}")
if [[ $cur == *, ]]; then
search=${cur//,/ }
for item in $search; do
match=$(command grep -nE "^(Check-Script|Abbrev): $item$" \
/usr/share/lintian/checks/*.desc | cut -d: -f1)
"${check_files[@]}" | cut -d: -f1)
todisable=$(awk '/^(Check-Script|Abbrev)/ { print $2 }' $match)
for name in $todisable; do
checks=$(command sed -e "s/\<$name\>//g" <<<$checks)
Expand All @@ -46,15 +50,17 @@ _lintian_checks()

_lintian_infos()
{
local match search infos
local match search infos collection_files
_comp_expand_glob collection_files '/usr/share/lintian/collection/*.desc'
((${#collection_files[@]})) || return 0

infos=$(awk '/^Collector/ { print $2 }' \
/usr/share/lintian/collection/*.desc)
"${collection_files[@]}")
if [[ $cur == *, ]]; then
search=${cur//,/ }
for item in $search; do
match=$(command grep -nE "^Collector: $item$" \
/usr/share/lintian/collection/*.desc | cut -d: -f1)
"${collection_files[@]}" | cut -d: -f1)
infos=$(command sed -e "s/\<$item\>//g" <<<$infos)
done
COMPREPLY+=($(compgen -W "$infos"))
Expand Down
10 changes: 5 additions & 5 deletions completions/minicom
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _minicom()
return
;;
--ptty | -!(-*)p)
COMPREPLY=(/dev/tty*)
_comp_expand_glob COMPREPLY '/dev/tty*'
((${#COMPREPLY[@]})) &&
COMPREPLY=($(compgen -W '"${COMPREPLY[@]}" "${COMPREPLY[@]#/dev/}}' \
-- "$cur"))
Expand All @@ -31,10 +31,10 @@ _minicom()
return
fi

COMPREPLY=(
$(printf '%s\n' /etc/minirc.* /etc/minicom/minirc.* ~/.minirc.* |
command sed -e '/\*$/d' -e 's/^.*minirc\.//' |
command grep "^${cur}"))
local -a files
_comp_expand_glob files '/etc/minirc.* /etc/minicom/minirc.* ~/.minirc.*'
((${#files[@]})) &&
COMPREPLY=($(compgen -W '"${files[@]##*minirc.}"' -- "$cur"))
} &&
complete -F _minicom -o default minicom

Expand Down
16 changes: 7 additions & 9 deletions completions/mysql
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

_comp_xfunc_mysql_character_sets()
{
local IFS=$' \t\n' reset=$(shopt -p failglob)
shopt -u failglob
local -a charsets=(/usr/share/m{ariadb,ysql}/charsets/*.xml)
$reset
if ((${#charsets[@]})); then
charsets=("${charsets[@]##*/}")
charsets=("${charsets[@]%%?(Index|\*).xml}" utf8)
COMPREPLY+=($(compgen -W '${charsets[@]}' -- "$cur"))
fi
local -a charsets
_comp_expand_glob charsets '/usr/share/m{ariadb,ysql}/charsets/!(Index).xml'
charsets+=(utf8)
charsets=("${charsets[@]##*/}")
charsets=("${charsets[@]%.xml}")
local IFS=$'\n'
COMPREPLY+=($(compgen -W '${charsets[@]}' -X '' -- "$cur"))
}

_comp_deprecate_func _mysql_character_sets _comp_xfunc_mysql_character_sets
Expand Down
5 changes: 1 addition & 4 deletions completions/ps
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ _comp_cmd_ps()
return
;;
?(-)t | [^-]*t | --tty)
COMPREPLY=($(
shopt -s nullglob
printf '%s\n' /dev/tty*
))
_comp_expand_glob COMPREPLY '/dev/tty*'
((${#COMPREPLY[@]})) &&
COMPREPLY=($(compgen -W '"${COMPREPLY[@]}" "${COMPREPLY[@]#/dev/}"' \
-- "$cur"))
Expand Down
9 changes: 2 additions & 7 deletions completions/qemu
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@ _qemu()
return
;;
-k)
local IFS=$' \t\n' reset=$(shopt -p nullglob)
shopt -s nullglob
local -a keymaps=($(
printf "%s\n" \
/usr/{local/,}share/qemu/keymaps/!(common|modifiers)
))
$reset
local -a keymaps
_comp_expand_glob keymaps '/usr/{local/,}share/qemu/keymaps/!(common|modifiers)'
((${#keymaps[@]})) &&
COMPREPLY=($(compgen -W '"${keymaps[@]##*/}"}' -- "$cur"))
return
Expand Down
Loading

0 comments on commit 3414cb6

Please sign in to comment.