Example #1
0
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
Example #2
0
def test_close_disconnect(ws_testdata):
    tctx, playbook = ws_testdata
    flow = Placeholder(WebSocketFlow)
    assert (playbook << websocket.WebsocketStartHook(flow) >> reply() >>
            ConnectionClosed(tctx.server) << CloseConnection(tctx.server) <<
            SendData(tctx.client, b"\x88\x02\x03\xe8") << CloseConnection(
                tctx.client) << websocket.WebsocketErrorHook(flow) >> reply()
            >> ConnectionClosed(tctx.client))
    assert "ABNORMAL_CLOSURE" in flow().error.msg
Example #3
0
def test_close_error(ws_testdata):
    tctx, playbook = ws_testdata
    flow = Placeholder(WebSocketFlow)
    assert (playbook << websocket.WebsocketStartHook(flow) >> reply() >>
            DataReceived(tctx.server, b"\x88\x02\x0f\xa0") << SendData(
                tctx.server, masked(b"\x88\x02\x0f\xa0")) << CloseConnection(
                    tctx.server) << SendData(tctx.client, b"\x88\x02\x0f\xa0")
            << CloseConnection(
                tctx.client) << websocket.WebsocketErrorHook(flow) >> reply())
    assert "UNKNOWN_ERROR=4000" in flow().error.msg
Example #4
0
def test_receive_data_after_half_close(tctx):
    """
    data received after the other connection has been half-closed should still be forwarded.
    """
    assert (Playbook(tcp.TCPLayer(tctx), hooks=False) <<
            OpenConnection(tctx.server) >> reply(None) >> DataReceived(
                tctx.client, b"eof-delimited-request") << SendData(
                    tctx.server, b"eof-delimited-request") >> ConnectionClosed(
                        tctx.client) <<
            CloseConnection(tctx.server, half_close=True) >> DataReceived(
                tctx.server, b"i'm late") << SendData(tctx.client, b"i'm late")
            >> ConnectionClosed(tctx.server) << CloseConnection(tctx.client))
Example #5
0
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))
Example #6
0
def test_simple(tctx):
    """open connection, receive data, send it to peer"""
    f = Placeholder(TCPFlow)

    assert (
        Playbook(tcp.TCPLayer(tctx)) << tcp.TcpStartHook(f) >> reply() <<
        OpenConnection(tctx.server) >> reply(None) >> DataReceived(
            tctx.client, b"hello!") << tcp.TcpMessageHook(f) >> reply()
        << SendData(tctx.server, b"hello!") >> DataReceived(
            tctx.server, b"hi") << tcp.TcpMessageHook(f) >> reply() <<
        SendData(tctx.client, b"hi") >> ConnectionClosed(tctx.server) <<
        CloseConnection(tctx.client, half_close=True) >> ConnectionClosed(
            tctx.client) << CloseConnection(tctx.server) << tcp.TcpEndHook(f)
        >> reply() >> ConnectionClosed(tctx.client) << None)
    assert len(f().messages) == 2
Example #7
0
 def assert_kill(err_hook: bool = True):
     playbook >> reply(side_effect=kill)
     if err_hook:
         playbook << http.HttpErrorHook(flow)
         playbook >> reply()
     playbook << CloseConnection(tctx.client)
     assert playbook
