From aabc78350a73534729611a6b9bf38fb7e4b390ae Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Mon, 12 Jun 2017 20:26:46 +0800 Subject: [PATCH 1/8] Add GET /options RESTful API for mitmweb. --- mitmproxy/optmanager.py | 14 ++++++++++++++ mitmproxy/tools/web/app.py | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index 70f60bb64..3685c0034 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -416,6 +416,20 @@ def dump_defaults(opts): return ruamel.yaml.round_trip_dump(s) +def dump_dicts(opts): + """ + Dumps the options into a list of dict object. + + Return: A list like: [ { name: "anticahce", type: "bool", default: false, value: true, help: "help text"}] + """ + options_list = [] + for k in sorted(opts.keys()): + o = opts._options[k] + option = {'name': k, 'type': o.typespec.__name__, 'default': o.default, 'value': o.current(), 'help': o.help.strip()} + options_list.append(option) + return options_list + + def parse(text): if not text: return {} diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py index c55c0cb5b..b512671cf 100644 --- a/mitmproxy/tools/web/app.py +++ b/mitmproxy/tools/web/app.py @@ -17,6 +17,8 @@ from mitmproxy import http from mitmproxy import io from mitmproxy import log from mitmproxy import version +from mitmproxy import options +from mitmproxy import optmanager import mitmproxy.tools.web.master # noqa @@ -438,6 +440,11 @@ class Settings(RequestHandler): self.master.options.update(**update) +class Options(RequestHandler): + def get(self): + self.write(optmanager.dump_dicts(self.master.options)) + + class Application(tornado.web.Application): def __init__(self, master, debug): self.master = master @@ -462,6 +469,7 @@ class Application(tornado.web.Application): FlowContentView), (r"/settings", Settings), (r"/clear", ClearAll), + (r"/options", Options) ] settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), From 03bb0a09b95d1942a62435545d867b0888de75fa Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Mon, 12 Jun 2017 20:27:21 +0800 Subject: [PATCH 2/8] Add tests for dump_dicts in optmanager.py. --- mitmproxy/tools/web/app.py | 1 - test/mitmproxy/test_optmanager.py | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py index b512671cf..ae542dc5d 100644 --- a/mitmproxy/tools/web/app.py +++ b/mitmproxy/tools/web/app.py @@ -17,7 +17,6 @@ from mitmproxy import http from mitmproxy import io from mitmproxy import log from mitmproxy import version -from mitmproxy import options from mitmproxy import optmanager import mitmproxy.tools.web.master # noqa diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index 04ec7ded8..37fe0e3a1 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -334,6 +334,11 @@ def test_dump_defaults(): assert optmanager.dump_defaults(o) +def test_dump_dicts(): + o = options.Options() + assert optmanager.dump_dicts(o) + + class TTypes(optmanager.OptManager): def __init__(self): super().__init__() From 2c0f6c202321a9d332dbe0181d319a3be4fd2614 Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Tue, 13 Jun 2017 23:03:07 +0800 Subject: [PATCH 3/8] Minor Update and add test. --- mitmproxy/optmanager.py | 19 +++++++++++++++++-- test/mitmproxy/tools/web/test_app.py | 5 +++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index 3685c0034..b67949e0c 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -420,12 +420,27 @@ def dump_dicts(opts): """ Dumps the options into a list of dict object. - Return: A list like: [ { name: "anticahce", type: "bool", default: false, value: true, help: "help text"}] + Return: A list like: [ { name: "anticache", type: "bool", default: false, value: true, help: "help text"} ] """ options_list = [] for k in sorted(opts.keys()): o = opts._options[k] - option = {'name': k, 'type': o.typespec.__name__, 'default': o.default, 'value': o.current(), 'help': o.help.strip()} + if o.typespec in (str, int, bool): + t = o.typespec.__name__ + elif o.typespec == typing.Optional[str]: + t = 'Union' + elif o.typespec == typing.Sequence[str]: + t = 'Sequence' + else: + raise NotImplementedError + option = { + 'name': k, + 'type': t, + 'default': o.default, + 'value': o.current(), + 'help': o.help, + 'choices': o.choices + } options_list.append(option) return options_list diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py index 5427b9954..d47b1af05 100644 --- a/test/mitmproxy/tools/web/test_app.py +++ b/test/mitmproxy/tools/web/test_app.py @@ -253,6 +253,11 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): assert self.put_json("/settings", {"anticache": True}).code == 200 assert self.put_json("/settings", {"wtf": True}).code == 400 + def test_options(self): + j = json(self.fetch("/options")) + assert type(j) == list + assert type(j[0]) == dict + def test_err(self): with mock.patch("mitmproxy.tools.web.app.IndexHandler.get") as f: f.side_effect = RuntimeError From 1177e6d90777da92c72a1e88de93312f2ca1e6ff Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Tue, 13 Jun 2017 23:21:52 +0800 Subject: [PATCH 4/8] Add typesepc_to_str function to mitmproxy/utils/typechck.py --- mitmproxy/optmanager.py | 9 +-------- mitmproxy/utils/typecheck.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index b67949e0c..4a4552826 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -425,14 +425,7 @@ def dump_dicts(opts): options_list = [] for k in sorted(opts.keys()): o = opts._options[k] - if o.typespec in (str, int, bool): - t = o.typespec.__name__ - elif o.typespec == typing.Optional[str]: - t = 'Union' - elif o.typespec == typing.Sequence[str]: - t = 'Sequence' - else: - raise NotImplementedError + t = typecheck.typespec_to_str(o.typespec) option = { 'name': k, 'type': t, diff --git a/mitmproxy/utils/typecheck.py b/mitmproxy/utils/typecheck.py index a5f27fee5..ed58182d4 100644 --- a/mitmproxy/utils/typecheck.py +++ b/mitmproxy/utils/typecheck.py @@ -98,3 +98,15 @@ def check_option_type(name: str, value: typing.Any, typeinfo: typing.Any) -> Non return elif not isinstance(value, typeinfo): raise e + + +def typespec_to_str(typespec: typing.Any) -> str: + if typespec in (str, int, bool): + t = typespec.__name__ + elif typespec == typing.Optional[str]: + t = 'Union' + elif typespec == typing.Sequence[str]: + t = 'Sequence' + else: + raise NotImplementedError + return t From 9687d676012379f5875331423fbbbe33fcd63434 Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Wed, 14 Jun 2017 07:44:00 +0800 Subject: [PATCH 5/8] Add test for typespec_to_str. --- test/mitmproxy/utils/test_typecheck.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/mitmproxy/utils/test_typecheck.py b/test/mitmproxy/utils/test_typecheck.py index fe33070e2..8e4198da4 100644 --- a/test/mitmproxy/utils/test_typecheck.py +++ b/test/mitmproxy/utils/test_typecheck.py @@ -111,3 +111,11 @@ def test_check_command_type(): m.__str__ = lambda self: "typing.Union" m.__union_params__ = (int,) assert not typecheck.check_command_type([22], m) + + +def test_typesec_to_str(): + assert(typecheck.typespec_to_str(str)) == "str" + assert(typecheck.typespec_to_str(typing.Sequence[str])) == "Sequence" + assert(typecheck.typespec_to_str(typing.Optional[str])) == "Union" + with pytest.raises(NotImplementedError): + typecheck.typespec_to_str(dict) From ab162361a1fe6d773807caa6071d49bdcf68b612 Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Fri, 23 Jun 2017 10:44:09 +0800 Subject: [PATCH 6/8] Add POST /options RESTful API for mitmweb. --- mitmproxy/tools/web/app.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py index ae542dc5d..ad8e5d2a1 100644 --- a/mitmproxy/tools/web/app.py +++ b/mitmproxy/tools/web/app.py @@ -443,6 +443,14 @@ class Options(RequestHandler): def get(self): self.write(optmanager.dump_dicts(self.master.options)) + def put(self): + update = self.json + try: + self.master.options.update(**update) + except (KeyError, TypeError) as err: + raise APIError(400, "{}".format(err)) + + class Application(tornado.web.Application): def __init__(self, master, debug): From c7ce7f84e6283fa08e87ee5ee35fd6053a2ab615 Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Fri, 23 Jun 2017 10:45:04 +0800 Subject: [PATCH 7/8] Add test for POST /options API of mitmweb. --- mitmproxy/tools/web/app.py | 1 - test/mitmproxy/tools/web/test_app.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py index ad8e5d2a1..8b4a39b6f 100644 --- a/mitmproxy/tools/web/app.py +++ b/mitmproxy/tools/web/app.py @@ -451,7 +451,6 @@ class Options(RequestHandler): raise APIError(400, "{}".format(err)) - class Application(tornado.web.Application): def __init__(self, master, debug): self.master = master diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py index d47b1af05..401f9fe65 100644 --- a/test/mitmproxy/tools/web/test_app.py +++ b/test/mitmproxy/tools/web/test_app.py @@ -258,6 +258,10 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): assert type(j) == list assert type(j[0]) == dict + def test_option_update(self): + assert self.put_json("/options", {"anticache": True}).code == 200 + assert self.put_json("/options", {"wtf": True}).code == 400 + def test_err(self): with mock.patch("mitmproxy.tools.web.app.IndexHandler.get") as f: f.side_effect = RuntimeError From 18633262201b33fb0c36381fbc65febf21313eb7 Mon Sep 17 00:00:00 2001 From: Matthew Shao Date: Sat, 24 Jun 2017 10:18:00 +0800 Subject: [PATCH 8/8] Minor Update for /options API of mitmweb. --- mitmproxy/optmanager.py | 11 ++--------- mitmproxy/utils/typecheck.py | 4 ++-- test/mitmproxy/tools/web/test_app.py | 1 + test/mitmproxy/utils/test_typecheck.py | 4 ++-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index 4a4552826..ed1310aae 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -401,18 +401,11 @@ def dump_defaults(opts): if o.choices: txt += " Valid values are %s." % ", ".join(repr(c) for c in o.choices) else: - if o.typespec in (str, int, bool): - t = o.typespec.__name__ - elif o.typespec == typing.Optional[str]: - t = "optional str" - elif o.typespec == typing.Sequence[str]: - t = "sequence of str" - else: # pragma: no cover - raise NotImplementedError + t = typecheck.typespec_to_str(o.typespec) txt += " Type %s." % t txt = "\n".join(textwrap.wrap(txt)) - s.yaml_set_comment_before_after_key(k, before = "\n" + txt) + s.yaml_set_comment_before_after_key(k, before="\n" + txt) return ruamel.yaml.round_trip_dump(s) diff --git a/mitmproxy/utils/typecheck.py b/mitmproxy/utils/typecheck.py index ed58182d4..87a0e8041 100644 --- a/mitmproxy/utils/typecheck.py +++ b/mitmproxy/utils/typecheck.py @@ -104,9 +104,9 @@ def typespec_to_str(typespec: typing.Any) -> str: if typespec in (str, int, bool): t = typespec.__name__ elif typespec == typing.Optional[str]: - t = 'Union' + t = 'optional str' elif typespec == typing.Sequence[str]: - t = 'Sequence' + t = 'sequence of str' else: raise NotImplementedError return t diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py index 401f9fe65..e6d563e7f 100644 --- a/test/mitmproxy/tools/web/test_app.py +++ b/test/mitmproxy/tools/web/test_app.py @@ -261,6 +261,7 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): def test_option_update(self): assert self.put_json("/options", {"anticache": True}).code == 200 assert self.put_json("/options", {"wtf": True}).code == 400 + assert self.put_json("/options", {"anticache": "foo"}).code == 400 def test_err(self): with mock.patch("mitmproxy.tools.web.app.IndexHandler.get") as f: diff --git a/test/mitmproxy/utils/test_typecheck.py b/test/mitmproxy/utils/test_typecheck.py index 8e4198da4..66b1884e8 100644 --- a/test/mitmproxy/utils/test_typecheck.py +++ b/test/mitmproxy/utils/test_typecheck.py @@ -115,7 +115,7 @@ def test_check_command_type(): def test_typesec_to_str(): assert(typecheck.typespec_to_str(str)) == "str" - assert(typecheck.typespec_to_str(typing.Sequence[str])) == "Sequence" - assert(typecheck.typespec_to_str(typing.Optional[str])) == "Union" + assert(typecheck.typespec_to_str(typing.Sequence[str])) == "sequence of str" + assert(typecheck.typespec_to_str(typing.Optional[str])) == "optional str" with pytest.raises(NotImplementedError): typecheck.typespec_to_str(dict)