X-Git-Url: https://mudpy.org/gitweb?p=mudpy.git;a=blobdiff_plain;f=mudpy%2Fcommand.py;h=279da788d9c63389b55af720f9bf3dca1ea78fa5;hp=ebd9036983b8178a0f3d9a7ef71840b811446c46;hb=0de1cbedcdff936f461aa6b9421cb925295bba10;hpb=b64bdabe02e5d30113df5052050cfb9b62683a74 diff --git a/mudpy/command.py b/mudpy/command.py index ebd9036..279da78 100644 --- a/mudpy/command.py +++ b/mudpy/command.py @@ -1,17 +1,18 @@ """User command functions for the mudpy engine.""" -# Copyright (c) 2004-2019 mudpy authors. Permission to use, copy, +# Copyright (c) 2004-2020 mudpy authors. Permission to use, copy, # modify, and distribute this software is granted under terms # provided in the LICENSE file distributed with this software. import random import re +import traceback import unicodedata import mudpy -def chat(actor): +def chat(actor, parameters): """Toggle chat mode.""" mode = actor.get("mode") if not mode: @@ -22,6 +23,7 @@ def chat(actor): actor.send("Exiting chat mode.") else: actor.send("Sorry, but you're already busy with something else!") + return True def create(actor, parameters): @@ -56,6 +58,7 @@ def create(actor, parameters): elif len(arguments) > 2: message = "You can only specify an element and a filename." actor.send(message) + return True def delete(actor, parameters): @@ -83,6 +86,7 @@ def delete(actor, parameters): + '". Try "show element ' + element + '" for verification.') actor.send(message) + return True def destroy(actor, parameters): @@ -104,21 +108,30 @@ def destroy(actor, parameters): 6 ) actor.send(message) + return True def error(actor, input_data): """Generic error for an unrecognized command word.""" # 90% of the time use a generic error - if random.randrange(10): + # Allow the random.randrange() call in bandit since it's not used for + # security/cryptographic purposes + if random.randrange(10): # nosec message = '''I'm not sure what "''' + input_data + '''" means...''' # 10% of the time use the classic diku error else: message = "Arglebargle, glop-glyf!?!" - # send the error message - actor.send(message) + # try to send the error message, and log if we can't + try: + actor.send(message) + except Exception: + mudpy.misc.log( + 'Sending a command error to user %s raised exception...\n%s' % ( + actor.owner.account.get("name"), traceback.format_exc())) + return True def halt(actor, parameters): @@ -139,6 +152,7 @@ def halt(actor, parameters): # set a flag to terminate the world actor.universe.terminate_flag = True + return True def help(actor, parameters): @@ -148,10 +162,7 @@ def help(actor, parameters): if parameters and actor.owner: # is the command word one for which we have data? - if parameters in actor.universe.groups["command"]: - command = actor.universe.groups["command"][parameters] - else: - command = None + command = mudpy.misc.find_command(parameters) # only for allowed commands if actor.can_run(command): @@ -160,11 +171,12 @@ def help(actor, parameters): description = command.get("description") if not description: description = "(no short description provided)" - if command.get("administrative"): + if command.is_restricted(): output = "$(red)" else: output = "$(grn)" - output += parameters + "$(nrm) - " + description + "$(eol)$(eol)" + output = "%s%s$(nrm) - %s$(eol)$(eol)" % ( + output, command.subkey, description) # add the help text if provided help_text = command.get("help") @@ -182,7 +194,7 @@ def help(actor, parameters): if actor.can_run(command): if really_see_also: really_see_also += ", " - if command.get("administrative"): + if command.is_restricted(): really_see_also += "$(red)" else: really_see_also += "$(grn)" @@ -197,26 +209,57 @@ def help(actor, parameters): # no specific command word was indicated else: - # give a sorted list of commands with descriptions if provided - output = "These are the commands available to you:$(eol)$(eol)" - sorted_commands = list(actor.universe.groups["command"].keys()) - sorted_commands.sort() - for item in sorted_commands: - command = actor.universe.groups["command"][item] + # preamble text + output = ("These are the commands available to you [brackets indicate " + "optional portion]:$(eol)$(eol)") + + # list command names in alphabetical order + for command_name, command in sorted( + actor.universe.groups["command"].items()): + + # skip over disallowed commands if actor.can_run(command): - description = command.get("description") - if not description: - description = "(no short description provided)" - if command.get("administrative"): - output += " $(red)" + + # start incrementing substrings + for position in range(1, len(command_name) + 1): + + # we've found our shortest possible abbreviation + candidate = mudpy.misc.find_command( + command_name[:position]) + try: + if candidate.subkey == command_name: + break + except AttributeError: + pass + + # use square brackets to indicate optional part of command name + if position < len(command_name): + abbrev = "%s[%s]" % ( + command_name[:position], command_name[position:]) + else: + abbrev = command_name + + # supply a useful default if the short description is missing + description = command.get( + "description", "(no short description provided)") + + # administrative command names are in red, others in green + if command.is_restricted(): + color = "red" else: - output += " $(grn)" - output += item + "$(nrm) - " + description + "$(eol)" - output += ('$(eol)Enter "help COMMAND" for help on a command ' - 'named "COMMAND".') + color = "grn" + + # format the entry for this command + output = "%s $(%s)%s$(nrm) - %s$(eol)" % ( + output, color, abbrev, description) + + # add a footer with instructions on getting additional information + output = ('%s $(eol)Enter "help COMMAND" for help on a command named ' + '"COMMAND".' % output) # send the accumulated output to the user actor.send(output) + return True def look(actor, parameters): @@ -225,37 +268,54 @@ def look(actor, parameters): actor.send("You can't look at or in anything yet.") else: actor.look_at(actor.get("location")) + return True def move(actor, parameters): """Move the avatar in a given direction.""" - if parameters in actor.universe.contents[actor.get("location")].portals(): - actor.move_direction(parameters) - else: - actor.send("You cannot go that way.") + for portal in sorted( + actor.universe.contents[actor.get("location")].portals()): + if portal.startswith(parameters): + actor.move_direction(portal) + return(portal) + actor.send("You cannot go that way.") + return True def preferences(actor, parameters): """List, view and change actor preferences.""" + + # Escape replacement macros in preferences + parameters = mudpy.misc.escape_macros(parameters) + message = "" arguments = parameters.split() allowed_prefs = set() + base_prefs = [] user_config = actor.universe.contents.get("mudpy.user") if user_config: - allowed_prefs.update(user_config.get("pref_allow", [])) + base_prefs = user_config.get("pref_allow", []) + allowed_prefs.update(base_prefs) if actor.owner.account.get("administrator"): allowed_prefs.update(user_config.get("pref_admin", [])) if not arguments: message += "These are your current preferences:" - for pref in allowed_prefs: - message += ("$(eol) $(red)%s $(grn)%s$(nrm)" - % (pref, actor.owner.account.get(pref))) + + # color-code base and admin prefs + for pref in sorted(allowed_prefs): + if pref in base_prefs: + color = "grn" + else: + color = "red" + message += ("$(eol) $(%s)%s$(nrm) - %s" % ( + color, pref, actor.owner.account.get(pref, ""))) + elif arguments[0] not in allowed_prefs: message += ( 'Preference "%s" does not exist. Try the `preferences` command by ' "itself for a list of valid preferences." % arguments[0]) elif len(arguments) == 1: - message += "%s" % actor.owner.account.get(arguments[0]) + message += "%s" % actor.owner.account.get(arguments[0], "") else: pref = arguments[0] value = " ".join(arguments[1:]) @@ -267,16 +327,18 @@ def preferences(actor, parameters): 'Preference "%s" cannot be set to type "%s".' % ( pref, type(value))) actor.send(message) + return True -def quit(actor): +def quit(actor, parameters): """Leave the world and go back to the main menu.""" if actor.owner: actor.owner.state = "main_utility" actor.owner.deactivate_avatar() + return True -def reload(actor): +def reload(actor, parameters): """Reload all code modules, configs and data.""" if actor.owner: @@ -290,6 +352,7 @@ def reload(actor): # set a flag to reload actor.universe.reload_flag = True + return True def say(actor, parameters): @@ -373,6 +436,7 @@ def say(actor, parameters): # there was no message else: actor.send("What do you want to say?") + return True def c_set(actor, parameters): @@ -410,6 +474,7 @@ def c_set(actor, parameters): + '". Try "show element ' + element + '" for verification.') actor.send(message) + return True def show(actor, parameters): @@ -421,9 +486,8 @@ def show(actor, parameters): elif arguments[0] == "version": message = repr(actor.universe.versions) elif arguments[0] == "time": - message = actor.universe.groups["internal"]["counters"].get( - "elapsed" - ) + " increments elapsed since the world was created." + message = "%s increments elapsed since the world was created." % ( + str(actor.universe.groups["internal"]["counters"].get("elapsed"))) elif arguments[0] == "groups": message = "These are the element groups:$(eol)" groups = list(actor.universe.groups.keys()) @@ -489,7 +553,12 @@ def show(actor, parameters): message = "You need to specify an expression." else: try: - message = repr(eval(" ".join(arguments[1:]))) + # there is no other option than to use eval() for this, since + # its purpose is to evaluate arbitrary expressions, so do what + # we can to secure it and allow it for bandit analysis + message = repr(eval( # nosec + " ".join(arguments[1:]), + {"mudpy": mudpy, "universe": actor.universe})) except Exception as e: message = ("$(red)Your expression raised an exception...$(eol)" "$(eol)$(bld)%s$(nrm)" % e) @@ -526,3 +595,4 @@ def show(actor, parameters): else: message = '''I don't know what "''' + parameters + '" is.' actor.send(message) + return True