diff --git a/CHANGELOG.md b/CHANGELOG.md index 2523dcf36..f7211e99b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Includes example addon which applies custom definitions for selected gRPC traffic (@mame82) * Fix a crash caused when editing string option (#4852, @rbdixon) * Base container image bumped to Debian 11 Bullseye (@Kriechi) +* Upstream replays don't do CONNECT on plaintext HTTP requests (#4876, @HoffmannP) ## 28 September 2021: mitmproxy 7.0.4 diff --git a/mitmproxy/addons/clientplayback.py b/mitmproxy/addons/clientplayback.py index b75034933..8dea6f794 100644 --- a/mitmproxy/addons/clientplayback.py +++ b/mitmproxy/addons/clientplayback.py @@ -78,7 +78,10 @@ class ReplayHandler(server.ConnectionHandler): super().__init__(context) - self.layer = layers.HttpLayer(context, HTTPMode.transparent) + if options.mode.startswith("upstream:"): + self.layer = layers.HttpLayer(context, HTTPMode.upstream) + else: + self.layer = layers.HttpLayer(context, HTTPMode.transparent) self.layer.connections[client] = MockServer(flow, context.fork()) self.flow = flow self.done = asyncio.Event() diff --git a/test/mitmproxy/addons/test_clientplayback.py b/test/mitmproxy/addons/test_clientplayback.py index ad68c1d26..4de48ffe5 100644 --- a/test/mitmproxy/addons/test_clientplayback.py +++ b/test/mitmproxy/addons/test_clientplayback.py @@ -31,13 +31,13 @@ async def test_playback(mode, concurrency): writer.close() handler_ok.set() return + req = await reader.readline() if mode == "upstream": - conn_req = await reader.readuntil(b"\r\n\r\n") - assert conn_req == b'CONNECT address:22 HTTP/1.1\r\n\r\n' - writer.write(b"HTTP/1.1 200 Connection Established\r\n\r\n") + assert req == b'GET http://address:22/path HTTP/1.1\r\n' + else: + assert req == b'GET /path HTTP/1.1\r\n' req = await reader.readuntil(b"data") assert req == ( - b'GET /path HTTP/1.1\r\n' b'header: qvalue\r\n' b'content-length: 4\r\n' b'\r\n' @@ -59,6 +59,8 @@ async def test_playback(mode, concurrency): flow.request.content = b"data" if mode == "upstream": tctx.options.mode = f"upstream:http://{addr[0]}:{addr[1]}" + flow.request.authority = f"{addr[0]}:{addr[1]}" + flow.request.host, flow.request.port = 'address', 22 else: flow.request.host, flow.request.port = addr cp.start_replay([flow]) @@ -70,6 +72,37 @@ async def test_playback(mode, concurrency): assert flow.response.status_code == 204 +@pytest.mark.asyncio +async def test_playback_https_upstream(): + handler_ok = asyncio.Event() + + async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + conn_req = await reader.readuntil(b"\r\n\r\n") + assert conn_req == b'CONNECT address:22 HTTP/1.1\r\n\r\n' + writer.write(b"HTTP/1.1 502 Bad Gateway\r\n\r\n") + await writer.drain() + assert not await reader.read() + handler_ok.set() + + cp = ClientPlayback() + ps = Proxyserver() + with taddons.context(cp, ps) as tctx: + tctx.configure(cp) + async with tcp_server(handler) as addr: + cp.running() + flow = tflow.tflow() + flow.request.scheme = b"https" + flow.request.content = b"data" + tctx.options.mode = f"upstream:http://{addr[0]}:{addr[1]}" + cp.start_replay([flow]) + assert cp.count() == 1 + await asyncio.wait_for(cp.queue.join(), 5) + await asyncio.wait_for(handler_ok.wait(), 5) + cp.done() + assert flow.response is None + assert str(flow.error) == f'Upstream proxy {addr[0]}:{addr[1]} refused HTTP CONNECT request: 502 Bad Gateway' + + @pytest.mark.asyncio async def test_playback_crash(monkeypatch): async def raise_err():