make options explicit

This commit is contained in:
Maximilian Hils 2016-07-13 21:40:13 -07:00
parent b8a23eeaa3
commit ec6fbe9eb6
13 changed files with 190 additions and 133 deletions

View File

@ -14,6 +14,7 @@ import traceback
import weakref import weakref
import urwid import urwid
from typing import Optional # noqa
from mitmproxy import builtins from mitmproxy import builtins
from mitmproxy import contentviews from mitmproxy import contentviews
@ -21,7 +22,6 @@ from mitmproxy import controller
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy import flow from mitmproxy import flow
from mitmproxy import script from mitmproxy import script
import mitmproxy.options
from mitmproxy.console import flowlist from mitmproxy.console import flowlist
from mitmproxy.console import flowview from mitmproxy.console import flowview
from mitmproxy.console import grideditor from mitmproxy.console import grideditor
@ -177,40 +177,26 @@ class ConsoleState(flow.State):
self.add_flow_setting(flow, "marked", marked) self.add_flow_setting(flow, "marked", marked)
class Options(mitmproxy.options.Options): class Options(flow.options.Options):
attributes = [ def __init__(
"app", self,
"app_domain", eventlog=False, # type: bool
"app_ip", follow=False, # type: bool
"anticache", intercept=False, # type: bool
"anticomp", limit=None, # type: Optional[str]
"client_replay", palette=None, # type: Optional[str]
"eventlog", palette_transparent=False, # type: bool
"follow", no_mouse=False, # type: bool
"keepserving", **kwargs
"kill", ):
"intercept", self.eventlog = eventlog
"limit", self.follow = follow
"no_server", self.intercept = intercept
"refresh_server_playback", self.limit = limit
"rfile", self.palette = palette
"scripts", self.palette_transparent = palette_transparent
"showhost", self.no_mouse = no_mouse
"replacements", super(Options, self).__init__(**kwargs)
"rheaders",
"setheaders",
"server_replay",
"stickycookie",
"stickyauth",
"stream_large_bodies",
"verbosity",
"wfile",
"nopop",
"palette",
"palette_transparent",
"no_mouse",
"outfile",
]
class ConsoleMaster(flow.FlowMaster): class ConsoleMaster(flow.FlowMaster):
@ -221,6 +207,8 @@ class ConsoleMaster(flow.FlowMaster):
self.addons.add(*builtins.default_addons()) self.addons.add(*builtins.default_addons())
self.stream_path = None self.stream_path = None
# This line is just for type hinting
self.options = self.options # type: Options
self.options.errored.connect(self.options_error) self.options.errored.connect(self.options_error)
if options.replacements: if options.replacements:

View File

@ -6,12 +6,14 @@ import traceback
import click import click
from typing import Optional # noqa
import typing
from mitmproxy import contentviews from mitmproxy import contentviews
from mitmproxy import controller from mitmproxy import controller
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy import filt from mitmproxy import filt
from mitmproxy import flow from mitmproxy import flow
from mitmproxy import options
from mitmproxy import builtins from mitmproxy import builtins
from netlib import human from netlib import human
from netlib import tcp from netlib import tcp
@ -22,40 +24,20 @@ class DumpError(Exception):
pass pass
class Options(options.Options): class Options(flow.options.Options):
attributes = [ def __init__(
"app", self,
"app_host", filtstr=None, # type: Optional[str]
"app_port", flow_detail=1, # type: int
"anticache", keepserving=False, # type: bool
"anticomp", tfile=None, # type: Optional[typing.io.TextIO]
"client_replay", **kwargs
"filtstr", ):
"flow_detail", self.filtstr = filtstr
"keepserving", self.flow_detail = flow_detail
"kill", self.keepserving = keepserving
"no_server", self.tfile = tfile
"nopop", super(Options, self).__init__(**kwargs)
"refresh_server_playback",
"replacements",
"rfile",
"rheaders",
"setheaders",
"server_replay",
"scripts",
"showhost",
"stickycookie",
"stickyauth",
"stream_large_bodies",
"verbosity",
"outfile",
"replay_ignore_content",
"replay_ignore_params",
"replay_ignore_payload_params",
"replay_ignore_host",
"tfile"
]
class DumpMaster(flow.FlowMaster): class DumpMaster(flow.FlowMaster):
@ -63,6 +45,8 @@ class DumpMaster(flow.FlowMaster):
def __init__(self, server, options): def __init__(self, server, options):
flow.FlowMaster.__init__(self, options, server, flow.State()) flow.FlowMaster.__init__(self, options, server, flow.State())
self.addons.add(*builtins.default_addons()) self.addons.add(*builtins.default_addons())
# This line is just for type hinting
self.options = self.options # type: Options
self.o = options self.o = options
self.showhost = options.showhost self.showhost = options.showhost
self.replay_ignore_params = options.replay_ignore_params self.replay_ignore_params = options.replay_ignore_params

View File

