def test_disconnect_while_intercept(tctx): """Test a server disconnect while a request is intercepted.""" tctx.options.connection_strategy = "eager" server1 = Placeholder(Server) server2 = Placeholder(Server) flow = Placeholder(HTTPFlow) assert ( Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) >> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") << http.HttpConnectHook(Placeholder(HTTPFlow)) >> reply() << OpenConnection(server1) >> reply(None) << SendData( tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') >> DataReceived( tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << http.HttpRequestHook(flow) >> ConnectionClosed(server1) << CloseConnection(server1) >> reply(to=-3) << OpenConnection(server2) >> reply(None) << SendData( server2, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived( server2, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")) assert server1() != server2() assert flow().server_conn == server2()
def test_https_proxy(strategy, tctx): """Test a CONNECT request, followed by a HTTP GET /""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) tctx.options.connection_strategy = strategy (playbook >> DataReceived(tctx.client, b"CONNECT example.proxy:80 HTTP/1.1\r\n\r\n") << http.HttpConnectHook(Placeholder()) >> reply()) if strategy == "eager": (playbook << OpenConnection(server) >> reply(None)) (playbook << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') >> DataReceived(tctx.client, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply()) if strategy == "lazy": (playbook << OpenConnection(server) >> reply(None)) (playbook << SendData( server, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!") << http.HttpResponseHeadersHook(flow) >> reply() << http.HttpResponseHook(flow) >> reply() << SendData( tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!")) assert playbook
def test_proxy_chain(tctx, strategy): server = Placeholder(Server) tctx.options.connection_strategy = strategy playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) playbook >> DataReceived(tctx.client, b"CONNECT proxy:8080 HTTP/1.1\r\n\r\n") if strategy == "eager": playbook << OpenConnection(server) playbook >> reply(None) playbook << SendData(tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n") playbook >> DataReceived(tctx.client, b"CONNECT second-proxy:8080 HTTP/1.1\r\n\r\n") playbook << layer.NextLayerHook(Placeholder()) playbook >> reply_next_layer( lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) playbook << SendData( tctx.client, b"HTTP/1.1 502 Bad Gateway\r\n" b"content-length: 198\r\n" b"\r\n" b"mitmproxy received an HTTP CONNECT request even though it is not running in regular/upstream mode. " b"This usually indicates a misconfiguration, please see the mitmproxy mode documentation for details." ) assert playbook
def test_redirect(strategy, https_server, https_client, tctx, monkeypatch): """Test redirects between http:// and https:// in regular proxy mode.""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) tctx.options.connection_strategy = strategy p = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) if https_server: monkeypatch.setattr(tls, "ServerTLSLayer", tls.MockTLSLayer) def redirect(flow: HTTPFlow): if https_server: flow.request.url = "https://redirected.site/" else: flow.request.url = "http://redirected.site/" if https_client: p >> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") if strategy == "eager": p << OpenConnection(Placeholder()) p >> reply(None) p << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') p >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") p << layer.NextLayerHook(Placeholder()) p >> reply_next_layer( lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) else: p >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") p << http.HttpRequestHook(flow) p >> reply(side_effect=redirect) p << OpenConnection(server) p >> reply(None) p << SendData(server, b"GET / HTTP/1.1\r\nHost: redirected.site\r\n\r\n") p >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!") p << SendData( tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!") assert p if https_server: assert server().address == ("redirected.site", 443) else: assert server().address == ("redirected.site", 80)
def test_server_unreachable(tctx, connect): """Test the scenario where the target server is unreachable.""" tctx.options.connection_strategy = "eager" server = Placeholder(Server) flow = Placeholder(HTTPFlow) err = Placeholder(bytes) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) if connect: playbook >> DataReceived(tctx.client, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n") else: playbook >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\n\r\n") playbook << OpenConnection(server) playbook >> reply("Connection failed") if not connect: # Our API isn't ideal here, there is no error hook for CONNECT requests currently. # We could fix this either by having CONNECT request go through all our regular hooks, # or by adding dedicated ok/error hooks. playbook << http.HttpErrorHook(flow) playbook >> reply() playbook << SendData(tctx.client, err) if not connect: playbook << CloseConnection(tctx.client) assert playbook if not connect: assert flow().error assert b"502 Bad Gateway" in err() assert b"Connection failed" in err()
def test_http_client_aborts(tctx, stream): """Test handling of the case where a client aborts during request transmission.""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=True) def enable_streaming(flow: HTTPFlow): flow.request.stream = True assert (playbook >> DataReceived( tctx.client, b"POST http://example.com/ HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 6\r\n" b"\r\n" b"abc") << http.HttpRequestHeadersHook(flow)) if stream: assert (playbook >> reply(side_effect=enable_streaming) << http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >> reply(None) << SendData( server, b"POST / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 6\r\n" b"\r\n" b"abc")) else: assert playbook >> reply() (playbook >> ConnectionClosed(tctx.client) << CloseConnection(tctx.client)) if stream: playbook << CloseConnection(server) assert (playbook << http.HttpErrorHook(flow) >> reply() << None) assert "peer closed connection" in flow().error.msg
def test_pipelining(tctx, transfer_encoding): """Test that multiple requests can be processed over the same connection""" tctx.server.address = ("example.com", 80) tctx.server.state = ConnectionState.OPEN req = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" if transfer_encoding == "identity": resp = (b"HTTP/1.1 200 OK\r\n" b"Content-Length: 12\r\n" b"\r\n" b"Hello World!") else: resp = (b"HTTP/1.1 200 OK\r\n" b"Transfer-Encoding: chunked\r\n" b"\r\n" b"c\r\n" b"Hello World!\r\n" b"0\r\n" b"\r\n") assert (Playbook(http.HttpLayer(tctx, HTTPMode.transparent), hooks=False) # Roundtrip 1 >> DataReceived(tctx.client, req) << SendData(tctx.server, req) >> DataReceived(tctx.server, resp) << SendData(tctx.client, resp) # Roundtrip 2 >> DataReceived(tctx.client, req) << SendData(tctx.server, req) >> DataReceived(tctx.server, resp) << SendData(tctx.client, resp))
def test_h1_to_h2(tctx): """Test HTTP/1 -> HTTP/2 request translation""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) conf = h2.config.H2Configuration(client_side=False) conn = h2.connection.H2Connection(conf) conn.initiate_connection() request = Placeholder(bytes) assert (playbook >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >> reply(None, side_effect=make_h2) << SendData(server, request)) events = conn.receive_data(request()) assert event_types(events) == [ h2.events.RemoteSettingsChanged, h2.events.RequestReceived, h2.events.StreamEnded ] conn.send_headers(1, example_response_headers) conn.send_data(1, b"Hello World!", end_stream=True) settings_ack = Placeholder(bytes) assert ( playbook >> DataReceived(server, conn.data_to_send()) << http.HttpResponseHeadersHook(flow) << SendData(server, settings_ack) >> reply(to=-2) << http.HttpResponseHook(flow) >> reply() << SendData( tctx.client, b"HTTP/1.1 200 OK\r\n\r\nHello World!") << CloseConnection(tctx.client)) assert settings_ack() == b'\x00\x00\x00\x04\x01\x00\x00\x00\x00'
def test_http_proxy_relative_request_no_host_header(tctx): """Test handling of a relative-form "GET /" in regular proxy mode, but without a host header.""" assert (Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\n\r\n") << SendData( tctx.client, b"HTTP/1.1 400 Bad Request\r\n" b"content-length: 53\r\n" b"\r\n" b"HTTP request has no host header, destination unknown."))
def test_close_during_connect_hook(tctx): flow = Placeholder(HTTPFlow) assert (Playbook(http.HttpLayer(tctx, HTTPMode.regular)) >> DataReceived( tctx.client, b'CONNECT hi.ls:443 HTTP/1.1\r\n' b'Proxy-Connection: keep-alive\r\n' b'Connection: keep-alive\r\n' b'Host: hi.ls:443\r\n\r\n') << http.HttpConnectHook(flow) >> ConnectionClosed(tctx.client) << CloseConnection( tctx.client) >> reply(to=-3))
def test_no_headers(tctx): """Test that we can correctly reassemble requests/responses with no headers.""" server = Placeholder(Server) assert ( Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\n\r\n") << OpenConnection(server) >> reply(None) << SendData( server, b"GET / HTTP/1.1\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 204 No Content\r\n\r\n") << SendData( tctx.client, b"HTTP/1.1 204 No Content\r\n\r\n")) assert server().address == ("example.com", 80)
def test_fuzz_h1_request(data): tctx = context.Context( context.Client(("client", 1234), ("127.0.0.1", 8080), 1605699329), opts) layer = http.HttpLayer(tctx, HTTPMode.regular) for _ in layer.handle_event(Start()): pass for chunk in data: for _ in layer.handle_event(DataReceived(tctx.client, chunk)): pass
def test_http_proxy_relative_request(tctx): """Test handling of a relative-form "GET /" in regular proxy mode.""" server = Placeholder(Server) assert (Playbook(http.HttpLayer( tctx, HTTPMode.regular), hooks=False) >> DataReceived( tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") << OpenConnection(server) >> reply(None) << SendData( server, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(server, b"HTTP/1.1 204 No Content\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 204 No Content\r\n\r\n")) assert server().address == ("example.com", 80)
def start_h2_client(tctx: Context) -> Tuple[Playbook, FrameFactory]: tctx.client.alpn = b"h2" frame_factory = FrameFactory() playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) assert ( playbook << SendData(tctx.client, Placeholder()) # initial settings frame >> DataReceived(tctx.client, frame_factory.preamble()) >> DataReceived( tctx.client, frame_factory.build_settings_frame({}, ack=True).serialize())) return playbook, frame_factory
def test_http_proxy_tcp(tctx, mode, close_first): """Test TCP over HTTP CONNECT.""" server = Placeholder(Server) if mode == "upstream": tctx.options.mode = "upstream:http://proxy:8080" toplayer = http.HttpLayer(tctx, HTTPMode.upstream) else: tctx.options.mode = "regular" toplayer = http.HttpLayer(tctx, HTTPMode.regular) playbook = Playbook(toplayer, hooks=False) assert (playbook >> DataReceived( tctx.client, b"CONNECT example:443 HTTP/1.1\r\n\r\n") << SendData( tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n") >> DataReceived(tctx.client, b"this is not http") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: TCPLayer(ctx, ignore=True)) << OpenConnection(server)) playbook >> reply(None) if mode == "upstream": playbook << SendData(server, b"CONNECT example:443 HTTP/1.1\r\n\r\n") playbook >> DataReceived( server, b"HTTP/1.1 200 Connection established\r\n\r\n") assert (playbook << SendData(server, b"this is not http") >> DataReceived( server, b"true that") << SendData(tctx.client, b"true that")) if mode == "regular": assert server().address == ("example", 443) else: assert server().address == ("proxy", 8080) if close_first == "client": a, b = tctx.client, server else: a, b = server, tctx.client assert (playbook >> ConnectionClosed(a) << CloseConnection(b) >> ConnectionClosed(b) << CloseConnection(a))
def h2_layer(opts): tctx = context.Context( context.Client(("client", 1234), ("127.0.0.1", 8080), 1605699329), opts) tctx.client.alpn = b"h2" layer = http.HttpLayer(tctx, HTTPMode.regular) for _ in layer.handle_event(Start()): pass for _ in layer.handle_event( DataReceived(tctx.client, b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n')): pass return tctx, layer
def test_http_reply_from_proxy(tctx): """Test a response served by mitmproxy itself.""" def reply_from_proxy(flow: HTTPFlow): flow.response = HTTPResponse.make(418) assert (Playbook(http.HttpLayer( tctx, HTTPMode.regular), hooks=False) >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << http.HttpRequestHook(Placeholder()) >> reply(side_effect=reply_from_proxy) << SendData( tctx.client, b"HTTP/1.1 418 I'm a teapot\r\ncontent-length: 0\r\n\r\n"))
def test_response_until_eof(tctx): """Test scenario where the server response body is terminated by EOF.""" server = Placeholder(Server) assert (Playbook(http.HttpLayer( tctx, HTTPMode.regular), hooks=False) >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << OpenConnection(server) >> reply(None) << SendData( server, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(server, b"HTTP/1.1 200 OK\r\n\r\nfoo") >> ConnectionClosed(server) << CloseConnection(server) << SendData( tctx.client, b"HTTP/1.1 200 OK\r\n\r\nfoo") << CloseConnection( tctx.client))
def _h2_response(chunks): tctx = context.Context( context.Client(("client", 1234), ("127.0.0.1", 8080), 1605699329), opts) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) server = Placeholder(context.Server) assert (playbook >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << OpenConnection(server) >> reply( None, side_effect=make_h2) << SendData(server, Placeholder())) for chunk in chunks: for _ in playbook.layer.handle_event( events.DataReceived(server(), chunk)): pass
def test_connection_close_header(tctx, client_close, server_close): """Test that we correctly close connections if we have a `Connection: close` header.""" if not client_close and not server_close: return server = Placeholder(Server) assert (Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) >> DataReceived( tctx.client, b"GET http://example/ HTTP/1.1\r\n" b"Host: example\r\n" + client_close + b"\r\n") << OpenConnection(server) >> reply(None) << SendData( server, b"GET / HTTP/1.1\r\n" b"Host: example\r\n" + client_close + b"\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\n" b"Content-Length: 0\r\n" + server_close + b"\r\n") << CloseConnection(server) << SendData( tctx.client, b"HTTP/1.1 200 OK\r\n" b"Content-Length: 0\r\n" + server_close + b"\r\n") << CloseConnection(tctx.client))
def test_server_aborts(tctx, data): """Test the scenario where the server doesn't serve a response""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) err = Placeholder(bytes) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) assert (playbook >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << OpenConnection(server) >> reply(None) << SendData( server, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")) if data: playbook >> DataReceived(server, data) assert (playbook >> ConnectionClosed(server) << CloseConnection(server) << http.HttpErrorHook(flow) >> reply() << SendData( tctx.client, err) << CloseConnection(tctx.client)) assert flow().error assert b"502 Bad Gateway" in err()
def test_http_proxy(tctx): """Test a simple HTTP GET / request""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) assert (Playbook(http.HttpLayer(tctx, HTTPMode.regular)) >> DataReceived( tctx.client, b"GET http://example.com/foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n" ) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook( flow) >> reply() << OpenConnection(server) >> reply(None) << SendData( server, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World") << http.HttpResponseHeadersHook(flow) >> reply() >> DataReceived( server, b"!") << http.HttpResponseHook(flow) >> reply() << SendData( tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!")) assert server().address == ("example.com", 80)
def h2_client(tctx: Context) -> Tuple[h2.connection.H2Connection, Playbook]: tctx.client.alpn = b"h2" playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) conn = h2.connection.H2Connection() conn.initiate_connection() server_preamble = Placeholder(bytes) assert (playbook << SendData(tctx.client, server_preamble)) assert event_types(conn.receive_data( server_preamble())) == [h2.events.RemoteSettingsChanged] settings_ack = Placeholder(bytes) assert (playbook >> DataReceived(tctx.client, conn.data_to_send()) << SendData(tctx.client, settings_ack)) assert event_types(conn.receive_data( settings_ack())) == [h2.events.SettingsAcknowledged] return conn, playbook
def test_http_expect(tctx): """Test handling of a 'Expect: 100-continue' header.""" server = Placeholder(Server) assert ( Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) >> DataReceived( tctx.client, b"PUT http://example.com/large-file HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 15\r\n" b"Expect: 100-continue\r\n\r\n") << SendData( tctx.client, b"HTTP/1.1 100 Continue\r\n\r\n") >> DataReceived( tctx.client, b"lots of content") << OpenConnection(server) >> reply(None) << SendData( server, b"PUT /large-file HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 15\r\n\r\n" b"lots of content") >> DataReceived( server, b"HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n")) assert server().address == ("example.com", 80)
def test_http_server_aborts(tctx, stream): """Test handling of the case where a server aborts during response transmission.""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) def enable_streaming(flow: HTTPFlow): flow.response.stream = True assert (playbook >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\n" b"Host: example.com\r\n\r\n") << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >> reply(None) << SendData( server, b"GET / HTTP/1.1\r\n" b"Host: example.com\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\n" b"Content-Length: 6\r\n" b"\r\n" b"abc") << http.HttpResponseHeadersHook(flow)) if stream: assert (playbook >> reply(side_effect=enable_streaming) << SendData( tctx.client, b"HTTP/1.1 200 OK\r\n" b"Content-Length: 6\r\n" b"\r\n" b"abc")) else: assert playbook >> reply() assert (playbook >> ConnectionClosed(server) << CloseConnection(server) << http.HttpErrorHook(flow)) if stream: assert (playbook >> reply() << CloseConnection(tctx.client)) else: error_html = Placeholder(bytes) assert (playbook >> reply() << SendData(tctx.client, error_html) << CloseConnection(tctx.client)) assert b"502 Bad Gateway" in error_html() assert b"peer closed connection" in error_html() assert "peer closed connection" in flow().error.msg
def test_upgrade(tctx): """Test a HTTP -> WebSocket upgrade""" tctx.server.address = ("example.com", 80) tctx.server.state = ConnectionState.OPEN http_flow = Placeholder(HTTPFlow) flow = Placeholder(WebSocketFlow) assert ( Playbook(http.HttpLayer(tctx, HTTPMode.transparent)) >> DataReceived( tctx.client, b"GET / HTTP/1.1\r\n" b"Connection: upgrade\r\n" b"Upgrade: websocket\r\n" b"Sec-WebSocket-Version: 13\r\n" b"\r\n") << http.HttpRequestHeadersHook(http_flow) >> reply() << http.HttpRequestHook(http_flow) >> reply() << SendData( tctx.server, b"GET / HTTP/1.1\r\n" b"Connection: upgrade\r\n" b"Upgrade: websocket\r\n" b"Sec-WebSocket-Version: 13\r\n" b"\r\n") >> DataReceived( tctx.server, b"HTTP/1.1 101 Switching Protocols\r\n" b"Upgrade: websocket\r\n" b"Connection: Upgrade\r\n" b"\r\n") << http.HttpResponseHeadersHook(http_flow) >> reply() << http.HttpResponseHook(http_flow) >> reply() << SendData( tctx.client, b"HTTP/1.1 101 Switching Protocols\r\n" b"Upgrade: websocket\r\n" b"Connection: Upgrade\r\n" b"\r\n") << websocket.WebsocketStartHook(flow) >> reply() >> DataReceived(tctx.client, masked_bytes(b"\x81\x0bhello world")) << websocket.WebsocketMessageHook(flow) >> reply() << SendData( tctx.server, masked(b"\x81\x0bhello world")) >> DataReceived( tctx.server, b"\x82\nhello back") << websocket.WebsocketMessageHook(flow) >> reply() << SendData( tctx.client, b"\x82\nhello back")) assert flow().handshake_flow == http_flow() assert len(flow().messages) == 2 assert flow().messages[0].content == "hello world" assert flow().messages[0].from_client assert flow().messages[1].content == b"hello back" assert flow().messages[1].from_client is False
def test_response_streaming(tctx): """Test HTTP response streaming""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) def enable_streaming(flow: HTTPFlow): flow.response.stream = lambda x: x.upper() assert (Playbook(http.HttpLayer(tctx, HTTPMode.regular)) >> DataReceived( tctx.client, b"GET http://example.com/largefile HTTP/1.1\r\nHost: example.com\r\n\r\n" ) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook( flow) >> reply() << OpenConnection(server) >> reply(None) << SendData( server, b"GET /largefile HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nabc") << http.HttpResponseHeadersHook(flow) >> reply(side_effect=enable_streaming) << SendData( tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nABC") >> DataReceived(server, b"def") << SendData( tctx.client, b"DEF") << http.HttpResponseHook(flow) >> reply())
def test_reverse_proxy(tctx, keep_host_header): """Test mitmproxy in reverse proxy mode. - make sure that we connect to the right host - make sure that we respect keep_host_header - make sure that we include non-standard ports in the host header (#4280) """ server = Placeholder(Server) tctx.options.mode = "reverse:http://localhost:8000" tctx.options.keep_host_header = keep_host_header assert ( Playbook(modes.ReverseProxy(tctx), hooks=False) >> DataReceived( tctx.client, b"GET /foo HTTP/1.1\r\n" b"Host: example.com\r\n\r\n") << NextLayerHook( Placeholder(NextLayer)) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << OpenConnection(server) >> reply(None) << SendData( server, b"GET /foo HTTP/1.1\r\n" b"Host: " + (b"example.com" if keep_host_header else b"localhost:8000") + b"\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")) assert server().address == ("localhost", 8000)
def test_multiple_server_connections(tctx): """Test multiple requests being rewritten to different targets.""" server1 = Placeholder(Server) server2 = Placeholder(Server) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) def redirect(to: str): def side_effect(flow: HTTPFlow): flow.request.url = to return side_effect assert ( playbook >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << http.HttpRequestHook(Placeholder()) >> reply(side_effect=redirect("http://one.redirect/")) << OpenConnection(server1) >> reply(None) << SendData( server1, b"GET / HTTP/1.1\r\nHost: one.redirect\r\n\r\n") >> DataReceived( server1, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")) assert ( playbook >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << http.HttpRequestHook(Placeholder()) >> reply(side_effect=redirect("http://two.redirect/")) << OpenConnection(server2) >> reply(None) << SendData( server2, b"GET / HTTP/1.1\r\nHost: two.redirect\r\n\r\n") >> DataReceived( server2, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")) assert server1().address == ("one.redirect", 80) assert server2().address == ("two.redirect", 80)
def test_upstream_https(tctx): """ Test mitmproxy in HTTPS upstream mode with another mitmproxy instance upstream. In other words: mitmdump --mode upstream:https://localhost:8081 --ssl-insecure mitmdump -p 8081 curl -x localhost:8080 -k http://example.com """ tctx1 = Context(Client(("client", 1234), ("127.0.0.1", 8080), 1605699329), copy.deepcopy(tctx.options)) tctx1.options.mode = "upstream:https://example.mitmproxy.org:8081" tctx2 = Context(Client(("client", 4321), ("127.0.0.1", 8080), 1605699329), copy.deepcopy(tctx.options)) assert tctx2.options.mode == "regular" del tctx proxy1 = Playbook(modes.HttpProxy(tctx1), hooks=False) proxy2 = Playbook(modes.HttpProxy(tctx2), hooks=False) upstream = Placeholder(Server) server = Placeholder(Server) clienthello = Placeholder(bytes) serverhello = Placeholder(bytes) request = Placeholder(bytes) tls_finished = Placeholder(bytes) h2_client_settings_ack = Placeholder(bytes) response = Placeholder(bytes) h2_server_settings_ack = Placeholder(bytes) assert ( proxy1 >> DataReceived( tctx1.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << NextLayerHook(Placeholder(NextLayer)) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.upstream)) << OpenConnection(upstream) >> reply(None) << TlsStartHook(Placeholder()) >> reply_tls_start(alpn=b"h2") << SendData(upstream, clienthello)) assert upstream().address == ("example.mitmproxy.org", 8081) assert (proxy2 >> DataReceived(tctx2.client, clienthello()) << NextLayerHook(Placeholder(NextLayer)) >> reply_next_layer(ClientTLSLayer) << TlsStartHook(Placeholder()) >> reply_tls_start(alpn=b"h2") << SendData(tctx2.client, serverhello)) assert (proxy1 >> DataReceived(upstream, serverhello()) << SendData( upstream, request)) assert (proxy2 >> DataReceived(tctx2.client, request()) << SendData( tctx2.client, tls_finished) << NextLayerHook(Placeholder(NextLayer)) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.regular)) << SendData(tctx2.client, h2_client_settings_ack) << OpenConnection(server) >> reply(None) << SendData( server, b'GET / HTTP/1.1\r\nhost: example.com\r\n\r\n') >> DataReceived(server, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << CloseConnection(server) << SendData(tctx2.client, response)) assert server().address == ("example.com", 80) assert (proxy1 >> DataReceived( upstream, tls_finished() + h2_client_settings_ack() + response()) << SendData( upstream, h2_server_settings_ack) << SendData( tctx1.client, b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n"))