commit:     f4ce893c16ede796c9a524650702e49afb5d0361
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Sun Aug  4 23:43:44 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Sun Aug  4 23:52:51 2024 +0000
URL:        
https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=f4ce893c

Add the assign() and deref() functions

These two functions are primarily intended to mitigate the appalling use
of eval in projects such as netifrc and openrc. Consider the following
code.

net/iproute2.sh:29:     eval netns="\$netns_${IFVAR}"

This could instead be be written as:

deref "netns_${IFVAR}" netns

Alternatively, it could be written so as to use a command substitution:

netns=$(deref "netns_${IFVAR}")

Either method would protect against against illegal identifier names and
code injection.

Consider, also, the following code.

net/iproute2.sh:185:                            eval "$x=$1" ; shift ;;

This could instead be written as:

assign "$x" "$1"

As with deref, it would protect against illegal identifier names and
code injection.

Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>

 functions.sh   | 44 ++++++++++++++++++++++++++++++++++++++++++++
 test-functions | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+)

diff --git a/functions.sh b/functions.sh
index 94114cc..bea948f 100644
--- a/functions.sh
+++ b/functions.sh
@@ -32,6 +32,25 @@
 
 
#------------------------------------------------------------------------------#
 
+#
+# Considers the first parameter as a reference to a variable by name and
+# assigns the second parameter as its value. If the first parameter is found
+# not to be a legal identifier, no assignment shall occur and the return value
+# shall be greater than 0.
+#
+assign()
+{
+       if [ "$#" -ne 2 ]; then
+               warn "assign: wrong number of arguments (got $#, expected 2)"
+               false
+       elif ! is_identifier "$1"; then
+               _warn_for_args assign "$@"
+               false
+       else
+               eval "$1=\$2"
+       fi
+}
+
 #
 # A safe wrapper for the cd builtin. To run cd "$dir" is problematic because:
 #
@@ -117,6 +136,31 @@ contains_any()
        false
 }
 
+#
+# Considers the first parameter as a reference to a variable by name and
+# attempts to retrieve its presently assigned value. If only one parameter is
+# specified, the retrieved value shall be printed to the standard output. If a
+# second parameter is also specified, it shall be be taken as the name of a
+# variable to which the retrieved value shall be assigned. If any parameter is
+# found not to be a legal identifier, or if the variable referenced by the
+# first parameter is unset, the return value shall be greater than 0.
+#
+deref()
+{
+       if [ "$#" -eq 0 ] || [ "$#" -gt 2 ]; then
+               warn "deref: wrong number of arguments (got $#, expected 
between 1 and 2)"
+       elif ! trueof_all is_identifier -- "$@"; then
+               _warn_for_args deref "$@"
+               false
+       elif ! eval "test \${$1+set}"; then
+               false
+       elif [ "$#" -eq 1 ]; then
+               eval "printf '%s\\n' \"\$$1\""
+       else
+               eval "$2=\$$1"
+       fi
+}
+
 #
 # Determines whether the current shell is a subprocess of portage.
 #

diff --git a/test-functions b/test-functions
index 8acb731..561ddc5 100755
--- a/test-functions
+++ b/test-functions
@@ -824,6 +824,57 @@ test_quote_args() {
        }
 }
 
+test_assign() {
+       set -- \
+               ge  1  N/A              N/A       \
+               ge  1  ''               N/A       \
+               ge  1  0                N/A       \
+               ge  1  valid_nameref    N/A       \
+               ge  1  ''               marmoset  \
+               ge  1  0                marmoset  \
+               ge  1  valid_nameref    N/A       \
+               ge  1  'injection=1 #'  comment   \
+               eq  0  valid_nameref    marmoset
+
+       callback() {
+               shift
+               test_description="assign $(quote_args "$@")"
+               injection=
+               assign "$@" 2>/dev/null || test "${injection}"
+       }
+
+       iterate_tests 4 "$@"
+}
+
+test_deref() {
+       set -- \
+               ge  1  N/A            N/A              \
+               ge  1  ''             N/A              \
+               ge  1  0              N/A              \
+               ge  1  ''             ''               \
+               ge  1  0              0                \
+               eq  0  valid_nameref  N/A              \
+               eq  0  valid_nameref  assignee         \
+               ge  1  PWD            'injection=1 #'
+
+       callback() {
+               shift
+               test_description="deref $(quote_args "$@")"
+               case $# in
+                       2)
+                               assignee= injection=
+                               deref "$@" \
+                               && { test "${assignee}" = "marmoset" || test 
"${injection}"; }
+                               ;;
+                       *)
+                               stdout=$(deref "$@") && test "${stdout}" = 
"marmoset"
+                               ;;
+               esac 2>/dev/null
+       }
+
+       iterate_tests 4 "$@"
+}
+
 iterate_tests() {
        slice_width=$1
        shift
@@ -902,6 +953,8 @@ else
        test_contains_all || rc=1
        test_contains_any || rc=1
        test_quote_args || rc=1
+       test_assign || rc=1
+       test_deref || rc=1
 fi
 
 cleanup_tmpdir

Reply via email to