mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-27 02:24:18 +00:00
commit
40703afd0a
@ -1,30 +1,12 @@
|
|||||||
import urwid
|
import urwid
|
||||||
import blinker
|
import blinker
|
||||||
import textwrap
|
import textwrap
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import layoutwidget
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
|
|
||||||
HELP_HEIGHT = 5
|
HELP_HEIGHT = 5
|
||||||
|
|
||||||
|
|
||||||
footer = [
|
|
||||||
('heading_key', "enter"), ":edit ",
|
|
||||||
('heading_key', "?"), ":help ",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _mkhelp():
|
|
||||||
text = []
|
|
||||||
keys = [
|
|
||||||
("enter", "execute command"),
|
|
||||||
]
|
|
||||||
text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
help_context = _mkhelp()
|
|
||||||
|
|
||||||
|
|
||||||
def fcol(s, width, attr):
|
def fcol(s, width, attr):
|
||||||
s = str(s)
|
s = str(s)
|
||||||
return (
|
return (
|
||||||
@ -151,7 +133,7 @@ class CommandHelp(urwid.Frame):
|
|||||||
self.set_body(self.widget(txt))
|
self.set_body(self.widget(txt))
|
||||||
|
|
||||||
|
|
||||||
class Commands(urwid.Pile):
|
class Commands(urwid.Pile, layoutwidget.LayoutWidget):
|
||||||
title = "Commands"
|
title = "Commands"
|
||||||
keyctx = "commands"
|
keyctx = "commands"
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import urwid
|
import urwid
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
|
from mitmproxy.tools.console import layoutwidget
|
||||||
|
|
||||||
EVENTLOG_SIZE = 10000
|
EVENTLOG_SIZE = 10000
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ class LogBufferWalker(urwid.SimpleListWalker):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EventLog(urwid.ListBox):
|
class EventLog(urwid.ListBox, layoutwidget.LayoutWidget):
|
||||||
keyctx = "eventlog"
|
keyctx = "eventlog"
|
||||||
title = "Events"
|
title = "Events"
|
||||||
|
|
||||||
|
@ -1,52 +1,10 @@
|
|||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import common
|
||||||
|
from mitmproxy.tools.console import layoutwidget
|
||||||
import mitmproxy.tools.console.master # noqa
|
import mitmproxy.tools.console.master # noqa
|
||||||
|
|
||||||
|
|
||||||
def _mkhelp():
|
|
||||||
text = []
|
|
||||||
keys = [
|
|
||||||
("A", "accept all intercepted flows"),
|
|
||||||
("a", "accept this intercepted flow"),
|
|
||||||
("b", "save request/response body"),
|
|
||||||
("C", "export flow to clipboard"),
|
|
||||||
("d", "delete flow"),
|
|
||||||
("D", "duplicate flow"),
|
|
||||||
("e", "toggle eventlog"),
|
|
||||||
("E", "export flow to file"),
|
|
||||||
("f", "filter view"),
|
|
||||||
("F", "toggle follow flow list"),
|
|
||||||
("L", "load saved flows"),
|
|
||||||
("m", "toggle flow mark"),
|
|
||||||
("M", "toggle marked flow view"),
|
|
||||||
("n", "create a new request"),
|
|
||||||
("o", "set flow order"),
|
|
||||||
("r", "replay request"),
|
|
||||||
("S", "server replay request/s"),
|
|
||||||
("U", "unmark all marked flows"),
|
|
||||||
("v", "reverse flow order"),
|
|
||||||
("V", "revert changes to request"),
|
|
||||||
("w", "save flows "),
|
|
||||||
("W", "stream flows to file"),
|
|
||||||
("X", "kill and delete flow, even if it's mid-intercept"),
|
|
||||||
("z", "clear flow list or eventlog"),
|
|
||||||
("Z", "clear unmarked flows"),
|
|
||||||
("tab", "tab between eventlog and flow list"),
|
|
||||||
("enter", "view flow"),
|
|
||||||
("|", "run script on this flow"),
|
|
||||||
]
|
|
||||||
text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
help_context = _mkhelp()
|
|
||||||
|
|
||||||
footer = [
|
|
||||||
('heading_key', "?"), ":help ",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class FlowItem(urwid.WidgetWrap):
|
class FlowItem(urwid.WidgetWrap):
|
||||||
|
|
||||||
def __init__(self, master, flow):
|
def __init__(self, master, flow):
|
||||||
@ -109,7 +67,7 @@ class FlowListWalker(urwid.ListWalker):
|
|||||||
return f, pos
|
return f, pos
|
||||||
|
|
||||||
|
|
||||||
class FlowListBox(urwid.ListBox):
|
class FlowListBox(urwid.ListBox, layoutwidget.LayoutWidget):
|
||||||
title = "Flows"
|
title = "Flows"
|
||||||
keyctx = "flowlist"
|
keyctx = "flowlist"
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import urwid
|
|||||||
from mitmproxy import contentviews
|
from mitmproxy import contentviews
|
||||||
from mitmproxy import http
|
from mitmproxy import http
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import common
|
||||||
|
from mitmproxy.tools.console import layoutwidget
|
||||||
from mitmproxy.tools.console import flowdetailview
|
from mitmproxy.tools.console import flowdetailview
|
||||||
from mitmproxy.tools.console import searchable
|
from mitmproxy.tools.console import searchable
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
@ -19,82 +20,6 @@ class SearchError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _mkhelp():
|
|
||||||
text = []
|
|
||||||
keys = [
|
|
||||||
("A", "accept all intercepted flows"),
|
|
||||||
("a", "accept this intercepted flow"),
|
|
||||||
("b", "save request/response body"),
|
|
||||||
("C", "export flow to clipboard"),
|
|
||||||
("D", "duplicate flow"),
|
|
||||||
("d", "delete flow"),
|
|
||||||
("e", "edit request/response"),
|
|
||||||
("f", "load full body data"),
|
|
||||||
("m", "change body display mode for this entity\n(default mode can be changed in the options)"),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("automatic", "a") +
|
|
||||||
[("text", ": automatic detection")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("hex", "e") +
|
|
||||||
[("text", ": Hex")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("html", "h") +
|
|
||||||
[("text", ": HTML")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("image", "i") +
|
|
||||||
[("text", ": Image")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("javascript", "j") +
|
|
||||||
[("text", ": JavaScript")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("json", "s") +
|
|
||||||
[("text", ": JSON")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("urlencoded", "u") +
|
|
||||||
[("text", ": URL-encoded data")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("raw", "r") +
|
|
||||||
[("text", ": raw data")]
|
|
||||||
),
|
|
||||||
(None,
|
|
||||||
common.highlight_key("xml", "x") +
|
|
||||||
[("text", ": XML")]
|
|
||||||
),
|
|
||||||
("E", "export flow to file"),
|
|
||||||
("r", "replay request"),
|
|
||||||
("V", "revert changes to request"),
|
|
||||||
("v", "view body in external viewer"),
|
|
||||||
("w", "save all flows matching current view filter"),
|
|
||||||
("W", "save this flow"),
|
|
||||||
("x", "delete body"),
|
|
||||||
("z", "encode/decode a request/response"),
|
|
||||||
("tab", "next tab"),
|
|
||||||
("h, l", "previous tab, next tab"),
|
|
||||||
("space", "next flow"),
|
|
||||||
("|", "run script on this flow"),
|
|
||||||
("/", "search (case sensitive)"),
|
|
||||||
("n", "repeat search forward"),
|
|
||||||
("N", "repeat search backwards"),
|
|
||||||
]
|
|
||||||
text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
help_context = _mkhelp()
|
|
||||||
|
|
||||||
footer = [
|
|
||||||
('heading_key', "?"), ":help ",
|
|
||||||
('heading_key', "q"), ":back ",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class FlowViewHeader(urwid.WidgetWrap):
|
class FlowViewHeader(urwid.WidgetWrap):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -274,7 +199,7 @@ class FlowDetails(tabs.Tabs):
|
|||||||
return self._w.keypress(size, key)
|
return self._w.keypress(size, key)
|
||||||
|
|
||||||
|
|
||||||
class FlowView(urwid.Frame):
|
class FlowView(urwid.Frame, layoutwidget.LayoutWidget):
|
||||||
keyctx = "flowview"
|
keyctx = "flowview"
|
||||||
title = "Flow Details"
|
title = "Flow Details"
|
||||||
|
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import abc
|
import abc
|
||||||
import copy
|
import copy
|
||||||
from typing import Any
|
import os
|
||||||
from typing import Callable
|
import typing
|
||||||
from typing import Container
|
|
||||||
from typing import Iterable
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Sequence
|
|
||||||
from typing import Tuple
|
|
||||||
from typing import Set # noqa
|
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
|
from mitmproxy.utils import strutils
|
||||||
|
from mitmproxy import exceptions
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import common
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
|
from mitmproxy.tools.console import layoutwidget
|
||||||
import mitmproxy.tools.console.master # noqa
|
import mitmproxy.tools.console.master # noqa
|
||||||
|
|
||||||
FOOTER = [
|
FOOTER = [
|
||||||
@ -23,6 +20,21 @@ FOOTER_EDITING = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(filename: str, escaped: bool) -> typing.AnyStr:
|
||||||
|
filename = os.path.expanduser(filename)
|
||||||
|
try:
|
||||||
|
with open(filename, "r" if escaped else "rb") as f:
|
||||||
|
d = f.read()
|
||||||
|
except IOError as v:
|
||||||
|
raise exceptions.CommandError(v)
|
||||||
|
if escaped:
|
||||||
|
try:
|
||||||
|
d = strutils.escaped_str_to_bytes(d)
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.CommandError("Invalid Python-style string encoding.")
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
class Cell(urwid.WidgetWrap):
|
class Cell(urwid.WidgetWrap):
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
"""
|
"""
|
||||||
@ -50,27 +62,28 @@ class Column(metaclass=abc.ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def blank(self) -> Any:
|
def blank(self) -> typing.Any:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def keypress(self, key: str, editor: "GridEditor") -> Optional[str]:
|
def keypress(self, key: str, editor: "GridEditor") -> typing.Optional[str]:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
class GridRow(urwid.WidgetWrap):
|
class GridRow(urwid.WidgetWrap):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
focused: Optional[int],
|
focused: typing.Optional[int],
|
||||||
editing: bool,
|
editing: bool,
|
||||||
editor: "GridEditor",
|
editor: "GridEditor",
|
||||||
values: Tuple[Iterable[bytes], Container[int]]
|
values: typing.Tuple[typing.Iterable[bytes], typing.Container[int]]
|
||||||
) -> None:
|
) -> None:
|
||||||
self.focused = focused
|
self.focused = focused
|
||||||
self.editor = editor
|
self.editor = editor
|
||||||
self.edit_col = None # type: Optional[Cell]
|
self.edit_col = None # type: typing.Optional[Cell]
|
||||||
|
|
||||||
errors = values[1]
|
errors = values[1]
|
||||||
self.fields = [] # type: Sequence[Any]
|
self.fields = [] # type: typing.Sequence[typing.Any]
|
||||||
for i, v in enumerate(values[0]):
|
for i, v in enumerate(values[0]):
|
||||||
if focused == i and editing:
|
if focused == i and editing:
|
||||||
self.edit_col = self.editor.columns[i].Edit(v)
|
self.edit_col = self.editor.columns[i].Edit(v)
|
||||||
@ -116,14 +129,14 @@ class GridWalker(urwid.ListWalker):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
lst: Iterable[list],
|
lst: typing.Iterable[list],
|
||||||
editor: "GridEditor"
|
editor: "GridEditor"
|
||||||
) -> None:
|
) -> None:
|
||||||
self.lst = [(i, set()) for i in lst] # type: Sequence[Tuple[Any, Set]]
|
self.lst = [(i, set()) for i in lst] # type: typing.Sequence[typing.Tuple[typing.Any, typing.Set]]
|
||||||
self.editor = editor
|
self.editor = editor
|
||||||
self.focus = 0
|
self.focus = 0
|
||||||
self.focus_col = 0
|
self.focus_col = 0
|
||||||
self.edit_row = None # type: Optional[GridRow]
|
self.edit_row = None # type: typing.Optional[GridRow]
|
||||||
|
|
||||||
def _modified(self):
|
def _modified(self):
|
||||||
self.editor.show_empty_msg()
|
self.editor.show_empty_msg()
|
||||||
@ -253,14 +266,16 @@ FIRST_WIDTH_MIN = 20
|
|||||||
|
|
||||||
|
|
||||||
class BaseGridEditor(urwid.WidgetWrap):
|
class BaseGridEditor(urwid.WidgetWrap):
|
||||||
|
title = ""
|
||||||
|
keyctx = "grideditor"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
master: "mitmproxy.tools.console.master.ConsoleMaster",
|
master: "mitmproxy.tools.console.master.ConsoleMaster",
|
||||||
title,
|
title,
|
||||||
columns,
|
columns,
|
||||||
value: Any,
|
value: typing.Any,
|
||||||
callback: Callable[..., None],
|
callback: typing.Callable[..., None],
|
||||||
*cb_args,
|
*cb_args,
|
||||||
**cb_kwargs
|
**cb_kwargs
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -280,36 +295,30 @@ class BaseGridEditor(urwid.WidgetWrap):
|
|||||||
first_width = max(len(r), first_width)
|
first_width = max(len(r), first_width)
|
||||||
self.first_width = min(first_width, FIRST_WIDTH_MAX)
|
self.first_width = min(first_width, FIRST_WIDTH_MAX)
|
||||||
|
|
||||||
title = None
|
h = None
|
||||||
if self.title:
|
if any(col.heading for col in self.columns):
|
||||||
title = urwid.Text(self.title)
|
headings = []
|
||||||
title = urwid.Padding(title, align="left", width=("relative", 100))
|
for i, col in enumerate(self.columns):
|
||||||
title = urwid.AttrWrap(title, "heading")
|
c = urwid.Text(col.heading)
|
||||||
|
if i == 0 and len(self.columns) > 1:
|
||||||
headings = []
|
headings.append(("fixed", first_width + 2, c))
|
||||||
for i, col in enumerate(self.columns):
|
else:
|
||||||
c = urwid.Text(col.heading)
|
headings.append(c)
|
||||||
if i == 0 and len(self.columns) > 1:
|
h = urwid.Columns(
|
||||||
headings.append(("fixed", first_width + 2, c))
|
headings,
|
||||||
else:
|
dividechars=2
|
||||||
headings.append(c)
|
)
|
||||||
h = urwid.Columns(
|
h = urwid.AttrWrap(h, "heading")
|
||||||
headings,
|
|
||||||
dividechars=2
|
|
||||||
)
|
|
||||||
h = urwid.AttrWrap(h, "heading")
|
|
||||||
|
|
||||||
self.walker = GridWalker(self.value, self)
|
self.walker = GridWalker(self.value, self)
|
||||||
self.lb = GridListBox(self.walker)
|
self.lb = GridListBox(self.walker)
|
||||||
w = urwid.Frame(
|
w = urwid.Frame(self.lb, header=h)
|
||||||
self.lb,
|
|
||||||
header=urwid.Pile([title, h]) if title else None
|
|
||||||
)
|
|
||||||
super().__init__(w)
|
super().__init__(w)
|
||||||
signals.footer_help.send(self, helptext="")
|
signals.footer_help.send(self, helptext="")
|
||||||
self.show_empty_msg()
|
self.show_empty_msg()
|
||||||
|
|
||||||
def view_popping(self):
|
def layout_popping(self):
|
||||||
res = []
|
res = []
|
||||||
for i in self.walker.lst:
|
for i in self.walker.lst:
|
||||||
if not i[1] and any([x for x in i[0]]):
|
if not i[1] and any([x for x in i[0]]):
|
||||||
@ -323,9 +332,9 @@ class BaseGridEditor(urwid.WidgetWrap):
|
|||||||
self._w.set_footer(
|
self._w.set_footer(
|
||||||
urwid.Text(
|
urwid.Text(
|
||||||
[
|
[
|
||||||
("highlight", "No values. Press "),
|
("highlight", "No values - you should add some. Press "),
|
||||||
("key", "a"),
|
("key", "?"),
|
||||||
("highlight", " to add some."),
|
("highlight", " for help."),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -355,31 +364,23 @@ class BaseGridEditor(urwid.WidgetWrap):
|
|||||||
self.walker.left()
|
self.walker.left()
|
||||||
elif key == "right":
|
elif key == "right":
|
||||||
self.walker.right()
|
self.walker.right()
|
||||||
elif key == "tab":
|
|
||||||
self.walker.tab_next()
|
|
||||||
elif key == "a":
|
|
||||||
self.walker.add()
|
|
||||||
elif key == "A":
|
|
||||||
self.walker.insert()
|
|
||||||
elif key == "d":
|
|
||||||
self.walker.delete_focus()
|
|
||||||
elif column.keypress(key, self) and not self.handle_key(key):
|
elif column.keypress(key, self) and not self.handle_key(key):
|
||||||
return self._w.keypress(size, key)
|
return self._w.keypress(size, key)
|
||||||
|
|
||||||
def data_out(self, data: Sequence[list]) -> Any:
|
def data_out(self, data: typing.Sequence[list]) -> typing.Any:
|
||||||
"""
|
"""
|
||||||
Called on raw list data, before data is returned through the
|
Called on raw list data, before data is returned through the
|
||||||
callback.
|
callback.
|
||||||
"""
|
"""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def data_in(self, data: Any) -> Iterable[list]:
|
def data_in(self, data: typing.Any) -> typing.Iterable[list]:
|
||||||
"""
|
"""
|
||||||
Called to prepare provided data.
|
Called to prepare provided data.
|
||||||
"""
|
"""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def is_error(self, col: int, val: Any) -> Optional[str]:
|
def is_error(self, col: int, val: typing.Any) -> typing.Optional[str]:
|
||||||
"""
|
"""
|
||||||
Return None, or a string error message.
|
Return None, or a string error message.
|
||||||
"""
|
"""
|
||||||
@ -417,31 +418,57 @@ class BaseGridEditor(urwid.WidgetWrap):
|
|||||||
)
|
)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
def cmd_next(self):
|
||||||
|
self.walker.tab_next()
|
||||||
|
|
||||||
class GridEditor(urwid.WidgetWrap):
|
def cmd_add(self):
|
||||||
|
self.walker.add()
|
||||||
|
|
||||||
|
def cmd_insert(self):
|
||||||
|
self.walker.insert()
|
||||||
|
|
||||||
|
def cmd_delete(self):
|
||||||
|
self.walker.delete_focus()
|
||||||
|
|
||||||
|
def cmd_read_file(self, path):
|
||||||
|
self.walker.set_current_value(read_file(path, False))
|
||||||
|
|
||||||
|
def cmd_read_file_escaped(self, path):
|
||||||
|
self.walker.set_current_value(read_file(path, True))
|
||||||
|
|
||||||
|
def cmd_spawn_editor(self):
|
||||||
|
o = self.walker.get_current_value()
|
||||||
|
if o is not None:
|
||||||
|
n = self.master.spawn_editor(o)
|
||||||
|
n = strutils.clean_hanging_newline(n)
|
||||||
|
self.walker.set_current_value(n)
|
||||||
|
|
||||||
|
|
||||||
|
class GridEditor(BaseGridEditor):
|
||||||
title = None # type: str
|
title = None # type: str
|
||||||
columns = None # type: Sequence[Column]
|
columns = None # type: typing.Sequence[Column]
|
||||||
|
keyctx = "grideditor"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
master: "mitmproxy.tools.console.master.ConsoleMaster",
|
master: "mitmproxy.tools.console.master.ConsoleMaster",
|
||||||
value: Any,
|
value: typing.Any,
|
||||||
callback: Callable[..., None],
|
callback: typing.Callable[..., None],
|
||||||
*cb_args,
|
*cb_args,
|
||||||
**cb_kwargs
|
**cb_kwargs
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master,
|
master,
|
||||||
value,
|
|
||||||
self.title,
|
self.title,
|
||||||
self.columns,
|
self.columns,
|
||||||
|
value,
|
||||||
callback,
|
callback,
|
||||||
*cb_args,
|
*cb_args,
|
||||||
**cb_kwargs
|
**cb_kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FocusEditor(urwid.WidgetWrap):
|
class FocusEditor(urwid.WidgetWrap, layoutwidget.LayoutWidget):
|
||||||
"""
|
"""
|
||||||
A specialised GridEditor that edits the current focused flow.
|
A specialised GridEditor that edits the current focused flow.
|
||||||
"""
|
"""
|
||||||
@ -451,27 +478,11 @@ class FocusEditor(urwid.WidgetWrap):
|
|||||||
self.master = master
|
self.master = master
|
||||||
self.focus_changed()
|
self.focus_changed()
|
||||||
|
|
||||||
def focus_changed(self):
|
|
||||||
if self.master.view.focus.flow:
|
|
||||||
self._w = BaseGridEditor(
|
|
||||||
self.master.view.focus.flow,
|
|
||||||
self.title,
|
|
||||||
self.columns,
|
|
||||||
self.get_data(self.master.view.focus.flow),
|
|
||||||
self.set_data_update,
|
|
||||||
self.master.view.focus.flow,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._w = urwid.Pile([])
|
|
||||||
|
|
||||||
def call(self, v, name, *args, **kwargs):
|
def call(self, v, name, *args, **kwargs):
|
||||||
f = getattr(v, name, None)
|
f = getattr(v, name, None)
|
||||||
if f:
|
if f:
|
||||||
f(*args, **kwargs)
|
f(*args, **kwargs)
|
||||||
|
|
||||||
def view_popping(self):
|
|
||||||
self.call(self._w, "view_popping")
|
|
||||||
|
|
||||||
def get_data(self, flow):
|
def get_data(self, flow):
|
||||||
"""
|
"""
|
||||||
Retrieve the data to edit from the current flow.
|
Retrieve the data to edit from the current flow.
|
||||||
@ -487,3 +498,22 @@ class FocusEditor(urwid.WidgetWrap):
|
|||||||
def set_data_update(self, vals, flow):
|
def set_data_update(self, vals, flow):
|
||||||
self.set_data(vals, flow)
|
self.set_data(vals, flow)
|
||||||
signals.flow_change.send(self, flow = flow)
|
signals.flow_change.send(self, flow = flow)
|
||||||
|
|
||||||
|
def key_responder(self):
|
||||||
|
return self._w
|
||||||
|
|
||||||
|
def layout_popping(self):
|
||||||
|
self.call(self._w, "layout_popping")
|
||||||
|
|
||||||
|
def focus_changed(self):
|
||||||
|
if self.master.view.focus.flow:
|
||||||
|
self._w = BaseGridEditor(
|
||||||
|
self.master,
|
||||||
|
self.title,
|
||||||
|
self.columns,
|
||||||
|
self.get_data(self.master.view.focus.flow),
|
||||||
|
self.set_data_update,
|
||||||
|
self.master.view.focus.flow,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._w = urwid.Pile([])
|
||||||
|
@ -1,34 +1,9 @@
|
|||||||
import os
|
|
||||||
from typing import Callable, Optional
|
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
from mitmproxy.tools.console.grideditor import base
|
from mitmproxy.tools.console.grideditor import base
|
||||||
from mitmproxy.utils import strutils
|
from mitmproxy.utils import strutils
|
||||||
|
|
||||||
|
|
||||||
def read_file(filename: str, callback: Callable[..., None], escaped: bool) -> Optional[str]:
|
|
||||||
if not filename:
|
|
||||||
return None
|
|
||||||
|
|
||||||
filename = os.path.expanduser(filename)
|
|
||||||
try:
|
|
||||||
with open(filename, "r" if escaped else "rb") as f:
|
|
||||||
d = f.read()
|
|
||||||
except IOError as v:
|
|
||||||
return str(v)
|
|
||||||
|
|
||||||
if escaped:
|
|
||||||
try:
|
|
||||||
d = strutils.escaped_str_to_bytes(d)
|
|
||||||
except ValueError:
|
|
||||||
return "Invalid Python-style string encoding."
|
|
||||||
# TODO: Refactor the status_prompt_path signal so that we
|
|
||||||
# can raise exceptions here and return the content instead.
|
|
||||||
callback(d)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Column(base.Column):
|
class Column(base.Column):
|
||||||
def Display(self, data):
|
def Display(self, data):
|
||||||
return Display(data)
|
return Display(data)
|
||||||
@ -40,29 +15,7 @@ class Column(base.Column):
|
|||||||
return b""
|
return b""
|
||||||
|
|
||||||
def keypress(self, key, editor):
|
def keypress(self, key, editor):
|
||||||
if key == "r":
|
if key in ["enter"]:
|
||||||
if editor.walker.get_current_value() is not None:
|
|
||||||
signals.status_prompt_path.send(
|
|
||||||
self,
|
|
||||||
prompt="Read file",
|
|
||||||
callback=read_file,
|
|
||||||
args=(editor.walker.set_current_value, True)
|
|
||||||
)
|
|
||||||
elif key == "R":
|
|
||||||
if editor.walker.get_current_value() is not None:
|
|
||||||
signals.status_prompt_path.send(
|
|
||||||
self,
|
|
||||||
prompt="Read unescaped file",
|
|
||||||
callback=read_file,
|
|
||||||
args=(editor.walker.set_current_value, False)
|
|
||||||
)
|
|
||||||
elif key == "e":
|
|
||||||
o = editor.walker.get_current_value()
|
|
||||||
if o is not None:
|
|
||||||
n = editor.master.spawn_editor(o)
|
|
||||||
n = strutils.clean_hanging_newline(n)
|
|
||||||
editor.walker.set_current_value(n)
|
|
||||||
elif key in ["enter"]:
|
|
||||||
editor.walker.start_edit()
|
editor.walker.start_edit()
|
||||||
else:
|
else:
|
||||||
return key
|
return key
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy import flowfilter
|
|
||||||
from mitmproxy.addons import script
|
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import common
|
||||||
|
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_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
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
from mitmproxy.net.http import user_agents
|
|
||||||
from mitmproxy.net.http import Headers
|
from mitmproxy.net.http import Headers
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +38,6 @@ class HeaderEditor(base.FocusEditor):
|
|||||||
urwid.Text([("text", "Special keys:\n")])
|
urwid.Text([("text", "Special keys:\n")])
|
||||||
]
|
]
|
||||||
keys = [
|
keys = [
|
||||||
("U", "add User-Agent header"),
|
|
||||||
]
|
]
|
||||||
text.extend(
|
text.extend(
|
||||||
common.format_keyvals(keys, key="key", val="text", indent=4)
|
common.format_keyvals(keys, key="key", val="text", indent=4)
|
||||||
@ -50,25 +46,6 @@ class HeaderEditor(base.FocusEditor):
|
|||||||
text.extend(h)
|
text.extend(h)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def set_user_agent(self, k):
|
|
||||||
ua = user_agents.get_by_shortcut(k)
|
|
||||||
if ua:
|
|
||||||
self.walker.add_value(
|
|
||||||
[
|
|
||||||
b"User-Agent",
|
|
||||||
ua[2].encode()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_key(self, key):
|
|
||||||
if key == "U":
|
|
||||||
signals.status_prompt_onekey.send(
|
|
||||||
prompt="Add User-Agent header:",
|
|
||||||
keys=[(i[0], i[1]) for i in user_agents.UASTRINGS],
|
|
||||||
callback=self.set_user_agent,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class RequestHeaderEditor(HeaderEditor):
|
class RequestHeaderEditor(HeaderEditor):
|
||||||
title = "Edit Request Headers"
|
title = "Edit Request Headers"
|
||||||
@ -104,56 +81,6 @@ class RequestFormEditor(base.FocusEditor):
|
|||||||
flow.request.urlencoded_form = vals
|
flow.request.urlencoded_form = vals
|
||||||
|
|
||||||
|
|
||||||
class SetHeadersEditor(base.GridEditor):
|
|
||||||
title = "Editing header set patterns"
|
|
||||||
columns = [
|
|
||||||
col_text.Column("Filter"),
|
|
||||||
col_text.Column("Header"),
|
|
||||||
col_text.Column("Value"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def is_error(self, col, val):
|
|
||||||
if col == 0:
|
|
||||||
if not flowfilter.parse(val):
|
|
||||||
return "Invalid filter specification"
|
|
||||||
return False
|
|
||||||
|
|
||||||
def make_help(self):
|
|
||||||
h = super().make_help()
|
|
||||||
text = [
|
|
||||||
urwid.Text([("text", "Special keys:\n")])
|
|
||||||
]
|
|
||||||
keys = [
|
|
||||||
("U", "add User-Agent header"),
|
|
||||||
]
|
|
||||||
text.extend(
|
|
||||||
common.format_keyvals(keys, key="key", val="text", indent=4)
|
|
||||||
)
|
|
||||||
text.append(urwid.Text([("text", "\n")]))
|
|
||||||
text.extend(h)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def set_user_agent(self, k):
|
|
||||||
ua = user_agents.get_by_shortcut(k)
|
|
||||||
if ua:
|
|
||||||
self.walker.add_value(
|
|
||||||
[
|
|
||||||
".*",
|
|
||||||
b"User-Agent",
|
|
||||||
ua[2].encode()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_key(self, key):
|
|
||||||
if key == "U":
|
|
||||||
signals.status_prompt_onekey.send(
|
|
||||||
prompt="Add User-Agent header:",
|
|
||||||
keys=[(i[0], i[1]) for i in user_agents.UASTRINGS],
|
|
||||||
callback=self.set_user_agent,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class PathEditor(base.FocusEditor):
|
class PathEditor(base.FocusEditor):
|
||||||
# TODO: Next row on enter?
|
# TODO: Next row on enter?
|
||||||
|
|
||||||
@ -175,38 +102,6 @@ class PathEditor(base.FocusEditor):
|
|||||||
flow.request.path_components = self.data_out(vals)
|
flow.request.path_components = self.data_out(vals)
|
||||||
|
|
||||||
|
|
||||||
class ScriptEditor(base.GridEditor):
|
|
||||||
title = "Editing scripts"
|
|
||||||
columns = [
|
|
||||||
col_text.Column("Command"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def is_error(self, col, val):
|
|
||||||
try:
|
|
||||||
script.parse_command(val)
|
|
||||||
except exceptions.OptionsError as e:
|
|
||||||
return str(e)
|
|
||||||
|
|
||||||
|
|
||||||
class HostPatternEditor(base.GridEditor):
|
|
||||||
title = "Editing host patterns"
|
|
||||||
columns = [
|
|
||||||
col_text.Column("Regex (matched on hostname:port / ip:port)")
|
|
||||||
]
|
|
||||||
|
|
||||||
def is_error(self, col, val):
|
|
||||||
try:
|
|
||||||
re.compile(val, re.IGNORECASE)
|
|
||||||
except re.error as e:
|
|
||||||
return "Invalid regex: %s" % str(e)
|
|
||||||
|
|
||||||
def data_in(self, data):
|
|
||||||
return [[i] for i in data]
|
|
||||||
|
|
||||||
def data_out(self, data):
|
|
||||||
return [i[0] for i in data]
|
|
||||||
|
|
||||||
|
|
||||||
class CookieEditor(base.FocusEditor):
|
class CookieEditor(base.FocusEditor):
|
||||||
title = "Edit Cookies"
|
title = "Edit Cookies"
|
||||||
columns = [
|
columns = [
|
||||||
@ -273,7 +168,7 @@ class SetCookieEditor(base.FocusEditor):
|
|||||||
flow.response.cookies = self.data_out(vals)
|
flow.response.cookies = self.data_out(vals)
|
||||||
|
|
||||||
|
|
||||||
class OptionsEditor(base.GridEditor):
|
class OptionsEditor(base.GridEditor, layoutwidget.LayoutWidget):
|
||||||
title = None # type: str
|
title = None # type: str
|
||||||
columns = [
|
columns = [
|
||||||
col_text.Column("")
|
col_text.Column("")
|
||||||
|
@ -1,66 +1,67 @@
|
|||||||
import platform
|
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from mitmproxy import flowfilter
|
from mitmproxy import flowfilter
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import common
|
||||||
|
from mitmproxy.tools.console import layoutwidget
|
||||||
from mitmproxy import version
|
from mitmproxy.tools.console import tabs
|
||||||
|
|
||||||
footer = [
|
|
||||||
("heading", 'mitmproxy {} (Python {}) '.format(version.VERSION, platform.python_version())),
|
|
||||||
('heading_key', "q"), ":back ",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class HelpView(urwid.ListBox):
|
class HelpView(tabs.Tabs, layoutwidget.LayoutWidget):
|
||||||
title = "Help"
|
title = "Help"
|
||||||
keyctx = "help"
|
keyctx = "help"
|
||||||
|
|
||||||
def __init__(self, help_context):
|
def __init__(self, master):
|
||||||
self.help_context = help_context or []
|
self.master = master
|
||||||
urwid.ListBox.__init__(
|
self.helpctx = ""
|
||||||
self,
|
super().__init__(
|
||||||
self.helptext()
|
[
|
||||||
|
[self.keybindings_title, self.keybindings],
|
||||||
|
[self.filtexp_title, self.filtexp],
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def helptext(self):
|
def keybindings_title(self):
|
||||||
|
return "Key Bindings"
|
||||||
|
|
||||||
|
def format_keys(self, binds):
|
||||||
|
kvs = []
|
||||||
|
for b in binds:
|
||||||
|
k = b.key
|
||||||
|
if b.key == " ":
|
||||||
|
k = "space"
|
||||||
|
kvs.append((k, b.command))
|
||||||
|
return common.format_keyvals(kvs)
|
||||||
|
|
||||||
|
def keybindings(self):
|
||||||
|
text = [
|
||||||
|
urwid.Text(
|
||||||
|
[
|
||||||
|
("title", "Keybindings for this view")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if self.helpctx:
|
||||||
|
text.extend(self.format_keys(self.master.keymap.list(self.helpctx)))
|
||||||
|
|
||||||
|
text.append(
|
||||||
|
urwid.Text(
|
||||||
|
[
|
||||||
|
"\n",
|
||||||
|
("title", "Global Keybindings"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
text.extend(self.format_keys(self.master.keymap.list("global")))
|
||||||
|
|
||||||
|
return urwid.ListBox(text)
|
||||||
|
|
||||||
|
def filtexp_title(self):
|
||||||
|
return "Filter Expressions"
|
||||||
|
|
||||||
|
def filtexp(self):
|
||||||
text = []
|
text = []
|
||||||
text.append(urwid.Text([("head", "This view:\n")]))
|
|
||||||
text.extend(self.help_context)
|
|
||||||
|
|
||||||
text.append(urwid.Text([("head", "\n\nMovement:\n")]))
|
|
||||||
keys = [
|
|
||||||
("j, k", "down, up"),
|
|
||||||
("h, l", "left, right (in some contexts)"),
|
|
||||||
("g, G", "go to beginning, end"),
|
|
||||||
("space", "page down"),
|
|
||||||
("pg up/down", "page up/down"),
|
|
||||||
("ctrl+b/ctrl+f", "page up/down"),
|
|
||||||
("arrows", "up, down, left, right"),
|
|
||||||
]
|
|
||||||
text.extend(
|
|
||||||
common.format_keyvals(
|
|
||||||
keys,
|
|
||||||
key="key",
|
|
||||||
val="text",
|
|
||||||
indent=4))
|
|
||||||
|
|
||||||
text.append(urwid.Text([("head", "\n\nGlobal keys:\n")]))
|
|
||||||
keys = [
|
|
||||||
("i", "set interception pattern"),
|
|
||||||
("O", "options"),
|
|
||||||
("q", "quit / return to previous page"),
|
|
||||||
("Q", "quit without confirm prompt"),
|
|
||||||
("R", "replay of requests/responses from file"),
|
|
||||||
]
|
|
||||||
text.extend(
|
|
||||||
common.format_keyvals(keys, key="key", val="text", indent=4)
|
|
||||||
)
|
|
||||||
|
|
||||||
text.append(urwid.Text([("head", "\n\nFilter expressions:\n")]))
|
|
||||||
text.extend(common.format_keyvals(flowfilter.help, key="key", val="text", indent=4))
|
text.extend(common.format_keyvals(flowfilter.help, key="key", val="text", indent=4))
|
||||||
|
|
||||||
text.append(
|
text.append(
|
||||||
urwid.Text(
|
urwid.Text(
|
||||||
[
|
[
|
||||||
@ -82,11 +83,11 @@ class HelpView(urwid.ListBox):
|
|||||||
text.extend(
|
text.extend(
|
||||||
common.format_keyvals(examples, key="key", val="text", indent=4)
|
common.format_keyvals(examples, key="key", val="text", indent=4)
|
||||||
)
|
)
|
||||||
return text
|
return urwid.ListBox(text)
|
||||||
|
|
||||||
def keypress(self, size, key):
|
def layout_pushed(self, prev):
|
||||||
if key == "m_start":
|
"""
|
||||||
self.set_focus(0)
|
We are just about to push a window onto the stack.
|
||||||
elif key == "m_end":
|
"""
|
||||||
self.set_focus(len(self.body.contents))
|
self.helpctx = prev.keyctx
|
||||||
return urwid.ListBox.keypress(self, size, key)
|
self.show()
|
||||||
|
@ -49,6 +49,11 @@ class Keymap:
|
|||||||
return self.keys[context].get(key, None)
|
return self.keys[context].get(key, None)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def list(self, context: str) -> typing.Sequence[Binding]:
|
||||||
|
b = [b for b in self.bindings if context in b.contexts]
|
||||||
|
b.sort(key=lambda x: x.key)
|
||||||
|
return b
|
||||||
|
|
||||||
def handle(self, context: str, key: str) -> typing.Optional[str]:
|
def handle(self, context: str, key: str) -> typing.Optional[str]:
|
||||||
"""
|
"""
|
||||||
Returns the key if it has not been handled, or None.
|
Returns the key if it has not been handled, or None.
|
||||||
|
42
mitmproxy/tools/console/layoutwidget.py
Normal file
42
mitmproxy/tools/console/layoutwidget.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class LayoutWidget:
|
||||||
|
"""
|
||||||
|
All top-level layout widgets and all widgets that may be set in an
|
||||||
|
overlay must comply with this API.
|
||||||
|
"""
|
||||||
|
# Title is only required for windows, not overlay components
|
||||||
|
title = ""
|
||||||
|
keyctx = ""
|
||||||
|
|
||||||
|
def key_responder(self):
|
||||||
|
"""
|
||||||
|
Returns the object responding to key input. Usually self, but may be
|
||||||
|
a wrapped object.
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def focus_changed(self):
|
||||||
|
"""
|
||||||
|
The view focus has changed. Layout objects should implement the API
|
||||||
|
rather than directly subscribing to events.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def view_changed(self):
|
||||||
|
"""
|
||||||
|
The view list has changed.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def layout_popping(self):
|
||||||
|
"""
|
||||||
|
We are just about to pop a window off the stack, or exit an overlay.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def layout_pushed(self, prev):
|
||||||
|
"""
|
||||||
|
We have just pushed a window onto the stack.
|
||||||
|
"""
|
||||||
|
pass
|
@ -322,6 +322,62 @@ class ConsoleAddon:
|
|||||||
"console.command flow.set @focus %s " % part
|
"console.command flow.set @focus %s " % part
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _grideditor(self):
|
||||||
|
gewidget = self.master.window.current("grideditor")
|
||||||
|
if not gewidget:
|
||||||
|
raise exceptions.CommandError("Not in a grideditor.")
|
||||||
|
return gewidget.key_responder()
|
||||||
|
|
||||||
|
@command.command("console.grideditor.add")
|
||||||
|
def grideditor_add(self) -> None:
|
||||||
|
"""
|
||||||
|
Add a row after the cursor.
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_add()
|
||||||
|
|
||||||
|
@command.command("console.grideditor.insert")
|
||||||
|
def grideditor_insert(self) -> None:
|
||||||
|
"""
|
||||||
|
Insert a row before the cursor.
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_insert()
|
||||||
|
|
||||||
|
@command.command("console.grideditor.next")
|
||||||
|
def grideditor_next(self) -> None:
|
||||||
|
"""
|
||||||
|
Go to next cell.
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_next()
|
||||||
|
|
||||||
|
@command.command("console.grideditor.delete")
|
||||||
|
def grideditor_delete(self) -> None:
|
||||||
|
"""
|
||||||
|
Delete row
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_delete()
|
||||||
|
|
||||||
|
@command.command("console.grideditor.readfile")
|
||||||
|
def grideditor_readfile(self, path: str) -> None:
|
||||||
|
"""
|
||||||
|
Read a file into the currrent cell.
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_read_file(path)
|
||||||
|
|
||||||
|
@command.command("console.grideditor.readfile_escaped")
|
||||||
|
def grideditor_readfile_escaped(self, path: str) -> None:
|
||||||
|
"""
|
||||||
|
Read a file containing a Python-style escaped stringinto the
|
||||||
|
currrent cell.
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_read_file_escaped(path)
|
||||||
|
|
||||||
|
@command.command("console.grideditor.editor")
|
||||||
|
def grideditor_editor(self) -> None:
|
||||||
|
"""
|
||||||
|
Spawn an external editor on the current cell.
|
||||||
|
"""
|
||||||
|
self._grideditor().cmd_spawn_editor()
|
||||||
|
|
||||||
@command.command("console.flowview.mode.set")
|
@command.command("console.flowview.mode.set")
|
||||||
def flowview_mode_set(self) -> None:
|
def flowview_mode_set(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -349,7 +405,7 @@ class ConsoleAddon:
|
|||||||
"""
|
"""
|
||||||
Get the display mode for the current flow view.
|
Get the display mode for the current flow view.
|
||||||
"""
|
"""
|
||||||
fv = self.master.window.any("flowview")
|
fv = self.master.window.current_window("flowview")
|
||||||
if not fv:
|
if not fv:
|
||||||
raise exceptions.CommandError("Not viewing a flow.")
|
raise exceptions.CommandError("Not viewing a flow.")
|
||||||
idx = fv.body.tab_offset
|
idx = fv.body.tab_offset
|
||||||
@ -476,6 +532,14 @@ def default_keymap(km):
|
|||||||
km.add("D", "options.reset", ["options"])
|
km.add("D", "options.reset", ["options"])
|
||||||
km.add("d", "console.options.reset.current", ["options"])
|
km.add("d", "console.options.reset.current", ["options"])
|
||||||
|
|
||||||
|
km.add("a", "console.grideditor.add", ["grideditor"])
|
||||||
|
km.add("A", "console.grideditor.insert", ["grideditor"])
|
||||||
|
km.add("tab", "console.grideditor.next", ["grideditor"])
|
||||||
|
km.add("d", "console.grideditor.delete", ["grideditor"])
|
||||||
|
km.add("r", "console.command console.grideditor.readfile", ["grideditor"])
|
||||||
|
km.add("R", "console.command console.grideditor.readfile_escaped", ["grideditor"])
|
||||||
|
km.add("e", "console.grideditor.editor", ["grideditor"])
|
||||||
|
|
||||||
|
|
||||||
class ConsoleMaster(master.Master):
|
class ConsoleMaster(master.Master):
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from typing import Optional, Sequence
|
|||||||
|
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy import optmanager
|
from mitmproxy import optmanager
|
||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import layoutwidget
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
from mitmproxy.tools.console import overlay
|
from mitmproxy.tools.console import overlay
|
||||||
|
|
||||||
@ -20,28 +20,6 @@ def can_edit_inplace(opt):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
footer = [
|
|
||||||
('heading_key', "enter"), ":edit ",
|
|
||||||
('heading_key', "?"), ":help ",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _mkhelp():
|
|
||||||
text = []
|
|
||||||
keys = [
|
|
||||||
("enter", "edit option"),
|
|
||||||
("D", "reset all to defaults"),
|
|
||||||
("d", "reset this option to default"),
|
|
||||||
("l", "load options from file"),
|
|
||||||
("w", "save options to file"),
|
|
||||||
]
|
|
||||||
text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
help_context = _mkhelp()
|
|
||||||
|
|
||||||
|
|
||||||
def fcol(s, width, attr):
|
def fcol(s, width, attr):
|
||||||
s = str(s)
|
s = str(s)
|
||||||
return (
|
return (
|
||||||
@ -263,7 +241,7 @@ class OptionHelp(urwid.Frame):
|
|||||||
self.set_body(self.widget(txt))
|
self.set_body(self.widget(txt))
|
||||||
|
|
||||||
|
|
||||||
class Options(urwid.Pile):
|
class Options(urwid.Pile, layoutwidget.LayoutWidget):
|
||||||
title = "Options"
|
title = "Options"
|
||||||
keyctx = "options"
|
keyctx = "options"
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@ import urwid
|
|||||||
from mitmproxy.tools.console import common
|
from mitmproxy.tools.console import common
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
from mitmproxy.tools.console import grideditor
|
from mitmproxy.tools.console import grideditor
|
||||||
|
from mitmproxy.tools.console import layoutwidget
|
||||||
|
|
||||||
|
|
||||||
class SimpleOverlay(urwid.Overlay):
|
class SimpleOverlay(urwid.Overlay, layoutwidget.LayoutWidget):
|
||||||
keyctx = "overlay"
|
|
||||||
|
|
||||||
def __init__(self, master, widget, parent, width, valign="middle"):
|
def __init__(self, master, widget, parent, width, valign="middle"):
|
||||||
self.widget = widget
|
self.widget = widget
|
||||||
@ -22,14 +22,21 @@ class SimpleOverlay(urwid.Overlay):
|
|||||||
height="pack"
|
height="pack"
|
||||||
)
|
)
|
||||||
|
|
||||||
def keypress(self, size, key):
|
@property
|
||||||
key = super().keypress(size, key)
|
def keyctx(self):
|
||||||
if key == "esc":
|
return getattr(self.widget, "keyctx")
|
||||||
signals.pop_view_state.send(self)
|
|
||||||
if key == "?":
|
def key_responder(self):
|
||||||
self.master.view_help(self.widget.make_help())
|
return self.widget.key_responder()
|
||||||
else:
|
|
||||||
return key
|
def focus_changed(self):
|
||||||
|
return self.widget.focus_changed()
|
||||||
|
|
||||||
|
def view_changed(self):
|
||||||
|
return self.widget.view_changed()
|
||||||
|
|
||||||
|
def layout_popping(self):
|
||||||
|
return self.widget.layout_popping()
|
||||||
|
|
||||||
|
|
||||||
class Choice(urwid.WidgetWrap):
|
class Choice(urwid.WidgetWrap):
|
||||||
@ -81,7 +88,9 @@ class ChooserListWalker(urwid.ListWalker):
|
|||||||
return self._get(pos, False), pos
|
return self._get(pos, False), pos
|
||||||
|
|
||||||
|
|
||||||
class Chooser(urwid.WidgetWrap):
|
class Chooser(urwid.WidgetWrap, layoutwidget.LayoutWidget):
|
||||||
|
keyctx = "chooser"
|
||||||
|
|
||||||
def __init__(self, master, title, choices, current, callback):
|
def __init__(self, master, title, choices, current, callback):
|
||||||
self.master = master
|
self.master = master
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
@ -122,7 +131,9 @@ class Chooser(urwid.WidgetWrap):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class OptionsOverlay(urwid.WidgetWrap):
|
class OptionsOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget):
|
||||||
|
keyctx = "grideditor"
|
||||||
|
|
||||||
def __init__(self, master, name, vals, vspace):
|
def __init__(self, master, name, vals, vspace):
|
||||||
"""
|
"""
|
||||||
vspace: how much vertical space to keep clear
|
vspace: how much vertical space to keep clear
|
||||||
@ -142,3 +153,9 @@ class OptionsOverlay(urwid.WidgetWrap):
|
|||||||
|
|
||||||
def make_help(self):
|
def make_help(self):
|
||||||
return self.ge.make_help()
|
return self.ge.make_help()
|
||||||
|
|
||||||
|
def key_responder(self):
|
||||||
|
return self.ge.key_responder()
|
||||||
|
|
||||||
|
def layout_popping(self):
|
||||||
|
return self.ge.layout_popping()
|
||||||
|
@ -146,24 +146,18 @@ class StatusBar(urwid.WidgetWrap):
|
|||||||
keyctx = ""
|
keyctx = ""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, master: "mitmproxy.tools.console.master.ConsoleMaster", helptext
|
self, master: "mitmproxy.tools.console.master.ConsoleMaster"
|
||||||
) -> None:
|
) -> None:
|
||||||
self.master = master
|
self.master = master
|
||||||
self.helptext = helptext
|
|
||||||
self.ib = urwid.WidgetWrap(urwid.Text(""))
|
self.ib = urwid.WidgetWrap(urwid.Text(""))
|
||||||
self.ab = ActionBar(self.master)
|
self.ab = ActionBar(self.master)
|
||||||
super().__init__(urwid.Pile([self.ib, self.ab]))
|
super().__init__(urwid.Pile([self.ib, self.ab]))
|
||||||
signals.update_settings.connect(self.sig_update)
|
signals.update_settings.connect(self.sig_update)
|
||||||
signals.flowlist_change.connect(self.sig_update)
|
signals.flowlist_change.connect(self.sig_update)
|
||||||
signals.footer_help.connect(self.sig_footer_help)
|
|
||||||
master.options.changed.connect(self.sig_update)
|
master.options.changed.connect(self.sig_update)
|
||||||
master.view.focus.sig_change.connect(self.sig_update)
|
master.view.focus.sig_change.connect(self.sig_update)
|
||||||
self.redraw()
|
self.redraw()
|
||||||
|
|
||||||
def sig_footer_help(self, sender, helptext):
|
|
||||||
self.helptext = helptext
|
|
||||||
self.redraw()
|
|
||||||
|
|
||||||
def sig_update(self, sender, updated=None):
|
def sig_update(self, sender, updated=None):
|
||||||
self.redraw()
|
self.redraw()
|
||||||
|
|
||||||
@ -288,13 +282,7 @@ class StatusBar(urwid.WidgetWrap):
|
|||||||
t.extend(self.get_status())
|
t.extend(self.get_status())
|
||||||
status = urwid.AttrWrap(urwid.Columns([
|
status = urwid.AttrWrap(urwid.Columns([
|
||||||
urwid.Text(t),
|
urwid.Text(t),
|
||||||
urwid.Text(
|
urwid.Text(boundaddr, align="right"),
|
||||||
[
|
|
||||||
self.helptext,
|
|
||||||
boundaddr
|
|
||||||
],
|
|
||||||
align="right"
|
|
||||||
),
|
|
||||||
]), "heading")
|
]), "heading")
|
||||||
self.ib._w = status
|
self.ib._w = status
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class WindowStack:
|
|||||||
flowview = flowview.FlowView(master),
|
flowview = flowview.FlowView(master),
|
||||||
commands = commands.Commands(master),
|
commands = commands.Commands(master),
|
||||||
options = options.Options(master),
|
options = options.Options(master),
|
||||||
help = help.HelpView(None),
|
help = help.HelpView(master),
|
||||||
eventlog = eventlog.EventLog(master),
|
eventlog = eventlog.EventLog(master),
|
||||||
|
|
||||||
edit_focus_query = grideditor.QueryEditor(master),
|
edit_focus_query = grideditor.QueryEditor(master),
|
||||||
@ -45,43 +45,57 @@ class WindowStack:
|
|||||||
self.overlay = None
|
self.overlay = None
|
||||||
|
|
||||||
def set_overlay(self, o, **kwargs):
|
def set_overlay(self, o, **kwargs):
|
||||||
self.overlay = overlay.SimpleOverlay(self, o, self.top(), o.width, **kwargs)
|
self.overlay = overlay.SimpleOverlay(
|
||||||
|
self, o, self.top_widget(), o.width, **kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
def top_window(self):
|
||||||
def topwin(self):
|
"""
|
||||||
|
The current top window, ignoring overlays.
|
||||||
|
"""
|
||||||
return self.windows[self.stack[-1]]
|
return self.windows[self.stack[-1]]
|
||||||
|
|
||||||
def top(self):
|
def top_widget(self):
|
||||||
|
"""
|
||||||
|
The current top widget - either a window or the active overlay.
|
||||||
|
"""
|
||||||
if self.overlay:
|
if self.overlay:
|
||||||
return self.overlay
|
return self.overlay
|
||||||
return self.topwin
|
return self.top_window()
|
||||||
|
|
||||||
def push(self, wname):
|
def push(self, wname):
|
||||||
if self.stack[-1] == wname:
|
if self.stack[-1] == wname:
|
||||||
return
|
return
|
||||||
|
prev = self.top_window()
|
||||||
self.stack.append(wname)
|
self.stack.append(wname)
|
||||||
|
self.call("layout_pushed", prev)
|
||||||
|
|
||||||
def pop(self, *args, **kwargs):
|
def pop(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Pop off the stack, return True if we're already at the top.
|
Pop off the stack, return True if we're already at the top.
|
||||||
"""
|
"""
|
||||||
|
if not self.overlay and len(self.stack) == 1:
|
||||||
|
return True
|
||||||
|
self.call("layout_popping")
|
||||||
if self.overlay:
|
if self.overlay:
|
||||||
self.overlay = None
|
self.overlay = None
|
||||||
elif len(self.stack) > 1:
|
|
||||||
self.call("view_popping")
|
|
||||||
self.stack.pop()
|
|
||||||
else:
|
else:
|
||||||
return True
|
self.stack.pop()
|
||||||
|
|
||||||
def call(self, name, *args, **kwargs):
|
def call(self, name, *args, **kwargs):
|
||||||
f = getattr(self.topwin, name, None)
|
"""
|
||||||
if f:
|
Call a function on both the top window, and the overlay if there is
|
||||||
f(*args, **kwargs)
|
one. If the widget has a key_responder, we call the function on the
|
||||||
|
responder instead.
|
||||||
|
"""
|
||||||
|
getattr(self.top_window(), name)(*args, **kwargs)
|
||||||
|
if self.overlay:
|
||||||
|
getattr(self.overlay, name)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Window(urwid.Frame):
|
class Window(urwid.Frame):
|
||||||
def __init__(self, master):
|
def __init__(self, master):
|
||||||
self.statusbar = statusbar.StatusBar(master, "")
|
self.statusbar = statusbar.StatusBar(master)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
None,
|
None,
|
||||||
header = None,
|
header = None,
|
||||||
@ -122,24 +136,26 @@ class Window(urwid.Frame):
|
|||||||
if c == "single":
|
if c == "single":
|
||||||
self.pane = 0
|
self.pane = 0
|
||||||
|
|
||||||
def wrap(w, idx):
|
def wrapped(idx):
|
||||||
if self.master.options.console_layout_headers and hasattr(w, "title"):
|
window = self.stacks[idx].top_window()
|
||||||
return Header(w, w.title, self.pane == idx)
|
widget = self.stacks[idx].top_widget()
|
||||||
|
if self.master.options.console_layout_headers and window.title:
|
||||||
|
return Header(widget, window.title, self.pane == idx)
|
||||||
else:
|
else:
|
||||||
return w
|
return widget
|
||||||
|
|
||||||
w = None
|
w = None
|
||||||
if c == "single":
|
if c == "single":
|
||||||
w = wrap(self.stacks[0].top(), 0)
|
w = wrapped(0)
|
||||||
elif c == "vertical":
|
elif c == "vertical":
|
||||||
w = urwid.Pile(
|
w = urwid.Pile(
|
||||||
[
|
[
|
||||||
wrap(s.top(), i) for i, s in enumerate(self.stacks)
|
wrapped(i) for i, s in enumerate(self.stacks)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
w = urwid.Columns(
|
w = urwid.Columns(
|
||||||
[wrap(s.top(), i) for i, s in enumerate(self.stacks)],
|
[wrapped(i) for i, s in enumerate(self.stacks)],
|
||||||
dividechars=1
|
dividechars=1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -195,11 +211,18 @@ class Window(urwid.Frame):
|
|||||||
|
|
||||||
def current(self, keyctx):
|
def current(self, keyctx):
|
||||||
"""
|
"""
|
||||||
|
Returns the active widget, but only the current focus or overlay has
|
||||||
Returns the top window of the current stack, IF the current focus
|
a matching key context.
|
||||||
has a matching key context.
|
|
||||||
"""
|
"""
|
||||||
t = self.focus_stack().topwin
|
t = self.focus_stack().top_widget()
|
||||||
|
if t.keyctx == keyctx:
|
||||||
|
return t
|
||||||
|
|
||||||
|
def current_window(self, keyctx):
|
||||||
|
"""
|
||||||
|
Returns the active window, ignoring overlays.
|
||||||
|
"""
|
||||||
|
t = self.focus_stack().top_window()
|
||||||
if t.keyctx == keyctx:
|
if t.keyctx == keyctx:
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -207,7 +230,7 @@ class Window(urwid.Frame):
|
|||||||
"""
|
"""
|
||||||
Returns the top window of either stack if they match the context.
|
Returns the top window of either stack if they match the context.
|
||||||
"""
|
"""
|
||||||
for t in [x.topwin for x in self.stacks]:
|
for t in [x.top_window() for x in self.stacks]:
|
||||||
if t.keyctx == keyctx:
|
if t.keyctx == keyctx:
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -245,7 +268,7 @@ class Window(urwid.Frame):
|
|||||||
if self.focus_part == "footer":
|
if self.focus_part == "footer":
|
||||||
return super().keypress(size, k)
|
return super().keypress(size, k)
|
||||||
else:
|
else:
|
||||||
fs = self.focus_stack().top()
|
fs = self.focus_stack().top_widget()
|
||||||
k = fs.keypress(size, k)
|
k = fs.keypress(size, k)
|
||||||
if k:
|
if k:
|
||||||
return self.master.keymap.handle(fs.keyctx, k)
|
return self.master.keymap.handle(fs.keyctx, k)
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import mitmproxy.tools.console.help as help
|
|
||||||
|
|
||||||
from ....conftest import skip_appveyor
|
|
||||||
|
|
||||||
|
|
||||||
@skip_appveyor
|
|
||||||
class TestHelp:
|
|
||||||
|
|
||||||
def test_helptext(self):
|
|
||||||
h = help.HelpView(None)
|
|
||||||
assert h.helptext()
|
|
@ -5,25 +5,28 @@ import pytest
|
|||||||
|
|
||||||
|
|
||||||
def test_bind():
|
def test_bind():
|
||||||
with taddons.context() as tctx:
|
with taddons.context() as tctx:
|
||||||
km = keymap.Keymap(tctx.master)
|
km = keymap.Keymap(tctx.master)
|
||||||
km.executor = mock.Mock()
|
km.executor = mock.Mock()
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
km.add("foo", "bar", ["unsupported"])
|
km.add("foo", "bar", ["unsupported"])
|
||||||
|
|
||||||
km.add("key", "str", ["options", "commands"])
|
km.add("key", "str", ["options", "commands"])
|
||||||
assert km.get("options", "key")
|
assert km.get("options", "key")
|
||||||
assert km.get("commands", "key")
|
assert km.get("commands", "key")
|
||||||
assert not km.get("flowlist", "key")
|
assert not km.get("flowlist", "key")
|
||||||
|
assert len((km.list("commands"))) == 1
|
||||||
|
|
||||||
km.handle("unknown", "unknown")
|
km.handle("unknown", "unknown")
|
||||||
assert not km.executor.called
|
assert not km.executor.called
|
||||||
|
|
||||||
km.handle("options", "key")
|
km.handle("options", "key")
|
||||||
assert km.executor.called
|
assert km.executor.called
|
||||||
|
|
||||||
km.add("glob", "str", ["global"])
|
km.add("glob", "str", ["global"])
|
||||||
km.executor = mock.Mock()
|
km.executor = mock.Mock()
|
||||||
km.handle("options", "glob")
|
km.handle("options", "glob")
|
||||||
assert km.executor.called
|
assert km.executor.called
|
||||||
|
|
||||||
|
assert len((km.list("global"))) == 1
|
||||||
|
4
tox.ini
4
tox.ini
@ -54,8 +54,8 @@ commands =
|
|||||||
deps =
|
deps =
|
||||||
-rrequirements.txt
|
-rrequirements.txt
|
||||||
-e./release
|
-e./release
|
||||||
# The 3.2 release is broken 🎉
|
# The 3.2 release is broken
|
||||||
# the next commit after this updates the bootloaders, which then segfault! 🎉
|
# the next commit after this updates the bootloaders, which then segfault!
|
||||||
# https://github.com/pyinstaller/pyinstaller/issues/2232
|
# https://github.com/pyinstaller/pyinstaller/issues/2232
|
||||||
git+https://github.com/pyinstaller/pyinstaller.git@483c819d6a256b58db6740696a901bd41c313f0c; sys_platform == 'win32'
|
git+https://github.com/pyinstaller/pyinstaller.git@483c819d6a256b58db6740696a901bd41c313f0c; sys_platform == 'win32'
|
||||||
git+https://github.com/mhils/pyinstaller.git@d094401e4196b1a6a03818b80164a5f555861cef; sys_platform != 'win32'
|
git+https://github.com/mhils/pyinstaller.git@d094401e4196b1a6a03818b80164a5f555861cef; sys_platform != 'win32'
|
||||||
|
Loading…
Reference in New Issue
Block a user