From 359573a764220a3bad94729ff5dce4e94ce2a187 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 23 Dec 2013 16:41:46 +1300 Subject: add search with / for compatibility with what I'll do. --- libmproxy/console/flowlist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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( -- cgit v1.2.3 From 932464d0a0f572e256f6dea898196db1e3f66b50 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 24 Dec 2013 14:28:20 +1300 Subject: test passing, UI still not working --- libmproxy/console/flowview.py | 79 +++++++++++++++++++++++++++++++++++++--- libmproxy/flow.py | 2 +- test/test_console_contentview.py | 14 +++++++ test/tutils.py | 10 +++++ 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index c19ba5e6..4a4ee9bf 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -119,7 +119,7 @@ class FlowView(common.WWrap): def _cached_content_view(self, viewmode, hdrItems, content, limit): return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event) - def content_view(self, viewmode, conn): + def content_view(self, viewmode, conn, highlight_string = None): full = self.state.get_flow_setting( self.flow, (self.state.view_flow_mode, "fullcontents"), @@ -129,7 +129,7 @@ class FlowView(common.WWrap): limit = sys.maxint else: limit = contentview.VIEW_CUTOFF - return cache.callback( + description, text_object = cache.callback( self, "_cached_content_view", viewmode, tuple(tuple(i) for i in conn.headers.lst), @@ -137,12 +137,20 @@ class FlowView(common.WWrap): limit ) - def conn_text(self, conn): + if highlight_string: + text_object = self.search_highlight_text(text_object[0], + highlight_string) + text_object = [text_object] + + return (description, text_object) + + def conn_text(self, conn, highlight_string=""): txt = 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, @@ -153,15 +161,16 @@ class FlowView(common.WWrap): if conn.content == flow.CONTENT_MISSING: msg, body = "", [urwid.Text([("error", "[content missing]")])] else: - msg, body = self.content_view(viewmode, conn) + msg, body = self.content_view(viewmode, conn, highlight_string) cols = [ - urwid.Text( + urwid.Text( [ ("heading", msg), ] ) ] + if override is not None: cols.append( urwid.Text( @@ -174,11 +183,15 @@ class FlowView(common.WWrap): align="right" ) ) + title = urwid.AttrWrap(urwid.Columns(cols), "heading") txt.append(title) txt.extend(body) elif conn.content == flow.CONTENT_MISSING: pass + + print(txt) + print("\n\n\n") return urwid.ListBox(txt) def _tab(self, content, attr): @@ -215,6 +228,58 @@ class FlowView(common.WWrap): ) return f + def search(self, search_string): + # two things need to happen. 1) text needs to be highlighted. 2) we + # need to focus on the highlighted text. + 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" + + # highlight + body = self.conn_text(text, search_string) + + self.w = self.wrap_body(const, body) + self.master.statusbar.redraw() + + def search_get_start(self, search_string): + last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + if search_string == last_search_string: + start_index = self.state.get_flow_setting(self.flow, + "last_search_index") + len(search_string) + else: + self.state.add_flow_setting(self.flow, "last_search_string", + search_string) + start_index = 0 + + return start_index + + def search_highlight_text(self, text_object, search_string): + text, style = text_object.get_text() + start_index = self.search_get_start(search_string) + 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, + ("dark red", search_string), + after, + ] + ) + + self.state.add_flow_setting(self.flow, "last_search_index", + find_index) + + return new_text + else: + return text_object + def view_request(self): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST body = self.conn_text(self.flow.request) @@ -574,6 +639,10 @@ class FlowView(common.WWrap): conn ) self.master.refresh_flow(self.flow) + elif key == "/": + self.master.prompt("Search body: ", + self.state.get_flow_setting(self.flow, "last_search_string"), + self.search) else: return key diff --git a/libmproxy/flow.py b/libmproxy/flow.py index 44dc57ae..0ab0170b 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -1431,7 +1431,7 @@ class FlowMaster(controller.Master): def run_script_hook(self, name, *args, **kwargs): for script in self.scripts: self.run_single_script_hook(script, name, *args, **kwargs) - + def set_stickycookie(self, txt): if txt: flt = filt.parse(txt) diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index ef44f834..c2ed2ffa 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -250,3 +250,17 @@ if cv.ViewProtobuf.is_available(): def test_get_by_shortcut(): assert cv.get_by_shortcut("h") + +def test_search_highlights(): + # Default text in requests is content. We will search for nt once, and + # expect the first bit to be highlighted. We will do it again and expect the + # second to be. + f = tutils.tflowview() + + ui_elements = f.search("nt") + text_object = ui_elements.contents()[2] + assert text_object.get_text() == ('content', [(None, 2), ('dark red', 2)]) + + ui_elements = f.search("nt") + text_object = ui_elements.contents()[2] + assert text_object.get_text() == ('content', [(None, 5), ('dark red', 2)]) diff --git a/test/tutils.py b/test/tutils.py index 4cd7b7f8..afc1fb51 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -1,8 +1,11 @@ import os, shutil, tempfile from contextlib import contextmanager from libmproxy import flow, utils, controller +from libmproxy.console.flowview import FlowView +from libmproxy.console import ConsoleState from netlib import certutils from nose.plugins.skip import SkipTest +from mock import Mock def _SkipWindows(): raise SkipTest("Skipped on Windows.") @@ -57,6 +60,13 @@ def tflow_err(): f.error = terr(f.request) return f +def tflowview(): + m = Mock() + cs = ConsoleState() + flow = tflow() + fv = FlowView(m, cs, flow) + return fv + @contextmanager def tmpdir(*args, **kwargs): -- cgit v1.2.3 From 95406bd119d87ccc3e99ddffd11c92e92a7da34b Mon Sep 17 00:00:00 2001 From: root Date: Wed, 25 Dec 2013 16:50:29 +1300 Subject: Add focusing, and fixes non-clearance of prev searches. Add documentation. --- libmproxy/console/flowview.py | 108 +++++++++++++++++++++++++++------------ test/test_console_contentview.py | 55 +++++++++++++++++--- test/tutils.py | 21 +++++--- 3 files changed, 139 insertions(+), 45 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 4a4ee9bf..d6d8c42b 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -63,6 +63,7 @@ def _mkhelp(): ("tab", "toggle request/response view"), ("space", "next flow"), ("|", "run script on this flow"), + ("/", "Search in response body (case sensitive)"), ] text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) return text @@ -85,7 +86,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 +112,12 @@ class FlowView(common.WWrap): ("options", "o"), ("edit raw", "e"), ] + + highlight_color = "key" + 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 +136,8 @@ class FlowView(common.WWrap): limit = sys.maxint else: limit = contentview.VIEW_CUTOFF - description, text_object = cache.callback( + + description, text_objects = cache.callback( self, "_cached_content_view", viewmode, tuple(tuple(i) for i in conn.headers.lst), @@ -138,11 +146,12 @@ class FlowView(common.WWrap): ) if highlight_string: - text_object = self.search_highlight_text(text_object[0], + text_objects, focus_position = self.search_highlight_text(text_objects, highlight_string) - text_object = [text_object] + else: + focus_position = None - return (description, text_object) + return (description, text_objects, focus_position) def conn_text(self, conn, highlight_string=""): txt = common.format_keyvals( @@ -159,9 +168,9 @@ class FlowView(common.WWrap): viewmode = self.state.default_body_view if override is None else override if conn.content == flow.CONTENT_MISSING: - msg, body = "", [urwid.Text([("error", "[content missing]")])] + msg, body, text_focus_position = "", [urwid.Text([("error", "[content missing]")])], 0 else: - msg, body = self.content_view(viewmode, conn, highlight_string) + msg, body, text_focus_position = self.content_view(viewmode, conn, highlight_string) cols = [ urwid.Text( @@ -190,9 +199,13 @@ class FlowView(common.WWrap): elif conn.content == flow.CONTENT_MISSING: pass - print(txt) - print("\n\n\n") - return urwid.ListBox(txt) + self.last_displayed_body = urwid.ListBox(txt) + + if text_focus_position : + # +2 because of the two header columns + self.last_displayed_body.set_focus(text_focus_position + 2) + + return self.last_displayed_body def _tab(self, content, attr): p = urwid.Text(content) @@ -229,6 +242,11 @@ class FlowView(common.WWrap): return f def search(self, search_string): + + if search_string == "": + search_string = self.state.get_flow_setting(self.flow, + "last_search_string") + # two things need to happen. 1) text needs to be highlighted. 2) we # need to focus on the highlighted text. if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: @@ -249,36 +267,58 @@ class FlowView(common.WWrap): def search_get_start(self, 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_index = self.state.get_flow_setting(self.flow, "last_search_index") + len(search_string) else: self.state.add_flow_setting(self.flow, "last_search_string", search_string) + start_line = 0 start_index = 0 - return start_index - - def search_highlight_text(self, text_object, search_string): - text, style = text_object.get_text() - start_index = self.search_get_start(search_string) - 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, - ("dark red", search_string), - after, - ] - ) + return (start_line, start_index) - self.state.add_flow_setting(self.flow, "last_search_index", - find_index) + def search_highlight_text(self, text_objects, search_string): + start_line, start_index = self.search_get_start(search_string) + i = start_line - return new_text - else: - return text_object + 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 : + focus_post = None + + return text_objects, focus_pos def view_request(self): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST @@ -640,8 +680,10 @@ class FlowView(common.WWrap): ) self.master.refresh_flow(self.flow) elif key == "/": - self.master.prompt("Search body: ", - 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:" + self.master.prompt(search_prompt, + None, self.search) else: return key diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index c2ed2ffa..95012657 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -257,10 +257,53 @@ def test_search_highlights(): # second to be. f = tutils.tflowview() - ui_elements = f.search("nt") - text_object = ui_elements.contents()[2] - assert text_object.get_text() == ('content', [(None, 2), ('dark red', 2)]) + f.search("nt") + text_object = tutils.get_body_line(f.last_displayed_body, 0) + assert text_object.get_text() == ('content', [(None, 2), (f.highlight_color, 2)]) + + f.search("nt") + text_object = tutils.get_body_line(f.last_displayed_body, 1) + assert text_object.get_text() == ('content', [(None, 5), (f.highlight_color, 2)]) + +def test_search_highlights_clears_prev(): + f = tutils.tflowview(request_contents="this is string\nstring is string") + + f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 0) + assert text_object.get_text() == ('this is string', [(None, 8), (f.highlight_color, 6)]) + + # search again, it should not be highlighted again. + f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 0) + assert text_object.get_text() != ('this is string', [(None, 8), (f.highlight_color, 6)]) + +def test_search_highlights_multi_line(): + f = tutils.tflowview(request_contents="this is string\nstring is string") + + # should highlight the first line. + f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 0) + assert text_object.get_text() == ('this is string', [(None, 8), (f.highlight_color, 6)]) + + # should highlight second line, first appearance of string. + f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 1) + assert text_object.get_text() == ('string is string', [(None, 0), ('key', 6)]) + + # should highlight third line, second appearance of string. + f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 1) + assert text_object.get_text() == ('string is string', [(None, 10), (f.highlight_color, 6)]) + +def test_search_focuses(): + f = tutils.tflowview(request_contents="this is string\nstring is string") + + # should highlight the first line. + f.search("string") + + # should be focusing on the 2nd text line. + f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 1) + assert f.last_displayed_body.focus == text_object + - ui_elements = f.search("nt") - text_object = ui_elements.contents()[2] - assert text_object.get_text() == ('content', [(None, 5), ('dark red', 2)]) diff --git a/test/tutils.py b/test/tutils.py index afc1fb51..d6332107 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -15,13 +15,14 @@ def SkipWindows(fn): else: return fn -def treq(conn=None): +def treq(conn=None, content="content"): if not conn: conn = flow.ClientConnect(("address", 22)) conn.reply = controller.DummyReply() headers = flow.ODictCaseless() headers["header"] = ["qvalue"] - r = flow.Request(conn, (1, 1), "host", 80, "http", "GET", "/path", headers, "content") + r = flow.Request(conn, (1, 1), "host", 80, "http", "GET", "/path", headers, + content) r.reply = controller.DummyReply() return r @@ -44,8 +45,9 @@ def terr(req=None): return err -def tflow(): - r = treq() +def tflow(r=None): + if r == None: + r = treq() return flow.Flow(r) @@ -60,13 +62,20 @@ def tflow_err(): f.error = terr(f.request) return f -def tflowview(): +def tflowview(request_contents=None): m = Mock() cs = ConsoleState() - flow = tflow() + if request_contents == None: + flow = tflow() + else: + req = treq(None, request_contents) + flow = tflow(req) + fv = FlowView(m, cs, flow) return fv +def get_body_line(last_displayed_body, line_nb): + return last_displayed_body.contents()[line_nb + 2] @contextmanager def tmpdir(*args, **kwargs): -- cgit v1.2.3 From bcf5620239870a5858ffd5fa43b87d529361da74 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 25 Dec 2013 16:57:54 +1300 Subject: fix crash --- libmproxy/console/flowview.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index d6d8c42b..601a7195 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -113,7 +113,7 @@ class FlowView(common.WWrap): ("edit raw", "e"), ] - highlight_color = "key" + highlight_color = "focusfield" def __init__(self, master, state, flow): self.master, self.state, self.flow = master, state, flow @@ -316,7 +316,7 @@ class FlowView(common.WWrap): if found: focus_pos = i else : - focus_post = None + focus_pos = None return text_objects, focus_pos -- cgit v1.2.3 From 9cf8a1a89dadfebedaf48258519c9a8953375597 Mon Sep 17 00:00:00 2001 From: Pedro Worcel Date: Wed, 25 Dec 2013 21:08:20 +1300 Subject: fix failing test --- test/test_console_contentview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index 95012657..32f4fad2 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -288,7 +288,8 @@ def test_search_highlights_multi_line(): # should highlight second line, first appearance of string. f.search("string") text_object = tutils.get_body_line(f.last_displayed_body, 1) - assert text_object.get_text() == ('string is string', [(None, 0), ('key', 6)]) + print text_object.get_text() + assert text_object.get_text() == ('string is string', [(None, 0), (f.highlight_color, 6)]) # should highlight third line, second appearance of string. f.search("string") -- cgit v1.2.3 From 21efe2f2c84cd0615d0b0f47a9007efe34abc5b9 Mon Sep 17 00:00:00 2001 From: Pedro Worcel Date: Thu, 26 Dec 2013 17:04:18 +1300 Subject: add looping around --- libmproxy/console/flowview.py | 169 +++++++++++++++++++++++++++------------ test/test_console_contentview.py | 21 +++++ 2 files changed, 137 insertions(+), 53 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 601a7195..9ec0ea21 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -126,7 +126,7 @@ class FlowView(common.WWrap): def _cached_content_view(self, viewmode, hdrItems, content, limit): return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event) - def content_view(self, viewmode, conn, highlight_string = None): + def content_view(self, viewmode, conn): full = self.state.get_flow_setting( self.flow, (self.state.view_flow_mode, "fullcontents"), @@ -145,67 +145,84 @@ class FlowView(common.WWrap): limit ) - if highlight_string: - text_objects, focus_position = self.search_highlight_text(text_objects, - highlight_string) - else: - focus_position = None + 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 (description, text_objects, focus_position) + return (msg, body) - def conn_text(self, conn, highlight_string=""): - txt = common.format_keyvals( + 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, text_focus_position = "", [urwid.Text([("error", "[content missing]")])], 0 - else: - msg, body, text_focus_position = self.content_view(viewmode, conn, highlight_string) + return headers, msg, body - cols = [ - urwid.Text( - [ - ("heading", msg), - ] - ) - ] + 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 + """ - if override is not None: - cols.append( - urwid.Text( - [ - " ", - ('heading', "["), - ('heading_key', "m"), - ('heading', (":%s]"%viewmode.name)), - ], - align="right" - ) + 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 + title = urwid.AttrWrap(urwid.Columns(cols), "heading") + headers.append(title) + headers.extend(body) - self.last_displayed_body = urwid.ListBox(txt) + return headers - if text_focus_position : - # +2 because of the two header columns - self.last_displayed_body.set_focus(text_focus_position + 2) + 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 self.last_displayed_body + return urwid.ListBox(merged) def _tab(self, content, attr): p = urwid.Text(content) @@ -241,14 +258,35 @@ 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") - # two things need to happen. 1) text needs to be highlighted. 2) we - # need to focus on the highlighted text. if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: text = self.flow.request const = common.VIEW_FLOW_REQUEST @@ -258,12 +296,31 @@ class FlowView(common.WWrap): if not self.flow.response: return "no response to search in" - # highlight - body = self.conn_text(text, search_string) + 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) - self.w = self.wrap_body(const, body) + 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): last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") if search_string == last_search_string: @@ -278,7 +335,7 @@ class FlowView(common.WWrap): return (start_line, start_index) - def search_highlight_text(self, text_objects, search_string): + def search_highlight_text(self, text_objects, search_string, looping = False): start_line, start_index = self.search_get_start(search_string) i = start_line @@ -316,7 +373,13 @@ class FlowView(common.WWrap): if found: focus_pos = i else : - focus_pos = None + # 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 diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index 32f4fad2..d013d10d 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -265,6 +265,13 @@ def test_search_highlights(): text_object = tutils.get_body_line(f.last_displayed_body, 1) assert text_object.get_text() == ('content', [(None, 5), (f.highlight_color, 2)]) +def test_search_returns_useful_messages(): + f = tutils.tflowview() + + # original string is content. this string should not be in there. + response = f.search("oranges and other fruit.") + assert response == "no matches for 'oranges and other fruit.'" + def test_search_highlights_clears_prev(): f = tutils.tflowview(request_contents="this is string\nstring is string") @@ -296,6 +303,20 @@ def test_search_highlights_multi_line(): text_object = tutils.get_body_line(f.last_displayed_body, 1) assert text_object.get_text() == ('string is string', [(None, 10), (f.highlight_color, 6)]) +def test_search_loops(): + f = tutils.tflowview(request_contents="this is string\nstring is string") + + # get to the end. + f.search("string") + f.search("string") + f.search("string") + + # should highlight the first line. + message = f.search("string") + text_object = tutils.get_body_line(f.last_displayed_body, 0) + assert text_object.get_text() == ('this is string', [(None, 8), (f.highlight_color, 6)]) + assert message == "search hit BOTTOM, continuing at TOP" + def test_search_focuses(): f = tutils.tflowview(request_contents="this is string\nstring is string") -- cgit v1.2.3 From 70f6bb301b2599b8823617a7f1038f44aa9e6acc Mon Sep 17 00:00:00 2001 From: Pedro Worcel Date: Thu, 26 Dec 2013 17:16:26 +1300 Subject: added 'n' functionality, tidy up & testing --- libmproxy/console/flowview.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 9ec0ea21..5989ee2e 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -744,10 +744,18 @@ class FlowView(common.WWrap): 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: " 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 -- cgit v1.2.3 From 34a09780ee1354cf70f36f635f9207fc4dc7db95 Mon Sep 17 00:00:00 2001 From: Pedro Worcel Date: Thu, 26 Dec 2013 17:26:51 +1300 Subject: add docs --- libmproxy/console/flowview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 5989ee2e..20ef864e 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -63,7 +63,8 @@ def _mkhelp(): ("tab", "toggle request/response view"), ("space", "next flow"), ("|", "run script on this flow"), - ("/", "Search in response body (case sensitive)"), + ("/", "search in response body (case sensitive)"), + ("n", "repeat previous search"), ] text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) return text -- cgit v1.2.3 From a4b059c2a7ebe4e03abedd63f297d359509347d6 Mon Sep 17 00:00:00 2001 From: Pedro Worcel Date: Thu, 26 Dec 2013 21:53:04 +1300 Subject: fix bug that happens after searching for a non-existant string, and later for an existant one. --- libmproxy/console/flowview.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 20ef864e..b0931fa2 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -327,7 +327,12 @@ class FlowView(common.WWrap): 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") + len(search_string) + "last_search_index") + + if start_index != None: + start_index += len(search_string) + else: + start_index = 0 else: self.state.add_flow_setting(self.flow, "last_search_string", search_string) -- cgit v1.2.3 From 799c87767684880469c12d75053fb860f4a0d3c9 Mon Sep 17 00:00:00 2001 From: Pedro Worcel Date: Thu, 26 Dec 2013 22:18:34 +1300 Subject: now really fix it + test --- libmproxy/console/flowview.py | 14 +++++++++----- test/test_console_contentview.py | 21 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index b0931fa2..35fc1e43 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -323,21 +323,25 @@ class FlowView(common.WWrap): 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 += len(search_string) - else: + 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) - start_line = 0 - start_index = 0 return (start_line, start_index) diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index d013d10d..f581424b 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -295,7 +295,6 @@ def test_search_highlights_multi_line(): # should highlight second line, first appearance of string. f.search("string") text_object = tutils.get_body_line(f.last_displayed_body, 1) - print text_object.get_text() assert text_object.get_text() == ('string is string', [(None, 0), (f.highlight_color, 6)]) # should highlight third line, second appearance of string. @@ -328,4 +327,24 @@ def test_search_focuses(): text_object = tutils.get_body_line(f.last_displayed_body, 1) assert f.last_displayed_body.focus == text_object +def test_search_does_not_crash_on_bad(): + """ + this used to crash, kept for reference. + """ + + f = tutils.tflowview(request_contents="this is string\nstring is string\n"+("A" * cv.VIEW_CUTOFF)+"AFTERCUTOFF") + f.search("AFTERCUTOFF") + + # pretend F + f.state.add_flow_setting( + f.flow, + (f.state.view_flow_mode, "fullcontents"), + True + ) + f.master.refresh_flow(f.flow) + + # text changed, now this string will exist. can happen when user presses F + # for full text view + f.search("AFTERCUTOFF") + -- cgit v1.2.3