Skip to content

Commit 3414cb6

Browse files
authored
Merge pull request #708 from akinomyoga/fix-failglob-included_ssh_config_files
feat(_comp_expand_glob): Add a utility to safely perform pathname expansions (Fix #705)
2 parents eb94fb1 + ec907d9 commit 3414cb6

File tree

18 files changed

+330
-107
lines changed

18 files changed

+330
-107
lines changed

bash_completion

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,55 @@ _upvars()
260260
done
261261
}
262262

263+
# Get the list of filenames that match with the specified glob pattern.
264+
# This function does the globbing in a controlled environment, avoiding
265+
# interference from user's shell options/settings or environment variables.
266+
# @param $1 array_name Array name
267+
# The array name should not start with the double underscores "__". The
268+
# array name should not be "GLOBIGNORE".
269+
# @param $2 pattern Pattern string to be evaluated.
270+
# This pattern string will be evaluated using "eval", so brace expansions,
271+
# parameter expansions, command substitutions, and other expansions will be
272+
# processed. The user-provided strings should not be directly specified to
273+
# this argument.
274+
_comp_expand_glob()
275+
{
276+
if (($# != 2)); then
277+
printf 'bash-completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2
278+
printf 'usage: %s ARRAY_NAME PATTERN\n' "$FUNCNAME" >&2
279+
return 2
280+
elif [[ $1 == @(GLOBIGNORE|__*|*[^_a-zA-Z0-9]*|[0-9]*|'') ]]; then
281+
printf 'bash-completion: %s: invalid array name "%s"\n' "$FUNCNAME" "$1" >&2
282+
return 2
283+
fi
284+
285+
# Save and adjust the settings.
286+
local __original_opts=$SHELLOPTS:$BASHOPTS
287+
set +o noglob
288+
shopt -s nullglob
289+
shopt -u failglob dotglob
290+
291+
# Also the user's GLOBIGNORE may affect the result of pathname expansions.
292+
local GLOBIGNORE=
293+
294+
eval -- "$1=()" # a fallback in case that the next line fails.
295+
eval -- "$1=($2)"
296+
297+
# Restore the settings. Note: Changing GLOBIGNORE affects the state of
298+
# "shopt -q dotglob", so we need to explicitly restore the original state
299+
# of "shopt -q dotglob".
300+
_comp_unlocal GLOBIGNORE
301+
if [[ :$__original_opts: == *:dotglob:* ]]; then
302+
shopt -s dotglob
303+
else
304+
shopt -u dotglob
305+
fi
306+
[[ :$__original_opts: == *:nullglob:* ]] || shopt -u nullglob
307+
[[ :$__original_opts: == *:failglob:* ]] && shopt -s failglob
308+
[[ :$__original_opts: == *:noglob:* ]] && set -o noglob
309+
return 0
310+
}
311+
263312
# Reassemble command line words, excluding specified characters from the
264313
# list of word completion separators (COMP_WORDBREAKS).
265314
# @param $1 chars Characters out of $COMP_WORDBREAKS which should
@@ -1099,15 +1148,20 @@ _mac_addresses()
10991148
#
11001149
_configured_interfaces()
11011150
{
1151+
local -a files
11021152
if [[ -f /etc/debian_version ]]; then
11031153
# Debian system
1154+
_comp_expand_glob files '/etc/network/interfaces /etc/network/interfaces.d/*'
1155+
((${#files[@]})) || return 0
11041156
COMPREPLY=($(compgen -W "$(command sed -ne 's|^iface \([^ ]\{1,\}\).*$|\1|p' \
1105-
/etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null)" \
1157+
"${files[@]}" 2>/dev/null)" \
11061158
-- "$cur"))
11071159
elif [[ -f /etc/SuSE-release ]]; then
11081160
# SuSE system
1161+
_comp_expand_glob files '/etc/sysconfig/network/ifcfg-*'
1162+
((${#files[@]})) || return 0
11091163
COMPREPLY=($(compgen -W "$(printf '%s\n' \
1110-
/etc/sysconfig/network/ifcfg-* |
1164+
"${files[@]}" |
11111165
command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
11121166
elif [[ -f /etc/pld-release ]]; then
11131167
# PLD Linux
@@ -1116,8 +1170,10 @@ _configured_interfaces()
11161170
command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
11171171
else
11181172
# Assume Red Hat
1173+
_comp_expand_glob files '/etc/sysconfig/network-scripts/ifcfg-*'
1174+
((${#files[@]})) || return 0
11191175
COMPREPLY=($(compgen -W "$(printf '%s\n' \
1120-
/etc/sysconfig/network-scripts/ifcfg-* |
1176+
"${files[@]}" |
11211177
command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
11221178
fi
11231179
}
@@ -1373,12 +1429,12 @@ _xinetd_services()
13731429
{
13741430
local xinetddir=${_comp__test_xinetd_dir:-/etc/xinetd.d}
13751431
if [[ -d $xinetddir ]]; then
1376-
local IFS=$' \t\n' reset=$(shopt -p nullglob)
1377-
shopt -s nullglob
1378-
local -a svcs=($xinetddir/!($_comp_backup_glob))
1379-
$reset
1380-
((!${#svcs[@]})) ||
1432+
local -a svcs
1433+
_comp_expand_glob svcs '$xinetddir/!($_comp_backup_glob)'
1434+
if ((${#svcs[@]})); then
1435+
local IFS=$'\n'
13811436
COMPREPLY+=($(compgen -W '"${svcs[@]#$xinetddir/}"' -- "${cur-}"))
1437+
fi
13821438
fi
13831439
}
13841440

@@ -1389,12 +1445,9 @@ _services()
13891445
local sysvdirs
13901446
_sysvdirs
13911447

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

1450+
local IFS=$'\n'
13981451
COMPREPLY+=($({
13991452
systemctl list-units --full --all ||
14001453
systemctl list-unit-files
@@ -1425,7 +1478,7 @@ _service()
14251478
_services
14261479
[[ -e /etc/mandrake-release ]] && _xinetd_services
14271480
else
1428-
local sysvdirs
1481+
local IFS=$'\n' sysvdirs
14291482
_sysvdirs
14301483
COMPREPLY=($(compgen -W '`command sed -e "y/|/ /" \
14311484
-ne "s/^.*\(U\|msg_u\)sage.*{\(.*\)}.*$/\2/p" \
@@ -1677,7 +1730,9 @@ _terms()
16771730
{
16781731
toe -a || toe
16791732
} | awk '{ print $1 }'
1680-
find /{etc,lib,usr/lib,usr/share}/terminfo/? -type f -maxdepth 1 |
1733+
_comp_expand_glob dirs '/{etc,lib,usr/lib,usr/share}/terminfo/?'
1734+
((${#dirs[@]})) &&
1735+
find "${dirs[@]}" -type f -maxdepth 1 |
16811736
awk -F/ '{ print $NF }'
16821737
} 2>/dev/null)" -- "$cur"))
16831738
}
@@ -1737,7 +1792,7 @@ _included_ssh_config_files()
17371792
{
17381793
(($# < 1)) &&
17391794
echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2
1740-
local configfile i f
1795+
local configfile i files f
17411796
configfile=$1
17421797

17431798
local IFS=$' \t\n' reset=$(shopt -po noglob)
@@ -1760,15 +1815,16 @@ _included_ssh_config_files()
17601815
fi
17611816
__expand_tilde_by_ref i
17621817
# In case the expanded variable contains multiple paths
1763-
set +o noglob
1764-
for f in $i; do
1765-
if [[ -r $f ]]; then
1766-
config+=("$f")
1767-
# The Included file is processed to look for Included files in itself
1768-
_included_ssh_config_files $f
1769-
fi
1770-
done
1771-
$reset
1818+
_comp_expand_glob files '$i'
1819+
if ((${#files[@]})); then
1820+
for f in "${files[@]}"; do
1821+
if [[ -r $f ]]; then
1822+
config+=("$f")
1823+
# The Included file is processed to look for Included files in itself
1824+
_included_ssh_config_files $f
1825+
fi
1826+
done
1827+
fi
17721828
done
17731829
} # _included_ssh_config_files()
17741830

completions/_rtcwake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ _rtcwake()
1717
return
1818
;;
1919
--device | -d)
20-
COMPREPLY=($(command ls -d /dev/rtc?* 2>/dev/null))
20+
_comp_expand_glob COMPREPLY '/dev/rtc?*'
2121
((${#COMPREPLY[@]})) &&
2222
COMPREPLY=($(compgen -W '"${COMPREPLY[@]#/dev/}"' -- "$cur"))
2323
return

completions/_yum

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ _yum_repolist()
3030

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

completions/dpkg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ _dpkg_reconfigure()
133133

134134
case $prev in
135135
--frontend | -!(-*)f)
136-
opt=(/usr/share/perl5/Debconf/FrontEnd/*)
136+
_comp_expand_glob opt '/usr/share/perl5/Debconf/FrontEnd/*'
137137
if ((${#opt[@]})); then
138138
opt=(${opt[@]##*/})
139139
opt=(${opt[@]%.pm})

completions/hcitool

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ _hciattach()
354354
_count_args
355355
case $args in
356356
1)
357-
COMPREPLY=(/dev/tty*)
357+
_comp_expand_glob COMPREPLY '/dev/tty*'
358358
((${#COMPREPLY[@]})) &&
359359
COMPREPLY=($(compgen -W '"${COMPREPLY[@]}"
360360
"${COMPREPLY[@]#/dev/}"' -- "$cur"))

completions/hunspell

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,12 @@ _hunspell()
1010
return
1111
;;
1212
-d)
13-
local IFS=$' \t\n' reset=$(shopt -p nullglob)
14-
shopt -s nullglob
15-
local -a dicts=(/usr/share/hunspell/*.dic
16-
/usr/local/share/hunspell/*.dic)
17-
$reset
13+
local -a dicts
14+
_comp_expand_glob dicts '/usr/share/hunspell/*.dic /usr/local/share/hunspell/*.dic'
1815
if ((${#dicts[@]})); then
1916
dicts=("${dicts[@]##*/}")
2017
dicts=("${dicts[@]%.dic}")
21-
IFS=$'\n'
18+
local IFS=$'\n'
2219
COMPREPLY=($(compgen -W '${dicts[@]}' -- "$cur"))
2320
fi
2421
return

completions/ipmitool

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ _ipmitool()
1616
return
1717
;;
1818
-*d)
19-
COMPREPLY=($(compgen -W "$(
20-
command ls -d /dev/ipmi* /dev/ipmi/* /dev/ipmidev/* \
21-
2>/dev/null | command sed -ne 's/^[^0-9]*\([0-9]\{1,\}\)/\1/p'
22-
)" \
23-
-- "$cur"))
19+
local -a files
20+
_comp_expand_glob files '/dev/ipmi* /dev/ipmi/* /dev/ipmidev/*'
21+
((${#files[@]})) &&
22+
COMPREPLY=($(compgen -W '"${files[@]##*([^0-9])}"' -X '![0-9]*' -- "$cur"))
2423
return
2524
;;
2625
-*I)

completions/lintian

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
_lintian_tags()
44
{
5-
local match search tags
5+
local match search tags check_files
6+
_comp_expand_glob check_files '/usr/share/lintian/checks/*.desc'
7+
((${#check_files[@]})) || return 0
68

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

2325
_lintian_checks()
2426
{
25-
local match search todisable checks
27+
local match search todisable checks check_files
28+
_comp_expand_glob check_files '/usr/share/lintian/checks/*.desc'
29+
((${#check_files[@]})) || return 0
2630

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

4751
_lintian_infos()
4852
{
49-
local match search infos
53+
local match search infos collection_files
54+
_comp_expand_glob collection_files '/usr/share/lintian/collection/*.desc'
55+
((${#collection_files[@]})) || return 0
5056

5157
infos=$(awk '/^Collector/ { print $2 }' \
52-
/usr/share/lintian/collection/*.desc)
58+
"${collection_files[@]}")
5359
if [[ $cur == *, ]]; then
5460
search=${cur//,/ }
5561
for item in $search; do
5662
match=$(command grep -nE "^Collector: $item$" \
57-
/usr/share/lintian/collection/*.desc | cut -d: -f1)
63+
"${collection_files[@]}" | cut -d: -f1)
5864
infos=$(command sed -e "s/\<$item\>//g" <<<$infos)
5965
done
6066
COMPREPLY+=($(compgen -W "$infos"))

completions/minicom

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ _minicom()
1515
return
1616
;;
1717
--ptty | -!(-*)p)
18-
COMPREPLY=(/dev/tty*)
18+
_comp_expand_glob COMPREPLY '/dev/tty*'
1919
((${#COMPREPLY[@]})) &&
2020
COMPREPLY=($(compgen -W '"${COMPREPLY[@]}" "${COMPREPLY[@]#/dev/}}' \
2121
-- "$cur"))
@@ -31,10 +31,10 @@ _minicom()
3131
return
3232
fi
3333

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

completions/mysql

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22

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

1614
_comp_deprecate_func _mysql_character_sets _comp_xfunc_mysql_character_sets

0 commit comments

Comments
 (0)