From f14ec2d8f043f051db45dd36a1d501788c7818e6 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 16 Dec 2017 11:53:45 +1300 Subject: [PATCH] console: add a data view overlay for command output Fixes #2654 --- mitmproxy/command.py | 2 +- mitmproxy/test/tutils.py | 1 - mitmproxy/tools/console/commandexecutor.py | 14 ++-- mitmproxy/tools/console/grideditor/col.py | 67 +++++++++++++++++++ .../tools/console/grideditor/col_bytes.py | 2 +- mitmproxy/tools/console/grideditor/editors.py | 18 +++++ mitmproxy/tools/console/overlay.py | 27 ++++++++ 7 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 mitmproxy/tools/console/grideditor/col.py diff --git a/mitmproxy/command.py b/mitmproxy/command.py index b3c8eb223..2d51317c2 100644 --- a/mitmproxy/command.py +++ b/mitmproxy/command.py @@ -170,7 +170,7 @@ class Command: if chk: pargs.extend(remainder) else: - raise exceptions.CommandError("Invalid value type.") + raise exceptions.CommandError("Invalid value type: %s - expected %s" % (remainder, self.paramtypes[-1])) with self.manager.master.handlecontext(): ret = self.func(*pargs) diff --git a/mitmproxy/test/tutils.py b/mitmproxy/test/tutils.py index bcce547ac..cd9f3b3f6 100644 --- a/mitmproxy/test/tutils.py +++ b/mitmproxy/test/tutils.py @@ -1,4 +1,3 @@ -import time from io import BytesIO from mitmproxy.utils import data diff --git a/mitmproxy/tools/console/commandexecutor.py b/mitmproxy/tools/console/commandexecutor.py index e57ddbb4d..829daee13 100644 --- a/mitmproxy/tools/console/commandexecutor.py +++ b/mitmproxy/tools/console/commandexecutor.py @@ -2,6 +2,8 @@ import typing from mitmproxy import exceptions from mitmproxy import flow + +from mitmproxy.tools.console import overlay from mitmproxy.tools.console import signals @@ -21,9 +23,11 @@ class CommandExecutor: signals.status_message.send( message="Command returned %s flows" % len(ret) ) - elif len(str(ret)) < 50: - signals.status_message.send(message=str(ret)) else: - signals.status_message.send( - message="Command returned too much data to display." - ) + self.master.overlay( + overlay.DataViewerOverlay( + self.master, + ret, + ), + valign="top" + ) \ No newline at end of file diff --git a/mitmproxy/tools/console/grideditor/col.py b/mitmproxy/tools/console/grideditor/col.py new file mode 100644 index 000000000..3331f3e72 --- /dev/null +++ b/mitmproxy/tools/console/grideditor/col.py @@ -0,0 +1,67 @@ +import typing + +import urwid + +from mitmproxy.tools.console import signals +from mitmproxy.tools.console.grideditor import base +from mitmproxy.utils import strutils + +strbytes = typing.Union[str, bytes] + + +class Column(base.Column): + def Display(self, data): + return Display(data) + + def Edit(self, data): + return Edit(data) + + def blank(self): + return "" + + def keypress(self, key, editor): + if key in ["m_select"]: + editor.walker.start_edit() + else: + return key + + +class Display(base.Cell): + def __init__(self, data: strbytes) -> None: + self.data = data + if isinstance(data, bytes): + escaped = strutils.bytes_to_escaped_str(data) + else: + escaped = data.encode() + w = urwid.Text(escaped, wrap="any") + super().__init__(w) + + def get_data(self) -> strbytes: + return self.data + + +class Edit(base.Cell): + def __init__(self, data: strbytes) -> None: + if isinstance(data, bytes): + escaped = strutils.bytes_to_escaped_str(data) + else: + escaped = data.encode() + self.type = type(data) # type: typing.Type + w = urwid.Edit(edit_text=escaped, wrap="any", multiline=True) + w = urwid.AttrWrap(w, "editfield") + super().__init__(w) + + def get_data(self) -> strbytes: + txt = self._w.get_text()[0].strip() + try: + if self.type == bytes: + return strutils.escaped_str_to_bytes(txt) + else: + return txt.decode() + except ValueError: + signals.status_message.send( + self, + message="Invalid Python-style string encoding.", + expire=1000 + ) + raise diff --git a/mitmproxy/tools/console/grideditor/col_bytes.py b/mitmproxy/tools/console/grideditor/col_bytes.py index da10cbafb..990253ea4 100644 --- a/mitmproxy/tools/console/grideditor/col_bytes.py +++ b/mitmproxy/tools/console/grideditor/col_bytes.py @@ -46,7 +46,7 @@ class Edit(base.Cell): except ValueError: signals.status_message.send( self, - message="Invalid Python-style string encoding.", + message="Invalid data.", expire=1000 ) raise diff --git a/mitmproxy/tools/console/grideditor/editors.py b/mitmproxy/tools/console/grideditor/editors.py index 074cdb77b..b5d16737a 100644 --- a/mitmproxy/tools/console/grideditor/editors.py +++ b/mitmproxy/tools/console/grideditor/editors.py @@ -2,6 +2,7 @@ from mitmproxy import exceptions from mitmproxy.tools.console import layoutwidget from mitmproxy.tools.console.grideditor import base +from mitmproxy.tools.console.grideditor import col from mitmproxy.tools.console.grideditor import col_text from mitmproxy.tools.console.grideditor import col_bytes from mitmproxy.tools.console.grideditor import col_subgrid @@ -169,3 +170,20 @@ class OptionsEditor(base.GridEditor, layoutwidget.LayoutWidget): def is_error(self, col, val): pass + + +class DataViewer(base.GridEditor, layoutwidget.LayoutWidget): + title = None # type: str + + def __init__(self, master, vals): + if vals: + if not isinstance(vals[0], list): + vals = [[i] for i in vals] + self.columns = [col.Column("")] * len(vals[0]) + super().__init__(master, vals, self.callback) + + def callback(self, vals): + pass + + def is_error(self, col, val): + pass diff --git a/mitmproxy/tools/console/overlay.py b/mitmproxy/tools/console/overlay.py index 7072d00ec..f97f23f92 100644 --- a/mitmproxy/tools/console/overlay.py +++ b/mitmproxy/tools/console/overlay.py @@ -148,3 +148,30 @@ class OptionsOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget): def layout_popping(self): return self.ge.layout_popping() + + +class DataViewerOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget): + keyctx = "grideditor" + + def __init__(self, master, vals): + """ + vspace: how much vertical space to keep clear + """ + cols, rows = master.ui.get_cols_rows() + self.ge = grideditor.DataViewer(master, vals) + super().__init__( + urwid.AttrWrap( + urwid.LineBox( + urwid.BoxAdapter(self.ge, rows - 5), + title="Data viewer" + ), + "background" + ) + ) + self.width = math.ceil(cols * 0.8) + + def key_responder(self): + return self.ge.key_responder() + + def layout_popping(self): + return self.ge.layout_popping()