aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/command.py99
-rw-r--r--mitmproxy/tools/console/commander/commander.py62
-rw-r--r--test/mitmproxy/test_command.py167
-rw-r--r--test/mitmproxy/tools/console/test_commander.py19
-rw-r--r--test/mitmproxy/tools/console/test_defaultkeys.py13
5 files changed, 252 insertions, 108 deletions
diff --git a/mitmproxy/command.py b/mitmproxy/command.py
index 0998601c..c7a45587 100644
--- a/mitmproxy/command.py
+++ b/mitmproxy/command.py
@@ -3,12 +3,11 @@
"""
import inspect
import types
-import io
import typing
-import shlex
import textwrap
import functools
import sys
+import pyparsing
from mitmproxy import exceptions
import mitmproxy.types
@@ -22,15 +21,6 @@ def verify_arg_signature(f: typing.Callable, args: list, kwargs: dict) -> None:
raise exceptions.CommandError("command argument mismatch: %s" % v.args[0])
-def lexer(s):
- # mypy mis-identifies shlex.shlex as abstract
- lex = shlex.shlex(s, posix=True) # type: ignore
- lex.wordchars += "."
- lex.whitespace_split = True
- lex.commenters = ''
- return lex
-
-
def typename(t: type) -> str:
"""
Translates a type to an explanatory string.
@@ -86,6 +76,20 @@ class Command:
return "%s %s%s" % (self.path, params, ret)
def prepare_args(self, args: typing.Sequence[str]) -> typing.List[typing.Any]:
+
+ # Arguments that are just blank spaces aren't really arguments
+ # We need to get rid of those. If the user intended to pass a sequence
+ # of spaces, it would come between quotes
+ clean_args = []
+ for a in args:
+ if isinstance(a, str):
+ if a.strip() != '':
+ clean_args.append(a)
+ else:
+ clean_args.append(a)
+
+ args = clean_args
+
verify_arg_signature(self.func, list(args), {})
remainder: typing.Sequence[str] = []
@@ -159,23 +163,36 @@ class CommandManager(mitmproxy.types._CommandBase):
"""
Parse a possibly partial command. Return a sequence of ParseResults and a sequence of remainder type help items.
"""
- buf = io.StringIO(cmdstr)
parts: typing.List[str] = []
- lex = lexer(buf)
- while 1:
- remainder = cmdstr[buf.tell():]
- try:
- t = lex.get_token()
- except ValueError:
- parts.append(remainder)
- break
- if not t:
- break
- parts.append(t)
+
+ rex = pyparsing.QuotedString("\"", escChar='\\', unquoteResults=False) |\
+ pyparsing.QuotedString("'", escChar='\\', unquoteResults=False) |\
+ pyparsing.Combine(pyparsing.Literal('"') + pyparsing.Word(pyparsing.printables + " ") + pyparsing.StringEnd()) |\
+ pyparsing.Word(pyparsing.printables) |\
+ pyparsing.Word(' ')
+
+ rex = rex.copy().leaveWhitespace()
+
+ remainder = cmdstr
+
+ for t, start, end in rex.scanString(cmdstr):
+
+ remainder = cmdstr[end:]
+ parts.append(t[0])
+
+ if remainder != '':
+ parts.append(remainder)
+
if not parts:
- parts = [""]
- elif cmdstr.endswith(" "):
- parts.append("")
+ parts = []
+
+ # First item in parts has always to be the command
+ # so we remove any blank tokens from the start of it
+ while True:
+ if parts and parts[0].strip() == '':
+ del parts[0]
+ else:
+ break
parse: typing.List[ParseResult] = []
params: typing.List[type] = []
@@ -186,10 +203,15 @@ class CommandManager(mitmproxy.types._CommandBase):
if parts[i] in self.commands:
params.extend(self.commands[parts[i]].paramtypes)
elif params:
- typ = params.pop(0)
- if typ == mitmproxy.types.Cmd and params and params[0] == mitmproxy.types.Arg:
- if parts[i] in self.commands:
- params[:] = self.commands[parts[i]].paramtypes
+ if parts[i].strip() != '':
+ typ = params.pop(0)
+ if typ == mitmproxy.types.Cmd and params and params[0] == mitmproxy.types.Arg:
+ if parts[i] in self.commands:
+ params[:] = self.commands[parts[i]].paramtypes
+ else:
+ # If the token is just a bunch of spaces, then we don't
+ # want to count it against the arguments of the command
+ typ = mitmproxy.types.Unknown
else:
typ = mitmproxy.types.Unknown
@@ -203,6 +225,8 @@ class CommandManager(mitmproxy.types._CommandBase):
else:
valid = True
+ # if ctx.log:
+ # ctx.log.info('[gilga] before parse.append. value = %s' % parts[i])
parse.append(
ParseResult(
value=parts[i],
@@ -232,19 +256,22 @@ class CommandManager(mitmproxy.types._CommandBase):
"""
if path not in self.commands:
raise exceptions.CommandError("Unknown command: %s" % path)
+
return self.commands[path].call(args)
def execute(self, cmdstr: str):
"""
Execute a command string. May raise CommandError.
"""
- try:
- parts = list(lexer(cmdstr))
- except ValueError as e:
- raise exceptions.CommandError("Command error: %s" % e)
- if not len(parts) >= 1:
+ parts, _ = self.parse_partial(cmdstr)
+ params = []
+ for p in parts:
+ params.append(p.value)
+
+ if len(parts) == 0:
raise exceptions.CommandError("Invalid command: %s" % cmdstr)
- return self.call_strings(parts[0], parts[1:])
+
+ return self.call_strings(params[0], params[1:])
def dump(self, out=sys.stdout) -> None:
cmds = list(self.commands.values())
diff --git a/mitmproxy/tools/console/commander/commander.py b/mitmproxy/tools/console/commander/commander.py
index f291b8fd..4909348f 100644
--- a/mitmproxy/tools/console/commander/commander.py
+++ b/mitmproxy/tools/console/commander/commander.py
@@ -52,7 +52,7 @@ CompletionState = typing.NamedTuple(
class CommandBuffer:
def __init__(self, master: mitmproxy.master.Master, start: str = "") -> None:
self.master = master
- self.text = self.flatten(start)
+ self.text = start
# Cursor is always within the range [0:len(buffer)].
self._cursor = len(self.text)
self.completion: typing.Optional[CompletionState] = None
@@ -70,16 +70,11 @@ class CommandBuffer:
else:
self._cursor = x
- def maybequote(self, value):
- if " " in value and not value.startswith("\""):
- return "\"%s\"" % value
- return value
-
def parse_quoted(self, txt):
parts, remhelp = self.master.commands.parse_partial(txt)
for i, p in enumerate(parts):
parts[i] = mitmproxy.command.ParseResult(
- value = self.maybequote(p.value),
+ value = p.value,
type = p.type,
valid = p.valid
)
@@ -94,27 +89,29 @@ class CommandBuffer:
"""
parts, remhelp = self.parse_quoted(self.text)
ret = []
- for p in parts:
- if p.valid:
- if p.type == mitmproxy.types.Cmd:
- ret.append(("commander_command", p.value))
- else:
- ret.append(("text", p.value))
- elif p.value:
- ret.append(("commander_invalid", p.value))
- else:
- ret.append(("text", ""))
+ if parts == []:
+ # Means we just received the leader, so we need to give a blank
+ # text to the widget to render or it crashes
+ ret.append(("text", ""))
ret.append(("text", " "))
- if remhelp:
- ret.append(("text", " "))
- for v in remhelp:
- ret.append(("commander_hint", "%s " % v))
- return ret
+ else:
+ for p in parts:
+ if p.valid:
+ if p.type == mitmproxy.types.Cmd:
+ ret.append(("commander_command", p.value))
+ else:
+ ret.append(("text", p.value))
+ elif p.value:
+ ret.append(("commander_invalid", p.value))
+ else:
+ ret.append(("text", ""))
+
+ if remhelp:
+ ret.append(("text", " "))
+ for v in remhelp:
+ ret.append(("commander_hint", "%s " % v))
- def flatten(self, txt):
- parts, _ = self.parse_quoted(txt)
- ret = [x.value for x in parts]
- return " ".join(ret)
+ return ret
def left(self) -> None:
self.cursor = self.cursor - 1
@@ -139,13 +136,13 @@ class CommandBuffer:
nxt = self.completion.completer.cycle()
buf = " ".join([i.value for i in self.completion.parse[:-1]]) + " " + nxt
buf = buf.strip()
- self.text = self.flatten(buf)
+ self.text = buf
self.cursor = len(self.text)
def backspace(self) -> None:
if self.cursor == 0:
return
- self.text = self.flatten(self.text[:self.cursor - 1] + self.text[self.cursor:])
+ self.text = self.text[:self.cursor - 1] + self.text[self.cursor:]
self.cursor = self.cursor - 1
self.completion = None
@@ -153,8 +150,13 @@ class CommandBuffer:
"""
Inserts text at the cursor.
"""
- self.text = self.flatten(self.text[:self.cursor] + k + self.text[self.cursor:])
- self.cursor += 1
+
+ # We don't want to insert a space before the command
+ if k == ' ' and self.text[0:self.cursor].strip() == '':
+ return
+
+ self.text = self.text[:self.cursor] + k + self.text[self.cursor:]
+ self.cursor += len(k)
self.completion = None
diff --git a/test/mitmproxy/test_command.py b/test/mitmproxy/test_command.py
index d9dcf5f9..ae4c400c 100644
--- a/test/mitmproxy/test_command.py
+++ b/test/mitmproxy/test_command.py
@@ -115,12 +115,9 @@ class TestCommand:
[
"foo bar",
[
- command.ParseResult(
- value = "foo", type = mitmproxy.types.Cmd, valid = False
- ),
- command.ParseResult(
- value = "bar", type = mitmproxy.types.Unknown, valid = False
- )
+ command.ParseResult(value = "foo", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = "bar", type = mitmproxy.types.Unknown, valid = False)
],
[],
],
@@ -128,6 +125,7 @@ class TestCommand:
"cmd1 'bar",
[
command.ParseResult(value = "cmd1", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "'bar", type = str, valid = True)
],
[],
@@ -139,13 +137,14 @@ class TestCommand:
],
[
"",
- [command.ParseResult(value = "", type = mitmproxy.types.Cmd, valid = False)],
+ [],
[]
],
[
"cmd3 1",
[
command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "1", type = int, valid = True),
],
[]
@@ -154,28 +153,27 @@ class TestCommand:
"cmd3 ",
[
command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd, valid = True),
- command.ParseResult(value = "", type = int, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
],
- []
+ ['int']
],
[
"subcommand ",
[
- command.ParseResult(
- value = "subcommand", type = mitmproxy.types.Cmd, valid = True,
- ),
- command.ParseResult(value = "", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = "subcommand", type = mitmproxy.types.Cmd, valid = True,),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
],
- ["arg"],
+ ["cmd", "arg"],
],
[
"subcommand cmd3 ",
[
command.ParseResult(value = "subcommand", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd, valid = True),
- command.ParseResult(value = "", type = int, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
],
- []
+ ["int"]
],
[
"cmd4",
@@ -188,22 +186,15 @@ class TestCommand:
"cmd4 ",
[
command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
- command.ParseResult(value = "", type = int, valid = False),
- ],
- ["str", "path"]
- ],
- [
- "cmd4 1",
- [
- command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
- command.ParseResult(value = "1", type = int, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
],
- ["str", "path"]
+ ["int", "str", "path"]
],
[
"cmd4 1",
[
command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "1", type = int, valid = True),
],
["str", "path"]
@@ -219,14 +210,15 @@ class TestCommand:
"flow ",
[
command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
- command.ParseResult(value = "", type = flow.Flow, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
],
- ["str"]
+ ["flow", "str"]
],
[
"flow x",
[
command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "x", type = flow.Flow, valid = False),
],
["str"]
@@ -235,28 +227,131 @@ class TestCommand:
"flow x ",
[
command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "x", type = flow.Flow, valid = False),
- command.ParseResult(value = "", type = str, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
],
- []
+ ["str"]
],
[
"flow \"one two",
[
command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
command.ParseResult(value = "\"one two", type = flow.Flow, valid = False),
],
["str"]
],
[
- "flow \"one two\"",
+ "flow \"three four\"",
[
command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
- command.ParseResult(value = "one two", type = flow.Flow, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = '"three four"', type = flow.Flow, valid = False),
],
["str"]
],
+ [
+ "spaces ' '",
+ [
+ command.ParseResult(value = "spaces", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = "' '", type = mitmproxy.types.Unknown, valid = False)
+ ],
+ [],
+ ],
+ [
+ 'spaces2 " "',
+ [
+ command.ParseResult(value = "spaces2", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = '" "', type = mitmproxy.types.Unknown, valid = False)
+ ],
+ [],
+ ],
+ [
+ '"abc"',
+ [
+ command.ParseResult(value = '"abc"', type = mitmproxy.types.Cmd, valid = False),
+ ],
+ [],
+ ],
+ [
+ "'def'",
+ [
+ command.ParseResult(value = "'def'", type = mitmproxy.types.Cmd, valid = False),
+ ],
+ [],
+ ],
+ [
+ "cmd10 'a' \"b\" c",
+ [
+ command.ParseResult(value = "cmd10", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = "'a'", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = '"b"', type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = "c", type = mitmproxy.types.Unknown, valid = False),
+ ],
+ [],
+ ],
+ [
+ "cmd11 'a \"b\" c'",
+ [
+ command.ParseResult(value = "cmd11", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = "'a \"b\" c'", type = mitmproxy.types.Unknown, valid = False),
+ ],
+ [],
+ ],
+ [
+ 'cmd12 "a \'b\' c"',
+ [
+ command.ParseResult(value = "cmd12", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = '"a \'b\' c"', type = mitmproxy.types.Unknown, valid = False),
+ ],
+ [],
+ ],
+ [
+ r'cmd13 "a \"b\" c"',
+ [
+ command.ParseResult(value = "cmd13", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ 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.Unknown, valid = False),
+ command.ParseResult(value = r"'a \'b\' c'", type = mitmproxy.types.Unknown, valid = False),
+ ],
+ [],
+ ],
+ [
+ " spaces_at_the_begining_are_stripped",
+ [
+ command.ParseResult(value = "spaces_at_the_begining_are_stripped", type = mitmproxy.types.Cmd, valid = False),
+ ],
+ [],
+ ],
+ [
+ " spaces_at_the_begining_are_stripped but_not_at_the_end ",
+ [
+ command.ParseResult(value = "spaces_at_the_begining_are_stripped", type = mitmproxy.types.Cmd, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = "but_not_at_the_end", type = mitmproxy.types.Unknown, valid = False),
+ command.ParseResult(value = " ", type = mitmproxy.types.Unknown, valid = False),
+ ],
+ [],
+ ],
+
]
+
with taddons.context() as tctx:
tctx.master.addons.add(TAddon())
for s, expected, expectedremain in tests:
@@ -270,7 +365,7 @@ def test_simple():
c = command.CommandManager(tctx.master)
a = TAddon()
c.add("one.two", a.cmd1)
- assert c.commands["one.two"].help == "cmd1 help"
+ assert(c.commands["one.two"].help == "cmd1 help")
assert(c.execute("one.two foo") == "ret foo")
assert(c.call("one.two", "foo") == "ret foo")
with pytest.raises(exceptions.CommandError, match="Unknown"):
@@ -281,8 +376,12 @@ def test_simple():
c.execute("one.two too many args")
with pytest.raises(exceptions.CommandError, match="Unknown"):
c.call("nonexistent")
- with pytest.raises(exceptions.CommandError, match="No escaped"):
+ 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"\"")
c.add("empty", a.empty)
c.execute("empty")
diff --git a/test/mitmproxy/tools/console/test_commander.py b/test/mitmproxy/tools/console/test_commander.py
index b5e226fe..798ca5fe 100644
--- a/test/mitmproxy/tools/console/test_commander.py
+++ b/test/mitmproxy/tools/console/test_commander.py
@@ -1,6 +1,6 @@
-
from mitmproxy.tools.console.commander import commander
from mitmproxy.test import taddons
+import pytest
class TestListCompleter:
@@ -28,6 +28,18 @@ class TestListCompleter:
assert c.cycle() == expected
+class TestCommandEdit:
+ def test_open_command_bar(self):
+ with taddons.context() as tctx:
+ history = commander.CommandHistory(tctx.master, size=3)
+ edit = commander.CommandEdit(tctx.master, '', history)
+
+ try:
+ edit.update()
+ except IndexError:
+ pytest.faied("Unexpected IndexError")
+
+
class TestCommandHistory:
def fill_history(self, commands):
with taddons.context() as tctx:
@@ -153,8 +165,3 @@ class TestCommandBuffer:
cb = commander.CommandBuffer(tctx.master)
cb.text = "foo"
assert cb.render()
-
- def test_flatten(self):
- with taddons.context() as tctx:
- cb = commander.CommandBuffer(tctx.master)
- assert cb.flatten("foo bar") == "foo bar"
diff --git a/test/mitmproxy/tools/console/test_defaultkeys.py b/test/mitmproxy/tools/console/test_defaultkeys.py
index 52075c84..7e8df6b6 100644
--- a/test/mitmproxy/tools/console/test_defaultkeys.py
+++ b/test/mitmproxy/tools/console/test_defaultkeys.py
@@ -3,12 +3,14 @@ from mitmproxy.tools.console import defaultkeys
from mitmproxy.tools.console import keymap
from mitmproxy.tools.console import master
from mitmproxy import command
-
+from mitmproxy import ctx
import pytest
@pytest.mark.asyncio
async def test_commands_exist():
+ command_manager = command.CommandManager(ctx)
+
km = keymap.Keymap(None)
defaultkeys.map(km)
assert km.bindings
@@ -16,11 +18,18 @@ async def test_commands_exist():
await m.load_flow(tflow())
for binding in km.bindings:
- cmd, *args = command.lexer(binding.command)
+ results = command_manager.parse_partial(binding.command)
+
+ cmd = results[0][0].value
+ args = [a.value for a in results[0][1:]]
+
assert cmd in m.commands.commands
cmd_obj = m.commands.commands[cmd]
try:
cmd_obj.prepare_args(args)
except Exception as e:
+
+ import pdb
+ pdb.set_trace()
raise ValueError("Invalid command: {}".format(binding.command)) from e