@ -8,6 +8,7 @@ from mitmproxy.flow.modules import (
ServerPlaybackState ServerPlaybackState
) )
from mitmproxy.flow.state import State, FlowView 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" # TODO: We may want to remove the imports from .modules and just expose "modules"
@ -17,4 +18,5 @@ __all__ = [
"FlowMaster", "FlowMaster",
"AppRegistry", "ReplaceHooks", "SetHeaders", "StreamLargeBodies", "ClientPlaybackState", "AppRegistry", "ReplaceHooks", "SetHeaders", "StreamLargeBodies", "ClientPlaybackState",
"ServerPlaybackState", "State", "FlowView", "ServerPlaybackState", "State", "FlowView",
"options",
] ]

69
mitmproxy/flow/options.py Normal file
View File

@ -0,0 +1,69 @@
from __future__ import absolute_import, print_function, division
from mitmproxy import options
from typing import Tuple, Optional, Sequence # noqa
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=1, # type: int
outfile=None, # type: Optional[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
):
# 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
super(Options, self).__init__()

View File

@ -14,14 +14,21 @@ class Options(object):
exception, all changes are rolled back, the exception is suppressed, exception, all changes are rolled back, the exception is suppressed,
and the .errored signal is notified. and the .errored signal is notified.
""" """
_initialized = False
attributes = [] attributes = []
def __init__(self, **kwargs): 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__["changed"] = blinker.Signal()
self.__dict__["errored"] = blinker.Signal() self.__dict__["errored"] = blinker.Signal()
self.__dict__["_opts"] = dict([(i, None) for i in self.attributes]) self.__dict__["_initialized"] = True
for k, v in kwargs.items():
self._opts[k] = v
@contextlib.contextmanager @contextlib.contextmanager
def rollback(self): def rollback(self):
@ -48,6 +55,9 @@ class Options(object):
raise AttributeError() raise AttributeError()
def __setattr__(self, attr, value): def __setattr__(self, attr, value):
if not self._initialized:
self._opts[attr] = value
return
if attr not in self._opts: if attr not in self._opts:
raise KeyError("No such option: %s" % attr) raise KeyError("No such option: %s" % attr)
with self.rollback(): with self.rollback():
@ -71,4 +81,11 @@ class Options(object):
return lambda x: self.__setattr__(attr, x) return lambda x: self.__setattr__(attr, x)
def __repr__(self): def __repr__(self):
return pprint.pformat(self._opts) 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
)

View File

@ -6,11 +6,12 @@ import collections
import tornado.httpserver import tornado.httpserver
import tornado.ioloop import tornado.ioloop
from typing import Optional # noqa
from mitmproxy import builtins from mitmproxy import builtins
from mitmproxy import controller from mitmproxy import controller
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy import flow from mitmproxy import flow
from mitmproxy import options
from mitmproxy.web import app from mitmproxy.web import app
from netlib.http import authentication from netlib.http import authentication
@ -90,43 +91,26 @@ class WebState(flow.State):
) )
class Options(options.Options): class Options(flow.options.Options):
attributes = [ def __init__(
"app", self,
"app_domain", wdebug=bool, # type: bool
"app_ip", wport=8081, # type: int
"anticache", wiface="127.0.0.1", # type: str
"anticomp", wauthenticator=None, # type: Optional[authentication.PassMan]
"client_replay", wsingleuser=None, # type: Optional[str]
"eventlog", whtpasswd=None, # type: Optional[str]
"keepserving", **kwargs
"kill", ):
"intercept", self.wdebug = wdebug
"no_server", self.wport = wport
"outfile", self.wiface = wiface
"refresh_server_playback", self.wauthenticator = wauthenticator
"rfile", self.wsingleuser = wsingleuser
"scripts", self.whtpasswd = whtpasswd
"showhost", super(Options, self).__init__(**kwargs)
"replacements",
"rheaders",
"setheaders",
"server_replay",
"stickycookie",
"stickyauth",
"stream_large_bodies",
"verbosity",
"wfile",
"nopop",
"wdebug",
"wport",
"wiface",
"wauthenticator",
"wsingleuser",
"whtpasswd",
]
# TODO: This doesn't belong here.
def process_web_options(self, parser): def process_web_options(self, parser):
if self.wsingleuser or self.whtpasswd: if self.wsingleuser or self.whtpasswd:
if self.wsingleuser: if self.wsingleuser:
@ -153,6 +137,8 @@ class WebMaster(flow.FlowMaster):
self.app = app.Application( self.app = app.Application(
self, self.options.wdebug, self.options.wauthenticator self, self.options.wdebug, self.options.wauthenticator
) )
# This line is just for type hinting
self.options = self.options # type: Options
if options.rfile: if options.rfile:
try: try:
self.load_flows_file(options.rfile) self.load_flows_file(options.rfile)

View File

