We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
mailcap deprecated for Python 3.11 removed for Python 3.13
I've been following this Python dev mailing thread which proposed to deprecate `mailcap` from the standard library: https://mail.python.org/archives/list/python-dev@python.org/thread/EB2BS4DBWSTBIOPQL5QTBSIOBORWSCMJ/

After a code search I noticed that you were the only library I recognized which used this module: 
https://grep.app/search?q=import%20mailcap
https://github.com/mitmproxy/mitmproxy/blob/4f7f64c516341c586c9f8cb6593217ff3e359351/mitmproxy/tools/console/master.py#L2

Please be aware the Steering Council just accepted this deprecation proposal meaning the module will be deprecated for Python 3.11 removed for Python 3.13.

(I don't have any affiliation with this decision, I am just trying to proactively raise awareness)

--- 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/tools/console/master.py`
Content:
```
1 import asyncio
2 import mailcap
3 import mimetypes
4 import os
5 import os.path
6 import shlex
7 import shutil
8 import stat
9 import subprocess
10 import sys
11 import tempfile
12 import contextlib
13 import threading
14 
15 from tornado.platform.asyncio import AddThreadSelectorEventLoop
16 
17 import urwid
18 
19 from mitmproxy import addons
20 from mitmproxy import master
21 from mitmproxy import log
22 from mitmproxy.addons import errorcheck, intercept
23 from mitmproxy.addons import eventstore
24 from mitmproxy.addons import readfile
25 from mitmproxy.addons import view
26 from mitmproxy.contrib.tornado import patch_tornado
27 from mitmproxy.tools.console import consoleaddons
28 from mitmproxy.tools.console import defaultkeys
29 from mitmproxy.tools.console import keymap
30 from mitmproxy.tools.console import palettes
31 from mitmproxy.tools.console import signals
32 from mitmproxy.tools.console import window
33 
34 
35 class ConsoleMaster(master.Master):
36     def __init__(self, opts):
37         super().__init__(opts)
38 
39         self.view: view.View = view.View()
40         self.events = eventstore.EventStore()
41         self.events.sig_add.connect(self.sig_add_log)
42 
43         self.stream_path = None
44         self.keymap = keymap.Keymap(self)
45         defaultkeys.map(self.keymap)
46         self.options.errored.connect(self.options_error)
47 
48         self.view_stack = []
49 
50         self.addons.add(*addons.default_addons())
51         self.addons.add(
52             intercept.Intercept(),
53             self.view,
54             self.events,
55             readfile.ReadFile(),
56             consoleaddons.ConsoleAddon(self),
57             keymap.KeymapConfig(),
58             errorcheck.ErrorCheck(log_to_stderr=True),
59         )
60 
61         self.window = None
62 
63     def __setattr__(self, name, value):
64         super().__setattr__(name, value)
65         signals.update_settings.send(self)
66 
67     def options_error(self, opts, exc):
68         signals.status_message.send(message=str(exc), expire=1)
69 
70     def prompt_for_exit(self):
71         signals.status_prompt_onekey.send(
72             self,
73             prompt="Quit",
74             keys=(
75                 ("yes", "y"),
76                 ("no", "n"),
77             ),
78             callback=self.quit,
79         )
80 
81     def sig_add_log(self, event_store, entry: log.LogEntry):
82         if log.log_tier(self.options.console_eventlog_verbosity) < log.log_tier(
83             entry.level
84         ):
85             return
86         if entry.level in ("error", "warn", "alert"):
87             signals.status_message.send(
88                 message=(
89                     entry.level,
90                     f"{entry.level.title()}: {str(entry.msg).lstrip()}",
91                 ),
92                 expire=5,
93             )
94 
95     def sig_call_in(self, sender, seconds, callback, args=()):
96         def cb(*_):
97             return callback(*args)
98 
99         self.loop.set_alarm_in(seconds, cb)
100 
101     @contextlib.contextmanager
102     def uistopped(self):
103         self.loop.stop()
104         try:
105             yield
106         finally:
107             self.loop.start()
108             self.loop.screen_size = None
109             self.loop.draw_screen()
110 
111     def get_editor(self) -> str:
112         # based upon https://github.com/pallets/click/blob/main/src/click/_termui_impl.py
113         if m := os.environ.get("MITMPROXY_EDITOR"):
114             return m
115         if m := os.environ.get("EDITOR"):
116             return m
117         for editor in "sensible-editor", "nano", "vim":
118             if shutil.which(editor):
119                 return editor
120         if os.name == "nt":
121             return "notepad"
122         else:
123             return "vi"
124 
125     def spawn_editor(self, data):
126         text = not isinstance(data, bytes)
127         fd, name = tempfile.mkstemp("", "mitmproxy", text=text)
128         with open(fd, "w" if text else "wb") as f:
129             f.write(data)
130         c = self.get_editor()
131         cmd = shlex.split(c)
132         cmd.append(name)
133         with self.uistopped():
134             try:
135                 subprocess.call(cmd)
136             except:
137                 signals.status_message.send(message="Can't start editor: %s" % c)
138             else:
139                 with open(name, "r" if text else "rb") as f:
140                     data = f.read()
141         os.unlink(name)
142         return data
143 
144     def spawn_external_viewer(self, data, contenttype):
145         if contenttype:
146             contenttype = contenttype.split(";")[0]
147             ext = mimetypes.guess_extension(contenttype) or ""
148         else:
149             ext = ""
150         fd, name = tempfile.mkstemp(ext, "mproxy")
151         os.write(fd, data)
152         os.close(fd)
153 
154         # read-only to remind the user that this is a view function
155         os.chmod(name, stat.S_IREAD)
156 
157         cmd = None
158         shell = False
159 
160         if contenttype:
161             c = mailcap.getcaps()
162             cmd, _ = mailcap.findmatch(c, contenttype, filename=name)
163             if cmd:
164                 shell = True
165         if not cmd:
166             # hm which one should get priority?
167             c = (
168                 os.environ.get("MITMPROXY_EDITOR")
169                 or os.environ.get("PAGER")
170                 or os.environ.get("EDITOR")
171             )
172             if not c:
173                 c = "less"
174             cmd = shlex.split(c)
175             cmd.append(name)
176         with self.uistopped():
177             try:
178                 subprocess.call(cmd, shell=shell)
179             except:
180                 signals.status_message.send(
181                     message="Can't start external viewer: %s" % " ".join(c)
182                 )
183         # add a small delay before deletion so that the file is not removed before being loaded by the viewer
184         t = threading.Timer(1.0, os.unlink, args=[name])
185         t.start()
186 
187     def set_palette(self, opts, updated):
188         self.ui.register_palette(
189             palettes.palettes[opts.console_palette].palette(
190                 opts.console_palette_transparent
191             )
192         )
193         self.ui.clear()
194 
195     def inject_key(self, key):
196         self.loop.process_input([key])
197 
198     async def running(self) -> None:
199         if not sys.stdout.isatty():
200             print(
201                 "Error: mitmproxy's console interface requires a tty. "
202                 "Please run mitmproxy in an interactive shell environment.",
203                 file=sys.stderr,
204             )
205             sys.exit(1)
206 
207         if os.name != "nt" and "utf" not in urwid.detected_encoding.lower():
208             print(
209                 f"mitmproxy expects a UTF-8 console environment, not {urwid.detected_encoding!r}. "
210                 f"Set your LANG environment variable to something like en_US.UTF-8.",
211                 file=sys.stderr,
212             )
213             # Experimental (04/2022): We just don't exit here and see if/how that affects users.
214             # sys.exit(1)
215         urwid.set_encoding("utf8")
216 
217         signals.call_in.connect(self.sig_call_in)
218         self.ui = window.Screen()
219         self.ui.set_terminal_properties(256)
220         self.set_palette(self.options, None)
221         self.options.subscribe(
222             self.set_palette, ["console_palette", "console_palette_transparent"]
223         )
224 
225         loop = asyncio.get_running_loop()
226         if isinstance(loop, getattr(asyncio, "ProactorEventLoop", tuple())):
227             patch_tornado()
228             # fix for https://bugs.python.org/issue37373
229             loop = AddThreadSelectorEventLoop(loop)  # type: ignore
230         self.loop = urwid.MainLoop(
231             urwid.SolidFill("x"),
232             event_loop=urwid.AsyncioEventLoop(loop=loop),
233             screen=self.ui,
234             handle_mouse=self.options.console_mouse,
235         )
236         self.window = window.Window(self)
237         self.loop.widget = self.window
238         self.window.refresh()
239 
240         self.loop.start()
241 
242         await super().running()
243 
244     async def done(self):
245         self.loop.stop()
246         await super().done()
247 
248     def overlay(self, widget, **kwargs):
249         self.window.set_overlay(widget, **kwargs)
250 
251     def switch_view(self, name):
252         self.window.push(name)
253 
254     def quit(self, a):
255         if a != "n":
256             self.shutdown()
257
```
--- 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/tools/console/master.py b/mitmproxy/tools/console/master.py
--- a/mitmproxy/tools/console/master.py
+++ b/mitmproxy/tools/console/master.py
@@ -1,5 +1,4 @@
 import asyncio
-import mailcap
 import mimetypes
 import os
 import os.path
@@ -154,28 +153,20 @@
         # read-only to remind the user that this is a view function
         os.chmod(name, stat.S_IREAD)
 
-        cmd = None
-        shell = False
+        # hm which one should get priority?
+        c = (
+            os.environ.get("MITMPROXY_EDITOR")
+            or os.environ.get("PAGER")
+            or os.environ.get("EDITOR")
+        )
+        if not c:
+            c = "less"
+        cmd = shlex.split(c)
+        cmd.append(name)
 
-        if contenttype:
-            c = mailcap.getcaps()
-            cmd, _ = mailcap.findmatch(c, contenttype, filename=name)
-            if cmd:
-                shell = True
-        if not cmd:
-            # hm which one should get priority?
-            c = (
-                os.environ.get("MITMPROXY_EDITOR")
-                or os.environ.get("PAGER")
-                or os.environ.get("EDITOR")
-            )
-            if not c:
-                c = "less"
-            cmd = shlex.split(c)
-            cmd.append(name)
         with self.uistopped():
             try:
-                subprocess.call(cmd, shell=shell)
+                subprocess.call(cmd, shell=False)
             except:
                 signals.status_message.send(
                     message="Can't start external viewer: %s" % " ".join(c)

{"golden_diff": "diff --git a/mitmproxy/tools/console/master.py b/mitmproxy/tools/console/master.py\n--- a/mitmproxy/tools/console/master.py\n+++ b/mitmproxy/tools/console/master.py\n@@ -1,5 +1,4 @@\n import asyncio\n-import mailcap\n import mimetypes\n import os\n import os.path\n@@ -154,28 +153,20 @@\n         # read-only to remind the user that this is a view function\n         os.chmod(name, stat.S_IREAD)\n \n-        cmd = None\n-        shell = False\n+        # hm which one should get priority?\n+        c = (\n+            os.environ.get(\"MITMPROXY_EDITOR\")\n+            or os.environ.get(\"PAGER\")\n+            or os.environ.get(\"EDITOR\")\n+        )\n+        if not c:\n+            c = \"less\"\n+        cmd = shlex.split(c)\n+        cmd.append(name)\n \n-        if contenttype:\n-            c = mailcap.getcaps()\n-            cmd, _ = mailcap.findmatch(c, contenttype, filename=name)\n-            if cmd:\n-                shell = True\n-        if not cmd:\n-            # hm which one should get priority?\n-            c = (\n-                os.environ.get(\"MITMPROXY_EDITOR\")\n-                or os.environ.get(\"PAGER\")\n-                or os.environ.get(\"EDITOR\")\n-            )\n-            if not c:\n-                c = \"less\"\n-            cmd = shlex.split(c)\n-            cmd.append(name)\n         with self.uistopped():\n             try:\n-                subprocess.call(cmd, shell=shell)\n+                subprocess.call(cmd, shell=False)\n             except:\n                 signals.status_message.send(\n                     message=\"Can't start external viewer: %s\" % \" \".join(c)\n", "issue": "mailcap deprecated for Python 3.11 removed for Python 3.13\nI've been following this Python dev mailing thread which proposed to deprecate `mailcap` from the standard library: https://mail.python.org/archives/list/python-dev@python.org/thread/EB2BS4DBWSTBIOPQL5QTBSIOBORWSCMJ/\r\n\r\nAfter a code search I noticed that you were the only library I recognized which used this module: \r\nhttps://grep.app/search?q=import%20mailcap\r\nhttps://github.com/mitmproxy/mitmproxy/blob/4f7f64c516341c586c9f8cb6593217ff3e359351/mitmproxy/tools/console/master.py#L2\r\n\r\nPlease be aware the Steering Council just accepted this deprecation proposal meaning the module will be deprecated for Python 3.11 removed for Python 3.13.\r\n\r\n(I don't have any affiliation with this decision, I am just trying to proactively raise awareness)\n", "before_files": [{"content": "import asyncio\nimport mailcap\nimport mimetypes\nimport os\nimport os.path\nimport shlex\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport tempfile\nimport contextlib\nimport threading\n\nfrom tornado.platform.asyncio import AddThreadSelectorEventLoop\n\nimport urwid\n\nfrom mitmproxy import addons\nfrom mitmproxy import master\nfrom mitmproxy import log\nfrom mitmproxy.addons import errorcheck, intercept\nfrom mitmproxy.addons import eventstore\nfrom mitmproxy.addons import readfile\nfrom mitmproxy.addons import view\nfrom mitmproxy.contrib.tornado import patch_tornado\nfrom mitmproxy.tools.console import consoleaddons\nfrom mitmproxy.tools.console import defaultkeys\nfrom mitmproxy.tools.console import keymap\nfrom mitmproxy.tools.console import palettes\nfrom mitmproxy.tools.console import signals\nfrom mitmproxy.tools.console import window\n\n\nclass ConsoleMaster(master.Master):\n    def __init__(self, opts):\n        super().__init__(opts)\n\n        self.view: view.View = view.View()\n        self.events = eventstore.EventStore()\n        self.events.sig_add.connect(self.sig_add_log)\n\n        self.stream_path = None\n        self.keymap = keymap.Keymap(self)\n        defaultkeys.map(self.keymap)\n        self.options.errored.connect(self.options_error)\n\n        self.view_stack = []\n\n        self.addons.add(*addons.default_addons())\n        self.addons.add(\n            intercept.Intercept(),\n            self.view,\n            self.events,\n            readfile.ReadFile(),\n            consoleaddons.ConsoleAddon(self),\n            keymap.KeymapConfig(),\n            errorcheck.ErrorCheck(log_to_stderr=True),\n        )\n\n        self.window = None\n\n    def __setattr__(self, name, value):\n        super().__setattr__(name, value)\n        signals.update_settings.send(self)\n\n    def options_error(self, opts, exc):\n        signals.status_message.send(message=str(exc), expire=1)\n\n    def prompt_for_exit(self):\n        signals.status_prompt_onekey.send(\n            self,\n            prompt=\"Quit\",\n            keys=(\n                (\"yes\", \"y\"),\n                (\"no\", \"n\"),\n            ),\n            callback=self.quit,\n        )\n\n    def sig_add_log(self, event_store, entry: log.LogEntry):\n        if log.log_tier(self.options.console_eventlog_verbosity) < log.log_tier(\n            entry.level\n        ):\n            return\n        if entry.level in (\"error\", \"warn\", \"alert\"):\n            signals.status_message.send(\n                message=(\n                    entry.level,\n                    f\"{entry.level.title()}: {str(entry.msg).lstrip()}\",\n                ),\n                expire=5,\n            )\n\n    def sig_call_in(self, sender, seconds, callback, args=()):\n        def cb(*_):\n            return callback(*args)\n\n        self.loop.set_alarm_in(seconds, cb)\n\n    @contextlib.contextmanager\n    def uistopped(self):\n        self.loop.stop()\n        try:\n            yield\n        finally:\n            self.loop.start()\n            self.loop.screen_size = None\n            self.loop.draw_screen()\n\n    def get_editor(self) -> str:\n        # based upon https://github.com/pallets/click/blob/main/src/click/_termui_impl.py\n        if m := os.environ.get(\"MITMPROXY_EDITOR\"):\n            return m\n        if m := os.environ.get(\"EDITOR\"):\n            return m\n        for editor in \"sensible-editor\", \"nano\", \"vim\":\n            if shutil.which(editor):\n                return editor\n        if os.name == \"nt\":\n            return \"notepad\"\n        else:\n            return \"vi\"\n\n    def spawn_editor(self, data):\n        text = not isinstance(data, bytes)\n        fd, name = tempfile.mkstemp(\"\", \"mitmproxy\", text=text)\n        with open(fd, \"w\" if text else \"wb\") as f:\n            f.write(data)\n        c = self.get_editor()\n        cmd = shlex.split(c)\n        cmd.append(name)\n        with self.uistopped():\n            try:\n                subprocess.call(cmd)\n            except:\n                signals.status_message.send(message=\"Can't start editor: %s\" % c)\n            else:\n                with open(name, \"r\" if text else \"rb\") as f:\n                    data = f.read()\n        os.unlink(name)\n        return data\n\n    def spawn_external_viewer(self, data, contenttype):\n        if contenttype:\n            contenttype = contenttype.split(\";\")[0]\n            ext = mimetypes.guess_extension(contenttype) or \"\"\n        else:\n            ext = \"\"\n        fd, name = tempfile.mkstemp(ext, \"mproxy\")\n        os.write(fd, data)\n        os.close(fd)\n\n        # read-only to remind the user that this is a view function\n        os.chmod(name, stat.S_IREAD)\n\n        cmd = None\n        shell = False\n\n        if contenttype:\n            c = mailcap.getcaps()\n            cmd, _ = mailcap.findmatch(c, contenttype, filename=name)\n            if cmd:\n                shell = True\n        if not cmd:\n            # hm which one should get priority?\n            c = (\n                os.environ.get(\"MITMPROXY_EDITOR\")\n                or os.environ.get(\"PAGER\")\n                or os.environ.get(\"EDITOR\")\n            )\n            if not c:\n                c = \"less\"\n            cmd = shlex.split(c)\n            cmd.append(name)\n        with self.uistopped():\n            try:\n                subprocess.call(cmd, shell=shell)\n            except:\n                signals.status_message.send(\n                    message=\"Can't start external viewer: %s\" % \" \".join(c)\n                )\n        # add a small delay before deletion so that the file is not removed before being loaded by the viewer\n        t = threading.Timer(1.0, os.unlink, args=[name])\n        t.start()\n\n    def set_palette(self, opts, updated):\n        self.ui.register_palette(\n            palettes.palettes[opts.console_palette].palette(\n                opts.console_palette_transparent\n            )\n        )\n        self.ui.clear()\n\n    def inject_key(self, key):\n        self.loop.process_input([key])\n\n    async def running(self) -> None:\n        if not sys.stdout.isatty():\n            print(\n                \"Error: mitmproxy's console interface requires a tty. \"\n                \"Please run mitmproxy in an interactive shell environment.\",\n                file=sys.stderr,\n            )\n            sys.exit(1)\n\n        if os.name != \"nt\" and \"utf\" not in urwid.detected_encoding.lower():\n            print(\n                f\"mitmproxy expects a UTF-8 console environment, not {urwid.detected_encoding!r}. \"\n                f\"Set your LANG environment variable to something like en_US.UTF-8.\",\n                file=sys.stderr,\n            )\n            # Experimental (04/2022): We just don't exit here and see if/how that affects users.\n            # sys.exit(1)\n        urwid.set_encoding(\"utf8\")\n\n        signals.call_in.connect(self.sig_call_in)\n        self.ui = window.Screen()\n        self.ui.set_terminal_properties(256)\n        self.set_palette(self.options, None)\n        self.options.subscribe(\n            self.set_palette, [\"console_palette\", \"console_palette_transparent\"]\n        )\n\n        loop = asyncio.get_running_loop()\n        if isinstance(loop, getattr(asyncio, \"ProactorEventLoop\", tuple())):\n            patch_tornado()\n            # fix for https://bugs.python.org/issue37373\n            loop = AddThreadSelectorEventLoop(loop)  # type: ignore\n        self.loop = urwid.MainLoop(\n            urwid.SolidFill(\"x\"),\n            event_loop=urwid.AsyncioEventLoop(loop=loop),\n            screen=self.ui,\n            handle_mouse=self.options.console_mouse,\n        )\n        self.window = window.Window(self)\n        self.loop.widget = self.window\n        self.window.refresh()\n\n        self.loop.start()\n\n        await super().running()\n\n    async def done(self):\n        self.loop.stop()\n        await super().done()\n\n    def overlay(self, widget, **kwargs):\n        self.window.set_overlay(widget, **kwargs)\n\n    def switch_view(self, name):\n        self.window.push(name)\n\n    def quit(self, a):\n        if a != \"n\":\n            self.shutdown()\n", "path": "mitmproxy/tools/console/master.py"}], "after_files": [{"content": "import asyncio\nimport mimetypes\nimport os\nimport os.path\nimport shlex\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport tempfile\nimport contextlib\nimport threading\n\nfrom tornado.platform.asyncio import AddThreadSelectorEventLoop\n\nimport urwid\n\nfrom mitmproxy import addons\nfrom mitmproxy import master\nfrom mitmproxy import log\nfrom mitmproxy.addons import errorcheck, intercept\nfrom mitmproxy.addons import eventstore\nfrom mitmproxy.addons import readfile\nfrom mitmproxy.addons import view\nfrom mitmproxy.contrib.tornado import patch_tornado\nfrom mitmproxy.tools.console import consoleaddons\nfrom mitmproxy.tools.console import defaultkeys\nfrom mitmproxy.tools.console import keymap\nfrom mitmproxy.tools.console import palettes\nfrom mitmproxy.tools.console import signals\nfrom mitmproxy.tools.console import window\n\n\nclass ConsoleMaster(master.Master):\n    def __init__(self, opts):\n        super().__init__(opts)\n\n        self.view: view.View = view.View()\n        self.events = eventstore.EventStore()\n        self.events.sig_add.connect(self.sig_add_log)\n\n        self.stream_path = None\n        self.keymap = keymap.Keymap(self)\n        defaultkeys.map(self.keymap)\n        self.options.errored.connect(self.options_error)\n\n        self.view_stack = []\n\n        self.addons.add(*addons.default_addons())\n        self.addons.add(\n            intercept.Intercept(),\n            self.view,\n            self.events,\n            readfile.ReadFile(),\n            consoleaddons.ConsoleAddon(self),\n            keymap.KeymapConfig(),\n            errorcheck.ErrorCheck(log_to_stderr=True),\n        )\n\n        self.window = None\n\n    def __setattr__(self, name, value):\n        super().__setattr__(name, value)\n        signals.update_settings.send(self)\n\n    def options_error(self, opts, exc):\n        signals.status_message.send(message=str(exc), expire=1)\n\n    def prompt_for_exit(self):\n        signals.status_prompt_onekey.send(\n            self,\n            prompt=\"Quit\",\n            keys=(\n                (\"yes\", \"y\"),\n                (\"no\", \"n\"),\n            ),\n            callback=self.quit,\n        )\n\n    def sig_add_log(self, event_store, entry: log.LogEntry):\n        if log.log_tier(self.options.console_eventlog_verbosity) < log.log_tier(\n            entry.level\n        ):\n            return\n        if entry.level in (\"error\", \"warn\", \"alert\"):\n            signals.status_message.send(\n                message=(\n                    entry.level,\n                    f\"{entry.level.title()}: {str(entry.msg).lstrip()}\",\n                ),\n                expire=5,\n            )\n\n    def sig_call_in(self, sender, seconds, callback, args=()):\n        def cb(*_):\n            return callback(*args)\n\n        self.loop.set_alarm_in(seconds, cb)\n\n    @contextlib.contextmanager\n    def uistopped(self):\n        self.loop.stop()\n        try:\n            yield\n        finally:\n            self.loop.start()\n            self.loop.screen_size = None\n            self.loop.draw_screen()\n\n    def get_editor(self) -> str:\n        # based upon https://github.com/pallets/click/blob/main/src/click/_termui_impl.py\n        if m := os.environ.get(\"MITMPROXY_EDITOR\"):\n            return m\n        if m := os.environ.get(\"EDITOR\"):\n            return m\n        for editor in \"sensible-editor\", \"nano\", \"vim\":\n            if shutil.which(editor):\n                return editor\n        if os.name == \"nt\":\n            return \"notepad\"\n        else:\n            return \"vi\"\n\n    def spawn_editor(self, data):\n        text = not isinstance(data, bytes)\n        fd, name = tempfile.mkstemp(\"\", \"mitmproxy\", text=text)\n        with open(fd, \"w\" if text else \"wb\") as f:\n            f.write(data)\n        c = self.get_editor()\n        cmd = shlex.split(c)\n        cmd.append(name)\n        with self.uistopped():\n            try:\n                subprocess.call(cmd)\n            except:\n                signals.status_message.send(message=\"Can't start editor: %s\" % c)\n            else:\n                with open(name, \"r\" if text else \"rb\") as f:\n                    data = f.read()\n        os.unlink(name)\n        return data\n\n    def spawn_external_viewer(self, data, contenttype):\n        if contenttype:\n            contenttype = contenttype.split(\";\")[0]\n            ext = mimetypes.guess_extension(contenttype) or \"\"\n        else:\n            ext = \"\"\n        fd, name = tempfile.mkstemp(ext, \"mproxy\")\n        os.write(fd, data)\n        os.close(fd)\n\n        # read-only to remind the user that this is a view function\n        os.chmod(name, stat.S_IREAD)\n\n        # hm which one should get priority?\n        c = (\n            os.environ.get(\"MITMPROXY_EDITOR\")\n            or os.environ.get(\"PAGER\")\n            or os.environ.get(\"EDITOR\")\n        )\n        if not c:\n            c = \"less\"\n        cmd = shlex.split(c)\n        cmd.append(name)\n\n        with self.uistopped():\n            try:\n                subprocess.call(cmd, shell=False)\n            except:\n                signals.status_message.send(\n                    message=\"Can't start external viewer: %s\" % \" \".join(c)\n                )\n        # add a small delay before deletion so that the file is not removed before being loaded by the viewer\n        t = threading.Timer(1.0, os.unlink, args=[name])\n        t.start()\n\n    def set_palette(self, opts, updated):\n        self.ui.register_palette(\n            palettes.palettes[opts.console_palette].palette(\n                opts.console_palette_transparent\n            )\n        )\n        self.ui.clear()\n\n    def inject_key(self, key):\n        self.loop.process_input([key])\n\n    async def running(self) -> None:\n        if not sys.stdout.isatty():\n            print(\n                \"Error: mitmproxy's console interface requires a tty. \"\n                \"Please run mitmproxy in an interactive shell environment.\",\n                file=sys.stderr,\n            )\n            sys.exit(1)\n\n        if os.name != \"nt\" and \"utf\" not in urwid.detected_encoding.lower():\n            print(\n                f\"mitmproxy expects a UTF-8 console environment, not {urwid.detected_encoding!r}. \"\n                f\"Set your LANG environment variable to something like en_US.UTF-8.\",\n                file=sys.stderr,\n            )\n            # Experimental (04/2022): We just don't exit here and see if/how that affects users.\n            # sys.exit(1)\n        urwid.set_encoding(\"utf8\")\n\n        signals.call_in.connect(self.sig_call_in)\n        self.ui = window.Screen()\n        self.ui.set_terminal_properties(256)\n        self.set_palette(self.options, None)\n        self.options.subscribe(\n            self.set_palette, [\"console_palette\", \"console_palette_transparent\"]\n        )\n\n        loop = asyncio.get_running_loop()\n        if isinstance(loop, getattr(asyncio, \"ProactorEventLoop\", tuple())):\n            patch_tornado()\n            # fix for https://bugs.python.org/issue37373\n            loop = AddThreadSelectorEventLoop(loop)  # type: ignore\n        self.loop = urwid.MainLoop(\n            urwid.SolidFill(\"x\"),\n            event_loop=urwid.AsyncioEventLoop(loop=loop),\n            screen=self.ui,\n            handle_mouse=self.options.console_mouse,\n        )\n        self.window = window.Window(self)\n        self.loop.widget = self.window\n        self.window.refresh()\n\n        self.loop.start()\n\n        await super().running()\n\n    async def done(self):\n        self.loop.stop()\n        await super().done()\n\n    def overlay(self, widget, **kwargs):\n        self.window.set_overlay(widget, **kwargs)\n\n    def switch_view(self, name):\n        self.window.push(name)\n\n    def quit(self, a):\n        if a != \"n\":\n            self.shutdown()\n", "path": "mitmproxy/tools/console/master.py"}]}