aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libmproxy/console/common.py148
-rw-r--r--libmproxy/console/flowlist.py6
-rw-r--r--libmproxy/console/flowview.py40
-rw-r--r--setup.py3
4 files changed, 165 insertions, 32 deletions
diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py
index 3e6e5ccc..e4a4acba 100644
--- a/libmproxy/console/common.py
+++ b/libmproxy/console/common.py
@@ -1,9 +1,14 @@
from __future__ import absolute_import
import urwid
import urwid.util
+import os
from .. import utils
-from ..protocol.http import CONTENT_MISSING
+from ..protocol.http import CONTENT_MISSING, decoded
+try:
+ import pyperclip
+except:
+ pyperclip = False
VIEW_LIST = 0
VIEW_FLOW = 1
@@ -162,6 +167,146 @@ def raw_format_flow(f, focus, extended, padding):
return urwid.Pile(pile)
+# Save file to disk
+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:
+ f.write(data)
+ except IOError, v:
+ master.statusbar.message(v.strerror)
+
+
+def ask_save_path(prompt, data, master, state):
+ master.path_prompt(
+ prompt,
+ state.last_saveload,
+ save_data,
+ data,
+ master,
+ state
+ )
+
+
+def copy_flow_format_data(part, scope, flow):
+ if part == "u":
+ data = flow.request.url
+ else:
+ data = ""
+ if scope in ("q", "a"):
+ with decoded(flow.request):
+ if part == "h":
+ data += flow.request.assemble()
+ elif part == "c":
+ data += flow.request.content
+ else:
+ raise ValueError("Unknown part: {}".format(part))
+ if scope == "a" and flow.request.content and flow.response:
+ # Add padding between request and response
+ data += "\r\n" * 2
+ if scope in ("s", "a") and flow.response:
+ with decoded(flow.response):
+ if part == "h":
+ data += flow.response.assemble()
+ elif part == "c":
+ data += flow.response.content
+ else:
+ raise ValueError("Unknown part: {}".format(part))
+ return data
+
+
+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)
+
+ if not data:
+ if scope == "q":
+ master.statusbar.message("No request content to copy.")
+ elif scope == "s":
+ master.statusbar.message("No response content to copy.")
+ else:
+ master.statusbar.message("No contents to copy.")
+ return
+
+ try:
+ master.add_event(str(len(data)))
+ pyperclip.copy(data)
+ except RuntimeError:
+ 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?",
+ (
+ ("yes", "y"),
+ ("no", "n"),
+ ),
+ save
+ )
+
+
+def ask_copy_part(scope, flow, master, state):
+ choices = [
+ ("content", "c"),
+ ("headers+content", "h")
+ ]
+ if scope != "s":
+ choices.append(("url", "u"))
+
+ master.prompt_onekey(
+ "Copy",
+ choices,
+ copy_flow,
+ scope,
+ flow,
+ master,
+ state
+ )
+
+
+def ask_save_body(part, master, state, flow):
+ """
+ Save either the request or the response body to disk.
+ part can either be "q" (request), "s" (response) or None (ask user if necessary).
+ """
+
+ request_has_content = flow.request and flow.request.content
+ response_has_content = flow.response and flow.response.content
+
+ if part is None:
+ # 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",
+ (
+ ("request", "q"),
+ ("response", "s"),
+ ),
+ ask_save_body,
+ master,
+ state,
+ flow
+ )
+ elif response_has_content:
+ ask_save_body("s", master, state, flow)
+ else:
+ ask_save_body("q", master, state, flow)
+
+ elif part == "q" and request_has_content:
+ ask_save_path("Save request content: ", flow.request.get_decoded_content(), master, state)
+ elif part == "s" and response_has_content:
+ ask_save_path("Save response content: ", flow.response.get_decoded_content(), master, state)
+ else:
+ master.statusbar.message("No content to save.")
+
+
class FlowCache:
@utils.LRUCache(200)
def format_flow(self, *args):
@@ -211,7 +356,6 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2):
return flowcache.format_flow(tuple(sorted(d.items())), focus, extended, padding)
-
def int_version(v):
SIG = 3
v = urwid.__version__.split("-")[0].split(".")
diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py
index 102fa7b9..c5cef061 100644
--- a/libmproxy/console/flowlist.py
+++ b/libmproxy/console/flowlist.py
@@ -7,11 +7,13 @@ def _mkhelp():
keys = [
("A", "accept all intercepted flows"),
("a", "accept this intercepted flow"),
+ ("b", "save request/response body"),
("C", "clear flow list or eventlog"),
("d", "delete flow"),
("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"),
("r", "replay request"),
@@ -204,6 +206,10 @@ class ConnectionItem(common.WWrap):
self.master.run_script_once,
self.flow
)
+ elif key == "g":
+ 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)
else:
return key
diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py
index 2aac575d..d5d41f7b 100644
--- a/libmproxy/console/flowview.py
+++ b/libmproxy/console/flowview.py
@@ -19,6 +19,7 @@ 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") +
@@ -508,22 +509,6 @@ class FlowView(common.WWrap):
self.flow.request.method = i[0].upper()
self.master.refresh_flow(self.flow)
- def save_body(self, path):
- if not path:
- return
- self.state.last_saveload = path
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- c = self.flow.request
- else:
- c = self.flow.response
- path = os.path.expanduser(path)
- try:
- f = file(path, "wb")
- f.write(str(c.content))
- f.close()
- except IOError, v:
- self.master.statusbar.message(v.strerror)
-
def set_url(self, url):
request = self.flow.request
try:
@@ -689,19 +674,10 @@ class FlowView(common.WWrap):
self.master.accept_all()
self.master.view_flow(self.flow)
elif key == "b":
- if conn:
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- self.master.path_prompt(
- "Save request body: ",
- self.state.last_saveload,
- self.save_body
- )
- else:
- self.master.path_prompt(
- "Save response body: ",
- self.state.last_saveload,
- self.save_body
- )
+ if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
+ 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()
@@ -752,6 +728,12 @@ class FlowView(common.WWrap):
)
self.master.refresh_flow(self.flow)
self.master.statusbar.message("")
+ elif key == "g":
+ if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
+ 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"))
diff --git a/setup.py b/setup.py
index fde6ff66..9e22a039 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,8 @@ deps = {
"pyasn1>0.1.2",
"pyOpenSSL>=0.14",
"tornado>=4.0.2",
- "configargparse>=0.9.3"
+ "configargparse>=0.9.3",
+ "pyperclip>=1.5.8"
}
script_deps = {
"mitmproxy": {