We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Crash when invoking replay.server.count from console
#### Problem Description
Invoking `replay.server.count` from the console causes a crash. I don't think it happens all the time, but see below for repeatable reproduction.

#### Steps to reproduce the behavior:
1. Start `mitmproxy`
2. Hit `n` to create a new flow
3. Hit Enter
4. Hit `r` to issue the request
5. With the same flow selected, issue the command `:replay.server @focus`
6. Issue the command `:replay.server.count`

Sample stack trace:
```python
  File "/home/elespike/venvs/mitmproxy/lib/python3.9/site-packages/mitmproxy/tools/console/grideditor/editors.py", line 222, in __init__
    if not isinstance(vals[0], list):
TypeError: 'int' object is not subscriptable
```

#### System Information
```
Mitmproxy: 7.0.4
Python:    3.9.2
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mitmproxy/addons/dumper.py`
Content:
```
1 import itertools
2 import shutil
3 from typing import IO, Optional, Union
4 
5 import click
6 
7 from mitmproxy import contentviews
8 from mitmproxy import ctx
9 from mitmproxy import exceptions
10 from mitmproxy import flowfilter
11 from mitmproxy import http
12 from mitmproxy import flow
13 from mitmproxy.tcp import TCPFlow, TCPMessage
14 from mitmproxy.utils import human
15 from mitmproxy.utils import strutils
16 from mitmproxy.websocket import WebSocketMessage, WebSocketData
17 from wsproto.frame_protocol import CloseReason
18 
19 
20 def indent(n: int, text: str) -> str:
21     l = str(text).strip().splitlines()
22     pad = " " * n
23     return "\n".join(pad + i for i in l)
24 
25 
26 def colorful(line, styles):
27     yield "    "  # we can already indent here
28     for (style, text) in line:
29         yield click.style(text, **styles.get(style, {}))
30 
31 
32 class Dumper:
33     def __init__(self, outfile=None):
34         self.filter: Optional[flowfilter.TFilter] = None
35         self.outfp: Optional[IO] = outfile
36 
37     def load(self, loader):
38         loader.add_option(
39             "flow_detail", int, 1,
40             """
41             The display detail level for flows in mitmdump: 0 (almost quiet) to 3 (very verbose).
42               0: shortened request URL, response status code, WebSocket and TCP message notifications.
43               1: full request URL with response status code
44               2: 1 + HTTP headers
45               3: 2 + truncated response content, content of WebSocket and TCP messages
46               4: 3 + nothing is truncated
47             """
48         )
49         loader.add_option(
50             "dumper_default_contentview", str, "auto",
51             "The default content view mode.",
52             choices=[i.name.lower() for i in contentviews.views]
53         )
54         loader.add_option(
55             "dumper_filter", Optional[str], None,
56             "Limit which flows are dumped."
57         )
58 
59     def configure(self, updated):
60         if "dumper_filter" in updated:
61             if ctx.options.dumper_filter:
62                 try:
63                     self.filter = flowfilter.parse(ctx.options.dumper_filter)
64                 except ValueError as e:
65                     raise exceptions.OptionsError(str(e)) from e
66             else:
67                 self.filter = None
68 
69     def echo(self, text: str, ident=None, **style):
70         if ident:
71             text = indent(ident, text)
72         click.secho(text, file=self.outfp, err=False, **style)
73         if self.outfp:
74             self.outfp.flush()
75 
76     def _echo_headers(self, headers: http.Headers):
77         for k, v in headers.fields:
78             ks = strutils.bytes_to_escaped_str(k)
79             vs = strutils.bytes_to_escaped_str(v)
80             out = "{}: {}".format(
81                 click.style(ks, fg="blue"),
82                 click.style(vs)
83             )
84             self.echo(out, ident=4)
85 
86     def _echo_trailers(self, trailers: Optional[http.Headers]):
87         if not trailers:
88             return
89         self.echo(click.style("--- HTTP Trailers", fg="magenta"), ident=4)
90         self._echo_headers(trailers)
91 
92     def _echo_message(
93         self,
94         message: Union[http.Message, TCPMessage, WebSocketMessage],
95         flow: Union[http.HTTPFlow, TCPFlow]
96     ):
97         _, lines, error = contentviews.get_message_content_view(
98             ctx.options.dumper_default_contentview,
99             message,
100             flow
101         )
102         if error:
103             ctx.log.debug(error)
104 
105         if ctx.options.flow_detail == 3:
106             lines_to_echo = itertools.islice(lines, 70)
107         else:
108             lines_to_echo = lines
109 
110         styles = dict(
111             highlight=dict(bold=True),
112             offset=dict(fg="blue"),
113             header=dict(fg="green", bold=True),
114             text=dict(fg="green")
115         )
116 
117         content = "\r\n".join(
118             "".join(colorful(line, styles)) for line in lines_to_echo
119         )
120         if content:
121             self.echo("")
122             self.echo(content)
123 
124         if next(lines, None):
125             self.echo("(cut off)", ident=4, dim=True)
126 
127         if ctx.options.flow_detail >= 2:
128             self.echo("")
129 
130     def _echo_request_line(self, flow: http.HTTPFlow) -> None:
131         if flow.is_replay == "request":
132             client = click.style("[replay]", fg="yellow", bold=True)
133         elif flow.client_conn.peername:
134             client = click.style(
135                 strutils.escape_control_characters(
136                     human.format_address(flow.client_conn.peername)
137                 )
138             )
139         else:  # pragma: no cover
140             # this should not happen, but we're defensive here.
141             client = ""
142 
143         pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else ''
144         method = flow.request.method + pushed
145         method_color = dict(
146             GET="green",
147             DELETE="red"
148         ).get(method.upper(), "magenta")
149         method = click.style(
150             strutils.escape_control_characters(method),
151             fg=method_color,
152             bold=True
153         )
154         if ctx.options.showhost:
155             url = flow.request.pretty_url
156         else:
157             url = flow.request.url
158 
159         if ctx.options.flow_detail <= 1:
160             # We need to truncate before applying styles, so we just focus on the URL.
161             terminal_width_limit = max(shutil.get_terminal_size()[0] - 25, 50)
162             if len(url) > terminal_width_limit:
163                 url = url[:terminal_width_limit] + "…"
164         url = click.style(strutils.escape_control_characters(url), bold=True)
165 
166         http_version = ""
167         if (
168             not (flow.request.is_http10 or flow.request.is_http11)
169             or flow.request.http_version != getattr(flow.response, "http_version", "HTTP/1.1")
170         ):
171             # Hide version for h1 <-> h1 connections.
172             http_version = " " + flow.request.http_version
173 
174         self.echo(f"{client}: {method} {url}{http_version}")
175 
176     def _echo_response_line(self, flow: http.HTTPFlow) -> None:
177         if flow.is_replay == "response":
178             replay_str = "[replay]"
179             replay = click.style(replay_str, fg="yellow", bold=True)
180         else:
181             replay_str = ""
182             replay = ""
183 
184         assert flow.response
185         code_int = flow.response.status_code
186         code_color = None
187         if 200 <= code_int < 300:
188             code_color = "green"
189         elif 300 <= code_int < 400:
190             code_color = "magenta"
191         elif 400 <= code_int < 600:
192             code_color = "red"
193         code = click.style(
194             str(code_int),
195             fg=code_color,
196             bold=True,
197             blink=(code_int == 418),
198         )
199 
200         if not flow.response.is_http2:
201             reason = flow.response.reason
202         else:
203             reason = http.status_codes.RESPONSES.get(flow.response.status_code, "")
204         reason = click.style(
205             strutils.escape_control_characters(reason),
206             fg=code_color,
207             bold=True
208         )
209 
210         if flow.response.raw_content is None:
211             size = "(content missing)"
212         else:
213             size = human.pretty_size(len(flow.response.raw_content))
214         size = click.style(size, bold=True)
215 
216         http_version = ""
217         if (
218             not (flow.response.is_http10 or flow.response.is_http11)
219             or flow.request.http_version != flow.response.http_version
220         ):
221             # Hide version for h1 <-> h1 connections.
222             http_version = f"{flow.response.http_version} "
223 
224         arrows = click.style(" <<", bold=True)
225         if ctx.options.flow_detail == 1:
226             # This aligns the HTTP response code with the HTTP request method:
227             # 127.0.0.1:59519: GET http://example.com/
228             #               << 304 Not Modified 0b
229             pad = max(0,
230                       len(human.format_address(flow.client_conn.peername)) - (2 + len(http_version) + len(replay_str)))
231             arrows = " " * pad + arrows
232 
233         self.echo(f"{replay}{arrows} {http_version}{code} {reason} {size}")
234 
235     def echo_flow(self, f: http.HTTPFlow) -> None:
236         if f.request:
237             self._echo_request_line(f)
238             if ctx.options.flow_detail >= 2:
239                 self._echo_headers(f.request.headers)
240             if ctx.options.flow_detail >= 3:
241                 self._echo_message(f.request, f)
242             if ctx.options.flow_detail >= 2:
243                 self._echo_trailers(f.request.trailers)
244 
245         if f.response:
246             self._echo_response_line(f)
247             if ctx.options.flow_detail >= 2:
248                 self._echo_headers(f.response.headers)
249             if ctx.options.flow_detail >= 3:
250                 self._echo_message(f.response, f)
251             if ctx.options.flow_detail >= 2:
252                 self._echo_trailers(f.response.trailers)
253 
254         if f.error:
255             msg = strutils.escape_control_characters(f.error.msg)
256             self.echo(f" << {msg}", bold=True, fg="red")
257 
258     def match(self, f):
259         if ctx.options.flow_detail == 0:
260             return False
261         if not self.filter:
262             return True
263         elif flowfilter.match(self.filter, f):
264             return True
265         return False
266 
267     def response(self, f):
268         if self.match(f):
269             self.echo_flow(f)
270 
271     def error(self, f):
272         if self.match(f):
273             self.echo_flow(f)
274 
275     def websocket_message(self, f: http.HTTPFlow):
276         assert f.websocket is not None  # satisfy type checker
277         if self.match(f):
278             message = f.websocket.messages[-1]
279 
280             direction = "->" if message.from_client else "<-"
281             self.echo(
282                 f"{human.format_address(f.client_conn.peername)} "
283                 f"{direction} WebSocket {message.type.name.lower()} message "
284                 f"{direction} {human.format_address(f.server_conn.address)}{f.request.path}"
285             )
286             if ctx.options.flow_detail >= 3:
287                 self._echo_message(message, f)
288 
289     def websocket_end(self, f: http.HTTPFlow):
290         assert f.websocket is not None  # satisfy type checker
291         if self.match(f):
292             if f.websocket.close_code in {1000, 1001, 1005}:
293                 c = 'client' if f.websocket.closed_by_client else 'server'
294                 self.echo(f"WebSocket connection closed by {c}: {f.websocket.close_code} {f.websocket.close_reason}")
295             else:
296                 error = flow.Error(f"WebSocket Error: {self.format_websocket_error(f.websocket)}")
297                 self.echo(
298                     f"Error in WebSocket connection to {human.format_address(f.server_conn.address)}: {error}",
299                     fg="red"
300                 )
301 
302     def format_websocket_error(self, websocket: WebSocketData) -> str:
303         try:
304             ret = CloseReason(websocket.close_code).name
305         except ValueError:
306             ret = f"UNKNOWN_ERROR={websocket.close_code}"
307         if websocket.close_reason:
308             ret += f" (reason: {websocket.close_reason})"
309         return ret
310 
311     def tcp_error(self, f):
312         if self.match(f):
313             self.echo(
314                 f"Error in TCP connection to {human.format_address(f.server_conn.address)}: {f.error}",
315                 fg="red"
316             )
317 
318     def tcp_message(self, f):
319         if self.match(f):
320             message = f.messages[-1]
321             direction = "->" if message.from_client else "<-"
322             self.echo("{client} {direction} tcp {direction} {server}".format(
323                 client=human.format_address(f.client_conn.peername),
324                 server=human.format_address(f.server_conn.address),
325                 direction=direction,
326             ))
327             if ctx.options.flow_detail >= 3:
328                 self._echo_message(message, f)
329
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py
--- a/mitmproxy/addons/dumper.py
+++ b/mitmproxy/addons/dumper.py
@@ -38,10 +38,10 @@
         loader.add_option(
             "flow_detail", int, 1,
             """
-            The display detail level for flows in mitmdump: 0 (almost quiet) to 3 (very verbose).
-              0: shortened request URL, response status code, WebSocket and TCP message notifications.
-              1: full request URL with response status code
-              2: 1 + HTTP headers
+            The display detail level for flows in mitmdump: 0 (quiet) to 4 (very verbose).
+              0: no output
+              1: shortened request URL with response status code
+              2: full request URL with response status code and HTTP headers
               3: 2 + truncated response content, content of WebSocket and TCP messages
               4: 3 + nothing is truncated
             """
@@ -156,7 +156,7 @@
         else:
             url = flow.request.url
 
-        if ctx.options.flow_detail <= 1:
+        if ctx.options.flow_detail == 1:
             # We need to truncate before applying styles, so we just focus on the URL.
             terminal_width_limit = max(shutil.get_terminal_size()[0] - 25, 50)
             if len(url) > terminal_width_limit:

{"golden_diff": "diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py\n--- a/mitmproxy/addons/dumper.py\n+++ b/mitmproxy/addons/dumper.py\n@@ -38,10 +38,10 @@\n         loader.add_option(\n             \"flow_detail\", int, 1,\n             \"\"\"\n-            The display detail level for flows in mitmdump: 0 (almost quiet) to 3 (very verbose).\n-              0: shortened request URL, response status code, WebSocket and TCP message notifications.\n-              1: full request URL with response status code\n-              2: 1 + HTTP headers\n+            The display detail level for flows in mitmdump: 0 (quiet) to 4 (very verbose).\n+              0: no output\n+              1: shortened request URL with response status code\n+              2: full request URL with response status code and HTTP headers\n               3: 2 + truncated response content, content of WebSocket and TCP messages\n               4: 3 + nothing is truncated\n             \"\"\"\n@@ -156,7 +156,7 @@\n         else:\n             url = flow.request.url\n \n-        if ctx.options.flow_detail <= 1:\n+        if ctx.options.flow_detail == 1:\n             # We need to truncate before applying styles, so we just focus on the URL.\n             terminal_width_limit = max(shutil.get_terminal_size()[0] - 25, 50)\n             if len(url) > terminal_width_limit:\n", "issue": "Crash when invoking replay.server.count from console\n#### Problem Description\r\nInvoking `replay.server.count` from the console causes a crash. I don't think it happens all the time, but see below for repeatable reproduction.\r\n\r\n#### Steps to reproduce the behavior:\r\n1. Start `mitmproxy`\r\n2. Hit `n` to create a new flow\r\n3. Hit Enter\r\n4. Hit `r` to issue the request\r\n5. With the same flow selected, issue the command `:replay.server @focus`\r\n6. Issue the command `:replay.server.count`\r\n\r\nSample stack trace:\r\n```python\r\n  File \"/home/elespike/venvs/mitmproxy/lib/python3.9/site-packages/mitmproxy/tools/console/grideditor/editors.py\", line 222, in __init__\r\n    if not isinstance(vals[0], list):\r\nTypeError: 'int' object is not subscriptable\r\n```\r\n\r\n#### System Information\r\n```\r\nMitmproxy: 7.0.4\r\nPython:    3.9.2\r\n```\n", "before_files": [{"content": "import itertools\nimport shutil\nfrom typing import IO, Optional, Union\n\nimport click\n\nfrom mitmproxy import contentviews\nfrom mitmproxy import ctx\nfrom mitmproxy import exceptions\nfrom mitmproxy import flowfilter\nfrom mitmproxy import http\nfrom mitmproxy import flow\nfrom mitmproxy.tcp import TCPFlow, TCPMessage\nfrom mitmproxy.utils import human\nfrom mitmproxy.utils import strutils\nfrom mitmproxy.websocket import WebSocketMessage, WebSocketData\nfrom wsproto.frame_protocol import CloseReason\n\n\ndef indent(n: int, text: str) -> str:\n    l = str(text).strip().splitlines()\n    pad = \" \" * n\n    return \"\\n\".join(pad + i for i in l)\n\n\ndef colorful(line, styles):\n    yield \"    \"  # we can already indent here\n    for (style, text) in line:\n        yield click.style(text, **styles.get(style, {}))\n\n\nclass Dumper:\n    def __init__(self, outfile=None):\n        self.filter: Optional[flowfilter.TFilter] = None\n        self.outfp: Optional[IO] = outfile\n\n    def load(self, loader):\n        loader.add_option(\n            \"flow_detail\", int, 1,\n            \"\"\"\n            The display detail level for flows in mitmdump: 0 (almost quiet) to 3 (very verbose).\n              0: shortened request URL, response status code, WebSocket and TCP message notifications.\n              1: full request URL with response status code\n              2: 1 + HTTP headers\n              3: 2 + truncated response content, content of WebSocket and TCP messages\n              4: 3 + nothing is truncated\n            \"\"\"\n        )\n        loader.add_option(\n            \"dumper_default_contentview\", str, \"auto\",\n            \"The default content view mode.\",\n            choices=[i.name.lower() for i in contentviews.views]\n        )\n        loader.add_option(\n            \"dumper_filter\", Optional[str], None,\n            \"Limit which flows are dumped.\"\n        )\n\n    def configure(self, updated):\n        if \"dumper_filter\" in updated:\n            if ctx.options.dumper_filter:\n                try:\n                    self.filter = flowfilter.parse(ctx.options.dumper_filter)\n                except ValueError as e:\n                    raise exceptions.OptionsError(str(e)) from e\n            else:\n                self.filter = None\n\n    def echo(self, text: str, ident=None, **style):\n        if ident:\n            text = indent(ident, text)\n        click.secho(text, file=self.outfp, err=False, **style)\n        if self.outfp:\n            self.outfp.flush()\n\n    def _echo_headers(self, headers: http.Headers):\n        for k, v in headers.fields:\n            ks = strutils.bytes_to_escaped_str(k)\n            vs = strutils.bytes_to_escaped_str(v)\n            out = \"{}: {}\".format(\n                click.style(ks, fg=\"blue\"),\n                click.style(vs)\n            )\n            self.echo(out, ident=4)\n\n    def _echo_trailers(self, trailers: Optional[http.Headers]):\n        if not trailers:\n            return\n        self.echo(click.style(\"--- HTTP Trailers\", fg=\"magenta\"), ident=4)\n        self._echo_headers(trailers)\n\n    def _echo_message(\n        self,\n        message: Union[http.Message, TCPMessage, WebSocketMessage],\n        flow: Union[http.HTTPFlow, TCPFlow]\n    ):\n        _, lines, error = contentviews.get_message_content_view(\n            ctx.options.dumper_default_contentview,\n            message,\n            flow\n        )\n        if error:\n            ctx.log.debug(error)\n\n        if ctx.options.flow_detail == 3:\n            lines_to_echo = itertools.islice(lines, 70)\n        else:\n            lines_to_echo = lines\n\n        styles = dict(\n            highlight=dict(bold=True),\n            offset=dict(fg=\"blue\"),\n            header=dict(fg=\"green\", bold=True),\n            text=dict(fg=\"green\")\n        )\n\n        content = \"\\r\\n\".join(\n            \"\".join(colorful(line, styles)) for line in lines_to_echo\n        )\n        if content:\n            self.echo(\"\")\n            self.echo(content)\n\n        if next(lines, None):\n            self.echo(\"(cut off)\", ident=4, dim=True)\n\n        if ctx.options.flow_detail >= 2:\n            self.echo(\"\")\n\n    def _echo_request_line(self, flow: http.HTTPFlow) -> None:\n        if flow.is_replay == \"request\":\n            client = click.style(\"[replay]\", fg=\"yellow\", bold=True)\n        elif flow.client_conn.peername:\n            client = click.style(\n                strutils.escape_control_characters(\n                    human.format_address(flow.client_conn.peername)\n                )\n            )\n        else:  # pragma: no cover\n            # this should not happen, but we're defensive here.\n            client = \"\"\n\n        pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else ''\n        method = flow.request.method + pushed\n        method_color = dict(\n            GET=\"green\",\n            DELETE=\"red\"\n        ).get(method.upper(), \"magenta\")\n        method = click.style(\n            strutils.escape_control_characters(method),\n            fg=method_color,\n            bold=True\n        )\n        if ctx.options.showhost:\n            url = flow.request.pretty_url\n        else:\n            url = flow.request.url\n\n        if ctx.options.flow_detail <= 1:\n            # We need to truncate before applying styles, so we just focus on the URL.\n            terminal_width_limit = max(shutil.get_terminal_size()[0] - 25, 50)\n            if len(url) > terminal_width_limit:\n                url = url[:terminal_width_limit] + \"\u2026\"\n        url = click.style(strutils.escape_control_characters(url), bold=True)\n\n        http_version = \"\"\n        if (\n            not (flow.request.is_http10 or flow.request.is_http11)\n            or flow.request.http_version != getattr(flow.response, \"http_version\", \"HTTP/1.1\")\n        ):\n            # Hide version for h1 <-> h1 connections.\n            http_version = \" \" + flow.request.http_version\n\n        self.echo(f\"{client}: {method} {url}{http_version}\")\n\n    def _echo_response_line(self, flow: http.HTTPFlow) -> None:\n        if flow.is_replay == \"response\":\n            replay_str = \"[replay]\"\n            replay = click.style(replay_str, fg=\"yellow\", bold=True)\n        else:\n            replay_str = \"\"\n            replay = \"\"\n\n        assert flow.response\n        code_int = flow.response.status_code\n        code_color = None\n        if 200 <= code_int < 300:\n            code_color = \"green\"\n        elif 300 <= code_int < 400:\n            code_color = \"magenta\"\n        elif 400 <= code_int < 600:\n            code_color = \"red\"\n        code = click.style(\n            str(code_int),\n            fg=code_color,\n            bold=True,\n            blink=(code_int == 418),\n        )\n\n        if not flow.response.is_http2:\n            reason = flow.response.reason\n        else:\n            reason = http.status_codes.RESPONSES.get(flow.response.status_code, \"\")\n        reason = click.style(\n            strutils.escape_control_characters(reason),\n            fg=code_color,\n            bold=True\n        )\n\n        if flow.response.raw_content is None:\n            size = \"(content missing)\"\n        else:\n            size = human.pretty_size(len(flow.response.raw_content))\n        size = click.style(size, bold=True)\n\n        http_version = \"\"\n        if (\n            not (flow.response.is_http10 or flow.response.is_http11)\n            or flow.request.http_version != flow.response.http_version\n        ):\n            # Hide version for h1 <-> h1 connections.\n            http_version = f\"{flow.response.http_version} \"\n\n        arrows = click.style(\" <<\", bold=True)\n        if ctx.options.flow_detail == 1:\n            # This aligns the HTTP response code with the HTTP request method:\n            # 127.0.0.1:59519: GET http://example.com/\n            #               << 304 Not Modified 0b\n            pad = max(0,\n                      len(human.format_address(flow.client_conn.peername)) - (2 + len(http_version) + len(replay_str)))\n            arrows = \" \" * pad + arrows\n\n        self.echo(f\"{replay}{arrows} {http_version}{code} {reason} {size}\")\n\n    def echo_flow(self, f: http.HTTPFlow) -> None:\n        if f.request:\n            self._echo_request_line(f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_headers(f.request.headers)\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(f.request, f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_trailers(f.request.trailers)\n\n        if f.response:\n            self._echo_response_line(f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_headers(f.response.headers)\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(f.response, f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_trailers(f.response.trailers)\n\n        if f.error:\n            msg = strutils.escape_control_characters(f.error.msg)\n            self.echo(f\" << {msg}\", bold=True, fg=\"red\")\n\n    def match(self, f):\n        if ctx.options.flow_detail == 0:\n            return False\n        if not self.filter:\n            return True\n        elif flowfilter.match(self.filter, f):\n            return True\n        return False\n\n    def response(self, f):\n        if self.match(f):\n            self.echo_flow(f)\n\n    def error(self, f):\n        if self.match(f):\n            self.echo_flow(f)\n\n    def websocket_message(self, f: http.HTTPFlow):\n        assert f.websocket is not None  # satisfy type checker\n        if self.match(f):\n            message = f.websocket.messages[-1]\n\n            direction = \"->\" if message.from_client else \"<-\"\n            self.echo(\n                f\"{human.format_address(f.client_conn.peername)} \"\n                f\"{direction} WebSocket {message.type.name.lower()} message \"\n                f\"{direction} {human.format_address(f.server_conn.address)}{f.request.path}\"\n            )\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(message, f)\n\n    def websocket_end(self, f: http.HTTPFlow):\n        assert f.websocket is not None  # satisfy type checker\n        if self.match(f):\n            if f.websocket.close_code in {1000, 1001, 1005}:\n                c = 'client' if f.websocket.closed_by_client else 'server'\n                self.echo(f\"WebSocket connection closed by {c}: {f.websocket.close_code} {f.websocket.close_reason}\")\n            else:\n                error = flow.Error(f\"WebSocket Error: {self.format_websocket_error(f.websocket)}\")\n                self.echo(\n                    f\"Error in WebSocket connection to {human.format_address(f.server_conn.address)}: {error}\",\n                    fg=\"red\"\n                )\n\n    def format_websocket_error(self, websocket: WebSocketData) -> str:\n        try:\n            ret = CloseReason(websocket.close_code).name\n        except ValueError:\n            ret = f\"UNKNOWN_ERROR={websocket.close_code}\"\n        if websocket.close_reason:\n            ret += f\" (reason: {websocket.close_reason})\"\n        return ret\n\n    def tcp_error(self, f):\n        if self.match(f):\n            self.echo(\n                f\"Error in TCP connection to {human.format_address(f.server_conn.address)}: {f.error}\",\n                fg=\"red\"\n            )\n\n    def tcp_message(self, f):\n        if self.match(f):\n            message = f.messages[-1]\n            direction = \"->\" if message.from_client else \"<-\"\n            self.echo(\"{client} {direction} tcp {direction} {server}\".format(\n                client=human.format_address(f.client_conn.peername),\n                server=human.format_address(f.server_conn.address),\n                direction=direction,\n            ))\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(message, f)\n", "path": "mitmproxy/addons/dumper.py"}], "after_files": [{"content": "import itertools\nimport shutil\nfrom typing import IO, Optional, Union\n\nimport click\n\nfrom mitmproxy import contentviews\nfrom mitmproxy import ctx\nfrom mitmproxy import exceptions\nfrom mitmproxy import flowfilter\nfrom mitmproxy import http\nfrom mitmproxy import flow\nfrom mitmproxy.tcp import TCPFlow, TCPMessage\nfrom mitmproxy.utils import human\nfrom mitmproxy.utils import strutils\nfrom mitmproxy.websocket import WebSocketMessage, WebSocketData\nfrom wsproto.frame_protocol import CloseReason\n\n\ndef indent(n: int, text: str) -> str:\n    l = str(text).strip().splitlines()\n    pad = \" \" * n\n    return \"\\n\".join(pad + i for i in l)\n\n\ndef colorful(line, styles):\n    yield \"    \"  # we can already indent here\n    for (style, text) in line:\n        yield click.style(text, **styles.get(style, {}))\n\n\nclass Dumper:\n    def __init__(self, outfile=None):\n        self.filter: Optional[flowfilter.TFilter] = None\n        self.outfp: Optional[IO] = outfile\n\n    def load(self, loader):\n        loader.add_option(\n            \"flow_detail\", int, 1,\n            \"\"\"\n            The display detail level for flows in mitmdump: 0 (quiet) to 4 (very verbose).\n              0: no output\n              1: shortened request URL with response status code\n              2: full request URL with response status code and HTTP headers\n              3: 2 + truncated response content, content of WebSocket and TCP messages\n              4: 3 + nothing is truncated\n            \"\"\"\n        )\n        loader.add_option(\n            \"dumper_default_contentview\", str, \"auto\",\n            \"The default content view mode.\",\n            choices=[i.name.lower() for i in contentviews.views]\n        )\n        loader.add_option(\n            \"dumper_filter\", Optional[str], None,\n            \"Limit which flows are dumped.\"\n        )\n\n    def configure(self, updated):\n        if \"dumper_filter\" in updated:\n            if ctx.options.dumper_filter:\n                try:\n                    self.filter = flowfilter.parse(ctx.options.dumper_filter)\n                except ValueError as e:\n                    raise exceptions.OptionsError(str(e)) from e\n            else:\n                self.filter = None\n\n    def echo(self, text: str, ident=None, **style):\n        if ident:\n            text = indent(ident, text)\n        click.secho(text, file=self.outfp, err=False, **style)\n        if self.outfp:\n            self.outfp.flush()\n\n    def _echo_headers(self, headers: http.Headers):\n        for k, v in headers.fields:\n            ks = strutils.bytes_to_escaped_str(k)\n            vs = strutils.bytes_to_escaped_str(v)\n            out = \"{}: {}\".format(\n                click.style(ks, fg=\"blue\"),\n                click.style(vs)\n            )\n            self.echo(out, ident=4)\n\n    def _echo_trailers(self, trailers: Optional[http.Headers]):\n        if not trailers:\n            return\n        self.echo(click.style(\"--- HTTP Trailers\", fg=\"magenta\"), ident=4)\n        self._echo_headers(trailers)\n\n    def _echo_message(\n        self,\n        message: Union[http.Message, TCPMessage, WebSocketMessage],\n        flow: Union[http.HTTPFlow, TCPFlow]\n    ):\n        _, lines, error = contentviews.get_message_content_view(\n            ctx.options.dumper_default_contentview,\n            message,\n            flow\n        )\n        if error:\n            ctx.log.debug(error)\n\n        if ctx.options.flow_detail == 3:\n            lines_to_echo = itertools.islice(lines, 70)\n        else:\n            lines_to_echo = lines\n\n        styles = dict(\n            highlight=dict(bold=True),\n            offset=dict(fg=\"blue\"),\n            header=dict(fg=\"green\", bold=True),\n            text=dict(fg=\"green\")\n        )\n\n        content = \"\\r\\n\".join(\n            \"\".join(colorful(line, styles)) for line in lines_to_echo\n        )\n        if content:\n            self.echo(\"\")\n            self.echo(content)\n\n        if next(lines, None):\n            self.echo(\"(cut off)\", ident=4, dim=True)\n\n        if ctx.options.flow_detail >= 2:\n            self.echo(\"\")\n\n    def _echo_request_line(self, flow: http.HTTPFlow) -> None:\n        if flow.is_replay == \"request\":\n            client = click.style(\"[replay]\", fg=\"yellow\", bold=True)\n        elif flow.client_conn.peername:\n            client = click.style(\n                strutils.escape_control_characters(\n                    human.format_address(flow.client_conn.peername)\n                )\n            )\n        else:  # pragma: no cover\n            # this should not happen, but we're defensive here.\n            client = \"\"\n\n        pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else ''\n        method = flow.request.method + pushed\n        method_color = dict(\n            GET=\"green\",\n            DELETE=\"red\"\n        ).get(method.upper(), \"magenta\")\n        method = click.style(\n            strutils.escape_control_characters(method),\n            fg=method_color,\n            bold=True\n        )\n        if ctx.options.showhost:\n            url = flow.request.pretty_url\n        else:\n            url = flow.request.url\n\n        if ctx.options.flow_detail == 1:\n            # We need to truncate before applying styles, so we just focus on the URL.\n            terminal_width_limit = max(shutil.get_terminal_size()[0] - 25, 50)\n            if len(url) > terminal_width_limit:\n                url = url[:terminal_width_limit] + \"\u2026\"\n        url = click.style(strutils.escape_control_characters(url), bold=True)\n\n        http_version = \"\"\n        if (\n            not (flow.request.is_http10 or flow.request.is_http11)\n            or flow.request.http_version != getattr(flow.response, \"http_version\", \"HTTP/1.1\")\n        ):\n            # Hide version for h1 <-> h1 connections.\n            http_version = \" \" + flow.request.http_version\n\n        self.echo(f\"{client}: {method} {url}{http_version}\")\n\n    def _echo_response_line(self, flow: http.HTTPFlow) -> None:\n        if flow.is_replay == \"response\":\n            replay_str = \"[replay]\"\n            replay = click.style(replay_str, fg=\"yellow\", bold=True)\n        else:\n            replay_str = \"\"\n            replay = \"\"\n\n        assert flow.response\n        code_int = flow.response.status_code\n        code_color = None\n        if 200 <= code_int < 300:\n            code_color = \"green\"\n        elif 300 <= code_int < 400:\n            code_color = \"magenta\"\n        elif 400 <= code_int < 600:\n            code_color = \"red\"\n        code = click.style(\n            str(code_int),\n            fg=code_color,\n            bold=True,\n            blink=(code_int == 418),\n        )\n\n        if not flow.response.is_http2:\n            reason = flow.response.reason\n        else:\n            reason = http.status_codes.RESPONSES.get(flow.response.status_code, \"\")\n        reason = click.style(\n            strutils.escape_control_characters(reason),\n            fg=code_color,\n            bold=True\n        )\n\n        if flow.response.raw_content is None:\n            size = \"(content missing)\"\n        else:\n            size = human.pretty_size(len(flow.response.raw_content))\n        size = click.style(size, bold=True)\n\n        http_version = \"\"\n        if (\n            not (flow.response.is_http10 or flow.response.is_http11)\n            or flow.request.http_version != flow.response.http_version\n        ):\n            # Hide version for h1 <-> h1 connections.\n            http_version = f\"{flow.response.http_version} \"\n\n        arrows = click.style(\" <<\", bold=True)\n        if ctx.options.flow_detail == 1:\n            # This aligns the HTTP response code with the HTTP request method:\n            # 127.0.0.1:59519: GET http://example.com/\n            #               << 304 Not Modified 0b\n            pad = max(0,\n                      len(human.format_address(flow.client_conn.peername)) - (2 + len(http_version) + len(replay_str)))\n            arrows = \" \" * pad + arrows\n\n        self.echo(f\"{replay}{arrows} {http_version}{code} {reason} {size}\")\n\n    def echo_flow(self, f: http.HTTPFlow) -> None:\n        if f.request:\n            self._echo_request_line(f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_headers(f.request.headers)\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(f.request, f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_trailers(f.request.trailers)\n\n        if f.response:\n            self._echo_response_line(f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_headers(f.response.headers)\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(f.response, f)\n            if ctx.options.flow_detail >= 2:\n                self._echo_trailers(f.response.trailers)\n\n        if f.error:\n            msg = strutils.escape_control_characters(f.error.msg)\n            self.echo(f\" << {msg}\", bold=True, fg=\"red\")\n\n    def match(self, f):\n        if ctx.options.flow_detail == 0:\n            return False\n        if not self.filter:\n            return True\n        elif flowfilter.match(self.filter, f):\n            return True\n        return False\n\n    def response(self, f):\n        if self.match(f):\n            self.echo_flow(f)\n\n    def error(self, f):\n        if self.match(f):\n            self.echo_flow(f)\n\n    def websocket_message(self, f: http.HTTPFlow):\n        assert f.websocket is not None  # satisfy type checker\n        if self.match(f):\n            message = f.websocket.messages[-1]\n\n            direction = \"->\" if message.from_client else \"<-\"\n            self.echo(\n                f\"{human.format_address(f.client_conn.peername)} \"\n                f\"{direction} WebSocket {message.type.name.lower()} message \"\n                f\"{direction} {human.format_address(f.server_conn.address)}{f.request.path}\"\n            )\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(message, f)\n\n    def websocket_end(self, f: http.HTTPFlow):\n        assert f.websocket is not None  # satisfy type checker\n        if self.match(f):\n            if f.websocket.close_code in {1000, 1001, 1005}:\n                c = 'client' if f.websocket.closed_by_client else 'server'\n                self.echo(f\"WebSocket connection closed by {c}: {f.websocket.close_code} {f.websocket.close_reason}\")\n            else:\n                error = flow.Error(f\"WebSocket Error: {self.format_websocket_error(f.websocket)}\")\n                self.echo(\n                    f\"Error in WebSocket connection to {human.format_address(f.server_conn.address)}: {error}\",\n                    fg=\"red\"\n                )\n\n    def format_websocket_error(self, websocket: WebSocketData) -> str:\n        try:\n            ret = CloseReason(websocket.close_code).name\n        except ValueError:\n            ret = f\"UNKNOWN_ERROR={websocket.close_code}\"\n        if websocket.close_reason:\n            ret += f\" (reason: {websocket.close_reason})\"\n        return ret\n\n    def tcp_error(self, f):\n        if self.match(f):\n            self.echo(\n                f\"Error in TCP connection to {human.format_address(f.server_conn.address)}: {f.error}\",\n                fg=\"red\"\n            )\n\n    def tcp_message(self, f):\n        if self.match(f):\n            message = f.messages[-1]\n            direction = \"->\" if message.from_client else \"<-\"\n            self.echo(\"{client} {direction} tcp {direction} {server}\".format(\n                client=human.format_address(f.client_conn.peername),\n                server=human.format_address(f.server_conn.address),\n                direction=direction,\n            ))\n            if ctx.options.flow_detail >= 3:\n                self._echo_message(message, f)\n", "path": "mitmproxy/addons/dumper.py"}]}