@ -2,7 +2,7 @@ from .. import tutils, mastertest
from mitmproxy.builtins import anticache from mitmproxy.builtins import anticache
from mitmproxy.flow import master from mitmproxy.flow import master
from mitmproxy.flow import state from mitmproxy.flow import state
from mitmproxy import options from mitmproxy.flow import options
class TestAntiCache(mastertest.MasterTest): class TestAntiCache(mastertest.MasterTest):

View File

@ -2,7 +2,7 @@ from .. import tutils, mastertest
from mitmproxy.builtins import anticomp from mitmproxy.builtins import anticomp
from mitmproxy.flow import master from mitmproxy.flow import master
from mitmproxy.flow import state from mitmproxy.flow import state
from mitmproxy import options from mitmproxy.flow import options
class TestAntiComp(mastertest.MasterTest): class TestAntiComp(mastertest.MasterTest):

View File

@ -2,7 +2,7 @@ from .. import tutils, mastertest
from mitmproxy.builtins import stickyauth from mitmproxy.builtins import stickyauth
from mitmproxy.flow import master from mitmproxy.flow import master
from mitmproxy.flow import state from mitmproxy.flow import state
from mitmproxy import options from mitmproxy.flow import options
class TestStickyAuth(mastertest.MasterTest): class TestStickyAuth(mastertest.MasterTest):

View File

@ -2,7 +2,7 @@ from .. import tutils, mastertest
from mitmproxy.builtins import stickycookie from mitmproxy.builtins import stickycookie
from mitmproxy.flow import master from mitmproxy.flow import master
from mitmproxy.flow import state from mitmproxy.flow import state
from mitmproxy import options from mitmproxy.flow import options
from netlib import tutils as ntutils from netlib import tutils as ntutils

View File

@ -111,14 +111,14 @@ def test_options():
class TestMaster(mastertest.MasterTest): class TestMaster(mastertest.MasterTest):
def mkmaster(self, filt, **options): def mkmaster(self, **options):
if "verbosity" not in options: if "verbosity" not in options:
options["verbosity"] = 0 options["verbosity"] = 0
o = console.master.Options(filtstr=filt, **options) o = console.master.Options(**options)
return console.master.ConsoleMaster(None, o) return console.master.ConsoleMaster(None, o)
def test_basic(self): def test_basic(self):
m = self.mkmaster(None) m = self.mkmaster()
for i in (1, 2, 3): for i in (1, 2, 3):
self.dummy_cycle(m, 1, b"") self.dummy_cycle(m, 1, b"")
assert len(m.state.flows) == i assert len(m.state.flows) == i

View File

@ -7,10 +7,10 @@ from netlib import tutils
class TO(options.Options): class TO(options.Options):
attributes = [ def __init__(self, one=None, two=None):
"one", self.one = one
"two" self.two = two
] super(TO, self).__init__()
def test_options(): def test_options():
@ -19,8 +19,13 @@ def test_options():
assert o.two == "three" assert o.two == "three"
o.one = "one" o.one = "one"
assert o.one == "one" assert o.one == "one"
tutils.raises("no such option", setattr, o, "nonexistent", "value")
tutils.raises("no such option", o.update, nonexistent = "value") with tutils.raises(TypeError):
TO(nonexistent = "value")
with tutils.raises("no such option"):
o.nonexistent = "value"
with tutils.raises("no such option"):
o.update(nonexistent = "value")
rec = [] rec = []
@ -43,7 +48,8 @@ def test_setter():
f = o.setter("two") f = o.setter("two")
f("xxx") f("xxx")
assert o.two == "xxx" assert o.two == "xxx"
tutils.raises("no such option", o.setter, "nonexistent") with tutils.raises("no such option"):
o.setter("nonexistent")
def test_rollback(): def test_rollback():
@ -61,7 +67,7 @@ def test_rollback():
def err(opts): def err(opts):
if opts.one == "ten": if opts.one == "ten":
raise exceptions.OptionsError raise exceptions.OptionsError()
o.changed.connect(sub) o.changed.connect(sub)
o.changed.connect(err) o.changed.connect(err)
@ -73,3 +79,11 @@ def test_rollback():
assert len(rec) == 2 assert len(rec) == 2
assert rec[0].one == "ten" assert rec[0].one == "ten"
assert rec[1].one == "two" assert rec[1].one == "two"
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({
'one': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'two': None
})"""

View File

@ -3,15 +3,12 @@ from . import mastertest
class TestWebMaster(mastertest.MasterTest): class TestWebMaster(mastertest.MasterTest):
def mkmaster(self, filt, **options): def mkmaster(self, **options):
o = master.Options( o = master.Options(**options)
filtstr=filt,
**options
)
return master.WebMaster(None, o) return master.WebMaster(None, o)
def test_basic(self): def test_basic(self):
m = self.mkmaster(None) m = self.mkmaster()
for i in (1, 2, 3): for i in (1, 2, 3):
self.dummy_cycle(m, 1, b"") self.dummy_cycle(m, 1, b"")
assert len(m.state.flows) == i assert len(m.state.flows) == i