aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@corte.si>2017-12-16 10:30:08 +1300
committerAldo Cortesi <aldo@corte.si>2017-12-17 10:11:02 +1300
commitcd913d598da421b4c4e264e029be5f13e3e009bb (patch)
tree7fb418ea9c7d57c9006f0e322ce8127df5d9d390 /mitmproxy
parent50a94db2cc2f3107f6f94c1e1407cb6840d1da08 (diff)
downloadmitmproxy-cd913d598da421b4c4e264e029be5f13e3e009bb.tar.gz
mitmproxy-cd913d598da421b4c4e264e029be5f13e3e009bb.tar.bz2
mitmproxy-cd913d598da421b4c4e264e029be5f13e3e009bb.zip
command cuts: add completion
- Remove shortcuts for request, response, etc. - we don't need them if we have completion - Restrict cuts specification to a set of prefixes - Extend cuts to add a few more items
Diffstat (limited to 'mitmproxy')
-rw-r--r--mitmproxy/addons/cut.py42
-rw-r--r--mitmproxy/command.py41
-rw-r--r--mitmproxy/test/tflow.py4
-rw-r--r--mitmproxy/test/tutils.py8
-rw-r--r--mitmproxy/tools/console/commander/commander.py13
-rw-r--r--mitmproxy/tools/console/defaultkeys.py2
6 files changed, 79 insertions, 31 deletions
diff --git a/mitmproxy/addons/cut.py b/mitmproxy/addons/cut.py
index 6b9dc723..efc9e5df 100644
--- a/mitmproxy/addons/cut.py
+++ b/mitmproxy/addons/cut.py
@@ -17,14 +17,6 @@ def headername(spec: str):
return spec[len("header["):-1].strip()
-flow_shortcuts = {
- "q": "request",
- "s": "response",
- "cc": "client_conn",
- "sc": "server_conn",
-}
-
-
def is_addr(v):
return isinstance(v, tuple) and len(v) > 1
@@ -35,8 +27,6 @@ def extract(cut: str, f: flow.Flow) -> typing.Union[str, bytes]:
for i, spec in enumerate(path):
if spec.startswith("_"):
raise exceptions.CommandError("Can't access internal attribute %s" % spec)
- if isinstance(current, flow.Flow):
- spec = flow_shortcuts.get(spec, spec)
part = getattr(current, spec, None)
if i == len(path) - 1:
@@ -65,13 +55,12 @@ class Cut:
) -> command.Cuts:
"""
Cut data from a set of flows. Cut specifications are attribute paths
- from the base of the flow object, with a few conveniences - "q",
- "s", "cc" and "sc" are shortcuts for request, response, client_conn
- and server_conn, "port" and "host" retrieve parts of an address
- tuple, ".header[key]" retrieves a header value. Return values
- converted to strings or bytes: SSL certicates are converted to PEM
- format, bools are "true" or "false", "bytes" are preserved, and all
- other values are converted to strings.
+ from the base of the flow object, with a few conveniences - "port"
+ and "host" retrieve parts of an address tuple, ".header[key]"
+ retrieves a header value. Return values converted to strings or
+ bytes: SSL certicates are converted to PEM format, bools are "true"
+ or "false", "bytes" are preserved, and all other values are
+ converted to strings.
"""
ret = []
for f in flows:
@@ -101,11 +90,11 @@ class Cut:
if fp.tell() > 0:
# We're appending to a file that already exists and has content
fp.write(b"\n")
- for v in [extract(cuts[0], f) for f in flows]:
- if isinstance(v, bytes):
- fp.write(v)
- else:
- fp.write(v.encode("utf8"))
+ v = extract(cuts[0], flows[0])
+ if isinstance(v, bytes):
+ fp.write(v)
+ else:
+ fp.write(v.encode("utf8"))
ctx.log.alert("Saved single cut.")
else:
with open(path, "a" if append else "w", newline='', encoding="utf8") as fp:
@@ -129,8 +118,8 @@ class Cut:
column, the data is written to file as-is, with raw bytes preserved.
"""
fp = io.StringIO(newline="")
- if len(cuts) == 1 and len(cuts[0]) == 1:
- v = cuts[0][0]
+ if len(cuts) == 1 and len(flows) == 1:
+ v = extract(cuts[0], flows[0])
if isinstance(v, bytes):
fp.write(strutils.always_str(v))
else:
@@ -138,9 +127,10 @@ class Cut:
ctx.log.alert("Clipped single cut.")
else:
writer = csv.writer(fp)
- for r in cuts:
+ for f in flows:
+ vals = [extract(c, f) for c in cuts]
writer.writerow(
- [strutils.always_str(c) or "" for c in r] # type: ignore
+ [strutils.always_str(v) or "" for v in vals] # type: ignore
)
ctx.log.alert("Clipped %s cuts as CSV." % len(cuts))
pyperclip.copy(fp.getvalue())
diff --git a/mitmproxy/command.py b/mitmproxy/command.py
index 82bad4fa..7d7fa735 100644
--- a/mitmproxy/command.py
+++ b/mitmproxy/command.py
@@ -30,7 +30,46 @@ Cuts = typing.Sequence[
class Cut(str):
- pass
+ # This is an awkward location for these values, but it's better than having
+ # the console core import and depend on an addon. FIXME: Add a way for
+ # addons to add custom types and manage their completion and validation.
+ valid_prefixes = [
+ "request.method",
+ "request.scheme",
+ "request.host",
+ "request.http_version",
+ "request.port",
+ "request.path",
+ "request.url",
+ "request.text",
+ "request.content",
+ "request.raw_content",
+ "request.timestamp_start",
+ "request.timestamp_end",
+ "request.header[",
+
+ "response.status_code",
+ "response.reason",
+ "response.text",
+ "response.content",
+ "response.timestamp_start",
+ "response.timestamp_end",
+ "response.raw_content",
+ "response.header[",
+
+ "client_conn.address.port",
+ "client_conn.address.host",
+ "client_conn.tls_version",
+ "client_conn.sni",
+ "client_conn.ssl_established",
+
+ "server_conn.address.port",
+ "server_conn.address.host",
+ "server_conn.ip_address.host",
+ "server_conn.tls_version",
+ "server_conn.sni",
+ "server_conn.ssl_established",
+ ]
class Path(str):
diff --git a/mitmproxy/test/tflow.py b/mitmproxy/test/tflow.py
index e754cb54..c3dab30c 100644
--- a/mitmproxy/test/tflow.py
+++ b/mitmproxy/test/tflow.py
@@ -53,6 +53,8 @@ def twebsocketflow(client_conn=True, server_conn=True, messages=True, err=None,
sec_websocket_version="13",
sec_websocket_key="1234",
),
+ timestamp_start=1,
+ timestamp_end=2,
content=b''
)
resp = http.HTTPResponse(
@@ -64,6 +66,8 @@ def twebsocketflow(client_conn=True, server_conn=True, messages=True, err=None,
upgrade='websocket',
sec_websocket_accept=b'',
),
+ timestamp_start=1,
+ timestamp_end=2,
content=b'',
)
handshake_flow = http.HTTPFlow(client_conn, server_conn)
diff --git a/mitmproxy/test/tutils.py b/mitmproxy/test/tutils.py
index 80e5b6fd..bcce547a 100644
--- a/mitmproxy/test/tutils.py
+++ b/mitmproxy/test/tutils.py
@@ -31,7 +31,9 @@ def treq(**kwargs):
path=b"/path",
http_version=b"HTTP/1.1",
headers=http.Headers(((b"header", b"qvalue"), (b"content-length", b"7"))),
- content=b"content"
+ content=b"content",
+ timestamp_start=1,
+ timestamp_end=2,
)
default.update(kwargs)
return http.Request(**default)
@@ -48,8 +50,8 @@ def tresp(**kwargs):
reason=b"OK",
headers=http.Headers(((b"header-response", b"svalue"), (b"content-length", b"7"))),
content=b"message",
- timestamp_start=time.time(),
- timestamp_end=time.time(),
+ timestamp_start=1,
+ timestamp_end=2,
)
default.update(kwargs)
return http.Response(**default)
diff --git a/mitmproxy/tools/console/commander/commander.py b/mitmproxy/tools/console/commander/commander.py
index 5fc7dd12..b94d6f69 100644
--- a/mitmproxy/tools/console/commander/commander.py
+++ b/mitmproxy/tools/console/commander/commander.py
@@ -113,6 +113,19 @@ class CommandBuffer():
),
parse = parts,
)
+ if last.type == typing.Sequence[mitmproxy.command.Cut]:
+ spec = parts[-1].value.split(",")
+ opts = []
+ for pref in mitmproxy.command.Cut.valid_prefixes:
+ spec[-1] = pref
+ opts.append(",".join(spec))
+ self.completion = CompletionState(
+ completer = ListCompleter(
+ parts[-1].value,
+ opts,
+ ),
+ parse = parts,
+ )
elif isinstance(last.type, mitmproxy.command.Choice):
self.completion = CompletionState(
completer = ListCompleter(
diff --git a/mitmproxy/tools/console/defaultkeys.py b/mitmproxy/tools/console/defaultkeys.py
index b845a3ae..7e078bbf 100644
--- a/mitmproxy/tools/console/defaultkeys.py
+++ b/mitmproxy/tools/console/defaultkeys.py
@@ -31,7 +31,7 @@ def map(km):
km.add("A", "flow.resume @all", ["flowlist", "flowview"], "Resume all intercepted flows")
km.add("a", "flow.resume @focus", ["flowlist", "flowview"], "Resume this intercepted flow")
km.add(
- "b", "console.command cut.save @focus s.content ",
+ "b", "console.command cut.save @focus response.content ",
["flowlist", "flowview"],
"Save response body to file"
)