Merge pull request #1690 from cortesi/consoleview

console: Port from state to view
This commit is contained in:
Aldo Cortesi 2016-10-30 16:27:12 +13:00 committed by GitHub
commit b229d470c4
9 changed files with 178 additions and 469 deletions

View File

@ -17,6 +17,7 @@ import sortedcontainers
import mitmproxy.flow import mitmproxy.flow
from mitmproxy import flowfilter from mitmproxy import flowfilter
from mitmproxy import exceptions
def key_request_start(f: mitmproxy.flow.Flow) -> datetime.datetime: def key_request_start(f: mitmproxy.flow.Flow) -> datetime.datetime:
@ -35,6 +36,8 @@ class View(collections.Sequence):
super().__init__() super().__init__()
self._store = {} self._store = {}
self.filter = matchall self.filter = matchall
# Should we show only marked flows?
self.show_marked = False
self.order_key = key_request_start self.order_key = key_request_start
self.order_reversed = False self.order_reversed = False
self._view = sortedcontainers.SortedListWithKey(key = self.order_key) self._view = sortedcontainers.SortedListWithKey(key = self.order_key)
@ -51,6 +54,15 @@ class View(collections.Sequence):
self.focus = Focus(self) self.focus = Focus(self)
self.settings = Settings(self) self.settings = Settings(self)
def store_count(self):
return len(self._store)
def inbounds(self, index: int) -> bool:
"""
Is this index >= 0 and < len(self)
"""
return index >= 0 and index < len(self)
def _rev(self, idx: int) -> int: def _rev(self, idx: int) -> int:
""" """
Reverses an index, if needed Reverses an index, if needed
@ -82,7 +94,19 @@ class View(collections.Sequence):
def index(self, f: mitmproxy.flow.Flow) -> int: def index(self, f: mitmproxy.flow.Flow) -> int:
return self._rev(self._view.index(f)) return self._rev(self._view.index(f))
def _refilter(self):
self._view.clear()
for i in self._store.values():
if self.show_marked and not i.marked:
continue
if self.filter(i):
self._view.add(i)
self.sig_refresh.send(self)
# API # API
def toggle_marked(self):
self.show_marked = not self.show_marked
self._refilter()
def toggle_reversed(self): def toggle_reversed(self):
self.order_reversed = not self.order_reversed self.order_reversed = not self.order_reversed
@ -102,17 +126,13 @@ class View(collections.Sequence):
Sets the current view filter. Sets the current view filter.
""" """
self.filter = flt or matchall self.filter = flt or matchall
self._view.clear() self._refilter()
for i in self._store.values():
if self.filter(i):
self._view.add(i)
self.sig_refresh.send(self)
def clear(self): def clear(self):
""" """
Clears both the state and view. Clears both the state and view.
""" """
self._state.clear() self._store.clear()
self._view.clear() self._view.clear()
self.sig_refresh.send(self) self.sig_refresh.send(self)
@ -157,6 +177,16 @@ class View(collections.Sequence):
pass pass
# Event handlers # Event handlers
def configure(self, opts, updated):
filt = None
if "filter" in updated:
if opts.filter:
filt = flowfilter.parse(opts.filter)
if not filt:
raise exceptions.OptionsError(
"Invalid interception filter: %s" % opts.filter
)
self.set_filter(filt)
def request(self, f): def request(self, f):
self.add(f) self.add(f)
@ -202,20 +232,10 @@ class Focus:
if self.flow: if self.flow:
return self.view.index(self.flow) return self.view.index(self.flow)
def next(self): @index.setter
""" def index(self, idx) -> typing.Optional[int]:
Sets the focus to the next flow. if idx < 0 or idx > len(self.view) - 1:
""" raise ValueError("Index out of view bounds")
if self.flow:
idx = min(self.index + 1, len(self.view) - 1)
self.flow = self.view[idx]
def prev(self):
"""
Sets the focus to the previous flow.
"""
if self.flow:
idx = max(self.index - 1, 0)
self.flow = self.view[idx] self.flow = self.view[idx]
def _nearest(self, f, v): def _nearest(self, f, v):

View File

@ -109,8 +109,8 @@ class BodyPile(urwid.Pile):
class ConnectionItem(urwid.WidgetWrap): class ConnectionItem(urwid.WidgetWrap):
def __init__(self, master, state, flow, focus): def __init__(self, master, view, flow, focus):
self.master, self.state, self.flow = master, state, flow self.master, self.view, self.flow = master, view, flow
self.f = focus self.f = focus
w = self.get_text() w = self.get_text()
urwid.WidgetWrap.__init__(self, w) urwid.WidgetWrap.__init__(self, w)
@ -143,7 +143,7 @@ class ConnectionItem(urwid.WidgetWrap):
def server_replay_prompt(self, k): def server_replay_prompt(self, k):
a = self.master.addons.get("serverplayback") a = self.master.addons.get("serverplayback")
if k == "a": if k == "a":
a.load([i.copy() for i in self.master.state.view]) a.load([i.copy() for i in self.master.view])
elif k == "t": elif k == "t":
a.load([self.flow.copy()]) a.load([self.flow.copy()])
signals.update_settings.send(self) signals.update_settings.send(self)
@ -163,21 +163,18 @@ class ConnectionItem(urwid.WidgetWrap):
elif key == "d": elif key == "d":
if self.flow.killable: if self.flow.killable:
self.flow.kill(self.master) self.flow.kill(self.master)
self.state.delete_flow(self.flow) self.view.remove(self.view.focus.flow)
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "D": elif key == "D":
f = self.master.state.duplicate_flow(self.flow) cp = self.flow.copy()
self.master.state.set_focus_flow(f) self.master.view.add(cp)
self.master.view.focus.flow = cp
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "m": elif key == "m":
self.flow.marked = not self.flow.marked self.flow.marked = not self.flow.marked
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "M": elif key == "M":
if self.state.mark_filter: self.master.view.toggle_marked()
self.state.disable_marked_filter()
else:
self.state.enable_marked_filter()
signals.flowlist_change.send(self)
elif key == "r": elif key == "r":
try: try:
self.master.replay_request(self.flow) self.master.replay_request(self.flow)
@ -208,14 +205,14 @@ class ConnectionItem(urwid.WidgetWrap):
callback = self.server_replay_prompt, callback = self.server_replay_prompt,
) )
elif key == "U": elif key == "U":
for f in self.state.flows: for f in self.view:
f.marked = False f.marked = False
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "V": elif key == "V":
if not self.flow.modified(): if not self.flow.modified():
signals.status_message.send(message="Flow not modified.") signals.status_message.send(message="Flow not modified.")
return return
self.state.revert(self.flow) self.flow.revert()
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
signals.status_message.send(message="Reverted.") signals.status_message.send(message="Reverted.")
elif key == "w": elif key == "w":
@ -264,38 +261,49 @@ class ConnectionItem(urwid.WidgetWrap):
class FlowListWalker(urwid.ListWalker): class FlowListWalker(urwid.ListWalker):
def __init__(self, master, state): def __init__(self, master, view):
self.master, self.state = master, state self.master, self.view = master, view
signals.flowlist_change.connect(self.sig_flowlist_change) self.view.sig_refresh.connect(self.sig_mod)
self.view.sig_add.connect(self.sig_mod)
self.view.sig_remove.connect(self.sig_mod)
self.view.sig_update.connect(self.sig_mod)
signals.flowlist_change.connect(self.sig_mod)
def sig_flowlist_change(self, sender): def sig_mod(self, *args, **kwargs):
self._modified() self._modified()
def get_focus(self): def get_focus(self):
f, i = self.state.get_focus() if not self.view.focus.flow:
f = ConnectionItem(self.master, self.state, f, True) if f else None return None, 0
return f, i return ConnectionItem(
self.master, self.view, self.view.focus.flow, True
), self.view.focus.index
def set_focus(self, focus): def set_focus(self, index):
ret = self.state.set_focus(focus) if self.view.inbounds(index):
return ret self.view.focus.index = index
signals.flowlist_change.send(self)
def get_next(self, pos): def get_next(self, pos):
f, i = self.state.get_next(pos) pos = pos + 1
f = ConnectionItem(self.master, self.state, f, False) if f else None if not self.view.inbounds(pos):
return f, i return None, None
f = ConnectionItem(self.master, self.view, self.view[pos], False)
return f, pos
def get_prev(self, pos): def get_prev(self, pos):
f, i = self.state.get_prev(pos) pos = pos - 1
f = ConnectionItem(self.master, self.state, f, False) if f else None if not self.view.inbounds(pos):
return f, i return None, None
f = ConnectionItem(self.master, self.view, self.view[pos], False)
return f, pos
class FlowListBox(urwid.ListBox): class FlowListBox(urwid.ListBox):
def __init__(self, master: "mitmproxy.console.master.ConsoleMaster"): def __init__(self, master: "mitmproxy.console.master.ConsoleMaster"):
self.master = master self.master = master
super().__init__(FlowListWalker(master, master.state)) super().__init__(FlowListWalker(master, master.view))
def get_method_raw(self, k): def get_method_raw(self, k):
if k: if k:
@ -331,29 +339,32 @@ class FlowListBox(urwid.ListBox):
return return
scheme, host, port, path = parts scheme, host, port, path = parts
f = self.master.create_request(method, scheme, host, port, path) f = self.master.create_request(method, scheme, host, port, path)
self.master.state.set_focus_flow(f) self.master.view.focus.flow = f
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
def keypress(self, size, key): def keypress(self, size, key):
key = common.shortcuts(key) key = common.shortcuts(key)
if key == "A": if key == "A":
self.master.accept_all() for f in self.master.view:
if f.intercepted:
f.resume()
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "z": elif key == "z":
self.master.clear_flows() self.master.view.clear()
signals.flowlist_change.send(self)
elif key == "e": elif key == "e":
self.master.toggle_eventlog() self.master.toggle_eventlog()
elif key == "g": elif key == "g":
self.master.state.set_focus(0) self.master.view.focus.index = 0
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "G": elif key == "G":
self.master.state.set_focus(self.master.state.flow_count()) self.master.view.focus.index = len(self.master.view) - 1
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
elif key == "f": elif key == "f":
signals.status_prompt.send( signals.status_prompt.send(
prompt = "Filter View", prompt = "Filter View",
text = self.master.state.filter_txt, text = self.master.options.filter,
callback = self.master.set_view_filter callback = self.master.options.setter("filter")
) )
elif key == "L": elif key == "L":
signals.status_prompt_path.send( signals.status_prompt_path.send(

View File

@ -127,8 +127,8 @@ TAB_RESP = 1
class FlowView(tabs.Tabs): class FlowView(tabs.Tabs):
highlight_color = "focusfield" highlight_color = "focusfield"
def __init__(self, master, state, flow, tab_offset): def __init__(self, master, view, flow, tab_offset):
self.master, self.state, self.flow = master, state, flow self.master, self.view, self.flow = master, view, flow
super().__init__( super().__init__(
[ [
(self.tab_request, self.view_request), (self.tab_request, self.view_request),
@ -164,7 +164,7 @@ class FlowView(tabs.Tabs):
return self.conn_text(self.flow.response) return self.conn_text(self.flow.response)
def view_details(self): def view_details(self):
return flowdetailview.flowdetails(self.state, self.flow) return flowdetailview.flowdetails(self.view, self.flow)
def sig_flow_change(self, sender, flow): def sig_flow_change(self, sender, flow):
if flow == self.flow: if flow == self.flow:
@ -175,11 +175,8 @@ class FlowView(tabs.Tabs):
msg, body = "", [urwid.Text([("error", "[content missing]")])] msg, body = "", [urwid.Text([("error", "[content missing]")])]
return msg, body return msg, body
else: else:
full = self.state.get_flow_setting( s = self.view.settings[self.flow]
self.flow, full = s.get((self.tab_offset, "fullcontents"), False)
(self.tab_offset, "fullcontents"),
False
)
if full: if full:
limit = sys.maxsize limit = sys.maxsize
else: else:
@ -237,9 +234,9 @@ class FlowView(tabs.Tabs):
return description, text_objects return description, text_objects
def viewmode_get(self): def viewmode_get(self):
override = self.state.get_flow_setting( override = self.view.settings[self.flow].get(
self.flow, (self.tab_offset, "prettyview"),
(self.tab_offset, "prettyview") None
) )
return self.master.options.default_contentview if override is None else override return self.master.options.default_contentview if override is None else override
@ -284,7 +281,7 @@ class FlowView(tabs.Tabs):
] ]
) )
] ]
return searchable.Searchable(self.state, txt) return searchable.Searchable(self.view, txt)
def set_method_raw(self, m): def set_method_raw(self, m):
if m: if m:
@ -466,33 +463,25 @@ class FlowView(tabs.Tabs):
) )
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
def _view_nextprev_flow(self, np, flow): def view_flow(self, flow):
try:
idx = self.state.view.index(flow)
except IndexError:
return
if np == "next":
new_flow, new_idx = self.state.get_next(idx)
else:
new_flow, new_idx = self.state.get_prev(idx)
if new_flow is None:
signals.status_message.send(message="No more flows")
else:
signals.pop_view_state.send(self) signals.pop_view_state.send(self)
self.master.view_flow(new_flow, self.tab_offset) self.master.view_flow(flow, self.tab_offset)
def _view_nextprev_flow(self, idx, flow):
if not self.view.inbounds(idx):
signals.status_message.send(message="No more flows")
return
self.view_flow(self.view[idx])
def view_next_flow(self, flow): def view_next_flow(self, flow):
return self._view_nextprev_flow("next", flow) return self._view_nextprev_flow(self.view.index(flow) + 1, flow)
def view_prev_flow(self, flow): def view_prev_flow(self, flow):
return self._view_nextprev_flow("prev", flow) return self._view_nextprev_flow(self.view.index(flow) - 1, flow)
def change_this_display_mode(self, t): def change_this_display_mode(self, t):
self.state.add_flow_setting( name = contentviews.get_by_shortcut(t).name
self.flow, self.view.settings[self.flow][(self.tab_offset, "prettyview")] = name
(self.tab_offset, "prettyview"),
contentviews.get_by_shortcut(t).name
)
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
def keypress(self, size, key): def keypress(self, size, key):
@ -521,20 +510,18 @@ class FlowView(tabs.Tabs):
self.master.accept_all() self.master.accept_all()
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
elif key == "d": elif key == "d":
if self.state.flow_count() == 1: if self.flow.killable:
self.flow.kill(self.master)
self.view.remove(self.flow)
if not self.view.focus.flow:
self.master.view_flowlist() self.master.view_flowlist()
elif self.state.view.index(self.flow) == len(self.state.view) - 1:
self.view_prev_flow(self.flow)
else: else:
self.view_next_flow(self.flow) self.view_flow(self.view.focus.flow)
f = self.flow
if f.killable:
f.kill(self.master)
self.state.delete_flow(f)
elif key == "D": elif key == "D":
f = self.master.state.duplicate_flow(self.flow) cp = self.flow.copy()
signals.pop_view_state.send(self) self.master.view.add(cp)
self.master.view_flow(f) self.master.view.focus.flow = cp
self.view_flow(cp)
signals.status_message.send(message="Duplicated.") signals.status_message.send(message="Duplicated.")
elif key == "p": elif key == "p":
self.view_prev_flow(self.flow) self.view_prev_flow(self.flow)
@ -546,7 +533,7 @@ class FlowView(tabs.Tabs):
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
elif key == "V": elif key == "V":
if self.flow.modified(): if self.flow.modified():
self.state.revert(self.flow) self.flow.revert()
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
signals.status_message.send(message="Reverted.") signals.status_message.send(message="Reverted.")
else: else:
@ -608,14 +595,9 @@ class FlowView(tabs.Tabs):
else: else:
common.ask_save_body("s", self.flow) common.ask_save_body("s", self.flow)
elif key == "f": elif key == "f":
signals.status_message.send(message="Loading all body data...") self.view.settings[self.flow][(self.tab_offset, "fullcontents")] = True
self.state.add_flow_setting(
self.flow,
(self.tab_offset, "fullcontents"),
True
)
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
signals.status_message.send(message="") signals.status_message.send(message="Loading all body data...")
elif key == "m": elif key == "m":
p = list(contentviews.view_prompts) p = list(contentviews.view_prompts)
p.insert(0, ("Clear", "C")) p.insert(0, ("Clear", "C"))

View File

@ -9,7 +9,6 @@ import subprocess
import sys import sys
import tempfile import tempfile
import traceback import traceback
import weakref
import urwid import urwid
from typing import Optional from typing import Optional
@ -19,9 +18,8 @@ from mitmproxy import controller
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy import master from mitmproxy import master
from mitmproxy import io from mitmproxy import io
from mitmproxy import flowfilter
from mitmproxy import log from mitmproxy import log
from mitmproxy.addons import state from mitmproxy.addons import view
from mitmproxy.addons import intercept from mitmproxy.addons import intercept
import mitmproxy.options import mitmproxy.options
from mitmproxy.tools.console import flowlist from mitmproxy.tools.console import flowlist
@ -34,7 +32,6 @@ from mitmproxy.tools.console import palettes
from mitmproxy.tools.console import signals from mitmproxy.tools.console import signals
from mitmproxy.tools.console import statusbar from mitmproxy.tools.console import statusbar
from mitmproxy.tools.console import window from mitmproxy.tools.console import window
from mitmproxy.flowfilter import FMarked
from mitmproxy.utils import strutils from mitmproxy.utils import strutils
from mitmproxy.net import tcp from mitmproxy.net import tcp
@ -42,163 +39,6 @@ from mitmproxy.net import tcp
EVENTLOG_SIZE = 500 EVENTLOG_SIZE = 500
class ConsoleState(state.State):
def __init__(self):
state.State.__init__(self)
self.focus = None
self.follow_focus = None
self.flowsettings = weakref.WeakKeyDictionary()
self.last_search = None
self.last_filter = ""
self.mark_filter = False
def __setattr__(self, name, value):
self.__dict__[name] = value
signals.update_settings.send(self)
def add_flow_setting(self, flow, key, value):
d = self.flowsettings.setdefault(flow, {})
d[key] = value
def get_flow_setting(self, flow, key, default=None):
d = self.flowsettings.get(flow, {})
return d.get(key, default)
def add_flow(self, f):
super().add_flow(f)
signals.flowlist_change.send(self)
self.update_focus()
return f
def update_flow(self, f):
super().update_flow(f)
signals.flowlist_change.send(self)
self.update_focus()
return f
def set_view_filter(self, txt):
ret = super().set_view_filter(txt)
self.set_focus(self.focus)
return ret
def get_focus(self):
if not self.view or self.focus is None:
return None, None
return self.view[self.focus], self.focus
def set_focus(self, idx):
if self.view:
if idx is None or idx < 0:
idx = 0
elif idx >= len(self.view):
idx = len(self.view) - 1
self.focus = idx
else:
self.focus = None
def update_focus(self):
if self.focus is None:
self.set_focus(0)
elif self.follow_focus:
self.set_focus(len(self.view) - 1)
def set_focus_flow(self, f):
self.set_focus(self.view.index(f))
def get_from_pos(self, pos):
if len(self.view) <= pos or pos < 0:
return None, None
return self.view[pos], pos
def get_next(self, pos):
return self.get_from_pos(pos + 1)
def get_prev(self, pos):
return self.get_from_pos(pos - 1)
def delete_flow(self, f):
if f in self.view and self.view.index(f) <= self.focus:
self.focus -= 1
if self.focus < 0:
self.focus = None
ret = super().delete_flow(f)
self.set_focus(self.focus)
return ret
def get_nearest_matching_flow(self, flow, flt):
fidx = self.view.index(flow)
dist = 1
fprev = fnext = True
while fprev or fnext:
fprev, _ = self.get_from_pos(fidx - dist)
fnext, _ = self.get_from_pos(fidx + dist)
if fprev and flowfilter.match(flt, fprev):
return fprev
elif fnext and flowfilter.match(flt, fnext):
return fnext
dist += 1
return None
def enable_marked_filter(self):
marked_flows = [f for f in self.flows if f.marked]
if not marked_flows:
return
marked_filter = "~%s" % FMarked.code
# Save Focus
last_focus, _ = self.get_focus()
nearest_marked = self.get_nearest_matching_flow(last_focus, marked_filter)
self.last_filter = self.filter_txt
self.set_view_filter(marked_filter)
# Restore Focus
if last_focus.marked:
self.set_focus_flow(last_focus)
else:
self.set_focus_flow(nearest_marked)
self.mark_filter = True
def disable_marked_filter(self):
marked_filter = "~%s" % FMarked.code
# Save Focus
last_focus, _ = self.get_focus()
nearest_marked = self.get_nearest_matching_flow(last_focus, marked_filter)
self.set_view_filter(self.last_filter)
self.last_filter = ""
# Restore Focus
if last_focus.marked:
self.set_focus_flow(last_focus)
else:
self.set_focus_flow(nearest_marked)
self.mark_filter = False
def clear(self):
marked_flows = [f for f in self.view if f.marked]
super().clear()
for f in marked_flows:
self.add_flow(f)
f.marked = True
if len(self.flows.views) == 0:
self.focus = None
else:
self.focus = 0
self.set_focus(self.focus)
class Options(mitmproxy.options.Options): class Options(mitmproxy.options.Options):
def __init__( def __init__(
self, self,
@ -210,6 +50,7 @@ class Options(mitmproxy.options.Options):
palette: Optional[str] = None, palette: Optional[str] = None,
palette_transparent: bool = False, palette_transparent: bool = False,
no_mouse: bool = False, no_mouse: bool = False,
follow_focus: bool = False,
**kwargs **kwargs
): ):
self.eventlog = eventlog self.eventlog = eventlog
@ -219,6 +60,7 @@ class Options(mitmproxy.options.Options):
self.palette = palette self.palette = palette
self.palette_transparent = palette_transparent self.palette_transparent = palette_transparent
self.no_mouse = no_mouse self.no_mouse = no_mouse
self.follow_focus = follow_focus
super().__init__(**kwargs) super().__init__(**kwargs)
@ -227,15 +69,12 @@ class ConsoleMaster(master.Master):
def __init__(self, options, server): def __init__(self, options, server):
super().__init__(options, server) super().__init__(options, server)
self.state = ConsoleState() self.view = view.View()
self.stream_path = None self.stream_path = None
# This line is just for type hinting # This line is just for type hinting
self.options = self.options # type: Options self.options = self.options # type: Options
self.options.errored.connect(self.options_error) self.options.errored.connect(self.options_error)
if options.filter:
self.set_view_filter(options.filter)
self.palette = options.palette self.palette = options.palette
self.palette_transparent = options.palette_transparent self.palette_transparent = options.palette_transparent
@ -250,7 +89,7 @@ class ConsoleMaster(master.Master):
signals.push_view_state.connect(self.sig_push_view_state) signals.push_view_state.connect(self.sig_push_view_state)
signals.sig_add_log.connect(self.sig_add_log) signals.sig_add_log.connect(self.sig_add_log)
self.addons.add(*addons.default_addons()) self.addons.add(*addons.default_addons())
self.addons.add(self.state, intercept.Intercept()) self.addons.add(intercept.Intercept(), self.view)
def __setattr__(self, name, value): def __setattr__(self, name, value):
self.__dict__[name] = value self.__dict__[name] = value
@ -432,13 +271,13 @@ class ConsoleMaster(master.Master):
if self.options.rfile: if self.options.rfile:
ret = self.load_flows_path(self.options.rfile) ret = self.load_flows_path(self.options.rfile)
if ret and self.state.flow_count(): if ret and self.view.store_count():
signals.add_log( signals.add_log(
"File truncated or corrupted. " "File truncated or corrupted. "
"Loaded as many flows as possible.", "Loaded as many flows as possible.",
"error" "error"
) )
elif ret and not self.state.flow_count(): elif ret and not self.view.store_count():
self.shutdown() self.shutdown()
print("Could not load file: {}".format(ret), file=sys.stderr) print("Could not load file: {}".format(ret), file=sys.stderr)
sys.exit(1) sys.exit(1)
@ -533,17 +372,12 @@ class ConsoleMaster(master.Master):
def view_flowlist(self): def view_flowlist(self):
if self.ui.started: if self.ui.started:
self.ui.clear() self.ui.clear()
if self.state.follow_focus:
self.state.set_focus(self.state.flow_count())
if self.options.eventlog: if self.options.eventlog:
body = flowlist.BodyPile(self) body = flowlist.BodyPile(self)
else: else:
body = flowlist.FlowListBox(self) body = flowlist.FlowListBox(self)
if self.follow:
self.toggle_follow_flows()
signals.push_view_state.send( signals.push_view_state.send(
self, self,
window = window.Window( window = window.Window(
@ -556,12 +390,12 @@ class ConsoleMaster(master.Master):
) )
def view_flow(self, flow, tab_offset=0): def view_flow(self, flow, tab_offset=0):
self.state.set_focus_flow(flow) self.view.focus.flow = flow
signals.push_view_state.send( signals.push_view_state.send(
self, self,
window = window.Window( window = window.Window(
self, self,
flowview.FlowView(self, self.state, flow, tab_offset), flowview.FlowView(self, self.view, flow, tab_offset),
flowview.FlowViewHeader(self, flow), flowview.FlowViewHeader(self, flow),
statusbar.StatusBar(self, flowview.footer), statusbar.StatusBar(self, flowview.footer),
flowview.help_context flowview.help_context
@ -585,7 +419,7 @@ class ConsoleMaster(master.Master):
return self._write_flows(path, [flow]) return self._write_flows(path, [flow])
def save_flows(self, path): def save_flows(self, path):
return self._write_flows(path, self.state.view) return self._write_flows(path, self.view)
def load_flows_callback(self, path): def load_flows_callback(self, path):
if not path: if not path:
@ -602,14 +436,6 @@ class ConsoleMaster(master.Master):
signals.flowlist_change.send(self) signals.flowlist_change.send(self)
return reterr return reterr
def accept_all(self):
self.state.accept_all(self)
def set_view_filter(self, txt):
v = self.state.set_view_filter(txt)
signals.flowlist_change.send(self)
return v
def edit_scripts(self, scripts): def edit_scripts(self, scripts):
self.options.scripts = [x[0] for x in scripts] self.options.scripts = [x[0] for x in scripts]
@ -617,56 +443,14 @@ class ConsoleMaster(master.Master):
if a != "n": if a != "n":
raise urwid.ExitMainLoop raise urwid.ExitMainLoop
def shutdown(self):
self.state.killall(self)
master.Master.shutdown(self)
def clear_flows(self):
self.state.clear()
signals.flowlist_change.send(self)
def toggle_follow_flows(self):
# toggle flow follow
self.state.follow_focus = not self.state.follow_focus
# jump to most recent flow if follow is now on
if self.state.follow_focus:
self.state.set_focus(self.state.flow_count())
signals.flowlist_change.send(self)
def delete_flow(self, f):
self.state.delete_flow(f)
signals.flowlist_change.send(self)
def refresh_focus(self): def refresh_focus(self):
if self.state.view: if self.view.focus.flow:
signals.flow_change.send( signals.flow_change.send(self, flow = self.view.focus.flow)
self,
flow = self.state.view[self.state.focus]
)
def process_flow(self, f):
signals.flowlist_change.send(self)
signals.flow_change.send(self, flow=f)
def clear_events(self): def clear_events(self):
self.logbuffer[:] = [] self.logbuffer[:] = []
# Handlers # Handlers
@controller.handler
def error(self, f):
super().error(f)
self.process_flow(f)
@controller.handler
def request(self, f):
super().request(f)
self.process_flow(f)
@controller.handler
def response(self, f):
super().response(f)
self.process_flow(f)
@controller.handler @controller.handler
def tcp_message(self, f): def tcp_message(self, f):
super().tcp_message(f) super().tcp_message(f)

View File

@ -172,10 +172,8 @@ class Options(urwid.WidgetWrap):
showhost = False, showhost = False,
stickyauth = None, stickyauth = None,
stickycookie = None, stickycookie = None,
default_contentview = "auto",
) )
self.master.state.default_body_view = contentviews.get("Auto")
signals.update_settings.send(self) signals.update_settings.send(self)
signals.status_message.send( signals.status_message.send(
message = "All select.Options cleared", message = "All select.Options cleared",

View File

@ -16,13 +16,14 @@ class Highlight(urwid.AttrMap):
class Searchable(urwid.ListBox): class Searchable(urwid.ListBox):
def __init__(self, state, contents): def __init__(self, view, contents):
self.walker = urwid.SimpleFocusListWalker(contents) self.walker = urwid.SimpleFocusListWalker(contents)
urwid.ListBox.__init__(self, self.walker) urwid.ListBox.__init__(self, self.walker)
self.state = state self.view = view
self.search_offset = 0 self.search_offset = 0
self.current_highlight = None self.current_highlight = None
self.search_term = None self.search_term = None
self.last_search = None
def keypress(self, size, key): def keypress(self, size, key):
if key == "/": if key == "/":
@ -45,7 +46,7 @@ class Searchable(urwid.ListBox):
return super().keypress(size, key) return super().keypress(size, key)
def set_search(self, text): def set_search(self, text):
self.state.last_search = text self.last_search = text
self.search_term = text or None self.search_term = text or None
self.find_next(False) self.find_next(False)
@ -69,8 +70,8 @@ class Searchable(urwid.ListBox):
def find_next(self, backwards): def find_next(self, backwards):
if not self.search_term: if not self.search_term:
if self.state.last_search: if self.last_search:
self.search_term = self.state.last_search self.search_term = self.last_search
else: else:
self.set_highlight(None) self.set_highlight(None)
return return

View File

@ -164,10 +164,10 @@ class StatusBar(urwid.WidgetWrap):
r.append("[") r.append("[")
r.append(("heading_key", "i")) r.append(("heading_key", "i"))
r.append(":%s]" % self.master.options.intercept) r.append(":%s]" % self.master.options.intercept)
if self.master.state.filter_txt: if self.master.options.filter:
r.append("[") r.append("[")
r.append(("heading_key", "f")) r.append(("heading_key", "f"))
r.append(":%s]" % self.master.state.filter_txt) r.append(":%s]" % self.master.options.filter)
if self.master.options.stickycookie: if self.master.options.stickycookie:
r.append("[") r.append("[")
r.append(("heading_key", "t")) r.append(("heading_key", "t"))
@ -194,7 +194,7 @@ class StatusBar(urwid.WidgetWrap):
opts.append("killextra") opts.append("killextra")
if self.master.options.no_upstream_cert: if self.master.options.no_upstream_cert:
opts.append("no-upstream-cert") opts.append("no-upstream-cert")
if self.master.state.follow_focus: if self.master.options.follow_focus:
opts.append("following") opts.append("following")
if self.master.options.stream_large_bodies: if self.master.options.stream_large_bodies:
opts.append( opts.append(
@ -224,11 +224,11 @@ class StatusBar(urwid.WidgetWrap):
return r return r
def redraw(self): def redraw(self):
fc = self.master.state.flow_count() fc = len(self.master.view)
if self.master.state.focus is None: if self.master.view.focus.flow is None:
offset = 0 offset = 0
else: else:
offset = min(self.master.state.focus + 1, fc) offset = self.master.view.focus.index + 1
t = [ t = [
('heading', ("[%s/%s]" % (offset, fc)).ljust(9)) ('heading', ("[%s/%s]" % (offset, fc)).ljust(9))
] ]

View File

@ -1,6 +1,8 @@
from mitmproxy.test import tflow from mitmproxy.test import tflow
from mitmproxy.addons import view from mitmproxy.addons import view
from mitmproxy import flowfilter from mitmproxy import flowfilter
from mitmproxy import options
from mitmproxy.test import taddons
from .. import tutils from .. import tutils
@ -31,6 +33,10 @@ def test_simple():
assert list(v) == [f, f3, f2] assert list(v) == [f, f3, f2]
assert len(v._store) == 3 assert len(v._store) == 3
v.clear()
assert len(v) == 0
assert len(v._store) == 0
def tft(*, method="get", start=0): def tft(*, method="get", start=0):
f = tflow.tflow() f = tflow.tflow()
@ -52,6 +58,14 @@ def test_filter():
assert len(v._store) == 4 assert len(v._store) == 4
v.set_filter(None) v.set_filter(None)
assert len(v) == 4
v[1].marked = True
v.toggle_marked()
assert len(v) == 1
assert v[0].marked
v.toggle_marked()
assert len(v) == 4
def test_order(): def test_order():
v = view.View() v = view.View()
@ -236,31 +250,6 @@ def test_focus():
assert f.index is None assert f.index is None
def test_focus_nextprev():
v = view.View()
# Nops on an empty view
v.focus.next()
v.focus.prev()
# Nops on a single-flow view
v.add(tft(start=0))
assert v.focus.flow == v[0]
v.focus.next()
assert v.focus.flow == v[0]
v.focus.prev()
assert v.focus.flow == v[0]
v.add(tft(start=1))
v.focus.next()
assert v.focus.flow == v[1]
v.focus.next()
assert v.focus.flow == v[1]
v.focus.prev()
assert v.focus.flow == v[0]
v.focus.prev()
assert v.focus.flow == v[0]
def test_settings(): def test_settings():
v = view.View() v = view.View()
f = tft() f = tft()
@ -274,3 +263,16 @@ def test_settings():
v.remove(f) v.remove(f)
tutils.raises(KeyError, v.settings.__getitem__, f) tutils.raises(KeyError, v.settings.__getitem__, f)
assert not v.settings.keys() assert not v.settings.keys()
class Options(options.Options):
def __init__(self, *, filter=None, **kwargs):
self.filter = filter
super().__init__(**kwargs)
def test_configure():
v = view.View()
with taddons.context(options=Options()) as tctx:
tctx.configure(v, filter="~q")
tutils.raises("invalid interception filter", tctx.configure, v, filter="~~")

View File

@ -1,5 +1,3 @@
import gc
from mitmproxy.test import tflow from mitmproxy.test import tflow
import mitmproxy.test.tutils import mitmproxy.test.tutils
from mitmproxy.tools import console from mitmproxy.tools import console
@ -9,93 +7,6 @@ from mitmproxy.tools.console import common
from .. import mastertest from .. import mastertest
class TestConsoleState:
def test_flow(self):
"""
normal flow:
connect -> request -> response
"""
c = console.master.ConsoleState()
f = self._add_request(c)
assert f in c.flows
assert c.get_focus() == (f, 0)
def test_focus(self):
"""
normal flow:
connect -> request -> response
"""
c = console.master.ConsoleState()
f = self._add_request(c)
assert c.get_focus() == (f, 0)
assert c.get_from_pos(0) == (f, 0)
assert c.get_from_pos(1) == (None, None)
assert c.get_next(0) == (None, None)
f2 = self._add_request(c)
assert c.get_focus() == (f, 0)
assert c.get_next(0) == (f2, 1)
assert c.get_prev(1) == (f, 0)
assert c.get_next(1) == (None, None)
c.set_focus(0)
assert c.get_focus() == (f, 0)
c.set_focus(-1)
assert c.get_focus() == (f, 0)
c.set_focus(2)
assert c.get_focus() == (f2, 1)
c.delete_flow(f2)
assert c.get_focus() == (f, 0)
c.delete_flow(f)
assert c.get_focus() == (None, None)
def _add_request(self, state):
f = tflow.tflow()
return state.add_flow(f)
def _add_response(self, state):
f = self._add_request(state)
f.response = mitmproxy.test.tutils.tresp()
state.update_flow(f)
def test_add_response(self):
c = console.master.ConsoleState()
f = self._add_request(c)
f.response = mitmproxy.test.tutils.tresp()
c.focus = None
c.update_flow(f)
def test_focus_view(self):
c = console.master.ConsoleState()
self._add_request(c)
self._add_response(c)
self._add_request(c)
self._add_response(c)
self._add_request(c)
self._add_response(c)
assert not c.set_view_filter("~s")
assert len(c.view) == 3
assert c.focus == 0
def test_settings(self):
c = console.master.ConsoleState()
f = self._add_request(c)
c.add_flow_setting(f, "foo", "bar")
assert c.get_flow_setting(f, "foo") == "bar"
assert c.get_flow_setting(f, "oink") is None
assert c.get_flow_setting(f, "oink", "foo") == "foo"
assert len(c.flowsettings) == 1
c.delete_flow(f)
del f
gc.collect()
assert len(c.flowsettings) == 0
def test_format_keyvals(): def test_format_keyvals():
assert common.format_keyvals( assert common.format_keyvals(
[ [
@ -123,17 +34,17 @@ class TestMaster(mastertest.MasterTest):
m = self.mkmaster() m = self.mkmaster()
for i in (1, 2, 3): for i in (1, 2, 3):
self.dummy_cycle(m, 1, b"") self.dummy_cycle(m, 1, b"")
assert len(m.state.flows) == i assert len(m.view) == i
def test_intercept(self): def test_intercept(self):
"""regression test for https://github.com/mitmproxy/mitmproxy/issues/1605""" """regression test for https://github.com/mitmproxy/mitmproxy/issues/1605"""
m = self.mkmaster(intercept="~b bar") m = self.mkmaster(intercept="~b bar")
f = tflow.tflow(req=mitmproxy.test.tutils.treq(content=b"foo")) f = tflow.tflow(req=mitmproxy.test.tutils.treq(content=b"foo"))
m.request(f) m.request(f)
assert not m.state.flows[0].intercepted assert not m.view[0].intercepted
f = tflow.tflow(req=mitmproxy.test.tutils.treq(content=b"bar")) f = tflow.tflow(req=mitmproxy.test.tutils.treq(content=b"bar"))
m.request(f) m.request(f)
assert m.state.flows[1].intercepted assert m.view[1].intercepted
f = tflow.tflow(resp=mitmproxy.test.tutils.tresp(content=b"bar")) f = tflow.tflow(resp=mitmproxy.test.tutils.tresp(content=b"bar"))
m.request(f) m.request(f)
assert m.state.flows[2].intercepted assert m.view[2].intercepted