aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/tools/console/keymap.py91
-rw-r--r--test/mitmproxy/tools/console/test_keymap.py94
2 files changed, 185 insertions, 0 deletions
diff --git a/mitmproxy/tools/console/keymap.py b/mitmproxy/tools/console/keymap.py
index fbb569a4..5f7cdb11 100644
--- a/mitmproxy/tools/console/keymap.py
+++ b/mitmproxy/tools/console/keymap.py
@@ -1,6 +1,18 @@
import typing
+import os
+
+import ruamel.yaml
+
+from mitmproxy import command
from mitmproxy.tools.console import commandexecutor
from mitmproxy.tools.console import signals
+from mitmproxy import ctx
+from mitmproxy import exceptions
+import mitmproxy.types
+
+
+class KeyBindingError(Exception):
+ pass
Contexts = {
@@ -139,3 +151,82 @@ class Keymap:
if b:
return self.executor(b.command)
return key
+
+
+keyAttrs = {
+ "key": lambda x: isinstance(x, str),
+ "cmd": lambda x: isinstance(x, str),
+ "ctx": lambda x: isinstance(x, list) and [isinstance(v, str) for v in x],
+ "help": lambda x: isinstance(x, str),
+}
+requiredKeyAttrs = set(["key", "cmd"])
+
+
+class KeymapConfig:
+ @command.command("console.keymap.load")
+ def keymap_load_path(self, path: mitmproxy.types.Path) -> None:
+ try:
+ master = ctx.master # type: mitmproxy.tools.console.master.ConsoleMaster
+ self.load_path(master.keymap, path)
+ except (OSError, KeyBindingError) as e:
+ raise exceptions.CommandError(
+ "Could not load key bindings - %s" % e
+ ) from e
+
+ def load_path(self, km, p):
+ if os.path.exists(p) and os.path.isfile(p):
+ with open(p, "rt", encoding="utf8") as f:
+ try:
+ txt = f.read()
+ except UnicodeDecodeError as e:
+ raise KeyBindingError(
+ "Encoding error - expected UTF8: %s: %s" % (p, e)
+ )
+ try:
+ vals = self.parse(txt)
+ except KeyBindingError as e:
+ raise KeyBindingError(
+ "Error reading %s: %s" % (p, e)
+ ) from e
+ for v in vals:
+ try:
+ km.add(
+ key = v["key"],
+ command = v["cmd"],
+ contexts = v.get("ctx", None),
+ help = v.get("help", None),
+ )
+ except ValueError as e:
+ raise KeyBindingError(
+ "Error reading %s: %s" % (p, e)
+ ) from e
+
+ def parse(self, text):
+ try:
+ data = ruamel.yaml.safe_load(text)
+ except ruamel.yaml.error.YAMLError as v:
+ if hasattr(v, "problem_mark"):
+ snip = v.problem_mark.get_snippet()
+ raise KeyBindingError(
+ "Key binding config error at line %s:\n%s\n%s" %
+ (v.problem_mark.line + 1, snip, v.problem)
+ )
+ else:
+ raise KeyBindingError("Could not parse key bindings.")
+ if not data:
+ return []
+ if not isinstance(data, list):
+ raise KeyBindingError("Inalid keybinding config - expected a list of keys")
+
+ for k in data:
+ unknown = k.keys() - keyAttrs.keys()
+ if unknown:
+ raise KeyBindingError("Unknown key attributes: %s" % unknown)
+ missing = requiredKeyAttrs - k.keys()
+ if missing:
+ raise KeyBindingError("Missing required key attributes: %s" % unknown)
+ for attr in k.keys():
+ if not keyAttrs[attr](k[attr]):
+ raise KeyBindingError("Invalid type for %s" % attr)
+
+ return data
diff --git a/test/mitmproxy/tools/console/test_keymap.py b/test/mitmproxy/tools/console/test_keymap.py
index 7b475ff8..901cd795 100644
--- a/test/mitmproxy/tools/console/test_keymap.py
+++ b/test/mitmproxy/tools/console/test_keymap.py
@@ -70,3 +70,97 @@ def test_remove():
km.remove("key", ["commands"])
assert len(km.bindings) == 0
+
+
+def test_load_path(tmpdir):
+ dst = str(tmpdir.join("conf"))
+
+ kmc = keymap.KeymapConfig()
+ with taddons.context(kmc) as tctx:
+ km = keymap.Keymap(tctx.master)
+
+ with open(dst, 'wb') as f:
+ f.write(b"\xff\xff\xff")
+ with pytest.raises(keymap.KeyBindingError, match="expected UTF8"):
+ kmc.load_path(km, dst)
+
+ with open(dst, 'w') as f:
+ f.write("'''")
+ with pytest.raises(keymap.KeyBindingError):
+ kmc.load_path(km, dst)
+
+ with open(dst, 'w') as f:
+ f.write(
+ """
+ - key: key1
+ ctx: [unknown]
+ cmd: >
+ foo bar
+ foo bar
+ """
+ )
+ with pytest.raises(keymap.KeyBindingError):
+ kmc.load_path(km, dst)
+
+ with open(dst, 'w') as f:
+ f.write(
+ """
+ - key: key1
+ ctx: [chooser]
+ help: one
+ cmd: >
+ foo bar
+ foo bar
+ """
+ )
+ kmc.load_path(km, dst)
+ assert(km.get("chooser", "key1"))
+
+
+def test_parse():
+ kmc = keymap.KeymapConfig()
+ with taddons.context(kmc):
+ assert kmc.parse("") == []
+ assert kmc.parse("\n\n\n \n") == []
+ with pytest.raises(keymap.KeyBindingError, match="expected a list of keys"):
+ kmc.parse("key: val")
+ with pytest.raises(keymap.KeyBindingError, match="expected a list of keys"):
+ kmc.parse("val")
+ with pytest.raises(keymap.KeyBindingError, match="Unknown key attributes"):
+ kmc.parse(
+ """
+ - key: key1
+ nonexistent: bar
+ """
+ )
+ with pytest.raises(keymap.KeyBindingError, match="Missing required key attributes"):
+ kmc.parse(
+ """
+ - help: key1
+ """
+ )
+ with pytest.raises(keymap.KeyBindingError, match="Invalid type for cmd"):
+ kmc.parse(
+ """
+ - key: key1
+ cmd: [ cmd ]
+ """
+ )
+ with pytest.raises(keymap.KeyBindingError, match="Invalid type for ctx"):
+ kmc.parse(
+ """
+ - key: key1
+ ctx: foo
+ cmd: cmd
+ """
+ )
+ assert kmc.parse(
+ """
+ - key: key1
+ ctx: [one, two]
+ help: one
+ cmd: >
+ foo bar
+ foo bar
+ """
+ ) == [{"key": "key1", "ctx": ["one", "two"], "help": "one", "cmd": "foo bar foo bar\n"}] \ No newline at end of file