console: add a data view overlay for command output

Fixes #2654
This commit is contained in:
Aldo Cortesi 2017-12-16 11:53:45 +13:00
parent eab27db7d6
commit f14ec2d8f0
7 changed files with 123 additions and 8 deletions

View File

@ -170,7 +170,7 @@ class Command:
if chk: if chk:
pargs.extend(remainder) pargs.extend(remainder)
else: 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(): with self.manager.master.handlecontext():
ret = self.func(*pargs) ret = self.func(*pargs)

View File

@ -1,4 +1,3 @@
import time
from io import BytesIO from io import BytesIO
from mitmproxy.utils import data from mitmproxy.utils import data

View File

@ -2,6 +2,8 @@ import typing
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy import flow from mitmproxy import flow
from mitmproxy.tools.console import overlay
from mitmproxy.tools.console import signals from mitmproxy.tools.console import signals
@ -21,9 +23,11 @@ class CommandExecutor:
signals.status_message.send( signals.status_message.send(
message="Command returned %s flows" % len(ret) message="Command returned %s flows" % len(ret)
) )
elif len(str(ret)) < 50:
signals.status_message.send(message=str(ret))
else: else:
signals.status_message.send( self.master.overlay(
message="Command returned too much data to display." overlay.DataViewerOverlay(
) self.master,
ret,
),
valign="top"
)

View File

@ -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

View File

@ -46,7 +46,7 @@ class Edit(base.Cell):
except ValueError: except ValueError:
signals.status_message.send( signals.status_message.send(
self, self,
message="Invalid Python-style string encoding.", message="Invalid data.",
expire=1000 expire=1000
) )
raise raise

View File

@ -2,6 +2,7 @@
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy.tools.console import layoutwidget from mitmproxy.tools.console import layoutwidget
from mitmproxy.tools.console.grideditor import base 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_text
from mitmproxy.tools.console.grideditor import col_bytes from mitmproxy.tools.console.grideditor import col_bytes
from mitmproxy.tools.console.grideditor import col_subgrid from mitmproxy.tools.console.grideditor import col_subgrid
@ -169,3 +170,20 @@ class OptionsEditor(base.GridEditor, layoutwidget.LayoutWidget):
def is_error(self, col, val): def is_error(self, col, val):
pass 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

View File

@ -148,3 +148,30 @@ class OptionsOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget):
def layout_popping(self): def layout_popping(self):
return self.ge.layout_popping() 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()