diff options
author | Aldo Cortesi <aldo@corte.si> | 2017-05-02 13:09:27 +1200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-02 13:09:27 +1200 |
commit | 9d2350b6709532bf185c6e0f56342b7b780092c7 (patch) | |
tree | 2442d2393690b70c9c66c924fe308c5ca8e4c37d | |
parent | 8d29492960a89078ad2743e648afa08a3c71ebc1 (diff) | |
parent | dcae79e017c5fb267a4091d9584990e75777cc38 (diff) | |
download | mitmproxy-9d2350b6709532bf185c6e0f56342b7b780092c7.tar.gz mitmproxy-9d2350b6709532bf185c6e0f56342b7b780092c7.tar.bz2 mitmproxy-9d2350b6709532bf185c6e0f56342b7b780092c7.zip |
Merge pull request #2305 from cortesi/consolebugs
console: various bugfixes
22 files changed, 219 insertions, 93 deletions
diff --git a/mitmproxy/addons/core.py b/mitmproxy/addons/core.py index 875da32b..426c47ad 100644 --- a/mitmproxy/addons/core.py +++ b/mitmproxy/addons/core.py @@ -4,6 +4,7 @@ from mitmproxy import ctx from mitmproxy import exceptions from mitmproxy import command from mitmproxy import flow +from mitmproxy import optmanager from mitmproxy.net.http import status_codes @@ -212,3 +213,47 @@ class Core: """ return ["gzip", "deflate", "br"] + + @command.command("options.load") + def options_load(self, path: str) -> None: + """ + Load options from a file. + """ + try: + optmanager.load_paths(ctx.options, path) + except (OSError, exceptions.OptionsError) as e: + raise exceptions.CommandError( + "Could not load options - %s" % e + ) from e + + @command.command("options.save") + def options_save(self, path: str) -> None: + """ + Save options to a file. + """ + try: + optmanager.save(ctx.options, path) + except OSError as e: + raise exceptions.CommandError( + "Could not save options - %s" % e + ) from e + + @command.command("options.reset") + def options_reset(self) -> None: + """ + Reset all options to defaults. + """ + ctx.options.reset() + + @command.command("options.reset.one") + def options_reset_one(self, name: str) -> None: + """ + Reset one option to its default value. + """ + if name not in ctx.options: + raise exceptions.CommandError("No such option: %s" % name) + setattr( + ctx.options, + name, + ctx.options.default(name), + ) diff --git a/mitmproxy/addons/view.py b/mitmproxy/addons/view.py index c7bced94..dd579585 100644 --- a/mitmproxy/addons/view.py +++ b/mitmproxy/addons/view.py @@ -353,6 +353,8 @@ class View(collections.Sequence): the view, negative from the end of the view, so that 0 is the first flow, -1 is the last flow. """ + if len(self) == 0: + return if dst < 0: dst = len(self) + dst if dst < 0: diff --git a/mitmproxy/tools/console/commands.py b/mitmproxy/tools/console/commands.py index 84455a88..76827a99 100644 --- a/mitmproxy/tools/console/commands.py +++ b/mitmproxy/tools/console/commands.py @@ -120,6 +120,12 @@ class CommandsList(urwid.ListBox): if key == "enter": foc, idx = self.get_focus() signals.status_prompt_command.send(partial=foc.cmd.path + " ") + elif key == "m_start": + self.set_focus(0) + self.walker._modified() + elif key == "m_end": + self.set_focus(len(self.walker.cmds) - 1) + self.walker._modified() return super().keypress(size, key) @@ -159,7 +165,6 @@ class Commands(urwid.Pile): self.master = master def keypress(self, size, key): - key = common.shortcuts(key) if key == "tab": self.focus_position = ( self.focus_position + 1 diff --git a/mitmproxy/tools/console/common.py b/mitmproxy/tools/console/common.py index 58129bd0..de024d1a 100644 --- a/mitmproxy/tools/console/common.py +++ b/mitmproxy/tools/console/common.py @@ -77,20 +77,6 @@ def format_keyvals(lst, key="key", val="text", indent=0): return ret -def shortcuts(k): - if k == " ": - k = "page down" - elif k == "ctrl f": - k = "page down" - elif k == "ctrl b": - k = "page up" - elif k == "j": - k = "down" - elif k == "k": - k = "up" - return k - - def fcol(s, attr): s = str(s) return ( diff --git a/mitmproxy/tools/console/flowdetailview.py b/mitmproxy/tools/console/flowdetailview.py index 6bca2a2f..28fe1fbc 100644 --- a/mitmproxy/tools/console/flowdetailview.py +++ b/mitmproxy/tools/console/flowdetailview.py @@ -27,7 +27,7 @@ def flowdetails(state, flow: http.HTTPFlow): text.append(urwid.Text([("head", "Metadata:")])) text.extend(common.format_keyvals(parts, key="key", val="text", indent=4)) - if sc is not None: + if sc is not None and sc.ip_address: text.append(urwid.Text([("head", "Server Connection:")])) parts = [ ["Address", human.format_address(sc.address)], diff --git a/mitmproxy/tools/console/flowlist.py b/mitmproxy/tools/console/flowlist.py index b14d27e7..8e28ff0f 100644 --- a/mitmproxy/tools/console/flowlist.py +++ b/mitmproxy/tools/console/flowlist.py @@ -58,13 +58,12 @@ class LogBufferBox(urwid.ListBox): super().set_focus(index) def keypress(self, size, key): - key = common.shortcuts(key) if key == "z": self.master.clear_events() key = None - elif key == "G": + elif key == "m_end": self.set_focus(len(self.master.logbuffer) - 1) - elif key == "g": + elif key == "m_start": self.set_focus(0) return urwid.ListBox.keypress(self, size, key) @@ -136,8 +135,7 @@ class FlowItem(urwid.WidgetWrap): return True def keypress(self, xxx_todo_changeme, key): - (maxcol,) = xxx_todo_changeme - return common.shortcuts(key) + return key class FlowListWalker(urwid.ListWalker): @@ -183,7 +181,10 @@ class FlowListBox(urwid.ListBox): super().__init__(FlowListWalker(master)) def keypress(self, size, key): - key = common.shortcuts(key) + if key == "m_start": + self.master.commands.call("view.go 0") + elif key == "m_end": + self.master.commands.call("view.go -1") return urwid.ListBox.keypress(self, size, key) def view_changed(self): diff --git a/mitmproxy/tools/console/flowview.py b/mitmproxy/tools/console/flowview.py index c564ce5a..00951610 100644 --- a/mitmproxy/tools/console/flowview.py +++ b/mitmproxy/tools/console/flowview.py @@ -271,7 +271,6 @@ class FlowDetails(tabs.Tabs): def keypress(self, size, key): key = super().keypress(size, key) - key = common.shortcuts(key) return self._w.keypress(size, key) diff --git a/mitmproxy/tools/console/grideditor/base.py b/mitmproxy/tools/console/grideditor/base.py index fa7f0439..35ae655f 100644 --- a/mitmproxy/tools/console/grideditor/base.py +++ b/mitmproxy/tools/console/grideditor/base.py @@ -253,6 +253,7 @@ FIRST_WIDTH_MIN = 20 class BaseGridEditor(urwid.WidgetWrap): + def __init__( self, master: "mitmproxy.tools.console.master.ConsoleMaster", @@ -345,15 +346,14 @@ class BaseGridEditor(urwid.WidgetWrap): self._w.keypress(size, key) return None - key = common.shortcuts(key) column = self.columns[self.walker.focus_col] - if key == "g": + if key == "m_start": self.walker.set_focus(0) - elif key == "G": + elif key == "m_end": self.walker.set_focus(len(self.walker.lst) - 1) - elif key in ["h", "left"]: + elif key == "left": self.walker.left() - elif key in ["l", "right"]: + elif key == "right": self.walker.right() elif key == "tab": self.walker.tab_next() diff --git a/mitmproxy/tools/console/help.py b/mitmproxy/tools/console/help.py index 33418624..ec0c95d9 100644 --- a/mitmproxy/tools/console/help.py +++ b/mitmproxy/tools/console/help.py @@ -4,7 +4,6 @@ import urwid from mitmproxy import flowfilter from mitmproxy.tools.console import common -from mitmproxy.tools.console import signals from mitmproxy import version @@ -85,14 +84,8 @@ class HelpView(urwid.ListBox): return text def keypress(self, size, key): - key = common.shortcuts(key) - if key == "q": - signals.pop_view_state.send(self) - return None - elif key == "?": - key = None - elif key == "g": + if key == "m_start": self.set_focus(0) - elif key == "G": + elif key == "m_end": self.set_focus(len(self.body.contents)) return urwid.ListBox.keypress(self, size, key) diff --git a/mitmproxy/tools/console/keymap.py b/mitmproxy/tools/console/keymap.py index 3b22d530..62e2dcfb 100644 --- a/mitmproxy/tools/console/keymap.py +++ b/mitmproxy/tools/console/keymap.py @@ -4,6 +4,7 @@ from mitmproxy.tools.console import commandeditor SupportedContexts = { + "chooser", "commands", "flowlist", "flowview", diff --git a/mitmproxy/tools/console/master.py b/mitmproxy/tools/console/master.py index 115785d3..b88a0354 100644 --- a/mitmproxy/tools/console/master.py +++ b/mitmproxy/tools/console/master.py @@ -27,7 +27,6 @@ from mitmproxy.tools.console import keymap from mitmproxy.tools.console import overlay from mitmproxy.tools.console import palettes from mitmproxy.tools.console import signals -from mitmproxy.tools.console import statusbar from mitmproxy.tools.console import window from mitmproxy import contentviews from mitmproxy.utils import strutils @@ -83,6 +82,72 @@ class ConsoleAddon: self.master = master self.started = False + @command.command("console.options.reset.current") + def options_reset_current(self) -> None: + """ + Reset the current option in the options editor. + """ + if self.master.window.focus.keyctx != "options": + raise exceptions.CommandError("Not viewing options.") + name = self.master.window.windows["options"].current_name() + self.master.commands.call("options.reset.one %s" % name) + + @command.command("console.nav.start") + def nav_start(self) -> None: + """ + Go to the start of a list or scrollable. + """ + self.master.inject_key("m_start") + + @command.command("console.nav.end") + def nav_end(self) -> None: + """ + Go to the end of a list or scrollable. + """ + self.master.inject_key("m_end") + + @command.command("console.nav.up") + def nav_up(self) -> None: + """ + Go up. + """ + self.master.inject_key("up") + + @command.command("console.nav.down") + def nav_down(self) -> None: + """ + Go down. + """ + self.master.inject_key("down") + + @command.command("console.nav.pageup") + def nav_pageup(self) -> None: + """ + Go up. + """ + self.master.inject_key("page up") + + @command.command("console.nav.pagedown") + def nav_pagedown(self) -> None: + """ + Go down. + """ + self.master.inject_key("page down") + + @command.command("console.nav.left") + def nav_left(self) -> None: + """ + Go left. + """ + self.master.inject_key("left") + + @command.command("console.nav.right") + def nav_right(self) -> None: + """ + Go right. + """ + self.master.inject_key("right") + @command.command("console.choose") def console_choose( self, prompt: str, choices: typing.Sequence[str], *cmd: typing.Sequence[str] @@ -101,7 +166,7 @@ class ConsoleAddon: except exceptions.CommandError as e: signals.status_message.send(message=str(e)) - self.master.overlay(overlay.Chooser(prompt, choices, "", callback)) + self.master.overlay(overlay.Chooser(self.master, prompt, choices, "", callback)) ctx.log.info(choices) @command.command("console.choose.cmd") @@ -124,7 +189,7 @@ class ConsoleAddon: except exceptions.CommandError as e: signals.status_message.send(message=str(e)) - self.master.overlay(overlay.Chooser(prompt, choices, "", callback)) + self.master.overlay(overlay.Chooser(self.master, prompt, choices, "", callback)) ctx.log.info(choices) @command.command("console.command") @@ -246,7 +311,7 @@ class ConsoleAddon: signals.status_message.send(message=str(e)) opts = [i.name.lower() for i in contentviews.views] - self.master.overlay(overlay.Chooser("Mode", opts, "", callback)) + self.master.overlay(overlay.Chooser(self.master, "Mode", opts, "", callback)) @command.command("console.flowview.mode") def flowview_mode(self) -> str: @@ -288,6 +353,17 @@ def default_keymap(km): km.add("O", "console.view.options", ["global"]) km.add("Q", "console.exit", ["global"]) km.add("q", "console.view.pop", ["global"]) + + km.add("g", "console.nav.start", ["global"]) + km.add("G", "console.nav.end", ["global"]) + km.add("k", "console.nav.up", ["global"]) + km.add("j", "console.nav.down", ["global"]) + km.add("l", "console.nav.right", ["global"]) + km.add("h", "console.nav.left", ["global"]) + km.add(" ", "console.nav.pagedown", ["global"]) + km.add("ctrl f", "console.nav.pagedown", ["global"]) + km.add("ctrl b", "console.nav.pageup", ["global"]) + km.add("i", "console.command set intercept=", ["global"]) km.add("W", "console.command set save_stream_file=", ["global"]) @@ -308,9 +384,7 @@ def default_keymap(km): ) km.add("f", "console.command set view_filter=", ["flowlist"]) km.add("F", "set console_focus_follow=toggle", ["flowlist"]) - km.add("g", "view.go 0", ["flowlist"]) - km.add("G", "view.go -1", ["flowlist"]) - km.add("l", "console.command cut.clip ", ["flowlist", "flowview"]) + km.add("ctrl l", "console.command cut.clip ", ["flowlist", "flowview"]) km.add("L", "console.command view.load ", ["flowlist"]) km.add("m", "flow.mark.toggle @focus", ["flowlist"]) km.add("M", "view.marked.toggle", ["flowlist"]) @@ -361,6 +435,7 @@ def default_keymap(km): ) km.add("p", "view.focus.prev", ["flowview"]) km.add("m", "console.flowview.mode.set", ["flowview"]) + km.add("tab", "console.nav.right", ["flowview"]) km.add( "z", "console.choose \"Part\" request,response " @@ -368,6 +443,11 @@ def default_keymap(km): ["flowview"] ) + km.add("L", "console.command options.load ", ["options"]) + km.add("S", "console.command options.save ", ["options"]) + km.add("D", "options.reset", ["options"]) + km.add("d", "console.options.reset.current", ["options"]) + class ConsoleMaster(master.Master): @@ -402,7 +482,6 @@ class ConsoleMaster(master.Master): signal.signal(signal.SIGINT, sigint_handler) - self.ab = None self.window = None def __setattr__(self, name, value): @@ -523,6 +602,9 @@ class ConsoleMaster(master.Master): self.loop.draw_screen() self.loop.set_alarm_in(0.01, self.ticker) + def inject_key(self, key): + self.loop.process_input([key]) + def run(self): self.ui = urwid.raw_display.Screen() self.ui.set_terminal_properties(256) @@ -537,7 +619,6 @@ class ConsoleMaster(master.Master): handle_mouse = self.options.console_mouse, ) - self.ab = statusbar.ActionBar(self) self.window = window.Window(self) self.loop.widget = self.window diff --git a/mitmproxy/tools/console/options.py b/mitmproxy/tools/console/options.py index 68967f91..fee61fe5 100644 --- a/mitmproxy/tools/console/options.py +++ b/mitmproxy/tools/console/options.py @@ -187,12 +187,6 @@ class OptionsList(urwid.ListBox): except exceptions.OptionsError as e: signals.status_message.send(message=str(e)) - def load_config(self, path): - try: - optmanager.load_paths(self.master.options, path) - except exceptions.OptionsError as e: - signals.status_message.send(message=str(e)) - def keypress(self, size, key): if self.walker.editing: if key == "enter": @@ -207,29 +201,12 @@ class OptionsList(urwid.ListBox): elif key == "esc": self.walker.stop_editing() else: - if key == "d": - foc, idx = self.get_focus() - setattr( - self.master.options, - foc.opt.name, - self.master.options.default(foc.opt.name) - ) - elif key == "g": + if key == "m_start": self.set_focus(0) self.walker._modified() - elif key == "G": + elif key == "m_end": self.set_focus(len(self.walker.opts) - 1) self.walker._modified() - elif key == "l": - signals.status_prompt_path.send( - prompt = "Load config from", - callback = self.load_config - ) - elif key == "w": - signals.status_prompt_path.send( - prompt = "Save config to", - callback = self.save_config - ) elif key == "enter": foc, idx = self.get_focus() if foc.opt.typespec == bool: @@ -242,6 +219,7 @@ class OptionsList(urwid.ListBox): elif foc.opt.choices: self.master.overlay( overlay.Chooser( + self.master, foc.opt.name, foc.opt.choices, foc.opt.current(), @@ -290,25 +268,26 @@ class Options(urwid.Pile): def __init__(self, master): oh = OptionHelp(master) + self.optionslist = OptionsList(master) super().__init__( [ - OptionsList(master), + self.optionslist, (HELP_HEIGHT, oh), ] ) self.master = master + def current_name(self): + foc, idx = self.optionslist.get_focus() + return foc.opt.name + def keypress(self, size, key): - key = common.shortcuts(key) if key == "tab": self.focus_position = ( self.focus_position + 1 ) % len(self.widget_list) self.widget_list[1].set_active(self.focus_position == 1) key = None - elif key == "D": - self.master.options.reset() - key = None # This is essentially a copypasta from urwid.Pile's keypress handler. # So much for "closed for modification, but open for extension". diff --git a/mitmproxy/tools/console/overlay.py b/mitmproxy/tools/console/overlay.py index 7e05fe81..2fa6aa46 100644 --- a/mitmproxy/tools/console/overlay.py +++ b/mitmproxy/tools/console/overlay.py @@ -80,7 +80,8 @@ class ChooserListWalker(urwid.ListWalker): class Chooser(urwid.WidgetWrap): - def __init__(self, title, choices, current, callback): + def __init__(self, master, title, choices, current, callback): + self.master = master self.choices = choices self.callback = callback choicewidth = max([len(i) for i in choices]) @@ -103,7 +104,7 @@ class Chooser(urwid.WidgetWrap): return True def keypress(self, size, key): - key = common.shortcuts(key) + key = self.master.keymap.handle("chooser", key) if key == "enter": self.callback(self.choices[self.walker.index]) signals.pop_view_state.send(self) diff --git a/mitmproxy/tools/console/searchable.py b/mitmproxy/tools/console/searchable.py index bb19135f..f2bb5612 100644 --- a/mitmproxy/tools/console/searchable.py +++ b/mitmproxy/tools/console/searchable.py @@ -35,10 +35,10 @@ class Searchable(urwid.ListBox): self.find_next(False) elif key == "N": self.find_next(True) - elif key == "g": + elif key == "m_start": self.set_focus(0) self.walker._modified() - elif key == "G": + elif key == "m_end": self.set_focus(len(self.walker) - 1) self.walker._modified() else: diff --git a/mitmproxy/tools/console/select.py b/mitmproxy/tools/console/select.py index a990dff8..f7e5d950 100644 --- a/mitmproxy/tools/console/select.py +++ b/mitmproxy/tools/console/select.py @@ -113,7 +113,6 @@ class Select(urwid.ListBox): if key == "enter" or key == " ": self.get_focus()[0].option.activate() return None - key = common.shortcuts(key) if key in self.keymap: self.keymap[key].activate() self.set_focus(self.options.index(self.keymap[key])) diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index f1cc4fae..a5db0f4a 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -151,7 +151,8 @@ class StatusBar(urwid.WidgetWrap): self.master = master self.helptext = helptext self.ib = urwid.WidgetWrap(urwid.Text("")) - super().__init__(urwid.Pile([self.ib, self.master.ab])) + self.ab = ActionBar(self) + super().__init__(urwid.Pile([self.ib, self.ab])) signals.update_settings.connect(self.sig_update) signals.flowlist_change.connect(self.sig_update) signals.footer_help.connect(self.sig_footer_help) @@ -167,7 +168,7 @@ class StatusBar(urwid.WidgetWrap): self.redraw() def keypress(self, *args, **kwargs): - return self.master.ab.keypress(*args, **kwargs) + return self.ab.keypress(*args, **kwargs) def get_status(self): r = [] diff --git a/mitmproxy/tools/console/tabs.py b/mitmproxy/tools/console/tabs.py index 4f5f270a..93d6909e 100644 --- a/mitmproxy/tools/console/tabs.py +++ b/mitmproxy/tools/console/tabs.py @@ -35,9 +35,9 @@ class Tabs(urwid.WidgetWrap): def keypress(self, size, key): n = len(self.tabs) - if key in ["tab", "l"]: + if key == "right": self.change_tab((self.tab_offset + 1) % n) - elif key == "h": + elif key == "left": self.change_tab((self.tab_offset - 1) % n) return self._w.keypress(size, key) diff --git a/mitmproxy/tools/console/window.py b/mitmproxy/tools/console/window.py index ed29465e..d7038da0 100644 --- a/mitmproxy/tools/console/window.py +++ b/mitmproxy/tools/console/window.py @@ -12,10 +12,11 @@ from mitmproxy.tools.console import grideditor class Window(urwid.Frame): def __init__(self, master): + self.statusbar = statusbar.StatusBar(master, "") super().__init__( None, header = None, - footer = statusbar.StatusBar(master, ""), + footer = urwid.AttrWrap(self.statusbar, "background") ) self.master = master self.primary_stack = [] @@ -76,6 +77,8 @@ class Window(urwid.Frame): self.call(self.focus, "view_popping") def push(self, wname): + if self.primary_stack and self.primary_stack[-1] == wname: + return self.primary_stack.append(wname) self.body = urwid.AttrWrap( self.windows[wname], "background" diff --git a/test/mitmproxy/addons/test_core.py b/test/mitmproxy/addons/test_core.py index eaf0dfa0..c132d80a 100644 --- a/test/mitmproxy/addons/test_core.py +++ b/test/mitmproxy/addons/test_core.py @@ -128,3 +128,38 @@ def test_encoding(): with pytest.raises(exceptions.CommandError): sa.encode([f], "request", "invalid") + + +def test_options(tmpdir): + p = str(tmpdir.join("path")) + sa = core.Core() + with taddons.context() as tctx: + tctx.options.stickycookie = "foo" + assert tctx.options.stickycookie == "foo" + sa.options_reset() + assert tctx.options.stickycookie is None + + tctx.options.stickycookie = "foo" + tctx.options.stickyauth = "bar" + sa.options_reset_one("stickycookie") + assert tctx.options.stickycookie is None + assert tctx.options.stickyauth == "bar" + + with pytest.raises(exceptions.CommandError): + sa.options_reset_one("unknown") + + sa.options_save(p) + with pytest.raises(exceptions.CommandError): + sa.options_save("/") + + sa.options_reset() + assert tctx.options.stickyauth is None + sa.options_load(p) + assert tctx.options.stickyauth == "bar" + + sa.options_load("/nonexistent") + + with open(p, 'a') as f: + f.write("'''") + with pytest.raises(exceptions.CommandError): + sa.options_load(p) diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py index a3df1fcf..dd5349cb 100644 --- a/test/mitmproxy/addons/test_script.py +++ b/test/mitmproxy/addons/test_script.py @@ -152,7 +152,7 @@ class TestScriptLoader: sc = script.ScriptLoader() with taddons.context(): with pytest.raises(exceptions.CommandError): - sc.script_run([tflow.tflow(resp=True)], "/nonexistent") + sc.script_run([tflow.tflow(resp=True)], "/") def test_simple(self): sc = script.ScriptLoader() diff --git a/test/mitmproxy/addons/test_view.py b/test/mitmproxy/addons/test_view.py index ef969d1d..6da13650 100644 --- a/test/mitmproxy/addons/test_view.py +++ b/test/mitmproxy/addons/test_view.py @@ -221,6 +221,7 @@ def test_resolve(): def test_movement(): v = view.View() with taddons.context(): + v.go(0) v.add([ tflow.tflow(), tflow.tflow(), diff --git a/test/mitmproxy/tools/console/test_help.py b/test/mitmproxy/tools/console/test_help.py index ac3011e6..0ebc2d6a 100644 --- a/test/mitmproxy/tools/console/test_help.py +++ b/test/mitmproxy/tools/console/test_help.py @@ -9,9 +9,3 @@ class TestHelp: def test_helptext(self): h = help.HelpView(None) assert h.helptext() - - def test_keypress(self): - h = help.HelpView([1, 2, 3]) - assert not h.keypress((0, 0), "q") - assert not h.keypress((0, 0), "?") - assert h.keypress((0, 0), "o") == "o" |