From c19b229608c208f4bf7121c588b1d403220d5361 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 20 Dec 2017 10:28:26 +1300 Subject: [PATCH 1/6] console: add console.bodyview.options This may be "request" or "response". --- mitmproxy/tools/console/consoleaddons.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py index 6ddcf394c..b5a62b599 100644 --- a/mitmproxy/tools/console/consoleaddons.py +++ b/mitmproxy/tools/console/consoleaddons.py @@ -322,6 +322,7 @@ class ConsoleAddon: signals.pop_view_state.send(self) @command.command("console.bodyview") + @command.argument("part", type=mitmproxy.types.Choice("console.bodyview.options")) def bodyview(self, f: flow.Flow, part: str) -> None: """ Spawn an external viewer for a flow request or response body based @@ -338,6 +339,13 @@ class ConsoleAddon: raise exceptions.CommandError("No content to view.") self.master.spawn_external_viewer(content, t) + @command.command("console.bodyview.options") + def bodyview_options(self) -> typing.Sequence[str]: + """ + Possible parts for console.bodyview. + """ + return ["request", "response"] + @command.command("console.edit.focus.options") def edit_focus_options(self) -> typing.Sequence[str]: """ From 6efff27be440a55ecbbebe84e313b48f464c6354 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 20 Dec 2017 10:35:53 +1300 Subject: [PATCH 2/6] commands: don't try to view flow returns as data Fixes #2704 --- mitmproxy/tools/console/commandexecutor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mitmproxy/tools/console/commandexecutor.py b/mitmproxy/tools/console/commandexecutor.py index 829daee13..26f92238d 100644 --- a/mitmproxy/tools/console/commandexecutor.py +++ b/mitmproxy/tools/console/commandexecutor.py @@ -23,6 +23,10 @@ class CommandExecutor: signals.status_message.send( message="Command returned %s flows" % len(ret) ) + elif type(ret) == flow.Flow: + signals.status_message.send( + message="Command returned 1 flow" + ) else: self.master.overlay( overlay.DataViewerOverlay( From 1e85345c00e4bd763f85ea2329d992ce6010c93c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 20 Dec 2017 10:49:29 +1300 Subject: [PATCH 3/6] "Commands" title -> "Command Reference" --- mitmproxy/tools/console/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mitmproxy/tools/console/commands.py b/mitmproxy/tools/console/commands.py index 20efcee37..1183ee9d4 100644 --- a/mitmproxy/tools/console/commands.py +++ b/mitmproxy/tools/console/commands.py @@ -124,7 +124,7 @@ class CommandHelp(urwid.Frame): class Commands(urwid.Pile, layoutwidget.LayoutWidget): - title = "Commands" + title = "Command Reference" keyctx = "commands" def __init__(self, master): From 4a9deb350df3d9f2f17533cfe10baad51b7b5426 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 20 Dec 2017 10:59:04 +1300 Subject: [PATCH 4/6] console options: retain focus position after edit Fixes #2622 --- mitmproxy/tools/console/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mitmproxy/tools/console/options.py b/mitmproxy/tools/console/options.py index 4d55aeec8..54772cf08 100644 --- a/mitmproxy/tools/console/options.py +++ b/mitmproxy/tools/console/options.py @@ -117,6 +117,7 @@ class OptionListWalker(urwid.ListWalker): def stop_editing(self): self.editing = False self.focus_obj = self._get(self.index, False) + self.set_focus(self.index) self._modified() def get_edit_text(self): From 2fd5bbe1e7db79ba2678cbb7875825d056f55762 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 20 Dec 2017 11:44:47 +1300 Subject: [PATCH 5/6] console: ignore non-nav keys in chooser Awkward, but works Fixes #2417 --- mitmproxy/tools/console/keymap.py | 17 +++++++++++++++++ mitmproxy/tools/console/overlay.py | 13 +++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mitmproxy/tools/console/keymap.py b/mitmproxy/tools/console/keymap.py index b268906c4..fbb569a40 100644 --- a/mitmproxy/tools/console/keymap.py +++ b/mitmproxy/tools/console/keymap.py @@ -17,6 +17,13 @@ Contexts = { } +navkeys = [ + "m_start", "m_end", "m_next", "m_select", + "up", "down", "page_up", "page_down", + "left", "right" +] + + class Binding: def __init__(self, key, command, contexts, help): self.key, self.command, self.contexts = key, command, sorted(contexts) @@ -122,3 +129,13 @@ class Keymap: if b: return self.executor(b.command) return key + + def handle_only(self, context: str, key: str) -> typing.Optional[str]: + """ + Like handle, but ignores global bindings. Returns the key if it has + not been handled, or None. + """ + b = self.get(context, key) + if b: + return self.executor(b.command) + return key diff --git a/mitmproxy/tools/console/overlay.py b/mitmproxy/tools/console/overlay.py index f97f23f92..55acbfddb 100644 --- a/mitmproxy/tools/console/overlay.py +++ b/mitmproxy/tools/console/overlay.py @@ -5,6 +5,7 @@ import urwid from mitmproxy.tools.console import signals from mitmproxy.tools.console import grideditor from mitmproxy.tools.console import layoutwidget +from mitmproxy.tools.console import keymap class SimpleOverlay(urwid.Overlay, layoutwidget.LayoutWidget): @@ -114,13 +115,21 @@ class Chooser(urwid.WidgetWrap, layoutwidget.LayoutWidget): return True def keypress(self, size, key): - key = self.master.keymap.handle("chooser", key) + key = self.master.keymap.handle_only("chooser", key) if key == "m_select": self.callback(self.choices[self.walker.index]) signals.pop_view_state.send(self) + return elif key == "esc": signals.pop_view_state.send(self) - return super().keypress(size, key) + return + + binding = self.master.keymap.get("global", key) + # This is extremely awkward. We need a better way to match nav keys only. + if binding and binding.command.startswith("console.nav"): + self.master.keymap.handle("global", key) + elif key in keymap.navkeys: + return super().keypress(size, key) class OptionsOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget): From 7d45d7f15e04ffd5dc1e593d2d3f3c4c9bfd1193 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 20 Dec 2017 11:53:44 +1300 Subject: [PATCH 6/6] cuts: don't crash when retrieving header of a non-existent response --- mitmproxy/addons/cut.py | 2 ++ test/mitmproxy/addons/test_cut.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/mitmproxy/addons/cut.py b/mitmproxy/addons/cut.py index b90df5497..f4b560e8f 100644 --- a/mitmproxy/addons/cut.py +++ b/mitmproxy/addons/cut.py @@ -36,6 +36,8 @@ def extract(cut: str, f: flow.Flow) -> typing.Union[str, bytes]: if spec == "host" and is_addr(current): return str(current[0]) elif spec.startswith("header["): + if not current: + return "" return current.headers.get(headername(spec), "") elif isinstance(part, bytes): return part diff --git a/test/mitmproxy/addons/test_cut.py b/test/mitmproxy/addons/test_cut.py index 0a523fff5..71e699db6 100644 --- a/test/mitmproxy/addons/test_cut.py +++ b/test/mitmproxy/addons/test_cut.py @@ -135,6 +135,11 @@ def test_cut(): with pytest.raises(exceptions.CommandError): assert c.cut(tflows, ["__dict__"]) == [[""]] + with taddons.context(): + tflows = [tflow.tflow(resp=False)] + assert c.cut(tflows, ["response.reason"]) == [[""]] + assert c.cut(tflows, ["response.header[key]"]) == [[""]] + c = cut.Cut() with taddons.context(): tflows = [tflow.ttcpflow()]