def test_upgrade(tctx): """Test a HTTP -> WebSocket upgrade""" tctx.server.address = ("example.com", 80) tctx.server.state = ConnectionState.OPEN flow = Placeholder(HTTPFlow) 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(flow) >> reply() << http.HttpRequestHook(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(flow) >> reply() << http.HttpResponseHook(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") >> DataReceived( tctx.client, masked_bytes(b"\x81\x0bhello again")) << websocket.WebsocketMessageHook(flow) >> reply() << SendData( tctx.server, masked(b"\x81\x0bhello again"))) assert len(flow().websocket.messages) == 3 assert flow().websocket.messages[0].content == b"hello world" assert flow().websocket.messages[0].from_client assert flow().websocket.messages[0].type == Opcode.TEXT assert flow().websocket.messages[1].content == b"hello back" assert flow().websocket.messages[1].from_client is False assert flow().websocket.messages[1].type == Opcode.BINARY
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 test_socks5_auth_fail(client_greeting: bytes, server_choice: bytes, client_auth: bytes, err: bytes, msg: str, tctx: Context): ProxyAuth().load(tctx.options) tctx.options.proxyauth = "user:password" playbook = (Playbook(modes.Socks5Proxy(tctx), logs=True) >> DataReceived( tctx.client, client_greeting)) if server_choice is None: playbook << SendData(tctx.client, err) else: playbook << SendData(tctx.client, server_choice) playbook >> DataReceived(tctx.client, client_auth) playbook << modes.Socks5AuthHook(Placeholder(modes.Socks5AuthData)) playbook >> reply() playbook << SendData(tctx.client, err) playbook << CloseConnection(tctx.client) playbook << Log(msg) assert playbook
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_tunnel_openconnection_error(tctx: Context): server = Server(("proxy", 1234)) tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert ( playbook << Log("Got start. Server state: CLOSED") >> DataReceived(tctx.client, b"open") << OpenConnection(server) ) assert tl.tunnel_state is tunnel.TunnelState.ESTABLISHING assert ( playbook >> reply("IPoAC packet dropped.") << Log("Opened: err='IPoAC packet dropped.'. Server state: CLOSED") ) assert tl.tunnel_state is tunnel.TunnelState.CLOSED
def test_no_data_on_closed_stream(self, tctx): frame_factory = FrameFactory() req = Request.make("GET", "http://example.com/") resp = { ":status": 200 } assert ( Playbook(Http2Client(tctx)) << SendData(tctx.server, Placeholder(bytes)) # preamble + initial settings frame >> DataReceived(tctx.server, frame_factory.build_settings_frame({}, ack=True).serialize()) >> http.RequestHeaders(1, req, end_stream=True) << SendData(tctx.server, b"\x00\x00\x06\x01\x05\x00\x00\x00\x01\x82\x86\x84\\\x81\x07") >> http.RequestEndOfMessage(1) >> DataReceived(tctx.server, frame_factory.build_headers_frame(resp).serialize()) << http.ReceiveHttp(Placeholder(http.ResponseHeaders)) >> http.RequestProtocolError(1, "cancelled", code=status_codes.CLIENT_CLOSED_REQUEST) << SendData(tctx.server, frame_factory.build_rst_stream_frame(1, ErrorCodes.CANCEL).serialize()) >> DataReceived(tctx.server, frame_factory.build_data_frame(b"foo").serialize()) << SendData(tctx.server, frame_factory.build_rst_stream_frame(1, ErrorCodes.STREAM_CLOSED).serialize()) ) # important: no ResponseData event here!
def test_connect(self, tctx, pipeline): playbook = Playbook(Http1Server(tctx)) ( playbook >> DataReceived(tctx.client, b"CONNECT example.com:443 HTTP/1.1\r\n" b"content-length: 0\r\n" b"\r\n" + (b"some plain tcp" if pipeline else b"")) << ReceiveHttp(Placeholder(RequestHeaders)) # << ReceiveHttp(RequestEndOfMessage(1)) >> ResponseHeaders(1, http.Response.make(200)) << SendData(tctx.client, b'HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n') >> ResponseEndOfMessage(1) ) if not pipeline: playbook >> DataReceived(tctx.client, b"some plain tcp") assert (playbook << ReceiveHttp(RequestData(1, b"some plain tcp")) )
def test_upgrade_denied(self, tctx): req = http.Request.make("GET", "http://example.com/ws", headers={ "Connection": "Upgrade", "Upgrade": "websocket", }) resp = Placeholder(ResponseHeaders) playbook = Playbook(Http1Client(tctx)) assert ( playbook >> RequestHeaders(1, req, True) << SendData(tctx.server, b"GET /ws HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\ncontent-length: 0\r\n\r\n") >> RequestEndOfMessage(1) >> DataReceived(tctx.server, b"HTTP/1.1 200 Ok\r\ncontent-length: 0\r\n\r\n") << ReceiveHttp(resp) << ReceiveHttp(ResponseEndOfMessage(1)) >> RequestHeaders(3, req, True) << SendData(tctx.server, Placeholder(bytes)) )
def test_upgrade_streamed(tctx): """If the HTTP response is streamed, we may get early data from the client.""" tctx.server.address = ("example.com", 80) tctx.server.state = ConnectionState.OPEN flow = Placeholder(HTTPFlow) def enable_streaming(flow: HTTPFlow): flow.response.stream = True 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(flow) >> reply() << http.HttpRequestHook(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(flow) >> reply(side_effect=enable_streaming) << 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") << http.HttpResponseHook(flow) >> DataReceived( tctx.client, masked_bytes(b"\x81\x0bhello world")) # early !! >> reply(to=-2) << websocket.WebsocketStartHook(flow) >> reply() << 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") >> DataReceived( tctx.client, masked_bytes(b"\x81\x0bhello again")) << websocket.WebsocketMessageHook(flow) >> reply() << SendData( tctx.server, masked(b"\x81\x0bhello again")))
def test_socks5_auth_success(client_greeting: bytes, server_choice: bytes, client_auth: bytes, server_resp: bytes, address: bytes, packed: bytes, tctx: Context): ProxyAuth().load(tctx.options) tctx.options.proxyauth = "user:password" server = Placeholder(Server) nextlayer = Placeholder(NextLayer) playbook = (Playbook(modes.Socks5Proxy(tctx), logs=True) >> DataReceived( tctx.client, client_greeting) << SendData(tctx.client, server_choice) >> DataReceived(tctx.client, client_auth) << modes.Socks5AuthHook(Placeholder(modes.Socks5AuthData)) >> reply(side_effect=_valid_socks_auth) << SendData( tctx.client, server_resp) >> DataReceived( tctx.client, b"\x05\x01\x00" + packed + b"\x12\x34applicationdata") << OpenConnection(server) >> reply(None) << SendData( tctx.client, b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00") << NextLayerHook(nextlayer)) assert playbook assert server().address == (address, 0x1234) assert nextlayer().data_client() == b"applicationdata"
def test_upgrade(self, tctx): req = http.Request.make("GET", "http://example.com/ws", headers={ "Connection": "Upgrade", "Upgrade": "websocket", }) resp = Placeholder(ResponseHeaders) playbook = Playbook(Http1Client(tctx)) assert (playbook >> RequestHeaders(1, req, True) << SendData( tctx.server, b"GET /ws HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\ncontent-length: 0\r\n\r\n" ) >> RequestEndOfMessage(1) >> DataReceived( tctx.server, b"HTTP/1.1 101 Switching Protocols\r\ncontent-length: 0\r\n\r\nhello" ) << ReceiveHttp(resp) << ReceiveHttp(ResponseEndOfMessage(1)) << ReceiveHttp(ResponseData(1, b"hello")) # no we can send plain data >> RequestData(1, b"some more websockets") << SendData( tctx.server, b"some more websockets"))
def test_simple(self, tctx, pipeline): hdrs1 = Placeholder(RequestHeaders) hdrs2 = Placeholder(RequestHeaders) req2 = (b"GET http://example.com/two HTTP/1.1\r\n" b"Host: example.com\r\n" b"\r\n") playbook = Playbook(Http1Server(tctx)) (playbook >> DataReceived( tctx.client, b"POST http://example.com/one HTTP/1.1\r\n" b"Content-Length: 3\r\n" b"\r\n" b"abc" + (req2 if pipeline else b"")) << ReceiveHttp(hdrs1) << ReceiveHttp( RequestData(1, b"abc")) << ReceiveHttp(RequestEndOfMessage(1)) >> ResponseHeaders(1, http.Response.make(200)) << SendData( tctx.client, b'HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n') >> ResponseEndOfMessage(1)) if not pipeline: playbook >> DataReceived(tctx.client, req2) assert (playbook << ReceiveHttp(hdrs2) << ReceiveHttp( RequestEndOfMessage(3)))
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_upgrade(self, tctx, pipeline): playbook = Playbook(Http1Server(tctx)) ( playbook >> DataReceived(tctx.client, b"POST http://example.com/one HTTP/1.1\r\n" b"Connection: Upgrade\r\n" b"Upgrade: websocket\r\n" b"\r\n" + (b"some websockets" if pipeline else b"")) << ReceiveHttp(Placeholder(RequestHeaders)) << ReceiveHttp(RequestEndOfMessage(1)) >> ResponseHeaders(1, http.Response.make(101)) << SendData(tctx.client, b'HTTP/1.1 101 Switching Protocols\r\ncontent-length: 0\r\n\r\n') >> ResponseEndOfMessage(1) ) if not pipeline: playbook >> DataReceived(tctx.client, b"some websockets") assert (playbook << ReceiveHttp(RequestData(1, b"some websockets")) )
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_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 test_upgrade(tctx, proto): """Test a HTTP -> WebSocket upgrade with different protocols enabled""" if proto != "websocket": tctx.options.websocket = False if proto != "tcp": tctx.options.rawtcp = False tctx.server.address = ("example.com", 80) tctx.server.state = ConnectionState.OPEN http_flow = Placeholder(HTTPFlow) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.transparent)) (playbook >> 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")) if proto == "websocket": assert playbook << WebsocketStartHook(http_flow) elif proto == "tcp": assert playbook << TcpStartHook(Placeholder(TCPFlow)) else: assert (playbook << Log( "Sent HTTP 101 response, but no protocol is enabled to upgrade to.", "warn") << CloseConnection(tctx.client))
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_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_disconnect_during_handshake_command(tctx: Context, disconnect): server = Server(("proxy", 1234)) tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert (playbook << Log("Got start. Server state: CLOSED") >> DataReceived( tctx.client, b"client-hello") << SendData( tctx.client, b"client-hello-reply") >> DataReceived( tctx.client, b"open") << OpenConnection(server) >> reply(None) << SendData(server, b"handshake-hello")) if disconnect == "client": assert (playbook >> ConnectionClosed(tctx.client) >> ConnectionClosed( server) # proxyserver will cancel all other connections as well. << CloseConnection(server) << Log("Opened: err='connection closed'. Server state: CLOSED") << Log("Got client close.") << CloseConnection(tctx.client)) else: assert ( playbook >> ConnectionClosed(server) << CloseConnection(server) << Log("Opened: err='connection closed'. Server state: CLOSED"))
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_simple(self, tctx, pipeline): req = http.Request.make("GET", "http://example.com/") resp = Placeholder(ResponseHeaders) playbook = Playbook(Http1Client(tctx)) (playbook >> RequestHeaders(1, req, True) << SendData( tctx.server, b"GET / HTTP/1.1\r\ncontent-length: 0\r\n\r\n") >> RequestEndOfMessage(1)) if pipeline: with pytest.raises( AssertionError, match="assert self.stream_id == event.stream_id"): assert (playbook >> RequestHeaders(3, req, True)) return assert ( playbook >> DataReceived( tctx.server, b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n") << ReceiveHttp(resp) << ReceiveHttp(ResponseEndOfMessage(1)) # no we can send the next request >> RequestHeaders(3, req, True) << SendData( tctx.server, b"GET / HTTP/1.1\r\ncontent-length: 0\r\n\r\n")) assert resp().response.status_code == 200
def test_dont_reuse_closed(tctx): """Test that a closed connection is not reused.""" server = Placeholder(Server) server2 = 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\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") >> ConnectionClosed(server) << CloseConnection(server) >> DataReceived( tctx.client, b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n" ) << OpenConnection(server2) >> reply(None) << SendData( server2, b"GET /two 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"))
def test_transparent_tcp(tctx: Context, monkeypatch, connection_strategy): monkeypatch.setattr(platform, "original_addr", lambda sock: ("address", 22)) flow = Placeholder(TCPFlow) tctx.options.connection_strategy = connection_strategy sock = object() playbook = Playbook(modes.TransparentProxy(tctx)) (playbook << GetSocket(tctx.client) >> reply(sock)) if connection_strategy == "lazy": assert playbook else: assert (playbook << OpenConnection(tctx.server) >> reply(None) >> DataReceived(tctx.server, b"hello") << NextLayerHook( Placeholder(NextLayer)) >> reply_next_layer(tcp.TCPLayer) << TcpStartHook(flow) >> reply() << TcpMessageHook(flow) >> reply() << SendData(tctx.client, b"hello")) assert flow().messages[0].content == b"hello" assert not flow().messages[0].from_client assert tctx.server.address == ("address", 22)
def test_tunnel_default_impls(tctx: Context): """ Some tunnels don't need certain features, so the default behaviour should be to be transparent. """ server = Server(None) server.state = ConnectionState.OPEN tl = tunnel.TunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert (playbook << Log("Got start. Server state: OPEN") >> DataReceived( server, b"server-hello") << SendData(server, b"server-hello-reply")) assert tl.tunnel_state is tunnel.TunnelState.OPEN assert (playbook >> ConnectionClosed(server) << Log("Got server close.") << CloseConnection(server)) assert tl.tunnel_state is tunnel.TunnelState.CLOSED assert ( playbook >> DataReceived( tctx.client, b"open") << OpenConnection(server) >> reply(None) << Log("Opened: err=None. Server state: OPEN") >> DataReceived( server, b"half-close") << CloseConnection(server, half_close=True))
def test_reverse_proxy_tcp_over_tls(tctx: Context, monkeypatch, patch, connection_strategy): """ Test client --TCP-- mitmproxy --TCP over TLS-- server reverse proxying. """ if patch: monkeypatch.setattr(tls, "ServerTLSLayer", tls.MockTLSLayer) flow = Placeholder(TCPFlow) data = Placeholder(bytes) tctx.options.mode = "reverse:https://localhost:8000" tctx.options.connection_strategy = connection_strategy playbook = Playbook(modes.ReverseProxy(tctx)) if connection_strategy == "eager": (playbook << OpenConnection(tctx.server) >> DataReceived( tctx.client, b"\x01\x02\x03") >> reply( None, to=OpenConnection(tctx.server))) else: (playbook >> DataReceived(tctx.client, b"\x01\x02\x03")) if patch: (playbook << NextLayerHook(Placeholder(NextLayer)) >> reply_next_layer( tcp.TCPLayer) << TcpStartHook(flow) >> reply()) if connection_strategy == "lazy": (playbook << OpenConnection(tctx.server) >> reply(None)) assert (playbook << TcpMessageHook(flow) >> reply() << SendData( tctx.server, data)) assert data() == b"\x01\x02\x03" else: if connection_strategy == "lazy": (playbook << NextLayerHook(Placeholder(NextLayer)) >> reply_next_layer(tcp.TCPLayer) << TcpStartHook(flow) >> reply() << OpenConnection(tctx.server) >> reply(None)) assert (playbook << TlsStartHook(Placeholder()) >> reply_tls_start() << SendData(tctx.server, data)) assert tls.parse_client_hello(data()).sni == "localhost"
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_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_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) )