Repository: mitmproxy/mitmproxy

Issue:
Unable to set `server_replay_ignore_params` in the UI
#### Value of server_replay_ignore_params is ignored when set in the UI or through command line

#### Steps to reproduce the behavior:
1. Prepare a mock endpoint that works with 5 s delay. In this example I will use a configured mock https://hello.free.beeceptor.com/delay
2. Prepare a recording for the endpoint that contains calls to the endpoint with a parameter `param` (any value) and save it to `my.flow`.
3. Launch the software in the reverse proxy mode, using any of following:
  a. `mitmweb --mode reverse:https://hello.free.beeceptor.com --server-replay my.flow --server-replay-nopop --web-port 8888`
  b. `mitmproxy --mode reverse:https://hello.free.beeceptor.com --server-replay my_flow --server-replay-nopop`
4. In the UI:
  a. Go to Options, Edit Options, set `server_replay_ignore_params` to `param` and close Options.
  b. Press Shift-O for Options, scroll to `server_replay_ignore_params`, press `a`, enter `param`, press `Esc`, `q`, `q`.
5. Issue some requests through the proxy with value of `param` not existing in the recording. E.g.  
`curl 'http://localhost:8080/delay?param=value5'`

**EDIT**: corrected repro steps for TUI.

#### Problem
The setting has no effect, you will see 5 s delay in the response.

#### System Information
Mitmproxy: 6.0.2
Python:    3.9.2
OpenSSL:   OpenSSL 1.1.1i  8 Dec 2020
Platform:  macOS-10.15.7-x86_64-i386-64bit


Discussion:
Thanks! The problem here ist that we compute a hash for each flow when server replay is started, which we then later use to serve that flow:

https://github.com/mitmproxy/mitmproxy/blob/0650f132e9526a6ac881c2fb0cb3f53fba19ed1e/mitmproxy/addons/serverplayback.py#L114-L161

We need to rerun that function and recompute all hashes when one of the options affecting it changes. PRs welcome! 😃 
Some notes that I found while testing this issue:
1. This issue only applies when `server_replay_ignore_params` is set through the UI after startup. It works as intended when the option is set before startup
i.e `mitmproxy --mode reverse:https://test-mitm.free.beeceptor.com --server-replay ~/my.flow --server-replay-reuse --set server_replay_ignore_params="param"` works as intended and ignores param while trying to match requests
2. If we set the option through the UI after startup, setting `server_replay_ignore_param` to `param=value` would match requests when the parameter for the request is `param=value` and return the matching response from the capture but wouldn't match `param=value5` i.e the entire `key=value` is matched when the option is set from the UI

I'll try to send a fix for this

Resolving patch:
diff --git a/mitmproxy/addons/serverplayback.py b/mitmproxy/addons/serverplayback.py
--- a/mitmproxy/addons/serverplayback.py
+++ b/mitmproxy/addons/serverplayback.py
@@ -16,6 +16,15 @@
 
 logger = logging.getLogger(__name__)
 
+HASH_OPTIONS = [
+    "server_replay_ignore_content",
+    "server_replay_ignore_host",
+    "server_replay_ignore_params",
+    "server_replay_ignore_payload_params",
+    "server_replay_ignore_port",
+    "server_replay_use_headers",
+]
+
 
 class ServerPlayback:
     flowmap: dict[Hashable, list[http.HTTPFlow]]
@@ -255,6 +264,16 @@ def configure(self, updated):
             except exceptions.FlowReadException as e:
                 raise exceptions.OptionsError(str(e))
             self.load_flows(flows)
+        if any(option in updated for option in HASH_OPTIONS):
+            self.recompute_hashes()
+
+    def recompute_hashes(self) -> None:
+        """
+        Rebuild flowmap if the hashing method has changed during execution,
+        see https://github.com/mitmproxy/mitmproxy/issues/4506
+        """
+        flows = [flow for lst in self.flowmap.values() for flow in lst]
+        self.load_flows(flows)
 
     def request(self, f: http.HTTPFlow) -> None:
         if self.flowmap:


Test patch:
diff --git a/test/mitmproxy/addons/test_serverplayback.py b/test/mitmproxy/addons/test_serverplayback.py
--- a/test/mitmproxy/addons/test_serverplayback.py
+++ b/test/mitmproxy/addons/test_serverplayback.py
@@ -325,6 +325,24 @@ def multipart_setter(r, **kwargs):
     thash(r, r2, multipart_setter)
 
 
+def test_runtime_modify_params():
+    s = serverplayback.ServerPlayback()
+    with taddons.context(s) as tctx:
+        r = tflow.tflow(resp=True)
+        r.request.path = "/test?param1=1"
+        r2 = tflow.tflow(resp=True)
+        r2.request.path = "/test"
+
+        s.load_flows([r])
+        hash = next(iter(s.flowmap.keys()))
+
+        tctx.configure(s, server_replay_ignore_params=["param1"])
+        hash_mod = next(iter(s.flowmap.keys()))
+
+        assert hash != hash_mod
+        assert hash_mod == s._hash(r2)
+
+
 def test_server_playback_full():
     s = serverplayback.ServerPlayback()
     with taddons.context(s) as tctx: