diff options
| -rw-r--r-- | mitmproxy/cmdline.py | 43 | ||||
| -rw-r--r-- | mitmproxy/console/master.py | 3 | ||||
| -rw-r--r-- | mitmproxy/dump.py | 3 | ||||
| -rw-r--r-- | mitmproxy/flow/__init__.py | 3 | ||||
| -rw-r--r-- | mitmproxy/flow/options.py | 124 | ||||
| -rw-r--r-- | mitmproxy/options.py | 239 | ||||
| -rw-r--r-- | mitmproxy/optmanager.py | 108 | ||||
| -rw-r--r-- | mitmproxy/web/master.py | 3 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_anticache.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_anticomp.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_filestreamer.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_replace.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_script.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_setheaders.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_stickyauth.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/builtins/test_stickycookie.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/test_flow.py | 7 | ||||
| -rw-r--r-- | test/mitmproxy/test_optmanager.py (renamed from test/mitmproxy/test_options.py) | 8 | ||||
| -rw-r--r-- | test/mitmproxy/test_protocol_http2.py | 5 | ||||
| -rw-r--r-- | test/mitmproxy/test_proxy.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/tservers.py | 10 | 
21 files changed, 287 insertions, 287 deletions
diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index b3d3e6dd..a6844241 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -8,26 +8,11 @@ import configargparse  from mitmproxy import exceptions  from mitmproxy import filt  from mitmproxy import platform +from mitmproxy import options  from netlib import human  from netlib import tcp  from netlib import version -APP_HOST = "mitm.it" -APP_PORT = 80 -CA_DIR = "~/.mitmproxy" - -# We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default. -# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old -DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:" \ -    "ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:" \ -    "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:" \ -    "ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:" \ -    "DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:" \ -    "DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:" \ -    "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:" \ -    "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:" \ -    "!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" -  class ParseException(Exception):      pass @@ -310,8 +295,8 @@ def basic_options(parser):      )      parser.add_argument(          "--cadir", -        action="store", type=str, dest="cadir", default=CA_DIR, -        help="Location of the default mitmproxy CA files. (%s)" % CA_DIR +        action="store", type=str, dest="cadir", default=options.CA_DIR, +        help="Location of the default mitmproxy CA files. (%s)" % options.CA_DIR      )      parser.add_argument(          "--host", @@ -462,7 +447,7 @@ def proxy_options(parser):      )      group.add_argument(          "-p", "--port", -        action="store", type=int, dest="port", default=8080, +        action="store", type=int, dest="port", default=options.LISTEN_PORT,          help="Proxy service port."      )      group.add_argument( @@ -509,7 +494,7 @@ def proxy_ssl_options(parser):               'as the first entry. Can be passed multiple times.')      group.add_argument(          "--ciphers-client", action="store", -        type=str, dest="ciphers_client", default=DEFAULT_CLIENT_CIPHERS, +        type=str, dest="ciphers_client", default=options.DEFAULT_CLIENT_CIPHERS,          help="Set supported ciphers for client connections. (OpenSSL Syntax)"      )      group.add_argument( @@ -575,18 +560,18 @@ def onboarding_app(parser):      )      group.add_argument(          "--app-host", -        action="store", dest="app_host", default=APP_HOST, metavar="host", +        action="store", dest="app_host", default=options.APP_HOST, metavar="host",          help="""              Domain to serve the onboarding app from. For transparent mode, use              an IP when a DNS entry for the app domain is not present. Default:              %s -        """ % APP_HOST +        """ % options.APP_HOST      )      group.add_argument(          "--app-port",          action="store",          dest="app_port", -        default=APP_PORT, +        default=options.APP_PORT,          type=int,          metavar="80",          help="Port to serve the onboarding app from." @@ -764,8 +749,8 @@ def mitmproxy():          usage="%(prog)s [options]",          args_for_setting_config_path=["--conf"],          default_config_files=[ -            os.path.join(CA_DIR, "common.conf"), -            os.path.join(CA_DIR, "mitmproxy.conf") +            os.path.join(options.CA_DIR, "common.conf"), +            os.path.join(options.CA_DIR, "mitmproxy.conf")          ],          add_config_file_help=True,          add_env_var_help=True @@ -819,8 +804,8 @@ def mitmdump():          usage="%(prog)s [options] [filter]",          args_for_setting_config_path=["--conf"],          default_config_files=[ -            os.path.join(CA_DIR, "common.conf"), -            os.path.join(CA_DIR, "mitmdump.conf") +            os.path.join(options.CA_DIR, "common.conf"), +            os.path.join(options.CA_DIR, "mitmdump.conf")          ],          add_config_file_help=True,          add_env_var_help=True @@ -849,8 +834,8 @@ def mitmweb():          usage="%(prog)s [options]",          args_for_setting_config_path=["--conf"],          default_config_files=[ -            os.path.join(CA_DIR, "common.conf"), -            os.path.join(CA_DIR, "mitmweb.conf") +            os.path.join(options.CA_DIR, "common.conf"), +            os.path.join(options.CA_DIR, "mitmweb.conf")          ],          add_config_file_help=True,          add_env_var_help=True diff --git a/mitmproxy/console/master.py b/mitmproxy/console/master.py index 86e889cc..59d07456 100644 --- a/mitmproxy/console/master.py +++ b/mitmproxy/console/master.py @@ -23,6 +23,7 @@ from mitmproxy import exceptions  from mitmproxy import flow  from mitmproxy import script  from mitmproxy import utils +import mitmproxy.options  from mitmproxy.console import flowlist  from mitmproxy.console import flowview  from mitmproxy.console import grideditor @@ -178,7 +179,7 @@ class ConsoleState(flow.State):          self.add_flow_setting(flow, "marked", marked) -class Options(flow.options.Options): +class Options(mitmproxy.options.Options):      def __init__(              self,              eventlog=False,  # type: bool diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py index 78dd2578..4f34ab95 100644 --- a/mitmproxy/dump.py +++ b/mitmproxy/dump.py @@ -12,6 +12,7 @@ from mitmproxy import exceptions  from mitmproxy import flow  from mitmproxy import builtins  from mitmproxy import utils +from mitmproxy import options  from mitmproxy.builtins import dumper  from netlib import tcp @@ -20,7 +21,7 @@ class DumpError(Exception):      pass -class Options(flow.options.Options): +class Options(options.Options):      def __init__(              self,              keepserving=False,  # type: bool diff --git a/mitmproxy/flow/__init__.py b/mitmproxy/flow/__init__.py index b2ab74c6..8a64180e 100644 --- a/mitmproxy/flow/__init__.py +++ b/mitmproxy/flow/__init__.py @@ -7,7 +7,6 @@ from mitmproxy.flow.modules import (      AppRegistry, StreamLargeBodies, ClientPlaybackState, ServerPlaybackState  )  from mitmproxy.flow.state import State, FlowView -from mitmproxy.flow import options  # TODO: We may want to remove the imports from .modules and just expose "modules" @@ -16,5 +15,5 @@ __all__ = [      "FlowWriter", "FilteredFlowWriter", "FlowReader", "read_flows_from_paths",      "FlowMaster",      "AppRegistry", "StreamLargeBodies", "ClientPlaybackState", -    "ServerPlaybackState", "State", "FlowView", "options", +    "ServerPlaybackState", "State", "FlowView",  ] diff --git a/mitmproxy/flow/options.py b/mitmproxy/flow/options.py deleted file mode 100644 index 726952e2..00000000 --- a/mitmproxy/flow/options.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import absolute_import, print_function, division -from mitmproxy import options -from typing import Tuple, Optional, Sequence  # noqa -from mitmproxy import cmdline - -APP_HOST = "mitm.it" -APP_PORT = 80 - - -class Options(options.Options): -    def __init__( -            self, -            # TODO: rename to onboarding_app_* -            app=True,  # type: bool -            app_host=APP_HOST,  # type: str -            app_port=APP_PORT,  # type: int -            anticache=False,  # type: bool -            anticomp=False,  # type: bool -            client_replay=None,  # type: Optional[str] -            kill=False,  # type: bool -            no_server=False,  # type: bool -            nopop=False,  # type: bool -            refresh_server_playback=False,  # type: bool -            rfile=None,  # type: Optional[str] -            scripts=(),  # type: Sequence[str] -            showhost=False,  # type: bool -            replacements=(),  # type: Sequence[Tuple[str, str, str]] -            rheaders=(),  # type: Sequence[str] -            setheaders=(),  # type: Sequence[Tuple[str, str, str]] -            server_replay=None,  # type: Optional[str] -            stickycookie=None,  # type: Optional[str] -            stickyauth=None,  # type: Optional[str] -            stream_large_bodies=None,  # type: Optional[str] -            verbosity=2,  # type: int -            outfile=None,  # type: Tuple[str, str] -            replay_ignore_content=False,  # type: bool -            replay_ignore_params=(),  # type: Sequence[str] -            replay_ignore_payload_params=(),  # type: Sequence[str] -            replay_ignore_host=False,  # type: bool - -            # Proxy options -            auth_nonanonymous=False,  # type: bool -            auth_singleuser=None,  # type: Optional[str] -            auth_htpasswd=None,  # type: Optional[str] -            add_upstream_certs_to_client_chain=False,  # type: bool -            body_size_limit=None,  # type: Optional[int] -            cadir = cmdline.CA_DIR,  # type: str -            certs = (),  # type: Sequence[Tuple[str, str]] -            ciphers_client = cmdline.DEFAULT_CLIENT_CIPHERS,   # type: str -            ciphers_server = None,   # type: Optional[str] -            clientcerts = None,  # type: Optional[str] -            http2 = True,  # type: bool -            ignore_hosts = (),  # type: Sequence[str] -            listen_host = "",  # type: str -            listen_port = 8080,  # type: int -            mode = "regular",  # type: str -            no_upstream_cert = False,  # type: bool -            rawtcp = False,  # type: bool -            upstream_server = "",  # type: str -            upstream_auth = "",  # type: str -            ssl_version_client="secure",  # type: str -            ssl_version_server="secure",  # type: str -            ssl_verify_upstream_cert=False,  # type: bool -            ssl_verify_upstream_trusted_cadir=None,  # type: str -            ssl_verify_upstream_trusted_ca=None,  # type: str -            tcp_hosts = (),  # type: Sequence[str] -    ): -        # We could replace all assignments with clever metaprogramming, -        # but type hints are a much more valueable asset. - -        self.app = app -        self.app_host = app_host -        self.app_port = app_port -        self.anticache = anticache -        self.anticomp = anticomp -        self.client_replay = client_replay -        self.kill = kill -        self.no_server = no_server -        self.nopop = nopop -        self.refresh_server_playback = refresh_server_playback -        self.rfile = rfile -        self.scripts = scripts -        self.showhost = showhost -        self.replacements = replacements -        self.rheaders = rheaders -        self.setheaders = setheaders -        self.server_replay = server_replay -        self.stickycookie = stickycookie -        self.stickyauth = stickyauth -        self.stream_large_bodies = stream_large_bodies -        self.verbosity = verbosity -        self.outfile = outfile -        self.replay_ignore_content = replay_ignore_content -        self.replay_ignore_params = replay_ignore_params -        self.replay_ignore_payload_params = replay_ignore_payload_params -        self.replay_ignore_host = replay_ignore_host - -        # Proxy options -        self.auth_nonanonymous = auth_nonanonymous -        self.auth_singleuser = auth_singleuser -        self.auth_htpasswd = auth_htpasswd -        self.add_upstream_certs_to_client_chain = add_upstream_certs_to_client_chain -        self.body_size_limit = body_size_limit -        self.cadir = cadir -        self.certs = certs -        self.ciphers_client = ciphers_client -        self.ciphers_server = ciphers_server -        self.clientcerts = clientcerts -        self.http2 = http2 -        self.ignore_hosts = ignore_hosts -        self.listen_host = listen_host -        self.listen_port = listen_port -        self.mode = mode -        self.no_upstream_cert = no_upstream_cert -        self.rawtcp = rawtcp -        self.upstream_server = upstream_server -        self.upstream_auth = upstream_auth -        self.ssl_version_client = ssl_version_client -        self.ssl_version_server = ssl_version_server -        self.ssl_verify_upstream_cert = ssl_verify_upstream_cert -        self.ssl_verify_upstream_trusted_cadir = ssl_verify_upstream_trusted_cadir -        self.ssl_verify_upstream_trusted_ca = ssl_verify_upstream_trusted_ca -        self.tcp_hosts = tcp_hosts -        super(Options, self).__init__() diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 94e5d573..bdc0db4e 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -1,104 +1,137 @@  from __future__ import absolute_import, print_function, division - -import contextlib -import blinker -import pprint - -from mitmproxy import exceptions - - -class Options(object): -    """ -        .changed is a blinker Signal that triggers whenever options are -        updated. If any handler in the chain raises an exceptions.OptionsError -        exception, all changes are rolled back, the exception is suppressed, -        and the .errored signal is notified. -    """ -    _initialized = False -    attributes = [] - -    def __new__(cls, *args, **kwargs): -        # Initialize instance._opts before __init__ is called. -        # This allows us to call super().__init__() last, which then sets -        # ._initialized = True as the final operation. -        instance = super(Options, cls).__new__(cls) -        instance.__dict__["_opts"] = {} -        return instance - -    def __init__(self): -        self.__dict__["changed"] = blinker.Signal() -        self.__dict__["errored"] = blinker.Signal() -        self.__dict__["_initialized"] = True - -    @contextlib.contextmanager -    def rollback(self): -        old = self._opts.copy() -        try: -            yield -        except exceptions.OptionsError as e: -            # Notify error handlers -            self.errored.send(self, exc=e) -            # Rollback -            self.__dict__["_opts"] = old -            self.changed.send(self) - -    def __eq__(self, other): -        return self._opts == other._opts - -    def __copy__(self): -        return self.__class__(**self._opts) - -    def __getattr__(self, attr): -        if attr in self._opts: -            return self._opts[attr] -        else: -            raise AttributeError("No such option: %s" % attr) - -    def __setattr__(self, attr, value): -        if not self._initialized: -            self._opts[attr] = value -            return -        if attr not in self._opts: -            raise KeyError("No such option: %s" % attr) -        with self.rollback(): -            self._opts[attr] = value -            self.changed.send(self) - -    def get(self, k, d=None): -        return self._opts.get(k, d) - -    def update(self, **kwargs): -        for k in kwargs: -            if k not in self._opts: -                raise KeyError("No such option: %s" % k) -        with self.rollback(): -            self._opts.update(kwargs) -            self.changed.send(self) - -    def setter(self, attr): -        """ -            Generate a setter for a given attribute. This returns a callable -            taking a single argument. -        """ -        if attr not in self._opts: -            raise KeyError("No such option: %s" % attr) -        return lambda x: self.__setattr__(attr, x) - -    def toggler(self, attr): -        """ -            Generate a toggler for a boolean attribute. This returns a callable -            that takes no arguments. -        """ -        if attr not in self._opts: -            raise KeyError("No such option: %s" % attr) -        return lambda: self.__setattr__(attr, not getattr(self, attr)) - -    def __repr__(self): -        options = pprint.pformat(self._opts, indent=4).strip(" {}") -        if "\n" in options: -            options = "\n    " + options + "\n" -        return "{mod}.{cls}({{{options}}})".format( -            mod=type(self).__module__, -            cls=type(self).__name__, -            options=options -        ) +from mitmproxy import optmanager +from typing import Tuple, Optional, Sequence  # noqa + +APP_HOST = "mitm.it" +APP_PORT = 80 +CA_DIR = "~/.mitmproxy" +LISTEN_PORT = 8080 + +# We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default. +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old +DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:" \ +    "ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:" \ +    "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:" \ +    "ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:" \ +    "DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:" \ +    "DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:" \ +    "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:" \ +    "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:" \ +    "!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" + + +class Options(optmanager.OptManager): +    def __init__( +            self, +            # TODO: rename to onboarding_app_* +            app=True,  # type: bool +            app_host=APP_HOST,  # type: str +            app_port=APP_PORT,  # type: int +            anticache=False,  # type: bool +            anticomp=False,  # type: bool +            client_replay=None,  # type: Optional[str] +            kill=False,  # type: bool +            no_server=False,  # type: bool +            nopop=False,  # type: bool +            refresh_server_playback=False,  # type: bool +            rfile=None,  # type: Optional[str] +            scripts=(),  # type: Sequence[str] +            showhost=False,  # type: bool +            replacements=(),  # type: Sequence[Tuple[str, str, str]] +            rheaders=(),  # type: Sequence[str] +            setheaders=(),  # type: Sequence[Tuple[str, str, str]] +            server_replay=None,  # type: Optional[str] +            stickycookie=None,  # type: Optional[str] +            stickyauth=None,  # type: Optional[str] +            stream_large_bodies=None,  # type: Optional[str] +            verbosity=2,  # type: int +            outfile=None,  # type: Tuple[str, str] +            replay_ignore_content=False,  # type: bool +            replay_ignore_params=(),  # type: Sequence[str] +            replay_ignore_payload_params=(),  # type: Sequence[str] +            replay_ignore_host=False,  # type: bool + +            # Proxy options +            auth_nonanonymous=False,  # type: bool +            auth_singleuser=None,  # type: Optional[str] +            auth_htpasswd=None,  # type: Optional[str] +            add_upstream_certs_to_client_chain=False,  # type: bool +            body_size_limit=None,  # type: Optional[int] +            cadir = CA_DIR,  # type: str +            certs = (),  # type: Sequence[Tuple[str, str]] +            ciphers_client = DEFAULT_CLIENT_CIPHERS,   # type: str +            ciphers_server = None,   # type: Optional[str] +            clientcerts = None,  # type: Optional[str] +            http2 = True,  # type: bool +            ignore_hosts = (),  # type: Sequence[str] +            listen_host = "",  # type: str +            listen_port = LISTEN_PORT,  # type: int +            mode = "regular",  # type: str +            no_upstream_cert = False,  # type: bool +            rawtcp = False,  # type: bool +            upstream_server = "",  # type: str +            upstream_auth = "",  # type: str +            ssl_version_client="secure",  # type: str +            ssl_version_server="secure",  # type: str +            ssl_verify_upstream_cert=False,  # type: bool +            ssl_verify_upstream_trusted_cadir=None,  # type: str +            ssl_verify_upstream_trusted_ca=None,  # type: str +            tcp_hosts = (),  # type: Sequence[str] +    ): +        # We could replace all assignments with clever metaprogramming, +        # but type hints are a much more valueable asset. + +        self.app = app +        self.app_host = app_host +        self.app_port = app_port +        self.anticache = anticache +        self.anticomp = anticomp +        self.client_replay = client_replay +        self.kill = kill +        self.no_server = no_server +        self.nopop = nopop +        self.refresh_server_playback = refresh_server_playback +        self.rfile = rfile +        self.scripts = scripts +        self.showhost = showhost +        self.replacements = replacements +        self.rheaders = rheaders +        self.setheaders = setheaders +        self.server_replay = server_replay +        self.stickycookie = stickycookie +        self.stickyauth = stickyauth +        self.stream_large_bodies = stream_large_bodies +        self.verbosity = verbosity +        self.outfile = outfile +        self.replay_ignore_content = replay_ignore_content +        self.replay_ignore_params = replay_ignore_params +        self.replay_ignore_payload_params = replay_ignore_payload_params +        self.replay_ignore_host = replay_ignore_host + +        # Proxy options +        self.auth_nonanonymous = auth_nonanonymous +        self.auth_singleuser = auth_singleuser +        self.auth_htpasswd = auth_htpasswd +        self.add_upstream_certs_to_client_chain = add_upstream_certs_to_client_chain +        self.body_size_limit = body_size_limit +        self.cadir = cadir +        self.certs = certs +        self.ciphers_client = ciphers_client +        self.ciphers_server = ciphers_server +        self.clientcerts = clientcerts +        self.http2 = http2 +        self.ignore_hosts = ignore_hosts +        self.listen_host = listen_host +        self.listen_port = listen_port +        self.mode = mode +        self.no_upstream_cert = no_upstream_cert +        self.rawtcp = rawtcp +        self.upstream_server = upstream_server +        self.upstream_auth = upstream_auth +        self.ssl_version_client = ssl_version_client +        self.ssl_version_server = ssl_version_server +        self.ssl_verify_upstream_cert = ssl_verify_upstream_cert +        self.ssl_verify_upstream_trusted_cadir = ssl_verify_upstream_trusted_cadir +        self.ssl_verify_upstream_trusted_ca = ssl_verify_upstream_trusted_ca +        self.tcp_hosts = tcp_hosts +        super(Options, self).__init__() diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py new file mode 100644 index 00000000..e94ef51d --- /dev/null +++ b/mitmproxy/optmanager.py @@ -0,0 +1,108 @@ +from __future__ import absolute_import, print_function, division + +import contextlib +import blinker +import pprint + +from mitmproxy import exceptions + +""" +    The base implementation for Options. +""" + + +class OptManager(object): +    """ +        .changed is a blinker Signal that triggers whenever options are +        updated. If any handler in the chain raises an exceptions.OptionsError +        exception, all changes are rolled back, the exception is suppressed, +        and the .errored signal is notified. +    """ +    _initialized = False +    attributes = [] + +    def __new__(cls, *args, **kwargs): +        # Initialize instance._opts before __init__ is called. +        # This allows us to call super().__init__() last, which then sets +        # ._initialized = True as the final operation. +        instance = super(OptManager, cls).__new__(cls) +        instance.__dict__["_opts"] = {} +        return instance + +    def __init__(self): +        self.__dict__["changed"] = blinker.Signal() +        self.__dict__["errored"] = blinker.Signal() +        self.__dict__["_initialized"] = True + +    @contextlib.contextmanager +    def rollback(self): +        old = self._opts.copy() +        try: +            yield +        except exceptions.OptionsError as e: +            # Notify error handlers +            self.errored.send(self, exc=e) +            # Rollback +            self.__dict__["_opts"] = old +            self.changed.send(self) + +    def __eq__(self, other): +        return self._opts == other._opts + +    def __copy__(self): +        return self.__class__(**self._opts) + +    def __getattr__(self, attr): +        if attr in self._opts: +            return self._opts[attr] +        else: +            raise AttributeError("No such option: %s" % attr) + +    def __setattr__(self, attr, value): +        if not self._initialized: +            self._opts[attr] = value +            return +        if attr not in self._opts: +            raise KeyError("No such option: %s" % attr) +        with self.rollback(): +            self._opts[attr] = value +            self.changed.send(self) + +    def get(self, k, d=None): +        return self._opts.get(k, d) + +    def update(self, **kwargs): +        for k in kwargs: +            if k not in self._opts: +                raise KeyError("No such option: %s" % k) +        with self.rollback(): +            self._opts.update(kwargs) +            self.changed.send(self) + +    def setter(self, attr): +        """ +            Generate a setter for a given attribute. This returns a callable +            taking a single argument. +        """ +        if attr not in self._opts: +            raise KeyError("No such option: %s" % attr) +        return lambda x: self.__setattr__(attr, x) + +    def toggler(self, attr): +        """ +            Generate a toggler for a boolean attribute. This returns a callable +            that takes no arguments. +        """ +        if attr not in self._opts: +            raise KeyError("No such option: %s" % attr) +        return lambda: self.__setattr__(attr, not getattr(self, attr)) + +    def __repr__(self): +        options = pprint.pformat(self._opts, indent=4).strip(" {}") +        if "\n" in options: +            options = "\n    " + options + "\n" +        return "{mod}.{cls}({{{options}}})".format( +            mod=type(self).__module__, +            cls=type(self).__name__, +            options=options +        ) diff --git a/mitmproxy/web/master.py b/mitmproxy/web/master.py index a0d68191..3d384612 100644 --- a/mitmproxy/web/master.py +++ b/mitmproxy/web/master.py @@ -12,6 +12,7 @@ from mitmproxy import builtins  from mitmproxy import controller  from mitmproxy import exceptions  from mitmproxy import flow +from mitmproxy import options  from mitmproxy.web import app  from netlib.http import authentication @@ -91,7 +92,7 @@ class WebState(flow.State):          ) -class Options(flow.options.Options): +class Options(options.Options):      def __init__(              self,              intercept=None,  # type: Optional[str] diff --git a/test/mitmproxy/builtins/test_anticache.py b/test/mitmproxy/builtins/test_anticache.py index 127e1c1a..5a00af03 100644 --- a/test/mitmproxy/builtins/test_anticache.py +++ b/test/mitmproxy/builtins/test_anticache.py @@ -2,7 +2,7 @@ from .. import tutils, mastertest  from mitmproxy.builtins import anticache  from mitmproxy.flow import master  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  class TestAntiCache(mastertest.MasterTest): diff --git a/test/mitmproxy/builtins/test_anticomp.py b/test/mitmproxy/builtins/test_anticomp.py index 601e56c8..6bfd54bb 100644 --- a/test/mitmproxy/builtins/test_anticomp.py +++ b/test/mitmproxy/builtins/test_anticomp.py @@ -2,7 +2,7 @@ from .. import tutils, mastertest  from mitmproxy.builtins import anticomp  from mitmproxy.flow import master  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  class TestAntiComp(mastertest.MasterTest): diff --git a/test/mitmproxy/builtins/test_filestreamer.py b/test/mitmproxy/builtins/test_filestreamer.py index 002006b7..c1d5947f 100644 --- a/test/mitmproxy/builtins/test_filestreamer.py +++ b/test/mitmproxy/builtins/test_filestreamer.py @@ -7,7 +7,7 @@ import os.path  from mitmproxy.builtins import filestreamer  from mitmproxy.flow import master, FlowReader  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  class TestStream(mastertest.MasterTest): diff --git a/test/mitmproxy/builtins/test_replace.py b/test/mitmproxy/builtins/test_replace.py index f8010bec..a0b4b722 100644 --- a/test/mitmproxy/builtins/test_replace.py +++ b/test/mitmproxy/builtins/test_replace.py @@ -2,7 +2,7 @@ from .. import tutils, mastertest  from mitmproxy.builtins import replace  from mitmproxy.flow import master  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  class TestReplace(mastertest.MasterTest): diff --git a/test/mitmproxy/builtins/test_script.py b/test/mitmproxy/builtins/test_script.py index c9616249..f37c7f94 100644 --- a/test/mitmproxy/builtins/test_script.py +++ b/test/mitmproxy/builtins/test_script.py @@ -4,7 +4,7 @@ from mitmproxy.builtins import script  from mitmproxy import exceptions  from mitmproxy.flow import master  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  from .. import tutils, mastertest diff --git a/test/mitmproxy/builtins/test_setheaders.py b/test/mitmproxy/builtins/test_setheaders.py index 1a8d048c..4465719d 100644 --- a/test/mitmproxy/builtins/test_setheaders.py +++ b/test/mitmproxy/builtins/test_setheaders.py @@ -2,7 +2,7 @@ from .. import tutils, mastertest  from mitmproxy.builtins import setheaders  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  class TestSetHeaders(mastertest.MasterTest): diff --git a/test/mitmproxy/builtins/test_stickyauth.py b/test/mitmproxy/builtins/test_stickyauth.py index 1e617402..9233f435 100644 --- a/test/mitmproxy/builtins/test_stickyauth.py +++ b/test/mitmproxy/builtins/test_stickyauth.py @@ -2,7 +2,7 @@ from .. import tutils, mastertest  from mitmproxy.builtins import stickyauth  from mitmproxy.flow import master  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  class TestStickyAuth(mastertest.MasterTest): diff --git a/test/mitmproxy/builtins/test_stickycookie.py b/test/mitmproxy/builtins/test_stickycookie.py index b8d703bd..81b540db 100644 --- a/test/mitmproxy/builtins/test_stickycookie.py +++ b/test/mitmproxy/builtins/test_stickycookie.py @@ -2,7 +2,7 @@ from .. import tutils, mastertest  from mitmproxy.builtins import stickycookie  from mitmproxy.flow import master  from mitmproxy.flow import state -from mitmproxy.flow import options +from mitmproxy import options  from netlib import tutils as ntutils diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index e17a125c..36b212a7 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -3,8 +3,7 @@ import io  import netlib.utils  from netlib.http import Headers -from mitmproxy import filt, controller, flow -from mitmproxy.flow import options +from mitmproxy import filt, controller, flow, options  from mitmproxy.contrib import tnetstring  from mitmproxy.exceptions import FlowReadException  from mitmproxy.models import Error @@ -745,7 +744,7 @@ class TestFlowMaster:          f = tutils.tflow(resp=True)          pb = [tutils.tflow(resp=True), f]          fm = flow.FlowMaster( -            flow.options.Options(), +            options.Options(),              DummyServer(ProxyConfig(options.Options())),              s          ) @@ -776,7 +775,7 @@ class TestFlowMaster:          f.response = HTTPResponse.wrap(netlib.tutils.tresp(content=f.request))          pb = [f] -        fm = flow.FlowMaster(flow.options.Options(), None, s) +        fm = flow.FlowMaster(options.Options(), None, s)          fm.refresh_server_playback = True          assert not fm.do_server_playback(tutils.tflow()) diff --git a/test/mitmproxy/test_options.py b/test/mitmproxy/test_optmanager.py index af619b27..67f76ecd 100644 --- a/test/mitmproxy/test_options.py +++ b/test/mitmproxy/test_optmanager.py @@ -1,12 +1,12 @@  from __future__ import absolute_import, print_function, division  import copy -from mitmproxy import options +from mitmproxy import optmanager  from mitmproxy import exceptions  from netlib import tutils -class TO(options.Options): +class TO(optmanager.OptManager):      def __init__(self, one=None, two=None):          self.one = one          self.two = two @@ -93,8 +93,8 @@ def test_rollback():  def test_repr(): -    assert repr(TO()) == "test.mitmproxy.test_options.TO({'one': None, 'two': None})" -    assert repr(TO(one='x' * 60)) == """test.mitmproxy.test_options.TO({ +    assert repr(TO()) == "test.mitmproxy.test_optmanager.TO({'one': None, 'two': None})" +    assert repr(TO(one='x' * 60)) == """test.mitmproxy.test_optmanager.TO({      'one': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',      'two': None  })""" diff --git a/test/mitmproxy/test_protocol_http2.py b/test/mitmproxy/test_protocol_http2.py index d11570d9..afbffb67 100644 --- a/test/mitmproxy/test_protocol_http2.py +++ b/test/mitmproxy/test_protocol_http2.py @@ -9,9 +9,8 @@ import traceback  import h2 -from mitmproxy.flow import options +from mitmproxy import options  from mitmproxy.proxy.config import ProxyConfig -from mitmproxy.cmdline import APP_HOST, APP_PORT  import netlib  from ..netlib import tservers as netlib_tservers @@ -94,7 +93,7 @@ class _Http2TestBase(object):          cls.config = ProxyConfig(opts)          tmaster = tservers.TestMaster(opts, cls.config) -        tmaster.start_app(APP_HOST, APP_PORT) +        tmaster.start_app(options.APP_HOST, options.APP_PORT)          cls.proxy = tservers.ProxyThread(tmaster)          cls.proxy.start() diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 7095d9d2..6e790e28 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -3,10 +3,10 @@ import mock  from OpenSSL import SSL  from mitmproxy import cmdline +from mitmproxy import options  from mitmproxy.proxy import ProxyConfig  from mitmproxy.models.connections import ServerConnection  from mitmproxy.proxy.server import DummyServer, ProxyServer, ConnectionHandler -from mitmproxy.flow import options  from mitmproxy.proxy import config  from netlib.exceptions import TcpDisconnect  from pathod import test diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py index 30980e40..f5119166 100644 --- a/test/mitmproxy/tservers.py +++ b/test/mitmproxy/tservers.py @@ -8,9 +8,7 @@ from mitmproxy.proxy.config import ProxyConfig  from mitmproxy.proxy.server import ProxyServer  import pathod.test  import pathod.pathoc -from mitmproxy import flow, controller -from mitmproxy.flow import options -from mitmproxy.cmdline import APP_HOST, APP_PORT +from mitmproxy import flow, controller, options  from mitmproxy import builtins  testapp = flask.Flask(__name__) @@ -93,7 +91,7 @@ class ProxyTestBase(object):          opts = cls.get_options()          cls.config = ProxyConfig(opts)          tmaster = cls.masterclass(opts, cls.config) -        tmaster.start_app(APP_HOST, APP_PORT) +        tmaster.start_app(options.APP_HOST, options.APP_PORT)          cls.proxy = ProxyThread(tmaster)          cls.proxy.start() @@ -160,11 +158,11 @@ class HTTPProxyTest(ProxyTestBase):              p = pathod.pathoc.Pathoc(                  ("127.0.0.1", self.proxy.port), True, fp=None              ) -            p.connect((APP_HOST, APP_PORT)) +            p.connect((options.APP_HOST, options.APP_PORT))              return p.request("get:'%s'" % page)          else:              p = self.pathoc() -            return p.request("get:'http://%s%s'" % (APP_HOST, page)) +            return p.request("get:'http://%s%s'" % (options.APP_HOST, page))  class TResolver:  | 