Example #8
0
def test_cancel_then_server_disconnect(tctx):
    """
    Test that we properly handle the case of the following event sequence:
        - client cancels a stream
        - we start an error hook
        - server disconnects
        - error hook completes.
    """
    playbook, cff = start_h2_client(tctx)
    flow = Placeholder(HTTPFlow)
    server = Placeholder(Server)

    assert (playbook >> DataReceived(
        tctx.client,
        cff.build_headers_frame(example_request_headers, flags=[
            "END_STREAM"
        ]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() <<
            http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >>
            reply(None) << SendData(
                server, b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') >>
            DataReceived(
                tctx.client,
                cff.build_rst_stream_frame(1, ErrorCodes.CANCEL).serialize())
            << CloseConnection(server) << http.HttpErrorHook(flow) >> reply()
            >> ConnectionClosed(server) << None)
Example #9
0
def test_rst_then_close(tctx):
    """
    Test that we properly handle the case of a client that first causes protocol errors and then disconnects.

    Adapted from h2spec http2/5.1/5.
    """
    playbook, cff = start_h2_client(tctx)
    flow = Placeholder(HTTPFlow)
    server = Placeholder(Server)

    assert (playbook >> DataReceived(
        tctx.client,
        cff.build_headers_frame(example_request_headers, flags=[
            "END_STREAM"
        ]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() <<
            http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >>
            DataReceived(
                tctx.client,
                cff.build_data_frame(b"unexpected data frame").serialize()) <<
            SendData(
                tctx.client,
                cff.build_rst_stream_frame(
                    1, ErrorCodes.STREAM_CLOSED).serialize()) >>
            ConnectionClosed(tctx.client) << CloseConnection(
                tctx.client) >> reply("connection cancelled", to=-5) <<
            http.HttpErrorHook(flow) >> reply())
    assert flow().error.msg == "connection cancelled"
Example #10
0
def test_h2_to_h1(tctx):
    """Test HTTP/2 -> HTTP/1 request translation"""
    server = Placeholder(Server)
    flow = Placeholder(HTTPFlow)

    conn, playbook = h2_client(tctx)

    conn.send_headers(1, example_request_headers, end_stream=True)
    response = Placeholder(bytes)
    assert (
        playbook >> DataReceived(tctx.client, conn.data_to_send()) <<
        http.HttpRequestHeadersHook(flow) >> reply() <<
        http.HttpRequestHook(flow) >> reply() << 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: 12\r\n\r\n")
        << http.HttpResponseHeadersHook(flow) >> reply() >> DataReceived(
            server, b"Hello World!") << http.HttpResponseHook(flow) <<
        CloseConnection(server) >> reply(to=-2) << SendData(
            tctx.client, response))
    events = conn.receive_data(response())
    assert event_types(events) == [
        h2.events.ResponseReceived, h2.events.DataReceived,
        h2.events.DataReceived, h2.events.StreamEnded
    ]
    resp: h2.events.ResponseReceived = events[0]
    body: h2.events.DataReceived = events[1]
    assert resp.headers == [(b':status', b'200'), (b'content-length', b'12')]
    assert body.data == b"Hello World!"
Example #11
0
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()
Example #12
0
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()
Example #13
0
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'
Example #14
0
def test_cancel_during_response_hook(tctx):
    """
    Test that we properly handle the case of the following event sequence:
        - we receive a server response
        - we trigger the response hook
        - the client cancels the stream
        - the response hook completes

    Given that we have already triggered the response hook, we don't want to trigger the error hook.
    """
    playbook, cff = start_h2_client(tctx)
    flow = Placeholder(HTTPFlow)
    server = Placeholder(Server)

    assert (playbook >> DataReceived(
        tctx.client,
        cff.build_headers_frame(example_request_headers, flags=[
            "END_STREAM"
        ]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() <<
            http.HttpRequestHook(flow) >> reply() << 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") <<
            http.HttpResponseHeadersHook(flow) << CloseConnection(server) >>
            reply(to=-2) << http.HttpResponseHook(flow) >> DataReceived(
                tctx.client,
                cff.build_rst_stream_frame(
                    1, ErrorCodes.CANCEL).serialize()) >> reply(to=-2))
Example #15
0
def test_protocol_error(ws_testdata):
    tctx, playbook = ws_testdata
    flow = Placeholder(WebSocketFlow)
    assert (
        playbook << websocket.WebsocketStartHook(flow) >> reply() >>
        DataReceived(tctx.server, b"\x01\x03foo") >>
        DataReceived(tctx.server, b"\x02\x03bar") << SendData(
            tctx.server,
            masked(
                b"\x88/\x03\xeaexpected CONTINUATION, got <Opcode.BINARY: 2>"))
        << CloseConnection(tctx.server) << SendData(
            tctx.client,
            b"\x88/\x03\xeaexpected CONTINUATION, got <Opcode.BINARY: 2>") <<
        CloseConnection(
            tctx.client) << websocket.WebsocketErrorHook(flow) >> reply())
    assert not flow().messages
Example #16
0
def test_close_normal(ws_testdata):
    tctx, playbook = ws_testdata
    flow = Placeholder(WebSocketFlow)
    masked_close = Placeholder(bytes)
    close = Placeholder(bytes)
    assert (playbook << websocket.WebsocketStartHook(flow) >> reply() >>
            DataReceived(tctx.client, masked_bytes(b"\x88\x00")) << SendData(
                tctx.server, masked_close) << CloseConnection(tctx.server) <<
            SendData(tctx.client, close) << CloseConnection(
                tctx.client) << websocket.WebsocketEndHook(flow) >> reply())
    # wsproto currently handles this inconsistently, see
    # https://github.com/python-hyper/wsproto/pull/153/files
    assert masked_close() == masked(
        b"\x88\x02\x03\xe8") or masked_close() == masked(b"\x88\x00")
    assert close() == b"\x88\x02\x03\xe8" or close() == b"\x88\x00"

    assert flow().close_code == 1005
Example #17
0
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()
Example #18
0
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))
Example #19
0
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))
Example #20
0
def test_reverse_eager_connect_failure(tctx: Context):
    """
    Test
        client --TCP-- mitmproxy --TCP over TLS-- server
    reverse proxying.
    """

    tctx.options.mode = "reverse:https://localhost:8000"
    tctx.options.connection_strategy = "eager"
    playbook = Playbook(modes.ReverseProxy(tctx))
    assert (playbook << OpenConnection(tctx.server) >> reply("IPoAC unstable")
            << CloseConnection(tctx.client) >> ConnectionClosed(tctx.client))
Example #21
0
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
Example #22
0
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))
Example #23
0
def test_http2_client_aborts(tctx, stream, when, how):
    """
    Test handling of the case where a client aborts during request or response transmission.

    If the client aborts the request transmission, we must trigger an error hook,
    if the client disconnects during response transmission, no error hook is triggered.
    """
    server = Placeholder(Server)
    flow = Placeholder(HTTPFlow)
    playbook, cff = start_h2_client(tctx)
    resp = Placeholder(bytes)

    def enable_request_streaming(flow: HTTPFlow):
        flow.request.stream = True

    def enable_response_streaming(flow: HTTPFlow):
        flow.response.stream = True

    assert (playbook >> DataReceived(
        tctx.client,
        cff.build_headers_frame(example_request_headers).serialize()) <<
            http.HttpRequestHeadersHook(flow))
    if stream and when == "request":
        assert (playbook >> reply(side_effect=enable_request_streaming) <<
                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"))
    else:
        assert playbook >> reply()

    if when == "request":
        if "RST" in how:
            playbook >> DataReceived(
                tctx.client,
                cff.build_rst_stream_frame(1, ErrorCodes.CANCEL).serialize())
        else:
            playbook >> ConnectionClosed(tctx.client)
            playbook << CloseConnection(tctx.client)

        if stream:
            playbook << CloseConnection(server)
        playbook << http.HttpErrorHook(flow)
        playbook >> reply()

        if how == "RST+disconnect":
            playbook >> ConnectionClosed(tctx.client)
            playbook << CloseConnection(tctx.client)

        assert playbook
        assert "stream reset" in flow(
        ).error.msg or "peer closed connection" in flow().error.msg
        return

    assert (playbook >> DataReceived(
        tctx.client,
        cff.build_data_frame(b"", flags=["END_STREAM"]).serialize()) <<
            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\nContent-Length: 6\r\n\r\n123")
            << http.HttpResponseHeadersHook(flow))
    if stream:
        assert (playbook >> reply(side_effect=enable_response_streaming) <<
                SendData(tctx.client, resp))
    else:
        assert playbook >> reply()

    if "RST" in how:
        playbook >> DataReceived(
            tctx.client,
            cff.build_rst_stream_frame(1, ErrorCodes.CANCEL).serialize())
    else:
        playbook >> ConnectionClosed(tctx.client)
        playbook << CloseConnection(tctx.client)

    assert (playbook << CloseConnection(server) << http.HttpErrorHook(flow) >>
            reply())

    if how == "RST+disconnect":
        assert (playbook >> ConnectionClosed(tctx.client) << CloseConnection(
            tctx.client))

    if "RST" in how:
        assert "stream reset" in flow().error.msg
    else:
        assert "peer closed connection" in flow().error.msg
Example #24
0
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"))
Example #25
0
def test_request_streaming(tctx, response):
    """
    Test HTTP request streaming

    This is a bit more contrived as we may receive server data while we are still sending the request.
    """
    server = Placeholder(Server)
    flow = Placeholder(HTTPFlow)
    playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular))

    def enable_streaming(flow: HTTPFlow):
        flow.request.stream = lambda x: x.upper()

    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\r\n"
        b"abc") << http.HttpRequestHeadersHook(flow) >>
            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\r\n"
                b"ABC"))
    if response == "normal response":
        assert (
            playbook >> DataReceived(tctx.client, b"def") << SendData(
                server, b"DEF") >> DataReceived(
                    server, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") <<
            http.HttpResponseHeadersHook(flow) >> reply() <<
            http.HttpResponseHook(flow) >> reply() << SendData(
                tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"))
    elif response == "early response":
        # We may receive a response before we have finished sending our request.
        # We continue sending unless the server closes the connection.
        # https://tools.ietf.org/html/rfc7231#section-6.5.11
        assert (playbook >> DataReceived(
            server,
            b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n"
        ) << http.HttpResponseHeadersHook(flow) >> reply(
        ) << http.HttpResponseHook(flow) >> reply() << SendData(
            tctx.client,
            b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n"
        ) >> DataReceived(tctx.client, b"def") << SendData(
            server, b"DEF")  # Important: no request hook here!
                )
    elif response == "early close":
        assert (playbook >> DataReceived(
            server,
            b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n"
        ) << http.HttpResponseHeadersHook(flow) >> reply(
        ) << http.HttpResponseHook(flow) >> reply() << SendData(
            tctx.client,
            b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n"
        ) >> ConnectionClosed(server) << CloseConnection(server) <<
                CloseConnection(tctx.client))
    elif response == "early kill":
        err = Placeholder(bytes)
        assert (playbook >> ConnectionClosed(server) << CloseConnection(server)
                << http.HttpErrorHook(flow) >> reply() << SendData(
                    tctx.client, err) << CloseConnection(tctx.client))
        assert b"502 Bad Gateway" in err()
    else:  # pragma: no cover
        assert False
Example #26
0
def test_upstream_proxy(tctx, redirect, scheme):
    """Test that an upstream HTTP proxy is used."""
    server = Placeholder(Server)
    server2 = Placeholder(Server)
    flow = Placeholder(HTTPFlow)
    tctx.options.mode = "upstream:http://proxy:8080"
    playbook = Playbook(http.HttpLayer(tctx, HTTPMode.upstream), hooks=False)

    if scheme == "http":
        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://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n"))

    else:
        assert (playbook >> DataReceived(
            tctx.client,
            b"CONNECT example.com: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"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)) <<
                OpenConnection(server) >> reply(None) << SendData(
                    server, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n")
                >> DataReceived(
                    server, b"HTTP/1.1 200 Connection established\r\n\r\n") <<
                SendData(server,
                         b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))

    playbook >> DataReceived(server,
                             b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")
    playbook << SendData(tctx.client,
                         b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")

    assert playbook
    assert server().address == ("proxy", 8080)

    if scheme == "http":
        playbook >> DataReceived(
            tctx.client,
            b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n"
        )
    else:
        playbook >> DataReceived(
            tctx.client, b"GET /two HTTP/1.1\r\nHost: example.com\r\n\r\n")

    assert (playbook << http.HttpRequestHook(flow))
    if redirect == "change-destination":
        flow().request.host = "other-server"
        flow().request.host_header = "example.com"
    elif redirect == "change-proxy":
        flow().server_conn.via = ServerSpec("http",
                                            address=("other-proxy", 1234))
    playbook >> reply()

    if redirect:
        # Protocol-wise we wouldn't need to open a new connection for plain http host redirects,
        # but we disregard this edge case to simplify implementation.
        playbook << OpenConnection(server2)
        playbook >> reply(None)
    else:
        server2 = server

    if scheme == "http":
        if redirect == "change-destination":
            playbook << SendData(
                server2,
                b"GET http://other-server/two HTTP/1.1\r\nHost: example.com\r\n\r\n"
            )
        else:
            playbook << SendData(
                server2,
                b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n"
            )
    else:
        if redirect == "change-destination":
            playbook << SendData(server2,
                                 b"CONNECT other-server:443 HTTP/1.1\r\n\r\n")
            playbook >> DataReceived(
                server2, b"HTTP/1.1 200 Connection established\r\n\r\n")
        elif redirect == "change-proxy":
            playbook << SendData(server2,
                                 b"CONNECT example.com:443 HTTP/1.1\r\n\r\n")
            playbook >> DataReceived(
                server2, b"HTTP/1.1 200 Connection established\r\n\r\n")
        playbook << SendData(
            server2, b"GET /two HTTP/1.1\r\nHost: example.com\r\n\r\n")

    playbook >> DataReceived(server2,
                             b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")
    playbook << SendData(tctx.client,
                         b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")

    assert playbook

    if redirect == "change-proxy":
        assert server2().address == ("other-proxy", 1234)
    else:
        assert server2().address == ("proxy", 8080)

    assert (playbook >> ConnectionClosed(tctx.client) << CloseConnection(
        tctx.client))
Example #27
0
def test_open_connection_err(tctx):
    f = Placeholder(TCPFlow)
    assert (Playbook(tcp.TCPLayer(tctx)) << tcp.TcpStartHook(f) >> reply() <<
            OpenConnection(tctx.server) >> reply("Connect call failed") <<
            tcp.TcpErrorHook(f) >> reply() << CloseConnection(tctx.client))
Example #28
0
def test_kill_flow(tctx, when):
    """Test that we properly kill flows if instructed to do so"""
    server = Placeholder(Server)
    connect_flow = Placeholder(HTTPFlow)
    flow = Placeholder(HTTPFlow)

    def kill(flow: HTTPFlow):
        # Can't use flow.kill() here because that currently still depends on a reply object.
        flow.error = Error(Error.KILLED_MESSAGE)

    def assert_kill(err_hook: bool = True):
        playbook >> reply(side_effect=kill)
        if err_hook:
            playbook << http.HttpErrorHook(flow)
            playbook >> reply()
        playbook << CloseConnection(tctx.client)
        assert playbook

    playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular))
    assert (playbook >> DataReceived(
        tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") <<
            http.HttpConnectHook(connect_flow))
    if when == "http_connect":
        return assert_kill(False)
    assert (
        playbook >> reply() << 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))
    if when == "requestheaders":
        return assert_kill()
    assert (playbook >> reply() << http.HttpRequestHook(flow))
    if when == "request":
        return assert_kill()
    if when == "script-response-responseheaders":
        assert (playbook >> reply(
            side_effect=lambda f: setattr(f, "response", HTTPResponse.make()))
                << http.HttpResponseHeadersHook(flow))
        return assert_kill()
    assert (
        playbook >> 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))
    if when == "responseheaders":
        return assert_kill()

    if when == "response":
        assert (playbook >> reply() >> DataReceived(server, b"!") <<
                http.HttpResponseHook(flow))
        return assert_kill(False)
    elif when == "error":
        assert (playbook >> reply() >> ConnectionClosed(server) <<
                CloseConnection(server) << http.HttpErrorHook(flow))
        return assert_kill(False)
    else:
        raise AssertionError