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.
This commit is contained in:
Maximilian Hils 2021-07-15 09:48:11 +02:00
parent e63437689e
commit fbb7d3e4c1
6 changed files with 23 additions and 56 deletions

View file

@ -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'(?<!\\)(\\\\)*"', lambda m: (m.group(1) or "") + '\\"', val) + '"'
return '"' + val.replace('"', r"\x22") + '"'
def unquote(x: str) -> 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"(?<!\\)(\\\\)*\\" + quote_char, lambda m: (m.group(1) or "") + quote_char, x[1:-1])
if len(x) > 1 and x[0] in "'\"" and x[0] == x[-1]:
return x[1:-1]
else:
return x

View file

@ -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]:

View file

@ -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")

View file

@ -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())

View file

@ -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"

View file

@ -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