diff --git a/mitmproxy/addons/core.py b/mitmproxy/addons/core.py index 7b6484030..1cbf4a014 100644 --- a/mitmproxy/addons/core.py +++ b/mitmproxy/addons/core.py @@ -7,7 +7,8 @@ class Core: """ Set an option of the form "key[=value]". When the value is omitted, booleans are set to true, strings and integers are set to None (if - permitted), and sequences are emptied. + permitted), and sequences are emptied. Boolean values can be true, + false or toggle. """ try: ctx.options.set(spec) diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index cf6e21b06..70f60bb64 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -293,6 +293,8 @@ class OptManager: else: return None elif o.typespec == bool: + if optstr == "toggle": + return not o.current() if not optstr or optstr == "true": return True elif optstr == "false": diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py index ca83d50e7..73ec04c75 100644 --- a/mitmproxy/tools/cmdline.py +++ b/mitmproxy/tools/cmdline.py @@ -44,7 +44,7 @@ def common_options(parser, opts): help=""" Set an option. When the value is omitted, booleans are set to true, strings and integers are set to None (if permitted), and sequences - are emptied. + are emptied. Boolean values can be true, false or toggle. """ ) parser.add_argument( diff --git a/mitmproxy/tools/console/flowlist.py b/mitmproxy/tools/console/flowlist.py index e75708248..c4de10424 100644 --- a/mitmproxy/tools/console/flowlist.py +++ b/mitmproxy/tools/console/flowlist.py @@ -70,9 +70,6 @@ class LogBufferBox(urwid.ListBox): self.set_focus(len(self.master.logbuffer) - 1) elif key == "g": self.set_focus(0) - elif key == "F": - o = self.master.options - o.console_focus_follow = not o.console_focus_follow return urwid.ListBox.keypress(self, size, key) @@ -106,9 +103,6 @@ class BodyPile(urwid.Pile): else: self.widget_list[1].header = self.inactive_header key = None - elif key == "e": - self.master.toggle_eventlog() - key = None # This is essentially a copypasta from urwid.Pile's keypress handler. # So much for "closed for modification, but open for extension". @@ -362,20 +356,12 @@ class FlowListBox(urwid.ListBox): self.master.view.clear() elif key == "Z": self.master.view.clear_not_marked() - elif key == "e": - self.master.toggle_eventlog() elif key == "g": if len(self.master.view): self.master.view.focus.index = 0 elif key == "G": if len(self.master.view): self.master.view.focus.index = len(self.master.view) - 1 - elif key == "f": - signals.status_prompt.send( - prompt = "Filter View", - text = self.master.options.view_filter, - callback = self.master.options.setter("view_filter") - ) elif key == "L": signals.status_prompt_path.send( self, @@ -402,20 +388,5 @@ class FlowListBox(urwid.ListBox): keys = orders, callback = change_order ) - elif key == "F": - o = self.master.options - o.console_focus_follow = not o.console_focus_follow - elif key == "v": - val = not self.master.options.console_order_reversed - self.master.options.console_order_reversed = val - elif key == "W": - if self.master.options.save_stream_file: - self.master.options.save_stream_file = None - else: - signals.status_prompt_path.send( - self, - prompt="Stream flows to", - callback= lambda path: self.master.options.update(save_stream_file=path) - ) else: return urwid.ListBox.keypress(self, size, key) diff --git a/mitmproxy/tools/console/keymap.py b/mitmproxy/tools/console/keymap.py index 018d1bdec..e3d28cf41 100644 --- a/mitmproxy/tools/console/keymap.py +++ b/mitmproxy/tools/console/keymap.py @@ -2,16 +2,29 @@ import typing from mitmproxy.tools.console import commandeditor +contexts = { + "commands", + "flowlist", + "flowview", + "global", + "grideditor", + "help", + "options", +} + + class Keymap: def __init__(self, master): self.executor = commandeditor.CommandExecutor(master) self.keys = {} - def add(self, key: str, command: str, context: str = "") -> None: + def add(self, key: str, command: str, context: str = "global") -> None: """ Add a key to the key map. If context is empty, it's considered to be a global binding. """ + if context not in contexts: + raise ValueError("Unsupported context: %s" % context) d = self.keys.setdefault(context, {}) d[key] = command @@ -25,10 +38,8 @@ class Keymap: Returns the key if it has not been handled, or None. """ cmd = self.get(context, key) + if not cmd: + cmd = self.get("global", key) if cmd: return self.executor(cmd) - if cmd != "": - cmd = self.get("", key) - if cmd: - return self.executor(cmd) return key diff --git a/mitmproxy/tools/console/master.py b/mitmproxy/tools/console/master.py index 74413f4ce..d23390605 100644 --- a/mitmproxy/tools/console/master.py +++ b/mitmproxy/tools/console/master.py @@ -82,9 +82,12 @@ class ConsoleCommands: """ def __init__(self, master): self.master = master + self.started = False def command(self, partial: str) -> None: - """Prompt for a command.""" + """ + Prompt the user to edit a command with a (possilby empty) starting value. + """ signals.status_prompt_command.send(partial=partial) def view_commands(self) -> None: @@ -118,6 +121,14 @@ class ConsoleCommands: l.add_command("console.view.options", self.view_options) l.add_command("console.view.pop", self.view_pop) + def running(self): + self.started = True + + def configure(self, updated): + if self.started: + if "console_eventlog" in updated: + self.master.refresh_view() + def default_keymap(km): km.add(":", "console.command ''") @@ -127,6 +138,12 @@ def default_keymap(km): km.add("Q", "console.exit") km.add("q", "console.view.pop") km.add("i", "console.command 'set intercept='") + km.add("W", "console.command 'set save_stream_file='") + + km.add("F", "set console_focus_follow=toggle", context="flowlist") + km.add("v", "set console_order_reversed=toggle", context="flowlist") + km.add("f", "console.command 'set view_filter='", context="flowlist") + km.add("e", "set console_eventlog=toggle", context="flowlist") class ConsoleMaster(master.Master): @@ -212,7 +229,7 @@ class ConsoleMaster(master.Master): def sig_replace_view_state(self, sender): """ A view has been pushed onto the stack, and is intended to replace - the current view rather tha creating a new stack entry. + the current view rather than creating a new stack entry. """ if len(self.view_stack) > 1: del self.view_stack[1] @@ -244,8 +261,7 @@ class ConsoleMaster(master.Master): except ValueError as e: signals.add_log("Input error: %s" % e, "warn") - def toggle_eventlog(self): - self.options.console_eventlog = not self.options.console_eventlog + def refresh_view(self): self.view_flowlist() signals.replace_view_state.send(self) @@ -389,7 +405,7 @@ class ConsoleMaster(master.Master): ) def view_help(self): - hc = self.view_stack[0].helpctx + hc = self.view_stack[-1].helpctx signals.push_view_state.send( self, window = window.Window( @@ -397,7 +413,8 @@ class ConsoleMaster(master.Master): help.HelpView(hc), None, statusbar.StatusBar(self, help.footer), - None + None, + "help" ) ) @@ -413,6 +430,7 @@ class ConsoleMaster(master.Master): None, statusbar.StatusBar(self, options.footer), options.help_context, + "options" ) ) @@ -427,7 +445,8 @@ class ConsoleMaster(master.Master): commands.Commands(self), None, statusbar.StatusBar(self, commands.footer), - options.help_context, + commands.help_context, + "commands" ) ) @@ -439,7 +458,8 @@ class ConsoleMaster(master.Master): ge, None, statusbar.StatusBar(self, grideditor.base.FOOTER), - ge.make_help() + ge.make_help(), + "grideditor" ) ) @@ -459,7 +479,8 @@ class ConsoleMaster(master.Master): body, None, statusbar.StatusBar(self, flowlist.footer), - flowlist.help_context + flowlist.help_context, + "flowlist" ) ) @@ -472,7 +493,8 @@ class ConsoleMaster(master.Master): flowview.FlowView(self, self.view, flow, tab_offset), flowview.FlowViewHeader(self, flow), statusbar.StatusBar(self, flowview.footer), - flowview.help_context + flowview.help_context, + "flowview" ) ) diff --git a/mitmproxy/tools/console/window.py b/mitmproxy/tools/console/window.py index b7fc8efab..ad972a668 100644 --- a/mitmproxy/tools/console/window.py +++ b/mitmproxy/tools/console/window.py @@ -5,7 +5,7 @@ from mitmproxy.tools.console import signals class Window(urwid.Frame): - def __init__(self, master, body, header, footer, helpctx): + def __init__(self, master, body, header, footer, helpctx, keyctx): urwid.Frame.__init__( self, urwid.AttrWrap(body, "background"), @@ -14,6 +14,7 @@ class Window(urwid.Frame): ) self.master = master self.helpctx = helpctx + self.keyctx = keyctx signals.focus.connect(self.sig_focus) def sig_focus(self, sender, section): @@ -82,4 +83,4 @@ class Window(urwid.Frame): def keypress(self, size, k): k = super().keypress(size, k) - return self.master.keymap.handle("", k) + return self.master.keymap.handle(self.keyctx, k) diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index a685570f5..04ec7ded8 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -381,6 +381,11 @@ def test_set(): with pytest.raises(exceptions.OptionsError): opts.set("bool=wobble") + opts.set("bool=toggle") + assert opts.bool is False + opts.set("bool=toggle") + assert opts.bool is True + opts.set("int=1") assert opts.int == 1 with pytest.raises(exceptions.OptionsError):