Skip to content

Commit 9243ba4

Browse files
committed
fix vt code detection on Windows
1 parent 218c942 commit 9243ba4

8 files changed

Lines changed: 72 additions & 11 deletions

File tree

mitmproxy/addons/dumper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from mitmproxy.tcp import TCPFlow, TCPMessage
1616
from mitmproxy.utils import human
1717
from mitmproxy.utils import strutils
18+
from mitmproxy.utils import vt_codes
1819
from mitmproxy.websocket import WebSocketData, WebSocketMessage
1920

2021

@@ -36,7 +37,7 @@ class Dumper:
3637
def __init__(self, outfile: Optional[IO[str]] = None):
3738
self.filter: Optional[flowfilter.TFilter] = None
3839
self.outfp: IO[str] = outfile or sys.stdout
39-
self.isatty = self.outfp.isatty()
40+
self.out_has_vt_codes = vt_codes.ensure_supported(self.outfp)
4041

4142
def load(self, loader):
4243
loader.add_option(
@@ -71,7 +72,7 @@ def configure(self, updated):
7172
self.filter = None
7273

7374
def style(self, text: str, **style) -> str:
74-
if style and self.isatty:
75+
if style and self.out_has_vt_codes:
7576
text = miniclick.style(text, **style)
7677
return text
7778

mitmproxy/addons/termlog.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from mitmproxy import ctx
55
from mitmproxy import log
66
from mitmproxy.contrib import click as miniclick
7+
from mitmproxy.utils import vt_codes
78

89
LOG_COLORS = {'error': "red", 'warn': "yellow", 'alert': "magenta"}
910

@@ -15,9 +16,9 @@ def __init__(
1516
err: Optional[IO[str]] = None,
1617
):
1718
self.out_file: IO[str] = out or sys.stdout
18-
self.out_isatty = self.out_file.isatty()
19+
self.out_has_vt_codes = vt_codes.ensure_supported(self.out_file)
1920
self.err_file: IO[str] = err or sys.stderr
20-
self.err_isatty = self.err_file.isatty()
21+
self.err_has_vt_codes = vt_codes.ensure_supported(self.err_file)
2122

2223
def load(self, loader):
2324
loader.add_option(
@@ -30,13 +31,13 @@ def add_log(self, e: log.LogEntry):
3031
if log.log_tier(ctx.options.termlog_verbosity) >= log.log_tier(e.level):
3132
if e.level == "error":
3233
f = self.err_file
33-
isatty = self.err_isatty
34+
has_vt_codes = self.err_has_vt_codes
3435
else:
3536
f = self.out_file
36-
isatty = self.out_isatty
37+
has_vt_codes = self.out_has_vt_codes
3738

3839
msg = e.msg
39-
if isatty:
40+
if has_vt_codes:
4041
msg = miniclick.style(
4142
e.msg,
4243
fg=LOG_COLORS.get(e.level),

mitmproxy/contrib/urwid/win32.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# https://docs.microsoft.com/de-de/windows/console/getstdhandle
55
STD_INPUT_HANDLE = -10
66
STD_OUTPUT_HANDLE = -11
7+
STD_ERROR_HANDLE = -12
78

89
# https://docs.microsoft.com/de-de/windows/console/setconsolemode
910
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004

mitmproxy/utils/vt_codes.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
This module provides a method to detect if a given file object supports virtual terminal escape codes.
3+
"""
4+
import os
5+
import sys
6+
from typing import IO
7+
8+
if os.name == "nt":
9+
from ctypes import byref, windll # type: ignore
10+
from ctypes.wintypes import BOOL, DWORD, HANDLE, LPDWORD
11+
12+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
13+
STD_OUTPUT_HANDLE = -11
14+
STD_ERROR_HANDLE = -12
15+
16+
# https://docs.microsoft.com/de-de/windows/console/getstdhandle
17+
GetStdHandle = windll.kernel32.GetStdHandle
18+
GetStdHandle.argtypes = [DWORD]
19+
GetStdHandle.restype = HANDLE
20+
21+
# https://docs.microsoft.com/de-de/windows/console/getconsolemode
22+
GetConsoleMode = windll.kernel32.GetConsoleMode
23+
GetConsoleMode.argtypes = [HANDLE, LPDWORD]
24+
GetConsoleMode.restype = BOOL
25+
26+
# https://docs.microsoft.com/de-de/windows/console/setconsolemode
27+
SetConsoleMode = windll.kernel32.SetConsoleMode
28+
SetConsoleMode.argtypes = [HANDLE, DWORD]
29+
SetConsoleMode.restype = BOOL
30+
31+
def ensure_supported(f: IO[str]) -> bool:
32+
if not f.isatty():
33+
return False
34+
if f == sys.stdout:
35+
h = STD_OUTPUT_HANDLE
36+
elif f == sys.stderr:
37+
h = STD_ERROR_HANDLE
38+
else:
39+
return False
40+
41+
handle = GetStdHandle(h)
42+
console_mode = DWORD()
43+
ok = GetConsoleMode(handle, byref(console_mode))
44+
if not ok:
45+
return False
46+
47+
ok = SetConsoleMode(handle, console_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
48+
return ok
49+
else:
50+
def ensure_supported(f: IO[str]) -> bool:
51+
return f.isatty()

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,5 @@ exclude =
7070
mitmproxy/proxy/server.py
7171
mitmproxy/proxy/layers/tls.py
7272
mitmproxy/utils/bits.py
73+
mitmproxy/utils/vt_codes.py
7374
release/hooks

test/mitmproxy/addons/test_dumper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,9 @@ def test_http2():
243243

244244
def test_styling():
245245
sio = io.StringIO()
246-
sio.isatty = lambda: True
247246

248247
d = dumper.Dumper(sio)
248+
d.out_has_vt_codes = True
249249
with taddons.context(d):
250250
d.response(tflow.tflow(resp=True))
251251
assert "\x1b[" in sio.getvalue()

test/mitmproxy/addons/test_termlog.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ def test_output(capsys):
1919
assert err.strip().splitlines() == ["four"]
2020

2121

22-
def test_styling() -> None:
22+
def test_styling(monkeypatch) -> None:
2323
f = io.StringIO()
24-
f.isatty = lambda: True
2524
t = termlog.TermLog(out=f)
26-
25+
t.out_has_vt_codes = True
2726
with taddons.context(t) as tctx:
2827
tctx.configure(t)
2928
t.add_log(log.LogEntry("hello world", "info"))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import io
2+
3+
from mitmproxy.utils.vt_codes import ensure_supported
4+
5+
6+
def test_simple():
7+
assert not ensure_supported(io.StringIO())

0 commit comments

Comments
 (0)