Replace eval() and exec() use for menu functions
authorJeremy Stanley <fungi@yuggoth.org>
Sat, 28 Dec 2019 21:01:32 +0000 (21:01 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Mon, 30 Dec 2019 15:07:54 +0000 (15:07 +0000)
Add a new misc.call_hook_function() routine based on the existing
command execution in misc.handler_active() to eliminate all use of
eval() and exec() built-ins in menu operations.

mudpy/misc.py

index dc37b6e..2cd7992 100644 (file)
@@ -1585,16 +1585,15 @@ def get_menu_choices(user):
     state = universe.groups["menu"][user.state]
     create_choices = state.get("create")
     if create_choices:
-        choices = eval(create_choices)
+        choices = call_hook_function(create_choices, (user,))
     else:
         choices = {}
     ignores = []
     options = {}
     creates = {}
     for facet in state.facets():
-        if facet.startswith("demand_") and not eval(
-           universe.groups["menu"][user.state].get(facet)
-           ):
+        if facet.startswith("demand_") and not call_hook_function(
+                universe.groups["menu"][user.state].get(facet), (user,)):
             ignores.append(facet.split("_", 2)[1])
         elif facet.startswith("create_"):
             creates[facet] = facet.split("_", 2)[1]
@@ -1602,7 +1601,8 @@ def get_menu_choices(user):
             options[facet] = facet.split("_", 2)[1]
     for facet in creates.keys():
         if not creates[facet] in ignores:
-            choices[creates[facet]] = eval(state.get(facet))
+            choices[creates[facet]] = call_hook_function(
+                state.get(facet), (user,))
     for facet in options.keys():
         if not options[facet] in ignores:
             choices[options[facet]] = state.get(facet)
@@ -1677,6 +1677,35 @@ def get_choice_action(user):
         return ""
 
 
+def call_hook_function(fname, arglist):
+    """Safely execute named function with supplied arguments, return result."""
+
+    # strip any explicit leader or parameter
+    # TODO(fungi) remove this once the menu functions transition is complete
+    if fname.startswith("mudpy."):
+        fname = fname[6:]
+    if fname.endswith("(user)"):
+        fname = fname[:-6]
+
+    # all functions relative to mudpy package
+    function = mudpy
+
+    for component in fname.split("."):
+        try:
+            function = getattr(function, component)
+        except AttributeError:
+            log('Could not find mudpy.%s() for arguments "%s"'
+                % (fname, arglist), 7)
+            function = None
+            break
+    if function:
+        try:
+            return function(*arglist)
+        except Exception:
+            log('Calling mudpy.%s(%s) raised an exception...\n%s'
+                % (fname, (*arglist,), traceback.format_exc()), 7)
+
+
 def handle_user_input(user):
     """The main handler, branches to a state-specific handler."""
 
@@ -1711,7 +1740,9 @@ def generic_menu_handler(user):
     if not user.choice:
         user.choice = get_default_menu_choice(user.state)
     if user.choice in user.menu_choices:
-        exec(get_choice_action(user))
+        action = get_choice_action(user)
+        if action:
+            call_hook_function(action, (user,))
         new_state = get_choice_branch(user)
         if new_state:
             user.state = new_state
@@ -1890,6 +1921,7 @@ def handler_active(user):
         ran = False
         if actor.can_run(command):
             # dereference the relative object path for the requested function
+            # TODO(fungi) use call_hook_function() here instead
             action = mudpy
             action_fname = command.get("action", command.key)
             for component in action_fname.split("."):