From fbb7d3e4c15ef2f80ab0fda8fb0c0cc299497f0a Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 15 Jul 2021 09:48:11 +0200 Subject: [PATCH] commands: only accept escaped quotes instead of accepting quotation marks in strings, we now just accept \x22, which is then unescaped by the str type. This greatly simplifies the lexing and is more consistent from a user perspective. --- mitmproxy/command_lexer.py | 22 +++++------------ mitmproxy/types.py | 2 +- test/mitmproxy/test_command.py | 24 ------------------- test/mitmproxy/test_command_lexer.py | 5 ++-- test/mitmproxy/test_websocket.py | 2 +- .../tools/console/test_defaultkeys.py | 24 +++++++++---------- 6 files changed, 23 insertions(+), 56 deletions(-) diff --git a/mitmproxy/command_lexer.py b/mitmproxy/command_lexer.py index 48b5a6abd..2ba691dff 100644 --- a/mitmproxy/command_lexer.py +++ b/mitmproxy/command_lexer.py @@ -9,13 +9,9 @@ import pyparsing PartialQuotedString = pyparsing.Regex( re.compile( r''' - (["']) # start quote - (?: - (?:\\.) # escape sequence - | - (?!\1). # unescaped character that is not our quote nor the begin of an escape sequence. We can't use \1 in [] - )* - (?:\1|$) # end quote + "[^"]*(?:"|$) # double-quoted string that ends with double quote or EOF + | + '[^']*(?:'|$) # single-quoted string that ends with double quote or EOF ''', re.VERBOSE ) @@ -35,17 +31,11 @@ def quote(val: str) -> str: return f'"{val}"' if "'" not in val: return f"'{val}'" - return '"' + re.sub(r'(? str: - quote_char = "" - if len(x) > 1 and x.startswith('"') and x.endswith('"'): - quote_char = '"' - if len(x) > 1 and x.startswith("'") and x.endswith("'"): - quote_char = "'" - - if quote_char: - return re.sub(r"(? 1 and x[0] in "'\"" and x[0] == x[-1]: + return x[1:-1] else: return x diff --git a/mitmproxy/types.py b/mitmproxy/types.py index ca00b36c6..01d7ac47f 100644 --- a/mitmproxy/types.py +++ b/mitmproxy/types.py @@ -119,7 +119,7 @@ class _StrType(_BaseType): """, re.VERBOSE) @staticmethod - def _unescape(match: re.Match[str]) -> str: + def _unescape(match: re.Match) -> str: return codecs.decode(match.group(0), "unicode-escape") # type: ignore def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]: diff --git a/test/mitmproxy/test_command.py b/test/mitmproxy/test_command.py index 808cc18b0..a74503841 100644 --- a/test/mitmproxy/test_command.py +++ b/test/mitmproxy/test_command.py @@ -367,24 +367,6 @@ class TestCommand: ], [], ], - [ - r'cmd13 "a \"b\" c"', - [ - command.ParseResult(value="cmd13", type=mitmproxy.types.Cmd, valid=False), - command.ParseResult(value=" ", type=mitmproxy.types.Space, valid=True), - command.ParseResult(value=r'"a \"b\" c"', type=mitmproxy.types.Unknown, valid=False), - ], - [], - ], - [ - r"cmd14 'a \'b\' c'", - [ - command.ParseResult(value="cmd14", type=mitmproxy.types.Cmd, valid=False), - command.ParseResult(value=" ", type=mitmproxy.types.Space, valid=True), - command.ParseResult(value=r"'a \'b\' c'", type=mitmproxy.types.Unknown, valid=False), - ], - [], - ], [ " spaces_at_the_begining_are_not_stripped", [ @@ -436,12 +418,6 @@ def test_simple(): c.call("nonexistent") with pytest.raises(exceptions.CommandError, match="Unknown"): c.execute("\\") - with pytest.raises(exceptions.CommandError, match="Unknown"): - c.execute(r"\'") - with pytest.raises(exceptions.CommandError, match="Unknown"): - c.execute(r"\"") - with pytest.raises(exceptions.CommandError, match="Unknown"): - c.execute(r"\"") c.add("empty", a.empty) c.execute("empty") diff --git a/test/mitmproxy/test_command_lexer.py b/test/mitmproxy/test_command_lexer.py index e20e49fe6..f94255cdb 100644 --- a/test/mitmproxy/test_command_lexer.py +++ b/test/mitmproxy/test_command_lexer.py @@ -11,7 +11,6 @@ from mitmproxy import command_lexer ("'foo'", True), ('"foo"', True), ("'foo' bar'", False), - ("'foo\\' bar'", True), ("'foo' 'bar'", False), ("'foo'x", False), ('''"foo ''', True), @@ -52,8 +51,10 @@ def test_expr(test_input, expected): @example("'foo\\\\'") @example("\"foo\\'\"") @example("\"foo\\\\'\"") +@example('\'foo\\"\'') +@example(r"\\\foo") def test_quote_unquote_cycle(s): - assert command_lexer.unquote(command_lexer.quote(s)) == s + assert command_lexer.unquote(command_lexer.quote(s)).replace(r"\x22", '"') == s @given(text()) diff --git a/test/mitmproxy/test_websocket.py b/test/mitmproxy/test_websocket.py index a47f6e4c3..d7404f290 100644 --- a/test/mitmproxy/test_websocket.py +++ b/test/mitmproxy/test_websocket.py @@ -34,7 +34,7 @@ class TestWebSocketMessage: bin = websocket.WebSocketMessage(Opcode.BINARY, True, b"foo") assert txt.is_text - assert txt.text + assert txt.text == "foo" txt.text = "bar" assert txt.content == b"bar" diff --git a/test/mitmproxy/tools/console/test_defaultkeys.py b/test/mitmproxy/tools/console/test_defaultkeys.py index f87c20224..389a8eefe 100644 --- a/test/mitmproxy/tools/console/test_defaultkeys.py +++ b/test/mitmproxy/tools/console/test_defaultkeys.py @@ -20,18 +20,18 @@ async def test_commands_exist(): await m.load_flow(tflow()) for binding in km.bindings: - parsed, _ = command_manager.parse_partial(binding.command.strip()) - - cmd = parsed[0].value - args = [ - a.value for a in parsed[1:] - if a.type != mitmproxy.types.Space - ] - - assert cmd in m.commands.commands - - cmd_obj = m.commands.commands[cmd] try: + parsed, _ = command_manager.parse_partial(binding.command.strip()) + + cmd = parsed[0].value + args = [ + a.value for a in parsed[1:] + if a.type != mitmproxy.types.Space + ] + + assert cmd in m.commands.commands + + cmd_obj = m.commands.commands[cmd] cmd_obj.prepare_args(args) except Exception as e: - raise ValueError(f"Invalid command: {binding.command}") from e + raise ValueError(f"Invalid binding: {binding.command}") from e