From 746537e0511e0316a144e05e7ba8cc6f6e44768b Mon Sep 17 00:00:00 2001 From: Daniel Karandikar Date: Wed, 8 Nov 2023 10:12:44 +0000 Subject: [PATCH] Add option to return various status codes rather than killing during server replay (#6465) #### Description Designed to satisfy the requirements of https://github.com/mitmproxy/mitmproxy/issues/3489 Add `server_replay_404_extra` which behaves similarly to the kill flag, but returns 404 responses rather than killing #### Checklist - [x] I have updated tests where applicable. - [x] I have added an entry to the CHANGELOG. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 3 ++ mitmproxy/addons/serverplayback.py | 29 +++++++++++++- mitmproxy/tools/cmdline.py | 1 + mitmproxy/tools/console/statusbar.py | 2 + test/mitmproxy/addons/test_serverplayback.py | 39 +++++++++++++++++++ .../mitmproxy/tools/console/test_statusbar.py | 2 +- web/src/js/ducks/_options_gen.ts | 2 + 7 files changed, 75 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 052194af90..dcdf1908c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * Update savehar addon to handle scenarios where "path" key in cookie attrs dict is missing. ([#6458](https://github.com/mitmproxy/mitmproxy/pull/6458), @pogzyb) +* Add `server_replay_extra` option to serverplayback to define behaviour + when replayable response is missing. + ([#6465](https://github.com/mitmproxy/mitmproxy/pull/6465), @dkarandikar) ## 04 November 2023: mitmproxy 10.1.3 diff --git a/mitmproxy/addons/serverplayback.py b/mitmproxy/addons/serverplayback.py index e479c23ad3..077eade378 100644 --- a/mitmproxy/addons/serverplayback.py +++ b/mitmproxy/addons/serverplayback.py @@ -30,7 +30,16 @@ def load(self, loader): "server_replay_kill_extra", bool, False, - "Kill extra requests during replay (for which no replayable response was found).", + "Kill extra requests during replay (for which no replayable response was found)." + "[Deprecated, prefer to use server_replay_extra='kill']", + ) + loader.add_option( + "server_replay_extra", + str, + "forward", + "Behaviour for extra requests during replay for which no replayable response was found. " + "Setting a numeric string value will return an empty HTTP response with the respective status code.", + choices=["forward", "kill", "204", "400", "404", "500"], ) loader.add_option( "server_replay_reuse", @@ -230,6 +239,11 @@ def next_flow(self, flow: http.HTTPFlow) -> http.HTTPFlow | None: return None def configure(self, updated): + if ctx.options.server_replay_kill_extra: + logger.warning( + "server_replay_kill_extra has been deprecated, " + "please update your config to use server_replay_extra='kill'." + ) if ctx.options.server_replay_nopop: # pragma: no cover logger.error( "server_replay_nopop has been renamed to server_replay_reuse, please update your config." @@ -252,10 +266,21 @@ def request(self, f: http.HTTPFlow) -> None: response.refresh() f.response = response f.is_replay = "response" - elif ctx.options.server_replay_kill_extra: + elif ( + ctx.options.server_replay_kill_extra + or ctx.options.server_replay_extra == "kill" + ): logging.warning( "server_playback: killed non-replay request {}".format( f.request.url ) ) f.kill() + elif ctx.options.server_replay_extra != "forward": + logging.warning( + "server_playback: returned {} non-replay request {}".format( + ctx.options.server_replay_extra, f.request.url + ) + ) + f.response = http.Response.make(int(ctx.options.server_replay_extra)) + f.is_replay = "response" diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py index fb2a25f5ed..efd3868340 100644 --- a/mitmproxy/tools/cmdline.py +++ b/mitmproxy/tools/cmdline.py @@ -83,6 +83,7 @@ def common_options(parser, opts): group = parser.add_argument_group("Server Replay") opts.make_parser(group, "server_replay", metavar="PATH", short="S") opts.make_parser(group, "server_replay_kill_extra") + opts.make_parser(group, "server_replay_extra") opts.make_parser(group, "server_replay_reuse") opts.make_parser(group, "server_replay_refresh") diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index 4390fe0237..3dc1e01254 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -280,6 +280,8 @@ def get_status(self) -> list[tuple[str, str] | str]: opts.append("norefresh") if self.master.options.server_replay_kill_extra: opts.append("killextra") + if self.master.options.server_replay_extra: + opts.append("replay-extra") if not self.master.options.upstream_cert: opts.append("no-upstream-cert") if self.master.options.console_focus_follow: diff --git a/test/mitmproxy/addons/test_serverplayback.py b/test/mitmproxy/addons/test_serverplayback.py index 3523786d09..aa3a2178de 100644 --- a/test/mitmproxy/addons/test_serverplayback.py +++ b/test/mitmproxy/addons/test_serverplayback.py @@ -364,6 +364,45 @@ async def test_server_playback_kill(): assert f.error +async def test_server_playback_kill_new_option(): + s = serverplayback.ServerPlayback() + with taddons.context(s) as tctx: + tctx.configure(s, server_replay_refresh=True, server_replay_extra="kill") + + f = tflow.tflow() + f.response = mitmproxy.test.tutils.tresp(content=f.request.content) + s.load_flows([f]) + + f = tflow.tflow() + f.request.host = "nonexistent" + await tctx.cycle(s, f) + assert f.error + + +@pytest.mark.parametrize( + "option,status", + [ + ("204", 204), + ("400", 400), + ("404", 404), + ("500", 500), + ], +) +async def test_server_playback_404(option, status): + s = serverplayback.ServerPlayback() + with taddons.context(s) as tctx: + tctx.configure(s, server_replay_refresh=True, server_replay_extra=option) + + f = tflow.tflow() + f.response = mitmproxy.test.tutils.tresp(content=f.request.content) + s.load_flows([f]) + + f = tflow.tflow() + f.request.host = "nonexistent" + s.request(f) + assert f.response.status_code == status + + def test_server_playback_response_deleted(): """ The server playback addon holds references to flows that can be modified by the user in the meantime. diff --git a/test/mitmproxy/tools/console/test_statusbar.py b/test/mitmproxy/tools/console/test_statusbar.py index 080f9197a2..bcf5f3d606 100644 --- a/test/mitmproxy/tools/console/test_statusbar.py +++ b/test/mitmproxy/tools/console/test_statusbar.py @@ -18,7 +18,7 @@ async def test_statusbar(console, monkeypatch): anticomp=True, showhost=True, server_replay_refresh=False, - server_replay_kill_extra=True, + server_replay_extra="kill", upstream_cert=False, stream_large_bodies="3m", mode=["transparent"], diff --git a/web/src/js/ducks/_options_gen.ts b/web/src/js/ducks/_options_gen.ts index d46dffe2b8..da939e0bdd 100644 --- a/web/src/js/ducks/_options_gen.ts +++ b/web/src/js/ducks/_options_gen.ts @@ -51,6 +51,7 @@ export interface OptionsState { scripts: string[]; server: boolean; server_replay: string[]; + server_replay_extra: string; server_replay_ignore_content: boolean; server_replay_ignore_host: boolean; server_replay_ignore_params: string[]; @@ -146,6 +147,7 @@ export const defaultState: OptionsState = { scripts: [], server: true, server_replay: [], + server_replay_extra: "forward", server_replay_ignore_content: false, server_replay_ignore_host: false, server_replay_ignore_params: [],