diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c560846..e1181b8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/mitmproxy/contentviews/json.py b/mitmproxy/contentviews/json.py index a75ba83bb..d8952e80b 100644 --- a/mitmproxy/contentviews/json.py +++ b/mitmproxy/contentviews/json.py @@ -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 diff --git a/mitmproxy/contentviews/msgpack.py b/mitmproxy/contentviews/msgpack.py index 01f74e49a..7e845bd11 100644 --- a/mitmproxy/contentviews/msgpack.py +++ b/mitmproxy/contentviews/msgpack.py @@ -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): diff --git a/mitmproxy/tools/console/palettes.py b/mitmproxy/tools/console/palettes.py index 236553c1c..4e86258e2 100644 --- a/mitmproxy/tools/console/palettes.py +++ b/mitmproxy/tools/console/palettes.py @@ -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"), diff --git a/test/mitmproxy/contentviews/test_json.py b/test/mitmproxy/contentviews/test_json.py index 43565b230..5b3883060 100644 --- a/test/mitmproxy/contentviews/test_json.py +++ b/test/mitmproxy/contentviews/test_json.py @@ -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(): diff --git a/test/mitmproxy/contentviews/test_msgpack.py b/test/mitmproxy/contentviews/test_msgpack.py index 458dac810..eeba8b2d1 100644 --- a/test/mitmproxy/contentviews/test_msgpack.py +++ b/test/mitmproxy/contentviews/test_msgpack.py @@ -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(): diff --git a/web/src/css/contentview.less b/web/src/css/contentview.less index ec5efab8a..e483214ff 100644 --- a/web/src/css/contentview.less +++ b/web/src/css/contentview.less @@ -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; + } }