Added a command recursion limit to cmdhandler, raising a clean exception if a command calls other commands to a too nested level (10 levels down, if you use commands to that nesting level you are doing something wrong anyway). See #765.

This commit is contained in:
Griatch 2015-09-27 13:44:06 +02:00
parent 9478177e55
commit 5d32d4f94e

View file

@ -35,6 +35,7 @@ command line. The processing of a command works as follows:
"""
from collections import defaultdict
from weakref import WeakValueDictionary
from copy import copy
from traceback import format_exc
@ -50,6 +51,11 @@ __all__ = ("cmdhandler",)
_GA = object.__getattribute__
_CMDSET_MERGE_CACHE = WeakValueDictionary()
# tracks recursive calls by each caller
# to avoid infinite loops (commands calling themselves)
_COMMAND_NESTING = defaultdict(lambda: 0)
_COMMAND_RECURSION_LIMIT = 10
# This decides which command parser is to be used.
# You have to restart the server for changes to take effect.
_COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit('.', 1))
@ -92,6 +98,9 @@ _ERROR_CMDHANDLER = "{traceback}\n"\
"Above traceback is from a Command handler bug." \
"Please file a bug report with the Evennia project."
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
"reached for '{raw_string}' ({cmdclass})."
def _msg_err(receiver, string):
"""
@ -111,7 +120,6 @@ class NoCmdSets(Exception):
"No cmdsets found. Critical error."
pass
class ExecSystemCommand(Exception):
"Run a system command"
def __init__(self, syscmd, sysarg):
@ -324,6 +332,7 @@ def get_and_merge_cmdsets(caller, session, player, obj,
# Main command-handler function
@inlineCallbacks
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sessid=None, **kwargs):
"""
@ -372,10 +381,16 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
cmd (Command): command object
cmdname (str): name of command
args (str): extra text entered after the identified command
Returns:
deferred (Deferred): this will fire with the return of the
command's `func` method.
Raises:
RuntimeError: If command recursion limit was reached.
"""
global _COMMAND_NESTING
try:
# Assign useful variables to the instance
cmd.caller = caller
@ -403,6 +418,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
for key, val in kwargs.items():
setattr(cmd, key, val)
_COMMAND_NESTING[called_by] += 1
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT,
raw_string=unformatted_raw_string,
cmdclass=cmd.__class__)
raise RuntimeError(err)
# pre-command hook
abort = yield cmd.at_pre_cmd()
if abort:
@ -425,6 +447,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
caller.ndb.last_cmd = yield copy(cmd)
else:
caller.ndb.last_cmd = None
_COMMAND_NESTING[called_by] -= 1
# return result to the deferred
returnValue(ret)