aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/dump.py
diff options
context:
space:
mode:
Diffstat (limited to 'mitmproxy/dump.py')
-rw-r--r--mitmproxy/dump.py314
1 files changed, 38 insertions, 276 deletions
diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py
index cc6896ed..eaa368a0 100644
--- a/mitmproxy/dump.py
+++ b/mitmproxy/dump.py
@@ -1,75 +1,50 @@
from __future__ import absolute_import, print_function, division
-import itertools
import sys
-import traceback
+
+from typing import Optional # noqa
+import typing # noqa
import click
-from mitmproxy import contentviews
from mitmproxy import controller
from mitmproxy import exceptions
-from mitmproxy import filt
from mitmproxy import flow
-from netlib import human
+from mitmproxy import builtins
+from mitmproxy import utils
+from mitmproxy.builtins import dumper
from netlib import tcp
-from netlib import strutils
class DumpError(Exception):
pass
-class Options(object):
- attributes = [
- "app",
- "app_host",
- "app_port",
- "anticache",
- "anticomp",
- "client_replay",
- "filtstr",
- "flow_detail",
- "keepserving",
- "kill",
- "no_server",
- "nopop",
- "refresh_server_playback",
- "replacements",
- "rfile",
- "rheaders",
- "setheaders",
- "server_replay",
- "scripts",
- "showhost",
- "stickycookie",
- "stickyauth",
- "stream_large_bodies",
- "verbosity",
- "outfile",
- "replay_ignore_content",
- "replay_ignore_params",
- "replay_ignore_payload_params",
- "replay_ignore_host"
- ]
-
- def __init__(self, **kwargs):
- for k, v in kwargs.items():
- setattr(self, k, v)
- for i in self.attributes:
- if not hasattr(self, i):
- setattr(self, i, None)
+class Options(flow.options.Options):
+ def __init__(
+ self,
+ keepserving=False, # type: bool
+ filtstr=None, # type: Optional[str]
+ flow_detail=1, # type: int
+ tfile=None, # type: Optional[typing.io.TextIO]
+ **kwargs
+ ):
+ self.filtstr = filtstr
+ self.flow_detail = flow_detail
+ self.keepserving = keepserving
+ self.tfile = tfile
+ super(Options, self).__init__(**kwargs)
class DumpMaster(flow.FlowMaster):
- def __init__(self, server, options, outfile=None):
- flow.FlowMaster.__init__(self, server, flow.State())
- self.outfile = outfile
- self.o = options
- self.anticache = options.anticache
- self.anticomp = options.anticomp
- self.showhost = options.showhost
+ def __init__(self, server, options):
+ flow.FlowMaster.__init__(self, options, server, flow.State())
+ self.has_errored = False
+ self.addons.add(*builtins.default_addons())
+ self.addons.add(dumper.Dumper())
+ # This line is just for type hinting
+ self.options = self.options # type: Options
self.replay_ignore_params = options.replay_ignore_params
self.replay_ignore_content = options.replay_ignore_content
self.replay_ignore_host = options.replay_ignore_host
@@ -83,34 +58,6 @@ class DumpMaster(flow.FlowMaster):
"HTTP/2 is disabled. Use --no-http2 to silence this warning.",
file=sys.stderr)
- if options.filtstr:
- self.filt = filt.parse(options.filtstr)
- else:
- self.filt = None
-
- if options.stickycookie:
- self.set_stickycookie(options.stickycookie)
-
- if options.stickyauth:
- self.set_stickyauth(options.stickyauth)
-
- if options.outfile:
- err = self.start_stream_to_path(
- options.outfile[0],
- options.outfile[1],
- self.filt
- )
- if err:
- raise DumpError(err)
-
- if options.replacements:
- for i in options.replacements:
- self.replacehooks.add(*i)
-
- if options.setheaders:
- for i in options.setheaders:
- self.setheaders.add(*i)
-
if options.server_replay:
self.start_server_playback(
self._readflow(options.server_replay),
@@ -129,22 +76,15 @@ class DumpMaster(flow.FlowMaster):
not options.keepserving
)
- scripts = options.scripts or []
- for command in scripts:
- try:
- self.load_script(command, use_reloader=True)
- except exceptions.ScriptException as e:
- raise DumpError(str(e))
-
if options.rfile:
try:
self.load_flows_file(options.rfile)
except exceptions.FlowReadException as v:
- self.add_event("Flow file corrupted.", "error")
+ self.add_log("Flow file corrupted.", "error")
raise DumpError(v)
- if self.o.app:
- self.start_app(self.o.app_host, self.o.app_port)
+ if self.options.app:
+ self.start_app(self.options.app_host, self.options.app_port)
def _readflow(self, paths):
"""
@@ -156,204 +96,26 @@ class DumpMaster(flow.FlowMaster):
except exceptions.FlowReadException as e:
raise DumpError(str(e))
- def add_event(self, e, level="info"):
- needed = dict(error=0, info=1, debug=2).get(level, 1)
- if self.o.verbosity >= needed:
- self.echo(
+ def add_log(self, e, level="info"):
+ if level == "error":
+ self.has_errored = True
+ if self.options.verbosity >= utils.log_tier(level):
+ click.secho(
e,
+ file=self.options.tfile,
fg="red" if level == "error" else None,
dim=(level == "debug"),
err=(level == "error")
)
- @staticmethod
- def indent(n, text):
- l = str(text).strip().splitlines()
- pad = " " * n
- return "\n".join(pad + i for i in l)
-
- def echo(self, text, indent=None, **style):
- if indent:
- text = self.indent(indent, text)
- click.secho(text, file=self.outfile, **style)
-
- def _echo_message(self, message):
- if self.o.flow_detail >= 2:
- 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, indent=4)
- if self.o.flow_detail >= 3:
- if message.content is None:
- self.echo("(content missing)", indent=4)
- elif message.content:
- self.echo("")
-
- try:
- type, lines = contentviews.get_content_view(
- contentviews.get("Auto"),
- message.content,
- headers=message.headers
- )
- except exceptions.ContentViewException:
- s = "Content viewer failed: \n" + traceback.format_exc()
- self.add_event(s, "debug")
- type, lines = contentviews.get_content_view(
- contentviews.get("Raw"),
- message.content,
- headers=message.headers
- )
-
- 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.o.flow_detail == 3:
- lines_to_echo = itertools.islice(lines, 70)
- else:
- lines_to_echo = lines
-
- lines_to_echo = list(lines_to_echo)
-
- content = u"\r\n".join(
- u"".join(colorful(line)) for line in lines_to_echo
- )
-
- self.echo(content)
- if next(lines, None):
- self.echo("(cut off)", indent=4, dim=True)
-
- if self.o.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.bytes_to_escaped_str(flow.client_conn.address.host), bold=True)
- else:
- client = click.style("[replay]", fg="yellow", bold=True)
-
- method = flow.request.method
- method_color = dict(
- GET="green",
- DELETE="red"
- ).get(method.upper(), "magenta")
- method = click.style(strutils.bytes_to_escaped_str(method), fg=method_color, bold=True)
- if self.showhost:
- url = flow.request.pretty_url
- else:
- url = flow.request.url
- url = click.style(strutils.bytes_to_escaped_str(url), bold=True)
-
- httpversion = ""
- if flow.request.http_version not in ("HTTP/1.1", "HTTP/1.0"):
- httpversion = " " + flow.request.http_version # We hide "normal" HTTP 1.
-
- line = "{stickycookie}{client} {method} {url}{httpversion}".format(
- stickycookie=stickycookie,
- client=client,
- method=method,
- url=url,
- httpversion=httpversion
- )
- 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.bytes_to_escaped_str(flow.response.reason), fg=code_color, bold=True)
-
- if flow.response.content is None:
- size = "(content missing)"
- else:
- size = human.pretty_size(len(flow.response.content))
- size = click.style(size, bold=True)
-
- arrows = click.style("<<", bold=True)
-
- 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 self.o.flow_detail == 0:
- return
-
- 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:
- self.echo(" << {}".format(f.error.msg), bold=True, fg="red")
-
- if self.outfile:
- self.outfile.flush()
-
- def _process_flow(self, f):
- if self.filt and not f.match(self.filt):
- return
-
- self.echo_flow(f)
-
@controller.handler
def request(self, f):
- f = flow.FlowMaster.request(self, f)
+ f = super(DumpMaster, self).request(f)
if f:
self.state.delete_flow(f)
return f
- @controller.handler
- def response(self, f):
- f = flow.FlowMaster.response(self, f)
- if f:
- self._process_flow(f)
- return f
-
- @controller.handler
- def error(self, f):
- flow.FlowMaster.error(self, f)
- if f:
- self._process_flow(f)
- return f
-
def run(self): # pragma: no cover
- if self.o.rfile and not self.o.keepserving:
- self.unload_scripts() # make sure to trigger script unload events.
+ if self.options.rfile and not self.options.keepserving:
return
super(DumpMaster, self).run()