1: 2# RCSid: 3# $Id: safe_eval.sh,v 1.36 2026/10/13 22:44:09 sjg Exp $ 4# 5# @(#) Copyright (c) 2023-2026 Simon J. Gerraty 6# 7# SPDX-License-Identifier: BSD-2-Clause 8# 9# Please send copies of changes and bug-fixes to: 10# sjg@crufty.net 11 12_SAFE_EVAL_SH=: 13 14# does local *actually* work? 15local_works() { 16 local _fu 17} 18 19if local_works > /dev/null 2>&1; then 20 _local=local 21else 22 _local=: 23fi 24 25## 26# safe_set_var ["options"] "var=val" 27# 28# Useful for processing command line input like 'var=val'. 29# 30# We perform a number of sanity checks similar to safe_set. 31# We set ssv_var=var and ssv_val=val so that caller can produce 32# error messages if desired. 33# To be safe, ssv_var and/or ssv_val will be set to 'invalid' 34# if they fail the relevant checks. 35# 36# Options: 37# 38# -a "allow" 39# list of allowed variable names. 40# 41# -o "only_chars" 42# white-list of acceptable chars in "val" 43# default is 'A-Za-z0-9_ ,/=:+-'. 44# 45# -v "values" 46# list of allowed values. 47# 48# -x "extra_chars" 49# eXtra chars to add to white-list for "val". 50# 51# 1. if "var" contains anything but 'A-Za-z0-9_' or matches 'ssv_*' 52# we set ssv_var=invalid and return 1. 53# 54# 2. if "val" contains anything not in the combined set of 55# "only_chars" and "extra_chars" we will set ssv_val=invalid 56# and return 2. 57# 58# 3. if "allow" is set and "var" is not a member we return 3. 59# 60# 4. if "values" is set and "val" is not a member we return 4. 61# 62# If we pass all that, we eval "var=val" and return 0. 63# 64safe_set_var() { 65 eval $_local ssv_allow ssv_extra ssv_only ssv_space ssv_rc ssv_values 66 # use [:space:] if we can as it includes \n 67 case " " in 68 [[:space:]]) ssv_space="[:space:]";; 69 *) ssv_space=" ";; 70 esac 71 ssv_only="${ssv_space}A-Za-z0-9_.,/=:+\$-" 72 ssv_rc= 73 74 while : 75 do 76 : 1="$1" 77 case "$1" in 78 -a) ssv_allow="$2"; shift 2;; 79 -o) ssv_only="$2"; shift 2;; 80 -v) ssv_values="$2"; shift 2;; 81 -x) ssv_extra="$2"; shift 2;; 82 *) break;; 83 esac 84 done 85 if ${isPOSIX_SHELL:-false}; then 86 ssv_var="${1%%=*}" 87 ssv_val="${1#*=}" 88 else 89 ssv_var=`expr "$1" : '\([^=]*\)=.*'` 90 ssv_val=`expr "$1" : '[^=]*=\(.*\)'` 91 fi 92 : first is ssv_var sane? 93 case "$ssv_var" in 94 *[!A-Za-z0-9_]*|ssv_*) 95 ssv_var=invalid 96 ssv_rc=1 97 ;; 98 esac 99 : is ssv_val ok - "$ssv_val" match only [$ssv_extra$ssv_only] 100 case "$ssv_val" in 101 *[!$ssv_extra$ssv_only]*) 102 ssv_val=invalid 103 ssv_rc=${ssv_rc:-2} 104 ;; 105 esac 106 : is $ssv_var is allowed - "${2:-$ssv_var}" 107 case " ${ssv_allow:-$ssv_var} " in 108 *" $ssv_var "*) ;; 109 *) ssv_rc=${ssv_rc:-3};; 110 esac 111 case " ${ssv_values:-$ssv_val} " in 112 *" $ssv_val "*) ;; 113 *) ssv_rc=${ssv_rc:-4};; 114 esac 115 ssv_rc=${ssv_rc:-0} 116 if [ $ssv_rc = 0 ]; then 117 : this should be safe 118 eval "$ssv_var=\"$ssv_val\"" 119 fi 120 return $ssv_rc 121} 122 123## 124# safe_set [xtras] 125# 126# return a safe variable setting 127# any non-alphanumeric chars other than those in "xtras" 128# will be replaced with '_' 129# Lines containing `` or $() are too likely to result in syntax errors 130# so just delete them. 131# 132# "xtras" should be used with caution and cannot include ';' 133# 134safe_set() { 135 ${SED:-sed} -e 's/^[ ]*//;s/[ ]*#.*//;s/^:.*//' \ 136 -e '/`/d' -e '/\$(/d' \ 137 -e '/^[A-Za-z_][A-Za-z0-9_]*=/!d;s;[^A-Za-z0-9_. "'"$1"'$,/=:+-];_;g;' \ 138 -e '/=.*_.*[ ]/s,=\(.*\),="\1",;s,"",",g' 139} 140 141## 142# safe_eval [file] 143# 144# eval variable assignments only from file 145# taking care to eliminate any shell meta chars 146# 147safe_eval() { 148 eval `cat "$@" | safe_set` 149} 150 151## 152# safe_eval_export [file] 153# 154# eval variable assignments only from file 155# taking care to eliminate any shell meta chars 156# export any variables thus set 157# 158safe_eval_export() { 159 eval `cat "$@" | safe_set | ${SED:-sed} 's/^\([^=]*\)=.*/&; export \1/'` 160} 161 162## 163# safe_dot file [...] 164# 165# feed all "file" that exist to safe_eval 166# 167safe_dot() { 168 eval $_local ef ex f rc 169 ef= 170 ex= 171 rc=1 172 while : 173 do 174 case "$1" in 175 --export) ex=_export; shift;; 176 *) break;; 177 esac 178 done 179 for f in "$@" 180 do 181 test -s "$f" -a -f "$f" || continue 182 : check for space or tab in "$f" 183 case "$f" in 184 *[[:space:]]*|*" "*|*" "*) # we cannot do this efficiently 185 dotted="$dotted $f" 186 safe_eval$ex "$f" 187 rc=$? 188 continue 189 ;; 190 esac 191 ef="${ef:+$ef }$f" 192 dotted="$dotted $f" 193 done 194 test -z "$ef" && return $rc 195 safe_eval$ex $ef 196 return 0 197} 198 199# for testing 200case /$0 in 201*/safe_eval*) 202 case "$1" in 203 dot|eval|set) op=safe_$1; shift; $op "$@";; 204 set_var) 205 shift 206 allow= 207 extra= 208 only= 209 values= 210 211 while : 212 do 213 : 1="$1" 214 case "$1" in 215 -a) allow="$2"; shift 2;; 216 -o) only="$2"; shift 2;; 217 -v) values="$2"; shift 2;; 218 -x) extra="$2"; shift 2;; 219 *=*) 220 if safe_set_var ${allow:+-a "$allow"} \ 221 ${only:+-o "$only"} \ 222 ${values:+-v "$values"} \ 223 ${extra:+-x "$extra"} "$1"; then 224 echo $ssv_var=$ssv_val 225 else 226 case $? in 227 1) echo invalid variable;; 228 2) echo "invalid value for '$ssv_var'";; 229 3) echo "denied variable '$ssv_var'";; 230 4) echo "denied value '$ssv_val' for '$ssv_var'";; 231 esac 232 fi 233 shift 234 ;; 235 *) break;; 236 esac 237 done 238 ;; 239 *) safe_dot "$@";; 240 esac 241 ;; 242esac 243