diff options
-rw-r--r-- | mitmproxy/addonmanager.py | 14 | ||||
-rw-r--r-- | mitmproxy/ctx.py | 6 | ||||
-rw-r--r-- | mitmproxy/options.py | 79 | ||||
-rw-r--r-- | mitmproxy/optmanager.py | 3 | ||||
-rw-r--r-- | test/helper_tools/typehints_for_options.py | 34 | ||||
-rw-r--r-- | test/mitmproxy/test_addonmanager.py | 5 | ||||
-rw-r--r-- | test/mitmproxy/test_optmanager.py | 4 |
7 files changed, 139 insertions, 6 deletions
diff --git a/mitmproxy/addonmanager.py b/mitmproxy/addonmanager.py index ec82d650..9094288b 100644 --- a/mitmproxy/addonmanager.py +++ b/mitmproxy/addonmanager.py @@ -1,3 +1,4 @@ +import types import typing import traceback import contextlib @@ -229,11 +230,18 @@ class AddonManager: for a in traverse([addon]): func = getattr(a, name, None) if func: - if not callable(func): + if callable(func): + func(*args, **kwargs) + elif isinstance(func, types.ModuleType): + # we gracefully exclude module imports with the same name as hooks. + # For example, a user may have "from mitmproxy import log" in an addon, + # which has the same name as the "log" hook. In this particular case, + # we end up in an error loop because we "log" this error. + pass + else: raise exceptions.AddonManagerError( - "Addon handler %s not callable" % name + "Addon handler {} ({}) not callable".format(name, a) ) - func(*args, **kwargs) def trigger(self, name, *args, **kwargs): """ diff --git a/mitmproxy/ctx.py b/mitmproxy/ctx.py index 954edcb1..2ad1faec 100644 --- a/mitmproxy/ctx.py +++ b/mitmproxy/ctx.py @@ -2,6 +2,6 @@ import mitmproxy.master # noqa import mitmproxy.log # noqa import mitmproxy.options # noqa -master = None # type: "mitmproxy.master.Master" -log = None # type: "mitmproxy.log.Log" -options = None # type: "mitmproxy.options.Options" +master = None # type: mitmproxy.master.Master +log = None # type: mitmproxy.log.Log +options = None # type: mitmproxy.options.Options diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 8a01cf40..10aaee12 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -48,6 +48,85 @@ LISTEN_PORT = 8080 class Options(optmanager.OptManager): + + if False: + # This provides type hints for IDEs (e.g. PyCharm) and mypy. + # Autogenerated using test/helper_tools/typehints_for_options.py + add_upstream_certs_to_client_chain = None # type: bool + allow_remote = None # type: bool + anticache = None # type: bool + anticomp = None # type: bool + body_size_limit = None # type: Optional[str] + cadir = None # type: str + certs = None # type: Sequence[str] + ciphers_client = None # type: Optional[str] + ciphers_server = None # type: Optional[str] + client_certs = None # type: Optional[str] + client_replay = None # type: Sequence[str] + console_focus_follow = None # type: bool + console_layout = None # type: str + console_layout_headers = None # type: bool + console_mouse = None # type: bool + console_order = None # type: str + console_order_reversed = None # type: bool + console_palette = None # type: str + console_palette_transparent = None # type: bool + default_contentview = None # type: str + flow_detail = None # type: int + http2 = None # type: bool + http2_priority = None # type: bool + ignore_hosts = None # type: Sequence[str] + intercept = None # type: Optional[str] + intercept_active = None # type: bool + keep_host_header = None # type: bool + keepserving = None # type: bool + listen_host = None # type: str + listen_port = None # type: int + mode = None # type: str + onboarding = None # type: bool + onboarding_host = None # type: str + onboarding_port = None # type: int + proxyauth = None # type: Optional[str] + rawtcp = None # type: bool + refresh_server_playback = None # type: bool + replacements = None # type: Sequence[str] + replay_kill_extra = None # type: bool + rfile = None # type: Optional[str] + save_stream_file = None # type: Optional[str] + save_stream_filter = None # type: Optional[str] + scripts = None # type: Sequence[str] + server = None # type: bool + server_replay = None # type: Sequence[str] + server_replay_ignore_content = None # type: bool + server_replay_ignore_host = None # type: bool + server_replay_ignore_params = None # type: Sequence[str] + server_replay_ignore_payload_params = None # type: Sequence[str] + server_replay_nopop = None # type: bool + server_replay_use_headers = None # type: Sequence[str] + setheaders = None # type: Sequence[str] + showhost = None # type: bool + spoof_source_address = None # type: bool + ssl_insecure = None # type: bool + ssl_verify_upstream_trusted_ca = None # type: Optional[str] + ssl_verify_upstream_trusted_cadir = None # type: Optional[str] + ssl_version_client = None # type: str + ssl_version_server = None # type: str + stickyauth = None # type: Optional[str] + stickycookie = None # type: Optional[str] + stream_large_bodies = None # type: Optional[str] + stream_websockets = None # type: bool + tcp_hosts = None # type: Sequence[str] + upstream_auth = None # type: Optional[str] + upstream_bind_address = None # type: str + upstream_cert = None # type: bool + verbosity = None # type: str + view_filter = None # type: Optional[str] + web_debug = None # type: bool + web_iface = None # type: str + web_open_browser = None # type: bool + web_port = None # type: int + websocket = None # type: bool + def __init__(self, **kwargs) -> None: super().__init__() self.add_option( diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index c28ec685..84c8d2ea 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -170,6 +170,9 @@ class OptManager: def keys(self): return set(self._options.keys()) + def items(self): + return self._options.items() + def __contains__(self, k): return k in self._options diff --git a/test/helper_tools/typehints_for_options.py b/test/helper_tools/typehints_for_options.py new file mode 100644 index 00000000..8c7d006c --- /dev/null +++ b/test/helper_tools/typehints_for_options.py @@ -0,0 +1,34 @@ +import typing +from unittest import mock + +from mitmproxy import proxy, options +from mitmproxy.tools import dump, console, web + + +def print_typehints(opts): + for name, option in sorted(opts.items()): + print( + # For Python 3.6, we can just use "{}: {}". + "{} = None # type: {}".format( + name, + { + int: "int", + str: "str", + bool: "bool", + typing.Optional[str]: "Optional[str]", + typing.Sequence[str]: "Sequence[str]" + }[option.typespec] + ) + ) + + +if __name__ == "__main__": + opts = options.Options() + server = proxy.server.DummyServer(None) + + # initialize with all three tools here to capture tool-specific options defined in addons. + dump.DumpMaster(opts, server) + with mock.patch("sys.stdout.isatty", lambda: True): + console.master.ConsoleMaster(opts, server) + web.master.WebMaster(opts, server) + print_typehints(opts) diff --git a/test/mitmproxy/test_addonmanager.py b/test/mitmproxy/test_addonmanager.py index 7295a468..722d108b 100644 --- a/test/mitmproxy/test_addonmanager.py +++ b/test/mitmproxy/test_addonmanager.py @@ -117,6 +117,11 @@ def test_simple(): a.trigger("tick") assert tctx.master.has_log("not callable") + tctx.master.clear() + a.get("one").tick = addons + a.trigger("tick") + assert not tctx.master.has_log("not callable") + a.remove(a.get("one")) assert not a.get("one") diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index 7b4ffb8b..fe72e6bb 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -229,6 +229,10 @@ def test_simple(): assert "one" in TO() +def test_items(): + assert TO().items() + + def test_serialize(): o = TD2() o.three = "set" |