diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py index 87f45976b..dcac6b82e 100644 --- a/mitmproxy/addons/dumper.py +++ b/mitmproxy/addons/dumper.py @@ -27,9 +27,10 @@ def colorful(line, styles): class Dumper: - def __init__(self, outfile=sys.stdout): + 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( @@ -70,6 +71,11 @@ class Dumper: 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) @@ -243,7 +249,7 @@ class Dumper: self.echo_flow(f) def websocket_error(self, f): - self.echo( + self.echo_error( "Error in WebSocket connection to {}: {}".format( human.format_address(f.server_conn.address), f.error ), @@ -268,7 +274,7 @@ class Dumper: f.close_reason)) def tcp_error(self, f): - self.echo( + self.echo_error( "Error in TCP connection to {}: {}".format( human.format_address(f.server_conn.address), f.error ), diff --git a/test/mitmproxy/addons/test_dumper.py b/test/mitmproxy/addons/test_dumper.py index c24801e4b..7a41c7b97 100644 --- a/test/mitmproxy/addons/test_dumper.py +++ b/test/mitmproxy/addons/test_dumper.py @@ -32,37 +32,50 @@ def test_configure(): def test_simple(): sio = io.StringIO() - d = dumper.Dumper(sio) + sio_err = io.StringIO() + d = dumper.Dumper(sio, sio_err) with taddons.context(d) as ctx: ctx.configure(d, flow_detail=0) d.response(tflow.tflow(resp=True)) assert not sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=1) d.response(tflow.tflow(resp=True)) assert sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=1) d.error(tflow.tflow(err=True)) assert sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=4) d.response(tflow.tflow(resp=True)) assert sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=4) d.response(tflow.tflow(resp=True)) assert "<<" in sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=4) d.response(tflow.tflow(err=True)) assert "<<" in sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=4) flow = tflow.tflow() @@ -75,6 +88,8 @@ def test_simple(): d.response(flow) assert sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=4) flow = tflow.tflow(resp=tutils.tresp(content=b"{")) @@ -83,6 +98,8 @@ def test_simple(): d.response(flow) assert sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) ctx.configure(d, flow_detail=4) flow = tflow.tflow() @@ -92,6 +109,8 @@ def test_simple(): d.response(flow) assert "content missing" in sio.getvalue() sio.truncate(0) + assert not sio_err.getvalue() + sio_err.truncate(0) def test_echo_body(): @@ -100,7 +119,8 @@ def test_echo_body(): f.response.content = b"foo bar voing\n" * 100 sio = io.StringIO() - d = dumper.Dumper(sio) + sio_err = io.StringIO() + d = dumper.Dumper(sio, sio_err) with taddons.context(d) as ctx: ctx.configure(d, flow_detail=3) d._echo_message(f.response) @@ -110,7 +130,8 @@ def test_echo_body(): def test_echo_request_line(): sio = io.StringIO() - d = dumper.Dumper(sio) + sio_err = io.StringIO() + d = dumper.Dumper(sio, sio_err) with taddons.context(d) as ctx: ctx.configure(d, flow_detail=3, showhost=True) f = tflow.tflow(client_conn=None, server_conn=True, resp=True) @@ -146,7 +167,8 @@ class TestContentView: with mock.patch("mitmproxy.contentviews.auto.ViewAuto.__call__") as va: va.side_effect = exceptions.ContentViewException("") sio = io.StringIO() - d = dumper.Dumper(sio) + sio_err = io.StringIO() + d = dumper.Dumper(sio, sio_err) with taddons.context(d) as ctx: ctx.configure(d, flow_detail=4) d.response(tflow.tflow()) @@ -155,7 +177,8 @@ class TestContentView: def test_tcp(): sio = io.StringIO() - d = dumper.Dumper(sio) + sio_err = io.StringIO() + d = dumper.Dumper(sio, sio_err) with taddons.context(d) as ctx: ctx.configure(d, flow_detail=3, showhost=True) f = tflow.ttcpflow() @@ -165,12 +188,13 @@ def test_tcp(): f = tflow.ttcpflow(client_conn=True, err=True) d.tcp_error(f) - assert "Error in TCP" in sio.getvalue() + assert "Error in TCP" in sio_err.getvalue() def test_websocket(): sio = io.StringIO() - d = dumper.Dumper(sio) + sio_err = io.StringIO() + d = dumper.Dumper(sio, sio_err) with taddons.context(d) as ctx: ctx.configure(d, flow_detail=3, showhost=True) f = tflow.twebsocketflow() @@ -183,4 +207,4 @@ def test_websocket(): f = tflow.twebsocketflow(client_conn=True, err=True) d.websocket_error(f) - assert "Error in WebSocket" in sio.getvalue() + assert "Error in WebSocket" in sio_err.getvalue()