aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/addons/dumper.py
diff options
context:
space:
mode:
Diffstat (limited to 'mitmproxy/addons/dumper.py')
-rw-r--r--mitmproxy/addons/dumper.py253
1 files changed, 253 insertions, 0 deletions
diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py
new file mode 100644
index 00000000..04dfb42c
--- /dev/null
+++ b/mitmproxy/addons/dumper.py
@@ -0,0 +1,253 @@
+import itertools
+
+import click
+
+import typing # noqa
+
+from mitmproxy import contentviews
+from mitmproxy import ctx
+from mitmproxy import exceptions
+from mitmproxy import flowfilter
+from netlib import human
+from netlib import strutils
+
+
+def indent(n, text):
+ l = str(text).strip().splitlines()
+ pad = " " * n
+ return "\n".join(pad + i for i in l)
+
+
+class Dumper:
+ def __init__(self):
+ self.filter = None # type: flowfilter.TFilter
+ self.flow_detail = None # type: int
+ self.outfp = None # type: typing.io.TextIO
+ self.showhost = None # type: bool
+
+ def configure(self, options, updated):
+ if options.filtstr:
+ self.filter = flowfilter.parse(options.filtstr)
+ if not self.filter:
+ raise exceptions.OptionsError(
+ "Invalid filter expression: %s" % options.filtstr
+ )
+ else:
+ self.filter = None
+ self.flow_detail = options.flow_detail
+ self.outfp = options.tfile
+ self.showhost = options.showhost
+
+ def echo(self, text, ident=None, **style):
+ if ident:
+ text = indent(ident, text)
+ click.secho(text, file=self.outfp, **style)
+ if self.outfp:
+ self.outfp.flush()
+
+ def _echo_message(self, message):
+ if self.flow_detail >= 2 and hasattr(message, "headers"):
+ headers = "\r\n".join(
+ "{}: {}".format(
+ click.style(
+ strutils.bytes_to_escaped_str(k), fg="blue", bold=True
+ ),
+ click.style(
+ strutils.bytes_to_escaped_str(v), fg="blue"
+ )
+ )
+ for k, v in message.headers.fields
+ )
+ self.echo(headers, ident=4)
+ if self.flow_detail >= 3:
+ _, lines, error = contentviews.get_message_content_view(
+ contentviews.get("Auto"),
+ message
+ )
+ if error:
+ ctx.log.debug(error)
+
+ styles = dict(
+ highlight=dict(bold=True),
+ offset=dict(fg="blue"),
+ header=dict(fg="green", bold=True),
+ text=dict(fg="green")
+ )
+
+ def colorful(line):
+ yield u" " # we can already indent here
+ for (style, text) in line:
+ yield click.style(text, **styles.get(style, {}))
+
+ if self.flow_detail == 3:
+ lines_to_echo = itertools.islice(lines, 70)
+ else:
+ lines_to_echo = lines
+
+ content = u"\r\n".join(
+ u"".join(colorful(line)) for line in lines_to_echo
+ )
+ if content:
+ self.echo("")
+ self.echo(content)
+
+ if next(lines, None):
+ self.echo("(cut off)", ident=4, dim=True)
+
+ if self.flow_detail >= 2:
+ self.echo("")
+
+ def _echo_request_line(self, flow):
+ if flow.request.stickycookie:
+ stickycookie = click.style(
+ "[stickycookie] ", fg="yellow", bold=True
+ )
+ else:
+ stickycookie = ""
+
+ if flow.client_conn:
+ client = click.style(
+ strutils.escape_control_characters(
+ repr(flow.client_conn.address)
+ )
+ )
+ elif flow.request.is_replay:
+ client = click.style("[replay]", fg="yellow", bold=True)
+ else:
+ client = ""
+
+ method = flow.request.method
+ method_color = dict(
+ GET="green",
+ DELETE="red"
+ ).get(method.upper(), "magenta")
+ method = click.style(
+ strutils.escape_control_characters(method),
+ fg=method_color,
+ bold=True
+ )
+ if self.showhost:
+ url = flow.request.pretty_url
+ else:
+ url = flow.request.url
+ if len(url) > 200:
+ url = url[:199] + "…"
+ url = click.style(strutils.escape_control_characters(url), bold=True)
+
+ http_version = ""
+ if flow.request.http_version not in ("HTTP/1.1", "HTTP/1.0"):
+ # We hide "normal" HTTP 1.
+ http_version = " " + flow.request.http_version
+
+ if self.flow_detail >= 2:
+ linebreak = "\n "
+ else:
+ linebreak = ""
+
+ line = "{client}: {linebreak}{stickycookie}{method} {url}{http_version}".format(
+ client=client,
+ stickycookie=stickycookie,
+ linebreak=linebreak,
+ method=method,
+ url=url,
+ http_version=http_version
+ )
+ self.echo(line)
+
+ def _echo_response_line(self, flow):
+ if flow.response.is_replay:
+ replay = click.style("[replay] ", fg="yellow", bold=True)
+ else:
+ replay = ""
+
+ code = flow.response.status_code
+ code_color = None
+ if 200 <= code < 300:
+ code_color = "green"
+ elif 300 <= code < 400:
+ code_color = "magenta"
+ elif 400 <= code < 600:
+ code_color = "red"
+ code = click.style(
+ str(code),
+ fg=code_color,
+ bold=True,
+ blink=(code == 418)
+ )
+ reason = click.style(
+ strutils.escape_control_characters(flow.response.reason),
+ fg=code_color,
+ bold=True
+ )
+
+ if flow.response.raw_content is None:
+ size = "(content missing)"
+ else:
+ size = human.pretty_size(len(flow.response.raw_content))
+ size = click.style(size, bold=True)
+
+ arrows = click.style(" <<", bold=True)
+ if self.flow_detail == 1:
+ # This aligns the HTTP response code with the HTTP request method:
+ # 127.0.0.1:59519: GET http://example.com/
+ # << 304 Not Modified 0b
+ arrows = " " * (len(repr(flow.client_conn.address)) - 2) + arrows
+
+ line = "{replay}{arrows} {code} {reason} {size}".format(
+ replay=replay,
+ arrows=arrows,
+ code=code,
+ reason=reason,
+ size=size
+ )
+ self.echo(line)
+
+ def echo_flow(self, f):
+ if f.request:
+ self._echo_request_line(f)
+ self._echo_message(f.request)
+
+ if f.response:
+ self._echo_response_line(f)
+ self._echo_message(f.response)
+
+ if f.error:
+ msg = strutils.escape_control_characters(f.error.msg)
+ self.echo(" << {}".format(msg), bold=True, fg="red")
+
+ def match(self, f):
+ if self.flow_detail == 0:
+ return False
+ if not self.filter:
+ return True
+ elif flowfilter.match(self.filter, f):
+ return True
+ return False
+
+ def response(self, f):
+ if self.match(f):
+ self.echo_flow(f)
+
+ def error(self, f):
+ if self.match(f):
+ self.echo_flow(f)
+
+ def tcp_error(self, f):
+ self.echo(
+ "Error in TCP connection to {}: {}".format(
+ repr(f.server_conn.address), f.error
+ ),
+ fg="red"
+ )
+
+ def tcp_message(self, f):
+ if not self.match(f):
+ return
+ message = f.messages[-1]
+ direction = "->" if message.from_client else "<-"
+ self.echo("{client} {direction} tcp {direction} {server}".format(
+ client=repr(f.client_conn.address),
+ server=repr(f.server_conn.address),
+ direction=direction,
+ ))
+ self._echo_message(message)