mirror of
https://github.com/vee1e/mitmproxy.git
synced 2026-09-02 18:57:22 +00:00
Add content view syntax highlighting for JSON and msgpack (#5648)
* feat(contentview): add json syntax highlighting * feat(contentview): add msgpack syntax highlighting * fix(contentview): address pipeline failures * feat(contentview): switch to pygment-like token names * feat(mitmproxy): update cli syntax highlighting * refactor: respond to review comments * nits Co-authored-by: Maximilian Hils <git@maximilianhils.com> Co-authored-by: Maximilian Hils <github@maximilianhils.com>
This commit is contained in:
parent
42df073f7e
commit
0bd36e7313
7 changed files with 151 additions and 41 deletions
|
|
@ -45,6 +45,8 @@
|
|||
([#5562](https://github.com/mitmproxy/mitmproxy/pull/5562), @decathorpe, @mhils)
|
||||
* Fix mitmweb not properly opening a browser and being stuck on some Linux.
|
||||
([#5522](https://github.com/mitmproxy/mitmproxy/issues/5522), @Prinzhorn)
|
||||
* Add syntax highlighting to JSON and msgpack content view.
|
||||
([#5623](https://github.com/mitmproxy/mitmproxy/issues/5623), @SapiensAnatis)
|
||||
* Fix race condition when updating mitmweb WebSocket connections that are closing.
|
||||
([#5405](https://github.com/mitmproxy/mitmproxy/issues/5405), @mhils)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,14 @@ def format_json(data: Any) -> Iterator[base.TViewLine]:
|
|||
yield current_line
|
||||
current_line = []
|
||||
if re.match(r'\s*"', chunk):
|
||||
current_line.append(("json_string", chunk))
|
||||
if len(current_line) == 1 and current_line[0][0] == "text" and current_line[0][1].isspace():
|
||||
current_line.append(("Token_Name_Tag", chunk))
|
||||
else:
|
||||
current_line.append(("Token_Literal_String", chunk))
|
||||
elif re.match(r"\s*\d", chunk):
|
||||
current_line.append(("json_number", chunk))
|
||||
current_line.append(("Token_Literal_Number", chunk))
|
||||
elif re.match(r"\s*(true|null|false)", chunk):
|
||||
current_line.append(("json_boolean", chunk))
|
||||
current_line.append(("Token_Keyword_Constant", chunk))
|
||||
else:
|
||||
current_line.append(("text", chunk))
|
||||
yield current_line
|
||||
|
|
|
|||
|
|
@ -15,23 +15,62 @@ def parse_msgpack(s: bytes) -> Any:
|
|||
return PARSE_ERROR
|
||||
|
||||
|
||||
def pretty(value, htchar=" ", lfchar="\n", indent=0):
|
||||
nlch = lfchar + htchar * (indent + 1)
|
||||
if type(value) is dict:
|
||||
items = [
|
||||
nlch + repr(key) + ": " + pretty(value[key], htchar, lfchar, indent + 1)
|
||||
for key in value
|
||||
]
|
||||
return "{%s}" % (",".join(items) + lfchar + htchar * indent)
|
||||
elif type(value) is list:
|
||||
items = [nlch + pretty(item, htchar, lfchar, indent + 1) for item in value]
|
||||
return "[%s]" % (",".join(items) + lfchar + htchar * indent)
|
||||
def format_msgpack(data: Any, output = None, indent_count: int = 0) -> list[base.TViewLine]:
|
||||
if output is None:
|
||||
output = [[]]
|
||||
|
||||
indent = ("text", " " * indent_count)
|
||||
|
||||
if type(data) is str:
|
||||
token = [("Token_Literal_String", f"\"{data}\"")]
|
||||
output[-1] += token
|
||||
|
||||
# Need to return if single value, but return is discarded in dict/list loop
|
||||
return output
|
||||
|
||||
elif type(data) is float or type(data) is int:
|
||||
token = [("Token_Literal_Number", repr(data))]
|
||||
output[-1] += token
|
||||
|
||||
return output
|
||||
|
||||
elif type(data) is bool:
|
||||
token = [("Token_Keyword_Constant", repr(data))]
|
||||
output[-1] += token
|
||||
|
||||
return output
|
||||
|
||||
elif type(data) is dict:
|
||||
output[-1] += [("text", "{")]
|
||||
for key in data:
|
||||
output.append([indent, ("text", " "), ("Token_Name_Tag", f'"{key}"'), ("text", ": ")])
|
||||
format_msgpack(data[key], output, indent_count + 1)
|
||||
|
||||
if key != list(data)[-1]:
|
||||
output[-1] += [("text", ",")]
|
||||
|
||||
output.append([indent, ("text", "}")])
|
||||
|
||||
return output
|
||||
|
||||
elif type(data) is list:
|
||||
output[-1] += [("text", "[")]
|
||||
for item in data:
|
||||
output.append([indent, ("text", " ")])
|
||||
format_msgpack(item, output, indent_count + 1)
|
||||
|
||||
if item != data[-1]:
|
||||
output[-1] += [("text", ",")]
|
||||
|
||||
output.append([indent, ("text", "]")])
|
||||
|
||||
return output
|
||||
|
||||
else:
|
||||
return repr(value)
|
||||
token = [("text", repr(data))]
|
||||
output[-1] += token
|
||||
|
||||
|
||||
def format_msgpack(data):
|
||||
return base.format_text(pretty(data))
|
||||
return output
|
||||
|
||||
|
||||
class ViewMsgPack(base.View):
|
||||
|
|
|
|||
|
|
@ -69,10 +69,11 @@ class Palette:
|
|||
"mark",
|
||||
# Hex view
|
||||
"offset",
|
||||
# JSON view
|
||||
"json_string",
|
||||
"json_number",
|
||||
"json_boolean",
|
||||
# JSON/msgpack view
|
||||
"Token_Name_Tag",
|
||||
"Token_Literal_String",
|
||||
"Token_Literal_Number",
|
||||
"Token_Keyword_Constant",
|
||||
# TCP flow details
|
||||
"from_client",
|
||||
"to_client",
|
||||
|
|
@ -207,10 +208,11 @@ class LowDark(Palette):
|
|||
mark=("light red", "default"),
|
||||
# Hex view
|
||||
offset=("dark cyan", "default"),
|
||||
# JSON view
|
||||
json_string=("dark blue", "default"),
|
||||
json_number=("light magenta", "default"),
|
||||
json_boolean=("dark magenta", "default"),
|
||||
# JSON/msgpack view
|
||||
Token_Name_Tag=("dark green", "default"),
|
||||
Token_Literal_String=("dark blue", "default"),
|
||||
Token_Literal_Number=("light magenta", "default"),
|
||||
Token_Keyword_Constant=("dark magenta", "default"),
|
||||
# TCP flow details
|
||||
from_client=("light blue", "default"),
|
||||
to_client=("light red", "default"),
|
||||
|
|
@ -306,10 +308,11 @@ class LowLight(Palette):
|
|||
mark=("dark red", "default"),
|
||||
# Hex view
|
||||
offset=("dark blue", "default"),
|
||||
# JSON view
|
||||
json_string=("dark blue", "default"),
|
||||
json_number=("light magenta", "default"),
|
||||
json_boolean=("dark magenta", "default"),
|
||||
# JSON/msgpack view
|
||||
Token_Name_Tag=("dark green", "default"),
|
||||
Token_Literal_String=("dark blue", "default"),
|
||||
Token_Literal_Number=("light magenta", "default"),
|
||||
Token_Keyword_Constant=("dark magenta", "default"),
|
||||
# TCP flow details
|
||||
from_client=("dark blue", "default"),
|
||||
to_client=("dark red", "default"),
|
||||
|
|
@ -427,10 +430,11 @@ class SolarizedLight(LowLight):
|
|||
),
|
||||
# Hex view
|
||||
offset=(sol_cyan, "default"),
|
||||
# JSON view
|
||||
json_string=(sol_cyan, "default"),
|
||||
json_number=(sol_blue, "default"),
|
||||
json_boolean=(sol_magenta, "default"),
|
||||
# JSON/msgpack view
|
||||
Token_Name_Tag=(sol_green, "default"),
|
||||
Token_Literal_String=(sol_cyan, "default"),
|
||||
Token_Literal_Number=(sol_blue, "default"),
|
||||
Token_Keyword_Constant=(sol_magenta, "default"),
|
||||
# TCP flow details
|
||||
from_client=(sol_blue, "default"),
|
||||
to_client=(sol_red, "default"),
|
||||
|
|
@ -506,10 +510,11 @@ class SolarizedDark(LowDark):
|
|||
),
|
||||
# Hex view
|
||||
offset=(sol_cyan, "default"),
|
||||
# JSON view
|
||||
json_string=(sol_cyan, "default"),
|
||||
json_number=(sol_blue, "default"),
|
||||
json_boolean=(sol_magenta, "default"),
|
||||
# JSON/msgpack view
|
||||
Token_Name_Tag=(sol_green, "default"),
|
||||
Token_Literal_String=(sol_cyan, "default"),
|
||||
Token_Literal_Number=(sol_blue, "default"),
|
||||
Token_Keyword_Constant=(sol_magenta, "default"),
|
||||
# TCP flow details
|
||||
from_client=(sol_blue, "default"),
|
||||
to_client=(sol_red, "default"),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,32 @@ def test_parse_json():
|
|||
|
||||
def test_format_json():
|
||||
assert list(json.format_json({"data": ["str", 42, True, False, None, {}, []]}))
|
||||
assert list(json.format_json({"string": "test"})) == [
|
||||
[('text', '{'), ('text', '')],
|
||||
[('text', ' '), ('Token_Name_Tag', '"string"'), ('text', ': '), ('Token_Literal_String', '"test"'), ('text', '')],
|
||||
[('text', ''), ('text', '}')]]
|
||||
assert list(json.format_json({"num": 4})) == [
|
||||
[('text', '{'), ('text', '')],
|
||||
[('text', ' '), ('Token_Name_Tag', '"num"'), ('text', ': '), ('Token_Literal_Number', '4'), ('text', '')],
|
||||
[('text', ''), ('text', '}')]]
|
||||
assert list(json.format_json({"bool": True})) == [
|
||||
[('text', '{'), ('text', '')],
|
||||
[('text', ' '), ('Token_Name_Tag', '"bool"'), ('text', ': '), ('Token_Keyword_Constant', 'true'), ('text', '')],
|
||||
[('text', ''), ('text', '}')]]
|
||||
assert list(json.format_json({"object": {"int": 1}})) == [
|
||||
[('text', '{'), ('text', '')],
|
||||
[('text', ' '), ('Token_Name_Tag', '"object"'), ('text', ': '), ('text', '{'), ('text', '')],
|
||||
[('text', ' '), ('Token_Name_Tag', '"int"'), ('text', ': '), ('Token_Literal_Number', '1'), ('text', '')],
|
||||
[('text', ' '), ('text', '}'), ('text', '')],
|
||||
[('text', ''), ('text', '}')]]
|
||||
assert list(json.format_json({"list": ["string", 1, True]})) == [
|
||||
[('text', '{'), ('text', '')],
|
||||
[('text', ' '), ('Token_Name_Tag', '"list"'), ('text', ': '), ('text', '[')],
|
||||
[('Token_Literal_String', ' "string"'), ('text', ',')],
|
||||
[('Token_Literal_Number', ' 1'), ('text', ',')],
|
||||
[('Token_Keyword_Constant', ' true'), ('text', '')],
|
||||
[('text', ' '), ('text', ']'), ('text', '')],
|
||||
[('text', ''), ('text', '}')]]
|
||||
|
||||
|
||||
def test_view_json():
|
||||
|
|
|
|||
|
|
@ -18,9 +18,32 @@ def test_parse_msgpack():
|
|||
|
||||
|
||||
def test_format_msgpack():
|
||||
assert list(
|
||||
msgpack.format_msgpack({"data": ["str", 42, True, False, None, {}, []]})
|
||||
)
|
||||
assert list(msgpack.format_msgpack({"string": "test", "int": 1, "float": 1.44, "bool": True})) == [
|
||||
[('text', '{')],
|
||||
[('text', ''), ('text', ' '), ('Token_Name_Tag', '"string"'), ('text', ': '), ('Token_Literal_String', '"test"'), ('text', ',')],
|
||||
[('text', ''), ('text', ' '), ('Token_Name_Tag', '"int"'), ('text', ': '), ('Token_Literal_Number', '1'), ('text', ',')],
|
||||
[('text', ''), ('text', ' '), ('Token_Name_Tag', '"float"'), ('text', ': '), ('Token_Literal_Number', '1.44'), ('text', ',')],
|
||||
[('text', ''), ('text', ' '), ('Token_Name_Tag', '"bool"'), ('text', ': '), ('Token_Keyword_Constant', 'True')],
|
||||
[('text', ''), ('text', '}')]
|
||||
]
|
||||
|
||||
assert list(msgpack.format_msgpack({"object": {"key": "value"}, "list": [1]})) == [
|
||||
[('text', '{')],
|
||||
[('text', ''), ('text', ' '), ('Token_Name_Tag', '"object"'), ('text', ': '), ('text', '{')],
|
||||
[('text', ' '), ('text', ' '), ('Token_Name_Tag', '"key"'), ('text', ': '), ('Token_Literal_String', '"value"')],
|
||||
[('text', ' '), ('text', '}'), ('text', ',')],
|
||||
[('text', ''), ('text', ' '), ('Token_Name_Tag', '"list"'), ('text', ': '), ('text', '[')],
|
||||
[('text', ' '), ('text', ' '), ('Token_Literal_Number', '1')],
|
||||
[('text', ' '), ('text', ']')],
|
||||
[('text', ''), ('text', '}')]]
|
||||
|
||||
assert list(msgpack.format_msgpack('string')) == [[('Token_Literal_String', '"string"')]]
|
||||
|
||||
assert list(msgpack.format_msgpack(1.2)) == [[('Token_Literal_Number', '1.2')]]
|
||||
|
||||
assert list(msgpack.format_msgpack(True)) == [[('Token_Keyword_Constant', 'True')]]
|
||||
|
||||
assert list(msgpack.format_msgpack(b'\x01\x02\x03')) == [[('text', "b'\\x01\\x02\\x03'")]]
|
||||
|
||||
|
||||
def test_view_msgpack():
|
||||
|
|
|
|||
|
|
@ -14,4 +14,16 @@
|
|||
.codeeditor{
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.Token_Name_Tag{
|
||||
color: darkgreen;
|
||||
}
|
||||
.Token_Literal_String{
|
||||
color: firebrick;
|
||||
}
|
||||
.Token_Literal_Number{
|
||||
color: purple;
|
||||
}
|
||||
.Token_Keyword_Constant{
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue