mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-12-02 03:53:42 +00:00
296 lines
9.4 KiB
Python
296 lines
9.4 KiB
Python
import itertools
|
|
import sys
|
|
|
|
import click
|
|
import shutil
|
|
|
|
import typing # noqa
|
|
|
|
from mitmproxy import contentviews
|
|
from mitmproxy import ctx
|
|
from mitmproxy import exceptions
|
|
from mitmproxy import flowfilter
|
|
from mitmproxy.utils import human
|
|
from mitmproxy.utils import strutils
|
|
|
|
|
|
def indent(n: int, text: str) -> str:
|
|
l = str(text).strip().splitlines()
|
|
pad = " " * n
|
|
return "\n".join(pad + i for i in l)
|
|
|
|
|
|
def colorful(line, styles):
|
|
yield u" " # we can already indent here
|
|
for (style, text) in line:
|
|
yield click.style(text, **styles.get(style, {}))
|
|
|
|
|
|
class Dumper:
|
|
def __init__(self, outfile=sys.stdout, errfile=sys.stderr):
|
|
self.filter: flowfilter.TFilter = None
|
|
self.outfp: typing.io.TextIO = outfile
|
|
self.errfp: typing.io.TextIO = errfile
|
|
|
|
def load(self, loader):
|
|
loader.add_option(
|
|
"flow_detail", int, 1,
|
|
"""
|
|
The display detail level for flows in mitmdump: 0 (almost quiet) to 3 (very verbose).
|
|
0: shortened request URL, response status code, WebSocket and TCP message notifications.
|
|
1: full request URL with response status code
|
|
2: 1 + HTTP headers
|
|
3: 2 + full response content, content of WebSocket and TCP messages.
|
|
"""
|
|
)
|
|
loader.add_option(
|
|
"dumper_default_contentview", str, "auto",
|
|
"The default content view mode.",
|
|
choices = [i.name.lower() for i in contentviews.views]
|
|
)
|
|
loader.add_option(
|
|
"dumper_filter", typing.Optional[str], None,
|
|
"Limit which flows are dumped."
|
|
)
|
|
|
|
def configure(self, updated):
|
|
if "dumper_filter" in updated:
|
|
if ctx.options.dumper_filter:
|
|
self.filter = flowfilter.parse(ctx.options.dumper_filter)
|
|
if not self.filter:
|
|
raise exceptions.OptionsError(
|
|
"Invalid filter expression: %s" % ctx.options.dumper_filter
|
|
)
|
|
else:
|
|
self.filter = None
|
|
|
|
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_error(self, text, **style):
|
|
click.secho(text, file=self.errfp, **style)
|
|
if self.errfp:
|
|
self.errfp.flush()
|
|
|
|
def _echo_headers(self, headers):
|
|
for k, v in headers.fields:
|
|
k = strutils.bytes_to_escaped_str(k)
|
|
v = strutils.bytes_to_escaped_str(v)
|
|
out = "{}: {}".format(
|
|
click.style(k, fg="blue"),
|
|
click.style(v)
|
|
)
|
|
self.echo(out, ident=4)
|
|
|
|
def _echo_message(self, message, flow):
|
|
_, lines, error = contentviews.get_message_content_view(
|
|
ctx.options.dumper_default_contentview,
|
|
message,
|
|
flow
|
|
)
|
|
if error:
|
|
ctx.log.debug(error)
|
|
|
|
if ctx.options.flow_detail == 3:
|
|
lines_to_echo = itertools.islice(lines, 70)
|
|
else:
|
|
lines_to_echo = lines
|
|
|
|
styles = dict(
|
|
highlight=dict(bold=True),
|
|
offset=dict(fg="blue"),
|
|
header=dict(fg="green", bold=True),
|
|
text=dict(fg="green")
|
|
)
|
|
|
|
content = u"\r\n".join(
|
|
u"".join(colorful(line, styles)) 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 ctx.options.flow_detail >= 2:
|
|
self.echo("")
|
|
|
|
def _echo_request_line(self, flow):
|
|
if flow.client_conn:
|
|
client = click.style(
|
|
strutils.escape_control_characters(
|
|
human.format_address(flow.client_conn.address)
|
|
)
|
|
)
|
|
elif flow.request.is_replay:
|
|
client = click.style("[replay]", fg="yellow", bold=True)
|
|
else:
|
|
client = ""
|
|
|
|
pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else ''
|
|
method = flow.request.method + pushed
|
|
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 ctx.options.showhost:
|
|
url = flow.request.pretty_url
|
|
else:
|
|
url = flow.request.url
|
|
terminalWidthLimit = max(shutil.get_terminal_size()[0] - 25, 50)
|
|
if ctx.options.flow_detail < 1 and len(url) > terminalWidthLimit:
|
|
url = url[:terminalWidthLimit] + "…"
|
|
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
|
|
|
|
line = "{client}: {method} {url}{http_version}".format(
|
|
client=client,
|
|
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 ctx.options.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(human.format_address(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)
|
|
if ctx.options.flow_detail >= 2:
|
|
self._echo_headers(f.request.headers)
|
|
if ctx.options.flow_detail >= 3:
|
|
self._echo_message(f.request, f)
|
|
|
|
if f.response:
|
|
self._echo_response_line(f)
|
|
if ctx.options.flow_detail >= 2:
|
|
self._echo_headers(f.response.headers)
|
|
if ctx.options.flow_detail >= 3:
|
|
self._echo_message(f.response, f)
|
|
|
|
if f.error:
|
|
msg = strutils.escape_control_characters(f.error.msg)
|
|
self.echo(" << {}".format(msg), bold=True, fg="red")
|
|
|
|
def match(self, f):
|
|
if ctx.options.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 websocket_error(self, f):
|
|
self.echo_error(
|
|
"Error in WebSocket connection to {}: {}".format(
|
|
human.format_address(f.server_conn.address), f.error
|
|
),
|
|
fg="red"
|
|
)
|
|
|
|
def websocket_message(self, f):
|
|
if self.match(f):
|
|
message = f.messages[-1]
|
|
self.echo(f.message_info(message))
|
|
if ctx.options.flow_detail >= 3:
|
|
message = message.from_state(message.get_state())
|
|
message.content = message.content.encode() if isinstance(message.content, str) else message.content
|
|
self._echo_message(message, f)
|
|
|
|
def websocket_end(self, f):
|
|
if self.match(f):
|
|
self.echo("WebSocket connection closed by {}: {} {}, {}".format(
|
|
f.close_sender,
|
|
f.close_code,
|
|
f.close_message,
|
|
f.close_reason))
|
|
|
|
def tcp_error(self, f):
|
|
self.echo_error(
|
|
"Error in TCP connection to {}: {}".format(
|
|
human.format_address(f.server_conn.address), f.error
|
|
),
|
|
fg="red"
|
|
)
|
|
|
|
def tcp_message(self, f):
|
|
if self.match(f):
|
|
message = f.messages[-1]
|
|
direction = "->" if message.from_client else "<-"
|
|
self.echo("{client} {direction} tcp {direction} {server}".format(
|
|
client=human.format_address(f.client_conn.address),
|
|
server=human.format_address(f.server_conn.address),
|
|
direction=direction,
|
|
))
|
|
if ctx.options.flow_detail >= 3:
|
|
self._echo_message(message, f)
|