diff options
Diffstat (limited to 'libmproxy/console')
-rw-r--r-- | libmproxy/console/__init__.py | 42 | ||||
-rw-r--r-- | libmproxy/console/contentview.py | 39 | ||||
-rw-r--r-- | libmproxy/console/flowlist.py | 3 | ||||
-rw-r--r-- | libmproxy/console/flowview.py | 266 | ||||
-rw-r--r-- | libmproxy/console/grideditor.py | 9 | ||||
-rw-r--r-- | libmproxy/console/help.py | 4 |
6 files changed, 301 insertions, 62 deletions
diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index e5a0283c..210e2b95 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -174,8 +174,7 @@ class StatusBar(common.WWrap): r.append("[%s]"%(":".join(opts))) if self.master.scripts: - r.append("[script:%s]"%self.master.script.path) - + r.append("[scripts:%s]"%len(self.master.scripts)) if self.master.debug: r.append("[lt:%0.3f]"%self.master.looptime) @@ -335,7 +334,7 @@ class Options(object): "no_server", "refresh_server_playback", "rfile", - "script", + "scripts", "showhost", "replacements", "rheaders", @@ -410,11 +409,12 @@ class ConsoleMaster(flow.FlowMaster): self.debug = options.debug - if options.script: - err = self.load_script(options.script) - if err: - print >> sys.stderr, "Script load error:", err - sys.exit(1) + if options.scripts: + for i in options.scripts: + err = self.load_script(i) + if err: + print >> sys.stderr, "Script load error:", err + sys.exit(1) if options.wfile: err = self.start_stream(options.wfile) @@ -423,7 +423,7 @@ class ConsoleMaster(flow.FlowMaster): sys.exit(1) if options.app: - self.start_app(self.o.app_host, self.o.app_port, self.o.app_external) + self.start_app(self.options.app_host, self.options.app_port, self.options.app_external) def start_stream(self, path): path = os.path.expanduser(path) @@ -434,7 +434,6 @@ class ConsoleMaster(flow.FlowMaster): return str(v) self.stream_path = path - def _run_script_method(self, method, s, f): status, val = s.run(method, f) if val: @@ -447,7 +446,7 @@ class ConsoleMaster(flow.FlowMaster): if not path: return self.add_event("Running script on flow: %s"%path) - ret = self.get_script(path) + ret = self.get_script(shlex.split(path, posix=(os.name != "nt"))) if ret[0]: self.statusbar.message("Error loading script.") self.add_event("Error loading script:\n%s"%ret[0]) @@ -880,14 +879,21 @@ class ConsoleMaster(flow.FlowMaster): ) ) elif k == "s": - if self.scripts: - self.load_script(None) - else: - self.path_prompt( - "Set script: ", - self.state.last_script, - self.set_script + self.view_grideditor( + grideditor.ScriptEditor( + self, + [[i.argv[0]] for i in self.scripts], + None ) + ) + #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( diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 8dd8ad1d..70f39d83 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -1,11 +1,9 @@ +import logging import re, cStringIO, traceback, json import urwid -try: from PIL import Image -except ImportError: import Image - -try: from PIL.ExifTags import TAGS -except ImportError: from ExifTags import TAGS +from PIL import Image +from PIL.ExifTags import TAGS import lxml.html, lxml.etree import netlib.utils @@ -19,6 +17,18 @@ try: except ImportError: # pragma nocover pyamf = None +try: + import cssutils +except ImportError: # pragma nocover + cssutils = None +else: + cssutils.log.setLevel(logging.CRITICAL) + + cssutils.ser.prefs.keepComments = True + cssutils.ser.prefs.omitLastSemicolon = False + cssutils.ser.prefs.indentClosingBrace = False + cssutils.ser.prefs.validOnly = False + VIEW_CUTOFF = 1024*50 @@ -318,7 +328,23 @@ class ViewJavaScript: opts = jsbeautifier.default_options() opts.indent_size = 2 res = jsbeautifier.beautify(content[:limit], opts) - return "JavaScript", _view_text(res, len(content), limit) + return "JavaScript", _view_text(res, len(res), limit) + +class ViewCSS: + name = "CSS" + prompt = ("css", "c") + content_types = [ + "text/css" + ] + + def __call__(self, hdrs, content, limit): + if cssutils: + sheet = cssutils.parseString(content) + beautified = sheet.cssText + else: + beautified = content + + return "CSS", _view_text(beautified, len(beautified), limit) class ViewImage: @@ -409,6 +435,7 @@ views = [ ViewHTML(), ViewHTMLOutline(), ViewJavaScript(), + ViewCSS(), ViewURLEncoded(), ViewMultipart(), ViewImage(), diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 6ba97733..7f11a7e2 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -12,6 +12,7 @@ def _mkhelp(): ("e", "toggle eventlog"), ("F", "toggle follow flow list"), ("l", "set limit filter pattern"), + ("/", "same as above"), ("L", "load saved flows"), ("r", "replay request"), ("V", "revert changes to request"), @@ -244,7 +245,7 @@ class FlowListBox(urwid.ListBox): self.master.clear_flows() elif key == "e": self.master.toggle_eventlog() - elif key == "l": + elif key == "l" or key == "/": self.master.prompt("Limit: ", self.master.state.limit_txt, self.master.set_limit) elif key == "L": self.master.path_prompt( diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index c19ba5e6..35fc1e43 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -63,6 +63,8 @@ def _mkhelp(): ("tab", "toggle request/response view"), ("space", "next flow"), ("|", "run script on this flow"), + ("/", "search in response body (case sensitive)"), + ("n", "repeat previous search"), ] text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) return text @@ -85,7 +87,9 @@ class FlowViewHeader(common.WWrap): class CallbackCache: - @utils.LRUCache(200) + #commented decorator because it was breaking search functionality (caching after + # searches.) If it can be made to only cache the first time, it'd be great. + #@utils.LRUCache(200) def _callback(self, method, *args, **kwargs): return getattr(self.obj, method)(*args, **kwargs) @@ -109,8 +113,12 @@ class FlowView(common.WWrap): ("options", "o"), ("edit raw", "e"), ] + + highlight_color = "focusfield" + def __init__(self, master, state, flow): self.master, self.state, self.flow = master, state, flow + self.last_displayed_body = None if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: self.view_response() else: @@ -129,7 +137,8 @@ class FlowView(common.WWrap): limit = sys.maxint else: limit = contentview.VIEW_CUTOFF - return cache.callback( + + description, text_objects = cache.callback( self, "_cached_content_view", viewmode, tuple(tuple(i) for i in conn.headers.lst), @@ -137,49 +146,84 @@ class FlowView(common.WWrap): limit ) - def conn_text(self, conn): - txt = common.format_keyvals( + return (description, text_objects) + + def cont_view_handle_missing(self, conn, viewmode): + if conn.content == flow.CONTENT_MISSING: + msg, body = "", [urwid.Text([("error", "[content missing]")])], 0 + else: + msg, body = self.content_view(viewmode, conn) + + return (msg, body) + + def viewmode_get(self, override): + 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 + display. + """ + headers = common.format_keyvals( [(h+":", v) for (h, v) in conn.headers.lst], key = "header", val = "text" ) + if conn.content is not None: - override = self.state.get_flow_setting( - self.flow, - (self.state.view_flow_mode, "prettyview"), - ) - viewmode = self.state.default_body_view if override is None else override + override = self.override_get() + viewmode = self.viewmode_get(override) + msg, body = self.cont_view_handle_missing(conn, viewmode) + elif conn.content == flow.CONTENT_MISSING: + pass - if conn.content == flow.CONTENT_MISSING: - msg, body = "", [urwid.Text([("error", "[content missing]")])] - else: - msg, body = self.content_view(viewmode, conn) + return headers, msg, body - cols = [ - urwid.Text( - [ - ("heading", msg), - ] - ) - ] - if override is not None: - cols.append( - urwid.Text( - [ - " ", - ('heading', "["), - ('heading_key', "m"), - ('heading', (":%s]"%viewmode.name)), - ], - align="right" - ) + 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 + """ + + override = self.override_get() + viewmode = self.viewmode_get(override) + + cols = [urwid.Text( + [ + ("heading", msg), + ] + ) + ] + + if override is not None: + cols.append(urwid.Text([ + " ", + ('heading', "["), + ('heading_key', "m"), + ('heading', (":%s]"%viewmode.name)), + ], + align="right" ) - title = urwid.AttrWrap(urwid.Columns(cols), "heading") - txt.append(title) - txt.extend(body) - elif conn.content == flow.CONTENT_MISSING: - pass - return urwid.ListBox(txt) + ) + + title = urwid.AttrWrap(urwid.Columns(cols), "heading") + headers.append(title) + headers.extend(body) + + return headers + + 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 urwid.ListBox(merged) def _tab(self, content, attr): p = urwid.Text(content) @@ -215,6 +259,140 @@ class FlowView(common.WWrap): ) return f + def search_wrapped_around(self, last_find_line, last_search_index): + """ + 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 current_find_line <= last_find_line: + return True + elif current_find_line == last_find_line: + if current_search_index <= last_search_index: + return True + + return False + + def search(self, search_string): + """ + 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 search_string == "": + search_string = self.state.get_flow_setting(self.flow, + "last_search_string") + + 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) + body, focus_position = self.search_highlight_text(body, search_string) + + 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) + self.master.statusbar.redraw() + + self.last_displayed_body = list_box + + if self.search_wrapped_around(last_find_line, last_search_index): + return "search hit BOTTOM, continuing at TOP" + + 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_highlight_text(self, text_objects, search_string, looping = False): + start_line, start_index = self.search_get_start(search_string) + i = start_line + + found = False + for text_object in text_objects[start_line:]: + if i != start_line: + start_index = 0 + + text, style = text_object.get_text() + + find_index = text.find(search_string, start_index) + if find_index != -1: + before = text[:find_index] + after = text[find_index+len(search_string):] + new_text = urwid.Text( + [ + before, + (self.highlight_color, search_string), + after, + ] + ) + + self.state.add_flow_setting(self.flow, "last_search_index", + find_index) + self.state.add_flow_setting(self.flow, "last_find_line", i) + + text_objects[i] = new_text + + found = True + + break + + i += 1 + + if found: + focus_pos = i + else : + # loop from the beginning, but not forever. + if (start_line == 0 and start_index == 0) or looping: + focus_pos = None + else: + self.state.add_flow_setting(self.flow, "last_search_index", 0) + self.state.add_flow_setting(self.flow, "last_find_line", 0) + text_objects, focus_pos = self.search_highlight_text(text_objects, search_string, True) + + return text_objects, focus_pos + def view_request(self): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST body = self.conn_text(self.flow.request) @@ -574,6 +752,20 @@ class FlowView(common.WWrap): 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) + elif key == "n": + last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + if last_search_string: + message = self.search(last_search_string) + if message: + self.master.statusbar.message(message) + else: + self.master.statusbar.message("no previous searches have been made") else: return key diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 4986840d..fa5142f3 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -482,3 +482,12 @@ class PathEditor(GridEditor): columns = 1 headings = ("Component",) + +class ScriptEditor(GridEditor): + title = "Editing scripts" + columns = 1 + headings = ("Path",) + def is_error(self, col, val): + return False + + diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index f8be6605..0d01ac6f 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -62,6 +62,10 @@ class HelpView(urwid.ListBox): [("text", ": JSON")] ), (None, + common.highlight_key("css", "c") + + [("text", ": CSS")] + ), + (None, common.highlight_key("urlencoded", "u") + [("text", ": URL-encoded data")] ), |