aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/types.py93
-rw-r--r--test/mitmproxy/test_types.py42
2 files changed, 129 insertions, 6 deletions
diff --git a/mitmproxy/types.py b/mitmproxy/types.py
index b6b414ba..713a0ae5 100644
--- a/mitmproxy/types.py
+++ b/mitmproxy/types.py
@@ -56,13 +56,29 @@ class _BaseType:
def completion(
self, manager: _CommandBase, t: typing.Any, s: str
- ) -> typing.Sequence[str]: # pragma: no cover
- pass
+ ) -> typing.Sequence[str]:
+ """
+ Returns a list of completion strings for a given prefix. The strings
+ returned don't necessarily need to be suffixes of the prefix, since
+ completers will do prefix filtering themselves..
+ """
+ raise NotImplementedError
def parse(
- self, manager: _CommandBase, t: typing.Any, s: str
- ) -> typing.Any: # pragma: no cover
- pass
+ self, manager: _CommandBase, typ: typing.Any, s: str
+ ) -> typing.Any:
+ """
+ Parse a string, given the specific type instance (to allow rich type annotations like Choice) and a string.
+
+ Raises exceptions.TypeError if the value is invalid.
+ """
+ raise NotImplementedError
+
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ """
+ Check if data is valid for this type.
+ """
+ raise NotImplementedError
class _BoolType(_BaseType):
@@ -82,6 +98,9 @@ class _BoolType(_BaseType):
"Booleans are 'true' or 'false', got %s" % s
)
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return val in [True, False]
+
class _StrType(_BaseType):
typ = str
@@ -93,6 +112,9 @@ class _StrType(_BaseType):
def parse(self, manager: _CommandBase, t: type, s: str) -> str:
return s
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return isinstance(val, str)
+
class _IntType(_BaseType):
typ = int
@@ -107,6 +129,9 @@ class _IntType(_BaseType):
except ValueError as e:
raise exceptions.TypeError from e
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return isinstance(val, int)
+
class _PathType(_BaseType):
typ = Path
@@ -137,6 +162,9 @@ class _PathType(_BaseType):
def parse(self, manager: _CommandBase, t: type, s: str) -> str:
return s
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return isinstance(val, str)
+
class _CmdType(_BaseType):
typ = Cmd
@@ -148,6 +176,9 @@ class _CmdType(_BaseType):
def parse(self, manager: _CommandBase, t: type, s: str) -> str:
return s
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return val in manager.commands
+
class _ArgType(_BaseType):
typ = Arg
@@ -159,6 +190,9 @@ class _ArgType(_BaseType):
def parse(self, manager: _CommandBase, t: type, s: str) -> str:
return s
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return isinstance(val, str)
+
class _StrSeqType(_BaseType):
typ = typing.Sequence[str]
@@ -170,6 +204,9 @@ class _StrSeqType(_BaseType):
def parse(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
return [x.strip() for x in s.split(",")]
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return isinstance(val, str)
+
class _CutSpecType(_BaseType):
typ = CutSpec
@@ -224,15 +261,29 @@ class _CutSpecType(_BaseType):
parts = s.split(",") # type: typing.Any
return parts
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ if not isinstance(val, str):
+ return False
+ parts = [x.strip() for x in val.split(",")]
+ for p in parts:
+ for pref in self.valid_prefixes:
+ if p.startswith(pref):
+ break
+ else:
+ return False
+ return True
+
class _BaseFlowType(_BaseType):
- valid_prefixes = [
+ viewmarkers = [
"@all",
"@focus",
"@shown",
"@hidden",
"@marked",
"@unmarked",
+ ]
+ valid_prefixes = viewmarkers + [
"~q",
"~s",
"~a",
@@ -264,6 +315,9 @@ class _FlowType(_BaseFlowType):
)
return flows[0]
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ return isinstance(val, flow.Flow)
+
class _FlowsType(_BaseFlowType):
typ = typing.Sequence[flow.Flow]
@@ -272,6 +326,15 @@ class _FlowsType(_BaseFlowType):
def parse(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[flow.Flow]:
return manager.call_args("view.resolve", [s])
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ try:
+ for v in val:
+ if not isinstance(v, flow.Flow):
+ return False
+ except TypeError:
+ return False
+ return True
+
class _DataType(_BaseType):
typ = Data
@@ -287,6 +350,17 @@ class _DataType(_BaseType):
) -> typing.Any: # pragma: no cover
raise exceptions.TypeError("data cannot be passed as argument")
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ # FIXME: validate that all rows have equal length, and all columns have equal types
+ try:
+ for row in val:
+ for cell in row:
+ if not (isinstance(cell, str) or isinstance(cell, bytes)):
+ return False
+ except TypeError:
+ return False
+ return True
+
class _ChoiceType(_BaseType):
typ = Choice
@@ -301,6 +375,13 @@ class _ChoiceType(_BaseType):
raise exceptions.TypeError("Invalid choice.")
return s
+ def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ try:
+ opts = manager.call(typ.options_command)
+ except exceptions.CommandError:
+ return False
+ return val in opts
+
class TypeManager:
def __init__(self, *types):
diff --git a/test/mitmproxy/test_types.py b/test/mitmproxy/test_types.py
index 90032204..dcf7a1d2 100644
--- a/test/mitmproxy/test_types.py
+++ b/test/mitmproxy/test_types.py
@@ -28,6 +28,8 @@ def test_bool():
assert b.completion(tctx.master.commands, bool, "b") == ["false", "true"]
assert b.parse(tctx.master.commands, bool, "true") is True
assert b.parse(tctx.master.commands, bool, "false") is False
+ assert b.is_valid(tctx.master.commands, bool, True) is True
+ assert b.is_valid(tctx.master.commands, bool, "foo") is False
with pytest.raises(mitmproxy.exceptions.TypeError):
b.parse(tctx.master.commands, bool, "foo")
@@ -35,6 +37,8 @@ def test_bool():
def test_str():
with taddons.context() as tctx:
b = mitmproxy.types._StrType()
+ assert b.is_valid(tctx.master.commands, str, "foo") is True
+ assert b.is_valid(tctx.master.commands, str, 1) is False
assert b.completion(tctx.master.commands, str, "") == []
assert b.parse(tctx.master.commands, str, "foo") == "foo"
@@ -42,6 +46,8 @@ def test_str():
def test_int():
with taddons.context() as tctx:
b = mitmproxy.types._IntType()
+ assert b.is_valid(tctx.master.commands, int, "foo") is False
+ assert b.is_valid(tctx.master.commands, int, 1) is True
assert b.completion(tctx.master.commands, int, "b") == []
assert b.parse(tctx.master.commands, int, "1") == 1
assert b.parse(tctx.master.commands, int, "999") == 999
@@ -54,6 +60,8 @@ def test_path():
b = mitmproxy.types._PathType()
assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/foo") == "/foo"
assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/bar") == "/bar"
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, "foo") is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, 3) is False
def normPathOpts(prefix, match):
ret = []
@@ -78,6 +86,8 @@ def test_cmd():
with taddons.context() as tctx:
tctx.master.addons.add(test_command.TAddon())
b = mitmproxy.types._CmdType()
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Cmd, "foo") is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Cmd, "cmd1") is True
assert b.parse(tctx.master.commands, mitmproxy.types.Cmd, "foo") == "foo"
assert len(
b.completion(tctx.master.commands, mitmproxy.types.Cmd, "")
@@ -88,6 +98,10 @@ def test_cutspec():
with taddons.context() as tctx:
b = mitmproxy.types._CutSpecType()
b.parse(tctx.master.commands, mitmproxy.types.CutSpec, "foo,bar") == ["foo", "bar"]
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, 1) is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, "foo") is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, "request.path") is True
+
assert b.completion(
tctx.master.commands, mitmproxy.types.CutSpec, "request.p"
) == b.valid_prefixes
@@ -101,6 +115,8 @@ def test_arg():
b = mitmproxy.types._ArgType()
assert b.completion(tctx.master.commands, mitmproxy.types.Arg, "") == []
assert b.parse(tctx.master.commands, mitmproxy.types.Arg, "foo") == "foo"
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Arg, "foo") is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Arg, 1) is False
def test_strseq():
@@ -109,6 +125,8 @@ def test_strseq():
assert b.completion(tctx.master.commands, typing.Sequence[str], "") == []
assert b.parse(tctx.master.commands, typing.Sequence[str], "foo") == ["foo"]
assert b.parse(tctx.master.commands, typing.Sequence[str], "foo,bar") == ["foo", "bar"]
+ assert b.is_valid(tctx.master.commands, typing.Sequence[str], "foo") is True
+ assert b.is_valid(tctx.master.commands, typing.Sequence[str], 1) is False
class DummyConsole:
@@ -132,6 +150,8 @@ def test_flow():
b = mitmproxy.types._FlowType()
assert len(b.completion(tctx.master.commands, flow.Flow, "")) == len(b.valid_prefixes)
assert b.parse(tctx.master.commands, flow.Flow, "1")
+ assert b.is_valid(tctx.master.commands, flow.Flow, tflow.tflow()) is True
+ assert b.is_valid(tctx.master.commands, flow.Flow, "xx") is False
with pytest.raises(mitmproxy.exceptions.TypeError):
assert b.parse(tctx.master.commands, flow.Flow, "0")
with pytest.raises(mitmproxy.exceptions.TypeError):
@@ -145,6 +165,9 @@ def test_flows():
assert len(
b.completion(tctx.master.commands, typing.Sequence[flow.Flow], "")
) == len(b.valid_prefixes)
+ assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], [tflow.tflow()]) is True
+ assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], "xx") is False
+ assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], 0) is False
assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "0")) == 0
assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "1")) == 1
assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "2")) == 2
@@ -153,6 +176,10 @@ def test_flows():
def test_data():
with taddons.context() as tctx:
b = mitmproxy.types._DataType()
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, 0) is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, []) is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [["x"]]) is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [[b"x"]]) is True
with pytest.raises(mitmproxy.exceptions.TypeError):
b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
with pytest.raises(mitmproxy.exceptions.TypeError):
@@ -163,6 +190,21 @@ def test_choice():
with taddons.context() as tctx:
tctx.master.addons.add(DummyConsole())
b = mitmproxy.types._ChoiceType()
+ assert b.is_valid(
+ tctx.master.commands,
+ mitmproxy.types.Choice("options"),
+ "one",
+ ) is True
+ assert b.is_valid(
+ tctx.master.commands,
+ mitmproxy.types.Choice("options"),
+ "invalid",
+ ) is False
+ assert b.is_valid(
+ tctx.master.commands,
+ mitmproxy.types.Choice("nonexistent"),
+ "invalid",
+ ) is False
comp = b.completion(tctx.master.commands, mitmproxy.types.Choice("options"), "")
assert comp == ["one", "two", "three"]
assert b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "one") == "one"