Repository: mitmproxy/mitmproxy

Issue:
Cannot intercept ASGI requests
```
error: Addon error: Traceback (most recent call last):
  File "/home/martin/mitmproxy/mitmproxy/addons/intercept.py", line 48, in request
    self.process_flow(f)
  File "/home/martin/mitmproxy/mitmproxy/addons/intercept.py", line 43, in process_flow
    f.intercept()
  File "/home/martin/mitmproxy/mitmproxy/flow.py", line 172, in intercept
    self.reply.take()
  File "/home/martin/mitmproxy/mitmproxy/controller.py", line 92, in take
    "Reply is {}, but expected it to be start.".format(self.state)
mitmproxy.exceptions.ControlException: Reply is taken, but expected it to be start.
```

_Originally posted by @mplattner in https://github.com/mitmproxy/mitmproxy/pull/4147#issuecomment-674060804_


Discussion:

Resolving patch:
diff --git a/mitmproxy/addons/intercept.py b/mitmproxy/addons/intercept.py
--- a/mitmproxy/addons/intercept.py
+++ b/mitmproxy/addons/intercept.py
@@ -1,20 +1,18 @@
 import typing
 
-from mitmproxy import flowfilter
+from mitmproxy import flow, flowfilter
 from mitmproxy import exceptions
 from mitmproxy import ctx
 
 
 class Intercept:
-    def __init__(self):
-        self.filt = None
+    filt: typing.Optional[flowfilter.TFilter] = None
 
     def load(self, loader):
         loader.add_option(
             "intercept_active", bool, False,
             "Intercept toggle"
         )
-
         loader.add_option(
             "intercept", typing.Optional[str], None,
             "Intercept filter expression."
@@ -22,25 +20,29 @@ def load(self, loader):
 
     def configure(self, updated):
         if "intercept" in updated:
-            if not ctx.options.intercept:
+            if ctx.options.intercept:
+                self.filt = flowfilter.parse(ctx.options.intercept)
+                if not self.filt:
+                    raise exceptions.OptionsError(f"Invalid interception filter: {ctx.options.intercept}")
+                ctx.options.intercept_active = True
+            else:
                 self.filt = None
                 ctx.options.intercept_active = False
-                return
-            self.filt = flowfilter.parse(ctx.options.intercept)
-            if not self.filt:
-                raise exceptions.OptionsError(
-                    "Invalid interception filter: %s" % ctx.options.intercept
-                )
-            ctx.options.intercept_active = True
-
-    def process_flow(self, f):
-        if self.filt:
-            should_intercept = all([
-                self.filt(f),
-                not f.is_replay == "request",
-            ])
-            if should_intercept and ctx.options.intercept_active:
-                f.intercept()
+
+    def should_intercept(self, f: flow.Flow) -> bool:
+        return bool(
+                ctx.options.intercept_active
+                and self.filt
+                and self.filt(f)
+                and not f.is_replay
+        )
+
+    def process_flow(self, f: flow.Flow) -> None:
+        if self.should_intercept(f):
+            assert f.reply
+            if f.reply.state != "start":
+                return ctx.log.debug("Cannot intercept request that is already taken by another addon.")
+            f.intercept()
 
     # Handlers
 
@@ -51,5 +53,4 @@ def response(self, f):
         self.process_flow(f)
 
     def tcp_message(self, f):
-        if self.filt and ctx.options.intercept_active:
-            f.intercept()
+        self.process_flow(f)


Test patch:
diff --git a/test/mitmproxy/addons/test_intercept.py b/test/mitmproxy/addons/test_intercept.py
--- a/test/mitmproxy/addons/test_intercept.py
+++ b/test/mitmproxy/addons/test_intercept.py
@@ -43,12 +43,31 @@ def test_simple():
         tctx.cycle(r, f)
         assert f.intercepted
 
-        tctx.configure(r, intercept_active=False)
+
+def test_tcp():
+    r = intercept.Intercept()
+    with taddons.context(r) as tctx:
+        tctx.configure(r, intercept="~tcp")
         f = tflow.ttcpflow()
         tctx.cycle(r, f)
-        assert not f.intercepted
+        assert f.intercepted
 
-        tctx.configure(r, intercept_active=True)
+        tctx.configure(r, intercept_active=False)
         f = tflow.ttcpflow()
         tctx.cycle(r, f)
+        assert not f.intercepted
+
+
+def test_already_taken():
+    r = intercept.Intercept()
+    with taddons.context(r) as tctx:
+        tctx.configure(r, intercept="~q")
+
+        f = tflow.tflow()
+        tctx.invoke(r, "request", f)
         assert f.intercepted
+
+        f = tflow.tflow()
+        f.reply.take()
+        tctx.invoke(r, "request", f)
+        assert not f.intercepted