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:
parent
9478177e55
commit
5d32d4f94e
1 changed files with 25 additions and 1 deletions
|
|
@ -35,6 +35,7 @@ command line. The processing of a command works as follows:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
from weakref import WeakValueDictionary
|
from weakref import WeakValueDictionary
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
|
@ -50,6 +51,11 @@ __all__ = ("cmdhandler",)
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_CMDSET_MERGE_CACHE = WeakValueDictionary()
|
_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.
|
# This decides which command parser is to be used.
|
||||||
# You have to restart the server for changes to take effect.
|
# You have to restart the server for changes to take effect.
|
||||||
_COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit('.', 1))
|
_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." \
|
"Above traceback is from a Command handler bug." \
|
||||||
"Please file a bug report with the Evennia project."
|
"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):
|
def _msg_err(receiver, string):
|
||||||
"""
|
"""
|
||||||
|
|
@ -111,7 +120,6 @@ class NoCmdSets(Exception):
|
||||||
"No cmdsets found. Critical error."
|
"No cmdsets found. Critical error."
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ExecSystemCommand(Exception):
|
class ExecSystemCommand(Exception):
|
||||||
"Run a system command"
|
"Run a system command"
|
||||||
def __init__(self, syscmd, sysarg):
|
def __init__(self, syscmd, sysarg):
|
||||||
|
|
@ -324,6 +332,7 @@ def get_and_merge_cmdsets(caller, session, player, obj,
|
||||||
|
|
||||||
# Main command-handler function
|
# Main command-handler function
|
||||||
|
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sessid=None, **kwargs):
|
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
|
cmd (Command): command object
|
||||||
cmdname (str): name of command
|
cmdname (str): name of command
|
||||||
args (str): extra text entered after the identified command
|
args (str): extra text entered after the identified command
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
deferred (Deferred): this will fire with the return of the
|
deferred (Deferred): this will fire with the return of the
|
||||||
command's `func` method.
|
command's `func` method.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If command recursion limit was reached.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
global _COMMAND_NESTING
|
||||||
try:
|
try:
|
||||||
# Assign useful variables to the instance
|
# Assign useful variables to the instance
|
||||||
cmd.caller = caller
|
cmd.caller = caller
|
||||||
|
|
@ -403,6 +418,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
for key, val in kwargs.items():
|
for key, val in kwargs.items():
|
||||||
setattr(cmd, key, val)
|
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
|
# pre-command hook
|
||||||
abort = yield cmd.at_pre_cmd()
|
abort = yield cmd.at_pre_cmd()
|
||||||
if abort:
|
if abort:
|
||||||
|
|
@ -425,6 +447,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
caller.ndb.last_cmd = yield copy(cmd)
|
caller.ndb.last_cmd = yield copy(cmd)
|
||||||
else:
|
else:
|
||||||
caller.ndb.last_cmd = None
|
caller.ndb.last_cmd = None
|
||||||
|
_COMMAND_NESTING[called_by] -= 1
|
||||||
|
|
||||||
# return result to the deferred
|
# return result to the deferred
|
||||||
returnValue(ret)
|
returnValue(ret)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue