From 244ef243d75145a01d9029589de65be51299b3f3 Mon Sep 17 00:00:00 2001 From: Krzysztof Bielicki Date: Tue, 10 Mar 2015 10:44:06 +0100 Subject: [#514] Add support for ignoring payload params in multipart/form-data --- libmproxy/console/contentview.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 582723bb..84e9946d 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -210,33 +210,13 @@ class ViewMultipart: prompt = ("multipart", "m") content_types = ["multipart/form-data"] def __call__(self, hdrs, content, limit): - v = hdrs.get_first("content-type") + v = utils.multipartdecode(hdrs, content) if v: - v = utils.parse_content_type(v) - if not v: - return - boundary = v[2].get("boundary") - if not boundary: - return - - rx = re.compile(r'\bname="([^"]+)"') - keys = [] - vals = [] - - for i in content.split("--" + boundary): - parts = i.splitlines() - if len(parts) > 1 and parts[0][0:2] != "--": - match = rx.search(parts[1]) - if match: - keys.append(match.group(1) + ":") - vals.append(netlib.utils.cleanBin( - "\n".join(parts[3+parts[2:].index(""):]) - )) r = [ urwid.Text(("highlight", "Form data:\n")), ] r.extend(common.format_keyvals( - zip(keys, vals), + v, key = "header", val = "text" )) -- cgit v1.2.3 From d7e53e6573426c40ac7cfbaa7754380985227eb1 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 09:30:29 +1300 Subject: Fix crashes on mouse click when input is being handled --- libmproxy/console/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 198b7bbe..70b82d1d 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -617,8 +617,6 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_execute(k) elif k == "enter": self.prompt_execute() - else: - self.view.keypress(self.loop.screen_size, k) else: k = self.view.keypress(self.loop.screen_size, k) if k: @@ -943,7 +941,7 @@ class ConsoleMaster(flow.FlowMaster): mkup.append(",") prompt.extend(mkup) prompt.append(")? ") - self.onekey = "".join(i[1] for i in keys) + self.onekey = set(i[1] for i in keys) self.prompt(prompt, "", callback, *args) def prompt_done(self): -- cgit v1.2.3 From a3f4296bf1ba0ac1a72d5a44a504d375707fdc39 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 10:02:34 +1300 Subject: Explicitly handle keyboard interrupt in mitmproxy Fixes #522 --- libmproxy/console/__init__.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 70b82d1d..9796677f 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -7,6 +7,7 @@ import tempfile import os import os.path import shlex +import signal import stat import subprocess import sys @@ -25,7 +26,8 @@ EVENTLOG_SIZE = 500 class _PathCompleter: def __init__(self, _testing=False): """ - _testing: disables reloading of the lookup table to make testing possible. + _testing: disables reloading of the lookup table to make testing + possible. """ self.lookup, self.offset = None, None self.final = None @@ -37,7 +39,8 @@ class _PathCompleter: def complete(self, txt): """ - Returns the next completion for txt, or None if there is no completion. + Returns the next completion for txt, or None if there is no + completion. """ path = os.path.expanduser(txt) if not self.lookup: @@ -702,14 +705,6 @@ class ConsoleMaster(flow.FlowMaster): self.edit_scripts ) ) - #if self.scripts: - # self.load_script(None) - #else: - # self.path_prompt( - # "Set script: ", - # self.state.last_script, - # self.set_script - # ) elif k == "S": if not self.server_playback: self.path_prompt( @@ -799,6 +794,14 @@ class ConsoleMaster(flow.FlowMaster): sys.exit(1) self.loop.set_alarm_in(0.01, self.ticker) + + # It's not clear why we need to handle this explicitly - without this, + # mitmproxy hangs on keyboard interrupt. Remove if we ever figure it + # out. + def exit(s, f): + raise urwid.ExitMainLoop + signal.signal(signal.SIGINT, exit) + try: self.loop.run() except Exception: -- cgit v1.2.3 From 560e44c637e4f1fcbeba1305fc1eb39e3d796013 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 10:54:57 +1300 Subject: Pull PathEdit out into its own file. --- libmproxy/console/__init__.py | 69 ++----------------------------------------- libmproxy/console/pathedit.py | 69 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 67 deletions(-) create mode 100644 libmproxy/console/pathedit.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 9796677f..013c8003 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -18,76 +18,11 @@ import weakref from .. import controller, utils, flow, script from . import flowlist, flowview, help, common -from . import grideditor, palettes, contentview, flowdetailview +from . import grideditor, palettes, contentview, flowdetailview, pathedit EVENTLOG_SIZE = 500 -class _PathCompleter: - def __init__(self, _testing=False): - """ - _testing: disables reloading of the lookup table to make testing - possible. - """ - self.lookup, self.offset = None, None - self.final = None - self._testing = _testing - - def reset(self): - self.lookup = None - self.offset = -1 - - def complete(self, txt): - """ - Returns the next completion for txt, or None if there is no - completion. - """ - path = os.path.expanduser(txt) - if not self.lookup: - if not self._testing: - # Lookup is a set of (display value, actual value) tuples. - self.lookup = [] - if os.path.isdir(path): - files = glob.glob(os.path.join(path, "*")) - prefix = txt - else: - files = glob.glob(path+"*") - prefix = os.path.dirname(txt) - prefix = prefix or "./" - for f in files: - display = os.path.join(prefix, os.path.basename(f)) - if os.path.isdir(f): - display += "/" - self.lookup.append((display, f)) - if not self.lookup: - self.final = path - return path - self.lookup.sort() - self.offset = -1 - self.lookup.append((txt, txt)) - self.offset += 1 - if self.offset >= len(self.lookup): - self.offset = 0 - ret = self.lookup[self.offset] - self.final = ret[1] - return ret[0] - - -class PathEdit(urwid.Edit, _PathCompleter): - def __init__(self, *args, **kwargs): - urwid.Edit.__init__(self, *args, **kwargs) - _PathCompleter.__init__(self) - - def keypress(self, size, key): - if key == "tab": - comp = self.complete(self.get_edit_text()) - self.set_edit_text(comp) - self.set_edit_pos(len(comp)) - else: - self.reset() - return urwid.Edit.keypress(self, size, key) - - class ActionBar(urwid.WidgetWrap): def __init__(self): self.message("") @@ -97,7 +32,7 @@ class ActionBar(urwid.WidgetWrap): def path_prompt(self, prompt, text): self.expire = None - self._w = PathEdit(prompt, text) + self._w = pathedit.PathEdit(prompt, text) def prompt(self, prompt, text = ""): self.expire = None diff --git a/libmproxy/console/pathedit.py b/libmproxy/console/pathedit.py new file mode 100644 index 00000000..53cda3be --- /dev/null +++ b/libmproxy/console/pathedit.py @@ -0,0 +1,69 @@ +import glob +import os.path + +import urwid + + +class _PathCompleter: + def __init__(self, _testing=False): + """ + _testing: disables reloading of the lookup table to make testing + possible. + """ + self.lookup, self.offset = None, None + self.final = None + self._testing = _testing + + def reset(self): + self.lookup = None + self.offset = -1 + + def complete(self, txt): + """ + Returns the next completion for txt, or None if there is no + completion. + """ + path = os.path.expanduser(txt) + if not self.lookup: + if not self._testing: + # Lookup is a set of (display value, actual value) tuples. + self.lookup = [] + if os.path.isdir(path): + files = glob.glob(os.path.join(path, "*")) + prefix = txt + else: + files = glob.glob(path+"*") + prefix = os.path.dirname(txt) + prefix = prefix or "./" + for f in files: + display = os.path.join(prefix, os.path.basename(f)) + if os.path.isdir(f): + display += "/" + self.lookup.append((display, f)) + if not self.lookup: + self.final = path + return path + self.lookup.sort() + self.offset = -1 + self.lookup.append((txt, txt)) + self.offset += 1 + if self.offset >= len(self.lookup): + self.offset = 0 + ret = self.lookup[self.offset] + self.final = ret[1] + return ret[0] + + +class PathEdit(urwid.Edit, _PathCompleter): + def __init__(self, *args, **kwargs): + urwid.Edit.__init__(self, *args, **kwargs) + _PathCompleter.__init__(self) + + def keypress(self, size, key): + if key == "tab": + comp = self.complete(self.get_edit_text()) + self.set_edit_text(comp) + self.set_edit_pos(len(comp)) + else: + self.reset() + return urwid.Edit.keypress(self, size, key) -- cgit v1.2.3 From 558e0a41c25ed927a3bd3244e82e50f2c1ec9f1c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 11:00:24 +1300 Subject: Fix general prompt input. --- libmproxy/console/__init__.py | 3 +++ libmproxy/console/common.py | 8 ++++++++ 2 files changed, 11 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 013c8003..5ff8e8d7 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -555,6 +555,9 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_execute(k) elif k == "enter": self.prompt_execute() + else: + if common.is_keypress(k): + self.view.keypress(self.loop.screen_size, k) else: k = self.view.keypress(self.loop.screen_size, k) if k: diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 3a708c7c..90204d79 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -31,6 +31,14 @@ METHOD_OPTIONS = [ ] +def is_keypress(k): + """ + Is this input event a keypress? + """ + if isinstance(k, basestring): + return True + + def highlight_key(s, k): l = [] parts = s.split(k, 1) -- cgit v1.2.3 From 241530eb0aa69c5a69bed979a1a2a3a23d473112 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 11:03:46 +1300 Subject: Remove cruft to work around an old Urwid bug --- libmproxy/console/__init__.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 5ff8e8d7..f3c8ee12 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -36,11 +36,6 @@ class ActionBar(urwid.WidgetWrap): def prompt(self, prompt, text = ""): self.expire = None - # A (partial) workaround for this Urwid issue: - # https://github.com/Nic0/tyrs/issues/115 - # We can remove it once veryone is beyond 1.0.1 - if isinstance(prompt, basestring): - prompt = unicode(prompt) self._w = urwid.Edit(prompt, text or "") def message(self, message, expire=None): -- cgit v1.2.3 From 2f8ebfdce2165f1bd9196954a1d3bcdfec463494 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 11:08:04 +1300 Subject: Pull console StatusBar into its own file. --- libmproxy/console/__init__.py | 189 ++--------------------------------------- libmproxy/console/statusbar.py | 180 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 182 deletions(-) create mode 100644 libmproxy/console/statusbar.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index f3c8ee12..5f564a20 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import glob import mailcap import mimetypes import tempfile @@ -11,191 +10,17 @@ import signal import stat import subprocess import sys -import time import traceback import urwid import weakref -from .. import controller, utils, flow, script +from .. import controller, flow, script from . import flowlist, flowview, help, common -from . import grideditor, palettes, contentview, flowdetailview, pathedit +from . import grideditor, palettes, contentview, flowdetailview, statusbar EVENTLOG_SIZE = 500 -class ActionBar(urwid.WidgetWrap): - def __init__(self): - self.message("") - - def selectable(self): - return True - - def path_prompt(self, prompt, text): - self.expire = None - self._w = pathedit.PathEdit(prompt, text) - - def prompt(self, prompt, text = ""): - self.expire = None - self._w = urwid.Edit(prompt, text or "") - - def message(self, message, expire=None): - self.expire = expire - self._w = urwid.Text(message) - - -class StatusBar(urwid.WidgetWrap): - def __init__(self, master, helptext): - self.master, self.helptext = master, helptext - self.ab = ActionBar() - self.ib = urwid.WidgetWrap(urwid.Text("")) - self._w = urwid.Pile([self.ib, self.ab]) - - def get_status(self): - r = [] - - if self.master.setheaders.count(): - r.append("[") - r.append(("heading_key", "H")) - r.append("eaders]") - if self.master.replacehooks.count(): - r.append("[") - r.append(("heading_key", "R")) - r.append("eplacing]") - if self.master.client_playback: - r.append("[") - r.append(("heading_key", "cplayback")) - r.append(":%s to go]"%self.master.client_playback.count()) - if self.master.server_playback: - r.append("[") - r.append(("heading_key", "splayback")) - if self.master.nopop: - r.append(":%s in file]"%self.master.server_playback.count()) - else: - r.append(":%s to go]"%self.master.server_playback.count()) - if self.master.get_ignore_filter(): - r.append("[") - r.append(("heading_key", "I")) - r.append("gnore:%d]" % len(self.master.get_ignore_filter())) - if self.master.get_tcp_filter(): - r.append("[") - r.append(("heading_key", "T")) - r.append("CP:%d]" % len(self.master.get_tcp_filter())) - if self.master.state.intercept_txt: - r.append("[") - r.append(("heading_key", "i")) - r.append(":%s]"%self.master.state.intercept_txt) - if self.master.state.limit_txt: - r.append("[") - r.append(("heading_key", "l")) - r.append(":%s]"%self.master.state.limit_txt) - if self.master.stickycookie_txt: - r.append("[") - r.append(("heading_key", "t")) - r.append(":%s]"%self.master.stickycookie_txt) - if self.master.stickyauth_txt: - r.append("[") - r.append(("heading_key", "u")) - r.append(":%s]"%self.master.stickyauth_txt) - if self.master.state.default_body_view.name != "Auto": - r.append("[") - r.append(("heading_key", "M")) - r.append(":%s]"%self.master.state.default_body_view.name) - - opts = [] - if self.master.anticache: - opts.append("anticache") - if self.master.anticomp: - opts.append("anticomp") - if self.master.showhost: - opts.append("showhost") - if not self.master.refresh_server_playback: - opts.append("norefresh") - if self.master.killextra: - opts.append("killextra") - if self.master.server.config.no_upstream_cert: - opts.append("no-upstream-cert") - if self.master.state.follow_focus: - opts.append("following") - if self.master.stream_large_bodies: - opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size)) - - if opts: - r.append("[%s]"%(":".join(opts))) - - if self.master.server.config.mode in ["reverse", "upstream"]: - dst = self.master.server.config.mode.dst - scheme = "https" if dst[0] else "http" - if dst[1] != dst[0]: - scheme += "2https" if dst[1] else "http" - r.append("[dest:%s]"%utils.unparse_url(scheme, *dst[2:])) - if self.master.scripts: - r.append("[") - r.append(("heading_key", "s")) - r.append("cripts:%s]"%len(self.master.scripts)) - # r.append("[lt:%0.3f]"%self.master.looptime) - - if self.master.stream: - r.append("[W:%s]"%self.master.stream_path) - - return r - - def redraw(self): - if self.ab.expire and time.time() > self.ab.expire: - self.message("") - - fc = self.master.state.flow_count() - if self.master.state.focus is None: - offset = 0 - else: - offset = min(self.master.state.focus + 1, fc) - t = [ - ('heading', ("[%s/%s]"%(offset, fc)).ljust(9)) - ] - - if self.master.server.bound: - host = self.master.server.address.host - if host == "0.0.0.0": - host = "*" - boundaddr = "[%s:%s]"%(host, self.master.server.address.port) - else: - boundaddr = "" - t.extend(self.get_status()) - status = urwid.AttrWrap(urwid.Columns([ - urwid.Text(t), - urwid.Text( - [ - self.helptext, - boundaddr - ], - align="right" - ), - ]), "heading") - self.ib._w = status - - def update(self, text): - self.helptext = text - self.redraw() - self.master.loop.draw_screen() - - def selectable(self): - return True - - def get_edit_text(self): - return self.ab._w.get_edit_text() - - def path_prompt(self, prompt, text): - return self.ab.path_prompt(prompt, text) - - def prompt(self, prompt, text = ""): - self.ab.prompt(prompt, text) - - def message(self, msg, expire=None): - if expire: - expire = time.time() + float(expire)/1000 - self.ab.message(msg, expire) - self.master.loop.draw_screen() - - class ConsoleState(flow.State): def __init__(self): flow.State.__init__(self) @@ -763,7 +588,7 @@ class ConsoleMaster(flow.FlowMaster): self.help_context, (self.statusbar, self.body, self.header) ) - self.statusbar = StatusBar(self, help.footer) + self.statusbar = statusbar.StatusBar(self, help.footer) self.body = h self.header = None self.loop.widget = self.make_view() @@ -774,7 +599,7 @@ class ConsoleMaster(flow.FlowMaster): flow, (self.statusbar, self.body, self.header) ) - self.statusbar = StatusBar(self, flowdetailview.footer) + self.statusbar = statusbar.StatusBar(self, flowdetailview.footer) self.body = h self.header = None self.loop.widget = self.make_view() @@ -783,7 +608,7 @@ class ConsoleMaster(flow.FlowMaster): self.body = ge self.header = None self.help_context = ge.make_help() - self.statusbar = StatusBar(self, grideditor.footer) + self.statusbar = statusbar.StatusBar(self, grideditor.footer) self.loop.widget = self.make_view() def view_flowlist(self): @@ -796,7 +621,7 @@ class ConsoleMaster(flow.FlowMaster): self.body = flowlist.BodyPile(self) else: self.body = flowlist.FlowListBox(self) - self.statusbar = StatusBar(self, flowlist.footer) + self.statusbar = statusbar.StatusBar(self, flowlist.footer) self.header = None self.state.view_mode = common.VIEW_LIST @@ -806,7 +631,7 @@ class ConsoleMaster(flow.FlowMaster): def view_flow(self, flow): self.body = flowview.FlowView(self, self.state, flow) self.header = flowview.FlowViewHeader(self, flow) - self.statusbar = StatusBar(self, flowview.footer) + self.statusbar = statusbar.StatusBar(self, flowview.footer) self.state.set_focus_flow(flow) self.state.view_mode = common.VIEW_FLOW self.loop.widget = self.make_view() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py new file mode 100644 index 00000000..4fb717cd --- /dev/null +++ b/libmproxy/console/statusbar.py @@ -0,0 +1,180 @@ + +import time + +import urwid + +from . import pathedit +from .. import utils + + +class ActionBar(urwid.WidgetWrap): + def __init__(self): + self.message("") + + def selectable(self): + return True + + def path_prompt(self, prompt, text): + self.expire = None + self._w = pathedit.PathEdit(prompt, text) + + def prompt(self, prompt, text = ""): + self.expire = None + self._w = urwid.Edit(prompt, text or "") + + def message(self, message, expire=None): + self.expire = expire + self._w = urwid.Text(message) + + +class StatusBar(urwid.WidgetWrap): + def __init__(self, master, helptext): + self.master, self.helptext = master, helptext + self.ab = ActionBar() + self.ib = urwid.WidgetWrap(urwid.Text("")) + self._w = urwid.Pile([self.ib, self.ab]) + + def get_status(self): + r = [] + + if self.master.setheaders.count(): + r.append("[") + r.append(("heading_key", "H")) + r.append("eaders]") + if self.master.replacehooks.count(): + r.append("[") + r.append(("heading_key", "R")) + r.append("eplacing]") + if self.master.client_playback: + r.append("[") + r.append(("heading_key", "cplayback")) + r.append(":%s to go]"%self.master.client_playback.count()) + if self.master.server_playback: + r.append("[") + r.append(("heading_key", "splayback")) + if self.master.nopop: + r.append(":%s in file]"%self.master.server_playback.count()) + else: + r.append(":%s to go]"%self.master.server_playback.count()) + if self.master.get_ignore_filter(): + r.append("[") + r.append(("heading_key", "I")) + r.append("gnore:%d]" % len(self.master.get_ignore_filter())) + if self.master.get_tcp_filter(): + r.append("[") + r.append(("heading_key", "T")) + r.append("CP:%d]" % len(self.master.get_tcp_filter())) + if self.master.state.intercept_txt: + r.append("[") + r.append(("heading_key", "i")) + r.append(":%s]"%self.master.state.intercept_txt) + if self.master.state.limit_txt: + r.append("[") + r.append(("heading_key", "l")) + r.append(":%s]"%self.master.state.limit_txt) + if self.master.stickycookie_txt: + r.append("[") + r.append(("heading_key", "t")) + r.append(":%s]"%self.master.stickycookie_txt) + if self.master.stickyauth_txt: + r.append("[") + r.append(("heading_key", "u")) + r.append(":%s]"%self.master.stickyauth_txt) + if self.master.state.default_body_view.name != "Auto": + r.append("[") + r.append(("heading_key", "M")) + r.append(":%s]"%self.master.state.default_body_view.name) + + opts = [] + if self.master.anticache: + opts.append("anticache") + if self.master.anticomp: + opts.append("anticomp") + if self.master.showhost: + opts.append("showhost") + if not self.master.refresh_server_playback: + opts.append("norefresh") + if self.master.killextra: + opts.append("killextra") + if self.master.server.config.no_upstream_cert: + opts.append("no-upstream-cert") + if self.master.state.follow_focus: + opts.append("following") + if self.master.stream_large_bodies: + opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size)) + + if opts: + r.append("[%s]"%(":".join(opts))) + + if self.master.server.config.mode in ["reverse", "upstream"]: + dst = self.master.server.config.mode.dst + scheme = "https" if dst[0] else "http" + if dst[1] != dst[0]: + scheme += "2https" if dst[1] else "http" + r.append("[dest:%s]"%utils.unparse_url(scheme, *dst[2:])) + if self.master.scripts: + r.append("[") + r.append(("heading_key", "s")) + r.append("cripts:%s]"%len(self.master.scripts)) + # r.append("[lt:%0.3f]"%self.master.looptime) + + if self.master.stream: + r.append("[W:%s]"%self.master.stream_path) + + return r + + def redraw(self): + if self.ab.expire and time.time() > self.ab.expire: + self.message("") + + fc = self.master.state.flow_count() + if self.master.state.focus is None: + offset = 0 + else: + offset = min(self.master.state.focus + 1, fc) + t = [ + ('heading', ("[%s/%s]"%(offset, fc)).ljust(9)) + ] + + if self.master.server.bound: + host = self.master.server.address.host + if host == "0.0.0.0": + host = "*" + boundaddr = "[%s:%s]"%(host, self.master.server.address.port) + else: + boundaddr = "" + t.extend(self.get_status()) + status = urwid.AttrWrap(urwid.Columns([ + urwid.Text(t), + urwid.Text( + [ + self.helptext, + boundaddr + ], + align="right" + ), + ]), "heading") + self.ib._w = status + + def update(self, text): + self.helptext = text + self.redraw() + self.master.loop.draw_screen() + + def selectable(self): + return True + + def get_edit_text(self): + return self.ab._w.get_edit_text() + + def path_prompt(self, prompt, text): + return self.ab.path_prompt(prompt, text) + + def prompt(self, prompt, text = ""): + self.ab.prompt(prompt, text) + + def message(self, msg, expire=None): + if expire: + expire = time.time() + float(expire)/1000 + self.ab.message(msg, expire) + self.master.loop.draw_screen() -- cgit v1.2.3 From c182133d645a07b7dee4504ecf6f99cc3f72f93a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 13:26:08 +1300 Subject: console: pull primary window frame management out into window.py --- libmproxy/console/__init__.py | 152 ++--------------------------------------- libmproxy/console/statusbar.py | 3 + libmproxy/console/window.py | 150 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 148 deletions(-) create mode 100644 libmproxy/console/window.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 5f564a20..426dda58 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -15,7 +15,7 @@ import urwid import weakref from .. import controller, flow, script -from . import flowlist, flowview, help, common +from . import flowlist, flowview, help, common, window from . import grideditor, palettes, contentview, flowdetailview, statusbar EVENTLOG_SIZE = 500 @@ -146,7 +146,6 @@ class ConsoleMaster(flow.FlowMaster): def __init__(self, server, options): flow.FlowMaster.__init__(self, server, ConsoleState()) - self.looptime = 0 self.stream_path = None self.options = options @@ -363,149 +362,6 @@ class ConsoleMaster(flow.FlowMaster): def set_palette(self, name): self.palette = palettes.palettes[name] - def input_filter(self, keys, raw): - for k in keys: - if self.prompting: - if k == "esc": - self.prompt_cancel() - elif self.onekey: - if k == "enter": - self.prompt_cancel() - elif k in self.onekey: - self.prompt_execute(k) - elif k == "enter": - self.prompt_execute() - else: - if common.is_keypress(k): - self.view.keypress(self.loop.screen_size, k) - else: - k = self.view.keypress(self.loop.screen_size, k) - if k: - self.statusbar.message("") - if k == "?": - self.view_help() - elif k == "c": - if not self.client_playback: - self.path_prompt( - "Client replay: ", - self.state.last_saveload, - self.client_playback_path - ) - else: - self.prompt_onekey( - "Stop current client replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.stop_client_playback_prompt, - ) - elif k == "H": - self.view_grideditor( - grideditor.SetHeadersEditor( - self, - self.setheaders.get_specs(), - self.setheaders.set - ) - ) - elif k == "I": - self.view_grideditor( - grideditor.HostPatternEditor( - self, - [[x] for x in self.get_ignore_filter()], - self.edit_ignore_filter - ) - ) - elif k == "T": - self.view_grideditor( - grideditor.HostPatternEditor( - self, - [[x] for x in self.get_tcp_filter()], - self.edit_tcp_filter - ) - ) - elif k == "i": - self.prompt( - "Intercept filter: ", - self.state.intercept_txt, - self.set_intercept - ) - elif k == "Q": - raise urwid.ExitMainLoop - elif k == "q": - self.prompt_onekey( - "Quit", - ( - ("yes", "y"), - ("no", "n"), - ), - self.quit, - ) - elif k == "M": - self.prompt_onekey( - "Global default display mode", - contentview.view_prompts, - self.change_default_display_mode - ) - elif k == "R": - self.view_grideditor( - grideditor.ReplaceEditor( - self, - self.replacehooks.get_specs(), - self.replacehooks.set - ) - ) - elif k == "s": - self.view_grideditor( - grideditor.ScriptEditor( - self, - [[i.command] for i in self.scripts], - self.edit_scripts - ) - ) - elif k == "S": - if not self.server_playback: - self.path_prompt( - "Server replay path: ", - self.state.last_saveload, - self.server_playback_path - ) - else: - self.prompt_onekey( - "Stop current server replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.stop_server_playback_prompt, - ) - elif k == "o": - self.prompt_onekey( - "Options", - ( - ("anticache", "a"), - ("anticomp", "c"), - ("showhost", "h"), - ("killextra", "k"), - ("norefresh", "n"), - ("no-upstream-certs", "u"), - ), - self._change_options - ) - elif k == "t": - self.prompt( - "Sticky cookie filter: ", - self.stickycookie_txt, - self.set_stickycookie - ) - elif k == "u": - self.prompt( - "Sticky auth filter: ", - self.stickyauth_txt, - self.set_stickyauth - ) - self.statusbar.redraw() - def ticker(self, *userdata): changed = self.tick(self.masterq, timeout=0) if changed: @@ -528,7 +384,6 @@ class ConsoleMaster(flow.FlowMaster): self.loop = urwid.MainLoop( self.view, screen = self.ui, - input_filter = self.input_filter ) self.view_flowlist() self.statusbar.redraw() @@ -574,12 +429,13 @@ class ConsoleMaster(flow.FlowMaster): self.shutdown() def make_view(self): - self.view = urwid.Frame( + self.view = window.Window( + self, self.body, header = self.header, footer = self.statusbar ) - self.view.set_focus("body") + self.statusbar.redraw() return self.view def view_help(self): diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 4fb717cd..a38615b4 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -34,6 +34,9 @@ class StatusBar(urwid.WidgetWrap): self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) + def keypress(self, *args, **kwargs): + return self.ab.keypress(*args, **kwargs) + def get_status(self): r = [] diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py new file mode 100644 index 00000000..8019adce --- /dev/null +++ b/libmproxy/console/window.py @@ -0,0 +1,150 @@ +import urwid + +class Window(urwid.Frame): + def __init__(self, master, body, header, footer): + urwid.Frame.__init__(self, body, header=header, footer=footer) + self.master = master + + def keypress(self, size, k): + if self.master.prompting: + if k == "esc": + self.master.prompt_cancel() + elif self.master.onekey: + if k == "enter": + self.master.prompt_cancel() + elif k in self.master.onekey: + self.master.prompt_execute(k) + elif k == "enter": + self.master.prompt_execute() + else: + if common.is_keypress(k): + urwid.Frame.keypress(self, self.master.loop.screen_size, k) + else: + return k + else: + k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) + if k == "?": + self.master.view_help() + elif k == "c": + if not self.master.client_playback: + self.master.path_prompt( + "Client replay: ", + self.master.state.last_saveload, + self.master.client_playback_path + ) + else: + self.master.prompt_onekey( + "Stop current client replay?", + ( + ("yes", "y"), + ("no", "n"), + ), + self.master.stop_client_playback_prompt, + ) + elif k == "H": + self.master.view_grideditor( + grideditor.SetHeadersEditor( + self.master, + self.master.setheaders.get_specs(), + self.master.setheaders.set + ) + ) + elif k == "I": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_ignore_filter()], + self.master.edit_ignore_filter + ) + ) + elif k == "T": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_tcp_filter()], + self.master.edit_tcp_filter + ) + ) + elif k == "i": + self.master.prompt( + "Intercept filter: ", + self.master.state.intercept_txt, + self.master.set_intercept + ) + elif k == "Q": + raise urwid.ExitMainLoop + elif k == "q": + self.master.prompt_onekey( + "Quit", + ( + ("yes", "y"), + ("no", "n"), + ), + self.master.quit, + ) + elif k == "M": + self.master.prompt_onekey( + "Global default display mode", + contentview.view_prompts, + self.master.change_default_display_mode + ) + elif k == "R": + self.master.view_grideditor( + grideditor.ReplaceEditor( + self.master, + self.master.replacehooks.get_specs(), + self.master.replacehooks.set + ) + ) + elif k == "s": + self.master.view_grideditor( + grideditor.ScriptEditor( + self.master, + [[i.command] for i in self.master.scripts], + self.master.edit_scripts + ) + ) + elif k == "S": + if not self.master.server_playback: + self.master.path_prompt( + "Server replay path: ", + self.master.state.last_saveload, + self.master.server_playback_path + ) + else: + self.master.prompt_onekey( + "Stop current server replay?", + ( + ("yes", "y"), + ("no", "n"), + ), + self.master.stop_server_playback_prompt, + ) + elif k == "o": + self.master.prompt_onekey( + "Options", + ( + ("anticache", "a"), + ("anticomp", "c"), + ("showhost", "h"), + ("killextra", "k"), + ("norefresh", "n"), + ("no-upstream-certs", "u"), + ), + self.master._change_options + ) + elif k == "t": + self.master.prompt( + "Sticky cookie filter: ", + self.master.stickycookie_txt, + self.master.set_stickycookie + ) + elif k == "u": + self.master.prompt( + "Sticky auth filter: ", + self.master.stickyauth_txt, + self.master.set_stickyauth + ) + else: + return k + self.footer.redraw() -- cgit v1.2.3 From b475c8d6eacd0d6a100cf6aaddc9c9915fdfb149 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 15:22:05 +1300 Subject: Add window.py import missed in refactoring --- libmproxy/console/window.py | 1 + 1 file changed, 1 insertion(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 8019adce..69f35183 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,4 +1,5 @@ import urwid +from . import common class Window(urwid.Frame): def __init__(self, master, body, header, footer): -- cgit v1.2.3 From 8725d50d03cf21b37a78c1d2fa03ade055c8a821 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 21 Mar 2015 11:19:20 +1300 Subject: Add blinker dependency, start using it to refactor console app Blinker lets us set up a central pub/sub mechanism to disentangle our object structure. --- libmproxy/console/__init__.py | 22 ++++++++++++---------- libmproxy/console/common.py | 11 ++++++----- libmproxy/console/flowlist.py | 10 +++++----- libmproxy/console/flowview.py | 24 ++++++++++++------------ libmproxy/console/grideditor.py | 8 ++++---- libmproxy/console/signals.py | 4 ++++ libmproxy/console/statusbar.py | 19 +++++-------------- libmproxy/console/window.py | 2 +- 8 files changed, 49 insertions(+), 51 deletions(-) create mode 100644 libmproxy/console/signals.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 426dda58..b5c59ecf 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -15,7 +15,7 @@ import urwid import weakref from .. import controller, flow, script -from . import flowlist, flowview, help, common, window +from . import flowlist, flowview, help, common, window, signals from . import grideditor, palettes, contentview, flowdetailview, statusbar EVENTLOG_SIZE = 500 @@ -238,7 +238,9 @@ class ConsoleMaster(flow.FlowMaster): try: s = script.Script(command, self) except script.ScriptError, v: - self.statusbar.message("Error loading script.") + signals.status_message.send( + message = "Error loading script." + ) self.add_event("Error loading script:\n%s"%v.args[0], "error") return @@ -257,7 +259,7 @@ class ConsoleMaster(flow.FlowMaster): return ret = self.load_script(command) if ret: - self.statusbar.message(ret) + signals.status_message.send(message=ret) self.state.last_script = command def toggle_eventlog(self): @@ -279,7 +281,7 @@ class ConsoleMaster(flow.FlowMaster): print >> sys.stderr, e.strerror sys.exit(1) else: - self.statusbar.message(e.strerror) + signals.status_message.send(message=e.strerror) return None def client_playback_path(self, path): @@ -314,7 +316,7 @@ class ConsoleMaster(flow.FlowMaster): try: subprocess.call(cmd) except: - self.statusbar.message("Can't start editor: %s" % " ".join(c)) + signals.status_message.send(message="Can't start editor: %s" % " ".join(c)) else: data = open(name, "rb").read() self.ui.start() @@ -353,8 +355,8 @@ class ConsoleMaster(flow.FlowMaster): try: subprocess.call(cmd, shell=shell) except: - self.statusbar.message( - "Can't start external viewer: %s" % " ".join(c) + signals.status_message.send( + message="Can't start external viewer: %s" % " ".join(c) ) self.ui.start() os.unlink(name) @@ -505,7 +507,7 @@ class ConsoleMaster(flow.FlowMaster): fw.add(i) f.close() except IOError, v: - self.statusbar.message(v.strerror) + signals.status_message.send(message=v.strerror) def save_one_flow(self, path, flow): return self._write_flows(path, [flow]) @@ -565,7 +567,7 @@ class ConsoleMaster(flow.FlowMaster): self.prompting = False self.onekey = False self.view.set_focus("body") - self.statusbar.message("") + signals.status_message.send(message="") def prompt_execute(self, txt=None): if not txt: @@ -574,7 +576,7 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_done() msg = p(txt, *args) if msg: - self.statusbar.message(msg, 1000) + signals.status_message.send(message=msg, expire=1000) def prompt_cancel(self): self.prompt_done() diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 90204d79..9731b682 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -6,6 +6,7 @@ import os from .. import utils from ..protocol.http import CONTENT_MISSING, decoded +from . import signals try: import pyperclip @@ -198,7 +199,7 @@ def save_data(path, data, master, state): with file(path, "wb") as f: f.write(data) except IOError, v: - master.statusbar.message(v.strerror) + signals.status_message.send(message=v.strerror) def ask_save_path(prompt, data, master, state): @@ -248,11 +249,11 @@ def copy_flow(part, scope, flow, master, state): if not data: if scope == "q": - master.statusbar.message("No request content to copy.") + signals.status_message.send(message="No request content to copy.") elif scope == "s": - master.statusbar.message("No response content to copy.") + signals.status_message.send(message="No response content to copy.") else: - master.statusbar.message("No contents to copy.") + signals.status_message.send(message="No contents to copy.") return try: @@ -336,7 +337,7 @@ def ask_save_body(part, master, state, flow): state ) else: - master.statusbar.message("No content to save.") + signals.status_message.send(message="No content to save.") class FlowCache: diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 5d8ad942..c8ecf15c 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import urwid from netlib import http -from . import common +from . import common, signals def _mkhelp(): @@ -171,7 +171,7 @@ class ConnectionItem(urwid.WidgetWrap): elif key == "r": r = self.master.replay_request(self.flow) if r: - self.master.statusbar.message(r) + signals.status_message.send(message=r) self.master.sync_list_view() elif key == "S": if not self.master.server_playback: @@ -195,11 +195,11 @@ class ConnectionItem(urwid.WidgetWrap): ) elif key == "V": if not self.flow.modified(): - self.master.statusbar.message("Flow not modified.") + signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) self.master.sync_list_view() - self.master.statusbar.message("Reverted.") + signals.status_message.send(message="Reverted.") elif key == "w": self.master.prompt_onekey( "Save", @@ -285,7 +285,7 @@ class FlowListBox(urwid.ListBox): def new_request(self, url, method): parts = http.parse_url(str(url)) if not parts: - self.master.statusbar.message("Invalid Url") + signals.status_message.send(message="Invalid Url") return scheme, host, port, path = parts f = self.master.create_request(method, scheme, host, port, path) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 89e75aad..b22bbb37 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import os, sys, copy import urwid -from . import common, grideditor, contentview +from . import common, grideditor, contentview, signals from .. import utils, flow, controller from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded @@ -282,10 +282,10 @@ class FlowView(urwid.WidgetWrap): if last_search_string: message = self.search(last_search_string, backwards) if message: - self.master.statusbar.message(message) + signals.status_message.send(message=message) else: message = "no previous searches have been made" - self.master.statusbar.message(message) + signals.status_message.send(message=message) return message @@ -606,7 +606,7 @@ class FlowView(urwid.WidgetWrap): else: new_flow, new_idx = self.state.get_prev(idx) if new_flow is None: - self.master.statusbar.message("No more flows!") + signals.status_message.send(message="No more flows!") return self.master.view_flow(new_flow) @@ -681,7 +681,7 @@ class FlowView(urwid.WidgetWrap): elif key == "D": f = self.master.duplicate_flow(self.flow) self.master.view_flow(f) - self.master.statusbar.message("Duplicated.") + signals.status_message.send(message="Duplicated.") elif key == "e": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: self.master.prompt_onekey( @@ -710,14 +710,14 @@ class FlowView(urwid.WidgetWrap): ) key = None elif key == "f": - self.master.statusbar.message("Loading all body data...") + signals.status_message.send(message="Loading all body data...") self.state.add_flow_setting( self.flow, (self.state.view_flow_mode, "fullcontents"), True ) self.master.refresh_flow(self.flow) - self.master.statusbar.message("") + signals.status_message.send(message="") elif key == "g": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: scope = "q" @@ -738,15 +738,15 @@ class FlowView(urwid.WidgetWrap): elif key == "r": r = self.master.replay_request(self.flow) if r: - self.master.statusbar.message(r) + signals.status_message.send(message=r) self.master.refresh_flow(self.flow) elif key == "V": if not self.flow.modified(): - self.master.statusbar.message("Flow not modified.") + signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) self.master.refresh_flow(self.flow) - self.master.statusbar.message("Reverted.") + signals.status_message.send(message="Reverted.") elif key == "W": self.master.path_prompt( "Save this flow: ", @@ -761,7 +761,7 @@ class FlowView(urwid.WidgetWrap): if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): self.master.spawn_external_viewer(conn.content, t) else: - self.master.statusbar.message("Error! Set $EDITOR or $PAGER.") + signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") elif key == "|": self.master.path_prompt( "Send flow to script: ", self.state.last_script, @@ -785,7 +785,7 @@ class FlowView(urwid.WidgetWrap): e = conn.headers.get_first("content-encoding", "identity") if e != "identity": if not conn.decode(): - self.master.statusbar.message("Could not decode - invalid data?") + signals.status_message.send(message="Could not decode - invalid data?") else: self.master.prompt_onekey( "Select encoding: ", diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index fe3df509..2d2754b1 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -5,7 +5,7 @@ import re import os import urwid -from . import common +from . import common, signals from .. import utils, filt, script from netlib import http_uastrings @@ -125,14 +125,14 @@ class GridWalker(urwid.ListWalker): try: val = val.decode("string-escape") except ValueError: - self.editor.master.statusbar.message( - "Invalid Python-style string encoding.", 1000 + signals.status_message.send( + self, message = "Invalid Python-style string encoding.", expure = 1000 ) return errors = self.lst[self.focus][1] emsg = self.editor.is_error(self.focus_col, val) if emsg: - self.editor.master.statusbar.message(emsg, 1000) + signals.status_message.send(message = emsg, expire = 1000) errors.add(self.focus_col) else: errors.discard(self.focus_col) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py new file mode 100644 index 00000000..a844ef8f --- /dev/null +++ b/libmproxy/console/signals.py @@ -0,0 +1,4 @@ + +import blinker + +status_message = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index a38615b4..7ad78f03 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -3,26 +3,26 @@ import time import urwid -from . import pathedit +from . import pathedit, signals from .. import utils + class ActionBar(urwid.WidgetWrap): def __init__(self): - self.message("") + urwid.WidgetWrap.__init__(self, urwid.Text("")) + signals.status_message.connect(self.message) def selectable(self): return True def path_prompt(self, prompt, text): - self.expire = None self._w = pathedit.PathEdit(prompt, text) def prompt(self, prompt, text = ""): - self.expire = None self._w = urwid.Edit(prompt, text or "") - def message(self, message, expire=None): + def message(self, sender, message, expire=None): self.expire = expire self._w = urwid.Text(message) @@ -127,9 +127,6 @@ class StatusBar(urwid.WidgetWrap): return r def redraw(self): - if self.ab.expire and time.time() > self.ab.expire: - self.message("") - fc = self.master.state.flow_count() if self.master.state.focus is None: offset = 0 @@ -175,9 +172,3 @@ class StatusBar(urwid.WidgetWrap): def prompt(self, prompt, text = ""): self.ab.prompt(prompt, text) - - def message(self, msg, expire=None): - if expire: - expire = time.time() + float(expire)/1000 - self.ab.message(msg, expire) - self.master.loop.draw_screen() diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 69f35183..44a5a316 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,5 +1,5 @@ import urwid -from . import common +from . import common, grideditor class Window(urwid.Frame): def __init__(self, master, body, header, footer): -- cgit v1.2.3 From 381a56306777900153939b1b46f20e63322944c2 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 21 Mar 2015 12:37:00 +1300 Subject: Status bar message expiry based on signals and Urwid main loop --- libmproxy/console/__init__.py | 8 +++++++- libmproxy/console/grideditor.py | 2 +- libmproxy/console/signals.py | 1 + libmproxy/console/statusbar.py | 21 ++++++++++++++------- 4 files changed, 23 insertions(+), 9 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index b5c59ecf..aae7a9c4 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -212,6 +212,12 @@ class ConsoleMaster(flow.FlowMaster): if options.app: self.start_app(self.options.app_host, self.options.app_port) + signals.call_in.connect(self.sig_call_in) + + def sig_call_in(self, sender, seconds, callback, args=()): + def cb(*_): + return callback(*args) + self.loop.set_alarm_in(seconds, cb) def start_stream_to_path(self, path, mode="wb"): path = os.path.expanduser(path) @@ -576,7 +582,7 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_done() msg = p(txt, *args) if msg: - signals.status_message.send(message=msg, expire=1000) + signals.status_message.send(message=msg, expire=1) def prompt_cancel(self): self.prompt_done() diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 2d2754b1..0b563c52 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -132,7 +132,7 @@ class GridWalker(urwid.ListWalker): errors = self.lst[self.focus][1] emsg = self.editor.is_error(self.focus_col, val) if emsg: - signals.status_message.send(message = emsg, expire = 1000) + signals.status_message.send(message = emsg, expire = 1) errors.add(self.focus_col) else: errors.discard(self.focus_col) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index a844ef8f..7b0ec937 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -2,3 +2,4 @@ import blinker status_message = blinker.Signal() +call_in = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7ad78f03..a29767e4 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -1,4 +1,3 @@ - import time import urwid @@ -7,11 +6,14 @@ from . import pathedit, signals from .. import utils - class ActionBar(urwid.WidgetWrap): def __init__(self): - urwid.WidgetWrap.__init__(self, urwid.Text("")) - signals.status_message.connect(self.message) + urwid.WidgetWrap.__init__(self, None) + self.clear() + signals.status_message.connect(self.sig_message) + + def clear(self): + self._w = urwid.Text("") def selectable(self): return True @@ -22,9 +24,14 @@ class ActionBar(urwid.WidgetWrap): def prompt(self, prompt, text = ""): self._w = urwid.Edit(prompt, text or "") - def message(self, sender, message, expire=None): - self.expire = expire - self._w = urwid.Text(message) + def sig_message(self, sender, message, expire=None): + w = urwid.Text(message) + self._w = w + if expire: + def cb(*args): + if w == self._w: + self.clear() + signals.call_in.send(seconds=expire, callback=cb) class StatusBar(urwid.WidgetWrap): -- cgit v1.2.3 From 89383e9c138f68caf1cc394174250c133d21aa04 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 13:32:24 +1300 Subject: Refactor status bar prompting to use signal system --- libmproxy/console/__init__.py | 58 +-------- libmproxy/console/common.py | 46 +++----- libmproxy/console/flowlist.py | 112 ++++++++++-------- libmproxy/console/flowview.py | 109 ++++++++++------- libmproxy/console/grideditor.py | 31 +++-- libmproxy/console/signals.py | 16 ++- libmproxy/console/statusbar.py | 84 +++++++++++-- libmproxy/console/window.py | 255 +++++++++++++++++++--------------------- 8 files changed, 381 insertions(+), 330 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index aae7a9c4..d8eb8a41 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -272,7 +272,7 @@ class ConsoleMaster(flow.FlowMaster): self.eventlog = not self.eventlog self.view_flowlist() - def _readflow(self, paths): + def _readflows(self, path): """ Utitility function that reads a list of flows or prints an error to the UI if that fails. @@ -281,7 +281,7 @@ class ConsoleMaster(flow.FlowMaster): - a list of flows, otherwise. """ try: - return flow.read_flows_from_paths(paths) + return flow.read_flows_from_paths([path]) except flow.FlowReadError as e: if not self.statusbar: print >> sys.stderr, e.strerror @@ -291,12 +291,12 @@ class ConsoleMaster(flow.FlowMaster): return None def client_playback_path(self, path): - flows = self._readflow(path) + flows = self._readflows(path) if flows: self.start_client_playback(flows, False) def server_playback_path(self, path): - flows = self._readflow(path) + flows = self._readflows(path) if flows: self.start_server_playback( flows, @@ -387,7 +387,6 @@ class ConsoleMaster(flow.FlowMaster): self.header = None self.body = None self.help_context = None - self.prompting = False self.onekey = False self.loop = urwid.MainLoop( self.view, @@ -538,55 +537,6 @@ class ConsoleMaster(flow.FlowMaster): self.sync_list_view() return reterr - def path_prompt(self, prompt, text, callback, *args): - self.statusbar.path_prompt(prompt, text) - self.view.set_focus("footer") - self.prompting = (callback, args) - - def prompt(self, prompt, text, callback, *args): - self.statusbar.prompt(prompt, text) - self.view.set_focus("footer") - self.prompting = (callback, args) - - def prompt_edit(self, prompt, text, callback): - self.statusbar.prompt(prompt + ": ", text) - self.view.set_focus("footer") - self.prompting = (callback, []) - - def prompt_onekey(self, prompt, keys, callback, *args): - """ - Keys are a set of (word, key) tuples. The appropriate key in the - word is highlighted. - """ - prompt = [prompt, " ("] - mkup = [] - for i, e in enumerate(keys): - mkup.extend(common.highlight_key(e[0], e[1])) - if i < len(keys)-1: - mkup.append(",") - prompt.extend(mkup) - prompt.append(")? ") - self.onekey = set(i[1] for i in keys) - self.prompt(prompt, "", callback, *args) - - def prompt_done(self): - self.prompting = False - self.onekey = False - self.view.set_focus("body") - signals.status_message.send(message="") - - def prompt_execute(self, txt=None): - if not txt: - txt = self.statusbar.get_edit_text() - p, args = self.prompting - self.prompt_done() - msg = p(txt, *args) - if msg: - signals.status_message.send(message=msg, expire=1) - - def prompt_cancel(self): - self.prompt_done() - def accept_all(self): self.state.accept_all(self) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 9731b682..185480db 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -203,13 +203,11 @@ def save_data(path, data, master, state): def ask_save_path(prompt, data, master, state): - master.path_prompt( - prompt, - state.last_saveload, - save_data, - data, - master, - state + signals.status_path_prompt.send( + prompt = prompt, + text = state.last_saveload, + callback = save_data, + args = (data, master, state) ) @@ -263,14 +261,13 @@ def copy_flow(part, scope, flow, master, state): def save(k): if k == "y": ask_save_path("Save data: ", data, master, state) - - master.prompt_onekey( - "Cannot copy binary data to clipboard. Save as file?", - ( + signals.status_prompt_onekey.send( + prompt = "Cannot copy binary data to clipboard. Save as file?", + keys = ( ("yes", "y"), ("no", "n"), ), - save + callback = save ) @@ -282,14 +279,11 @@ def ask_copy_part(scope, flow, master, state): if scope != "s": choices.append(("url", "u")) - master.prompt_onekey( - "Copy", - choices, - copy_flow, - scope, - flow, - master, - state + signals.status_prompt_onekey.send( + prompt = "Copy", + keys = choices, + callback = copy_flow, + args = (scope, flow, master, state) ) @@ -306,16 +300,14 @@ def ask_save_body(part, master, state, flow): # We first need to determine whether we want to save the request or the # response content. if request_has_content and response_has_content: - master.prompt_onekey( - "Save", - ( + signals.status_prompt_onekey.send( + prompt = "Save", + keys = ( ("request", "q"), ("response", "s"), ), - ask_save_body, - master, - state, - flow + callback = ask_save_body, + args = (master, state, flow) ) elif response_has_content: ask_save_body("s", master, state, flow) diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index c8ecf15c..d4dd89d8 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -111,17 +111,17 @@ class ConnectionItem(urwid.WidgetWrap): def save_flows_prompt(self, k): if k == "a": - self.master.path_prompt( - "Save all flows to: ", - self.state.last_saveload, - self.master.save_flows + signals.status_path_prompt.send( + prompt = "Save all flows to: ", + text = self.state.last_saveload, + callback = self.master.save_flows ) else: - self.master.path_prompt( - "Save this flow to: ", - self.state.last_saveload, - self.master.save_one_flow, - self.flow + signals.status_path_prompt.send( + prompt = "Save this flow to: ", + text = self.state.last_saveload, + callback = self.master.save_one_flow, + args = (self.flow,) ) def stop_server_playback_prompt(self, a): @@ -150,10 +150,10 @@ class ConnectionItem(urwid.WidgetWrap): self.master.options.replay_ignore_host ) else: - self.master.path_prompt( - "Server replay path: ", - self.state.last_saveload, - self.master.server_playback_path + signals.status_path_prompt.send( + prompt = "Server replay path: ", + text = self.state.last_saveload, + callback = self.master.server_playback_path ) def keypress(self, (maxcol,), key): @@ -175,23 +175,23 @@ class ConnectionItem(urwid.WidgetWrap): self.master.sync_list_view() elif key == "S": if not self.master.server_playback: - self.master.prompt_onekey( - "Server Replay", - ( + signals.status_prompt_onekey.send( + prompt = "Server Replay", + keys = ( ("all flows", "a"), ("this flow", "t"), ("file", "f"), ), - self.server_replay_prompt, + callback = self.server_replay_prompt, ) else: - self.master.prompt_onekey( - "Stop current server replay?", - ( + signals.status_prompt_onekey.send( + prompt = "Stop current server replay?", + keys = ( ("yes", "y"), ("no", "n"), ), - self.stop_server_playback_prompt, + callback = self.stop_server_playback_prompt, ) elif key == "V": if not self.flow.modified(): @@ -201,13 +201,14 @@ class ConnectionItem(urwid.WidgetWrap): self.master.sync_list_view() signals.status_message.send(message="Reverted.") elif key == "w": - self.master.prompt_onekey( - "Save", - ( + signals.status_prompt_onekey.send( + self, + prompt = "Save", + keys = ( ("all flows", "a"), ("this flow", "t"), ), - self.save_flows_prompt, + callback = self.save_flows_prompt, ) elif key == "X": self.flow.kill(self.master) @@ -215,11 +216,11 @@ class ConnectionItem(urwid.WidgetWrap): if self.flow.request: self.master.view_flow(self.flow) elif key == "|": - self.master.path_prompt( - "Send flow to script: ", - self.state.last_script, - self.master.run_script_once, - self.flow + signals.status_path_prompt.send( + prompt = "Send flow to script: ", + text = self.state.last_script, + callback = self.master.run_script_once, + args = (self.flow,) ) elif key == "g": common.ask_copy_part("a", self.flow, self.master, self.state) @@ -266,7 +267,12 @@ class FlowListBox(urwid.ListBox): def get_method(self, k): if k == "e": - self.master.prompt("Method:", "", self.get_method_raw) + signals.status_prompt.send( + self, + prompt = "Method:", + text = "", + callback = self.get_method_raw + ) else: method = "" for i in common.METHOD_OPTIONS: @@ -275,11 +281,11 @@ class FlowListBox(urwid.ListBox): self.get_url(method) def get_url(self, method): - self.master.prompt( - "URL:", - "http://www.example.com/", - self.new_request, - method + signals.status_prompt.send( + prompt = "URL:", + text = "http://www.example.com/", + callback = self.new_request, + args = (method,) ) def new_request(self, url, method): @@ -301,22 +307,23 @@ class FlowListBox(urwid.ListBox): elif key == "e": self.master.toggle_eventlog() elif key == "l": - self.master.prompt( - "Limit: ", - self.master.state.limit_txt, - self.master.set_limit + signals.status_prompt.send( + prompt = "Limit: ", + text = self.master.state.limit_txt, + callback = self.master.set_limit ) elif key == "L": - self.master.path_prompt( - "Load flows: ", - self.master.state.last_saveload, - self.master.load_flows_callback + signals.status_path_prompt.send( + self, + prompt = "Load flows: ", + text = self.master.state.last_saveload, + callback = self.master.load_flows_callback ) elif key == "n": - self.master.prompt_onekey( - "Method", - common.METHOD_OPTIONS, - self.get_method + signals.status_prompt_onekey.send( + prompt = "Method", + keys = common.METHOD_OPTIONS, + callback = self.get_method ) elif key == "F": self.master.toggle_follow_flows() @@ -324,10 +331,11 @@ class FlowListBox(urwid.ListBox): if self.master.stream: self.master.stop_stream() else: - self.master.path_prompt( - "Stream flows to: ", - self.master.state.last_saveload, - self.master.start_stream_to_path + signals.status_path_prompt.send( + self, + prompt = "Stream flows to: ", + text = self.master.state.last_saveload, + callback = self.master.start_stream_to_path ) else: return urwid.ListBox.keypress(self, size, key) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index b22bbb37..941ceb94 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -492,7 +492,11 @@ class FlowView(urwid.WidgetWrap): def edit_method(self, m): if m == "e": - self.master.prompt_edit("Method", self.flow.request.method, self.set_method_raw) + signals.status_prompt.send( + prompt = "Method: ", + text = self.flow.request.method, + callback = self.set_method_raw + ) else: for i in common.METHOD_OPTIONS: if i[1] == m: @@ -567,14 +571,14 @@ class FlowView(urwid.WidgetWrap): message.content = c.rstrip("\n") elif part == "f": if not message.get_form_urlencoded() and message.content: - self.master.prompt_onekey( - "Existing body is not a URL-encoded form. Clear and edit?", - [ + signals.status_prompt_onekey.send( + prompt = "Existing body is not a URL-encoded form. Clear and edit?", + keys = [ ("yes", "y"), ("no", "n"), ], - self.edit_form_confirm, - message + callback = self.edit_form_confirm, + args = (message,) ) else: self.edit_form(message) @@ -587,13 +591,29 @@ class FlowView(urwid.WidgetWrap): elif part == "q": self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message)) elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.master.prompt_edit("URL", message.url, self.set_url) + signals.status_prompt.send( + prompt = "URL: ", + text = message.url, + callback = self.set_url + ) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.master.prompt_onekey("Method", common.METHOD_OPTIONS, self.edit_method) + signals.status_prompt_onekey.send( + prompt = "Method", + keys = common.METHOD_OPTIONS, + callback = self.edit_method + ) elif part == "c" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: - self.master.prompt_edit("Code", str(message.code), self.set_resp_code) + signals.status_prompt.send( + prompt = "Code: ", + text = str(message.code), + callback = self.set_resp_code + ) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: - self.master.prompt_edit("Message", message.msg, self.set_resp_msg) + signals.status_prompt.send( + prompt = "Message: ", + text = message.msg, + callback = self.set_resp_msg + ) self.master.refresh_flow(self.flow) def _view_nextprev_flow(self, np, flow): @@ -684,9 +704,9 @@ class FlowView(urwid.WidgetWrap): signals.status_message.send(message="Duplicated.") elif key == "e": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.master.prompt_onekey( - "Edit request", - ( + signals.status_prompt_onekey.send( + prompt = "Edit request", + keys = ( ("query", "q"), ("path", "p"), ("url", "u"), @@ -695,18 +715,18 @@ class FlowView(urwid.WidgetWrap): ("raw body", "r"), ("method", "m"), ), - self.edit + callback = self.edit ) else: - self.master.prompt_onekey( - "Edit response", - ( + signals.status_prompt_onekey.send( + prompt = "Edit response", + keys = ( ("code", "c"), ("message", "m"), ("header", "h"), ("raw body", "r"), ), - self.edit + callback = self.edit ) key = None elif key == "f": @@ -727,10 +747,11 @@ class FlowView(urwid.WidgetWrap): elif key == "m": p = list(contentview.view_prompts) p.insert(0, ("Clear", "C")) - self.master.prompt_onekey( - "Display mode", - p, - self.change_this_display_mode + signals.status_prompt_onekey.send( + self, + prompt = "Display mode", + keys = p, + callback = self.change_this_display_mode ) key = None elif key == "p": @@ -748,11 +769,11 @@ class FlowView(urwid.WidgetWrap): self.master.refresh_flow(self.flow) signals.status_message.send(message="Reverted.") elif key == "W": - self.master.path_prompt( - "Save this flow: ", - self.state.last_saveload, - self.master.save_one_flow, - self.flow + signals.status_path_prompt.send( + prompt = "Save this flow: ", + text = self.state.last_saveload, + callback = self.master.save_one_flow, + args = (self.flow,) ) elif key == "v": if conn and conn.content: @@ -763,18 +784,20 @@ class FlowView(urwid.WidgetWrap): else: signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") elif key == "|": - self.master.path_prompt( - "Send flow to script: ", self.state.last_script, - self.master.run_script_once, self.flow + signals.status_path_prompt.send( + prompt = "Send flow to script: ", + text = self.state.last_script, + callback = self.master.run_script_once, + args = (self.flow,) ) elif key == "x": - self.master.prompt_onekey( - "Delete body", - ( + signals.status_prompt_onekey.send( + prompt = "Delete body", + keys = ( ("completely", "c"), ("mark as missing", "m"), ), - self.delete_body + callback = self.delete_body ) key = None elif key == "X": @@ -787,22 +810,24 @@ class FlowView(urwid.WidgetWrap): if not conn.decode(): signals.status_message.send(message="Could not decode - invalid data?") else: - self.master.prompt_onekey( - "Select encoding: ", - ( + signals.status_prompt_onekey.send( + prompt = "Select encoding: ", + keys = ( ("gzip", "z"), ("deflate", "d"), ), - self.encode_callback, - conn + callback = self.encode_callback, + args = (conn,) ) self.master.refresh_flow(self.flow) elif key == "/": last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") search_prompt = "Search body ["+last_search_string+"]: " if last_search_string else "Search body: " - self.master.prompt(search_prompt, - None, - self.search) + signals.status_prompt.send( + prompt = search_prompt, + text = "", + callback = self.search + ) elif key == "n": self.search_again(backwards=False) elif key == "N": diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 0b563c52..eb66e59e 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -338,11 +338,20 @@ class GridEditor(urwid.WidgetWrap): self.walker.delete_focus() elif key == "r": if self.walker.get_current_value() is not None: - self.master.path_prompt("Read file: ", "", self.read_file) + signals.status_path_prompt.send( + self, + prompt = "Read file: ", + text = "", + callback = self.read_file + ) elif key == "R": if self.walker.get_current_value() is not None: - self.master.path_prompt( - "Read unescaped file: ", "", self.read_file, True + signals.status_path_prompt.send( + self, + prompt = "Read unescaped file: ", + text = "", + callback = self.read_file, + args = (True,) ) elif key == "e": o = self.walker.get_current_value() @@ -431,10 +440,10 @@ class HeaderEditor(GridEditor): def handle_key(self, key): if key == "U": - self.master.prompt_onekey( - "Add User-Agent header:", - [(i[0], i[1]) for i in http_uastrings.UASTRINGS], - self.set_user_agent, + signals.status_prompt_onekey.send( + prompt = "Add User-Agent header:", + keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS], + callback = self.set_user_agent, ) return True @@ -500,10 +509,10 @@ class SetHeadersEditor(GridEditor): def handle_key(self, key): if key == "U": - self.master.prompt_onekey( - "Add User-Agent header:", - [(i[0], i[1]) for i in http_uastrings.UASTRINGS], - self.set_user_agent, + signals.status_prompt_onekey.send( + prompt = "Add User-Agent header:", + keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS], + callback = self.set_user_agent, ) return True diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 7b0ec937..8fb35cff 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -1,5 +1,19 @@ - import blinker +# Show a status message in the action bar status_message = blinker.Signal() + +# Prompt for input +status_prompt = blinker.Signal() + +# Prompt for a path +status_path_prompt = blinker.Signal() + +# Prompt for a single keystroke +status_prompt_onekey = blinker.Signal() + +# Call a callback in N seconds call_in = blinker.Signal() + +# Focus the body, footer or header of the main window +focus = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index a29767e4..c1a907bd 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -2,7 +2,7 @@ import time import urwid -from . import pathedit, signals +from . import pathedit, signals, common from .. import utils @@ -11,18 +11,12 @@ class ActionBar(urwid.WidgetWrap): urwid.WidgetWrap.__init__(self, None) self.clear() signals.status_message.connect(self.sig_message) + signals.status_prompt.connect(self.sig_prompt) + signals.status_path_prompt.connect(self.sig_path_prompt) + signals.status_prompt_onekey.connect(self.sig_prompt_onekey) - def clear(self): - self._w = urwid.Text("") - - def selectable(self): - return True - - def path_prompt(self, prompt, text): - self._w = pathedit.PathEdit(prompt, text) - - def prompt(self, prompt, text = ""): - self._w = urwid.Edit(prompt, text or "") + self.prompting = False + self.onekey = False def sig_message(self, sender, message, expire=None): w = urwid.Text(message) @@ -33,6 +27,72 @@ class ActionBar(urwid.WidgetWrap): self.clear() signals.call_in.send(seconds=expire, callback=cb) + def sig_prompt(self, sender, prompt, text, callback, args=()): + signals.focus.send(self, section="footer") + self._w = urwid.Edit(prompt, text or "") + self.prompting = (callback, args) + + def sig_path_prompt(self, sender, prompt, text, callback, args=()): + signals.focus.send(self, section="footer") + self._w = pathedit.PathEdit(prompt, text) + self.prompting = (callback, args) + + def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): + """ + Keys are a set of (word, key) tuples. The appropriate key in the + word is highlighted. + """ + signals.focus.send(self, section="footer") + prompt = [prompt, " ("] + mkup = [] + for i, e in enumerate(keys): + mkup.extend(common.highlight_key(e[0], e[1])) + if i < len(keys)-1: + mkup.append(",") + prompt.extend(mkup) + prompt.append(")? ") + self.onekey = set(i[1] for i in keys) + self._w = urwid.Edit(prompt, "") + self.prompting = (callback, args) + + def selectable(self): + return True + + def keypress(self, size, k): + if self.prompting: + if k == "esc": + self.prompt_done() + elif self.onekey: + if k == "enter": + self.prompt_done() + elif k in self.onekey: + self.prompt_execute(k) + elif k == "enter": + self.prompt_execute() + else: + if common.is_keypress(k): + self._w.keypress(size, k) + else: + return k + + def clear(self): + self._w = urwid.Text("") + + def prompt_done(self): + self.prompting = False + self.onekey = False + signals.status_message.send(message="") + signals.focus.send(self, section="body") + + def prompt_execute(self, txt=None): + if not txt: + txt = self._w.get_edit_text() + p, args = self.prompting + self.prompt_done() + msg = p(txt, *args) + if msg: + signals.status_message.send(message=msg, expire=1) + class StatusBar(urwid.WidgetWrap): def __init__(self, master, helptext): diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 44a5a316..55145c48 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,151 +1,144 @@ import urwid -from . import common, grideditor +from . import common, grideditor, signals, contentview class Window(urwid.Frame): def __init__(self, master, body, header, footer): urwid.Frame.__init__(self, body, header=header, footer=footer) self.master = master + signals.focus.connect(self.sig_focus) + + def sig_focus(self, sender, section): + self.focus_position = section def keypress(self, size, k): - if self.master.prompting: - if k == "esc": - self.master.prompt_cancel() - elif self.master.onekey: - if k == "enter": - self.master.prompt_cancel() - elif k in self.master.onekey: - self.master.prompt_execute(k) - elif k == "enter": - self.master.prompt_execute() - else: - if common.is_keypress(k): - urwid.Frame.keypress(self, self.master.loop.screen_size, k) - else: - return k - else: - k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) - if k == "?": - self.master.view_help() - elif k == "c": - if not self.master.client_playback: - self.master.path_prompt( - "Client replay: ", - self.master.state.last_saveload, - self.master.client_playback_path - ) - else: - self.master.prompt_onekey( - "Stop current client replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.master.stop_client_playback_prompt, - ) - elif k == "H": - self.master.view_grideditor( - grideditor.SetHeadersEditor( - self.master, - self.master.setheaders.get_specs(), - self.master.setheaders.set - ) - ) - elif k == "I": - self.master.view_grideditor( - grideditor.HostPatternEditor( - self.master, - [[x] for x in self.master.get_ignore_filter()], - self.master.edit_ignore_filter - ) + k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) + if k == "?": + self.master.view_help() + elif k == "c": + if not self.master.client_playback: + signals.status_path_prompt.send( + self, + prompt = "Client replay: ", + text = self.master.state.last_saveload, + callback = self.master.client_playback_path ) - elif k == "T": - self.master.view_grideditor( - grideditor.HostPatternEditor( - self.master, - [[x] for x in self.master.get_tcp_filter()], - self.master.edit_tcp_filter - ) - ) - elif k == "i": - self.master.prompt( - "Intercept filter: ", - self.master.state.intercept_txt, - self.master.set_intercept - ) - elif k == "Q": - raise urwid.ExitMainLoop - elif k == "q": - self.master.prompt_onekey( - "Quit", - ( + else: + signals.status_prompt_onekey.send( + self, + prompt = "Stop current client replay?", + keys = ( ("yes", "y"), ("no", "n"), ), - self.master.quit, + callback = self.master.stop_client_playback_prompt, ) - elif k == "M": - self.master.prompt_onekey( - "Global default display mode", - contentview.view_prompts, - self.master.change_default_display_mode + elif k == "H": + self.master.view_grideditor( + grideditor.SetHeadersEditor( + self.master, + self.master.setheaders.get_specs(), + self.master.setheaders.set ) - elif k == "R": - self.master.view_grideditor( - grideditor.ReplaceEditor( - self.master, - self.master.replacehooks.get_specs(), - self.master.replacehooks.set - ) + ) + elif k == "I": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_ignore_filter()], + self.master.edit_ignore_filter ) - elif k == "s": - self.master.view_grideditor( - grideditor.ScriptEditor( - self.master, - [[i.command] for i in self.master.scripts], - self.master.edit_scripts - ) + ) + elif k == "T": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_tcp_filter()], + self.master.edit_tcp_filter ) - elif k == "S": - if not self.master.server_playback: - self.master.path_prompt( - "Server replay path: ", - self.master.state.last_saveload, - self.master.server_playback_path - ) - else: - self.master.prompt_onekey( - "Stop current server replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.master.stop_server_playback_prompt, - ) - elif k == "o": - self.master.prompt_onekey( - "Options", - ( - ("anticache", "a"), - ("anticomp", "c"), - ("showhost", "h"), - ("killextra", "k"), - ("norefresh", "n"), - ("no-upstream-certs", "u"), - ), - self.master._change_options + ) + elif k == "i": + signals.status_prompt.send( + self, + prompt = "Intercept filter: ", + text = self.master.state.intercept_txt, + callback = self.master.set_intercept + ) + elif k == "Q": + raise urwid.ExitMainLoop + elif k == "q": + signals.status_prompt_onekey.send( + self, + prompt = "Quit", + keys = ( + ("yes", "y"), + ("no", "n"), + ), + callback = self.master.quit, + ) + elif k == "M": + signals.status_prompt_onekey.send( + prompt = "Global default display mode", + keys = contentview.view_prompts, + callback = self.master.change_default_display_mode + ) + elif k == "R": + self.master.view_grideditor( + grideditor.ReplaceEditor( + self.master, + self.master.replacehooks.get_specs(), + self.master.replacehooks.set ) - elif k == "t": - self.master.prompt( - "Sticky cookie filter: ", - self.master.stickycookie_txt, - self.master.set_stickycookie + ) + elif k == "s": + self.master.view_grideditor( + grideditor.ScriptEditor( + self.master, + [[i.command] for i in self.master.scripts], + self.master.edit_scripts ) - elif k == "u": - self.master.prompt( - "Sticky auth filter: ", - self.master.stickyauth_txt, - self.master.set_stickyauth + ) + elif k == "S": + if not self.master.server_playback: + signals.status_path_prompt.send( + self, + prompt = "Server replay path: ", + text = self.master.state.last_saveload, + callback = self.master.server_playback_path ) else: - return k - self.footer.redraw() + signals.status_prompt_onekey.send( + self, + prompt = "Stop current server replay?", + keys = ( + ("yes", "y"), + ("no", "n"), + ), + callback = self.master.stop_server_playback_prompt, + ) + elif k == "o": + signals.status_prompt_onekey.send( + prompt = "Options", + keys = ( + ("anticache", "a"), + ("anticomp", "c"), + ("showhost", "h"), + ("killextra", "k"), + ("norefresh", "n"), + ("no-upstream-certs", "u"), + ), + callback = self.master._change_options + ) + elif k == "t": + signals.status_prompt.send( + prompt = "Sticky cookie filter: ", + text = self.master.stickycookie_txt, + callback = self.master.set_stickycookie + ) + elif k == "u": + signals.status_prompt.send( + prompt = "Sticky auth filter: ", + text = self.master.stickyauth_txt, + callback = self.master.set_stickyauth + ) + else: + return k -- cgit v1.2.3 From 572000aa039a789ba35d4ef14e0c096256d6997d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 13:59:34 +1300 Subject: Rationalise prompt calling conventions --- libmproxy/console/common.py | 8 ++++---- libmproxy/console/flowlist.py | 30 +++++++++++++++--------------- libmproxy/console/flowview.py | 18 +++++++++--------- libmproxy/console/grideditor.py | 8 ++++---- libmproxy/console/signals.py | 2 +- libmproxy/console/statusbar.py | 18 ++++++------------ libmproxy/console/window.py | 14 +++++++------- 7 files changed, 46 insertions(+), 52 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 185480db..e4ecde91 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -203,7 +203,7 @@ def save_data(path, data, master, state): def ask_save_path(prompt, data, master, state): - signals.status_path_prompt.send( + signals.status_prompt_path.send( prompt = prompt, text = state.last_saveload, callback = save_data, @@ -260,7 +260,7 @@ def copy_flow(part, scope, flow, master, state): except RuntimeError: def save(k): if k == "y": - ask_save_path("Save data: ", data, master, state) + ask_save_path("Save data", data, master, state) signals.status_prompt_onekey.send( prompt = "Cannot copy binary data to clipboard. Save as file?", keys = ( @@ -316,14 +316,14 @@ def ask_save_body(part, master, state, flow): elif part == "q" and request_has_content: ask_save_path( - "Save request content: ", + "Save request content", flow.request.get_decoded_content(), master, state ) elif part == "s" and response_has_content: ask_save_path( - "Save response content: ", + "Save response content", flow.response.get_decoded_content(), master, state diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index d4dd89d8..f39188bb 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -111,14 +111,14 @@ class ConnectionItem(urwid.WidgetWrap): def save_flows_prompt(self, k): if k == "a": - signals.status_path_prompt.send( - prompt = "Save all flows to: ", + signals.status_prompt_path.send( + prompt = "Save all flows to", text = self.state.last_saveload, callback = self.master.save_flows ) else: - signals.status_path_prompt.send( - prompt = "Save this flow to: ", + signals.status_prompt_path.send( + prompt = "Save this flow to", text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) @@ -150,8 +150,8 @@ class ConnectionItem(urwid.WidgetWrap): self.master.options.replay_ignore_host ) else: - signals.status_path_prompt.send( - prompt = "Server replay path: ", + signals.status_prompt_path.send( + prompt = "Server replay path", text = self.state.last_saveload, callback = self.master.server_playback_path ) @@ -216,8 +216,8 @@ class ConnectionItem(urwid.WidgetWrap): if self.flow.request: self.master.view_flow(self.flow) elif key == "|": - signals.status_path_prompt.send( - prompt = "Send flow to script: ", + signals.status_prompt_path.send( + prompt = "Send flow to script", text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) @@ -269,7 +269,7 @@ class FlowListBox(urwid.ListBox): if k == "e": signals.status_prompt.send( self, - prompt = "Method:", + prompt = "Method", text = "", callback = self.get_method_raw ) @@ -282,7 +282,7 @@ class FlowListBox(urwid.ListBox): def get_url(self, method): signals.status_prompt.send( - prompt = "URL:", + prompt = "URL", text = "http://www.example.com/", callback = self.new_request, args = (method,) @@ -308,14 +308,14 @@ class FlowListBox(urwid.ListBox): self.master.toggle_eventlog() elif key == "l": signals.status_prompt.send( - prompt = "Limit: ", + prompt = "Limit", text = self.master.state.limit_txt, callback = self.master.set_limit ) elif key == "L": - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Load flows: ", + prompt = "Load flows", text = self.master.state.last_saveload, callback = self.master.load_flows_callback ) @@ -331,9 +331,9 @@ class FlowListBox(urwid.ListBox): if self.master.stream: self.master.stop_stream() else: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Stream flows to: ", + prompt = "Stream flows to", text = self.master.state.last_saveload, callback = self.master.start_stream_to_path ) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 941ceb94..b9d5fbca 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -493,7 +493,7 @@ class FlowView(urwid.WidgetWrap): def edit_method(self, m): if m == "e": signals.status_prompt.send( - prompt = "Method: ", + prompt = "Method", text = self.flow.request.method, callback = self.set_method_raw ) @@ -592,7 +592,7 @@ class FlowView(urwid.WidgetWrap): self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message)) elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: signals.status_prompt.send( - prompt = "URL: ", + prompt = "URL", text = message.url, callback = self.set_url ) @@ -604,13 +604,13 @@ class FlowView(urwid.WidgetWrap): ) elif part == "c" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: signals.status_prompt.send( - prompt = "Code: ", + prompt = "Code", text = str(message.code), callback = self.set_resp_code ) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: signals.status_prompt.send( - prompt = "Message: ", + prompt = "Message", text = message.msg, callback = self.set_resp_msg ) @@ -769,8 +769,8 @@ class FlowView(urwid.WidgetWrap): self.master.refresh_flow(self.flow) signals.status_message.send(message="Reverted.") elif key == "W": - signals.status_path_prompt.send( - prompt = "Save this flow: ", + signals.status_prompt_path.send( + prompt = "Save this flow", text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) @@ -784,8 +784,8 @@ class FlowView(urwid.WidgetWrap): else: signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") elif key == "|": - signals.status_path_prompt.send( - prompt = "Send flow to script: ", + signals.status_prompt_path.send( + prompt = "Send flow to script", text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) @@ -822,7 +822,7 @@ class FlowView(urwid.WidgetWrap): self.master.refresh_flow(self.flow) elif key == "/": last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") - search_prompt = "Search body ["+last_search_string+"]: " if last_search_string else "Search body: " + search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" signals.status_prompt.send( prompt = search_prompt, text = "", diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index eb66e59e..e7c9854b 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -338,17 +338,17 @@ class GridEditor(urwid.WidgetWrap): self.walker.delete_focus() elif key == "r": if self.walker.get_current_value() is not None: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Read file: ", + prompt = "Read file", text = "", callback = self.read_file ) elif key == "R": if self.walker.get_current_value() is not None: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Read unescaped file: ", + prompt = "Read unescaped file", text = "", callback = self.read_file, args = (True,) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 8fb35cff..e8944afb 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -7,7 +7,7 @@ status_message = blinker.Signal() status_prompt = blinker.Signal() # Prompt for a path -status_path_prompt = blinker.Signal() +status_prompt_path = blinker.Signal() # Prompt for a single keystroke status_prompt_onekey = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index c1a907bd..7ff26b15 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -12,7 +12,7 @@ class ActionBar(urwid.WidgetWrap): self.clear() signals.status_message.connect(self.sig_message) signals.status_prompt.connect(self.sig_prompt) - signals.status_path_prompt.connect(self.sig_path_prompt) + signals.status_prompt_path.connect(self.sig_path_prompt) signals.status_prompt_onekey.connect(self.sig_prompt_onekey) self.prompting = False @@ -27,14 +27,17 @@ class ActionBar(urwid.WidgetWrap): self.clear() signals.call_in.send(seconds=expire, callback=cb) + def prep_prompt(self, p): + return p.strip() + ": " + def sig_prompt(self, sender, prompt, text, callback, args=()): signals.focus.send(self, section="footer") - self._w = urwid.Edit(prompt, text or "") + self._w = urwid.Edit(self.prep_prompt(prompt), text or "") self.prompting = (callback, args) def sig_path_prompt(self, sender, prompt, text, callback, args=()): signals.focus.send(self, section="footer") - self._w = pathedit.PathEdit(prompt, text) + self._w = pathedit.PathEdit(self.prep_prompt(prompt), text) self.prompting = (callback, args) def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): @@ -230,12 +233,3 @@ class StatusBar(urwid.WidgetWrap): def selectable(self): return True - - def get_edit_text(self): - return self.ab._w.get_edit_text() - - def path_prompt(self, prompt, text): - return self.ab.path_prompt(prompt, text) - - def prompt(self, prompt, text = ""): - self.ab.prompt(prompt, text) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 55145c48..87f06637 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -16,9 +16,9 @@ class Window(urwid.Frame): self.master.view_help() elif k == "c": if not self.master.client_playback: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Client replay: ", + prompt = "Client replay", text = self.master.state.last_saveload, callback = self.master.client_playback_path ) @@ -59,7 +59,7 @@ class Window(urwid.Frame): elif k == "i": signals.status_prompt.send( self, - prompt = "Intercept filter: ", + prompt = "Intercept filter", text = self.master.state.intercept_txt, callback = self.master.set_intercept ) @@ -99,9 +99,9 @@ class Window(urwid.Frame): ) elif k == "S": if not self.master.server_playback: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Server replay path: ", + prompt = "Server replay path", text = self.master.state.last_saveload, callback = self.master.server_playback_path ) @@ -130,13 +130,13 @@ class Window(urwid.Frame): ) elif k == "t": signals.status_prompt.send( - prompt = "Sticky cookie filter: ", + prompt = "Sticky cookie filter", text = self.master.stickycookie_txt, callback = self.master.set_stickycookie ) elif k == "u": signals.status_prompt.send( - prompt = "Sticky auth filter: ", + prompt = "Sticky auth filter", text = self.master.stickyauth_txt, callback = self.master.set_stickyauth ) -- cgit v1.2.3 From 200498e7aa57effd7158c8d735f95c6556203a07 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 14:14:44 +1300 Subject: Simplify the way in which path prompts keep state In the past, we kept the last path the user specified for a number of different path types to pre-seed the path prompt. Now, we no longer distinguish between types, and pre-seed with the last used directory regardless. --- libmproxy/console/__init__.py | 6 ------ libmproxy/console/common.py | 2 -- libmproxy/console/flowlist.py | 6 ------ libmproxy/console/flowview.py | 2 -- libmproxy/console/grideditor.py | 2 -- libmproxy/console/statusbar.py | 22 ++++++++++++++++------ libmproxy/console/window.py | 2 -- 7 files changed, 16 insertions(+), 26 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index d8eb8a41..34abe6f4 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -31,8 +31,6 @@ class ConsoleState(flow.State): self.view_mode = common.VIEW_LIST self.view_flow_mode = common.VIEW_FLOW_REQUEST - self.last_script = "" - self.last_saveload = "" self.flowsettings = weakref.WeakKeyDictionary() def add_flow_setting(self, flow, key, value): @@ -258,7 +256,6 @@ class ConsoleMaster(flow.FlowMaster): self._run_script_method("error", s, f) s.unload() self.refresh_flow(f) - self.state.last_script = command def set_script(self, command): if not command: @@ -266,7 +263,6 @@ class ConsoleMaster(flow.FlowMaster): ret = self.load_script(command) if ret: signals.status_message.send(message=ret) - self.state.last_script = command def toggle_eventlog(self): self.eventlog = not self.eventlog @@ -501,7 +497,6 @@ class ConsoleMaster(flow.FlowMaster): self.help_context = flowview.help_context def _write_flows(self, path, flows): - self.state.last_saveload = path if not path: return path = os.path.expanduser(path) @@ -527,7 +522,6 @@ class ConsoleMaster(flow.FlowMaster): return ret or "Flows loaded from %s"%path def load_flows_path(self, path): - self.state.last_saveload = path reterr = None try: flow.FlowMaster.load_flows_file(self, path) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index e4ecde91..c0593af4 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -193,7 +193,6 @@ def raw_format_flow(f, focus, extended, padding): def save_data(path, data, master, state): if not path: return - state.last_saveload = path path = os.path.expanduser(path) try: with file(path, "wb") as f: @@ -205,7 +204,6 @@ def save_data(path, data, master, state): def ask_save_path(prompt, data, master, state): signals.status_prompt_path.send( prompt = prompt, - text = state.last_saveload, callback = save_data, args = (data, master, state) ) diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index f39188bb..946bd97b 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -113,13 +113,11 @@ class ConnectionItem(urwid.WidgetWrap): if k == "a": signals.status_prompt_path.send( prompt = "Save all flows to", - text = self.state.last_saveload, callback = self.master.save_flows ) else: signals.status_prompt_path.send( prompt = "Save this flow to", - text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) ) @@ -152,7 +150,6 @@ class ConnectionItem(urwid.WidgetWrap): else: signals.status_prompt_path.send( prompt = "Server replay path", - text = self.state.last_saveload, callback = self.master.server_playback_path ) @@ -218,7 +215,6 @@ class ConnectionItem(urwid.WidgetWrap): elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", - text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) ) @@ -316,7 +312,6 @@ class FlowListBox(urwid.ListBox): signals.status_prompt_path.send( self, prompt = "Load flows", - text = self.master.state.last_saveload, callback = self.master.load_flows_callback ) elif key == "n": @@ -334,7 +329,6 @@ class FlowListBox(urwid.ListBox): signals.status_prompt_path.send( self, prompt = "Stream flows to", - text = self.master.state.last_saveload, callback = self.master.start_stream_to_path ) else: diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index b9d5fbca..d63b8a8c 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -771,7 +771,6 @@ class FlowView(urwid.WidgetWrap): elif key == "W": signals.status_prompt_path.send( prompt = "Save this flow", - text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) ) @@ -786,7 +785,6 @@ class FlowView(urwid.WidgetWrap): elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", - text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) ) diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index e7c9854b..dc3bad0e 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -341,7 +341,6 @@ class GridEditor(urwid.WidgetWrap): signals.status_prompt_path.send( self, prompt = "Read file", - text = "", callback = self.read_file ) elif key == "R": @@ -349,7 +348,6 @@ class GridEditor(urwid.WidgetWrap): signals.status_prompt_path.send( self, prompt = "Read unescaped file", - text = "", callback = self.read_file, args = (True,) ) diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7ff26b15..30819188 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -1,4 +1,5 @@ import time +import os.path import urwid @@ -15,8 +16,12 @@ class ActionBar(urwid.WidgetWrap): signals.status_prompt_path.connect(self.sig_path_prompt) signals.status_prompt_onekey.connect(self.sig_prompt_onekey) + self.last_path = "" + self.prompting = False self.onekey = False + self.pathprompt = False + def sig_message(self, sender, message, expire=None): w = urwid.Text(message) @@ -35,9 +40,13 @@ class ActionBar(urwid.WidgetWrap): self._w = urwid.Edit(self.prep_prompt(prompt), text or "") self.prompting = (callback, args) - def sig_path_prompt(self, sender, prompt, text, callback, args=()): + def sig_path_prompt(self, sender, prompt, callback, args=()): signals.focus.send(self, section="footer") - self._w = pathedit.PathEdit(self.prep_prompt(prompt), text) + self._w = pathedit.PathEdit( + self.prep_prompt(prompt), + os.path.dirname(self.last_path) + ) + self.pathprompt = True self.prompting = (callback, args) def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): @@ -71,7 +80,7 @@ class ActionBar(urwid.WidgetWrap): elif k in self.onekey: self.prompt_execute(k) elif k == "enter": - self.prompt_execute() + self.prompt_execute(self._w.get_edit_text()) else: if common.is_keypress(k): self._w.keypress(size, k) @@ -84,12 +93,13 @@ class ActionBar(urwid.WidgetWrap): def prompt_done(self): self.prompting = False self.onekey = False + self.pathprompt = False signals.status_message.send(message="") signals.focus.send(self, section="body") - def prompt_execute(self, txt=None): - if not txt: - txt = self._w.get_edit_text() + def prompt_execute(self, txt): + if self.pathprompt: + self.last_path = txt p, args = self.prompting self.prompt_done() msg = p(txt, *args) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 87f06637..d686f61d 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -19,7 +19,6 @@ class Window(urwid.Frame): signals.status_prompt_path.send( self, prompt = "Client replay", - text = self.master.state.last_saveload, callback = self.master.client_playback_path ) else: @@ -102,7 +101,6 @@ class Window(urwid.Frame): signals.status_prompt_path.send( self, prompt = "Server replay path", - text = self.master.state.last_saveload, callback = self.master.server_playback_path ) else: -- cgit v1.2.3 From c9a09754464e27a5f34295d8a1c0b435248c104c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 15:11:54 +1300 Subject: console: observe state objects for changes, fire event to update status bar. --- libmproxy/console/__init__.py | 9 +++++++++ libmproxy/console/signals.py | 3 +++ libmproxy/console/statusbar.py | 4 ++++ 3 files changed, 16 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 34abe6f4..b593d282 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -33,6 +33,10 @@ class ConsoleState(flow.State): self.flowsettings = weakref.WeakKeyDictionary() + 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 @@ -212,6 +216,10 @@ class ConsoleMaster(flow.FlowMaster): self.start_app(self.options.app_host, self.options.app_port) signals.call_in.connect(self.sig_call_in) + def __setattr__(self, name, value): + self.__dict__[name] = value + signals.update_settings.send(self) + def sig_call_in(self, sender, seconds, callback, args=()): def cb(*_): return callback(*args) @@ -598,6 +606,7 @@ class ConsoleMaster(flow.FlowMaster): elif a == "u": self.server.config.no_upstream_cert =\ not self.server.config.no_upstream_cert + signals.update_settings.send(self) def shutdown(self): self.state.killall(self) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index e8944afb..a62b2a4e 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -17,3 +17,6 @@ call_in = blinker.Signal() # Focus the body, footer or header of the main window focus = blinker.Signal() + +# Fired when settings change +update_settings = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 30819188..7663ee44 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -113,6 +113,10 @@ class StatusBar(urwid.WidgetWrap): self.ab = ActionBar() self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) + signals.update_settings.connect(self.sig_update_settings) + + def sig_update_settings(self, sender): + self.redraw() def keypress(self, *args, **kwargs): return self.ab.keypress(*args, **kwargs) -- cgit v1.2.3 From aa9a38522f5fbfef556578b6018ad365ad5e844d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 15:58:32 +1300 Subject: Remove refresh_flow mechanism in favor of a signal-based implementation --- libmproxy/console/__init__.py | 21 +++++---------- libmproxy/console/flowview.py | 60 ++++++++++++++++++++++++------------------- libmproxy/console/signals.py | 3 +++ 3 files changed, 43 insertions(+), 41 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index b593d282..d988ba84 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -263,7 +263,7 @@ class ConsoleMaster(flow.FlowMaster): if f.error: self._run_script_method("error", s, f) s.unload() - self.refresh_flow(f) + signals.flow_change.send(self, flow = f) def set_script(self, command): if not command: @@ -378,7 +378,7 @@ class ConsoleMaster(flow.FlowMaster): changed = self.tick(self.masterq, timeout=0) if changed: self.loop.draw_screen() - self.statusbar.redraw() + signals.update_settings.send() self.loop.set_alarm_in(0.01, self.ticker) def run(self): @@ -397,7 +397,6 @@ class ConsoleMaster(flow.FlowMaster): screen = self.ui, ) self.view_flowlist() - self.statusbar.redraw() self.server.start_slave( controller.Slave, @@ -446,7 +445,6 @@ class ConsoleMaster(flow.FlowMaster): header = self.header, footer = self.statusbar ) - self.statusbar.redraw() return self.view def view_help(self): @@ -633,15 +631,10 @@ class ConsoleMaster(flow.FlowMaster): def refresh_focus(self): if self.state.view: - self.refresh_flow(self.state.view[self.state.focus]) - - def refresh_flow(self, c): - if hasattr(self.header, "refresh_flow"): - self.header.refresh_flow(c) - if hasattr(self.body, "refresh_flow"): - self.body.refresh_flow(c) - if hasattr(self.statusbar, "refresh_flow"): - self.statusbar.refresh_flow(c) + signals.flow_change.send( + self, + flow = self.state.view[self.state.focus] + ) def process_flow(self, f): if self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay: @@ -649,7 +642,7 @@ class ConsoleMaster(flow.FlowMaster): else: f.reply() self.sync_list_view() - self.refresh_flow(f) + signals.flow_change.send(self, flow = f) def clear_events(self): self.eventlist[:] = [] diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index d63b8a8c..2dd2cb82 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -88,10 +88,17 @@ class FlowViewHeader(urwid.WidgetWrap): def __init__(self, master, f): self.master, self.flow = master, f self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost) - - def refresh_flow(self, f): - if f == self.flow: - self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost) + signals.flow_change.connect(self.sig_flow_change) + + def sig_flow_change(self, sender, flow): + if flow == self.flow: + self._w = common.format_flow( + flow, + False, + extended=True, + padding=0, + hostheader=self.master.showhost + ) class CallbackCache: @@ -119,6 +126,14 @@ class FlowView(urwid.WidgetWrap): self.view_response() else: self.view_request() + signals.flow_change.connect(self.sig_flow_change) + + def sig_flow_change(self, sender, flow): + if flow == self.flow: + if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE and self.flow.response: + self.view_response() + else: + self.view_request() def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request): return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event, is_request) @@ -332,7 +347,7 @@ class FlowView(urwid.WidgetWrap): list_box = urwid.ListBox(merged) list_box.set_focus(focus_position + 2) self._w = self.wrap_body(const, list_box) - self.master.statusbar.redraw() + signals.update_settings.send(self) self.last_displayed_body = list_box @@ -456,7 +471,6 @@ class FlowView(urwid.WidgetWrap): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST body = self.conn_text(self.flow.request) self._w = self.wrap_body(common.VIEW_FLOW_REQUEST, body) - self.master.statusbar.redraw() def view_response(self): self.state.view_flow_mode = common.VIEW_FLOW_RESPONSE @@ -476,19 +490,11 @@ class FlowView(urwid.WidgetWrap): ] ) self._w = self.wrap_body(common.VIEW_FLOW_RESPONSE, body) - self.master.statusbar.redraw() - - def refresh_flow(self, c=None): - if c == self.flow: - if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE and self.flow.response: - self.view_response() - else: - self.view_request() def set_method_raw(self, m): if m: self.flow.request.method = m - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def edit_method(self, m): if m == "e": @@ -501,7 +507,7 @@ class FlowView(urwid.WidgetWrap): for i in common.METHOD_OPTIONS: if i[1] == m: self.flow.request.method = i[0].upper() - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_url(self, url): request = self.flow.request @@ -509,7 +515,7 @@ class FlowView(urwid.WidgetWrap): request.url = str(url) except ValueError: return "Invalid URL." - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_resp_code(self, code): response = self.flow.response @@ -520,12 +526,12 @@ class FlowView(urwid.WidgetWrap): import BaseHTTPServer if BaseHTTPServer.BaseHTTPRequestHandler.responses.has_key(int(code)): response.msg = BaseHTTPServer.BaseHTTPRequestHandler.responses[int(code)][0] - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_resp_msg(self, msg): response = self.flow.response response.msg = msg - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_headers(self, lst, conn): conn.headers = flow.ODictCaseless(lst) @@ -614,7 +620,7 @@ class FlowView(urwid.WidgetWrap): text = message.msg, callback = self.set_resp_msg ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def _view_nextprev_flow(self, np, flow): try: @@ -642,7 +648,7 @@ class FlowView(urwid.WidgetWrap): (self.state.view_flow_mode, "prettyview"), contentview.get_by_shortcut(t) ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def delete_body(self, t): if t == "m": @@ -653,7 +659,7 @@ class FlowView(urwid.WidgetWrap): self.flow.request.content = val else: self.flow.response.content = val - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def keypress(self, size, key): if key == " ": @@ -736,7 +742,7 @@ class FlowView(urwid.WidgetWrap): (self.state.view_flow_mode, "fullcontents"), True ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) signals.status_message.send(message="") elif key == "g": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: @@ -760,13 +766,13 @@ class FlowView(urwid.WidgetWrap): r = self.master.replay_request(self.flow) if r: signals.status_message.send(message=r) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) elif key == "V": if not self.flow.modified(): signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) signals.status_message.send(message="Reverted.") elif key == "W": signals.status_prompt_path.send( @@ -817,7 +823,7 @@ class FlowView(urwid.WidgetWrap): callback = self.encode_callback, args = (conn,) ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) elif key == "/": last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" @@ -839,4 +845,4 @@ class FlowView(urwid.WidgetWrap): "d": "deflate", } conn.encode(encoding_map[key]) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index a62b2a4e..9afde6f4 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -20,3 +20,6 @@ focus = blinker.Signal() # Fired when settings change update_settings = blinker.Signal() + +# Fired when a flow changes +flow_change = blinker.Signal() -- cgit v1.2.3 From 120c8db8a413018bde60d156f480ade001b492ef Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 16:59:11 +1300 Subject: console: refactor the way we keep global view state --- libmproxy/console/__init__.py | 99 ++++++++++++++++++------------------- libmproxy/console/flowdetailview.py | 5 +- libmproxy/console/grideditor.py | 10 ++-- libmproxy/console/help.py | 5 +- libmproxy/console/statusbar.py | 7 ++- 5 files changed, 60 insertions(+), 66 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index d988ba84..f6f8e721 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -188,8 +188,6 @@ class ConsoleMaster(flow.FlowMaster): self.eventlog = options.eventlog self.eventlist = urwid.SimpleListWalker([]) - self.statusbar = None - if options.client_replay: self.client_playback_path(options.client_replay) @@ -287,12 +285,7 @@ class ConsoleMaster(flow.FlowMaster): try: return flow.read_flows_from_paths([path]) except flow.FlowReadError as e: - if not self.statusbar: - print >> sys.stderr, e.strerror - sys.exit(1) - else: - signals.status_message.send(message=e.strerror) - return None + signals.status_message.send(message=e.strerror) def client_playback_path(self, path): flows = self._readflows(path) @@ -326,7 +319,9 @@ class ConsoleMaster(flow.FlowMaster): try: subprocess.call(cmd) except: - signals.status_message.send(message="Can't start editor: %s" % " ".join(c)) + signals.status_message.send( + message = "Can't start editor: %s" % " ".join(c) + ) else: data = open(name, "rb").read() self.ui.start() @@ -386,17 +381,11 @@ class ConsoleMaster(flow.FlowMaster): self.ui.set_terminal_properties(256) self.ui.register_palette(self.palette.palette()) self.flow_list_walker = flowlist.FlowListWalker(self, self.state) - self.view = None - self.statusbar = None - self.header = None - self.body = None self.help_context = None - self.onekey = False self.loop = urwid.MainLoop( - self.view, + urwid.SolidFill("x"), screen = self.ui, ) - self.view_flowlist() self.server.start_slave( controller.Slave, @@ -425,6 +414,11 @@ class ConsoleMaster(flow.FlowMaster): raise urwid.ExitMainLoop signal.signal(signal.SIGINT, exit) + self.loop.set_alarm_in( + 0.0001, + lambda *args: self.view_flowlist() + ) + try: self.loop.run() except Exception: @@ -438,43 +432,38 @@ class ConsoleMaster(flow.FlowMaster): sys.stderr.flush() self.shutdown() - def make_view(self): - self.view = window.Window( - self, - self.body, - header = self.header, - footer = self.statusbar - ) - return self.view - def view_help(self): - h = help.HelpView( + self.loop.widget = window.Window( self, - self.help_context, - (self.statusbar, self.body, self.header) + help.HelpView( + self, + self.help_context, + self.loop.widget, + ), + None, + statusbar.StatusBar(self, help.footer) ) - self.statusbar = statusbar.StatusBar(self, help.footer) - self.body = h - self.header = None - self.loop.widget = self.make_view() def view_flowdetails(self, flow): - h = flowdetailview.FlowDetailsView( + self.loop.widget = window.Window( self, - flow, - (self.statusbar, self.body, self.header) + flowdetailview.FlowDetailsView( + self, + flow, + self.loop.widget + ), + None, + statusbar.StatusBar(self, flowdetailview.footer) ) - self.statusbar = statusbar.StatusBar(self, flowdetailview.footer) - self.body = h - self.header = None - self.loop.widget = self.make_view() def view_grideditor(self, ge): - self.body = ge - self.header = None self.help_context = ge.make_help() - self.statusbar = statusbar.StatusBar(self, grideditor.footer) - self.loop.widget = self.make_view() + self.loop.widget = window.Window( + self, + ge, + None, + statusbar.StatusBar(self, grideditor.FOOTER) + ) def view_flowlist(self): if self.ui.started: @@ -483,24 +472,30 @@ class ConsoleMaster(flow.FlowMaster): self.state.set_focus(self.state.flow_count()) if self.eventlog: - self.body = flowlist.BodyPile(self) + body = flowlist.BodyPile(self) else: - self.body = flowlist.FlowListBox(self) - self.statusbar = statusbar.StatusBar(self, flowlist.footer) - self.header = None + body = flowlist.FlowListBox(self) self.state.view_mode = common.VIEW_LIST - self.loop.widget = self.make_view() self.help_context = flowlist.help_context + self.loop.widget = window.Window( + self, + body, + None, + statusbar.StatusBar(self, flowlist.footer) + ) + self.loop.draw_screen() def view_flow(self, flow): - self.body = flowview.FlowView(self, self.state, flow) - self.header = flowview.FlowViewHeader(self, flow) - self.statusbar = statusbar.StatusBar(self, flowview.footer) self.state.set_focus_flow(flow) self.state.view_mode = common.VIEW_FLOW - self.loop.widget = self.make_view() self.help_context = flowview.help_context + self.loop.widget = window.Window( + self, + flowview.FlowView(self, self.state, flow), + flowview.FlowViewHeader(self, flow), + statusbar.StatusBar(self, flowview.footer) + ) def _write_flows(self, path, flows): if not path: diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index f351bff1..15350ea1 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -18,10 +18,7 @@ class FlowDetailsView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.statusbar = self.state[0] - self.master.body = self.state[1] - self.master.header = self.state[2] - self.master.loop.widget = self.master.make_view() + self.master.loop.widget = self.state return None elif key == "?": key = None diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index dc3bad0e..a1d662c8 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -10,11 +10,11 @@ from .. import utils, filt, script from netlib import http_uastrings -footer = [ +FOOTER = [ ('heading_key', "enter"), ":edit ", ('heading_key', "q"), ":back ", ] -footer_editing = [ +FOOTER_EDITING = [ ('heading_key', "esc"), ":stop editing ", ] @@ -164,12 +164,12 @@ class GridWalker(urwid.ListWalker): self.editing = GridRow( self.focus_col, True, self.editor, self.lst[self.focus] ) - self.editor.master.statusbar.update(footer_editing) + self.editor.master.loop.widget.footer.update(FOOTER_EDITING) self._modified() def stop_edit(self): if self.editing: - self.editor.master.statusbar.update(footer) + self.editor.master.loop.widget.footer.update(FOOTER) self.set_current_value(self.editing.get_edit_value(), False) self.editing = False self._modified() @@ -268,7 +268,7 @@ class GridEditor(urwid.WidgetWrap): self.lb, header = urwid.Pile([title, h]) ) - self.master.statusbar.update("") + self.master.loop.widget.footer.update("") self.show_empty_msg() def show_empty_msg(self): diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 6bb49a92..109a9792 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -180,10 +180,7 @@ class HelpView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.statusbar = self.state[0] - self.master.body = self.state[1] - self.master.header = self.state[2] - self.master.loop.widget = self.master.make_view() + self.master.loop.widget = self.state return None elif key == "?": key = None diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7663ee44..7fb15aa6 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -114,6 +114,7 @@ class StatusBar(urwid.WidgetWrap): self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) signals.update_settings.connect(self.sig_update_settings) + self.redraw() def sig_update_settings(self, sender): self.redraw() @@ -188,7 +189,11 @@ class StatusBar(urwid.WidgetWrap): if self.master.state.follow_focus: opts.append("following") if self.master.stream_large_bodies: - opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size)) + opts.append( + "stream:%s" % utils.pretty_size( + self.master.stream_large_bodies.max_size + ) + ) if opts: r.append("[%s]"%(":".join(opts))) -- cgit v1.2.3 From 08bb07653306ed0f84932391732391227ee07ba2 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:18:53 +1300 Subject: console: signal-based view stack, unifying mechanisms for help, flow views, etc. --- libmproxy/console/__init__.py | 36 +++++++++++++++++------------------- libmproxy/console/common.py | 3 --- libmproxy/console/flowdetailview.py | 8 ++++---- libmproxy/console/flowview.py | 12 +++++------- libmproxy/console/grideditor.py | 2 +- libmproxy/console/help.py | 7 +++---- libmproxy/console/signals.py | 5 +++++ 7 files changed, 35 insertions(+), 38 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index f6f8e721..90c8bd89 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -28,7 +28,6 @@ class ConsoleState(flow.State): self.follow_focus = None self.default_body_view = contentview.get("Auto") - self.view_mode = common.VIEW_LIST self.view_flow_mode = common.VIEW_FLOW_REQUEST self.flowsettings = weakref.WeakKeyDictionary() @@ -210,9 +209,13 @@ class ConsoleMaster(flow.FlowMaster): print >> sys.stderr, "Stream file error:", err sys.exit(1) + self.view_stack = [] + if options.app: self.start_app(self.options.app_host, self.options.app_port) signals.call_in.connect(self.sig_call_in) + signals.pop_view_state.connect(self.sig_pop_view_state) + signals.push_view_state.connect(self.sig_push_view_state) def __setattr__(self, name, value): self.__dict__[name] = value @@ -223,6 +226,13 @@ class ConsoleMaster(flow.FlowMaster): return callback(*args) self.loop.set_alarm_in(seconds, cb) + def sig_pop_view_state(self, sender): + if self.view_stack: + self.loop.widget = self.view_stack.pop() + + def sig_push_view_state(self, sender): + self.view_stack.append(self.loop.widget) + def start_stream_to_path(self, path, mode="wb"): path = os.path.expanduser(path) try: @@ -433,30 +443,25 @@ class ConsoleMaster(flow.FlowMaster): self.shutdown() def view_help(self): + signals.push_view_state.send(self) self.loop.widget = window.Window( self, - help.HelpView( - self, - self.help_context, - self.loop.widget, - ), + help.HelpView(self.help_context), None, statusbar.StatusBar(self, help.footer) ) def view_flowdetails(self, flow): + signals.push_view_state.send(self) self.loop.widget = window.Window( self, - flowdetailview.FlowDetailsView( - self, - flow, - self.loop.widget - ), + flowdetailview.FlowDetailsView(low), None, statusbar.StatusBar(self, flowdetailview.footer) ) def view_grideditor(self, ge): + signals.push_view_state.send(self) self.help_context = ge.make_help() self.loop.widget = window.Window( self, @@ -475,7 +480,6 @@ class ConsoleMaster(flow.FlowMaster): body = flowlist.BodyPile(self) else: body = flowlist.FlowListBox(self) - self.state.view_mode = common.VIEW_LIST self.help_context = flowlist.help_context self.loop.widget = window.Window( @@ -487,8 +491,8 @@ class ConsoleMaster(flow.FlowMaster): self.loop.draw_screen() def view_flow(self, flow): + signals.push_view_state.send(self) self.state.set_focus_flow(flow) - self.state.view_mode = common.VIEW_FLOW self.help_context = flowview.help_context self.loop.widget = window.Window( self, @@ -548,12 +552,6 @@ class ConsoleMaster(flow.FlowMaster): self.state.default_body_view = v self.refresh_focus() - def pop_view(self): - if self.state.view_mode == common.VIEW_FLOW: - self.view_flow(self.state.view[self.state.focus]) - else: - self.view_flowlist() - def edit_scripts(self, scripts): commands = [x[0] for x in scripts] # remove outer array if commands == [s.command for s in self.scripts]: diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index c0593af4..a0590bb1 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -13,9 +13,6 @@ try: except: pyperclip = False -VIEW_LIST = 0 -VIEW_FLOW = 1 - VIEW_FLOW_REQUEST = 0 VIEW_FLOW_RESPONSE = 1 diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index 15350ea1..8bfdae4a 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -1,6 +1,6 @@ from __future__ import absolute_import import urwid -from . import common +from . import common, signals from .. import utils footer = [ @@ -8,8 +8,8 @@ footer = [ ] class FlowDetailsView(urwid.ListBox): - def __init__(self, master, flow, state): - self.master, self.flow, self.state = master, flow, state + def __init__(self, flow): + self.flow = flow urwid.ListBox.__init__( self, self.flowtext() @@ -18,7 +18,7 @@ class FlowDetailsView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.loop.widget = self.state + signals.pop_view_state.send(self) return None elif key == "?": key = None diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 2dd2cb82..fcb967cc 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -114,9 +114,6 @@ cache = CallbackCache() class FlowView(urwid.WidgetWrap): - REQ = 0 - RESP = 1 - highlight_color = "focusfield" def __init__(self, master, state, flow): @@ -633,8 +630,9 @@ class FlowView(urwid.WidgetWrap): new_flow, new_idx = self.state.get_prev(idx) if new_flow is None: signals.status_message.send(message="No more flows!") - return - self.master.view_flow(new_flow) + else: + signals.pop_view_state.send(self) + self.master.view_flow(new_flow) def view_next_flow(self, flow): return self._view_nextprev_flow("next", flow) @@ -673,8 +671,8 @@ class FlowView(urwid.WidgetWrap): conn = self.flow.response if key == "q": - self.master.view_flowlist() - key = None + signals.pop_view_state.send(self) + return None elif key == "tab": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: self.view_response() diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index a1d662c8..4bcc0171 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -323,7 +323,7 @@ class GridEditor(urwid.WidgetWrap): if not i[1] and any([x.strip() for x in i[0]]): res.append(i[0]) self.callback(res, *self.cb_args, **self.cb_kwargs) - self.master.pop_view() + signals.pop_view_state.send(self) elif key in ["h", "left"]: self.walker.left() elif key in ["l", "right"]: diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 109a9792..73cd8a50 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import urwid -from . import common +from . import common, signals from .. import filt, version footer = [ @@ -12,8 +12,7 @@ footer = [ class HelpView(urwid.ListBox): - def __init__(self, master, help_context, state): - self.master, self.state = master, state + def __init__(self, help_context): self.help_context = help_context or [] urwid.ListBox.__init__( self, @@ -180,7 +179,7 @@ class HelpView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.loop.widget = self.state + signals.pop_view_state.send(self) return None elif key == "?": key = None diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 9afde6f4..e4c11f5a 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -23,3 +23,8 @@ update_settings = blinker.Signal() # Fired when a flow changes flow_change = blinker.Signal() + + +# Pop and push view state onto a stack +pop_view_state = blinker.Signal() +push_view_state = blinker.Signal() -- cgit v1.2.3 From 15f65d63f633b6b6a540f74006efe542796aa7e4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:28:13 +1300 Subject: Trigger flow change when flow elements are edited --- libmproxy/console/flowview.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index fcb967cc..04440888 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -532,19 +532,28 @@ class FlowView(urwid.WidgetWrap): def set_headers(self, lst, conn): conn.headers = flow.ODictCaseless(lst) + signals.flow_change.send(self, flow = self.flow) def set_query(self, lst, conn): conn.set_query(flow.ODict(lst)) + signals.flow_change.send(self, flow = self.flow) def set_path_components(self, lst, conn): conn.set_path_components([i[0] for i in lst]) + signals.flow_change.send(self, flow = self.flow) def set_form(self, lst, conn): conn.set_form_urlencoded(flow.ODict(lst)) + signals.flow_change.send(self, flow = self.flow) def edit_form(self, conn): self.master.view_grideditor( - grideditor.URLEncodedFormEditor(self.master, conn.get_form_urlencoded().lst, self.set_form, conn) + grideditor.URLEncodedFormEditor( + self.master, + conn.get_form_urlencoded().lst, + self.set_form, + conn + ) ) def edit_form_confirm(self, key, conn): @@ -586,7 +595,14 @@ class FlowView(urwid.WidgetWrap): else: self.edit_form(message) elif part == "h": - self.master.view_grideditor(grideditor.HeaderEditor(self.master, message.headers.lst, self.set_headers, message)) + self.master.view_grideditor( + grideditor.HeaderEditor( + self.master, + message.headers.lst, + self.set_headers, + message + ) + ) elif part == "p": p = message.get_path_components() p = [[i] for i in p] -- cgit v1.2.3 From a2da38cc8339887abef4efa23cc54fa02c981f3f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:33:25 +1300 Subject: Whitespace, indentation, formatting --- libmproxy/console/flowview.py | 128 +++++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 26 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 04440888..e864cf47 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -87,7 +87,13 @@ footer = [ class FlowViewHeader(urwid.WidgetWrap): def __init__(self, master, f): self.master, self.flow = master, f - self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost) + self._w = common.format_flow( + f, + False, + extended=True, + padding=0, + hostheader=self.master.showhost + ) signals.flow_change.connect(self.sig_flow_change) def sig_flow_change(self, sender, flow): @@ -133,7 +139,14 @@ class FlowView(urwid.WidgetWrap): self.view_request() def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request): - return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event, is_request) + return contentview.get_content_view( + viewmode, + hdrItems, + content, + limit, + self.master.add_event, + is_request + ) def content_view(self, viewmode, conn): full = self.state.get_flow_setting( @@ -219,7 +232,8 @@ class FlowView(urwid.WidgetWrap): def conn_text(self, conn): """ - Same as conn_text_raw, but returns result wrapped in a listbox ready for usage. + Same as conn_text_raw, but returns result wrapped in a listbox ready for + usage. """ headers, msg, body = self.conn_text_raw(conn) merged = self.conn_text_merge(headers, msg, body) @@ -290,7 +304,9 @@ class FlowView(urwid.WidgetWrap): """ runs the previous search again, forwards or backwards. """ - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + last_search_string = self.state.get_flow_setting( + self.flow, "last_search_string" + ) if last_search_string: message = self.search(last_search_string, backwards) if message: @@ -331,7 +347,11 @@ class FlowView(urwid.WidgetWrap): # generate the body, highlight the words and get focus headers, msg, body = self.conn_text_raw(text) try: - body, focus_position = self.search_highlight_text(body, search_string, backwards=backwards) + body, focus_position = self.search_highlight_text( + body, + search_string, + backwards=backwards + ) except SearchError: return "Search not supported in this view." @@ -348,7 +368,11 @@ class FlowView(urwid.WidgetWrap): self.last_displayed_body = list_box - wrapped, wrapped_message = self.search_wrapped_around(last_find_line, last_search_index, backwards) + wrapped, wrapped_message = self.search_wrapped_around( + last_find_line, + last_search_index, + backwards + ) if wrapped: return wrapped_message @@ -356,9 +380,15 @@ class FlowView(urwid.WidgetWrap): def search_get_start(self, search_string): start_line = 0 start_index = 0 - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + last_search_string = self.state.get_flow_setting( + self.flow, + "last_search_string" + ) if search_string == last_search_string: - start_line = self.state.get_flow_setting(self.flow, "last_find_line") + start_line = self.state.get_flow_setting( + self.flow, + "last_find_line" + ) start_index = self.state.get_flow_setting(self.flow, "last_search_index") @@ -403,7 +433,10 @@ class FlowView(urwid.WidgetWrap): found = False text_objects = copy.deepcopy(text_objects) - loop_range = self.search_get_range(len(text_objects), start_line, backwards) + loop_range = self.search_get_range( + len(text_objects), + start_line, backwards + ) for i in loop_range: text_object = text_objects[i] @@ -415,10 +448,19 @@ class FlowView(urwid.WidgetWrap): if i != start_line: start_index = 0 - find_index = self.search_find(text, search_string, start_index, backwards) + find_index = self.search_find( + text, + search_string, + start_index, + backwards + ) if find_index != -1: - new_text = self.search_highlight_object(text, find_index, search_string) + new_text = self.search_highlight_object( + text, + find_index, + search_string + ) text_objects[i] = new_text found = True @@ -436,14 +478,26 @@ class FlowView(urwid.WidgetWrap): focus_pos = None else: if not backwards: - self.state.add_flow_setting(self.flow, "last_search_index", 0) - self.state.add_flow_setting(self.flow, "last_find_line", 0) + self.state.add_flow_setting( + self.flow, "last_search_index", 0 + ) + self.state.add_flow_setting( + self.flow, "last_find_line", 0 + ) else: - self.state.add_flow_setting(self.flow, "last_search_index", None) - self.state.add_flow_setting(self.flow, "last_find_line", len(text_objects) - 1) + self.state.add_flow_setting( + self.flow, "last_search_index", None + ) + self.state.add_flow_setting( + self.flow, "last_find_line", len(text_objects) - 1 + ) - text_objects, focus_pos = self.search_highlight_text(text_objects, - search_string, looping=True, backwards=backwards) + text_objects, focus_pos = self.search_highlight_text( + text_objects, + search_string, + looping=True, + backwards=backwards + ) return text_objects, focus_pos @@ -575,10 +629,12 @@ class FlowView(urwid.WidgetWrap): self.flow.backup() if part == "r": with decoded(message): - # Fix an issue caused by some editors when editing a request/response body. - # Many editors make it hard to save a file without a terminating newline on the last - # line. When editing message bodies, this can cause problems. For now, I just - # strip the newlines off the end of the body when we return from an editor. + # Fix an issue caused by some editors when editing a + # request/response body. Many editors make it hard to save a + # file without a terminating newline on the last line. When + # editing message bodies, this can cause problems. For now, I + # just strip the newlines off the end of the body when we return + # from an editor. c = self.master.spawn_editor(message.content or "") message.content = c.rstrip("\n") elif part == "f": @@ -606,9 +662,22 @@ class FlowView(urwid.WidgetWrap): elif part == "p": p = message.get_path_components() p = [[i] for i in p] - self.master.view_grideditor(grideditor.PathEditor(self.master, p, self.set_path_components, message)) + self.master.view_grideditor( + grideditor.PathEditor( + self.master, + p, + self.set_path_components, + message + ) + ) elif part == "q": - self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message)) + self.master.view_grideditor( + grideditor.QueryEditor( + self.master, + message.get_query().lst, + self.set_query, message + ) + ) elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: signals.status_prompt.send( prompt = "URL", @@ -801,7 +870,9 @@ class FlowView(urwid.WidgetWrap): if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): self.master.spawn_external_viewer(conn.content, t) else: - signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") + signals.status_message.send( + message = "Error! Set $EDITOR or $PAGER." + ) elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", @@ -826,7 +897,9 @@ class FlowView(urwid.WidgetWrap): e = conn.headers.get_first("content-encoding", "identity") if e != "identity": if not conn.decode(): - signals.status_message.send(message="Could not decode - invalid data?") + signals.status_message.send( + message = "Could not decode - invalid data?" + ) else: signals.status_prompt_onekey.send( prompt = "Select encoding: ", @@ -839,7 +912,10 @@ class FlowView(urwid.WidgetWrap): ) signals.flow_change.send(self, flow = self.flow) elif key == "/": - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + last_search_string = self.state.get_flow_setting( + self.flow, + "last_search_string" + ) search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" signals.status_prompt.send( prompt = search_prompt, -- cgit v1.2.3 From 842e23d3e386169d9a90cef2a634c55a3e5fdd8e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 21:00:41 +1300 Subject: Replace far-too-clever decorator LRU cache with something simpler --- libmproxy/console/common.py | 9 +++------ libmproxy/console/flowview.py | 15 +++------------ 2 files changed, 6 insertions(+), 18 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index a0590bb1..2f143f01 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -327,11 +327,7 @@ def ask_save_body(part, master, state, flow): signals.status_message.send(message="No content to save.") -class FlowCache: - @utils.LRUCache(200) - def format_flow(self, *args): - return raw_format_flow(*args) -flowcache = FlowCache() +flowcache = utils.LRUCache(800) def format_flow(f, focus, extended=False, hostheader=False, padding=2): @@ -370,6 +366,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2): d["resp_ctype"] = t[0].split(";")[0] else: d["resp_ctype"] = "" - return flowcache.format_flow( + return flowcache.get( + raw_format_flow, tuple(sorted(d.items())), focus, extended, padding ) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index e864cf47..2c847fba 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -107,16 +107,7 @@ class FlowViewHeader(urwid.WidgetWrap): ) -class CallbackCache: - @utils.LRUCache(200) - def _callback(self, method, *args, **kwargs): - return getattr(self.obj, method)(*args, **kwargs) - - def callback(self, obj, method, *args, **kwargs): - # obj varies! - self.obj = obj - return self._callback(method, *args, **kwargs) -cache = CallbackCache() +cache = utils.LRUCache(200) class FlowView(urwid.WidgetWrap): @@ -158,8 +149,8 @@ class FlowView(urwid.WidgetWrap): limit = sys.maxint else: limit = contentview.VIEW_CUTOFF - description, text_objects = cache.callback( - self, "_cached_content_view", + description, text_objects = cache.get( + self._cached_content_view, viewmode, tuple(tuple(i) for i in conn.headers.lst), conn.content, -- cgit v1.2.3 From 6fb661dab518c036e9333d360f2efc91bc2631ab Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 21:08:18 +1300 Subject: Unwind twisty maze of cache layers. Holy confusing, Batman. --- libmproxy/console/flowview.py | 57 +++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 35 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 2c847fba..1aebb0f0 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -129,43 +129,30 @@ class FlowView(urwid.WidgetWrap): else: self.view_request() - def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request): - return contentview.get_content_view( - viewmode, - hdrItems, - content, - limit, - self.master.add_event, - is_request - ) - def content_view(self, viewmode, conn): - full = self.state.get_flow_setting( - self.flow, - (self.state.view_flow_mode, "fullcontents"), - False - ) - if full: - limit = sys.maxint + if conn.content == CONTENT_MISSING: + msg, body = "", [urwid.Text([("error", "[content missing]")])] + return (msg, body) else: - limit = contentview.VIEW_CUTOFF - description, text_objects = cache.get( - self._cached_content_view, - viewmode, - tuple(tuple(i) for i in conn.headers.lst), - conn.content, - limit, - isinstance(conn, HTTPRequest) - ) - return (description, text_objects) - - def cont_view_handle_missing(self, conn, viewmode): - if conn.content == CONTENT_MISSING: - msg, body = "", [urwid.Text([("error", "[content missing]")])] + full = self.state.get_flow_setting( + self.flow, + (self.state.view_flow_mode, "fullcontents"), + False + ) + if full: + limit = sys.maxint else: - msg, body = self.content_view(viewmode, conn) - - return (msg, body) + limit = contentview.VIEW_CUTOFF + description, text_objects = cache.get( + contentview.get_content_view, + viewmode, + tuple(tuple(i) for i in conn.headers.lst), + conn.content, + limit, + self.master.add_event, + isinstance(conn, HTTPRequest) + ) + return (description, text_objects) def viewmode_get(self, override): return self.state.default_body_view if override is None else override @@ -186,7 +173,7 @@ class FlowView(urwid.WidgetWrap): ) override = self.override_get() viewmode = self.viewmode_get(override) - msg, body = self.cont_view_handle_missing(conn, viewmode) + msg, body = self.content_view(viewmode, conn) return headers, msg, body def conn_text_merge(self, headers, msg, body): -- cgit v1.2.3 From f45ac12d203a9ace1879ed5fa164f2890f9ce207 Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Mon, 23 Mar 2015 23:57:18 -0300 Subject: handles UnicodeDecodeError --- libmproxy/console/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 2f143f01..bc8a2aad 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -252,7 +252,7 @@ def copy_flow(part, scope, flow, master, state): try: master.add_event(str(len(data))) pyperclip.copy(data) - except RuntimeError: + except (RuntimeError, UnicodeDecodeError): def save(k): if k == "y": ask_save_path("Save data", data, master, state) -- cgit v1.2.3 From e6a8863c51f2e2f5809ae0a42fc874afc3c7f908 Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Wed, 25 Mar 2015 16:57:28 -0300 Subject: fixed exception in mitmproxy -c fix #535 --- libmproxy/console/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 90c8bd89..de549ee6 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -293,7 +293,7 @@ class ConsoleMaster(flow.FlowMaster): - a list of flows, otherwise. """ try: - return flow.read_flows_from_paths([path]) + return flow.read_flows_from_paths(path) except flow.FlowReadError as e: signals.status_message.send(message=e.strerror) -- cgit v1.2.3 From c5f153f8d0e50e12b9c9cd6ab0988a1294bcd4be Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Wed, 25 Mar 2015 17:07:31 -0300 Subject: fix #535 now works with both cases: using -c from command line and pressing c in the ui --- libmproxy/console/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index de549ee6..75d1e1ac 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -298,6 +298,8 @@ class ConsoleMaster(flow.FlowMaster): signals.status_message.send(message=e.strerror) def client_playback_path(self, path): + if not isinstance(path, list): + path = [path] flows = self._readflows(path) if flows: self.start_client_playback(flows, False) -- cgit v1.2.3 From fb17eea5e0adcbe8d0dc5c2f029b10b172d5a3e7 Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Wed, 25 Mar 2015 17:12:38 -0300 Subject: fix #535 server replay was failing from ui but working from command line (fixed now too) --- libmproxy/console/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 75d1e1ac..9375f973 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -305,6 +305,8 @@ class ConsoleMaster(flow.FlowMaster): self.start_client_playback(flows, False) def server_playback_path(self, path): + if not isinstance(path, list): + path = [path] flows = self._readflows(path) if flows: self.start_server_playback( -- cgit v1.2.3 From 8a0404ddf81a61642e516dd32025ba54d7d3676f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 14:32:36 +1300 Subject: Beginning of a simpler and more flexible search implementation --- libmproxy/console/flowview.py | 270 +--------------------------------------- libmproxy/console/searchable.py | 83 ++++++++++++ 2 files changed, 88 insertions(+), 265 deletions(-) create mode 100644 libmproxy/console/searchable.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 1aebb0f0..a431d65f 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import os, sys, copy import urwid -from . import common, grideditor, contentview, signals +from . import common, grideditor, contentview, signals, searchable from .. import utils, flow, controller from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded @@ -179,7 +179,7 @@ class FlowView(urwid.WidgetWrap): def conn_text_merge(self, headers, msg, body): """ Grabs what is returned by conn_text_raw and merges them all - toghether, mainly used by conn_text and search + toghether, mainly used by conn_text """ override = self.override_get() viewmode = self.viewmode_get(override) @@ -215,7 +215,7 @@ class FlowView(urwid.WidgetWrap): """ headers, msg, body = self.conn_text_raw(conn) merged = self.conn_text_merge(headers, msg, body) - return urwid.ListBox(merged) + return searchable.Searchable(merged) def _tab(self, content, attr): p = urwid.Text(content) @@ -251,251 +251,6 @@ class FlowView(urwid.WidgetWrap): ) return f - def search_wrapped_around(self, last_find_line, last_search_index, backwards): - """ - returns true if search wrapped around the bottom. - """ - - current_find_line = self.state.get_flow_setting(self.flow, - "last_find_line") - current_search_index = self.state.get_flow_setting(self.flow, - "last_search_index") - - if not backwards: - message = "search hit BOTTOM, continuing at TOP" - if current_find_line <= last_find_line: - return True, message - elif current_find_line == last_find_line: - if current_search_index <= last_search_index: - return True, message - else: - message = "search hit TOP, continuing at BOTTOM" - if current_find_line >= last_find_line: - return True, message - elif current_find_line == last_find_line: - if current_search_index >= last_search_index: - return True, message - - return False, "" - - def search_again(self, backwards=False): - """ - runs the previous search again, forwards or backwards. - """ - last_search_string = self.state.get_flow_setting( - self.flow, "last_search_string" - ) - if last_search_string: - message = self.search(last_search_string, backwards) - if message: - signals.status_message.send(message=message) - else: - message = "no previous searches have been made" - signals.status_message.send(message=message) - - return message - - def search(self, search_string, backwards=False): - """ - similar to view_response or view_request, but instead of just - displaying the conn, it highlights a word that the user is - searching for and handles all the logic surrounding that. - """ - - if not search_string: - search_string = self.state.get_flow_setting(self.flow, - "last_search_string") - if not search_string: - return - - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - text = self.flow.request - const = common.VIEW_FLOW_REQUEST - else: - text = self.flow.response - const = common.VIEW_FLOW_RESPONSE - if not self.flow.response: - return "no response to search in" - - last_find_line = self.state.get_flow_setting(self.flow, - "last_find_line") - last_search_index = self.state.get_flow_setting(self.flow, - "last_search_index") - - # generate the body, highlight the words and get focus - headers, msg, body = self.conn_text_raw(text) - try: - body, focus_position = self.search_highlight_text( - body, - search_string, - backwards=backwards - ) - except SearchError: - return "Search not supported in this view." - - if focus_position == None: - # no results found. - return "no matches for '%s'" % search_string - - # UI stuff. - merged = self.conn_text_merge(headers, msg, body) - list_box = urwid.ListBox(merged) - list_box.set_focus(focus_position + 2) - self._w = self.wrap_body(const, list_box) - signals.update_settings.send(self) - - self.last_displayed_body = list_box - - wrapped, wrapped_message = self.search_wrapped_around( - last_find_line, - last_search_index, - backwards - ) - - if wrapped: - return wrapped_message - - def search_get_start(self, search_string): - start_line = 0 - start_index = 0 - last_search_string = self.state.get_flow_setting( - self.flow, - "last_search_string" - ) - if search_string == last_search_string: - start_line = self.state.get_flow_setting( - self.flow, - "last_find_line" - ) - start_index = self.state.get_flow_setting(self.flow, - "last_search_index") - - if start_index == None: - start_index = 0 - else: - start_index += len(search_string) - - if start_line == None: - start_line = 0 - - else: - self.state.add_flow_setting(self.flow, "last_search_string", - search_string) - - return (start_line, start_index) - - def search_get_range(self, len_text_objects, start_line, backwards): - if not backwards: - loop_range = xrange(start_line, len_text_objects) - else: - loop_range = xrange(start_line, -1, -1) - - return loop_range - - def search_find(self, text, search_string, start_index, backwards): - if backwards == False: - find_index = text.find(search_string, start_index) - else: - if start_index != 0: - start_index -= len(search_string) - else: - start_index = None - - find_index = text.rfind(search_string, 0, start_index) - - return find_index - - def search_highlight_text(self, text_objects, search_string, looping = False, backwards = False): - start_line, start_index = self.search_get_start(search_string) - i = start_line - - found = False - text_objects = copy.deepcopy(text_objects) - loop_range = self.search_get_range( - len(text_objects), - start_line, backwards - ) - for i in loop_range: - text_object = text_objects[i] - - try: - text, style = text_object.get_text() - except AttributeError: - raise SearchError() - - if i != start_line: - start_index = 0 - - find_index = self.search_find( - text, - search_string, - start_index, - backwards - ) - - if find_index != -1: - new_text = self.search_highlight_object( - text, - find_index, - search_string - ) - text_objects[i] = new_text - - found = True - self.state.add_flow_setting(self.flow, "last_search_index", - find_index) - self.state.add_flow_setting(self.flow, "last_find_line", i) - - break - - # handle search WRAP - if found: - focus_pos = i - else : - if looping: - focus_pos = None - else: - if not backwards: - self.state.add_flow_setting( - self.flow, "last_search_index", 0 - ) - self.state.add_flow_setting( - self.flow, "last_find_line", 0 - ) - else: - self.state.add_flow_setting( - self.flow, "last_search_index", None - ) - self.state.add_flow_setting( - self.flow, "last_find_line", len(text_objects) - 1 - ) - - text_objects, focus_pos = self.search_highlight_text( - text_objects, - search_string, - looping=True, - backwards=backwards - ) - - return text_objects, focus_pos - - def search_highlight_object(self, text_object, find_index, search_string): - """ - just a little abstraction - """ - before = text_object[:find_index] - after = text_object[find_index+len(search_string):] - - new_text = urwid.Text( - [ - before, - (self.highlight_color, search_string), - after, - ] - ) - - return new_text - def view_request(self): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST body = self.conn_text(self.flow.request) @@ -506,7 +261,7 @@ class FlowView(urwid.WidgetWrap): if self.flow.response: body = self.conn_text(self.flow.response) else: - body = urwid.ListBox( + body = searchable.Searchable( [ urwid.Text(""), urwid.Text( @@ -889,23 +644,8 @@ class FlowView(urwid.WidgetWrap): args = (conn,) ) signals.flow_change.send(self, flow = self.flow) - elif key == "/": - last_search_string = self.state.get_flow_setting( - self.flow, - "last_search_string" - ) - search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" - signals.status_prompt.send( - prompt = search_prompt, - text = "", - callback = self.search - ) - elif key == "n": - self.search_again(backwards=False) - elif key == "N": - self.search_again(backwards=True) else: - return key + return super(self.__class__, self).keypress(size, key) def encode_callback(self, key, conn): encoding_map = { diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py new file mode 100644 index 00000000..dc8b0bab --- /dev/null +++ b/libmproxy/console/searchable.py @@ -0,0 +1,83 @@ +import urwid + +from . import signals + + +class Highlight(urwid.AttrMap): + def __init__(self, t): + urwid.AttrMap.__init__( + self, + urwid.Text(t.text), + "focusfield", + ) + self.backup = t + + +class Searchable(urwid.ListBox): + def __init__(self, contents): + urwid.ListBox.__init__( + self, + urwid.SimpleFocusListWalker(contents) + ) + + self.search_offset = 0 + self.current_highlight = None + self.search_term = None + + def keypress(self, size, key): + if key == "/": + signals.status_prompt.send( + prompt = "Search for", + text = self.search_term or "", + callback = self.set_search + ) + if key == "n": + self.find_next(False) + if key == "N": + self.find_next(True) + else: + return super(self.__class__, self).keypress(size, key) + + def set_search(self, text): + self.search_term = text or None + self.find_next(False) + + def set_highlight(self, offset): + if self.current_highlight is not None: + old = self.body[self.current_highlight] + self.body[self.current_highlight] = old.backup + if offset is None: + self.current_highlight = None + else: + self.body[offset] = Highlight(self.body[offset]) + self.current_highlight = offset + + def get_text(self, w): + if isinstance(w, urwid.Text): + return w.text + elif isinstance(w, Highlight): + return w.backup.text + else: + return None + + def find_next(self, backwards): + if not self.search_term: + self.set_highlight(None) + return + # Start search at focus + 1 + if backwards: + rng = xrange(len(self.body)-1, -1, -1) + else: + rng = xrange(1, len(self.body)) + for i in rng: + off = (self.focus_position + i)%len(self.body) + w = self.body[off] + txt = self.get_text(w) + if txt and self.search_term in txt: + self.set_highlight(off) + self.set_focus(off, coming_from="above") + self.body._modified() + return + else: + self.set_highlight(None) + signals.status_message.send(message="Search not found.", expire=1) -- cgit v1.2.3 From e4738bdd39c50f682f50eb16d996e8c35ebb053f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 14:39:47 +1300 Subject: Fix search wrap-around offsets. --- libmproxy/console/searchable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index dc8b0bab..5539ecb2 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -19,7 +19,6 @@ class Searchable(urwid.ListBox): self, urwid.SimpleFocusListWalker(contents) ) - self.search_offset = 0 self.current_highlight = None self.search_term = None @@ -68,7 +67,7 @@ class Searchable(urwid.ListBox): if backwards: rng = xrange(len(self.body)-1, -1, -1) else: - rng = xrange(1, len(self.body)) + rng = xrange(1, len(self.body) + 1) for i in rng: off = (self.focus_position + i)%len(self.body) w = self.body[off] -- cgit v1.2.3 From 80c4de5ca462a211fca33d45fe52441b246d4d03 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 15:14:56 +1300 Subject: Keep record of last search term --- libmproxy/console/__init__.py | 3 +-- libmproxy/console/flowview.py | 3 ++- libmproxy/console/searchable.py | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 9375f973..d9e49331 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -27,10 +27,9 @@ class ConsoleState(flow.State): self.focus = None self.follow_focus = None self.default_body_view = contentview.get("Auto") - self.view_flow_mode = common.VIEW_FLOW_REQUEST - self.flowsettings = weakref.WeakKeyDictionary() + self.last_search = None def __setattr__(self, name, value): self.__dict__[name] = value diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index a431d65f..c38b9fea 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -215,7 +215,7 @@ class FlowView(urwid.WidgetWrap): """ headers, msg, body = self.conn_text_raw(conn) merged = self.conn_text_merge(headers, msg, body) - return searchable.Searchable(merged) + return searchable.Searchable(self.state, merged) def _tab(self, content, attr): p = urwid.Text(content) @@ -262,6 +262,7 @@ class FlowView(urwid.WidgetWrap): body = self.conn_text(self.flow.response) else: body = searchable.Searchable( + self.state, [ urwid.Text(""), urwid.Text( diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index 5539ecb2..bd1288d5 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -14,11 +14,12 @@ class Highlight(urwid.AttrMap): class Searchable(urwid.ListBox): - def __init__(self, contents): + def __init__(self, state, contents): urwid.ListBox.__init__( self, urwid.SimpleFocusListWalker(contents) ) + self.state = state self.search_offset = 0 self.current_highlight = None self.search_term = None @@ -38,6 +39,7 @@ class Searchable(urwid.ListBox): return super(self.__class__, self).keypress(size, key) def set_search(self, text): + self.state.last_search = text self.search_term = text or None self.find_next(False) @@ -61,8 +63,11 @@ class Searchable(urwid.ListBox): def find_next(self, backwards): if not self.search_term: - self.set_highlight(None) - return + if self.state.last_search: + self.search_term = self.state.last_search + else: + self.set_highlight(None) + return # Start search at focus + 1 if backwards: rng = xrange(len(self.body)-1, -1, -1) -- cgit v1.2.3 From bdc2fda7ef9d6085f14f52abf2447773a63d9712 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 15:16:20 +1300 Subject: Seems more natural to re-prompt for search every time --- libmproxy/console/searchable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index bd1288d5..8f63c3f5 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -28,7 +28,7 @@ class Searchable(urwid.ListBox): if key == "/": signals.status_prompt.send( prompt = "Search for", - text = self.search_term or "", + text = "", callback = self.set_search ) if key == "n": -- cgit v1.2.3 From 8f0e4a9bdd29d75ff451002f933d86a09f63dbc8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 15:27:17 +1300 Subject: console: simplify view modes. --- libmproxy/console/flowview.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index c38b9fea..d4bdb458 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -154,13 +154,13 @@ class FlowView(urwid.WidgetWrap): ) return (description, text_objects) - def viewmode_get(self, override): + def viewmode_get(self): + override = self.state.get_flow_setting( + self.flow, + (self.state.view_flow_mode, "prettyview") + ) return self.state.default_body_view if override is None else override - def override_get(self): - return self.state.get_flow_setting(self.flow, - (self.state.view_flow_mode, "prettyview")) - def conn_text_raw(self, conn): """ Based on a request/response, conn, returns the elements for @@ -171,8 +171,7 @@ class FlowView(urwid.WidgetWrap): key = "header", val = "text" ) - override = self.override_get() - viewmode = self.viewmode_get(override) + viewmode = self.viewmode_get() msg, body = self.content_view(viewmode, conn) return headers, msg, body @@ -181,26 +180,22 @@ class FlowView(urwid.WidgetWrap): Grabs what is returned by conn_text_raw and merges them all toghether, mainly used by conn_text """ - override = self.override_get() - viewmode = self.viewmode_get(override) - + viewmode = self.viewmode_get() cols = [urwid.Text( [ ("heading", msg), ] ) ] - - if override is not None: - cols.append(urwid.Text([ - " ", - ('heading', "["), - ('heading_key', "m"), - ('heading', (":%s]"%viewmode.name)), - ], - align="right" - ) + cols.append(urwid.Text([ + " ", + ('heading', "["), + ('heading_key', "m"), + ('heading', (":%s]"%viewmode.name)), + ], + align="right" ) + ) title = urwid.AttrWrap(urwid.Columns(cols), "heading") headers.append(title) -- cgit v1.2.3 From cfeee347d95cf56dfaf53c77ac5726bb72347234 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 15:54:29 +1300 Subject: Simplify content generation in flow view --- libmproxy/console/flowview.py | 104 ++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 64 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index d4bdb458..a7a47694 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -161,56 +161,47 @@ class FlowView(urwid.WidgetWrap): ) return self.state.default_body_view if override is None else override - def conn_text_raw(self, conn): - """ - Based on a request/response, conn, returns the elements for - display. - """ - headers = common.format_keyvals( - [(h+":", v) for (h, v) in conn.headers.lst], - key = "header", - val = "text" - ) - viewmode = self.viewmode_get() - msg, body = self.content_view(viewmode, conn) - return headers, msg, body - - def conn_text_merge(self, headers, msg, body): - """ - Grabs what is returned by conn_text_raw and merges them all - toghether, mainly used by conn_text - """ - viewmode = self.viewmode_get() - cols = [urwid.Text( - [ - ("heading", msg), - ] - ) - ] - cols.append(urwid.Text([ - " ", - ('heading', "["), - ('heading_key', "m"), - ('heading', (":%s]"%viewmode.name)), - ], - align="right" - ) - ) - - title = urwid.AttrWrap(urwid.Columns(cols), "heading") - headers.append(title) - headers.extend(body) + def conn_text(self, conn): + if conn: + txt = common.format_keyvals( + [(h+":", v) for (h, v) in conn.headers.lst], + key = "header", + val = "text" + ) + viewmode = self.viewmode_get() + msg, body = self.content_view(viewmode, conn) - return headers + cols = [urwid.Text( + [ + ("heading", msg), + ] + ) + ] + cols.append(urwid.Text([ + " ", + ('heading', "["), + ('heading_key', "m"), + ('heading', (":%s]"%viewmode.name)), + ], + align="right" + ) + ) + title = urwid.AttrWrap(urwid.Columns(cols), "heading") - def conn_text(self, conn): - """ - Same as conn_text_raw, but returns result wrapped in a listbox ready for - usage. - """ - headers, msg, body = self.conn_text_raw(conn) - merged = self.conn_text_merge(headers, msg, body) - return searchable.Searchable(self.state, merged) + txt.append(title) + txt.extend(body) + else: + txt = [ + urwid.Text(""), + urwid.Text( + [ + ("highlight", "No response. Press "), + ("key", "e"), + ("highlight", " and edit any aspect to add one."), + ] + ) + ] + return searchable.Searchable(self.state, txt) def _tab(self, content, attr): p = urwid.Text(content) @@ -253,22 +244,7 @@ class FlowView(urwid.WidgetWrap): def view_response(self): self.state.view_flow_mode = common.VIEW_FLOW_RESPONSE - if self.flow.response: - body = self.conn_text(self.flow.response) - else: - body = searchable.Searchable( - self.state, - [ - urwid.Text(""), - urwid.Text( - [ - ("highlight", "No response. Press "), - ("key", "e"), - ("highlight", " and edit any aspect to add one."), - ] - ) - ] - ) + body = self.conn_text(self.flow.response) self._w = self.wrap_body(common.VIEW_FLOW_RESPONSE, body) def set_method_raw(self, m): -- cgit v1.2.3 From cacd09fafc19b323c46c4c565d0044593b677e17 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 17:40:43 +1300 Subject: console: add a tabs widget, and use it for flowview. --- libmproxy/console/__init__.py | 7 +-- libmproxy/console/flowview.py | 124 +++++++++++++++++------------------------- libmproxy/console/tabs.py | 35 ++++++++++++ 3 files changed, 87 insertions(+), 79 deletions(-) create mode 100644 libmproxy/console/tabs.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index d9e49331..35126eba 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -27,7 +27,6 @@ class ConsoleState(flow.State): self.focus = None self.follow_focus = None self.default_body_view = contentview.get("Auto") - self.view_flow_mode = common.VIEW_FLOW_REQUEST self.flowsettings = weakref.WeakKeyDictionary() self.last_search = None @@ -458,7 +457,7 @@ class ConsoleMaster(flow.FlowMaster): signals.push_view_state.send(self) self.loop.widget = window.Window( self, - flowdetailview.FlowDetailsView(low), + flowdetailview.FlowDetailsView(flow), None, statusbar.StatusBar(self, flowdetailview.footer) ) @@ -493,13 +492,13 @@ class ConsoleMaster(flow.FlowMaster): ) self.loop.draw_screen() - def view_flow(self, flow): + def view_flow(self, flow, tab_offset=0): signals.push_view_state.send(self) self.state.set_focus_flow(flow) self.help_context = flowview.help_context self.loop.widget = window.Window( self, - flowview.FlowView(self, self.state, flow), + flowview.FlowView(self, self.state, flow, tab_offset), flowview.FlowViewHeader(self, flow), statusbar.StatusBar(self, flowview.footer) ) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index a7a47694..ffe499d7 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import os, sys, copy import urwid -from . import common, grideditor, contentview, signals, searchable +from . import common, grideditor, contentview, signals, searchable, tabs from .. import utils, flow, controller from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded @@ -109,25 +109,48 @@ class FlowViewHeader(urwid.WidgetWrap): cache = utils.LRUCache(200) +TAB_REQ = 0 +TAB_RESP = 1 -class FlowView(urwid.WidgetWrap): +class FlowView(tabs.Tabs): highlight_color = "focusfield" - def __init__(self, master, state, flow): + def __init__(self, master, state, flow, tab_offset): self.master, self.state, self.flow = master, state, flow + tabs.Tabs.__init__(self, + [ + (self.tab_request, self.view_request), + (self.tab_response, self.view_response), + ], + tab_offset + ) + self.show() self.last_displayed_body = None - if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: - self.view_response() - else: - self.view_request() signals.flow_change.connect(self.sig_flow_change) + def tab_request(self): + if self.flow.intercepted and not self.flow.reply.acked and not self.flow.response: + return "Request intercepted" + else: + return "Request" + + def tab_response(self): + if self.flow.intercepted and not self.flow.reply.acked and self.flow.response: + return "Response intercepted" + else: + return "Response" + + def view_request(self): + return self.conn_text(self.flow.request) + + def view_response(self): + return self.conn_text(self.flow.response) + + + def sig_flow_change(self, sender, flow): if flow == self.flow: - if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE and self.flow.response: - self.view_response() - else: - self.view_request() + self.show() def content_view(self, viewmode, conn): if conn.content == CONTENT_MISSING: @@ -136,7 +159,7 @@ class FlowView(urwid.WidgetWrap): else: full = self.state.get_flow_setting( self.flow, - (self.state.view_flow_mode, "fullcontents"), + (self.tab_offset, "fullcontents"), False ) if full: @@ -157,7 +180,7 @@ class FlowView(urwid.WidgetWrap): def viewmode_get(self): override = self.state.get_flow_setting( self.flow, - (self.state.view_flow_mode, "prettyview") + (self.tab_offset, "prettyview") ) return self.state.default_body_view if override is None else override @@ -203,50 +226,6 @@ class FlowView(urwid.WidgetWrap): ] return searchable.Searchable(self.state, txt) - def _tab(self, content, attr): - p = urwid.Text(content) - p = urwid.Padding(p, align="left", width=("relative", 100)) - p = urwid.AttrWrap(p, attr) - return p - - def wrap_body(self, active, body): - parts = [] - - if self.flow.intercepted and not self.flow.reply.acked and not self.flow.response: - qt = "Request intercepted" - else: - qt = "Request" - if active == common.VIEW_FLOW_REQUEST: - parts.append(self._tab(qt, "heading")) - else: - parts.append(self._tab(qt, "heading_inactive")) - - if self.flow.intercepted and not self.flow.reply.acked and self.flow.response: - st = "Response intercepted" - else: - st = "Response" - if active == common.VIEW_FLOW_RESPONSE: - parts.append(self._tab(st, "heading")) - else: - parts.append(self._tab(st, "heading_inactive")) - - h = urwid.Columns(parts) - f = urwid.Frame( - body, - header=h - ) - return f - - def view_request(self): - self.state.view_flow_mode = common.VIEW_FLOW_REQUEST - body = self.conn_text(self.flow.request) - self._w = self.wrap_body(common.VIEW_FLOW_REQUEST, body) - - def view_response(self): - self.state.view_flow_mode = common.VIEW_FLOW_RESPONSE - body = self.conn_text(self.flow.response) - self._w = self.wrap_body(common.VIEW_FLOW_RESPONSE, body) - def set_method_raw(self, m): if m: self.flow.request.method = m @@ -320,7 +299,7 @@ class FlowView(urwid.WidgetWrap): self.edit_form(conn) def edit(self, part): - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + if self.tab_offset == TAB_REQ: message = self.flow.request else: if not self.flow.response: @@ -383,25 +362,25 @@ class FlowView(urwid.WidgetWrap): self.set_query, message ) ) - elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + elif part == "u" and self.tab_offset == TAB_REQ: signals.status_prompt.send( prompt = "URL", text = message.url, callback = self.set_url ) - elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + elif part == "m" and self.tab_offset == TAB_REQ: signals.status_prompt_onekey.send( prompt = "Method", keys = common.METHOD_OPTIONS, callback = self.edit_method ) - elif part == "c" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: + elif part == "c" and self.tab_offset == TAB_RESP: signals.status_prompt.send( prompt = "Code", text = str(message.code), callback = self.set_resp_code ) - elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: + elif part == "m" and self.tab_offset == TAB_RESP: signals.status_prompt.send( prompt = "Message", text = message.msg, @@ -422,7 +401,7 @@ class FlowView(urwid.WidgetWrap): signals.status_message.send(message="No more flows!") else: signals.pop_view_state.send(self) - self.master.view_flow(new_flow) + self.master.view_flow(new_flow, self.tab_offset) def view_next_flow(self, flow): return self._view_nextprev_flow("next", flow) @@ -433,7 +412,7 @@ class FlowView(urwid.WidgetWrap): def change_this_display_mode(self, t): self.state.add_flow_setting( self.flow, - (self.state.view_flow_mode, "prettyview"), + (self.tab_offset, "prettyview"), contentview.get_by_shortcut(t) ) signals.flow_change.send(self, flow = self.flow) @@ -443,7 +422,7 @@ class FlowView(urwid.WidgetWrap): val = CONTENT_MISSING else: val = None - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + if self.tab_offset == TAB_REQ: self.flow.request.content = val else: self.flow.response.content = val @@ -455,7 +434,7 @@ class FlowView(urwid.WidgetWrap): return key = common.shortcuts(key) - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + if self.tab_offset == TAB_REQ: conn = self.flow.request else: conn = self.flow.response @@ -463,11 +442,6 @@ class FlowView(urwid.WidgetWrap): if key == "q": signals.pop_view_state.send(self) return None - elif key == "tab": - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.view_response() - else: - self.view_request() elif key in ("up", "down", "page up", "page down"): # Why doesn't this just work?? self._w.keypress(size, key) @@ -478,7 +452,7 @@ class FlowView(urwid.WidgetWrap): self.master.accept_all() self.master.view_flow(self.flow) elif key == "b": - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + if self.tab_offset == TAB_REQ: common.ask_save_body("q", self.master, self.state, self.flow) else: common.ask_save_body("s", self.master, self.state, self.flow) @@ -497,7 +471,7 @@ class FlowView(urwid.WidgetWrap): self.master.view_flow(f) signals.status_message.send(message="Duplicated.") elif key == "e": - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + if self.tab_offset == TAB_REQ: signals.status_prompt_onekey.send( prompt = "Edit request", keys = ( @@ -527,13 +501,13 @@ class FlowView(urwid.WidgetWrap): signals.status_message.send(message="Loading all body data...") self.state.add_flow_setting( self.flow, - (self.state.view_flow_mode, "fullcontents"), + (self.tab_state, "fullcontents"), True ) signals.flow_change.send(self, flow = self.flow) signals.status_message.send(message="") elif key == "g": - if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: + if self.tab_offset == TAB_REQ: scope = "q" else: scope = "s" diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py new file mode 100644 index 00000000..3245f430 --- /dev/null +++ b/libmproxy/console/tabs.py @@ -0,0 +1,35 @@ +import urwid + +class Tabs(urwid.WidgetWrap): + def __init__(self, tabs, tab_offset=0): + urwid.WidgetWrap.__init__(self, "") + self.tab_offset = tab_offset + self.tabs = tabs + self.show() + + def _tab(self, content, attr): + p = urwid.Text(content) + p = urwid.Padding(p, align="left", width=("relative", 100)) + p = urwid.AttrWrap(p, attr) + return p + + def keypress(self, size, key): + if key == "tab": + self.tab_offset = (self.tab_offset + 1)%(len(self.tabs)) + self.show() + else: + return key + + def show(self): + headers = [] + for i in range(len(self.tabs)): + txt = self.tabs[i][0]() + if i == self.tab_offset: + headers.append(self._tab(txt, "heading")) + else: + headers.append(self._tab(txt, "heading_inactive")) + headers = urwid.Columns(headers) + self._w = urwid.Frame( + body = self.tabs[self.tab_offset][1](), + header = headers + ) -- cgit v1.2.3 From 8f5cf833d08aba685263554d0bd89f922cd6afae Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Mar 2015 19:21:54 +1300 Subject: Add flow detail view as a tab in the flow view --- libmproxy/console/__init__.py | 9 -- libmproxy/console/flowdetailview.py | 176 +++++++++++++++++------------------- libmproxy/console/flowview.py | 11 ++- libmproxy/console/searchable.py | 2 +- libmproxy/console/tabs.py | 8 +- 5 files changed, 95 insertions(+), 111 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 35126eba..6e9d227b 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -453,15 +453,6 @@ class ConsoleMaster(flow.FlowMaster): statusbar.StatusBar(self, help.footer) ) - def view_flowdetails(self, flow): - signals.push_view_state.send(self) - self.loop.widget = window.Window( - self, - flowdetailview.FlowDetailsView(flow), - None, - statusbar.StatusBar(self, flowdetailview.footer) - ) - def view_grideditor(self, ge): signals.push_view_state.send(self) self.help_context = ge.make_help() diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index 8bfdae4a..99f2a262 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -1,110 +1,100 @@ from __future__ import absolute_import import urwid -from . import common, signals +from . import common, signals, searchable from .. import utils -footer = [ - ('heading_key', "q"), ":back ", -] +def flowdetails(state, flow): + text = [] -class FlowDetailsView(urwid.ListBox): - def __init__(self, flow): - self.flow = flow - urwid.ListBox.__init__( - self, - self.flowtext() - ) - - def keypress(self, size, key): - key = common.shortcuts(key) - if key == "q": - signals.pop_view_state.send(self) - return None - elif key == "?": - key = None - return urwid.ListBox.keypress(self, size, key) - - def flowtext(self): - text = [] + cc = flow.client_conn + sc = flow.server_conn + req = flow.request + resp = flow.response - title = urwid.Text("Flow details") - title = urwid.Padding(title, align="left", width=("relative", 100)) - title = urwid.AttrWrap(title, "heading") - text.append(title) + if sc: + text.append(urwid.Text([("head", "Server Connection:")])) + parts = [ + ["Address", "%s:%s" % sc.address()], + ] - cc = self.flow.client_conn - sc = self.flow.server_conn - req = self.flow.request - resp = self.flow.response + text.extend( + common.format_keyvals(parts, key="key", val="text", indent=4) + ) - if sc: - text.append(urwid.Text([("head", "Server Connection:")])) + c = sc.cert + if c: + text.append(urwid.Text([("head", "Server Certificate:")])) parts = [ - ["Address", "%s:%s" % sc.address()], + ["Type", "%s, %s bits"%c.keyinfo], + ["SHA1 digest", c.digest("sha1")], + ["Valid to", str(c.notafter)], + ["Valid from", str(c.notbefore)], + ["Serial", str(c.serial)], + [ + "Subject", + urwid.BoxAdapter( + urwid.ListBox( + common.format_keyvals( + c.subject, + key="highlight", + val="text" + ) + ), + len(c.subject) + ) + ], + [ + "Issuer", + urwid.BoxAdapter( + urwid.ListBox( + common.format_keyvals( + c.issuer, key="highlight", val="text" + ) + ), + len(c.issuer) + ) + ] ] - text.extend(common.format_keyvals(parts, key="key", val="text", indent=4)) - - c = sc.cert - if c: - text.append(urwid.Text([("head", "Server Certificate:")])) - parts = [ - ["Type", "%s, %s bits"%c.keyinfo], - ["SHA1 digest", c.digest("sha1")], - ["Valid to", str(c.notafter)], - ["Valid from", str(c.notbefore)], - ["Serial", str(c.serial)], - [ - "Subject", - urwid.BoxAdapter( - urwid.ListBox(common.format_keyvals(c.subject, key="highlight", val="text")), - len(c.subject) - ) - ], + if c.altnames: + parts.append( [ - "Issuer", - urwid.BoxAdapter( - urwid.ListBox(common.format_keyvals(c.issuer, key="highlight", val="text")), - len(c.issuer) - ) + "Alt names", + ", ".join(c.altnames) ] - ] - - if c.altnames: - parts.append( - [ - "Alt names", - ", ".join(c.altnames) - ] - ) - text.extend(common.format_keyvals(parts, key="key", val="text", indent=4)) + ) + text.extend( + common.format_keyvals(parts, key="key", val="text", indent=4) + ) - if cc: - text.append(urwid.Text([("head", "Client Connection:")])) + if cc: + text.append(urwid.Text([("head", "Client Connection:")])) - parts = [ - ["Address", "%s:%s" % cc.address()], - # ["Requests", "%s"%cc.requestcount], - ] + parts = [ + ["Address", "%s:%s" % cc.address()], + # ["Requests", "%s"%cc.requestcount], + ] - text.extend(common.format_keyvals(parts, key="key", val="text", indent=4)) - - parts = [] - - parts.append(["Client conn. established", utils.format_timestamp_with_milli(cc.timestamp_start) if (cc and cc.timestamp_start) else "active"]) - parts.append(["Server conn. initiated", utils.format_timestamp_with_milli(sc.timestamp_start) if sc else "active" ]) - parts.append(["Server conn. TCP handshake", utils.format_timestamp_with_milli(sc.timestamp_tcp_setup) if (sc and sc.timestamp_tcp_setup) else "active"]) - if sc.ssl_established: - parts.append(["Server conn. SSL handshake", utils.format_timestamp_with_milli(sc.timestamp_ssl_setup) if sc.timestamp_ssl_setup else "active"]) - parts.append(["Client conn. SSL handshake", utils.format_timestamp_with_milli(cc.timestamp_ssl_setup) if (cc and cc.timestamp_ssl_setup) else "active"]) - parts.append(["First request byte", utils.format_timestamp_with_milli(req.timestamp_start)]) - parts.append(["Request complete", utils.format_timestamp_with_milli(req.timestamp_end) if req.timestamp_end else "active"]) - parts.append(["First response byte", utils.format_timestamp_with_milli(resp.timestamp_start) if resp else "active"]) - parts.append(["Response complete", utils.format_timestamp_with_milli(resp.timestamp_end) if (resp and resp.timestamp_end) else "active"]) - - # sort operations by timestamp - parts = sorted(parts, key=lambda p: p[1]) + text.extend( + common.format_keyvals(parts, key="key", val="text", indent=4) + ) - text.append(urwid.Text([("head", "Timing:")])) - text.extend(common.format_keyvals(parts, key="key", val="text", indent=4)) - return text + parts = [] + + parts.append(["Client conn. established", utils.format_timestamp_with_milli(cc.timestamp_start) if (cc and cc.timestamp_start) else "active"]) + parts.append(["Server conn. initiated", utils.format_timestamp_with_milli(sc.timestamp_start) if sc else "active" ]) + parts.append(["Server conn. TCP handshake", utils.format_timestamp_with_milli(sc.timestamp_tcp_setup) if (sc and sc.timestamp_tcp_setup) else "active"]) + if sc.ssl_established: + parts.append(["Server conn. SSL handshake", utils.format_timestamp_with_milli(sc.timestamp_ssl_setup) if sc.timestamp_ssl_setup else "active"]) + parts.append(["Client conn. SSL handshake", utils.format_timestamp_with_milli(cc.timestamp_ssl_setup) if (cc and cc.timestamp_ssl_setup) else "active"]) + parts.append(["First request byte", utils.format_timestamp_with_milli(req.timestamp_start)]) + parts.append(["Request complete", utils.format_timestamp_with_milli(req.timestamp_end) if req.timestamp_end else "active"]) + parts.append(["First response byte", utils.format_timestamp_with_milli(resp.timestamp_start) if resp else "active"]) + parts.append(["Response complete", utils.format_timestamp_with_milli(resp.timestamp_end) if (resp and resp.timestamp_end) else "active"]) + + # sort operations by timestamp + parts = sorted(parts, key=lambda p: p[1]) + + text.append(urwid.Text([("head", "Timing:")])) + text.extend(common.format_keyvals(parts, key="key", val="text", indent=4)) + return searchable.Searchable(state, text) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index ffe499d7..514340c9 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -2,6 +2,7 @@ from __future__ import absolute_import import os, sys, copy import urwid from . import common, grideditor, contentview, signals, searchable, tabs +from . import flowdetailview from .. import utils, flow, controller from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded @@ -65,7 +66,6 @@ def _mkhelp(): ("w", "save all flows matching current limit"), ("W", "save this flow"), ("x", "delete body"), - ("X", "view flow details"), ("z", "encode/decode a request/response"), ("tab", "toggle request/response view"), ("space", "next flow"), @@ -121,6 +121,7 @@ class FlowView(tabs.Tabs): [ (self.tab_request, self.view_request), (self.tab_response, self.view_response), + (self.tab_details, self.view_details), ], tab_offset ) @@ -140,13 +141,17 @@ class FlowView(tabs.Tabs): else: return "Response" + def tab_details(self): + return "Detail" + def view_request(self): return self.conn_text(self.flow.request) def view_response(self): return self.conn_text(self.flow.response) - + def view_details(self): + return flowdetailview.flowdetails(self.state, self.flow) def sig_flow_change(self, sender, flow): if flow == self.flow: @@ -568,8 +573,6 @@ class FlowView(tabs.Tabs): callback = self.delete_body ) key = None - elif key == "X": - self.master.view_flowdetails(self.flow) elif key == "z": if conn: self.flow.backup() diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index 8f63c3f5..9d66c718 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -36,7 +36,7 @@ class Searchable(urwid.ListBox): if key == "N": self.find_next(True) else: - return super(self.__class__, self).keypress(size, key) + return key def set_search(self, text): self.state.last_search = text diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index 3245f430..b8943ad4 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -8,8 +8,8 @@ class Tabs(urwid.WidgetWrap): self.show() def _tab(self, content, attr): - p = urwid.Text(content) - p = urwid.Padding(p, align="left", width=("relative", 100)) + p = urwid.Text(content, align="center") + p = urwid.Padding(p, align="center", width=("relative", 100)) p = urwid.AttrWrap(p, attr) return p @@ -18,7 +18,7 @@ class Tabs(urwid.WidgetWrap): self.tab_offset = (self.tab_offset + 1)%(len(self.tabs)) self.show() else: - return key + return self._w.keypress(size, key) def show(self): headers = [] @@ -28,7 +28,7 @@ class Tabs(urwid.WidgetWrap): headers.append(self._tab(txt, "heading")) else: headers.append(self._tab(txt, "heading_inactive")) - headers = urwid.Columns(headers) + headers = urwid.Columns(headers, dividechars=1) self._w = urwid.Frame( body = self.tabs[self.tab_offset][1](), header = headers -- cgit v1.2.3 From a32698fc0410068774a240d9485934c889102223 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 30 Mar 2015 10:34:02 +1300 Subject: Update statusbar when flow list is cleared. --- libmproxy/console/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 6e9d227b..660024cc 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -598,6 +598,7 @@ class ConsoleMaster(flow.FlowMaster): def sync_list_view(self): self.flow_list_walker._modified() + signals.update_settings.send(self) def clear_flows(self): self.state.clear() -- cgit v1.2.3 From e964983e8137ac7ae77a473dbb308ac910c72ca6 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 30 Mar 2015 11:53:10 +1300 Subject: Disable keystrokes requiring a request/response on details page --- libmproxy/console/flowview.py | 180 ++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 85 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 514340c9..538f42f0 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -67,10 +67,10 @@ def _mkhelp(): ("W", "save this flow"), ("x", "delete body"), ("z", "encode/decode a request/response"), - ("tab", "toggle request/response view"), + ("tab", "next tab"), ("space", "next flow"), ("|", "run script on this flow"), - ("/", "search in response body (case sensitive)"), + ("/", "search (case sensitive)"), ("n", "repeat search forward"), ("N", "repeat search backwards"), ] @@ -367,25 +367,25 @@ class FlowView(tabs.Tabs): self.set_query, message ) ) - elif part == "u" and self.tab_offset == TAB_REQ: + elif part == "u": signals.status_prompt.send( prompt = "URL", text = message.url, callback = self.set_url ) - elif part == "m" and self.tab_offset == TAB_REQ: + elif part == "m": signals.status_prompt_onekey.send( prompt = "Method", keys = common.METHOD_OPTIONS, callback = self.edit_method ) - elif part == "c" and self.tab_offset == TAB_RESP: + elif part == "c": signals.status_prompt.send( prompt = "Code", text = str(message.code), callback = self.set_resp_code ) - elif part == "m" and self.tab_offset == TAB_RESP: + elif part == "m": signals.status_prompt.send( prompt = "Message", text = message.msg, @@ -441,8 +441,10 @@ class FlowView(tabs.Tabs): key = common.shortcuts(key) if self.tab_offset == TAB_REQ: conn = self.flow.request - else: + elif self.tab_offset == TAB_RESP: conn = self.flow.response + else: + conn = None if key == "q": signals.pop_view_state.send(self) @@ -456,11 +458,6 @@ class FlowView(tabs.Tabs): elif key == "A": self.master.accept_all() self.master.view_flow(self.flow) - elif key == "b": - if self.tab_offset == TAB_REQ: - common.ask_save_body("q", self.master, self.state, self.flow) - else: - common.ask_save_body("s", self.master, self.state, self.flow) elif key == "d": if self.state.flow_count() == 1: self.master.view_flowlist() @@ -475,58 +472,6 @@ class FlowView(tabs.Tabs): f = self.master.duplicate_flow(self.flow) self.master.view_flow(f) signals.status_message.send(message="Duplicated.") - elif key == "e": - if self.tab_offset == TAB_REQ: - signals.status_prompt_onekey.send( - prompt = "Edit request", - keys = ( - ("query", "q"), - ("path", "p"), - ("url", "u"), - ("header", "h"), - ("form", "f"), - ("raw body", "r"), - ("method", "m"), - ), - callback = self.edit - ) - else: - signals.status_prompt_onekey.send( - prompt = "Edit response", - keys = ( - ("code", "c"), - ("message", "m"), - ("header", "h"), - ("raw body", "r"), - ), - callback = self.edit - ) - key = None - elif key == "f": - signals.status_message.send(message="Loading all body data...") - self.state.add_flow_setting( - self.flow, - (self.tab_state, "fullcontents"), - True - ) - signals.flow_change.send(self, flow = self.flow) - signals.status_message.send(message="") - elif key == "g": - if self.tab_offset == TAB_REQ: - scope = "q" - else: - scope = "s" - common.ask_copy_part(scope, self.flow, self.master, self.state) - elif key == "m": - p = list(contentview.view_prompts) - p.insert(0, ("Clear", "C")) - signals.status_prompt_onekey.send( - self, - prompt = "Display mode", - keys = p, - callback = self.change_this_display_mode - ) - key = None elif key == "p": self.view_prev_flow(self.flow) elif key == "r": @@ -547,34 +492,97 @@ class FlowView(tabs.Tabs): callback = self.master.save_one_flow, args = (self.flow,) ) - elif key == "v": - if conn and conn.content: - t = conn.headers["content-type"] or [None] - t = t[0] - if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): - self.master.spawn_external_viewer(conn.content, t) - else: - signals.status_message.send( - message = "Error! Set $EDITOR or $PAGER." - ) elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", callback = self.master.run_script_once, args = (self.flow,) ) - elif key == "x": - signals.status_prompt_onekey.send( - prompt = "Delete body", - keys = ( - ("completely", "c"), - ("mark as missing", "m"), - ), - callback = self.delete_body + + if not conn and key in "befgmxvz": + signals.status_message.send( + message = "Tab to the request or response", + expire = 1 ) - key = None - elif key == "z": - if conn: + elif conn: + if key == "b": + if self.tab_offset == TAB_REQ: + common.ask_save_body("q", self.master, self.state, self.flow) + else: + common.ask_save_body("s", self.master, self.state, self.flow) + elif key == "e": + if self.tab_offset == TAB_REQ: + signals.status_prompt_onekey.send( + prompt = "Edit request", + keys = ( + ("query", "q"), + ("path", "p"), + ("url", "u"), + ("header", "h"), + ("form", "f"), + ("raw body", "r"), + ("method", "m"), + ), + callback = self.edit + ) + else: + signals.status_prompt_onekey.send( + prompt = "Edit response", + keys = ( + ("code", "c"), + ("message", "m"), + ("header", "h"), + ("raw body", "r"), + ), + callback = self.edit + ) + key = None + elif key == "f": + signals.status_message.send(message="Loading all body data...") + self.state.add_flow_setting( + self.flow, + (self.tab_offset, "fullcontents"), + True + ) + signals.flow_change.send(self, flow = self.flow) + signals.status_message.send(message="") + elif key == "g": + if self.tab_offset == TAB_REQ: + scope = "q" + else: + scope = "s" + common.ask_copy_part(scope, self.flow, self.master, self.state) + elif key == "m": + p = list(contentview.view_prompts) + p.insert(0, ("Clear", "C")) + signals.status_prompt_onekey.send( + self, + prompt = "Display mode", + keys = p, + callback = self.change_this_display_mode + ) + key = None + elif key == "x": + signals.status_prompt_onekey.send( + prompt = "Delete body", + keys = ( + ("completely", "c"), + ("mark as missing", "m"), + ), + callback = self.delete_body + ) + key = None + elif key == "v": + if conn.content: + t = conn.headers["content-type"] or [None] + t = t[0] + if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): + self.master.spawn_external_viewer(conn.content, t) + else: + signals.status_message.send( + message = "Error! Set $EDITOR or $PAGER." + ) + elif key == "z": self.flow.backup() e = conn.headers.get_first("content-encoding", "identity") if e != "identity": @@ -593,6 +601,8 @@ class FlowView(tabs.Tabs): args = (conn,) ) signals.flow_change.send(self, flow = self.flow) + else: + return super(self.__class__, self).keypress(size, key) else: return super(self.__class__, self).keypress(size, key) -- cgit v1.2.3 From 82997cb31173f9a82555ff7d5eb7ef9746256329 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 31 Mar 2015 09:49:07 +1300 Subject: Fix timestamps in detail view - Fix a crash when connection timestamps don't exist yet - Fix display of response timestamps - Get rid of those colossal ternaries. I want a device that pokes people in the eye every time they try to use a ternary operator. --- libmproxy/console/flowdetailview.py | 74 ++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 10 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index 99f2a262..48845a62 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -1,8 +1,17 @@ from __future__ import absolute_import import urwid -from . import common, signals, searchable +from . import common, searchable from .. import utils + +def maybe_timestamp(base, attr): + if base and getattr(base, attr): + return utils.format_timestamp_with_milli(getattr(base, attr)) + else: + return "active" + pass + + def flowdetails(state, flow): text = [] @@ -81,16 +90,61 @@ def flowdetails(state, flow): parts = [] - parts.append(["Client conn. established", utils.format_timestamp_with_milli(cc.timestamp_start) if (cc and cc.timestamp_start) else "active"]) - parts.append(["Server conn. initiated", utils.format_timestamp_with_milli(sc.timestamp_start) if sc else "active" ]) - parts.append(["Server conn. TCP handshake", utils.format_timestamp_with_milli(sc.timestamp_tcp_setup) if (sc and sc.timestamp_tcp_setup) else "active"]) + parts.append( + [ + "Client conn. established", + maybe_timestamp(cc, "timestamp_start") + ] + ) + parts.append( + [ + "Server conn. initiated", + maybe_timestamp(sc, "timestamp_start") + ] + ) + parts.append( + [ + "Server conn. TCP handshake", + maybe_timestamp(sc, "timestamp_tcp_setup") + ] + ) if sc.ssl_established: - parts.append(["Server conn. SSL handshake", utils.format_timestamp_with_milli(sc.timestamp_ssl_setup) if sc.timestamp_ssl_setup else "active"]) - parts.append(["Client conn. SSL handshake", utils.format_timestamp_with_milli(cc.timestamp_ssl_setup) if (cc and cc.timestamp_ssl_setup) else "active"]) - parts.append(["First request byte", utils.format_timestamp_with_milli(req.timestamp_start)]) - parts.append(["Request complete", utils.format_timestamp_with_milli(req.timestamp_end) if req.timestamp_end else "active"]) - parts.append(["First response byte", utils.format_timestamp_with_milli(resp.timestamp_start) if resp else "active"]) - parts.append(["Response complete", utils.format_timestamp_with_milli(resp.timestamp_end) if (resp and resp.timestamp_end) else "active"]) + parts.append( + [ + "Server conn. SSL handshake", + maybe_timestamp(sc, "timestamp_ssl_setup") + ] + ) + parts.append( + [ + "Client conn. SSL handshake", + maybe_timestamp(cc, "timestamp_ssl_setup") + ] + ) + parts.append( + [ + "First request byte", + maybe_timestamp(req, "timestamp_start") + ] + ) + parts.append( + [ + "Request complete", + maybe_timestamp(req, "timestamp_end") + ] + ) + parts.append( + [ + "First response byte", + maybe_timestamp(resp, "timestamp_start") + ] + ) + parts.append( + [ + "Response complete", + maybe_timestamp(resp, "timestamp_end") + ] + ) # sort operations by timestamp parts = sorted(parts, key=lambda p: p[1]) -- cgit v1.2.3 From 44fb42185f0e177b775c5e4f11249a55390048f6 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 31 Mar 2015 15:59:54 +1300 Subject: console: fix body scrolling --- libmproxy/console/searchable.py | 2 +- libmproxy/console/tabs.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index 9d66c718..a723dca8 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -36,7 +36,7 @@ class Searchable(urwid.ListBox): if key == "N": self.find_next(True) else: - return key + super(self.__class__, self).keypress(size, key) def set_search(self, text): self.state.last_search = text diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index b8943ad4..bb188c28 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -33,3 +33,4 @@ class Tabs(urwid.WidgetWrap): body = self.tabs[self.tab_offset][1](), header = headers ) + self._w.set_focus("body") -- cgit v1.2.3 From 32ba6021b3c07efaa45a9223479151cd7e74ccbd Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 1 Apr 2015 09:25:50 +1300 Subject: console: improve handling of help contexts, fix key bindings in flow views --- libmproxy/console/__init__.py | 34 ++++++++++++++++++++++------------ libmproxy/console/flowview.py | 14 +++++--------- libmproxy/console/searchable.py | 2 +- libmproxy/console/tabs.py | 3 +-- libmproxy/console/window.py | 20 +++++++------------- 5 files changed, 36 insertions(+), 37 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 660024cc..a963924e 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -15,8 +15,8 @@ import urwid import weakref from .. import controller, flow, script -from . import flowlist, flowview, help, common, window, signals -from . import grideditor, palettes, contentview, flowdetailview, statusbar +from . import flowlist, flowview, help, window, signals +from . import grideditor, palettes, contentview, statusbar EVENTLOG_SIZE = 500 @@ -227,6 +227,16 @@ class ConsoleMaster(flow.FlowMaster): def sig_pop_view_state(self, sender): if self.view_stack: self.loop.widget = self.view_stack.pop() + else: + signals.status_prompt_onekey.send( + self, + prompt = "Quit", + keys = ( + ("yes", "y"), + ("no", "n"), + ), + callback = self.quit, + ) def sig_push_view_state(self, sender): self.view_stack.append(self.loop.widget) @@ -393,7 +403,6 @@ class ConsoleMaster(flow.FlowMaster): self.ui.set_terminal_properties(256) self.ui.register_palette(self.palette.palette()) self.flow_list_walker = flowlist.FlowListWalker(self, self.state) - self.help_context = None self.loop = urwid.MainLoop( urwid.SolidFill("x"), screen = self.ui, @@ -444,23 +453,24 @@ class ConsoleMaster(flow.FlowMaster): sys.stderr.flush() self.shutdown() - def view_help(self): + def view_help(self, helpctx): signals.push_view_state.send(self) self.loop.widget = window.Window( self, - help.HelpView(self.help_context), + help.HelpView(helpctx), None, - statusbar.StatusBar(self, help.footer) + statusbar.StatusBar(self, help.footer), + None ) def view_grideditor(self, ge): signals.push_view_state.send(self) - self.help_context = ge.make_help() self.loop.widget = window.Window( self, ge, None, - statusbar.StatusBar(self, grideditor.FOOTER) + statusbar.StatusBar(self, grideditor.FOOTER), + ge.make_help() ) def view_flowlist(self): @@ -474,24 +484,24 @@ class ConsoleMaster(flow.FlowMaster): else: body = flowlist.FlowListBox(self) - self.help_context = flowlist.help_context self.loop.widget = window.Window( self, body, None, - statusbar.StatusBar(self, flowlist.footer) + statusbar.StatusBar(self, flowlist.footer), + flowlist.help_context ) self.loop.draw_screen() def view_flow(self, flow, tab_offset=0): signals.push_view_state.send(self) self.state.set_focus_flow(flow) - self.help_context = flowview.help_context self.loop.widget = window.Window( self, flowview.FlowView(self, self.state, flow, tab_offset), flowview.FlowViewHeader(self, flow), - statusbar.StatusBar(self, flowview.footer) + statusbar.StatusBar(self, flowview.footer), + flowview.help_context ) def _write_flows(self, path, flows): diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 538f42f0..6a3ced6e 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -434,6 +434,8 @@ class FlowView(tabs.Tabs): signals.flow_change.send(self, flow = self.flow) def keypress(self, size, key): + key = super(self.__class__, self).keypress(size, key) + if key == " ": self.view_next_flow(self.flow) return @@ -446,10 +448,7 @@ class FlowView(tabs.Tabs): else: conn = None - if key == "q": - signals.pop_view_state.send(self) - return None - elif key in ("up", "down", "page up", "page down"): + if key in ("up", "down", "page up", "page down"): # Why doesn't this just work?? self._w.keypress(size, key) elif key == "a": @@ -499,7 +498,7 @@ class FlowView(tabs.Tabs): args = (self.flow,) ) - if not conn and key in "befgmxvz": + if not conn and key in set(list("befgmxvz")): signals.status_message.send( message = "Tab to the request or response", expire = 1 @@ -601,10 +600,7 @@ class FlowView(tabs.Tabs): args = (conn,) ) signals.flow_change.send(self, flow = self.flow) - else: - return super(self.__class__, self).keypress(size, key) - else: - return super(self.__class__, self).keypress(size, key) + return key def encode_callback(self, key, conn): encoding_map = { diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index a723dca8..8f63c3f5 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -36,7 +36,7 @@ class Searchable(urwid.ListBox): if key == "N": self.find_next(True) else: - super(self.__class__, self).keypress(size, key) + return super(self.__class__, self).keypress(size, key) def set_search(self, text): self.state.last_search = text diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index bb188c28..fff13890 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -17,8 +17,7 @@ class Tabs(urwid.WidgetWrap): if key == "tab": self.tab_offset = (self.tab_offset + 1)%(len(self.tabs)) self.show() - else: - return self._w.keypress(size, key) + return self._w.keypress(size, key) def show(self): headers = [] diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index d686f61d..14a3acd2 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,19 +1,21 @@ import urwid -from . import common, grideditor, signals, contentview +from . import grideditor, signals, contentview + class Window(urwid.Frame): - def __init__(self, master, body, header, footer): + def __init__(self, master, body, header, footer, helpctx): urwid.Frame.__init__(self, body, header=header, footer=footer) self.master = master + self.helpctx = helpctx signals.focus.connect(self.sig_focus) def sig_focus(self, sender, section): self.focus_position = section def keypress(self, size, k): - k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) + k = super(self.__class__, self).keypress(size, k) if k == "?": - self.master.view_help() + self.master.view_help(self.helpctx) elif k == "c": if not self.master.client_playback: signals.status_prompt_path.send( @@ -65,15 +67,7 @@ class Window(urwid.Frame): elif k == "Q": raise urwid.ExitMainLoop elif k == "q": - signals.status_prompt_onekey.send( - self, - prompt = "Quit", - keys = ( - ("yes", "y"), - ("no", "n"), - ), - callback = self.master.quit, - ) + signals.pop_view_state.send(self) elif k == "M": signals.status_prompt_onekey.send( prompt = "Global default display mode", -- cgit v1.2.3 From c794d362f93df3a13ed5ac96dd6a04f766f8fbbc Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 1 Apr 2015 10:34:58 +1300 Subject: Next/previous tab keybindings --- libmproxy/console/flowview.py | 1 + libmproxy/console/tabs.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 6a3ced6e..99844cb7 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -68,6 +68,7 @@ def _mkhelp(): ("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)"), diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index fff13890..2c46e59e 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -14,9 +14,12 @@ class Tabs(urwid.WidgetWrap): return p def keypress(self, size, key): - if key == "tab": + if key in ["tab", "l"]: self.tab_offset = (self.tab_offset + 1)%(len(self.tabs)) self.show() + elif key == "h": + self.tab_offset = (self.tab_offset - 1)%(len(self.tabs)) + self.show() return self._w.keypress(size, key) def show(self): -- cgit v1.2.3 From fe6957eddb7fc781420b8dd1fcc016c352ddd5f8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 1 Apr 2015 10:47:28 +1300 Subject: console: fix a crash that sometimes occurs when setting a limit --- libmproxy/console/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index a963924e..a3a8aa42 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -73,6 +73,8 @@ class ConsoleState(flow.State): elif idx < 0: idx = 0 self.focus = idx + else: + self.focus = None def set_focus_flow(self, f): self.set_focus(self.view.index(f)) -- cgit v1.2.3 From 65971f02ade7cc2126b4142a32c363e02112f95c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 3 Apr 2015 14:10:57 +1300 Subject: console: basic options page --- libmproxy/console/__init__.py | 30 +++------ libmproxy/console/options.py | 151 ++++++++++++++++++++++++++++++++++++++++++ libmproxy/console/palettes.py | 29 ++++++-- libmproxy/console/window.py | 15 +---- 4 files changed, 188 insertions(+), 37 deletions(-) create mode 100644 libmproxy/console/options.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index a3a8aa42..e7776fdf 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -15,7 +15,7 @@ import urwid import weakref from .. import controller, flow, script -from . import flowlist, flowview, help, window, signals +from . import flowlist, flowview, help, window, signals, options from . import grideditor, palettes, contentview, statusbar EVENTLOG_SIZE = 500 @@ -465,6 +465,16 @@ class ConsoleMaster(flow.FlowMaster): None ) + def view_options(self): + signals.push_view_state.send(self) + self.loop.widget = window.Window( + self, + options.Options(self), + None, + statusbar.StatusBar(self, help.footer), + None + ) + def view_grideditor(self, ge): signals.push_view_state.send(self) self.loop.widget = window.Window( @@ -586,24 +596,6 @@ class ConsoleMaster(flow.FlowMaster): if a != "n": raise urwid.ExitMainLoop - def _change_options(self, a): - if a == "a": - self.anticache = not self.anticache - if a == "c": - self.anticomp = not self.anticomp - if a == "h": - self.showhost = not self.showhost - self.sync_list_view() - self.refresh_focus() - elif a == "k": - self.killextra = not self.killextra - elif a == "n": - self.refresh_server_playback = not self.refresh_server_playback - elif a == "u": - self.server.config.no_upstream_cert =\ - not self.server.config.no_upstream_cert - signals.update_settings.send(self) - def shutdown(self): self.state.killall(self) flow.FlowMaster.shutdown(self) diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py new file mode 100644 index 00000000..c6c4afb7 --- /dev/null +++ b/libmproxy/console/options.py @@ -0,0 +1,151 @@ +import urwid + +from . import common, signals + +help_context = None + + +class OptionWidget(urwid.WidgetWrap): + def __init__(self, option, text, active, focus): + self.option = option + opt = urwid.Text(text, align="center") + if focus and active: + opt = urwid.AttrWrap(opt, "option_active_selected") + elif focus: + opt = urwid.AttrWrap(opt, "option_selected") + elif active: + opt = urwid.AttrWrap(opt, "option_active") + opt = urwid.Padding(opt, align="center", width=("relative", 20)) + urwid.WidgetWrap.__init__(self, opt) + + def keypress(self, size, key): + return key + + def selectable(self): + return True + + +class OptionWalker(urwid.ListWalker): + def __init__(self, options): + urwid.ListWalker.__init__(self) + self.options = options + self.focus = 0 + signals.update_settings.connect(self.sig_update_settings) + + def sig_update_settings(self, sender): + self._modified() + + def set_focus(self, pos): + self.focus = pos + + def get_focus(self): + return self.options[self.focus].render(True), self.focus + + def get_next(self, pos): + if pos >= len(self.options)-1: + return None, None + return self.options[pos+1].render(False), pos+1 + + def get_prev(self, pos): + if pos <= 0: + return None, None + return self.options[pos-1].render(False), pos-1 + + +class OptionListBox(urwid.ListBox): + def __init__(self, options): + urwid.ListBox.__init__( + self, + OptionWalker(options) + ) + + def keypress(self, size, key): + key = common.shortcuts(key) + if key == "enter": + self.get_focus()[0].option.ativate() + return None + return super(self.__class__, self).keypress(size, key) + + +_neg = lambda: False +class Option: + def __init__(self, text, getstate=None, ativate=None): + self.text = text + self.getstate = getstate or _neg + self.ativate = ativate or _neg + + def render(self, focus): + return OptionWidget(self, self.text, self.getstate(), focus) + + +class Options(urwid.WidgetWrap): + def __init__(self, master): + self.master = master + self.lb = OptionListBox( + [ + Option( + "Anti-Cache", + lambda: master.anticache, + self.toggle_anticache + ), + Option( + "Anti-Compression", + lambda: master.anticomp, + self.toggle_anticomp + ), + #Option("Header Set Patterns"), + #Option("Ignore Patterns"), + Option( + "Kill Extra", + lambda: master.killextra, + self.toggle_killextra + ), + #Option("Manage Scripts"), + #Option("Replacement Patterns"), + Option( + "Show Host", + lambda: master.showhost, + self.toggle_showhost + ), + #Option("Sticky Cookies"), + #Option("Sticky Auth"), + #Option("TCP Proxying"), + Option( + "No Refresh", + lambda: not master.refresh_server_playback, + self.toggle_refresh_server_playback + ), + Option( + "No Upstream Certs", + lambda: master.server.config.no_upstream_cert, + self.toggle_upstream_cert + ), + ] + ) + title = urwid.Text("Options") + title = urwid.Padding(title, align="left", width=("relative", 100)) + title = urwid.AttrWrap(title, "heading") + self._w = urwid.Frame( + self.lb, + header = title + ) + self.master.loop.widget.footer.update("") + + def toggle_anticache(self): + self.master.anticache = not self.master.anticache + + def toggle_anticomp(self): + self.master.anticomp = not self.master.anticomp + + def toggle_killextra(self): + self.master.killextra = not self.master.killextra + + def toggle_showhost(self): + self.master.showhost = not self.master.showhost + + def toggle_refresh_server_playback(self): + self.master.refresh_server_playback = not self.master.refresh_server_playback + + def toggle_upstream_cert(self): + self.master.server.config.no_upstream_cert = not self.master.server.config.no_upstream_cert + signals.update_settings.send(self) diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index cfb2702c..020863f8 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -17,6 +17,9 @@ class Palette: # Help 'key', 'head', 'text', + # Options + 'option_selected', 'option_active', 'option_active_selected', + # List and Connections 'method', 'focus', 'code_200', 'code_300', 'code_400', 'code_500', 'code_other', @@ -60,6 +63,9 @@ class LowDark(Palette): head = ('white,bold', 'default'), text = ('light gray', 'default'), + # Options + option_selected = ('light gray', 'dark blue'), + # List and Connections method = ('dark cyan', 'default'), focus = ('yellow', 'default'), @@ -112,6 +118,11 @@ class LowLight(Palette): head = ('black,bold', 'default'), text = ('dark gray', 'default'), + # Options + option_selected = ('light gray', 'dark blue'), + option_active = ('light red', 'default'), + option_active_selected = ('light red', 'dark blue'), + # List and Connections method = ('dark cyan', 'default'), focus = ('black', 'default'), @@ -181,6 +192,11 @@ class SolarizedLight(LowLight): key = (sol_blue, 'default',), head = (sol_base00, 'default'), + # Options + option_selected = (sol_base2, sol_base02), + option_active = (sol_orange, 'default'), + option_active_selected = (sol_orange, sol_base02), + # List and Connections method = (sol_cyan, 'default'), focus = (sol_base01, 'default'), @@ -223,6 +239,9 @@ class SolarizedDark(LowDark): key = (sol_blue, 'default',), head = (sol_base00, 'default'), + # Options + option_selected = (sol_base03, sol_base01), + # List and Connections method = (sol_cyan, 'default'), focus = (sol_base1, 'default'), @@ -252,10 +271,10 @@ class SolarizedDark(LowDark): palettes = { - "lowlight": LowLight(), - "lowdark": LowDark(), - "light": Light(), - "dark": Dark(), + #"lowlight": LowLight(), + #"lowdark": LowDark(), + #"light": Light(), + #"dark": Dark(), "solarized_light": SolarizedLight(), - "solarized_dark": SolarizedDark(), + #"solarized_dark": SolarizedDark(), } diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 14a3acd2..1d22f280 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -64,6 +64,8 @@ class Window(urwid.Frame): text = self.master.state.intercept_txt, callback = self.master.set_intercept ) + elif k == "o": + self.master.view_options() elif k == "Q": raise urwid.ExitMainLoop elif k == "q": @@ -107,19 +109,6 @@ class Window(urwid.Frame): ), callback = self.master.stop_server_playback_prompt, ) - elif k == "o": - signals.status_prompt_onekey.send( - prompt = "Options", - keys = ( - ("anticache", "a"), - ("anticomp", "c"), - ("showhost", "h"), - ("killextra", "k"), - ("norefresh", "n"), - ("no-upstream-certs", "u"), - ), - callback = self.master._change_options - ) elif k == "t": signals.status_prompt.send( prompt = "Sticky cookie filter", -- cgit v1.2.3 From 57bdb893425058d03b1aaf28e1c774c81a8d9403 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 3 Apr 2015 14:38:04 +1300 Subject: console: keyboard shortcuts for options --- libmproxy/console/common.py | 10 +++++----- libmproxy/console/options.py | 43 ++++++++++++++++++++++++++++++++++--------- libmproxy/console/palettes.py | 5 ++++- 3 files changed, 43 insertions(+), 15 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index bc8a2aad..23d3a4a4 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -37,14 +37,14 @@ def is_keypress(k): return True -def highlight_key(s, k): +def highlight_key(str, key, textattr="text", keyattr="key"): l = [] - parts = s.split(k, 1) + parts = str.split(key, 1) if parts[0]: - l.append(("text", parts[0])) - l.append(("key", k)) + l.append((textattr, parts[0])) + l.append((keyattr, key)) if parts[1]: - l.append(("text", parts[1])) + l.append((textattr, parts[1])) return l diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index c6c4afb7..bb6b6c09 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -6,15 +6,25 @@ help_context = None class OptionWidget(urwid.WidgetWrap): - def __init__(self, option, text, active, focus): + def __init__(self, option, text, shortcut, active, focus): self.option = option - opt = urwid.Text(text, align="center") + textattr = "text" + keyattr = "key" if focus and active: - opt = urwid.AttrWrap(opt, "option_active_selected") + textattr = "option_active_selected" elif focus: - opt = urwid.AttrWrap(opt, "option_selected") + textattr = "option_selected" + keyattr = "option_selected_key" elif active: - opt = urwid.AttrWrap(opt, "option_active") + textattr = "option_active" + text = common.highlight_key( + text, + shortcut, + textattr=textattr, + keyattr=keyattr + ) + opt = urwid.Text(text, align="center") + opt = urwid.AttrWrap(opt, textattr) opt = urwid.Padding(opt, align="center", width=("relative", 20)) urwid.WidgetWrap.__init__(self, opt) @@ -58,24 +68,33 @@ class OptionListBox(urwid.ListBox): self, OptionWalker(options) ) + self.options = options + self.keymap = {} + for i in options: + self.keymap[i.shortcut] = i def keypress(self, size, key): key = common.shortcuts(key) if key == "enter": - self.get_focus()[0].option.ativate() + self.get_focus()[0].option.activate() + return None + if key in self.keymap: + self.keymap[key].activate() + self.set_focus(self.options.index(self.keymap[key])) return None return super(self.__class__, self).keypress(size, key) _neg = lambda: False class Option: - def __init__(self, text, getstate=None, ativate=None): + def __init__(self, text, shortcut, getstate=None, activate=None): self.text = text + self.shortcut = shortcut self.getstate = getstate or _neg - self.ativate = ativate or _neg + self.activate = activate or _neg def render(self, focus): - return OptionWidget(self, self.text, self.getstate(), focus) + return OptionWidget(self, self.text, self.shortcut, self.getstate(), focus) class Options(urwid.WidgetWrap): @@ -85,11 +104,13 @@ class Options(urwid.WidgetWrap): [ Option( "Anti-Cache", + "C", lambda: master.anticache, self.toggle_anticache ), Option( "Anti-Compression", + "o", lambda: master.anticomp, self.toggle_anticomp ), @@ -97,6 +118,7 @@ class Options(urwid.WidgetWrap): #Option("Ignore Patterns"), Option( "Kill Extra", + "E", lambda: master.killextra, self.toggle_killextra ), @@ -104,6 +126,7 @@ class Options(urwid.WidgetWrap): #Option("Replacement Patterns"), Option( "Show Host", + "H", lambda: master.showhost, self.toggle_showhost ), @@ -112,11 +135,13 @@ class Options(urwid.WidgetWrap): #Option("TCP Proxying"), Option( "No Refresh", + "R", lambda: not master.refresh_server_playback, self.toggle_refresh_server_playback ), Option( "No Upstream Certs", + "U", lambda: master.server.config.no_upstream_cert, self.toggle_upstream_cert ), diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index 020863f8..98a1e042 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -19,6 +19,7 @@ class Palette: # Options 'option_selected', 'option_active', 'option_active_selected', + 'option_selected_key', # List and Connections 'method', 'focus', @@ -120,6 +121,7 @@ class LowLight(Palette): # Options option_selected = ('light gray', 'dark blue'), + option_selected_key = ('dark blue,bold', 'dark blue'), option_active = ('light red', 'default'), option_active_selected = ('light red', 'dark blue'), @@ -194,6 +196,7 @@ class SolarizedLight(LowLight): # Options option_selected = (sol_base2, sol_base02), + option_selected_key = (sol_blue, sol_base02), option_active = (sol_orange, 'default'), option_active_selected = (sol_orange, sol_base02), @@ -271,7 +274,7 @@ class SolarizedDark(LowDark): palettes = { - #"lowlight": LowLight(), + "lowlight": LowLight(), #"lowdark": LowDark(), #"light": Light(), #"dark": Dark(), -- cgit v1.2.3 From 41a1a0bef3b40b744c232a1adba478f8ac0f2c6c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 3 Apr 2015 14:47:55 +1300 Subject: console: C to clear all options, correct footer in options screen --- libmproxy/console/__init__.py | 2 +- libmproxy/console/options.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index e7776fdf..3b3f2df2 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -471,7 +471,7 @@ class ConsoleMaster(flow.FlowMaster): self, options.Options(self), None, - statusbar.StatusBar(self, help.footer), + statusbar.StatusBar(self, options.footer), None ) diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index bb6b6c09..db6cc151 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -3,6 +3,10 @@ import urwid from . import common, signals help_context = None +footer = [ + ('heading_key', "enter/space"), ":toggle ", + ('heading_key', "C"), ":clear all ", +] class OptionWidget(urwid.WidgetWrap): @@ -12,6 +16,7 @@ class OptionWidget(urwid.WidgetWrap): keyattr = "key" if focus and active: textattr = "option_active_selected" + keyattr = "option_selected_key" elif focus: textattr = "option_selected" keyattr = "option_selected_key" @@ -74,10 +79,10 @@ class OptionListBox(urwid.ListBox): self.keymap[i.shortcut] = i def keypress(self, size, key): - key = common.shortcuts(key) - if key == "enter": + if key == "enter" or key == " ": self.get_focus()[0].option.activate() return None + key = common.shortcuts(key) if key in self.keymap: self.keymap[key].activate() self.set_focus(self.options.index(self.keymap[key])) @@ -104,7 +109,7 @@ class Options(urwid.WidgetWrap): [ Option( "Anti-Cache", - "C", + "a", lambda: master.anticache, self.toggle_anticache ), @@ -156,6 +161,25 @@ class Options(urwid.WidgetWrap): ) self.master.loop.widget.footer.update("") + def keypress(self, size, key): + if key == "C": + self.clearall() + return None + return super(self.__class__, self).keypress(size, key) + + def clearall(self): + self.master.anticache = False + self.master.anticomp = False + self.master.killextra = False + self.master.showhost = False + self.master.refresh_server_playback = True + self.master.server.config.no_upstream_cert = False + signals.update_settings.send(self) + signals.status_message.send( + message = "All options cleared", + expire = 1 + ) + def toggle_anticache(self): self.master.anticache = not self.master.anticache -- cgit v1.2.3 From ddc353955d73c83a770b6d35bfe5fc282abe243f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 3 Apr 2015 19:10:12 +1300 Subject: Don't list all options shortcuts in help --- libmproxy/console/help.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 73cd8a50..19f17625 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -89,32 +89,7 @@ class HelpView(urwid.ListBox): common.highlight_key("amf", "f") + [("text", ": AMF (requires PyAMF)")] ), - ("o", "toggle options:"), - (None, - common.highlight_key("anticache", "a") + - [("text", ": prevent cached responses")] - ), - (None, - common.highlight_key("anticomp", "c") + - [("text", ": prevent compressed responses")] - ), - (None, - common.highlight_key("showhost", "h") + - [("text", ": use Host header for URL display")] - ), - (None, - common.highlight_key("killextra", "k") + - [("text", ": kill requests not part of server replay")] - ), - (None, - common.highlight_key("norefresh", "n") + - [("text", ": disable server replay response refresh")] - ), - (None, - common.highlight_key("upstream certs", "u") + - [("text", ": sniff cert info from upstream server")] - ), - + ("o", "options"), ("q", "quit / return to flow list"), ("Q", "quit without confirm prompt"), ("R", "edit replacement patterns"), -- cgit v1.2.3 From aadaa66d0b92ce7e97e3303e3152a2dda6d320e9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 3 Apr 2015 19:44:09 +1300 Subject: console: tune and re-enable palettes --- libmproxy/console/palettes.py | 46 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 20 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index 98a1e042..b55a34aa 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -66,6 +66,9 @@ class LowDark(Palette): # Options option_selected = ('light gray', 'dark blue'), + option_selected_key = ('light cyan', 'dark blue'), + option_active = ('light red', 'default'), + option_active_selected = ('light red', 'dark blue'), # List and Connections method = ('dark cyan', 'default'), @@ -107,23 +110,23 @@ class LowLight(Palette): Low-color light background """ low = dict( - title = ('dark magenta,bold', 'light blue'), + title = ('dark magenta', 'default'), # Status bar & heading - heading = ('light gray', 'dark blue'), - heading_key = ('light cyan', 'dark blue'), + heading = ('white', 'black'), + heading_key = ('dark blue', 'black'), heading_inactive = ('black', 'light gray'), # Help - key = ('dark blue,bold', 'default'), - head = ('black,bold', 'default'), + key = ('dark blue', 'default'), + head = ('black', 'default'), text = ('dark gray', 'default'), # Options - option_selected = ('light gray', 'dark blue'), - option_selected_key = ('dark blue,bold', 'dark blue'), + option_selected = ('black', 'light gray'), + option_selected_key = ('dark blue', 'light gray'), option_active = ('light red', 'default'), - option_active_selected = ('light red', 'dark blue'), + option_active_selected = ('light red', 'light gray'), # List and Connections method = ('dark cyan', 'default'), @@ -195,10 +198,10 @@ class SolarizedLight(LowLight): head = (sol_base00, 'default'), # Options - option_selected = (sol_base2, sol_base02), - option_selected_key = (sol_blue, sol_base02), + option_selected = (sol_base03, sol_base2), + option_selected_key = (sol_blue, sol_base2), option_active = (sol_orange, 'default'), - option_active_selected = (sol_orange, sol_base02), + option_active_selected = (sol_orange, sol_base2), # List and Connections method = (sol_cyan, 'default'), @@ -231,19 +234,22 @@ class SolarizedLight(LowLight): class SolarizedDark(LowDark): high = dict( title = (sol_blue, 'default'), - text = (sol_base0, 'default'), + text = (sol_base1, 'default'), # Status bar & heading - heading = (sol_base03, sol_base1), - heading_key = (sol_blue+",bold", sol_base1), + heading = (sol_base2, sol_base01), + heading_key = (sol_blue+",bold", sol_base01), heading_inactive = (sol_base1, sol_base02), # Help key = (sol_blue, 'default',), - head = (sol_base00, 'default'), + head = (sol_base2, 'default'), # Options - option_selected = (sol_base03, sol_base01), + option_selected = (sol_base03, sol_base00), + option_selected_key = (sol_blue, sol_base00), + option_active = (sol_orange, 'default'), + option_active_selected = (sol_orange, sol_base00), # List and Connections method = (sol_cyan, 'default'), @@ -275,9 +281,9 @@ class SolarizedDark(LowDark): palettes = { "lowlight": LowLight(), - #"lowdark": LowDark(), - #"light": Light(), - #"dark": Dark(), + "lowdark": LowDark(), + "light": Light(), + "dark": Dark(), "solarized_light": SolarizedLight(), - #"solarized_dark": SolarizedDark(), + "solarized_dark": SolarizedDark(), } -- cgit v1.2.3 From 3704411466364bea76e0fb231cd1724f45318522 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 3 Apr 2015 20:04:19 +1300 Subject: Minimal help context for options --- libmproxy/console/__init__.py | 2 +- libmproxy/console/options.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 3b3f2df2..7e6326f7 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -472,7 +472,7 @@ class ConsoleMaster(flow.FlowMaster): options.Options(self), None, statusbar.StatusBar(self, options.footer), - None + options.help_context, ) def view_grideditor(self, ge): diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index db6cc151..e2227f08 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -2,12 +2,21 @@ import urwid from . import common, signals -help_context = None footer = [ ('heading_key', "enter/space"), ":toggle ", ('heading_key', "C"), ":clear all ", ] +def _mkhelp(): + text = [] + keys = [ + ("enter/space", "activate option"), + ("C", "clear all options"), + ] + text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) + return text +help_context = _mkhelp() + class OptionWidget(urwid.WidgetWrap): def __init__(self, option, text, shortcut, active, focus): @@ -25,12 +34,12 @@ class OptionWidget(urwid.WidgetWrap): text = common.highlight_key( text, shortcut, - textattr=textattr, - keyattr=keyattr + textattr = textattr, + keyattr = keyattr ) opt = urwid.Text(text, align="center") opt = urwid.AttrWrap(opt, textattr) - opt = urwid.Padding(opt, align="center", width=("relative", 20)) + opt = urwid.Padding(opt, align = "center", width = ("relative", 20)) urwid.WidgetWrap.__init__(self, opt) def keypress(self, size, key): -- cgit v1.2.3 From 9e39999706dc1fbe3907a4d98aa30d777d6dfba7 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 16:46:51 +1200 Subject: Add headings to options, start migrating more options into options screen SetHeaders first... --- libmproxy/console/options.py | 108 +++++++++++++++++++++++++++++++++--------- libmproxy/console/palettes.py | 2 +- libmproxy/console/window.py | 8 ---- 3 files changed, 86 insertions(+), 32 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index e2227f08..b6e274f3 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -1,6 +1,6 @@ import urwid -from . import common, signals +from . import common, signals, grideditor footer = [ ('heading_key', "enter/space"), ":toggle ", @@ -37,9 +37,9 @@ class OptionWidget(urwid.WidgetWrap): textattr = textattr, keyattr = keyattr ) - opt = urwid.Text(text, align="center") + opt = urwid.Text(text, align="left") opt = urwid.AttrWrap(opt, textattr) - opt = urwid.Padding(opt, align = "center", width = ("relative", 20)) + opt = urwid.Padding(opt, align = "center", width = 40) urwid.WidgetWrap.__init__(self, opt) def keypress(self, size, key): @@ -85,7 +85,10 @@ class OptionListBox(urwid.ListBox): self.options = options self.keymap = {} for i in options: - self.keymap[i.shortcut] = i + if hasattr(i, "shortcut"): + if i.shortcut in self.keymap: + raise ValueError("Duplicate shortcut key: %s"%i.shortcut) + self.keymap[i.shortcut] = i def keypress(self, size, key): if key == "enter" or key == " ": @@ -99,6 +102,18 @@ class OptionListBox(urwid.ListBox): return super(self.__class__, self).keypress(size, key) + +class Heading: + def __init__(self, text): + self.text = text + + def render(self, focus): + opt = urwid.Text("\n" + self.text, align="left") + opt = urwid.AttrWrap(opt, "title") + opt = urwid.Padding(opt, align = "center", width = 40) + return opt + + _neg = lambda: False class Option: def __init__(self, text, shortcut, getstate=None, activate=None): @@ -116,6 +131,51 @@ class Options(urwid.WidgetWrap): self.master = master self.lb = OptionListBox( [ + Heading("Traffic Manipulation"), + Option( + "Header Set Patterns", + "H", + lambda: master.setheaders.count(), + self.setheaders + ), + Option( + "Ignore Patterns", + "I" + ), + Option( + "Replacement Patterns", + "R" + ), + Option( + "Scripts", + "S" + ), + + Heading("Interface"), + Option( + "Default Display Mode", + "M" + ), + Option( + "Show Host", + "w", + lambda: master.showhost, + self.toggle_showhost + ), + + Heading("Network"), + Option( + "No Upstream Certs", + "U", + lambda: master.server.config.no_upstream_cert, + self.toggle_upstream_cert + ), + Option( + "TCP Proxying", + "T" + ), + + Heading("Utility"), Option( "Anti-Cache", "a", @@ -128,36 +188,25 @@ class Options(urwid.WidgetWrap): lambda: master.anticomp, self.toggle_anticomp ), - #Option("Header Set Patterns"), - #Option("Ignore Patterns"), Option( "Kill Extra", - "E", + "x", lambda: master.killextra, self.toggle_killextra ), - #Option("Manage Scripts"), - #Option("Replacement Patterns"), - Option( - "Show Host", - "H", - lambda: master.showhost, - self.toggle_showhost - ), - #Option("Sticky Cookies"), - #Option("Sticky Auth"), - #Option("TCP Proxying"), Option( "No Refresh", - "R", + "f", lambda: not master.refresh_server_playback, self.toggle_refresh_server_playback ), Option( - "No Upstream Certs", - "U", - lambda: master.server.config.no_upstream_cert, - self.toggle_upstream_cert + "Sticky Auth", + "A" + ), + Option( + "Sticky Cookies", + "t" ), ] ) @@ -183,6 +232,7 @@ class Options(urwid.WidgetWrap): self.master.showhost = False self.master.refresh_server_playback = True self.master.server.config.no_upstream_cert = False + self.master.setheaders.clear() signals.update_settings.send(self) signals.status_message.send( message = "All options cleared", @@ -207,3 +257,15 @@ class Options(urwid.WidgetWrap): def toggle_upstream_cert(self): self.master.server.config.no_upstream_cert = not self.master.server.config.no_upstream_cert signals.update_settings.send(self) + + def setheaders(self): + def _set(*args, **kwargs): + self.master.setheaders.set(*args, **kwargs) + signals.update_settings.send(self) + self.master.view_grideditor( + grideditor.SetHeadersEditor( + self.master, + self.master.setheaders.get_specs(), + _set + ) + ) diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index b55a34aa..3e6ce21f 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -185,7 +185,7 @@ sol_cyan = "h37" sol_green = "h64" class SolarizedLight(LowLight): high = dict( - title = (sol_blue, 'default'), + title = (sol_cyan, 'default'), text = (sol_base00, 'default'), # Status bar & heading diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 1d22f280..f2ef920b 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -33,14 +33,6 @@ class Window(urwid.Frame): ), callback = self.master.stop_client_playback_prompt, ) - elif k == "H": - self.master.view_grideditor( - grideditor.SetHeadersEditor( - self.master, - self.master.setheaders.get_specs(), - self.master.setheaders.set - ) - ) elif k == "I": self.master.view_grideditor( grideditor.HostPatternEditor( -- cgit v1.2.3 From 15246c34039ecd1c27da52d1474910cecf6e2061 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:02:51 +1200 Subject: console: ignore patterns to new options screen. --- libmproxy/console/__init__.py | 4 ---- libmproxy/console/help.py | 2 -- libmproxy/console/options.py | 18 +++++++++++++++++- libmproxy/console/window.py | 8 -------- 4 files changed, 17 insertions(+), 15 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 7e6326f7..f40987e3 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -576,10 +576,6 @@ class ConsoleMaster(flow.FlowMaster): for command in commands: self.load_script(command) - def edit_ignore_filter(self, ignore): - patterns = (x[0] for x in ignore) - self.set_ignore_filter(patterns) - def edit_tcp_filter(self, tcp): patterns = (x[0] for x in tcp) self.set_tcp_filter(patterns) diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 19f17625..ea1e469c 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -37,8 +37,6 @@ class HelpView(urwid.ListBox): text.append(urwid.Text([("head", "\n\nGlobal keys:\n")])) keys = [ ("c", "client replay"), - ("H", "edit global header set patterns"), - ("I", "set ignore pattern"), ("i", "set interception pattern"), ("M", "change global default display mode"), (None, diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index b6e274f3..4cc768b6 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -140,7 +140,9 @@ class Options(urwid.WidgetWrap): ), Option( "Ignore Patterns", - "I" + "I", + lambda: master.server.config.check_ignore, + self.ignorepatterns ), Option( "Replacement Patterns", @@ -233,6 +235,7 @@ class Options(urwid.WidgetWrap): self.master.refresh_server_playback = True self.master.server.config.no_upstream_cert = False self.master.setheaders.clear() + self.master.set_ignore_filter([]) signals.update_settings.send(self) signals.status_message.send( message = "All options cleared", @@ -269,3 +272,16 @@ class Options(urwid.WidgetWrap): _set ) ) + + def ignorepatterns(self): + def _set(ignore): + patterns = (x[0] for x in ignore) + self.master.set_ignore_filter(patterns) + signals.update_settings.send(self) + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_ignore_filter()], + _set + ) + ) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index f2ef920b..c28e6b13 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -33,14 +33,6 @@ class Window(urwid.Frame): ), callback = self.master.stop_client_playback_prompt, ) - elif k == "I": - self.master.view_grideditor( - grideditor.HostPatternEditor( - self.master, - [[x] for x in self.master.get_ignore_filter()], - self.master.edit_ignore_filter - ) - ) elif k == "T": self.master.view_grideditor( grideditor.HostPatternEditor( -- cgit v1.2.3 From 488adcb79ef2820d1bed59ab51728e59c7924e1f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:05:52 +1200 Subject: console: replacement patterns to new option screen --- libmproxy/console/help.py | 1 - libmproxy/console/options.py | 17 ++++++++++++++++- libmproxy/console/window.py | 8 -------- 3 files changed, 16 insertions(+), 10 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index ea1e469c..b5f9bff3 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -90,7 +90,6 @@ class HelpView(urwid.ListBox): ("o", "options"), ("q", "quit / return to flow list"), ("Q", "quit without confirm prompt"), - ("R", "edit replacement patterns"), ("s", "add/remove scripts"), ("S", "server replay"), ("t", "set sticky cookie expression"), diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 4cc768b6..88c9cd98 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -146,7 +146,9 @@ class Options(urwid.WidgetWrap): ), Option( "Replacement Patterns", - "R" + "R", + lambda: master.replacehooks.count(), + self.replacepatterns ), Option( "Scripts", @@ -235,6 +237,7 @@ class Options(urwid.WidgetWrap): self.master.refresh_server_playback = True self.master.server.config.no_upstream_cert = False self.master.setheaders.clear() + self.master.replacehooks.clear() self.master.set_ignore_filter([]) signals.update_settings.send(self) signals.status_message.send( @@ -285,3 +288,15 @@ class Options(urwid.WidgetWrap): _set ) ) + + def replacepatterns(self): + def _set(*args, **kwargs): + self.master.replacehooks.set(*args, **kwargs) + signals.update_settings.send(self) + self.master.view_grideditor( + grideditor.ReplaceEditor( + self.master, + self.master.replacehooks.get_specs(), + _set + ) + ) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index c28e6b13..b0db8cbe 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -60,14 +60,6 @@ class Window(urwid.Frame): keys = contentview.view_prompts, callback = self.master.change_default_display_mode ) - elif k == "R": - self.master.view_grideditor( - grideditor.ReplaceEditor( - self.master, - self.master.replacehooks.get_specs(), - self.master.replacehooks.set - ) - ) elif k == "s": self.master.view_grideditor( grideditor.ScriptEditor( -- cgit v1.2.3 From acb6b5667cd3dfd972b0229be2e2e7dc62ea01ac Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:11:02 +1200 Subject: console: scripts to new options screen --- libmproxy/console/__init__.py | 1 + libmproxy/console/help.py | 1 - libmproxy/console/options.py | 14 +++++++++++++- libmproxy/console/window.py | 8 -------- 4 files changed, 14 insertions(+), 10 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index f40987e3..08659f32 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -575,6 +575,7 @@ class ConsoleMaster(flow.FlowMaster): self.unload_scripts() for command in commands: self.load_script(command) + signals.update_settings.send(self) def edit_tcp_filter(self, tcp): patterns = (x[0] for x in tcp) diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index b5f9bff3..3b8fbd97 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -90,7 +90,6 @@ class HelpView(urwid.ListBox): ("o", "options"), ("q", "quit / return to flow list"), ("Q", "quit without confirm prompt"), - ("s", "add/remove scripts"), ("S", "server replay"), ("t", "set sticky cookie expression"), ("T", "set tcp proxying pattern"), diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 88c9cd98..10c301f4 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -152,7 +152,9 @@ class Options(urwid.WidgetWrap): ), Option( "Scripts", - "S" + "S", + lambda: master.scripts, + self.scripts ), Heading("Interface"), @@ -239,6 +241,7 @@ class Options(urwid.WidgetWrap): self.master.setheaders.clear() self.master.replacehooks.clear() self.master.set_ignore_filter([]) + self.master.scripts = [] signals.update_settings.send(self) signals.status_message.send( message = "All options cleared", @@ -300,3 +303,12 @@ class Options(urwid.WidgetWrap): _set ) ) + + def scripts(self): + self.master.view_grideditor( + grideditor.ScriptEditor( + self.master, + [[i.command] for i in self.master.scripts], + self.master.edit_scripts + ) + ) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index b0db8cbe..772102eb 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -60,14 +60,6 @@ class Window(urwid.Frame): keys = contentview.view_prompts, callback = self.master.change_default_display_mode ) - elif k == "s": - self.master.view_grideditor( - grideditor.ScriptEditor( - self.master, - [[i.command] for i in self.master.scripts], - self.master.edit_scripts - ) - ) elif k == "S": if not self.master.server_playback: signals.status_prompt_path.send( -- cgit v1.2.3 From ec7572697a3877d26a5a05569c71a1487556889e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:14:32 +1200 Subject: console: default display mode to new options screen --- libmproxy/console/help.py | 49 -------------------------------------------- libmproxy/console/options.py | 16 +++++++++++++-- libmproxy/console/window.py | 6 ------ 3 files changed, 14 insertions(+), 57 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 3b8fbd97..223d2e83 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -38,55 +38,6 @@ class HelpView(urwid.ListBox): keys = [ ("c", "client replay"), ("i", "set interception pattern"), - ("M", "change global default display mode"), - (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("css", "c") + - [("text", ": CSS")] - ), - (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")] - ), - (None, - common.highlight_key("wbxml", "w") + - [("text", ": WBXML")] - ), - (None, - common.highlight_key("amf", "f") + - [("text", ": AMF (requires PyAMF)")] - ), ("o", "options"), ("q", "quit / return to flow list"), ("Q", "quit without confirm prompt"), diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 10c301f4..2b03f388 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -1,6 +1,6 @@ import urwid -from . import common, signals, grideditor +from . import common, signals, grideditor, contentview footer = [ ('heading_key', "enter/space"), ":toggle ", @@ -160,7 +160,9 @@ class Options(urwid.WidgetWrap): Heading("Interface"), Option( "Default Display Mode", - "M" + "M", + self.has_default_displaymode, + self.default_displaymode ), Option( "Show Host", @@ -312,3 +314,13 @@ class Options(urwid.WidgetWrap): self.master.edit_scripts ) ) + + def default_displaymode(self): + signals.status_prompt_onekey.send( + prompt = "Global default display mode", + keys = contentview.view_prompts, + callback = self.master.change_default_display_mode + ) + + def has_default_displaymode(self): + return self.master.state.default_body_view.name != "Auto" diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 772102eb..bb811537 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -54,12 +54,6 @@ class Window(urwid.Frame): raise urwid.ExitMainLoop elif k == "q": signals.pop_view_state.send(self) - elif k == "M": - signals.status_prompt_onekey.send( - prompt = "Global default display mode", - keys = contentview.view_prompts, - callback = self.master.change_default_display_mode - ) elif k == "S": if not self.master.server_playback: signals.status_prompt_path.send( -- cgit v1.2.3 From c4e0f9d8d77c7306f7af4509250541f4b9ea8524 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:19:52 +1200 Subject: console: TCP proxy patterns to new options screen --- libmproxy/console/__init__.py | 4 ---- libmproxy/console/help.py | 1 - libmproxy/console/options.py | 18 +++++++++++++++++- libmproxy/console/window.py | 8 -------- 4 files changed, 17 insertions(+), 14 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 08659f32..2421aa6b 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -577,10 +577,6 @@ class ConsoleMaster(flow.FlowMaster): self.load_script(command) signals.update_settings.send(self) - def edit_tcp_filter(self, tcp): - patterns = (x[0] for x in tcp) - self.set_tcp_filter(patterns) - def stop_client_playback_prompt(self, a): if a != "n": self.stop_client_playback() diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 223d2e83..53296df9 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -43,7 +43,6 @@ class HelpView(urwid.ListBox): ("Q", "quit without confirm prompt"), ("S", "server replay"), ("t", "set sticky cookie expression"), - ("T", "set tcp proxying pattern"), ("u", "set sticky auth expression"), ] text.extend( diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 2b03f388..bfe6a591 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -180,7 +180,9 @@ class Options(urwid.WidgetWrap): ), Option( "TCP Proxying", - "T" + "T", + lambda: master.server.config.check_tcp, + self.tcp_proxy ), Heading("Utility"), @@ -243,6 +245,7 @@ class Options(urwid.WidgetWrap): self.master.setheaders.clear() self.master.replacehooks.clear() self.master.set_ignore_filter([]) + self.master.set_tcp_filter([]) self.master.scripts = [] signals.update_settings.send(self) signals.status_message.send( @@ -324,3 +327,16 @@ class Options(urwid.WidgetWrap): def has_default_displaymode(self): return self.master.state.default_body_view.name != "Auto" + + def tcp_proxy(self): + def _set(tcp): + patterns = (x[0] for x in tcp) + self.master.set_tcp_filter(patterns) + signals.update_settings.send(self) + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_tcp_filter()], + _set + ) + ) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index bb811537..cdc57b17 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -33,14 +33,6 @@ class Window(urwid.Frame): ), callback = self.master.stop_client_playback_prompt, ) - elif k == "T": - self.master.view_grideditor( - grideditor.HostPatternEditor( - self.master, - [[x] for x in self.master.get_tcp_filter()], - self.master.edit_tcp_filter - ) - ) elif k == "i": signals.status_prompt.send( self, -- cgit v1.2.3 From 0d6de19b070789405ed2713b6d973b06ea7922fc Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:27:46 +1200 Subject: console: sticky cookies and auth to options screen --- libmproxy/console/help.py | 4 +--- libmproxy/console/options.py | 26 ++++++++++++++++++++++++-- libmproxy/console/window.py | 12 ------------ 3 files changed, 25 insertions(+), 17 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 53296df9..0da29907 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -39,11 +39,9 @@ class HelpView(urwid.ListBox): ("c", "client replay"), ("i", "set interception pattern"), ("o", "options"), - ("q", "quit / return to flow list"), + ("q", "quit / return to previous page"), ("Q", "quit without confirm prompt"), ("S", "server replay"), - ("t", "set sticky cookie expression"), - ("u", "set sticky auth expression"), ] text.extend( common.format_keyvals(keys, key="key", val="text", indent=4) diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index bfe6a591..7a03f718 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -212,11 +212,15 @@ class Options(urwid.WidgetWrap): ), Option( "Sticky Auth", - "A" + "A", + lambda: master.stickyauth_txt, + self.sticky_auth ), Option( "Sticky Cookies", - "t" + "t", + lambda: master.stickycookie_txt, + self.sticky_cookie ), ] ) @@ -247,6 +251,10 @@ class Options(urwid.WidgetWrap): self.master.set_ignore_filter([]) self.master.set_tcp_filter([]) self.master.scripts = [] + self.master.set_stickyauth(None) + self.master.set_stickycookie(None) + self.master.state.default_body_view = contentview.get("Auto") + signals.update_settings.send(self) signals.status_message.send( message = "All options cleared", @@ -340,3 +348,17 @@ class Options(urwid.WidgetWrap): _set ) ) + + def sticky_auth(self): + signals.status_prompt.send( + prompt = "Sticky auth filter", + text = self.master.stickyauth_txt, + callback = self.master.set_stickyauth + ) + + def sticky_cookie(self): + signals.status_prompt.send( + prompt = "Sticky cookie filter", + text = self.master.stickycookie_txt, + callback = self.master.set_stickycookie + ) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index cdc57b17..af9da81f 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -63,17 +63,5 @@ class Window(urwid.Frame): ), callback = self.master.stop_server_playback_prompt, ) - elif k == "t": - signals.status_prompt.send( - prompt = "Sticky cookie filter", - text = self.master.stickycookie_txt, - callback = self.master.set_stickycookie - ) - elif k == "u": - signals.status_prompt.send( - prompt = "Sticky auth filter", - text = self.master.stickyauth_txt, - callback = self.master.set_stickyauth - ) else: return k -- cgit v1.2.3 From 538f215458891f045b2de6a8b675db48754fbb4a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 6 Apr 2015 17:45:36 +1200 Subject: console: factor out selection widget --- libmproxy/console/options.py | 157 ++++++++----------------------------------- libmproxy/console/select.py | 106 +++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 130 deletions(-) create mode 100644 libmproxy/console/select.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 7a03f718..27e01468 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -1,6 +1,7 @@ import urwid from . import common, signals, grideditor, contentview +from . import select footer = [ ('heading_key', "enter/space"), ":toggle ", @@ -18,205 +19,97 @@ def _mkhelp(): help_context = _mkhelp() -class OptionWidget(urwid.WidgetWrap): - def __init__(self, option, text, shortcut, active, focus): - self.option = option - textattr = "text" - keyattr = "key" - if focus and active: - textattr = "option_active_selected" - keyattr = "option_selected_key" - elif focus: - textattr = "option_selected" - keyattr = "option_selected_key" - elif active: - textattr = "option_active" - text = common.highlight_key( - text, - shortcut, - textattr = textattr, - keyattr = keyattr - ) - opt = urwid.Text(text, align="left") - opt = urwid.AttrWrap(opt, textattr) - opt = urwid.Padding(opt, align = "center", width = 40) - urwid.WidgetWrap.__init__(self, opt) - - def keypress(self, size, key): - return key - - def selectable(self): - return True - - -class OptionWalker(urwid.ListWalker): - def __init__(self, options): - urwid.ListWalker.__init__(self) - self.options = options - self.focus = 0 - signals.update_settings.connect(self.sig_update_settings) - - def sig_update_settings(self, sender): - self._modified() - - def set_focus(self, pos): - self.focus = pos - - def get_focus(self): - return self.options[self.focus].render(True), self.focus - - def get_next(self, pos): - if pos >= len(self.options)-1: - return None, None - return self.options[pos+1].render(False), pos+1 - - def get_prev(self, pos): - if pos <= 0: - return None, None - return self.options[pos-1].render(False), pos-1 - - -class OptionListBox(urwid.ListBox): - def __init__(self, options): - urwid.ListBox.__init__( - self, - OptionWalker(options) - ) - self.options = options - self.keymap = {} - for i in options: - if hasattr(i, "shortcut"): - if i.shortcut in self.keymap: - raise ValueError("Duplicate shortcut key: %s"%i.shortcut) - self.keymap[i.shortcut] = i - - def keypress(self, size, key): - if key == "enter" or key == " ": - self.get_focus()[0].option.activate() - return None - key = common.shortcuts(key) - if key in self.keymap: - self.keymap[key].activate() - self.set_focus(self.options.index(self.keymap[key])) - return None - return super(self.__class__, self).keypress(size, key) - - - -class Heading: - def __init__(self, text): - self.text = text - - def render(self, focus): - opt = urwid.Text("\n" + self.text, align="left") - opt = urwid.AttrWrap(opt, "title") - opt = urwid.Padding(opt, align = "center", width = 40) - return opt - - -_neg = lambda: False -class Option: - def __init__(self, text, shortcut, getstate=None, activate=None): - self.text = text - self.shortcut = shortcut - self.getstate = getstate or _neg - self.activate = activate or _neg - - def render(self, focus): - return OptionWidget(self, self.text, self.shortcut, self.getstate(), focus) - - class Options(urwid.WidgetWrap): def __init__(self, master): self.master = master - self.lb = OptionListBox( + self.lb = select.Select( [ - Heading("Traffic Manipulation"), - Option( + select.Heading("Traffic Manipulation"), + select.Option( "Header Set Patterns", "H", lambda: master.setheaders.count(), self.setheaders ), - Option( + select.Option( "Ignore Patterns", "I", lambda: master.server.config.check_ignore, self.ignorepatterns ), - Option( + select.Option( "Replacement Patterns", "R", lambda: master.replacehooks.count(), self.replacepatterns ), - Option( + select.Option( "Scripts", "S", lambda: master.scripts, self.scripts ), - Heading("Interface"), - Option( + select.Heading("Interface"), + select.Option( "Default Display Mode", "M", self.has_default_displaymode, self.default_displaymode ), - Option( + select.Option( "Show Host", "w", lambda: master.showhost, self.toggle_showhost ), - Heading("Network"), - Option( + select.Heading("Network"), + select.Option( "No Upstream Certs", "U", lambda: master.server.config.no_upstream_cert, self.toggle_upstream_cert ), - Option( + select.Option( "TCP Proxying", "T", lambda: master.server.config.check_tcp, self.tcp_proxy ), - Heading("Utility"), - Option( + select.Heading("Utility"), + select.Option( "Anti-Cache", "a", lambda: master.anticache, self.toggle_anticache ), - Option( + select.Option( "Anti-Compression", "o", lambda: master.anticomp, self.toggle_anticomp ), - Option( + select.Option( "Kill Extra", "x", lambda: master.killextra, self.toggle_killextra ), - Option( + select.Option( "No Refresh", "f", lambda: not master.refresh_server_playback, self.toggle_refresh_server_playback ), - Option( + select.Option( "Sticky Auth", "A", lambda: master.stickyauth_txt, self.sticky_auth ), - Option( + select.Option( "Sticky Cookies", "t", lambda: master.stickycookie_txt, @@ -224,14 +117,18 @@ class Options(urwid.WidgetWrap): ), ] ) - title = urwid.Text("Options") + title = urwid.Text("select.Options") title = urwid.Padding(title, align="left", width=("relative", 100)) - title = urwid.AttrWrap(title, "heading") + title = urwid.AttrWrap(title, "select.Heading") self._w = urwid.Frame( self.lb, header = title ) self.master.loop.widget.footer.update("") + signals.update_settings.connect(self.sig_update_settings) + + def sig_update_settings(self, sender): + self.lb.walker._modified() def keypress(self, size, key): if key == "C": @@ -257,7 +154,7 @@ class Options(urwid.WidgetWrap): signals.update_settings.send(self) signals.status_message.send( - message = "All options cleared", + message = "All select.Options cleared", expire = 1 ) diff --git a/libmproxy/console/select.py b/libmproxy/console/select.py new file mode 100644 index 00000000..411fc179 --- /dev/null +++ b/libmproxy/console/select.py @@ -0,0 +1,106 @@ +import urwid + +from . import common + +class _OptionWidget(urwid.WidgetWrap): + def __init__(self, option, text, shortcut, active, focus): + self.option = option + textattr = "text" + keyattr = "key" + if focus and active: + textattr = "option_active_selected" + keyattr = "option_selected_key" + elif focus: + textattr = "option_selected" + keyattr = "option_selected_key" + elif active: + textattr = "option_active" + text = common.highlight_key( + text, + shortcut, + textattr = textattr, + keyattr = keyattr + ) + opt = urwid.Text(text, align="left") + opt = urwid.AttrWrap(opt, textattr) + opt = urwid.Padding(opt, align = "center", width = 40) + urwid.WidgetWrap.__init__(self, opt) + + def keypress(self, size, key): + return key + + def selectable(self): + return True + + +class OptionWalker(urwid.ListWalker): + def __init__(self, options): + urwid.ListWalker.__init__(self) + self.options = options + self.focus = 0 + + def set_focus(self, pos): + self.focus = pos + + def get_focus(self): + return self.options[self.focus].render(True), self.focus + + def get_next(self, pos): + if pos >= len(self.options)-1: + return None, None + return self.options[pos+1].render(False), pos+1 + + def get_prev(self, pos): + if pos <= 0: + return None, None + return self.options[pos-1].render(False), pos-1 + + +class Heading: + def __init__(self, text): + self.text = text + + def render(self, focus): + opt = urwid.Text("\n" + self.text, align="left") + opt = urwid.AttrWrap(opt, "title") + opt = urwid.Padding(opt, align = "center", width = 40) + return opt + + +_neg = lambda: False +class Option: + def __init__(self, text, shortcut, getstate=None, activate=None): + self.text = text + self.shortcut = shortcut + self.getstate = getstate or _neg + self.activate = activate or _neg + + def render(self, focus): + return _OptionWidget(self, self.text, self.shortcut, self.getstate(), focus) + + +class Select(urwid.ListBox): + def __init__(self, options): + self.walker = OptionWalker(options) + urwid.ListBox.__init__( + self, + self.walker + ) + self.options = options + self.keymap = {} + for i in options: + if hasattr(i, "shortcut"): + if i.shortcut in self.keymap: + raise ValueError("Duplicate shortcut key: %s"%i.shortcut) + self.keymap[i.shortcut] = i + + def keypress(self, size, key): + if key == "enter" or key == " ": + self.get_focus()[0].option.activate() + return None + key = common.shortcuts(key) + if key in self.keymap: + self.keymap[key].activate() + self.set_focus(self.options.index(self.keymap[key])) + return None + return super(self.__class__, self).keypress(size, key) -- cgit v1.2.3 From 1cb1ee411b9c8ffc40f83bcca99770af7f43a521 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 08:42:40 +1200 Subject: console: palette picker for the options screen --- libmproxy/console/__init__.py | 22 +++++++++++--- libmproxy/console/help.py | 22 ++++++++++++++ libmproxy/console/options.py | 13 +++++++-- libmproxy/console/palettepicker.py | 59 ++++++++++++++++++++++++++++++++++++++ libmproxy/console/palettes.py | 1 + libmproxy/console/select.py | 15 +++++----- 6 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 libmproxy/console/palettepicker.py (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 2421aa6b..1fdf503a 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -16,7 +16,7 @@ import weakref from .. import controller, flow, script from . import flowlist, flowview, help, window, signals, options -from . import grideditor, palettes, contentview, statusbar +from . import grideditor, palettes, contentview, statusbar, palettepicker EVENTLOG_SIZE = 500 @@ -157,7 +157,6 @@ class ConsoleMaster(flow.FlowMaster): self.setheaders.add(*i) self.flow_list_walker = None - self.set_palette(options.palette) r = self.set_intercept(options.intercept) if r: @@ -183,6 +182,7 @@ class ConsoleMaster(flow.FlowMaster): self.rheaders = options.rheaders self.nopop = options.nopop self.showhost = options.showhost + self.palette = options.palette self.eventlog = options.eventlog self.eventlist = urwid.SimpleListWalker([]) @@ -391,7 +391,11 @@ class ConsoleMaster(flow.FlowMaster): os.unlink(name) def set_palette(self, name): - self.palette = palettes.palettes[name] + self.palette = name + self.ui.register_palette( + palettes.palettes[name].palette() + ) + self.ui.clear() def ticker(self, *userdata): changed = self.tick(self.masterq, timeout=0) @@ -403,7 +407,7 @@ class ConsoleMaster(flow.FlowMaster): def run(self): self.ui = urwid.raw_display.Screen() self.ui.set_terminal_properties(256) - self.ui.register_palette(self.palette.palette()) + self.set_palette(self.palette) self.flow_list_walker = flowlist.FlowListWalker(self, self.state) self.loop = urwid.MainLoop( urwid.SolidFill("x"), @@ -475,6 +479,16 @@ class ConsoleMaster(flow.FlowMaster): options.help_context, ) + def view_palette_picker(self): + signals.push_view_state.send(self) + self.loop.widget = window.Window( + self, + palettepicker.PalettePicker(self), + None, + statusbar.StatusBar(self, palettepicker.footer), + palettepicker.help_context, + ) + def view_grideditor(self, ge): signals.push_view_state.send(self) self.loop.widget = window.Window( diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 0da29907..44195798 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -103,3 +103,25 @@ class HelpView(urwid.ListBox): elif key == "?": key = None return urwid.ListBox.keypress(self, size, key) + + +class PalettePicker(urwid.WidgetWrap): + def __init__(self, master): + self.master = master + self.lb = select.Select( + [ + select.Heading("Low"), + select.Option( + "One Two", + "O", + ), + ] + ) + title = urwid.Text("Palettes") + title = urwid.Padding(title, align="left", width=("relative", 100)) + title = urwid.AttrWrap(title, "heading") + self._w = urwid.Frame( + self.lb, + header = title + ) + diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 27e01468..23774dc3 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -57,6 +57,12 @@ class Options(urwid.WidgetWrap): self.has_default_displaymode, self.default_displaymode ), + select.Option( + "Palette", + "P", + lambda: False, + self.palette + ), select.Option( "Show Host", "w", @@ -117,9 +123,9 @@ class Options(urwid.WidgetWrap): ), ] ) - title = urwid.Text("select.Options") + title = urwid.Text("Options") title = urwid.Padding(title, align="left", width=("relative", 100)) - title = urwid.AttrWrap(title, "select.Heading") + title = urwid.AttrWrap(title, "heading") self._w = urwid.Frame( self.lb, header = title @@ -259,3 +265,6 @@ class Options(urwid.WidgetWrap): text = self.master.stickycookie_txt, callback = self.master.set_stickycookie ) + + def palette(self): + self.master.view_palette_picker() diff --git a/libmproxy/console/palettepicker.py b/libmproxy/console/palettepicker.py new file mode 100644 index 00000000..0062d9ae --- /dev/null +++ b/libmproxy/console/palettepicker.py @@ -0,0 +1,59 @@ +import urwid + +from . import select, common, palettes + +footer = [ + ('heading_key', "enter/space"), ":select", +] + +def _mkhelp(): + text = [] + keys = [ + ("enter/space", "select"), + ] + text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) + return text +help_context = _mkhelp() + + +class PalettePicker(urwid.WidgetWrap): + def __init__(self, master): + self.master = master + low, high = [], [] + for k, v in palettes.palettes.items(): + if v.high: + high.append(k) + else: + low.append(k) + high.sort() + low.sort() + + options = [ + select.Heading("High Colour") + ] + + def mkopt(name): + return select.Option( + i, + None, + None, + lambda: self.select(name) + ) + + for i in high: + options.append(mkopt(i)) + options.append(select.Heading("Low Colour")) + for i in low: + options.append(mkopt(i)) + + self.lb = select.Select(options) + title = urwid.Text("Palettes") + title = urwid.Padding(title, align="left", width=("relative", 100)) + title = urwid.AttrWrap(title, "heading") + self._w = urwid.Frame( + self.lb, + header = title + ) + + def select(self, name): + self.master.set_palette(name) diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index 3e6ce21f..a6ad3f45 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -279,6 +279,7 @@ class SolarizedDark(LowDark): ) +DEFAULT = "dark" palettes = { "lowlight": LowLight(), "lowdark": LowDark(), diff --git a/libmproxy/console/select.py b/libmproxy/console/select.py index 411fc179..61ee50e4 100644 --- a/libmproxy/console/select.py +++ b/libmproxy/console/select.py @@ -15,12 +15,13 @@ class _OptionWidget(urwid.WidgetWrap): keyattr = "option_selected_key" elif active: textattr = "option_active" - text = common.highlight_key( - text, - shortcut, - textattr = textattr, - keyattr = keyattr - ) + if shortcut: + text = common.highlight_key( + text, + shortcut, + textattr = textattr, + keyattr = keyattr + ) opt = urwid.Text(text, align="left") opt = urwid.AttrWrap(opt, textattr) opt = urwid.Padding(opt, align = "center", width = 40) @@ -89,7 +90,7 @@ class Select(urwid.ListBox): self.options = options self.keymap = {} for i in options: - if hasattr(i, "shortcut"): + if hasattr(i, "shortcut") and i.shortcut: if i.shortcut in self.keymap: raise ValueError("Duplicate shortcut key: %s"%i.shortcut) self.keymap[i.shortcut] = i -- cgit v1.2.3 From f6a3bd15b3b8c3d30abf6e5209e10cff3c4784e0 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 10:01:18 +1200 Subject: console: All palettes now explicitly set a background colour There's a new option --palette-transparent to turn this off if you want to use your own terminal background. --- libmproxy/console/__init__.py | 4 +++- libmproxy/console/palettes.py | 50 ++++++++++++++++++++++++++++++++----------- libmproxy/console/window.py | 9 ++++++-- 3 files changed, 47 insertions(+), 16 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 1fdf503a..804f73bc 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -132,6 +132,7 @@ class Options(object): "wfile", "nopop", "palette", + "palette_transparent" ] def __init__(self, **kwargs): @@ -183,6 +184,7 @@ class ConsoleMaster(flow.FlowMaster): self.nopop = options.nopop self.showhost = options.showhost self.palette = options.palette + self.palette_transparent = options.palette_transparent self.eventlog = options.eventlog self.eventlist = urwid.SimpleListWalker([]) @@ -393,7 +395,7 @@ class ConsoleMaster(flow.FlowMaster): def set_palette(self, name): self.palette = name self.ui.register_palette( - palettes.palettes[name].palette() + palettes.palettes[name].palette(self.palette_transparent) ) self.ui.clear() diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index a6ad3f45..52d7ab8f 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -1,4 +1,3 @@ - # Low-color themes should ONLY use the standard foreground and background # colours listed here: # @@ -6,9 +5,9 @@ # - class Palette: _fields = [ + 'background', 'title', # Status bar & heading @@ -35,15 +34,33 @@ class Palette: ] high = None - def palette(self): + def palette(self, transparent): l = [] + highback, lowback = None, None + if not transparent: + if self.high and self.high.get("background"): + highback = self.high["background"][1] + lowback = self.low["background"][1] + for i in self._fields: - v = [i] - v.extend(self.low[i]) - if self.high and i in self.high: - v.append(None) - v.extend(self.high[i]) - l.append(tuple(v)) + if transparent and i == "background": + l.append(["background", "default", "default"]) + else: + v = [i] + low = list(self.low[i]) + if lowback and low[1] == "default": + low[1] = lowback + v.extend(low) + if self.high and i in self.high: + v.append(None) + high = list(self.high[i]) + if highback and high[1] == "default": + high[1] = highback + v.extend(high) + elif highback: + high = [None, low[0], highback] + v.extend(high) + l.append(tuple(v)) return l @@ -52,6 +69,7 @@ class LowDark(Palette): Low-color dark background """ low = dict( + background = ('white', 'black'), title = ('white,bold', 'default'), # Status bar & heading @@ -110,6 +128,7 @@ class LowLight(Palette): Low-color light background """ low = dict( + background = ('black', 'white'), title = ('dark magenta', 'default'), # Status bar & heading @@ -158,6 +177,7 @@ class LowLight(Palette): class Light(LowLight): high = dict( + background = ('black', 'g100'), heading = ('g99', '#08f'), heading_key = ('#0ff,bold', '#08f'), heading_inactive = ('g35', 'g85'), @@ -171,10 +191,10 @@ sol_base03 = "h234" sol_base02 = "h235" sol_base01 = "h240" sol_base00 = "h241" -sol_base0 = "h244" -sol_base1 = "h245" -sol_base2 = "h254" -sol_base3 = "h230" +sol_base0 = "h244" +sol_base1 = "h245" +sol_base2 = "h254" +sol_base3 = "h230" sol_yellow = "h136" sol_orange = "h166" sol_red = "h160" @@ -183,8 +203,11 @@ sol_violet = "h61" sol_blue = "h33" sol_cyan = "h37" sol_green = "h64" + + class SolarizedLight(LowLight): high = dict( + background = (sol_base00, sol_base3), title = (sol_cyan, 'default'), text = (sol_base00, 'default'), @@ -233,6 +256,7 @@ class SolarizedLight(LowLight): class SolarizedDark(LowDark): high = dict( + background = (sol_base2, sol_base03), title = (sol_blue, 'default'), text = (sol_base1, 'default'), diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index af9da81f..d64e83df 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,10 +1,15 @@ import urwid -from . import grideditor, signals, contentview +from . import signals class Window(urwid.Frame): def __init__(self, master, body, header, footer, helpctx): - urwid.Frame.__init__(self, body, header=header, footer=footer) + urwid.Frame.__init__( + self, + urwid.AttrWrap(body, "background"), + header = urwid.AttrWrap(header, "background") if header else None, + footer = urwid.AttrWrap(footer, "background") if footer else None + ) self.master = master self.helpctx = helpctx signals.focus.connect(self.sig_focus) -- cgit v1.2.3 From 94cd704f73d5c0543dda66a861c3ae0ba1ec4330 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 10:11:25 +1200 Subject: console: toggle palette transparency from options, indicate selected palette --- libmproxy/console/palettepicker.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/palettepicker.py b/libmproxy/console/palettepicker.py index 0062d9ae..7e2c10cd 100644 --- a/libmproxy/console/palettepicker.py +++ b/libmproxy/console/palettepicker.py @@ -1,11 +1,12 @@ import urwid -from . import select, common, palettes +from . import select, common, palettes, signals footer = [ ('heading_key', "enter/space"), ":select", ] + def _mkhelp(): text = [] keys = [ @@ -36,7 +37,7 @@ class PalettePicker(urwid.WidgetWrap): return select.Option( i, None, - None, + lambda: self.master.palette == name, lambda: self.select(name) ) @@ -46,6 +47,18 @@ class PalettePicker(urwid.WidgetWrap): for i in low: options.append(mkopt(i)) + options.extend( + [ + select.Heading("Options"), + select.Option( + "Transparent", + "T", + lambda: master.palette_transparent, + self.toggle_palette_transparent + ) + ] + ) + self.lb = select.Select(options) title = urwid.Text("Palettes") title = urwid.Padding(title, align="left", width=("relative", 100)) @@ -54,6 +67,15 @@ class PalettePicker(urwid.WidgetWrap): self.lb, header = title ) + signals.update_settings.connect(self.sig_update_settings) + + def sig_update_settings(self, sender): + self.lb.walker._modified() def select(self, name): self.master.set_palette(name) + + def toggle_palette_transparent(self): + self.master.palette_transparent = not self.master.palette_transparent + self.master.set_palette(self.master.palette) + signals.update_settings.send(self) -- cgit v1.2.3 From 6bab6f2ef1b5043bdb8263bc45429fc45d66c7e2 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 11:36:42 +1200 Subject: console: tune palettes a bit --- libmproxy/console/palettes.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index 52d7ab8f..e34233a6 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -57,7 +57,7 @@ class Palette: if highback and high[1] == "default": high[1] = highback v.extend(high) - elif highback: + elif highback and self.low[i][1] == "default": high = [None, low[0], highback] v.extend(high) l.append(tuple(v)) @@ -73,9 +73,9 @@ class LowDark(Palette): title = ('white,bold', 'default'), # Status bar & heading - heading = ('light gray', 'dark blue'), + heading = ('white', 'dark blue'), heading_key = ('light cyan', 'dark blue'), - heading_inactive = ('white', 'dark gray'), + heading_inactive = ('dark gray', 'light gray'), # Help key = ('light cyan', 'default'), @@ -83,10 +83,10 @@ class LowDark(Palette): text = ('light gray', 'default'), # Options - option_selected = ('light gray', 'dark blue'), - option_selected_key = ('light cyan', 'dark blue'), + option_selected = ('black', 'light gray'), + option_selected_key = ('light cyan', 'light gray'), option_active = ('light red', 'default'), - option_active_selected = ('light red', 'dark blue'), + option_active_selected = ('light red', 'light gray'), # List and Connections method = ('dark cyan', 'default'), @@ -120,6 +120,10 @@ class Dark(LowDark): high = dict( heading_inactive = ('g58', 'g11'), intercept = ('#f60', 'default'), + + option_selected = ('g85', 'g45'), + option_selected_key = ('light cyan', 'g50'), + option_active_selected = ('light red', 'g50'), ) @@ -182,6 +186,10 @@ class Light(LowLight): heading_key = ('#0ff,bold', '#08f'), heading_inactive = ('g35', 'g85'), replay = ('#0a0,bold', 'default'), + + option_selected = ('black', 'g85'), + option_selected_key = ('dark blue', 'g85'), + option_active_selected = ('light red', 'g85'), ) -- cgit v1.2.3 From c7b1234e13bd81891d4dc0fcc68de6237cec2e8c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 12:26:56 +1200 Subject: consone: don't enter Options if it's already open --- libmproxy/console/__init__.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 804f73bc..b055ba96 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -472,6 +472,9 @@ class ConsoleMaster(flow.FlowMaster): ) def view_options(self): + for i in self.view_stack: + if isinstance(i["body"], options.Options): + return signals.push_view_state.send(self) self.loop.widget = window.Window( self, -- cgit v1.2.3 From e76467e977c061d92f88500b23f11bbf3cc365bb Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 15:59:38 +1200 Subject: Refactor flow list state management - Use signal mechanism for state synchronisation - Move "Copy to clipboard" shortcut to "P" --- libmproxy/console/__init__.py | 20 ++++++-------------- libmproxy/console/flowlist.py | 23 +++++++++++++++-------- libmproxy/console/flowview.py | 4 ++-- libmproxy/console/help.py | 22 ---------------------- libmproxy/console/options.py | 4 ++-- libmproxy/console/signals.py | 2 ++ libmproxy/console/statusbar.py | 1 + 7 files changed, 28 insertions(+), 48 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index b055ba96..5c2402f6 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -157,8 +157,6 @@ class ConsoleMaster(flow.FlowMaster): for i in options.setheaders: self.setheaders.add(*i) - self.flow_list_walker = None - r = self.set_intercept(options.intercept) if r: print >> sys.stderr, "Intercept error:", r @@ -410,7 +408,6 @@ class ConsoleMaster(flow.FlowMaster): self.ui = urwid.raw_display.Screen() self.ui.set_terminal_properties(256) self.set_palette(self.palette) - self.flow_list_walker = flowlist.FlowListWalker(self, self.state) self.loop = urwid.MainLoop( urwid.SolidFill("x"), screen = self.ui, @@ -566,8 +563,7 @@ class ConsoleMaster(flow.FlowMaster): flow.FlowMaster.load_flows_file(self, path) except flow.FlowReadError, v: reterr = str(v) - if self.flow_list_walker: - self.sync_list_view() + signals.flowlist_change.send(self) return reterr def accept_all(self): @@ -575,7 +571,7 @@ class ConsoleMaster(flow.FlowMaster): def set_limit(self, txt): v = self.state.set_limit(txt) - self.sync_list_view() + signals.flowlist_change.send(self) return v def set_intercept(self, txt): @@ -612,13 +608,9 @@ class ConsoleMaster(flow.FlowMaster): self.state.killall(self) flow.FlowMaster.shutdown(self) - def sync_list_view(self): - self.flow_list_walker._modified() - signals.update_settings.send(self) - def clear_flows(self): self.state.clear() - self.sync_list_view() + signals.flowlist_change.send(self) def toggle_follow_flows(self): # toggle flow follow @@ -626,11 +618,11 @@ class ConsoleMaster(flow.FlowMaster): # jump to most recent flow if follow is now on if self.state.follow_focus: self.state.set_focus(self.state.flow_count()) - self.sync_list_view() + signals.flowlist_change.send(self) def delete_flow(self, f): self.state.delete_flow(f) - self.sync_list_view() + signals.flowlist_change.send(self) def refresh_focus(self): if self.state.view: @@ -644,7 +636,7 @@ class ConsoleMaster(flow.FlowMaster): f.intercept(self) else: f.reply() - self.sync_list_view() + signals.flowlist_change.send(self) signals.flow_change.send(self, flow = f) def clear_events(self): diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 946bd97b..8923fd60 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -15,10 +15,10 @@ def _mkhelp(): ("D", "duplicate flow"), ("e", "toggle eventlog"), ("F", "toggle follow flow list"), - ("g", "copy flow to clipboard"), ("l", "set limit filter pattern"), ("L", "load saved flows"), ("n", "create a new request"), + ("P", "copy flow to clipboard"), ("r", "replay request"), ("V", "revert changes to request"), ("w", "save flows "), @@ -157,11 +157,11 @@ class ConnectionItem(urwid.WidgetWrap): key = common.shortcuts(key) if key == "a": self.flow.accept_intercept(self.master) - self.master.sync_list_view() + signals.flowlist_change.send(self) elif key == "d": self.flow.kill(self.master) self.state.delete_flow(self.flow) - self.master.sync_list_view() + signals.flowlist_change.send(self) elif key == "D": f = self.master.duplicate_flow(self.flow) self.master.view_flow(f) @@ -169,7 +169,7 @@ class ConnectionItem(urwid.WidgetWrap): r = self.master.replay_request(self.flow) if r: signals.status_message.send(message=r) - self.master.sync_list_view() + signals.flowlist_change.send(self) elif key == "S": if not self.master.server_playback: signals.status_prompt_onekey.send( @@ -195,7 +195,7 @@ class ConnectionItem(urwid.WidgetWrap): signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) - self.master.sync_list_view() + signals.flowlist_change.send(self) signals.status_message.send(message="Reverted.") elif key == "w": signals.status_prompt_onekey.send( @@ -218,7 +218,7 @@ class ConnectionItem(urwid.WidgetWrap): callback = self.master.run_script_once, args = (self.flow,) ) - elif key == "g": + elif key == "P": common.ask_copy_part("a", self.flow, self.master, self.state) elif key == "b": common.ask_save_body(None, self.master, self.state, self.flow) @@ -231,6 +231,10 @@ class FlowListWalker(urwid.ListWalker): self.master, self.state = master, state if self.state.flow_count(): self.set_focus(0) + signals.flowlist_change.connect(self.sig_flowlist_change) + + def sig_flowlist_change(self, sender): + self._modified() def get_focus(self): f, i = self.state.get_focus() @@ -255,7 +259,10 @@ class FlowListWalker(urwid.ListWalker): class FlowListBox(urwid.ListBox): def __init__(self, master): self.master = master - urwid.ListBox.__init__(self, master.flow_list_walker) + urwid.ListBox.__init__( + self, + FlowListWalker(master, master.state) + ) def get_method_raw(self, k): if k: @@ -297,7 +304,7 @@ class FlowListBox(urwid.ListBox): key = common.shortcuts(key) if key == "A": self.master.accept_all() - self.master.sync_list_view() + signals.flowlist_change.send(self) elif key == "C": self.master.clear_flows() elif key == "e": diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 99844cb7..9776f2b1 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -20,7 +20,6 @@ def _mkhelp(): ("D", "duplicate flow"), ("e", "edit request/response"), ("f", "load full body data"), - ("g", "copy response(content/headers) to clipboard"), ("m", "change body display mode for this entity"), (None, common.highlight_key("automatic", "a") + @@ -60,6 +59,7 @@ def _mkhelp(): ), ("M", "change default body display mode"), ("p", "previous flow"), + ("P", "copy response(content/headers) to clipboard"), ("r", "replay request"), ("V", "revert changes to request"), ("v", "view body in external viewer"), @@ -546,7 +546,7 @@ class FlowView(tabs.Tabs): ) signals.flow_change.send(self, flow = self.flow) signals.status_message.send(message="") - elif key == "g": + elif key == "P": if self.tab_offset == TAB_REQ: scope = "q" else: diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 44195798..0da29907 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -103,25 +103,3 @@ class HelpView(urwid.ListBox): elif key == "?": key = None return urwid.ListBox.keypress(self, size, key) - - -class PalettePicker(urwid.WidgetWrap): - def __init__(self, master): - self.master = master - self.lb = select.Select( - [ - select.Heading("Low"), - select.Option( - "One Two", - "O", - ), - ] - ) - title = urwid.Text("Palettes") - title = urwid.Padding(title, align="left", width=("relative", 100)) - title = urwid.AttrWrap(title, "heading") - self._w = urwid.Frame( - self.lb, - header = title - ) - diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index 23774dc3..dc7e00d5 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -1,7 +1,7 @@ import urwid from . import common, signals, grideditor, contentview -from . import select +from . import select, palettes footer = [ ('heading_key', "enter/space"), ":toggle ", @@ -60,7 +60,7 @@ class Options(urwid.WidgetWrap): select.Option( "Palette", "P", - lambda: False, + lambda: self.master.palette != palettes.DEFAULT, self.palette ), select.Option( diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index e4c11f5a..6d429b93 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -24,6 +24,8 @@ update_settings = blinker.Signal() # Fired when a flow changes flow_change = blinker.Signal() +# Fired when the flow list changes +flowlist_change = blinker.Signal() # Pop and push view state onto a stack pop_view_state = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7fb15aa6..5455ad6e 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -114,6 +114,7 @@ class StatusBar(urwid.WidgetWrap): self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) signals.update_settings.connect(self.sig_update_settings) + signals.flowlist_change.connect(self.sig_update_settings) self.redraw() def sig_update_settings(self, sender): -- cgit v1.2.3 From 8e2e83a3c6d47e52a5398f851b2900dd042f3d6a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 7 Apr 2015 16:13:42 +1200 Subject: console: add g/G shortcuts throughout g: go to end G: go to beginning --- libmproxy/console/flowlist.py | 6 ++++++ libmproxy/console/help.py | 1 + libmproxy/console/searchable.py | 16 ++++++++++------ libmproxy/console/signals.py | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 8923fd60..aa3fd718 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -309,6 +309,12 @@ class FlowListBox(urwid.ListBox): self.master.clear_flows() elif key == "e": self.master.toggle_eventlog() + elif key == "G": + self.master.state.set_focus(0) + signals.flowlist_change.send(self) + elif key == "g": + self.master.state.set_focus(self.master.state.flow_count()) + signals.flowlist_change.send(self) elif key == "l": signals.status_prompt.send( prompt = "Limit", diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 0da29907..6d36ee71 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -28,6 +28,7 @@ class HelpView(urwid.ListBox): keys = [ ("j, k", "down, up"), ("h, l", "left, right (in some contexts)"), + ("g, G", "go to end, beginning"), ("space", "page down"), ("pg up/down", "page up/down"), ("arrows", "up, down, left, right"), diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index 8f63c3f5..a9572ae3 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -15,10 +15,8 @@ class Highlight(urwid.AttrMap): class Searchable(urwid.ListBox): def __init__(self, state, contents): - urwid.ListBox.__init__( - self, - urwid.SimpleFocusListWalker(contents) - ) + self.walker = urwid.SimpleFocusListWalker(contents) + urwid.ListBox.__init__(self, self.walker) self.state = state self.search_offset = 0 self.current_highlight = None @@ -31,10 +29,16 @@ class Searchable(urwid.ListBox): text = "", callback = self.set_search ) - if key == "n": + elif key == "n": self.find_next(False) - if key == "N": + elif key == "N": self.find_next(True) + elif key == "G": + self.set_focus(0) + self.walker._modified() + elif key == "g": + self.set_focus(len(self.walker)-1) + self.walker._modified() else: return super(self.__class__, self).keypress(size, key) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 6d429b93..c1bcf201 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -24,7 +24,7 @@ update_settings = blinker.Signal() # Fired when a flow changes flow_change = blinker.Signal() -# Fired when the flow list changes +# Fired when the flow list or focus changes flowlist_change = blinker.Signal() # Pop and push view state onto a stack -- cgit v1.2.3 From bea0bd236a60e3e6c80027448e51b7802394304a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 14 Apr 2015 11:58:10 +1200 Subject: Housekeeping and cleanups - No output to stdout on load in examples - they muck up the test suite. - Use the odict module directly, rather than aliasing it. The small convenience this gives to scripters is not worth it. - Move the cookie tests from the flow test module to the protocol_http test module. --- libmproxy/console/contentview.py | 3 ++- libmproxy/console/flowview.py | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 12ed5b64..454c992f 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -12,6 +12,7 @@ import traceback import urwid import netlib.utils +from netlib import odict from . import common from .. import utils, encoding, flow @@ -519,7 +520,7 @@ def get_content_view(viewmode, hdrItems, content, limit, logfunc, is_request): return "No content", "" msg = [] - hdrs = flow.ODictCaseless([list(i) for i in hdrItems]) + hdrs = odict.ODictCaseless([list(i) for i in hdrItems]) enc = hdrs.get_first("content-encoding") if enc and enc != "identity": diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 9776f2b1..d7dc0d23 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import os, sys, copy import urwid +from netlib import odict from . import common, grideditor, contentview, signals, searchable, tabs from . import flowdetailview from .. import utils, flow, controller @@ -275,11 +276,11 @@ class FlowView(tabs.Tabs): signals.flow_change.send(self, flow = self.flow) def set_headers(self, lst, conn): - conn.headers = flow.ODictCaseless(lst) + conn.headers = odict.ODictCaseless(lst) signals.flow_change.send(self, flow = self.flow) def set_query(self, lst, conn): - conn.set_query(flow.ODict(lst)) + conn.set_query(odict.ODict(lst)) signals.flow_change.send(self, flow = self.flow) def set_path_components(self, lst, conn): @@ -287,7 +288,7 @@ class FlowView(tabs.Tabs): signals.flow_change.send(self, flow = self.flow) def set_form(self, lst, conn): - conn.set_form_urlencoded(flow.ODict(lst)) + conn.set_form_urlencoded(odict.ODict(lst)) signals.flow_change.send(self, flow = self.flow) def edit_form(self, conn): @@ -311,7 +312,7 @@ class FlowView(tabs.Tabs): if not self.flow.response: self.flow.response = HTTPResponse( self.flow.request.httpversion, - 200, "OK", flow.ODictCaseless(), "" + 200, "OK", odict.ODictCaseless(), "" ) self.flow.response.reply = controller.DummyReply() message = self.flow.response -- cgit v1.2.3 From f33b483110db7a62b45e77b693176e2fba0dede9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 15 Apr 2015 09:43:15 +1200 Subject: Editor for request cookies --- libmproxy/console/flowview.py | 68 ++++++++++++++++++++++++++++------------- libmproxy/console/grideditor.py | 6 ++++ 2 files changed, 53 insertions(+), 21 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index d7dc0d23..3b1a92ec 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -1,14 +1,16 @@ from __future__ import absolute_import -import os, sys, copy +import os +import sys import urwid from netlib import odict from . import common, grideditor, contentview, signals, searchable, tabs from . import flowdetailview -from .. import utils, flow, controller +from .. import utils, controller from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded -class SearchError(Exception): pass +class SearchError(Exception): + pass def _mkhelp(): @@ -114,6 +116,7 @@ cache = utils.LRUCache(200) TAB_REQ = 0 TAB_RESP = 1 + class FlowView(tabs.Tabs): highlight_color = "focusfield" @@ -174,14 +177,14 @@ class FlowView(tabs.Tabs): else: limit = contentview.VIEW_CUTOFF description, text_objects = cache.get( - contentview.get_content_view, - viewmode, - tuple(tuple(i) for i in conn.headers.lst), - conn.content, - limit, - self.master.add_event, - isinstance(conn, HTTPRequest) - ) + contentview.get_content_view, + viewmode, + tuple(tuple(i) for i in conn.headers.lst), + conn.content, + limit, + self.master.add_event, + isinstance(conn, HTTPRequest) + ) return (description, text_objects) def viewmode_get(self): @@ -194,20 +197,23 @@ class FlowView(tabs.Tabs): def conn_text(self, conn): if conn: txt = common.format_keyvals( - [(h+":", v) for (h, v) in conn.headers.lst], - key = "header", - val = "text" - ) + [(h+":", v) for (h, v) in conn.headers.lst], + key = "header", + val = "text" + ) viewmode = self.viewmode_get() msg, body = self.content_view(viewmode, conn) - cols = [urwid.Text( + cols = [ + urwid.Text( [ ("heading", msg), ] ) ] - cols.append(urwid.Text([ + cols.append( + urwid.Text( + [ " ", ('heading', "["), ('heading_key', "m"), @@ -305,6 +311,11 @@ class FlowView(tabs.Tabs): if key == "y": self.edit_form(conn) + def set_cookies(self, lst, conn): + od = odict.ODict(lst) + conn.set_cookies(od) + signals.flow_change.send(self, flow = self.flow) + def edit(self, part): if self.tab_offset == TAB_REQ: message = self.flow.request @@ -318,6 +329,16 @@ class FlowView(tabs.Tabs): message = self.flow.response self.flow.backup() + if message == self.flow.request and part == "c": + self.master.view_grideditor( + grideditor.CookieEditor( + self.master, + message.get_cookies().lst, + self.set_cookies, + message + ) + ) + pass if part == "r": with decoded(message): # Fix an issue caused by some editors when editing a @@ -381,7 +402,7 @@ class FlowView(tabs.Tabs): keys = common.METHOD_OPTIONS, callback = self.edit_method ) - elif part == "c": + elif part == "o": signals.status_prompt.send( prompt = "Code", text = str(message.code), @@ -508,14 +529,19 @@ class FlowView(tabs.Tabs): elif conn: if key == "b": if self.tab_offset == TAB_REQ: - common.ask_save_body("q", self.master, self.state, self.flow) + common.ask_save_body( + "q", self.master, self.state, self.flow + ) else: - common.ask_save_body("s", self.master, self.state, self.flow) + common.ask_save_body( + "s", self.master, self.state, self.flow + ) elif key == "e": if self.tab_offset == TAB_REQ: signals.status_prompt_onekey.send( prompt = "Edit request", keys = ( + ("cookie", "c"), ("query", "q"), ("path", "p"), ("url", "u"), @@ -530,7 +556,7 @@ class FlowView(tabs.Tabs): signals.status_prompt_onekey.send( prompt = "Edit response", keys = ( - ("code", "c"), + ("code", "o"), ("message", "m"), ("header", "h"), ("raw body", "r"), diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 4bcc0171..4cba0849 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -543,3 +543,9 @@ class HostPatternEditor(GridEditor): re.compile(val, re.IGNORECASE) except re.error as e: return "Invalid regex: %s" % str(e) + + +class CookieEditor(GridEditor): + title = "Editing request Cookie header" + columns = 2 + headings = ("Name", "Value") -- cgit v1.2.3 From 850a50262be8264fabe1d1a18b2985b70f7a86ae Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 15 Apr 2015 12:56:43 +1200 Subject: console: add a -l flag to set the limit on startup --- libmproxy/console/__init__.py | 4 ++++ libmproxy/console/grideditor.py | 6 ++++++ 2 files changed, 10 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 5c2402f6..f6e09f0f 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -116,6 +116,7 @@ class Options(object): "keepserving", "kill", "intercept", + "limit", "no_server", "refresh_server_playback", "rfile", @@ -162,6 +163,9 @@ class ConsoleMaster(flow.FlowMaster): print >> sys.stderr, "Intercept error:", r sys.exit(1) + if options.limit: + self.set_limit(options.limit) + r = self.set_stickycookie(options.stickycookie) if r: print >> sys.stderr, "Sticky cookies error:", r diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 4cba0849..493268ba 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -549,3 +549,9 @@ class CookieEditor(GridEditor): title = "Editing request Cookie header" columns = 2 headings = ("Name", "Value") + + +class SetCookieEditor(GridEditor): + title = "Editing request SetCookie header" + columns = 2 + headings = ("Name", "Value") -- cgit v1.2.3 From 52716e3439ceeb47d1fe8545a4875dc36866c37c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 16 Apr 2015 10:57:12 +1200 Subject: console: first pass of a Set-cookie editor for responses --- libmproxy/console/common.py | 34 ++++--- libmproxy/console/flowview.py | 29 +++++- libmproxy/console/grideditor.py | 199 ++++++++++++++++++++++++++++++---------- 3 files changed, 196 insertions(+), 66 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 23d3a4a4..74d510eb 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -66,20 +66,26 @@ def format_keyvals(lst, key="key", val="text", indent=0): if kv is None: ret.append(urwid.Text("")) else: - cols = [] - # This cumbersome construction process is here for a reason: - # Urwid < 1.0 barfs if given a fixed size column of size zero. - if indent: - cols.append(("fixed", indent, urwid.Text(""))) - cols.extend([ - ( - "fixed", - maxk, - urwid.Text([(key, kv[0] or "")]) - ), - kv[1] if isinstance(kv[1], urwid.Widget) else urwid.Text([(val, kv[1])]) - ]) - ret.append(urwid.Columns(cols, dividechars = 2)) + if isinstance(kv[1], urwid.Widget): + v = kv[1] + elif kv[1] is None: + v = urwid.Text("") + else: + v = urwid.Text([(val, kv[1])]) + ret.append( + urwid.Columns( + [ + ("fixed", indent, urwid.Text("")), + ( + "fixed", + maxk, + urwid.Text([(key, kv[0] or "")]) + ), + v + ], + dividechars = 2 + ) + ) return ret diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 3b1a92ec..497248de 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -316,6 +316,19 @@ class FlowView(tabs.Tabs): conn.set_cookies(od) signals.flow_change.send(self, flow = self.flow) + def set_setcookies(self, lst, conn): + vals = [] + for i in lst: + vals.append( + [ + i[0], + [i[1], odict.ODictCaseless(i[2])] + ] + ) + od = odict.ODict(vals) + conn.set_cookies(od) + signals.flow_change.send(self, flow = self.flow) + def edit(self, part): if self.tab_offset == TAB_REQ: message = self.flow.request @@ -338,7 +351,18 @@ class FlowView(tabs.Tabs): message ) ) - pass + if message == self.flow.response and part == "c": + flattened = [] + for k, v in message.get_cookies().items(): + flattened.append([k, v[0], v[1].lst]) + self.master.view_grideditor( + grideditor.SetCookieEditor( + self.master, + flattened, + self.set_setcookies, + message + ) + ) if part == "r": with decoded(message): # Fix an issue caused by some editors when editing a @@ -541,7 +565,7 @@ class FlowView(tabs.Tabs): signals.status_prompt_onekey.send( prompt = "Edit request", keys = ( - ("cookie", "c"), + ("cookies", "c"), ("query", "q"), ("path", "p"), ("url", "u"), @@ -556,6 +580,7 @@ class FlowView(tabs.Tabs): signals.status_prompt_onekey.send( prompt = "Edit response", keys = ( + ("cookies", "c"), ("code", "o"), ("message", "m"), ("header", "h"), diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 493268ba..6d112e42 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -7,7 +7,7 @@ import urwid from . import common, signals from .. import utils, filt, script -from netlib import http_uastrings +from netlib import http_uastrings, http_cookies FOOTER = [ @@ -19,17 +19,36 @@ FOOTER_EDITING = [ ] -class SText(urwid.WidgetWrap): - def __init__(self, txt, focused, error): +class TextColumn: + subeditor = None + + def __init__(self, heading): + self.heading = heading + + def text(self, obj): + return SEscaped(obj or "") + + def blank(self): + return "" + + +class SubgridColumn: + def __init__(self, heading, subeditor): + self.heading = heading + self.subeditor = subeditor + + def text(self, obj): + p = http_cookies._format_pairs(obj, sep="\n") + return urwid.Text(p) + + def blank(self): + return [] + + +class SEscaped(urwid.WidgetWrap): + def __init__(self, txt): txt = txt.encode("string-escape") w = urwid.Text(txt, wrap="any") - if focused: - if error: - w = urwid.AttrWrap(w, "focusfield_error") - else: - w = urwid.AttrWrap(w, "focusfield") - elif error: - w = urwid.AttrWrap(w, "field_error") urwid.WidgetWrap.__init__(self, w) def get_text(self): @@ -50,7 +69,7 @@ class SEdit(urwid.WidgetWrap): urwid.WidgetWrap.__init__(self, w) def get_text(self): - return self._w.get_text()[0] + return self._w.get_text()[0].strip() def selectable(self): return True @@ -67,9 +86,15 @@ class GridRow(urwid.WidgetWrap): self.editing = SEdit(v) self.fields.append(self.editing) else: - self.fields.append( - SText(v, True if focused == i else False, i in errors) - ) + w = self.editor.columns[i].text(v) + if focused == i: + if i in errors: + w = urwid.AttrWrap(w, "focusfield_error") + else: + w = urwid.AttrWrap(w, "focusfield") + elif i in errors: + w = urwid.AttrWrap(w, "field_error") + self.fields.append(w) fspecs = self.fields[:] if len(self.fields) > 1: @@ -126,7 +151,9 @@ class GridWalker(urwid.ListWalker): val = val.decode("string-escape") except ValueError: signals.status_message.send( - self, message = "Invalid Python-style string encoding.", expure = 1000 + self, + message = "Invalid Python-style string encoding.", + expire = 1000 ) return errors = self.lst[self.focus][1] @@ -136,10 +163,15 @@ class GridWalker(urwid.ListWalker): errors.add(self.focus_col) else: errors.discard(self.focus_col) - - row = list(self.lst[self.focus][0]) - row[self.focus_col] = val - self.lst[self.focus] = [tuple(row), errors] + self.set_value(val, self.focus, self.focus_col, errors) + + def set_value(self, val, focus, focus_col, errors=None): + if not errors: + errors = set([]) + row = list(self.lst[focus][0]) + row[focus_col] = val + self.lst[focus] = [tuple(row), errors] + self._modified() def delete_focus(self): if self.lst: @@ -149,7 +181,10 @@ class GridWalker(urwid.ListWalker): def _insert(self, pos): self.focus = pos - self.lst.insert(self.focus, [[""]*self.editor.columns, set([])]) + self.lst.insert( + self.focus, [ + [c.blank() for c in self.editor.columns], set([])] + ) self.focus_col = 0 self.start_edit() @@ -179,12 +214,12 @@ class GridWalker(urwid.ListWalker): self._modified() def right(self): - self.focus_col = min(self.focus_col + 1, self.editor.columns-1) + self.focus_col = min(self.focus_col + 1, len(self.editor.columns)-1) self._modified() def tab_next(self): self.stop_edit() - if self.focus_col < self.editor.columns-1: + if self.focus_col < len(self.editor.columns)-1: self.focus_col += 1 elif self.focus != len(self.lst)-1: self.focus_col = 0 @@ -231,7 +266,6 @@ FIRST_WIDTH_MIN = 20 class GridEditor(urwid.WidgetWrap): title = None columns = None - headings = None def __init__(self, master, value, callback, *cb_args, **cb_kwargs): value = copy.deepcopy(value) @@ -241,7 +275,7 @@ class GridEditor(urwid.WidgetWrap): first_width = 20 if value: for r in value: - assert len(r) == self.columns + assert len(r) == len(self.columns) first_width = max(len(r), first_width) self.first_width = min(first_width, FIRST_WIDTH_MAX) @@ -250,9 +284,9 @@ class GridEditor(urwid.WidgetWrap): title = urwid.AttrWrap(title, "heading") headings = [] - for i, h in enumerate(self.headings): - c = urwid.Text(h) - if i == 0 and len(self.headings) > 1: + for i, col in enumerate(self.columns): + c = urwid.Text(col.heading) + if i == 0 and len(self.columns) > 1: headings.append(("fixed", first_width + 2, c)) else: headings.append(c) @@ -303,6 +337,9 @@ class GridEditor(urwid.WidgetWrap): except IOError, v: return str(v) + def set_subeditor_value(self, val, focus, focus_col): + self.walker.set_value(val, focus, focus_col) + def keypress(self, size, key): if self.walker.editing: if key in ["esc"]: @@ -317,10 +354,11 @@ class GridEditor(urwid.WidgetWrap): return None key = common.shortcuts(key) + column = self.columns[self.walker.focus_col] if key in ["q", "esc"]: res = [] for i in self.walker.lst: - if not i[1] and any([x.strip() for x in i[0]]): + if not i[1] and any([x for x in i[0]]): res.append(i[0]) self.callback(res, *self.cb_args, **self.cb_kwargs) signals.pop_view_state.send(self) @@ -337,6 +375,13 @@ class GridEditor(urwid.WidgetWrap): elif key == "d": self.walker.delete_focus() elif key == "r": + if column.subeditor: + signals.status_message.send( + self, + message = "Press enter to edit this field.", + expire = 1000 + ) + return if self.walker.get_current_value() is not None: signals.status_prompt_path.send( self, @@ -344,6 +389,13 @@ class GridEditor(urwid.WidgetWrap): callback = self.read_file ) elif key == "R": + if column.subeditor: + signals.status_message.send( + self, + message = "Press enter to edit this field.", + expire = 1000 + ) + return if self.walker.get_current_value() is not None: signals.status_prompt_path.send( self, @@ -352,6 +404,13 @@ class GridEditor(urwid.WidgetWrap): args = (True,) ) elif key == "e": + if column.subeditor: + signals.status_message.send( + self, + message = "Press enter to edit this field.", + expire = 1000 + ) + return o = self.walker.get_current_value() if o is not None: n = self.master.spawn_editor(o.encode("string-escape")) @@ -359,7 +418,19 @@ class GridEditor(urwid.WidgetWrap): self.walker.set_current_value(n, False) self.walker._modified() elif key in ["enter"]: - self.walker.start_edit() + if column.subeditor: + self.master.view_grideditor( + self.columns[self.walker.focus_col].subeditor( + self.master, + self.walker.get_current_value(), + self.set_subeditor_value, + self.walker.focus, + self.walker.focus_col + ) + ) + else: + self.walker.start_edit() + elif not self.handle_key(key): return self._w.keypress(size, key) @@ -403,14 +474,18 @@ class GridEditor(urwid.WidgetWrap): class QueryEditor(GridEditor): title = "Editing query" - columns = 2 - headings = ("Key", "Value") + columns = [ + TextColumn("Key"), + TextColumn("Value") + ] class HeaderEditor(GridEditor): title = "Editing headers" - columns = 2 - headings = ("Key", "Value") + columns = [ + TextColumn("Key"), + TextColumn("Value") + ] def make_help(self): h = GridEditor.make_help(self) @@ -448,14 +523,19 @@ class HeaderEditor(GridEditor): class URLEncodedFormEditor(GridEditor): title = "Editing URL-encoded form" - columns = 2 - headings = ("Key", "Value") + columns = [ + TextColumn("Key"), + TextColumn("Value") + ] class ReplaceEditor(GridEditor): title = "Editing replacement patterns" - columns = 3 - headings = ("Filter", "Regex", "Replacement") + columns = [ + TextColumn("Filter"), + TextColumn("Regex"), + TextColumn("Replacement"), + ] def is_error(self, col, val): if col == 0: @@ -471,8 +551,11 @@ class ReplaceEditor(GridEditor): class SetHeadersEditor(GridEditor): title = "Editing header set patterns" - columns = 3 - headings = ("Filter", "Header", "Value") + columns = [ + TextColumn("Filter"), + TextColumn("Header"), + TextColumn("Value"), + ] def is_error(self, col, val): if col == 0: @@ -517,14 +600,16 @@ class SetHeadersEditor(GridEditor): class PathEditor(GridEditor): title = "Editing URL path components" - columns = 1 - headings = ("Component",) + columns = [ + TextColumn("Component"), + ] class ScriptEditor(GridEditor): title = "Editing scripts" - columns = 1 - headings = ("Command",) + columns = [ + TextColumn("Command"), + ] def is_error(self, col, val): try: @@ -535,8 +620,9 @@ class ScriptEditor(GridEditor): class HostPatternEditor(GridEditor): title = "Editing host patterns" - columns = 1 - headings = ("Regex (matched on hostname:port / ip:port)",) + columns = [ + TextColumn("Regex (matched on hostname:port / ip:port)") + ] def is_error(self, col, val): try: @@ -547,11 +633,24 @@ class HostPatternEditor(GridEditor): class CookieEditor(GridEditor): title = "Editing request Cookie header" - columns = 2 - headings = ("Name", "Value") + columns = [ + TextColumn("Name"), + TextColumn("Value"), + ] + + +class CookieAttributeEditor(GridEditor): + title = "Editing Set-Cookie attributes" + columns = [ + TextColumn("Name"), + TextColumn("Value"), + ] class SetCookieEditor(GridEditor): - title = "Editing request SetCookie header" - columns = 2 - headings = ("Name", "Value") + title = "Editing response SetCookie header" + columns = [ + TextColumn("Name"), + TextColumn("Value"), + SubgridColumn("Attributes", CookieAttributeEditor), + ] -- cgit v1.2.3 From cb880cc65a6fc522ea457e0e2d0a61da4cc9405a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 16 Apr 2015 12:16:07 +1200 Subject: Adjust header key color in solarized palettes --- libmproxy/console/palettes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index e34233a6..6490eb73 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -246,7 +246,7 @@ class SolarizedLight(LowLight): error = (sol_red, 'default'), - header = (sol_base01, 'default'), + header = (sol_blue, 'default'), highlight = (sol_base01, 'default'), intercept = (sol_red, 'default',), replay = (sol_green, 'default',), @@ -295,7 +295,7 @@ class SolarizedDark(LowDark): error = (sol_red, 'default'), - header = (sol_base01, 'default'), + header = (sol_blue, 'default'), highlight = (sol_base01, 'default'), intercept = (sol_red, 'default',), replay = (sol_green, 'default',), -- cgit v1.2.3 From 7abaf3c3626a68a23233c3dc759ade32068f0462 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 16 Apr 2015 22:01:54 +1200 Subject: console: refactor grideditor, fix a crash on tab for subeditors --- libmproxy/console/grideditor.py | 111 +++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 59 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 6d112e42..e406fbaf 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -31,6 +31,34 @@ class TextColumn: def blank(self): return "" + def keypress(self, key, editor): + if key == "r": + if editor.walker.get_current_value() is not None: + signals.status_prompt_path.send( + self, + prompt = "Read file", + callback = editor.read_file + ) + elif key == "R": + if editor.walker.get_current_value() is not None: + signals.status_prompt_path.send( + editor, + prompt = "Read unescaped file", + callback = editor.read_file, + args = (True,) + ) + elif key == "e": + o = editor.walker.get_current_value() + if o is not None: + n = editor.master.spawn_editor(o.encode("string-escape")) + n = utils.clean_hanging_newline(n) + editor.walker.set_current_value(n, False) + editor.walker._modified() + elif key in ["enter"]: + editor.walker.start_edit() + else: + return key + class SubgridColumn: def __init__(self, heading, subeditor): @@ -44,6 +72,27 @@ class SubgridColumn: def blank(self): return [] + def keypress(self, key, editor): + if key in "rRe": + signals.status_message.send( + self, + message = "Press enter to edit this field.", + expire = 1000 + ) + return + elif key in ["enter"]: + editor.master.view_grideditor( + self.subeditor( + editor.master, + editor.walker.get_current_value(), + editor.set_subeditor_value, + editor.walker.focus, + editor.walker.focus_col + ) + ) + else: + return key + class SEscaped(urwid.WidgetWrap): def __init__(self, txt): @@ -195,7 +244,8 @@ class GridWalker(urwid.ListWalker): return self._insert(min(self.focus + 1, len(self.lst))) def start_edit(self): - if self.lst: + col = self.editor.columns[self.focus_col] + if self.lst and not col.subeditor: self.editing = GridRow( self.focus_col, True, self.editor, self.lst[self.focus] ) @@ -374,64 +424,7 @@ class GridEditor(urwid.WidgetWrap): self.walker.insert() elif key == "d": self.walker.delete_focus() - elif key == "r": - if column.subeditor: - signals.status_message.send( - self, - message = "Press enter to edit this field.", - expire = 1000 - ) - return - if self.walker.get_current_value() is not None: - signals.status_prompt_path.send( - self, - prompt = "Read file", - callback = self.read_file - ) - elif key == "R": - if column.subeditor: - signals.status_message.send( - self, - message = "Press enter to edit this field.", - expire = 1000 - ) - return - if self.walker.get_current_value() is not None: - signals.status_prompt_path.send( - self, - prompt = "Read unescaped file", - callback = self.read_file, - args = (True,) - ) - elif key == "e": - if column.subeditor: - signals.status_message.send( - self, - message = "Press enter to edit this field.", - expire = 1000 - ) - return - o = self.walker.get_current_value() - if o is not None: - n = self.master.spawn_editor(o.encode("string-escape")) - n = utils.clean_hanging_newline(n) - self.walker.set_current_value(n, False) - self.walker._modified() - elif key in ["enter"]: - if column.subeditor: - self.master.view_grideditor( - self.columns[self.walker.focus_col].subeditor( - self.master, - self.walker.get_current_value(), - self.set_subeditor_value, - self.walker.focus, - self.walker.focus_col - ) - ) - else: - self.walker.start_edit() - - elif not self.handle_key(key): + elif column.keypress(key, self) and not self.handle_key(key): return self._w.keypress(size, key) def is_error(self, col, val): -- cgit v1.2.3 From e963a9da4887268b03ceecf55086674121047056 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 17 Apr 2015 12:54:29 +1200 Subject: console: suport unary attributes Attributes with no value are treated as unary, e.g. "Secure" rather than "Secure=". If you really want to have an empty attribute value you can edit the header directly. Behind the scenes, restructure GridEditor to know about data conversion in and out of the editor. --- libmproxy/console/flowview.py | 21 +++----------- libmproxy/console/grideditor.py | 63 +++++++++++++++++++++++++++++++++++++---- libmproxy/console/options.py | 10 +++---- 3 files changed, 66 insertions(+), 28 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 497248de..632b725e 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -290,7 +290,7 @@ class FlowView(tabs.Tabs): signals.flow_change.send(self, flow = self.flow) def set_path_components(self, lst, conn): - conn.set_path_components([i[0] for i in lst]) + conn.set_path_components(lst) signals.flow_change.send(self, flow = self.flow) def set_form(self, lst, conn): @@ -316,17 +316,8 @@ class FlowView(tabs.Tabs): conn.set_cookies(od) signals.flow_change.send(self, flow = self.flow) - def set_setcookies(self, lst, conn): - vals = [] - for i in lst: - vals.append( - [ - i[0], - [i[1], odict.ODictCaseless(i[2])] - ] - ) - od = odict.ODict(vals) - conn.set_cookies(od) + def set_setcookies(self, data, conn): + conn.set_cookies(data) signals.flow_change.send(self, flow = self.flow) def edit(self, part): @@ -352,13 +343,10 @@ class FlowView(tabs.Tabs): ) ) if message == self.flow.response and part == "c": - flattened = [] - for k, v in message.get_cookies().items(): - flattened.append([k, v[0], v[1].lst]) self.master.view_grideditor( grideditor.SetCookieEditor( self.master, - flattened, + message.get_cookies(), self.set_setcookies, message ) @@ -397,7 +385,6 @@ class FlowView(tabs.Tabs): ) elif part == "p": p = message.get_path_components() - p = [[i] for i in p] self.master.view_grideditor( grideditor.PathEditor( self.master, diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index e406fbaf..e5e64403 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -7,7 +7,7 @@ import urwid from . import common, signals from .. import utils, filt, script -from netlib import http_uastrings, http_cookies +from netlib import http_uastrings, http_cookies, odict FOOTER = [ @@ -231,8 +231,10 @@ class GridWalker(urwid.ListWalker): def _insert(self, pos): self.focus = pos self.lst.insert( - self.focus, [ - [c.blank() for c in self.editor.columns], set([])] + self.focus, + [ + [c.blank() for c in self.editor.columns], set([]) + ] ) self.focus_col = 0 self.start_edit() @@ -318,7 +320,7 @@ class GridEditor(urwid.WidgetWrap): columns = None def __init__(self, master, value, callback, *cb_args, **cb_kwargs): - value = copy.deepcopy(value) + value = self.data_in(copy.deepcopy(value)) self.master, self.value, self.callback = master, value, callback self.cb_args, self.cb_kwargs = cb_args, cb_kwargs @@ -410,7 +412,7 @@ class GridEditor(urwid.WidgetWrap): for i in self.walker.lst: if not i[1] and any([x for x in i[0]]): res.append(i[0]) - self.callback(res, *self.cb_args, **self.cb_kwargs) + self.callback(self.data_out(res), *self.cb_args, **self.cb_kwargs) signals.pop_view_state.send(self) elif key in ["h", "left"]: self.walker.left() @@ -427,6 +429,19 @@ class GridEditor(urwid.WidgetWrap): elif column.keypress(key, self) and not self.handle_key(key): return self._w.keypress(size, key) + def data_out(self, data): + """ + Called on raw list data, before data is returned through the + callback. + """ + return data + + def data_in(self, data): + """ + Called to prepare provided data. + """ + return data + def is_error(self, col, val): """ Return False, or a string error message. @@ -597,6 +612,12 @@ class PathEditor(GridEditor): TextColumn("Component"), ] + def data_in(self, data): + return [[i] for i in data] + + def data_out(self, data): + return [i[0] for i in data] + class ScriptEditor(GridEditor): title = "Editing scripts" @@ -623,6 +644,12 @@ class HostPatternEditor(GridEditor): 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(GridEditor): title = "Editing request Cookie header" @@ -639,6 +666,15 @@ class CookieAttributeEditor(GridEditor): TextColumn("Value"), ] + def data_out(self, data): + ret = [] + for i in data: + if not i[1]: + ret.append([i[0], None]) + else: + ret.append(i) + return ret + class SetCookieEditor(GridEditor): title = "Editing response SetCookie header" @@ -647,3 +683,20 @@ class SetCookieEditor(GridEditor): TextColumn("Value"), SubgridColumn("Attributes", CookieAttributeEditor), ] + + def data_in(self, data): + flattened = [] + for k, v in data.items(): + flattened.append([k, v[0], v[1].lst]) + return flattened + + def data_out(self, data): + vals = [] + for i in data: + vals.append( + [ + i[0], + [i[1], odict.ODictCaseless(i[2])] + ] + ) + return odict.ODict(vals) diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index dc7e00d5..c728123f 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -197,13 +197,12 @@ class Options(urwid.WidgetWrap): def ignorepatterns(self): def _set(ignore): - patterns = (x[0] for x in ignore) - self.master.set_ignore_filter(patterns) + self.master.set_ignore_filter(ignore) signals.update_settings.send(self) self.master.view_grideditor( grideditor.HostPatternEditor( self.master, - [[x] for x in self.master.get_ignore_filter()], + self.master.get_ignore_filter(), _set ) ) @@ -241,13 +240,12 @@ class Options(urwid.WidgetWrap): def tcp_proxy(self): def _set(tcp): - patterns = (x[0] for x in tcp) - self.master.set_tcp_filter(patterns) + self.master.set_tcp_filter(tcp) signals.update_settings.send(self) self.master.view_grideditor( grideditor.HostPatternEditor( self.master, - [[x] for x in self.master.get_tcp_filter()], + self.master.get_tcp_filter(), _set ) ) -- cgit v1.2.3 From d20069fcd201cec534fbdc348cec5d63fa087c23 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 17 Apr 2015 13:06:45 +1200 Subject: console: more consistent view stack management --- libmproxy/console/__init__.py | 95 +++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 40 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index f6e09f0f..dc551a22 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -231,8 +231,9 @@ class ConsoleMaster(flow.FlowMaster): self.loop.set_alarm_in(seconds, cb) def sig_pop_view_state(self, sender): - if self.view_stack: - self.loop.widget = self.view_stack.pop() + if len(self.view_stack) > 1: + self.view_stack.pop() + self.loop.widget = self.view_stack[-1] else: signals.status_prompt_onekey.send( self, @@ -244,8 +245,10 @@ class ConsoleMaster(flow.FlowMaster): callback = self.quit, ) - def sig_push_view_state(self, sender): - self.view_stack.append(self.loop.widget) + def sig_push_view_state(self, sender, window): + self.view_stack.append(window) + self.loop.widget = window + self.loop.draw_screen() def start_stream_to_path(self, path, mode="wb"): path = os.path.expanduser(path) @@ -463,46 +466,54 @@ class ConsoleMaster(flow.FlowMaster): self.shutdown() def view_help(self, helpctx): - signals.push_view_state.send(self) - self.loop.widget = window.Window( + signals.push_view_state.send( self, - help.HelpView(helpctx), - None, - statusbar.StatusBar(self, help.footer), - None + window = window.Window( + self, + help.HelpView(helpctx), + None, + statusbar.StatusBar(self, help.footer), + None + ) ) def view_options(self): for i in self.view_stack: if isinstance(i["body"], options.Options): return - signals.push_view_state.send(self) - self.loop.widget = window.Window( + signals.push_view_state.send( self, - options.Options(self), - None, - statusbar.StatusBar(self, options.footer), - options.help_context, + window = window.Window( + self, + options.Options(self), + None, + statusbar.StatusBar(self, options.footer), + options.help_context, + ) ) def view_palette_picker(self): - signals.push_view_state.send(self) - self.loop.widget = window.Window( + signals.push_view_state.send( self, - palettepicker.PalettePicker(self), - None, - statusbar.StatusBar(self, palettepicker.footer), - palettepicker.help_context, + window = window.Window( + self, + palettepicker.PalettePicker(self), + None, + statusbar.StatusBar(self, palettepicker.footer), + palettepicker.help_context, + ) ) def view_grideditor(self, ge): - signals.push_view_state.send(self) - self.loop.widget = window.Window( + signals.push_view_state.send( self, - ge, - None, - statusbar.StatusBar(self, grideditor.FOOTER), - ge.make_help() + window = window.Window( + self, + ge, + None, + statusbar.StatusBar(self, grideditor.FOOTER), + ge.make_help() + ) ) def view_flowlist(self): @@ -516,24 +527,28 @@ class ConsoleMaster(flow.FlowMaster): else: body = flowlist.FlowListBox(self) - self.loop.widget = window.Window( + signals.push_view_state.send( self, - body, - None, - statusbar.StatusBar(self, flowlist.footer), - flowlist.help_context + window = window.Window( + self, + body, + None, + statusbar.StatusBar(self, flowlist.footer), + flowlist.help_context + ) ) - self.loop.draw_screen() def view_flow(self, flow, tab_offset=0): - signals.push_view_state.send(self) self.state.set_focus_flow(flow) - self.loop.widget = window.Window( + signals.push_view_state.send( self, - flowview.FlowView(self, self.state, flow, tab_offset), - flowview.FlowViewHeader(self, flow), - statusbar.StatusBar(self, flowview.footer), - flowview.help_context + window = window.Window( + self, + flowview.FlowView(self, self.state, flow, tab_offset), + flowview.FlowViewHeader(self, flow), + statusbar.StatusBar(self, flowview.footer), + flowview.help_context + ) ) def _write_flows(self, path, flows): -- cgit v1.2.3 From ff654730e8dc0e68998487a8dcaf3db00a42c371 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 17 Apr 2015 13:30:49 +1200 Subject: console: make g/G shortcuts work in more contexts --- libmproxy/console/flowlist.py | 4 ++++ libmproxy/console/grideditor.py | 9 +++++++-- libmproxy/console/help.py | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index aa3fd718..c7a0d1b7 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -47,6 +47,10 @@ class EventListBox(urwid.ListBox): if key == "C": self.master.clear_events() key = None + elif key == "G": + self.set_focus(0) + elif key == "g": + self.set_focus(len(self.master.eventlist)-1) return urwid.ListBox.keypress(self, size, key) diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index e5e64403..5a2da59f 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -294,6 +294,7 @@ class GridWalker(urwid.ListWalker): def set_focus(self, focus): self.stop_edit() self.focus = focus + self._modified() def get_next(self, pos): if pos+1 >= len(self.lst): @@ -414,6 +415,10 @@ class GridEditor(urwid.WidgetWrap): res.append(i[0]) self.callback(self.data_out(res), *self.cb_args, **self.cb_kwargs) signals.pop_view_state.send(self) + elif key == "G": + self.walker.set_focus(0) + elif key == "g": + self.walker.set_focus(len(self.walker.lst)-1) elif key in ["h", "left"]: self.walker.left() elif key in ["l", "right"]: @@ -459,10 +464,10 @@ class GridEditor(urwid.WidgetWrap): ("a", "add row after cursor"), ("d", "delete row"), ("e", "spawn external editor on current field"), - ("q", "return to flow view"), + ("q", "save changes and exit editor"), ("r", "read value from file"), ("R", "read unescaped value from file"), - ("esc", "return to flow view/exit field edit mode"), + ("esc", "save changes and exit editor"), ("tab", "next field"), ("enter", "edit field"), ] diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 6d36ee71..cbd5bef8 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -103,4 +103,8 @@ class HelpView(urwid.ListBox): return None elif key == "?": key = None + elif key == "G": + self.set_focus(0) + elif key == "g": + self.set_focus(len(self.body.contents)) return urwid.ListBox.keypress(self, size, key) -- cgit v1.2.3 From f4f57e62e55c70af12ad1044dd00009be9febf6d Mon Sep 17 00:00:00 2001 From: Choongwoo Han Date: Thu, 23 Apr 2015 15:02:57 +0900 Subject: Check overwrite, when save to file instaed of clipboard --- libmproxy/console/common.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 74d510eb..f417aade 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -196,18 +196,36 @@ def raw_format_flow(f, focus, extended, padding): def save_data(path, data, master, state): if not path: return - path = os.path.expanduser(path) try: with file(path, "wb") as f: f.write(data) except IOError, v: signals.status_message.send(message=v.strerror) +def ask_save_overwite(path, data, master, state): + if not path: + return + path = os.path.expanduser(path) + if os.path.exists(path): + def save_overwite(k): + if k == "y": + save_data(path, data, master, state) + + signals.status_prompt_onekey.send( + prompt = "'"+path+"' already exists. Overwite?", + keys = ( + ("yes", "y"), + ("no", "n"), + ), + callback = save_overwite + ) + else: + save_data(path, data, master, state) def ask_save_path(prompt, data, master, state): signals.status_prompt_path.send( prompt = prompt, - callback = save_data, + callback = ask_save_overwite, args = (data, master, state) ) -- cgit v1.2.3 From ad33d0925fa3ccd20fcd043a4a2c8ba28e8e0436 Mon Sep 17 00:00:00 2001 From: Choongwoo Han Date: Thu, 23 Apr 2015 15:40:57 +0900 Subject: Fix crash when save to clipboard before loading --- libmproxy/console/common.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 74d510eb..6d5579d4 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -218,6 +218,9 @@ def copy_flow_format_data(part, scope, flow): else: data = "" if scope in ("q", "a"): + if flow.request.content == None: + signals.status_message.send(message="Please retry, after finishing loading.") + return "", True with decoded(flow.request): if part == "h": data += flow.request.assemble() @@ -229,6 +232,9 @@ def copy_flow_format_data(part, scope, flow): # Add padding between request and response data += "\r\n" * 2 if scope in ("s", "a") and flow.response: + if flow.response.content == None: + signals.status_message.send(message="Please retry, after finishing loading.") + return "", True with decoded(flow.response): if part == "h": data += flow.response.assemble() @@ -236,17 +242,18 @@ def copy_flow_format_data(part, scope, flow): data += flow.response.content else: raise ValueError("Unknown part: {}".format(part)) - return data - + return data, False def copy_flow(part, scope, flow, master, state): """ part: _c_ontent, _a_ll, _u_rl scope: _a_ll, re_q_uest, re_s_ponse """ - data = copy_flow_format_data(part, scope, flow) + data, err = copy_flow_format_data(part, scope, flow) - if not data: + if err: + return + elif not data: if scope == "q": signals.status_message.send(message="No request content to copy.") elif scope == "s": -- cgit v1.2.3 From 79e587fe040be1cefe8637f28e6c2e1a8a50779d Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 26 Apr 2015 18:41:27 +0200 Subject: fix #568 --- libmproxy/console/__init__.py | 1 + libmproxy/console/flowlist.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index dc551a22..527ed07d 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -299,6 +299,7 @@ class ConsoleMaster(flow.FlowMaster): def toggle_eventlog(self): self.eventlog = not self.eventlog + signals.pop_view_state.send(self) self.view_flowlist() def _readflows(self, path): diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index c7a0d1b7..ffd9dbdd 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -233,8 +233,8 @@ class ConnectionItem(urwid.WidgetWrap): class FlowListWalker(urwid.ListWalker): def __init__(self, master, state): self.master, self.state = master, state - if self.state.flow_count(): - self.set_focus(0) + _, i = self.state.get_focus() + self.set_focus(i) signals.flowlist_change.connect(self.sig_flowlist_change) def sig_flowlist_change(self, sender): -- cgit v1.2.3 From b5690b81034ee0173cf2706429862fdd3ac688b8 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 26 Apr 2015 18:43:27 +0200 Subject: remove superfluous set_focus --- libmproxy/console/flowlist.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index ffd9dbdd..6ab45bad 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -233,8 +233,6 @@ class ConnectionItem(urwid.WidgetWrap): class FlowListWalker(urwid.ListWalker): def __init__(self, master, state): self.master, self.state = master, state - _, i = self.state.get_focus() - self.set_focus(i) signals.flowlist_change.connect(self.sig_flowlist_change) def sig_flowlist_change(self, sender): -- cgit v1.2.3 From 1742017752164c3fe28d6a549698ccecadf4cf49 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 26 Apr 2015 19:25:59 +0200 Subject: make code more pythonic --- libmproxy/console/common.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index f7dc0d37..ba6ba5b0 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -202,6 +202,7 @@ def save_data(path, data, master, state): except IOError, v: signals.status_message.send(message=v.strerror) + def ask_save_overwite(path, data, master, state): if not path: return @@ -222,6 +223,7 @@ def ask_save_overwite(path, data, master, state): else: save_data(path, data, master, state) + def ask_save_path(prompt, data, master, state): signals.status_prompt_path.send( prompt = prompt, @@ -236,9 +238,8 @@ def copy_flow_format_data(part, scope, flow): else: data = "" if scope in ("q", "a"): - if flow.request.content == None: - signals.status_message.send(message="Please retry, after finishing loading.") - return "", True + if flow.request.content is None or flow.request.content == CONTENT_MISSING: + return None, "Request content is missing" with decoded(flow.request): if part == "h": data += flow.request.assemble() @@ -250,9 +251,8 @@ def copy_flow_format_data(part, scope, flow): # Add padding between request and response data += "\r\n" * 2 if scope in ("s", "a") and flow.response: - if flow.response.content == None: - signals.status_message.send(message="Please retry, after finishing loading.") - return "", True + if flow.response.content is None or flow.response.content == CONTENT_MISSING: + return None, "Response content is missing" with decoded(flow.response): if part == "h": data += flow.response.assemble() @@ -262,16 +262,19 @@ def copy_flow_format_data(part, scope, flow): raise ValueError("Unknown part: {}".format(part)) return data, False + def copy_flow(part, scope, flow, master, state): """ - part: _c_ontent, _a_ll, _u_rl + part: _c_ontent, _h_eaders+content, _u_rl scope: _a_ll, re_q_uest, re_s_ponse """ data, err = copy_flow_format_data(part, scope, flow) if err: + signals.status_message.send(message=err) return - elif not data: + + if not data: if scope == "q": signals.status_message.send(message="No request content to copy.") elif scope == "s": -- cgit v1.2.3 From 1c26516b1822d82e3b701539591a1d22831e0a19 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 30 Apr 2015 12:18:01 +1200 Subject: pretty_size now lives in netlib.utils --- libmproxy/console/common.py | 3 ++- libmproxy/console/contentview.py | 2 +- libmproxy/console/statusbar.py | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index ba6ba5b0..b920a11f 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -7,6 +7,7 @@ import os from .. import utils from ..protocol.http import CONTENT_MISSING, decoded from . import signals +import netlib.utils try: import pyperclip @@ -379,7 +380,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2): ) if f.response: if f.response.content: - contentdesc = utils.pretty_size(len(f.response.content)) + contentdesc = netlib.utils.pretty_size(len(f.response.content)) elif f.response.content == CONTENT_MISSING: contentdesc = "[content missing]" else: diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 454c992f..aafba4d7 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -60,7 +60,7 @@ def trailer(clen, txt, limit): txt.append( urwid.Text( [ - ("highlight", "... %s of data not shown. Press "%utils.pretty_size(rem)), + ("highlight", "... %s of data not shown. Press "%netlib.utils.pretty_size(rem)), ("key", "f"), ("highlight", " to load all data.") ] diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 5455ad6e..37ceef94 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -1,8 +1,8 @@ -import time import os.path import urwid +import netlib.utils from . import pathedit, signals, common from .. import utils @@ -22,7 +22,6 @@ class ActionBar(urwid.WidgetWrap): self.onekey = False self.pathprompt = False - def sig_message(self, sender, message, expire=None): w = urwid.Text(message) self._w = w @@ -191,7 +190,7 @@ class StatusBar(urwid.WidgetWrap): opts.append("following") if self.master.stream_large_bodies: opts.append( - "stream:%s" % utils.pretty_size( + "stream:%s" % netlib.utils.pretty_size( self.master.stream_large_bodies.max_size ) ) -- cgit v1.2.3 From 50b7bfaadcd78e36a0fd3ade5e912a93308e2815 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 2 May 2015 16:46:15 +1200 Subject: Adapt to new Pathoc API - silence output to stdout. --- libmproxy/console/contentview.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index aafba4d7..a121dfab 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -6,7 +6,6 @@ import lxml.html import lxml.etree from PIL import Image from PIL.ExifTags import TAGS -import re import subprocess import traceback import urwid @@ -15,7 +14,7 @@ import netlib.utils from netlib import odict from . import common -from .. import utils, encoding, flow +from .. import utils, encoding from ..contrib import jsbeautifier, html2text from ..contrib.wbxml.ASCommandResponse import ASCommandResponse -- cgit v1.2.3 From a05a70d8168a07c92b2a3ecbbb1958d85532efe3 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 30 May 2015 12:03:28 +1200 Subject: Add coding style check, reformat. --- libmproxy/console/__init__.py | 29 ++++++----- libmproxy/console/common.py | 6 +-- libmproxy/console/contentview.py | 23 ++++---- libmproxy/console/flowdetailview.py | 2 +- libmproxy/console/flowlist.py | 8 +-- libmproxy/console/flowview.py | 101 ++++++++++++++++++------------------ libmproxy/console/grideditor.py | 23 ++++---- libmproxy/console/help.py | 25 +++++---- libmproxy/console/options.py | 1 + libmproxy/console/palettes.py | 2 +- libmproxy/console/pathedit.py | 2 +- libmproxy/console/searchable.py | 6 +-- libmproxy/console/select.py | 18 +++++-- libmproxy/console/statusbar.py | 30 +++++------ libmproxy/console/tabs.py | 5 +- 15 files changed, 151 insertions(+), 130 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 527ed07d..8f39e283 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -85,10 +85,10 @@ class ConsoleState(flow.State): return self.view[pos], pos def get_next(self, pos): - return self.get_from_pos(pos+1) + return self.get_from_pos(pos + 1) def get_prev(self, pos): - return self.get_from_pos(pos-1) + return self.get_from_pos(pos - 1) def delete_flow(self, f): if f in self.view and self.view.index(f) <= self.focus: @@ -255,7 +255,7 @@ class ConsoleMaster(flow.FlowMaster): try: f = file(path, mode) self.start_stream(f, None) - except IOError, v: + except IOError as v: return str(v) self.stream_path = path @@ -263,22 +263,24 @@ class ConsoleMaster(flow.FlowMaster): status, val = s.run(method, f) if val: if status: - self.add_event("Method %s return: %s"%(method, val), "debug") + self.add_event("Method %s return: %s" % (method, val), "debug") else: - self.add_event("Method %s error: %s"%(method, val[1]), "error") + self.add_event( + "Method %s error: %s" % + (method, val[1]), "error") def run_script_once(self, command, f): if not command: return - self.add_event("Running script on flow: %s"%command, "debug") + self.add_event("Running script on flow: %s" % command, "debug") try: s = script.Script(command, self) - except script.ScriptError, v: + except script.ScriptError as v: signals.status_message.send( message = "Error loading script." ) - self.add_event("Error loading script:\n%s"%v.args[0], "error") + self.add_event("Error loading script:\n%s" % v.args[0], "error") return if f.request: @@ -562,7 +564,7 @@ class ConsoleMaster(flow.FlowMaster): for i in flows: fw.add(i) f.close() - except IOError, v: + except IOError as v: signals.status_message.send(message=v.strerror) def save_one_flow(self, path, flow): @@ -575,13 +577,13 @@ class ConsoleMaster(flow.FlowMaster): if not path: return ret = self.load_flows_path(path) - return ret or "Flows loaded from %s"%path + return ret or "Flows loaded from %s" % path def load_flows_path(self, path): reterr = None try: flow.FlowMaster.load_flows_file(self, path) - except flow.FlowReadError, v: + except flow.FlowReadError as v: reterr = str(v) signals.flowlist_change.send(self) return reterr @@ -652,7 +654,8 @@ class ConsoleMaster(flow.FlowMaster): ) def process_flow(self, f): - if self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay: + if self.state.intercept and f.match( + self.state.intercept) and not f.request.is_replay: f.intercept(self) else: f.reply() @@ -674,7 +677,7 @@ class ConsoleMaster(flow.FlowMaster): self.eventlist.append(e) if len(self.eventlist) > EVENTLOG_SIZE: self.eventlist.pop(0) - self.eventlist.set_focus(len(self.eventlist)-1) + self.eventlist.set_focus(len(self.eventlist) - 1) # Handlers def handle_error(self, f): diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index b920a11f..3180170d 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -164,7 +164,7 @@ def raw_format_flow(f, focus, extended, padding): 4: "code_400", 5: "code_500", } - ccol = codes.get(f["resp_code"]/100, "code_other") + ccol = codes.get(f["resp_code"] / 100, "code_other") resp.append(fcol(SYMBOL_RETURN, ccol)) if f["resp_is_replay"]: resp.append(fcol(SYMBOL_REPLAY, "replay")) @@ -200,7 +200,7 @@ def save_data(path, data, master, state): try: with file(path, "wb") as f: f.write(data) - except IOError, v: + except IOError as v: signals.status_message.send(message=v.strerror) @@ -214,7 +214,7 @@ def ask_save_overwite(path, data, master, state): save_data(path, data, master, state) signals.status_prompt_onekey.send( - prompt = "'"+path+"' already exists. Overwite?", + prompt = "'" + path + "' already exists. Overwite?", keys = ( ("yes", "y"), ("no", "n"), diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index a121dfab..2b3c6def 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -21,12 +21,12 @@ from ..contrib.wbxml.ASCommandResponse import ASCommandResponse try: import pyamf from pyamf import remoting, flex -except ImportError: # pragma nocover +except ImportError: # pragma nocover pyamf = None try: import cssutils -except ImportError: # pragma nocover +except ImportError: # pragma nocover cssutils = None else: cssutils.log.setLevel(logging.CRITICAL) @@ -36,7 +36,7 @@ else: cssutils.ser.prefs.indentClosingBrace = False cssutils.ser.prefs.validOnly = False -VIEW_CUTOFF = 1024*50 +VIEW_CUTOFF = 1024 * 50 def _view_text(content, total, limit): @@ -59,7 +59,7 @@ def trailer(clen, txt, limit): txt.append( urwid.Text( [ - ("highlight", "... %s of data not shown. Press "%netlib.utils.pretty_size(rem)), + ("highlight", "... %s of data not shown. Press " % netlib.utils.pretty_size(rem)), ("key", "f"), ("highlight", " to load all data.") ] @@ -76,7 +76,7 @@ class ViewAuto: ctype = hdrs.get_first("content-type") if ctype: ct = utils.parse_content_type(ctype) if ctype else None - ct = "%s/%s"%(ct[0], ct[1]) + ct = "%s/%s" % (ct[0], ct[1]) if ct in content_types_map: return content_types_map[ct][0](hdrs, content, limit) elif utils.isXML(content): @@ -227,7 +227,7 @@ class ViewURLEncoded: lines = utils.urldecode(content) if lines: body = common.format_keyvals( - [(k+":", v) for (k, v) in lines], + [(k + ":", v) for (k, v) in lines], key = "header", val = "text" ) @@ -304,7 +304,6 @@ if pyamf: if not envelope: return None - txt = [] for target, message in iter(envelope): if isinstance(message, pyamf.remoting.Request): @@ -315,13 +314,13 @@ if pyamf: else: txt.append(urwid.Text([ ("header", "Response: "), - ("text", "%s, code %s"%(target, message.status)), + ("text", "%s, code %s" % (target, message.status)), ])) s = json.dumps(self.unpack(message), indent=4) txt.extend(_view_text(s[:limit], len(s), limit)) - return "AMF v%s"%envelope.amfVersion, txt + return "AMF v%s" % envelope.amfVersion, txt class ViewJavaScript: @@ -375,7 +374,7 @@ class ViewImage: return None parts = [ ("Format", str(img.format_description)), - ("Size", "%s x %s px"%img.size), + ("Size", "%s x %s px" % img.size), ("Mode", str(img.mode)), ] for i in sorted(img.info.keys()): @@ -401,7 +400,7 @@ class ViewImage: key = "header", val = "text" ) - return "%s image"%img.format, fmt + return "%s image" % img.format, fmt class ViewProtobuf: @@ -526,7 +525,7 @@ def get_content_view(viewmode, hdrItems, content, limit, logfunc, is_request): decoded = encoding.decode(enc, content) if decoded: content = decoded - msg.append("[decoded %s]"%enc) + msg.append("[decoded %s]" % enc) try: ret = viewmode(hdrs, content, limit) # Third-party viewers can fail in unexpected ways... diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index 48845a62..40769c95 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -34,7 +34,7 @@ def flowdetails(state, flow): if c: text.append(urwid.Text([("head", "Server Certificate:")])) parts = [ - ["Type", "%s, %s bits"%c.keyinfo], + ["Type", "%s, %s bits" % c.keyinfo], ["SHA1 digest", c.digest("sha1")], ["Valid to", str(c.notafter)], ["Valid from", str(c.notbefore)], diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 6ab45bad..fd071569 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -50,7 +50,7 @@ class EventListBox(urwid.ListBox): elif key == "G": self.set_focus(0) elif key == "g": - self.set_focus(len(self.master.eventlist)-1) + self.set_focus(len(self.master.eventlist) - 1) return urwid.ListBox.keypress(self, size, key) @@ -76,7 +76,8 @@ class BodyPile(urwid.Pile): def keypress(self, size, key): if key == "tab": - self.focus_position = (self.focus_position + 1)%len(self.widget_list) + self.focus_position = ( + self.focus_position + 1) % len(self.widget_list) if self.focus_position == 1: self.widget_list[1].header = self.active_header else: @@ -157,7 +158,8 @@ class ConnectionItem(urwid.WidgetWrap): callback = self.master.server_playback_path ) - def keypress(self, (maxcol,), key): + def keypress(self, xxx_todo_changeme, key): + (maxcol,) = xxx_todo_changeme key = common.shortcuts(key) if key == "a": self.flow.accept_intercept(self.master) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 632b725e..43a40d69 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -24,42 +24,42 @@ def _mkhelp(): ("e", "edit request/response"), ("f", "load full body data"), ("m", "change body display mode for this entity"), - (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")] - ), + (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")] + ), ("M", "change default body display mode"), ("p", "previous flow"), ("P", "copy response(content/headers) to clipboard"), @@ -123,13 +123,13 @@ class FlowView(tabs.Tabs): def __init__(self, master, state, flow, tab_offset): self.master, self.state, self.flow = master, state, flow tabs.Tabs.__init__(self, - [ - (self.tab_request, self.view_request), - (self.tab_response, self.view_response), - (self.tab_details, self.view_details), - ], - tab_offset - ) + [ + (self.tab_request, self.view_request), + (self.tab_response, self.view_response), + (self.tab_details, self.view_details), + ], + tab_offset + ) self.show() self.last_displayed_body = None signals.flow_change.connect(self.sig_flow_change) @@ -173,7 +173,7 @@ class FlowView(tabs.Tabs): False ) if full: - limit = sys.maxint + limit = sys.maxsize else: limit = contentview.VIEW_CUTOFF description, text_objects = cache.get( @@ -197,7 +197,7 @@ class FlowView(tabs.Tabs): def conn_text(self, conn): if conn: txt = common.format_keyvals( - [(h+":", v) for (h, v) in conn.headers.lst], + [(h + ":", v) for (h, v) in conn.headers.lst], key = "header", val = "text" ) @@ -217,7 +217,7 @@ class FlowView(tabs.Tabs): " ", ('heading', "["), ('heading_key', "m"), - ('heading', (":%s]"%viewmode.name)), + ('heading', (":%s]" % viewmode.name)), ], align="right" ) @@ -272,8 +272,9 @@ class FlowView(tabs.Tabs): except ValueError: return None import BaseHTTPServer - if BaseHTTPServer.BaseHTTPRequestHandler.responses.has_key(int(code)): - response.msg = BaseHTTPServer.BaseHTTPRequestHandler.responses[int(code)][0] + if int(code) in BaseHTTPServer.BaseHTTPRequestHandler.responses: + response.msg = BaseHTTPServer.BaseHTTPRequestHandler.responses[ + int(code)][0] signals.flow_change.send(self, flow = self.flow) def set_resp_msg(self, msg): @@ -494,7 +495,7 @@ class FlowView(tabs.Tabs): elif key == "d": if self.state.flow_count() == 1: self.master.view_flowlist() - elif self.state.view.index(self.flow) == len(self.state.view)-1: + elif self.state.view.index(self.flow) == len(self.state.view) - 1: self.view_prev_flow(self.flow) else: self.view_next_flow(self.flow) @@ -615,7 +616,7 @@ class FlowView(tabs.Tabs): if conn.content: t = conn.headers["content-type"] or [None] t = t[0] - if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): + if "EDITOR" in os.environ or "PAGER" in os.environ: self.master.spawn_external_viewer(conn.content, t) else: signals.status_message.send( diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 5a2da59f..b20e54e4 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -175,6 +175,7 @@ class GridWalker(urwid.ListWalker): and errors is a set with an entry of each offset in rows that is an error. """ + def __init__(self, lst, editor): self.lst = [(i, set([])) for i in lst] self.editor = editor @@ -225,7 +226,7 @@ class GridWalker(urwid.ListWalker): def delete_focus(self): if self.lst: del self.lst[self.focus] - self.focus = min(len(self.lst)-1, self.focus) + self.focus = min(len(self.lst) - 1, self.focus) self._modified() def _insert(self, pos): @@ -266,14 +267,14 @@ class GridWalker(urwid.ListWalker): self._modified() def right(self): - self.focus_col = min(self.focus_col + 1, len(self.editor.columns)-1) + self.focus_col = min(self.focus_col + 1, len(self.editor.columns) - 1) self._modified() def tab_next(self): self.stop_edit() - if self.focus_col < len(self.editor.columns)-1: + if self.focus_col < len(self.editor.columns) - 1: self.focus_col += 1 - elif self.focus != len(self.lst)-1: + elif self.focus != len(self.lst) - 1: self.focus_col = 0 self.focus += 1 self._modified() @@ -297,14 +298,14 @@ class GridWalker(urwid.ListWalker): self._modified() def get_next(self, pos): - if pos+1 >= len(self.lst): + if pos + 1 >= len(self.lst): return None, None - return GridRow(None, False, self.editor, self.lst[pos+1]), pos+1 + return GridRow(None, False, self.editor, self.lst[pos + 1]), pos + 1 def get_prev(self, pos): - if pos-1 < 0: + if pos - 1 < 0: return None, None - return GridRow(None, False, self.editor, self.lst[pos-1]), pos-1 + return GridRow(None, False, self.editor, self.lst[pos - 1]), pos - 1 class GridListBox(urwid.ListBox): @@ -387,7 +388,7 @@ class GridEditor(urwid.WidgetWrap): d = file(p, "rb").read() self.walker.set_current_value(d, unescaped) self.walker._modified() - except IOError, v: + except IOError as v: return str(v) def set_subeditor_value(self, val, focus, focus_col): @@ -418,7 +419,7 @@ class GridEditor(urwid.WidgetWrap): elif key == "G": self.walker.set_focus(0) elif key == "g": - self.walker.set_focus(len(self.walker.lst)-1) + self.walker.set_focus(len(self.walker.lst) - 1) elif key in ["h", "left"]: self.walker.left() elif key in ["l", "right"]: @@ -633,7 +634,7 @@ class ScriptEditor(GridEditor): def is_error(self, col, val): try: script.Script.parse_command(val) - except script.ScriptError, v: + except script.ScriptError as v: return str(v) diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index cbd5bef8..4e81a566 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -6,7 +6,7 @@ from . import common, signals from .. import filt, version footer = [ - ("heading", 'mitmproxy v%s '%version.VERSION), + ("heading", 'mitmproxy v%s ' % version.VERSION), ('heading_key', "q"), ":back ", ] @@ -33,7 +33,12 @@ class HelpView(urwid.ListBox): ("pg up/down", "page up/down"), ("arrows", "up, down, left, right"), ] - text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) + text.extend( + common.format_keyvals( + keys, + key="key", + val="text", + indent=4)) text.append(urwid.Text([("head", "\n\nGlobal keys:\n")])) keys = [ @@ -52,15 +57,15 @@ class HelpView(urwid.ListBox): f = [] for i in filt.filt_unary: f.append( - ("~%s"%i.code, i.help) + ("~%s" % i.code, i.help) ) for i in filt.filt_rex: f.append( - ("~%s regex"%i.code, i.help) + ("~%s regex" % i.code, i.help) ) for i in filt.filt_int: f.append( - ("~%s int"%i.code, i.help) + ("~%s int" % i.code, i.help) ) f.sort() f.extend( @@ -75,7 +80,7 @@ class HelpView(urwid.ListBox): text.append( urwid.Text( - [ + [ "\n", ("text", " Regexes are Python-style.\n"), ("text", " Regexes can be specified as quoted strings.\n"), @@ -83,13 +88,13 @@ class HelpView(urwid.ListBox): ("text", " Expressions with no operators are regex matches against URL.\n"), ("text", " Default binary operator is &.\n"), ("head", "\n Examples:\n"), - ] + ] ) ) examples = [ - ("google\.com", "Url containing \"google.com"), - ("~q ~b test", "Requests where body contains \"test\""), - ("!(~q & ~t \"text/html\")", "Anything but requests with a text/html content type."), + ("google\.com", "Url containing \"google.com"), + ("~q ~b test", "Requests where body contains \"test\""), + ("!(~q & ~t \"text/html\")", "Anything but requests with a text/html content type."), ] text.extend( common.format_keyvals(examples, key="key", val="text", indent=4) diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py index c728123f..58a4d469 100644 --- a/libmproxy/console/options.py +++ b/libmproxy/console/options.py @@ -8,6 +8,7 @@ footer = [ ('heading_key', "C"), ":clear all ", ] + def _mkhelp(): text = [] keys = [ diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index 6490eb73..ea3d1b62 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -270,7 +270,7 @@ class SolarizedDark(LowDark): # Status bar & heading heading = (sol_base2, sol_base01), - heading_key = (sol_blue+",bold", sol_base01), + heading_key = (sol_blue + ",bold", sol_base01), heading_inactive = (sol_base1, sol_base02), # Help diff --git a/libmproxy/console/pathedit.py b/libmproxy/console/pathedit.py index 53cda3be..dccec14a 100644 --- a/libmproxy/console/pathedit.py +++ b/libmproxy/console/pathedit.py @@ -32,7 +32,7 @@ class _PathCompleter: files = glob.glob(os.path.join(path, "*")) prefix = txt else: - files = glob.glob(path+"*") + files = glob.glob(path + "*") prefix = os.path.dirname(txt) prefix = prefix or "./" for f in files: diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py index a9572ae3..627d595d 100644 --- a/libmproxy/console/searchable.py +++ b/libmproxy/console/searchable.py @@ -37,7 +37,7 @@ class Searchable(urwid.ListBox): self.set_focus(0) self.walker._modified() elif key == "g": - self.set_focus(len(self.walker)-1) + self.set_focus(len(self.walker) - 1) self.walker._modified() else: return super(self.__class__, self).keypress(size, key) @@ -74,11 +74,11 @@ class Searchable(urwid.ListBox): return # Start search at focus + 1 if backwards: - rng = xrange(len(self.body)-1, -1, -1) + rng = xrange(len(self.body) - 1, -1, -1) else: rng = xrange(1, len(self.body) + 1) for i in rng: - off = (self.focus_position + i)%len(self.body) + off = (self.focus_position + i) % len(self.body) w = self.body[off] txt = self.get_text(w) if txt and self.search_term in txt: diff --git a/libmproxy/console/select.py b/libmproxy/console/select.py index 61ee50e4..bf96a785 100644 --- a/libmproxy/console/select.py +++ b/libmproxy/console/select.py @@ -2,6 +2,7 @@ import urwid from . import common + class _OptionWidget(urwid.WidgetWrap): def __init__(self, option, text, shortcut, active, focus): self.option = option @@ -47,14 +48,14 @@ class OptionWalker(urwid.ListWalker): return self.options[self.focus].render(True), self.focus def get_next(self, pos): - if pos >= len(self.options)-1: + if pos >= len(self.options) - 1: return None, None - return self.options[pos+1].render(False), pos+1 + return self.options[pos + 1].render(False), pos + 1 def get_prev(self, pos): if pos <= 0: return None, None - return self.options[pos-1].render(False), pos-1 + return self.options[pos - 1].render(False), pos - 1 class Heading: @@ -69,6 +70,8 @@ class Heading: _neg = lambda: False + + class Option: def __init__(self, text, shortcut, getstate=None, activate=None): self.text = text @@ -77,7 +80,12 @@ class Option: self.activate = activate or _neg def render(self, focus): - return _OptionWidget(self, self.text, self.shortcut, self.getstate(), focus) + return _OptionWidget( + self, + self.text, + self.shortcut, + self.getstate(), + focus) class Select(urwid.ListBox): @@ -92,7 +100,7 @@ class Select(urwid.ListBox): for i in options: if hasattr(i, "shortcut") and i.shortcut: if i.shortcut in self.keymap: - raise ValueError("Duplicate shortcut key: %s"%i.shortcut) + raise ValueError("Duplicate shortcut key: %s" % i.shortcut) self.keymap[i.shortcut] = i def keypress(self, size, key): diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 37ceef94..7eb2131b 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -58,7 +58,7 @@ class ActionBar(urwid.WidgetWrap): mkup = [] for i, e in enumerate(keys): mkup.extend(common.highlight_key(e[0], e[1])) - if i < len(keys)-1: + if i < len(keys) - 1: mkup.append(",") prompt.extend(mkup) prompt.append(")? ") @@ -136,14 +136,14 @@ class StatusBar(urwid.WidgetWrap): if self.master.client_playback: r.append("[") r.append(("heading_key", "cplayback")) - r.append(":%s to go]"%self.master.client_playback.count()) + r.append(":%s to go]" % self.master.client_playback.count()) if self.master.server_playback: r.append("[") r.append(("heading_key", "splayback")) if self.master.nopop: - r.append(":%s in file]"%self.master.server_playback.count()) + r.append(":%s in file]" % self.master.server_playback.count()) else: - r.append(":%s to go]"%self.master.server_playback.count()) + r.append(":%s to go]" % self.master.server_playback.count()) if self.master.get_ignore_filter(): r.append("[") r.append(("heading_key", "I")) @@ -155,23 +155,23 @@ class StatusBar(urwid.WidgetWrap): if self.master.state.intercept_txt: r.append("[") r.append(("heading_key", "i")) - r.append(":%s]"%self.master.state.intercept_txt) + r.append(":%s]" % self.master.state.intercept_txt) if self.master.state.limit_txt: r.append("[") r.append(("heading_key", "l")) - r.append(":%s]"%self.master.state.limit_txt) + r.append(":%s]" % self.master.state.limit_txt) if self.master.stickycookie_txt: r.append("[") r.append(("heading_key", "t")) - r.append(":%s]"%self.master.stickycookie_txt) + r.append(":%s]" % self.master.stickycookie_txt) if self.master.stickyauth_txt: r.append("[") r.append(("heading_key", "u")) - r.append(":%s]"%self.master.stickyauth_txt) + r.append(":%s]" % self.master.stickyauth_txt) if self.master.state.default_body_view.name != "Auto": r.append("[") r.append(("heading_key", "M")) - r.append(":%s]"%self.master.state.default_body_view.name) + r.append(":%s]" % self.master.state.default_body_view.name) opts = [] if self.master.anticache: @@ -196,22 +196,22 @@ class StatusBar(urwid.WidgetWrap): ) if opts: - r.append("[%s]"%(":".join(opts))) + r.append("[%s]" % (":".join(opts))) if self.master.server.config.mode in ["reverse", "upstream"]: dst = self.master.server.config.mode.dst scheme = "https" if dst[0] else "http" if dst[1] != dst[0]: scheme += "2https" if dst[1] else "http" - r.append("[dest:%s]"%utils.unparse_url(scheme, *dst[2:])) + r.append("[dest:%s]" % utils.unparse_url(scheme, *dst[2:])) if self.master.scripts: r.append("[") r.append(("heading_key", "s")) - r.append("cripts:%s]"%len(self.master.scripts)) + r.append("cripts:%s]" % len(self.master.scripts)) # r.append("[lt:%0.3f]"%self.master.looptime) if self.master.stream: - r.append("[W:%s]"%self.master.stream_path) + r.append("[W:%s]" % self.master.stream_path) return r @@ -222,14 +222,14 @@ class StatusBar(urwid.WidgetWrap): else: offset = min(self.master.state.focus + 1, fc) t = [ - ('heading', ("[%s/%s]"%(offset, fc)).ljust(9)) + ('heading', ("[%s/%s]" % (offset, fc)).ljust(9)) ] if self.master.server.bound: host = self.master.server.address.host if host == "0.0.0.0": host = "*" - boundaddr = "[%s:%s]"%(host, self.master.server.address.port) + boundaddr = "[%s:%s]" % (host, self.master.server.address.port) else: boundaddr = "" t.extend(self.get_status()) diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index 2c46e59e..953f6b12 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -1,5 +1,6 @@ import urwid + class Tabs(urwid.WidgetWrap): def __init__(self, tabs, tab_offset=0): urwid.WidgetWrap.__init__(self, "") @@ -15,10 +16,10 @@ class Tabs(urwid.WidgetWrap): def keypress(self, size, key): if key in ["tab", "l"]: - self.tab_offset = (self.tab_offset + 1)%(len(self.tabs)) + self.tab_offset = (self.tab_offset + 1) % (len(self.tabs)) self.show() elif key == "h": - self.tab_offset = (self.tab_offset - 1)%(len(self.tabs)) + self.tab_offset = (self.tab_offset - 1) % (len(self.tabs)) self.show() return self._w.keypress(size, key) -- cgit v1.2.3 From 14bce0dd12c206162f62fff256cc58fac42db968 Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Mon, 1 Jun 2015 12:06:46 -0300 Subject: fixes #604 catch method not found for handling pyperclip not found --- libmproxy/console/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 3180170d..683faf8e 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -287,12 +287,12 @@ def copy_flow(part, scope, flow, master, state): try: master.add_event(str(len(data))) pyperclip.copy(data) - except (RuntimeError, UnicodeDecodeError): + except (RuntimeError, UnicodeDecodeError, AttributeError): def save(k): if k == "y": ask_save_path("Save data", data, master, state) signals.status_prompt_onekey.send( - prompt = "Cannot copy binary data to clipboard. Save as file?", + prompt = "Cannot copy data to clipboard. Save as file?", keys = ( ("yes", "y"), ("no", "n"), -- cgit v1.2.3 From 62330e4b0f690e1b5a187b0b1d18654fc51dfe8a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 2 Jun 2015 11:05:45 +1200 Subject: Enable mouse interaction, add a hint for selecting text. Fixes #597 --- libmproxy/console/__init__.py | 1 + libmproxy/console/window.py | 10 ++++++++++ 2 files changed, 11 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 8f39e283..7d4a1164 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -416,6 +416,7 @@ class ConsoleMaster(flow.FlowMaster): def run(self): self.ui = urwid.raw_display.Screen() + self.ui.set_mouse_tracking() self.ui.set_terminal_properties(256) self.set_palette(self.palette) self.loop = urwid.MainLoop( diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index d64e83df..600ae413 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -17,6 +17,16 @@ class Window(urwid.Frame): def sig_focus(self, sender, section): self.focus_position = section + def mouse_event(self, *args, **kwargs): + # args: (size, event, button, col, row) + k = super(self.__class__, self).mouse_event(*args, **kwargs) + if args[1] == "mouse drag": + signals.status_message.send( + message = "Hold down alt or ctrl to select text.", + expire = 1 + ) + return False + def keypress(self, size, k): k = super(self.__class__, self).keypress(size, k) if k == "?": -- cgit v1.2.3 From b5bb4106fd9a017b87fc9515bb3448b78497f80c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 2 Jun 2015 11:13:12 +1200 Subject: console: mouse scrollwheel throughout. --- libmproxy/console/window.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 600ae413..8754ed57 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -20,12 +20,19 @@ class Window(urwid.Frame): def mouse_event(self, *args, **kwargs): # args: (size, event, button, col, row) k = super(self.__class__, self).mouse_event(*args, **kwargs) - if args[1] == "mouse drag": - signals.status_message.send( - message = "Hold down alt or ctrl to select text.", - expire = 1 - ) - return False + if not k: + if args[1] == "mouse drag": + signals.status_message.send( + message = "Hold down alt or ctrl to select text.", + expire = 1 + ) + elif args[1] == "mouse press" and args[2] == 4: + self.keypress(args[0], "up") + elif args[1] == "mouse press" and args[2] == 5: + self.keypress(args[0], "down") + else: + return False + return True def keypress(self, size, k): k = super(self.__class__, self).keypress(size, k) -- cgit v1.2.3 From 1076c25e5b292b9c655e5acc3c587d06fe90b4c4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 2 Jun 2015 11:27:26 +1200 Subject: console: click in flow list to view flow --- libmproxy/console/flowlist.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index fd071569..39245984 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -158,6 +158,12 @@ class ConnectionItem(urwid.WidgetWrap): callback = self.master.server_playback_path ) + def mouse_event(self, size, event, button, col, row, focus): + if event == "mouse press" and button == 1: + if self.flow.request: + self.master.view_flow(self.flow) + return True + def keypress(self, xxx_todo_changeme, key): (maxcol,) = xxx_todo_changeme key = common.shortcuts(key) -- cgit v1.2.3 From 57a61ae8fd420744d616765d13ad93dec3b3aa52 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 2 Jun 2015 12:09:07 +1200 Subject: console: convert add_event to a signal. --- libmproxy/console/__init__.py | 42 ++++++++++++++++++++++------------------ libmproxy/console/common.py | 1 - libmproxy/console/contentview.py | 6 +++--- libmproxy/console/flowview.py | 1 - libmproxy/console/signals.py | 9 +++++++++ libmproxy/console/tabs.py | 1 + 6 files changed, 36 insertions(+), 24 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 7d4a1164..052ac7dd 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -220,11 +220,29 @@ class ConsoleMaster(flow.FlowMaster): signals.call_in.connect(self.sig_call_in) signals.pop_view_state.connect(self.sig_pop_view_state) signals.push_view_state.connect(self.sig_push_view_state) + signals.sig_add_event.connect(self.sig_add_event) def __setattr__(self, name, value): self.__dict__[name] = value signals.update_settings.send(self) + def sig_add_event(self, sender, e, level): + needed = dict(error=0, info=1, debug=2).get(level, 1) + if self.options.verbosity < needed: + return + + if level == "error": + e = urwid.Text(("error", str(e))) + else: + e = urwid.Text(str(e)) + self.eventlist.append(e) + if len(self.eventlist) > EVENTLOG_SIZE: + self.eventlist.pop(0) + self.eventlist.set_focus(len(self.eventlist) - 1) + + def add_event(self, e, level): + signals.add_event(e, level) + def sig_call_in(self, sender, seconds, callback, args=()): def cb(*_): return callback(*args) @@ -263,16 +281,16 @@ class ConsoleMaster(flow.FlowMaster): status, val = s.run(method, f) if val: if status: - self.add_event("Method %s return: %s" % (method, val), "debug") + signals.add_event("Method %s return: %s" % (method, val), "debug") else: - self.add_event( + signals.add_event( "Method %s error: %s" % (method, val[1]), "error") def run_script_once(self, command, f): if not command: return - self.add_event("Running script on flow: %s" % command, "debug") + signals.add_event("Running script on flow: %s" % command, "debug") try: s = script.Script(command, self) @@ -280,7 +298,7 @@ class ConsoleMaster(flow.FlowMaster): signals.status_message.send( message = "Error loading script." ) - self.add_event("Error loading script:\n%s" % v.args[0], "error") + signals.add_event("Error loading script:\n%s" % v.args[0], "error") return if f.request: @@ -432,7 +450,7 @@ class ConsoleMaster(flow.FlowMaster): if self.options.rfile: ret = self.load_flows_path(self.options.rfile) if ret and self.state.flow_count(): - self.add_event( + signals.add_event( "File truncated or corrupted. " "Loaded as many flows as possible.", "error" @@ -666,20 +684,6 @@ class ConsoleMaster(flow.FlowMaster): def clear_events(self): self.eventlist[:] = [] - def add_event(self, e, level="info"): - needed = dict(error=0, info=1, debug=2).get(level, 1) - if self.options.verbosity < needed: - return - - if level == "error": - e = urwid.Text(("error", str(e))) - else: - e = urwid.Text(str(e)) - self.eventlist.append(e) - if len(self.eventlist) > EVENTLOG_SIZE: - self.eventlist.pop(0) - self.eventlist.set_focus(len(self.eventlist) - 1) - # Handlers def handle_error(self, f): f = flow.FlowMaster.handle_error(self, f) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 683faf8e..57d4c994 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -285,7 +285,6 @@ def copy_flow(part, scope, flow, master, state): return try: - master.add_event(str(len(data))) pyperclip.copy(data) except (RuntimeError, UnicodeDecodeError, AttributeError): def save(k): diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 2b3c6def..fd28a83a 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -13,7 +13,7 @@ import urwid import netlib.utils from netlib import odict -from . import common +from . import common, signals from .. import utils, encoding from ..contrib import jsbeautifier, html2text from ..contrib.wbxml.ASCommandResponse import ASCommandResponse @@ -507,7 +507,7 @@ def get(name): return i -def get_content_view(viewmode, hdrItems, content, limit, logfunc, is_request): +def get_content_view(viewmode, hdrItems, content, limit, is_request): """ Returns a (msg, body) tuple. """ @@ -532,7 +532,7 @@ def get_content_view(viewmode, hdrItems, content, limit, logfunc, is_request): except Exception: s = traceback.format_exc() s = "Content viewer failed: \n" + s - logfunc(s, "error") + signals.add_event(s, "error") ret = None if not ret: ret = get("Raw")(hdrs, content, limit) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 43a40d69..c6c4c10d 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -182,7 +182,6 @@ class FlowView(tabs.Tabs): tuple(tuple(i) for i in conn.headers.lst), conn.content, limit, - self.master.add_event, isinstance(conn, HTTPRequest) ) return (description, text_objects) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index c1bcf201..52f72d34 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -1,5 +1,14 @@ import blinker +# Show a status message in the action bar +sig_add_event = blinker.Signal() +def add_event(e, level): + sig_add_event.send( + None, + e=e, + level=level + ) + # Show a status message in the action bar status_message = blinker.Signal() diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index 953f6b12..6e7d4d99 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -1,4 +1,5 @@ import urwid +import signals class Tabs(urwid.WidgetWrap): -- cgit v1.2.3 From 0b8cddddf5beb509aab5d03eafd61e21ae323f9e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 2 Jun 2015 15:25:58 +1200 Subject: console: click-enable tabs --- libmproxy/console/tabs.py | 51 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py index 6e7d4d99..fea9bbde 100644 --- a/libmproxy/console/tabs.py +++ b/libmproxy/console/tabs.py @@ -2,6 +2,24 @@ import urwid import signals +class Tab(urwid.WidgetWrap): + def __init__(self, offset, content, attr, onclick): + """ + onclick is called on click with the tab offset as argument + """ + p = urwid.Text(content, align="center") + p = urwid.Padding(p, align="center", width=("relative", 100)) + p = urwid.AttrWrap(p, attr) + urwid.WidgetWrap.__init__(self, p) + self.offset = offset + self.onclick = onclick + + def mouse_event(self, size, event, button, col, row, focus): + if event == "mouse press" and button == 1: + self.onclick(self.offset) + return True + + class Tabs(urwid.WidgetWrap): def __init__(self, tabs, tab_offset=0): urwid.WidgetWrap.__init__(self, "") @@ -9,19 +27,16 @@ class Tabs(urwid.WidgetWrap): self.tabs = tabs self.show() - def _tab(self, content, attr): - p = urwid.Text(content, align="center") - p = urwid.Padding(p, align="center", width=("relative", 100)) - p = urwid.AttrWrap(p, attr) - return p + def change_tab(self, offset): + self.tab_offset = offset + self.show() def keypress(self, size, key): + n = len(self.tabs) if key in ["tab", "l"]: - self.tab_offset = (self.tab_offset + 1) % (len(self.tabs)) - self.show() + self.change_tab((self.tab_offset + 1) % n) elif key == "h": - self.tab_offset = (self.tab_offset - 1) % (len(self.tabs)) - self.show() + self.change_tab((self.tab_offset - 1) % n) return self._w.keypress(size, key) def show(self): @@ -29,9 +44,23 @@ class Tabs(urwid.WidgetWrap): for i in range(len(self.tabs)): txt = self.tabs[i][0]() if i == self.tab_offset: - headers.append(self._tab(txt, "heading")) + headers.append( + Tab( + i, + txt, + "heading", + self.change_tab + ) + ) else: - headers.append(self._tab(txt, "heading_inactive")) + headers.append( + Tab( + i, + txt, + "heading_inactive", + self.change_tab + ) + ) headers = urwid.Columns(headers, dividechars=1) self._w = urwid.Frame( body = self.tabs[self.tab_offset][1](), -- cgit v1.2.3 From fbb2633dd3e8bb4764e4d7627bec83bf73f4ae45 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sun, 31 May 2015 12:39:37 +0200 Subject: replace contrib libraries with pypi dependencies --- libmproxy/console/contentview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index fd28a83a..e4ffcd47 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -9,13 +9,14 @@ from PIL.ExifTags import TAGS import subprocess import traceback import urwid +import html2text import netlib.utils from netlib import odict from . import common, signals from .. import utils, encoding -from ..contrib import jsbeautifier, html2text +from ..contrib import jsbeautifier from ..contrib.wbxml.ASCommandResponse import ASCommandResponse try: -- cgit v1.2.3 From 17b34de28d374e7c2b189d796475668779f2ce3a Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Fri, 5 Jun 2015 15:19:57 -0300 Subject: fix #607 decode data before sending it to pyperclip --- libmproxy/console/common.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 57d4c994..2395a67d 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -284,8 +284,15 @@ def copy_flow(part, scope, flow, master, state): signals.status_message.send(message="No contents to copy.") return + # this is because pyperclip does an encode('utf-8') without checking if data is already encoded or not + toclip = "" try: - pyperclip.copy(data) + toclip = data.decode('utf-8') + except (UnicodeDecodeError): + toclip = data + + try: + pyperclip.copy(toclip) except (RuntimeError, UnicodeDecodeError, AttributeError): def save(k): if k == "y": -- cgit v1.2.3 From 1befa9477c0494fe4400dc62ee0c0907c32d46bd Mon Sep 17 00:00:00 2001 From: Marcelo Glezer Date: Fri, 5 Jun 2015 15:33:36 -0300 Subject: fix #607 fix message --- libmproxy/console/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 2395a67d..e5bebf7f 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -284,7 +284,8 @@ def copy_flow(part, scope, flow, master, state): signals.status_message.send(message="No contents to copy.") return - # this is because pyperclip does an encode('utf-8') without checking if data is already encoded or not + # pyperclip calls encode('utf-8') on data to be copied without checking. + # if data are already encoded that way UnicodeDecodeError is thrown. toclip = "" try: toclip = data.decode('utf-8') -- cgit v1.2.3 From 8b998cfbeace0777293f3cef804c1bf239758273 Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 10:27:48 -0500 Subject: Implemented basic marking of flows - Press m to toggle flow mark - Flow mark is set in libmproxy/console/common.py. Currently set to "===" --- libmproxy/console/common.py | 7 +++++++ libmproxy/console/flowlist.py | 8 ++++++++ 2 files changed, 15 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index e5bebf7f..584b7475 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -115,6 +115,7 @@ def fcol(s, attr): if urwid.util.detected_encoding: SYMBOL_REPLAY = u"\u21ba" SYMBOL_RETURN = u"\u2190" + SYMBOL_MARK = "===" else: SYMBOL_REPLAY = u"[r]" SYMBOL_RETURN = u"<-" @@ -133,6 +134,10 @@ def raw_format_flow(f, focus, extended, padding): ) else: req.append(fcol(">>" if focus else " ", "focus")) + + if f["marked"]: + req.append(fcol(SYMBOL_MARK, "mark")) + if f["req_is_replay"]: req.append(fcol(SYMBOL_REPLAY, "replay")) req.append(fcol(f["req_method"], "method")) @@ -384,6 +389,8 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2): err_msg = f.error.msg if f.error else None, resp_code = f.response.code if f.response else None, + + marked = f.marked, ) if f.response: if f.response.content: diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 39245984..8bb6f87a 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -17,6 +17,7 @@ def _mkhelp(): ("F", "toggle follow flow list"), ("l", "set limit filter pattern"), ("L", "load saved flows"), + ("m", "toggle flow mark"), ("n", "create a new request"), ("P", "copy flow to clipboard"), ("r", "replay request"), @@ -177,6 +178,13 @@ class ConnectionItem(urwid.WidgetWrap): elif key == "D": f = self.master.duplicate_flow(self.flow) self.master.view_flow(f) + elif key == "m": + self.flow.toggle_mark() + signals.flowlist_change.send(self) + if self.flow.marked: + signals.status_message.send(message="Flow is now marked") + else: + signals.status_message.send(message="Flow is now not marked") elif key == "r": r = self.master.replay_request(self.flow) if r: -- cgit v1.2.3 From e53a2426c153337fe25ef6dd13059cf2c4d0ab0b Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 11:40:03 -0500 Subject: Marked flows not deleted on clear all Marked flows survive a clear all unless all current flows are marked. Bug: They don't show up until another flow is added --- libmproxy/console/flowlist.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 8bb6f87a..f7835419 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -181,10 +181,6 @@ class ConnectionItem(urwid.WidgetWrap): elif key == "m": self.flow.toggle_mark() signals.flowlist_change.send(self) - if self.flow.marked: - signals.status_message.send(message="Flow is now marked") - else: - signals.status_message.send(message="Flow is now not marked") elif key == "r": r = self.master.replay_request(self.flow) if r: -- cgit v1.2.3 From a34eeb9a281fa4cd036d2ede096dbe44f78ab1d2 Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 11:49:23 -0500 Subject: Fixed console rendering bug Clearing all flows now works properly --- libmproxy/console/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 052ac7dd..cbcba52f 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -100,9 +100,12 @@ class ConsoleState(flow.State): return ret def clear(self): - self.focus = None super(ConsoleState, self).clear() - + if len(self.flows.views) == 0: + self.focus = None + else: + self.focus = 0 + self.set_focus(self.focus) class Options(object): attributes = [ -- cgit v1.2.3 From 13e71eba100a36a9464b0f09b5f6dbfcbec17833 Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 12:02:15 -0500 Subject: Changed symbols and colors Added a better symbol for the mark, and changed the color to red. This helps it stand out more easily. --- libmproxy/console/common.py | 3 ++- libmproxy/console/palettes.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 584b7475..51911746 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -115,10 +115,11 @@ def fcol(s, attr): if urwid.util.detected_encoding: SYMBOL_REPLAY = u"\u21ba" SYMBOL_RETURN = u"\u2190" - SYMBOL_MARK = "===" + SYMBOL_MARK = u"[M] \u2192" else: SYMBOL_REPLAY = u"[r]" SYMBOL_RETURN = u"<-" + SYMBOL_MARK = "[M] ==>" def raw_format_flow(f, focus, extended, padding): diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py index ea3d1b62..d897a0a2 100644 --- a/libmproxy/console/palettes.py +++ b/libmproxy/console/palettes.py @@ -24,7 +24,7 @@ class Palette: 'method', 'focus', 'code_200', 'code_300', 'code_400', 'code_500', 'code_other', 'error', - 'header', 'highlight', 'intercept', 'replay', + 'header', 'highlight', 'intercept', 'replay', 'mark', # Hex view 'offset', @@ -104,6 +104,7 @@ class LowDark(Palette): highlight = ('white,bold', 'default'), intercept = ('brown', 'default'), replay = ('light green', 'default'), + mark = ('light red', 'default'), # Hex view offset = ('dark cyan', 'default'), @@ -167,6 +168,7 @@ class LowLight(Palette): highlight = ('black,bold', 'default'), intercept = ('brown', 'default'), replay = ('dark green', 'default'), + mark = ('dark red', 'default'), # Hex view offset = ('dark blue', 'default'), -- cgit v1.2.3 From 486177edc7090539e063ec2a0dd70caffd8ec3cc Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 16:15:24 -0500 Subject: Added functionality to write marked flows to file w (write) -> m (marked) --- libmproxy/console/__init__.py | 7 +++++++ libmproxy/console/flowlist.py | 6 ++++++ 2 files changed, 13 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index cbcba52f..20580a28 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -594,6 +594,13 @@ class ConsoleMaster(flow.FlowMaster): def save_flows(self, path): return self._write_flows(path, self.state.view) + + def save_marked_flows(self, path): + marked_flows = [] + for f in self.state.view: + if f.marked: + marked_flows.append(f) + return self._write_flows(path, marked_flows) def load_flows_callback(self, path): if not path: diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index f7835419..72d507c8 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -121,6 +121,11 @@ class ConnectionItem(urwid.WidgetWrap): prompt = "Save all flows to", callback = self.master.save_flows ) + elif k == "m": + signals.status_prompt_path.send( + prompt = "Save marked flows to", + callback = self.master.save_marked_flows + ) else: signals.status_prompt_path.send( prompt = "Save this flow to", @@ -220,6 +225,7 @@ class ConnectionItem(urwid.WidgetWrap): keys = ( ("all flows", "a"), ("this flow", "t"), + ("marked flows", "m"), ), callback = self.save_flows_prompt, ) -- cgit v1.2.3 From dd1e401e014be7b3251d08313ac6553282401cfe Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 16:32:13 -0500 Subject: Changed mark symbol Smaller symbol now, still just as easy to see while scrolling --- libmproxy/console/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 51911746..cbf39e6e 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -115,11 +115,11 @@ def fcol(s, attr): if urwid.util.detected_encoding: SYMBOL_REPLAY = u"\u21ba" SYMBOL_RETURN = u"\u2190" - SYMBOL_MARK = u"[M] \u2192" + SYMBOL_MARK = u"\u25cf" else: SYMBOL_REPLAY = u"[r]" SYMBOL_RETURN = u"<-" - SYMBOL_MARK = "[M] ==>" + SYMBOL_MARK = "[m]" def raw_format_flow(f, focus, extended, padding): -- cgit v1.2.3 From 2a6698bf5a2ebe576ae0bbcacdee69d6eed10be9 Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 20:27:33 -0500 Subject: Moved marking from flow to console No longer taints the flow primitive --- libmproxy/console/__init__.py | 20 +++++++++++++++++++- libmproxy/console/common.py | 5 +++-- libmproxy/console/flowlist.py | 8 ++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) (limited to 'libmproxy/console') diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 20580a28..3d20947b 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -48,6 +48,7 @@ class ConsoleState(flow.State): self.set_focus(0) elif self.follow_focus: self.set_focus(len(self.view) - 1) + self.set_flow_marked(f, False) return f def update_flow(self, f): @@ -100,12 +101,29 @@ class ConsoleState(flow.State): return ret def clear(self): + marked_flows = [] + for f in self.flows: + if self.flow_marked(f): + marked_flows.append(f) + super(ConsoleState, self).clear() + + for f in marked_flows: + self.add_flow(f) + self.set_flow_marked(f, True) + if len(self.flows.views) == 0: self.focus = None else: self.focus = 0 self.set_focus(self.focus) + + def flow_marked(self, flow): + return self.get_flow_setting(flow, "marked", False) + + def set_flow_marked(self, flow, marked): + self.add_flow_setting(flow, "marked", marked) + class Options(object): attributes = [ @@ -598,7 +616,7 @@ class ConsoleMaster(flow.FlowMaster): def save_marked_flows(self, path): marked_flows = [] for f in self.state.view: - if f.marked: + if self.state.flow_marked(f): marked_flows.append(f) return self._write_flows(path, marked_flows) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index cbf39e6e..90bccfe7 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -378,7 +378,8 @@ def ask_save_body(part, master, state, flow): flowcache = utils.LRUCache(800) -def format_flow(f, focus, extended=False, hostheader=False, padding=2): +def format_flow(f, focus, extended=False, hostheader=False, padding=2, + marked=False): d = dict( intercepted = f.intercepted, acked = f.reply.acked, @@ -391,7 +392,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2): err_msg = f.error.msg if f.error else None, resp_code = f.response.code if f.response else None, - marked = f.marked, + marked = marked, ) if f.response: if f.response.content: diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 72d507c8..87e7c77a 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -109,7 +109,8 @@ class ConnectionItem(urwid.WidgetWrap): return common.format_flow( self.flow, self.f, - hostheader = self.master.showhost + hostheader = self.master.showhost, + marked=self.state.flow_marked(self.flow) ) def selectable(self): @@ -184,7 +185,10 @@ class ConnectionItem(urwid.WidgetWrap): f = self.master.duplicate_flow(self.flow) self.master.view_flow(f) elif key == "m": - self.flow.toggle_mark() + if self.state.flow_marked(self.flow): + self.state.set_flow_marked(self.flow, False) + else: + self.state.set_flow_marked(self.flow, True) signals.flowlist_change.send(self) elif key == "r": r = self.master.replay_request(self.flow) -- cgit v1.2.3 From 946030367fee0d624a29ba57a11d5f2d1dea4105 Mon Sep 17 00:00:00 2001 From: Jake Drahos Date: Thu, 11 Jun 2015 20:31:54 -0500 Subject: Added unmark all functionality - 'U' to unmark all marked flows --- libmproxy/console/flowlist.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'libmproxy/console') diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 87e7c77a..bb23df75 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -21,6 +21,7 @@ def _mkhelp(): ("n", "create a new request"), ("P", "copy flow to clipboard"), ("r", "replay request"), + ("U", "unmark all marked flows"), ("V", "revert changes to request"), ("w", "save flows "), ("W", "stream flows to file"), @@ -215,6 +216,10 @@ class ConnectionItem(urwid.WidgetWrap): ), callback = self.stop_server_playback_prompt, ) + elif key == "U": + for f in self.state.flows: + self.state.set_flow_marked(f, False) + signals.flowlist_change.send(self) elif key == "V": if not self.flow.modified(): signals.status_message.send(message="Flow not modified.") -- cgit v1.2.3