aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/builtins/__init__.py4
-rw-r--r--mitmproxy/builtins/clientplayback.py39
-rw-r--r--mitmproxy/builtins/dumper.py8
-rw-r--r--mitmproxy/builtins/script.py16
-rw-r--r--mitmproxy/builtins/serverplayback.py30
-rw-r--r--mitmproxy/cmdline.py32
-rw-r--r--mitmproxy/console/flowlist.py61
-rw-r--r--mitmproxy/console/flowview.py4
-rw-r--r--mitmproxy/console/help.py2
-rw-r--r--mitmproxy/console/master.py49
-rw-r--r--mitmproxy/console/options.py6
-rw-r--r--mitmproxy/console/statusbar.py14
-rw-r--r--mitmproxy/dump.py12
-rw-r--r--mitmproxy/exceptions.py28
-rw-r--r--mitmproxy/flow/__init__.py6
-rw-r--r--mitmproxy/flow/master.py156
-rw-r--r--mitmproxy/flow/modules.py35
-rw-r--r--mitmproxy/options.py28
-rw-r--r--mitmproxy/protocol/http_replay.py3
-rw-r--r--mitmproxy/web/app.py2
-rw-r--r--setup.py2
-rw-r--r--test/mitmproxy/builtins/test_clientplayback.py37
-rw-r--r--test/mitmproxy/builtins/test_script.py25
-rw-r--r--test/mitmproxy/builtins/test_serverplayback.py26
-rw-r--r--test/mitmproxy/console/test_master.py2
-rw-r--r--test/mitmproxy/mastertest.py13
-rw-r--r--test/mitmproxy/protocol/test_http1.py12
-rw-r--r--test/mitmproxy/test_dump.py9
-rw-r--r--test/mitmproxy/test_flow.py59
-rw-r--r--test/mitmproxy/test_fuzzing.py9
-rw-r--r--test/mitmproxy/test_server.py337
-rw-r--r--test/mitmproxy/tservers.py49
32 files changed, 544 insertions, 571 deletions
diff --git a/mitmproxy/builtins/__init__.py b/mitmproxy/builtins/__init__.py
index 5f668570..26e9dfbd 100644
--- a/mitmproxy/builtins/__init__.py
+++ b/mitmproxy/builtins/__init__.py
@@ -9,6 +9,7 @@ from mitmproxy.builtins import script
from mitmproxy.builtins import replace
from mitmproxy.builtins import setheaders
from mitmproxy.builtins import serverplayback
+from mitmproxy.builtins import clientplayback
def default_addons():
@@ -21,5 +22,6 @@ def default_addons():
filestreamer.FileStreamer(),
replace.Replace(),
setheaders.SetHeaders(),
- serverplayback.ServerPlayback()
+ serverplayback.ServerPlayback(),
+ clientplayback.ClientPlayback(),
]
diff --git a/mitmproxy/builtins/clientplayback.py b/mitmproxy/builtins/clientplayback.py
new file mode 100644
index 00000000..c40d1904
--- /dev/null
+++ b/mitmproxy/builtins/clientplayback.py
@@ -0,0 +1,39 @@
+from mitmproxy import exceptions, flow, ctx
+
+
+class ClientPlayback:
+ def __init__(self):
+ self.flows = None
+ self.current = None
+ self.keepserving = None
+ self.has_replayed = False
+
+ def count(self):
+ if self.flows:
+ return len(self.flows)
+ return 0
+
+ def load(self, flows):
+ self.flows = flows
+
+ def configure(self, options, updated):
+ if "client_replay" in updated:
+ if options.client_replay:
+ try:
+ flows = flow.read_flows_from_paths(options.client_replay)
+ except exceptions.FlowReadException as e:
+ raise exceptions.OptionsError(str(e))
+ self.load(flows)
+ else:
+ self.flows = None
+ self.keepserving = options.keepserving
+
+ def tick(self):
+ if self.current and not self.current.is_alive():
+ self.current = None
+ if self.flows and not self.current:
+ self.current = ctx.master.replay_request(self.flows.pop(0))
+ self.has_replayed = True
+ if self.has_replayed:
+ if not self.flows and not self.current and not self.keepserving:
+ ctx.master.shutdown()
diff --git a/mitmproxy/builtins/dumper.py b/mitmproxy/builtins/dumper.py
index 743ca72e..60d00518 100644
--- a/mitmproxy/builtins/dumper.py
+++ b/mitmproxy/builtins/dumper.py
@@ -232,6 +232,14 @@ class Dumper(object):
if self.match(f):
self.echo_flow(f)
+ def tcp_error(self, f):
+ self.echo(
+ "Error in TCP connection to {}: {}".format(
+ repr(f.server_conn.address), f.error
+ ),
+ fg="red"
+ )
+
def tcp_message(self, f):
if not self.match(f):
return
diff --git a/mitmproxy/builtins/script.py b/mitmproxy/builtins/script.py
index ae1d1b91..1ebec873 100644
--- a/mitmproxy/builtins/script.py
+++ b/mitmproxy/builtins/script.py
@@ -10,6 +10,7 @@ import traceback
from mitmproxy import exceptions
from mitmproxy import controller
from mitmproxy import ctx
+from mitmproxy.flow import master as flowmaster
import watchdog.events
@@ -67,7 +68,11 @@ def scriptenv(path, args):
tb = tb.tb_next
if not os.path.abspath(s[0]).startswith(scriptdir):
break
- ctx.log.error("Script error: %s" % "".join(traceback.format_exception(etype, value, tb)))
+ ctx.log.error(
+ "Script error: %s" % "".join(
+ traceback.format_exception(etype, value, tb)
+ )
+ )
finally:
sys.argv = oldargs
sys.path.pop()
@@ -189,6 +194,15 @@ class ScriptLoader():
"""
An addon that manages loading scripts from options.
"""
+ def run_once(self, command, flows):
+ sc = Script(command)
+ sc.load_script()
+ for f in flows:
+ for evt, o in flowmaster.event_sequence(f):
+ sc.run(evt, o)
+ sc.done()
+ return sc
+
def configure(self, options, updated):
if "scripts" in updated:
for s in options.scripts:
diff --git a/mitmproxy/builtins/serverplayback.py b/mitmproxy/builtins/serverplayback.py
index be82cad9..29fc95ef 100644
--- a/mitmproxy/builtins/serverplayback.py
+++ b/mitmproxy/builtins/serverplayback.py
@@ -36,12 +36,12 @@ class ServerPlayback(object):
queriesArray = urllib.parse.parse_qsl(query, keep_blank_values=True)
key = [str(r.port), str(r.scheme), str(r.method), str(path)]
- if not self.options.replay_ignore_content:
+ if not self.options.server_replay_ignore_content:
form_contents = r.urlencoded_form or r.multipart_form
- if self.options.replay_ignore_payload_params and form_contents:
+ if self.options.server_replay_ignore_payload_params and form_contents:
params = [
strutils.always_bytes(i)
- for i in self.options.replay_ignore_payload_params
+ for i in self.options.server_replay_ignore_payload_params
]
for p in form_contents.items(multi=True):
if p[0] not in params:
@@ -49,11 +49,11 @@ class ServerPlayback(object):
else:
key.append(str(r.raw_content))
- if not self.options.replay_ignore_host:
+ if not self.options.server_replay_ignore_host:
key.append(r.host)
filtered = []
- ignore_params = self.options.replay_ignore_params or []
+ ignore_params = self.options.server_replay_ignore_params or []
for p in queriesArray:
if p[0] not in ignore_params:
filtered.append(p)
@@ -61,9 +61,9 @@ class ServerPlayback(object):
key.append(p[0])
key.append(p[1])
- if self.options.rheaders:
+ if self.options.server_replay_use_headers:
headers = []
- for i in self.options.rheaders:
+ for i in self.options.server_replay_use_headers:
v = r.headers.get(i)
headers.append((i, v))
key.append(headers)
@@ -78,7 +78,7 @@ class ServerPlayback(object):
"""
hsh = self._hash(request)
if hsh in self.flowmap:
- if self.options.nopop:
+ if self.options.server_replay_nopop:
return self.flowmap[hsh][0]
else:
ret = self.flowmap[hsh].pop(0)
@@ -97,18 +97,6 @@ class ServerPlayback(object):
raise exceptions.OptionsError(str(e))
self.load(flows)
- # FIXME: These options have to be renamed to something more sensible -
- # prefixed with serverplayback_ where appropriate, and playback_ where
- # they're shared with client playback.
- #
- # options.kill
- # options.rheaders,
- # options.nopop,
- # options.replay_ignore_params,
- # options.replay_ignore_content,
- # options.replay_ignore_payload_params,
- # options.replay_ignore_host
-
def tick(self):
if self.stop and not self.final_flow.live:
ctx.master.shutdown()
@@ -125,7 +113,7 @@ class ServerPlayback(object):
if not self.flowmap and not self.options.keepserving:
self.final_flow = f
self.stop = True
- elif self.options.kill:
+ elif self.options.replay_kill_extra:
ctx.log.warn(
"server_playback: killed non-replay request {}".format(
f.request.url
diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py
index d888b93f..fe55ad5a 100644
--- a/mitmproxy/cmdline.py
+++ b/mitmproxy/cmdline.py
@@ -218,10 +218,10 @@ def get_common_options(args):
anticache=args.anticache,
anticomp=args.anticomp,
client_replay=args.client_replay,
- kill=args.kill,
+ replay_kill_extra=args.replay_kill_extra,
no_server=args.no_server,
refresh_server_playback=not args.norefresh,
- rheaders=args.rheaders,
+ server_replay_use_headers=args.server_replay_use_headers,
rfile=args.rfile,
replacements=reps,
setheaders=setheaders,
@@ -233,11 +233,11 @@ def get_common_options(args):
showhost=args.showhost,
outfile=args.outfile,
verbosity=args.verbose,
- nopop=args.nopop,
- replay_ignore_content=args.replay_ignore_content,
- replay_ignore_params=args.replay_ignore_params,
- replay_ignore_payload_params=args.replay_ignore_payload_params,
- replay_ignore_host=args.replay_ignore_host,
+ server_replay_nopop=args.server_replay_nopop,
+ server_replay_ignore_content=args.server_replay_ignore_content,
+ server_replay_ignore_params=args.server_replay_ignore_params,
+ server_replay_ignore_payload_params=args.server_replay_ignore_payload_params,
+ server_replay_ignore_host=args.server_replay_ignore_host,
auth_nonanonymous = args.auth_nonanonymous,
auth_singleuser = args.auth_singleuser,
@@ -594,13 +594,13 @@ def server_replay(parser):
help="Replay server responses from a saved file."
)
group.add_argument(
- "-k", "--kill",
- action="store_true", dest="kill", default=False,
+ "-k", "--replay-kill-extra",
+ action="store_true", dest="replay_kill_extra", default=False,
help="Kill extra requests during replay."
)
group.add_argument(
- "--rheader",
- action="append", dest="rheaders", type=str,
+ "--server-replay-use-header",
+ action="append", dest="server_replay_use_headers", type=str,
help="Request headers to be considered during replay. "
"Can be passed multiple times."
)
@@ -614,21 +614,21 @@ def server_replay(parser):
)
group.add_argument(
"--no-pop",
- action="store_true", dest="nopop", default=False,
+ action="store_true", dest="server_replay_nopop", default=False,
help="Disable response pop from response flow. "
"This makes it possible to replay same response multiple times."
)
payload = group.add_mutually_exclusive_group()
payload.add_argument(
"--replay-ignore-content",
- action="store_true", dest="replay_ignore_content", default=False,
+ action="store_true", dest="server_replay_ignore_content", default=False,
help="""
Ignore request's content while searching for a saved flow to replay
"""
)
payload.add_argument(
"--replay-ignore-payload-param",
- action="append", dest="replay_ignore_payload_params", type=str,
+ action="append", dest="server_replay_ignore_payload_params", type=str,
help="""
Request's payload parameters (application/x-www-form-urlencoded or multipart/form-data) to
be ignored while searching for a saved flow to replay.
@@ -638,7 +638,7 @@ def server_replay(parser):
group.add_argument(
"--replay-ignore-param",
- action="append", dest="replay_ignore_params", type=str,
+ action="append", dest="server_replay_ignore_params", type=str,
help="""
Request's parameters to be ignored while searching for a saved flow
to replay. Can be passed multiple times.
@@ -647,7 +647,7 @@ def server_replay(parser):
group.add_argument(
"--replay-ignore-host",
action="store_true",
- dest="replay_ignore_host",
+ dest="server_replay_ignore_host",
default=False,
help="Ignore request's destination host while searching for a saved flow to replay")
diff --git a/mitmproxy/console/flowlist.py b/mitmproxy/console/flowlist.py
index 11e8fc99..c052de7b 100644
--- a/mitmproxy/console/flowlist.py
+++ b/mitmproxy/console/flowlist.py
@@ -18,14 +18,15 @@ def _mkhelp():
("d", "delete flow"),
("D", "duplicate flow"),
("e", "toggle eventlog"),
+ ("E", "export flow to file"),
("f", "filter view"),
("F", "toggle follow flow list"),
("L", "load saved flows"),
("m", "toggle flow mark"),
("M", "toggle marked flow view"),
("n", "create a new request"),
- ("E", "export flow to file"),
("r", "replay request"),
+ ("S", "server replay request/s"),
("U", "unmark all marked flows"),
("V", "revert changes to request"),
("w", "save flows "),
@@ -140,36 +141,13 @@ class ConnectionItem(urwid.WidgetWrap):
args = (self.flow,)
)
- def stop_server_playback_prompt(self, a):
- if a != "n":
- self.master.stop_server_playback()
-
def server_replay_prompt(self, k):
+ a = self.master.addons.get("serverplayback")
if k == "a":
- self.master.start_server_playback(
- [i.copy() for i in self.master.state.view],
- self.master.options.kill, self.master.options.rheaders,
- False, self.master.options.nopop,
- self.master.options.replay_ignore_params,
- self.master.options.replay_ignore_content,
- self.master.options.replay_ignore_payload_params,
- self.master.options.replay_ignore_host
- )
+ a.load([i.copy() for i in self.master.state.view])
elif k == "t":
- self.master.start_server_playback(
- [self.flow.copy()],
- self.master.options.kill, self.master.options.rheaders,
- False, self.master.options.nopop,
- self.master.options.replay_ignore_params,
- self.master.options.replay_ignore_content,
- self.master.options.replay_ignore_payload_params,
- self.master.options.replay_ignore_host
- )
- else:
- signals.status_prompt_path.send(
- prompt = "Server replay path",
- callback = self.master.server_playback_path
- )
+ a.load([self.flow.copy()])
+ signals.update_settings.send(self)
def mouse_event(self, size, event, button, col, row, focus):
if event == "mouse press" and button == 1:
@@ -202,29 +180,30 @@ class ConnectionItem(urwid.WidgetWrap):
self.state.enable_marked_filter()
signals.flowlist_change.send(self)
elif key == "r":
- r = self.master.replay_request(self.flow)
- if r:
- signals.status_message.send(message=r)
+ self.master.replay_request(self.flow)
signals.flowlist_change.send(self)
elif key == "S":
- if not self.master.server_playback:
+ def stop_server_playback(response):
+ if response == "y":
+ self.master.options.server_replay = []
+ a = self.master.addons.get("serverplayback")
+ if a.count():
signals.status_prompt_onekey.send(
- prompt = "Server Replay",
+ prompt = "Stop current server replay?",
keys = (
- ("all flows", "a"),
- ("this flow", "t"),
- ("file", "f"),
+ ("yes", "y"),
+ ("no", "n"),
),
- callback = self.server_replay_prompt,
+ callback = stop_server_playback,
)
else:
signals.status_prompt_onekey.send(
- prompt = "Stop current server replay?",
+ prompt = "Server Replay",
keys = (
- ("yes", "y"),
- ("no", "n"),
+ ("all flows", "a"),
+ ("this flow", "t"),
),
- callback = self.stop_server_playback_prompt,
+ callback = self.server_replay_prompt,
)
elif key == "U":
for f in self.state.flows:
diff --git a/mitmproxy/console/flowview.py b/mitmproxy/console/flowview.py
index 15be379b..add10527 100644
--- a/mitmproxy/console/flowview.py
+++ b/mitmproxy/console/flowview.py
@@ -544,9 +544,7 @@ class FlowView(tabs.Tabs):
elif key == "p":
self.view_prev_flow(self.flow)
elif key == "r":
- r = self.master.replay_request(self.flow)
- if r:
- signals.status_message.send(message=r)
+ self.master.replay_request(self.flow)
signals.flow_change.send(self, flow = self.flow)
elif key == "V":
if self.flow.modified():
diff --git a/mitmproxy/console/help.py b/mitmproxy/console/help.py
index 8024dc31..e3e2f54c 100644
--- a/mitmproxy/console/help.py
+++ b/mitmproxy/console/help.py
@@ -53,7 +53,7 @@ class HelpView(urwid.ListBox):
("o", "options"),
("q", "quit / return to previous page"),
("Q", "quit without confirm prompt"),
- ("R", "replay of HTTP requests/responses"),
+ ("R", "replay of requests/responses from file"),
]
text.extend(
common.format_keyvals(keys, key="key", val="text", indent=4)
diff --git a/mitmproxy/console/master.py b/mitmproxy/console/master.py
index 1cb3a32b..8ded1ea1 100644
--- a/mitmproxy/console/master.py
+++ b/mitmproxy/console/master.py
@@ -22,7 +22,6 @@ from mitmproxy import contentviews
from mitmproxy import controller
from mitmproxy import exceptions
from mitmproxy import flow
-from mitmproxy import script
from mitmproxy import utils
import mitmproxy.options
from mitmproxy.console import flowlist
@@ -67,11 +66,13 @@ class ConsoleState(flow.State):
def add_flow(self, f):
super(ConsoleState, self).add_flow(f)
+ signals.flowlist_change.send(self)
self.update_focus()
return f
def update_flow(self, f):
super(ConsoleState, self).update_flow(f)
+ signals.flowlist_change.send(self)
self.update_focus()
return f
@@ -245,9 +246,6 @@ class ConsoleMaster(flow.FlowMaster):
self.logbuffer = urwid.SimpleListWalker([])
self.follow = options.follow
- if options.client_replay:
- self.client_playback_path(options.client_replay)
-
self.view_stack = []
if options.app:
@@ -329,39 +327,13 @@ class ConsoleMaster(flow.FlowMaster):
self.loop.widget = window
self.loop.draw_screen()
- def _run_script_method(self, method, s, f):
- status, val = s.run(method, f)
- if val:
- if status:
- signals.add_log("Method %s return: %s" % (method, val), "debug")
- else:
- signals.add_log(
- "Method %s error: %s" %
- (method, val[1]), "error")
-
def run_script_once(self, command, f):
- if not command:
- return
- signals.add_log("Running script on flow: %s" % command, "debug")
-
+ sc = self.addons.get("scriptloader")
try:
- s = script.Script(command)
- s.load()
- except script.ScriptException as e:
- signals.status_message.send(
- message='Error loading "{}".'.format(command)
- )
- signals.add_log('Error loading "{}":\n{}'.format(command, e), "error")
- return
-
- if f.request:
- self._run_script_method("request", s, f)
- if f.response:
- self._run_script_method("response", s, f)
- if f.error:
- self._run_script_method("error", s, f)
- s.unload()
- signals.flow_change.send(self, flow = f)
+ with self.handlecontext():
+ sc.run_once(command, [f])
+ except mitmproxy.exceptions.AddonError as e:
+ signals.add_log("Script error: %s" % e, "warn")
def toggle_eventlog(self):
self.options.eventlog = not self.options.eventlog
@@ -381,13 +353,6 @@ class ConsoleMaster(flow.FlowMaster):
except exceptions.FlowReadException as e:
signals.status_message.send(message=str(e))
- def client_playback_path(self, path):
- if not isinstance(path, list):
- path = [path]
- flows = self._readflows(path)
- if flows:
- self.start_client_playback(flows, False)
-
def spawn_editor(self, data):
text = not isinstance(data, bytes)
fd, name = tempfile.mkstemp('', "mproxy", text=text)
diff --git a/mitmproxy/console/options.py b/mitmproxy/console/options.py
index f7fb2f90..97313bf4 100644
--- a/mitmproxy/console/options.py
+++ b/mitmproxy/console/options.py
@@ -114,8 +114,8 @@ class Options(urwid.WidgetWrap):
select.Option(
"Kill Extra",
"x",
- lambda: master.options.kill,
- master.options.toggler("kill")
+ lambda: master.options.replay_kill_extra,
+ master.options.toggler("replay_kill_extra")
),
select.Option(
"No Refresh",
@@ -165,7 +165,7 @@ class Options(urwid.WidgetWrap):
anticomp = False,
ignore_hosts = (),
tcp_hosts = (),
- kill = False,
+ replay_kill_extra = False,
no_upstream_cert = False,
refresh_server_playback = True,
replacements = [],
diff --git a/mitmproxy/console/statusbar.py b/mitmproxy/console/statusbar.py
index 6c4cc8b5..bbfb41ab 100644
--- a/mitmproxy/console/statusbar.py
+++ b/mitmproxy/console/statusbar.py
@@ -136,6 +136,9 @@ class StatusBar(urwid.WidgetWrap):
def get_status(self):
r = []
+ sreplay = self.master.addons.get("serverplayback")
+ creplay = self.master.addons.get("clientplayback")
+
if len(self.master.options.setheaders):
r.append("[")
r.append(("heading_key", "H"))
@@ -144,15 +147,14 @@ class StatusBar(urwid.WidgetWrap):
r.append("[")
r.append(("heading_key", "R"))
r.append("eplacing]")
- if self.master.client_playback:
+ if creplay.count():
r.append("[")
r.append(("heading_key", "cplayback"))
- r.append(":%s]" % self.master.client_playback.count())
- if self.master.options.server_replay:
+ r.append(":%s]" % creplay.count())
+ if sreplay.count():
r.append("[")
r.append(("heading_key", "splayback"))
- a = self.master.addons.get("serverplayback")
- r.append(":%s]" % a.count())
+ r.append(":%s]" % sreplay.count())
if self.master.options.ignore_hosts:
r.append("[")
r.append(("heading_key", "I"))
@@ -191,7 +193,7 @@ class StatusBar(urwid.WidgetWrap):
opts.append("showhost")
if not self.master.options.refresh_server_playback:
opts.append("norefresh")
- if self.master.options.kill:
+ if self.master.options.replay_kill_extra:
opts.append("killextra")
if self.master.options.no_upstream_cert:
opts.append("no-upstream-cert")
diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py
index 49215b3a..2dc90031 100644
--- a/mitmproxy/dump.py
+++ b/mitmproxy/dump.py
@@ -46,12 +46,6 @@ class DumpMaster(flow.FlowMaster):
self.addons.add(options, dumper.Dumper())
# This line is just for type hinting
self.options = self.options # type: Options
- self.replay_ignore_params = options.replay_ignore_params
- self.replay_ignore_content = options.replay_ignore_content
- self.replay_ignore_host = options.replay_ignore_host
- self.refresh_server_playback = options.refresh_server_playback
- self.replay_ignore_payload_params = options.replay_ignore_payload_params
-
self.set_stream_large_bodies(options.stream_large_bodies)
if self.server and self.options.http2 and not tcp.HAS_ALPN: # pragma: no cover
@@ -59,12 +53,6 @@ class DumpMaster(flow.FlowMaster):
"HTTP/2 is disabled. Use --no-http2 to silence this warning.",
file=sys.stderr)
- if options.client_replay:
- self.start_client_playback(
- self._readflow(options.client_replay),
- not options.keepserving
- )
-
if options.rfile:
try:
self.load_flows_file(options.rfile)
diff --git a/mitmproxy/exceptions.py b/mitmproxy/exceptions.py
index 4d1d90c3..c4797e21 100644
--- a/mitmproxy/exceptions.py
+++ b/mitmproxy/exceptions.py
@@ -7,9 +7,6 @@ See also: http://lucumr.pocoo.org/2014/10/16/on-error-handling/
"""
from __future__ import absolute_import, print_function, division
-import sys
-import traceback
-
class ProxyException(Exception):
@@ -78,27 +75,6 @@ class ReplayException(ProxyException):
pass
-class ScriptException(ProxyException):
-
- @classmethod
- def from_exception_context(cls, cut_tb=1):
- """
- Must be called while the current stack handles an exception.
-
- Args:
- cut_tb: remove N frames from the stack trace to hide internal calls.
- """
- exc_type, exc_value, exc_traceback = sys.exc_info()
-
- while cut_tb > 0:
- exc_traceback = exc_traceback.tb_next
- cut_tb -= 1
-
- tb = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
-
- return cls(tb)
-
-
class FlowReadException(ProxyException):
pass
@@ -117,3 +93,7 @@ class OptionsError(Exception):
class AddonError(Exception):
pass
+
+
+class ReplayError(Exception):
+ pass
diff --git a/mitmproxy/flow/__init__.py b/mitmproxy/flow/__init__.py
index 10e66f08..cb79482c 100644
--- a/mitmproxy/flow/__init__.py
+++ b/mitmproxy/flow/__init__.py
@@ -4,14 +4,12 @@ from mitmproxy.flow import export, modules
from mitmproxy.flow.io import FlowWriter, FilteredFlowWriter, FlowReader, read_flows_from_paths
from mitmproxy.flow.master import FlowMaster
from mitmproxy.flow.modules import (
- AppRegistry, StreamLargeBodies, ClientPlaybackState
+ AppRegistry, StreamLargeBodies
)
from mitmproxy.flow.state import State, FlowView
__all__ = [
"export", "modules",
"FlowWriter", "FilteredFlowWriter", "FlowReader", "read_flows_from_paths",
- "FlowMaster",
- "AppRegistry", "StreamLargeBodies", "ClientPlaybackState",
- "State", "FlowView",
+ "FlowMaster", "AppRegistry", "StreamLargeBodies", "State", "FlowView",
]
diff --git a/mitmproxy/flow/master.py b/mitmproxy/flow/master.py
index b71c2c8d..94b46f3f 100644
--- a/mitmproxy/flow/master.py
+++ b/mitmproxy/flow/master.py
@@ -15,6 +15,30 @@ from mitmproxy.onboarding import app
from mitmproxy.protocol import http_replay
+def event_sequence(f):
+ if isinstance(f, models.HTTPFlow):
+ if f.request:
+ yield "request", f
+ if f.response:
+ yield "responseheaders", f
+ yield "response", f
+ if f.error:
+ yield "error", f
+ elif isinstance(f, models.TCPFlow):
+ messages = f.messages
+ f.messages = []
+ f.reply = controller.DummyReply()
+ yield "tcp_open", f
+ while messages:
+ f.messages.append(messages.pop(0))
+ yield "tcp_message", f
+ if f.error:
+ yield "tcp_error", f
+ yield "tcp_close", f
+ else:
+ raise NotImplementedError
+
+
class FlowMaster(controller.Master):
@property
@@ -29,16 +53,11 @@ class FlowMaster(controller.Master):
if server:
self.add_server(server)
self.state = state
- self.client_playback = None # type: Optional[modules.ClientPlaybackState]
self.stream_large_bodies = None # type: Optional[modules.StreamLargeBodies]
self.apps = modules.AppRegistry()
def start_app(self, host, port):
- self.apps.add(
- app.mapp,
- host,
- port
- )
+ self.apps.add(app.mapp, host, port)
def set_stream_large_bodies(self, max_size):
if max_size is not None:
@@ -46,31 +65,6 @@ class FlowMaster(controller.Master):
else:
self.stream_large_bodies = False
- def start_client_playback(self, flows, exit):
- """
- flows: List of flows.
- """
- self.client_playback = modules.ClientPlaybackState(flows, exit)
-
- def stop_client_playback(self):
- self.client_playback = None
-
- def tick(self, timeout):
- if self.client_playback:
- stop = (
- self.client_playback.done() and
- self.state.active_flow_count() == 0
- )
- exit = self.client_playback.exit
- if stop:
- self.stop_client_playback()
- if exit:
- self.shutdown()
- else:
- self.client_playback.tick(self)
-
- return super(FlowMaster, self).tick(timeout)
-
def duplicate_flow(self, f):
"""
Duplicate flow, and insert it into state without triggering any of
@@ -114,28 +108,9 @@ class FlowMaster(controller.Master):
f.request.host = self.server.config.upstream_server.address.host
f.request.port = self.server.config.upstream_server.address.port
f.request.scheme = self.server.config.upstream_server.scheme
-
- f.reply = controller.DummyReply()
- if f.request:
- self.request(f)
- if f.response:
- self.responseheaders(f)
- self.response(f)
- if f.error:
- self.error(f)
- elif isinstance(f, models.TCPFlow):
- messages = f.messages
- f.messages = []
- f.reply = controller.DummyReply()
- self.tcp_open(f)
- while messages:
- f.messages.append(messages.pop(0))
- self.tcp_message(f)
- if f.error:
- self.tcp_error(f)
- self.tcp_close(f)
- else:
- raise NotImplementedError()
+ f.reply = controller.DummyReply()
+ for e, o in event_sequence(f):
+ getattr(self, e)(o)
def load_flows(self, fr):
"""
@@ -163,35 +138,47 @@ class FlowMaster(controller.Master):
def replay_request(self, f, block=False):
"""
- Returns None if successful, or error message if not.
+ Returns an http_replay.RequestReplayThred object.
+ May raise exceptions.ReplayError.
"""
if f.live:
- return "Can't replay live request."
+ raise exceptions.ReplayError(
+ "Can't replay live flow."
+ )
if f.intercepted:
- return "Can't replay while intercepting..."
+ raise exceptions.ReplayError(
+ "Can't replay intercepted flow."
+ )
if f.request.raw_content is None:
- return "Can't replay request with missing content..."
- if f.request:
- f.backup()
- f.request.is_replay = True
-
- # TODO: We should be able to remove this.
- if "Content-Length" in f.request.headers:
- f.request.headers["Content-Length"] = str(len(f.request.raw_content))
-
- f.response = None
- f.error = None
- # FIXME: process through all addons?
- # self.process_new_request(f)
- rt = http_replay.RequestReplayThread(
- self.server.config,
- f,
- self.event_queue,
- self.should_exit
+ raise exceptions.ReplayError(
+ "Can't replay flow with missing content."
)
- rt.start() # pragma: no cover
- if block:
- rt.join()
+ if not f.request:
+ raise exceptions.ReplayError(
+ "Can't replay flow with missing request."
+ )
+
+ f.backup()
+ f.request.is_replay = True
+
+ # TODO: We should be able to remove this.
+ if "Content-Length" in f.request.headers:
+ f.request.headers["Content-Length"] = str(len(f.request.raw_content))
+
+ f.response = None
+ f.error = None
+ # FIXME: process through all addons?
+ # self.process_new_request(f)
+ rt = http_replay.RequestReplayThread(
+ self.server.config,
+ f,
+ self.event_queue,
+ self.should_exit
+ )
+ rt.start() # pragma: no cover
+ if block:
+ rt.join()
+ return rt
@controller.handler
def log(self, l):
@@ -220,9 +207,6 @@ class FlowMaster(controller.Master):
@controller.handler
def error(self, f):
self.state.update_flow(f)
- if self.client_playback:
- self.client_playback.clear(f)
- return f
@controller.handler
def request(self, f):
@@ -240,7 +224,6 @@ class FlowMaster(controller.Master):
return
if f not in self.state.flows: # don't add again on replay
self.state.add_flow(f)
- return f
@controller.handler
def responseheaders(self, f):
@@ -250,18 +233,14 @@ class FlowMaster(controller.Master):
except netlib.exceptions.HttpException:
f.reply.kill()
return
- return f
@controller.handler
def response(self, f):
self.state.update_flow(f)
- if self.client_playback:
- self.client_playback.clear(f)
- return f
@controller.handler
def websockets_handshake(self, f):
- return f
+ pass
def handle_intercept(self, f):
self.state.update_flow(f)
@@ -281,10 +260,7 @@ class FlowMaster(controller.Master):
@controller.handler
def tcp_error(self, flow):
- self.add_log("Error in TCP connection to {}: {}".format(
- repr(flow.server_conn.address),
- flow.error
- ), "info")
+ pass
@controller.handler
def tcp_close(self, flow):
diff --git a/mitmproxy/flow/modules.py b/mitmproxy/flow/modules.py
index e44416c3..7d8a282e 100644
--- a/mitmproxy/flow/modules.py
+++ b/mitmproxy/flow/modules.py
@@ -1,6 +1,5 @@
from __future__ import absolute_import, print_function, division
-from mitmproxy import controller
from netlib import wsgi
from netlib import version
from netlib.http import http1
@@ -45,37 +44,3 @@ class StreamLargeBodies(object):
if not r.raw_content and not (0 <= expected_size <= self.max_size):
# r.stream may already be a callable, which we want to preserve.
r.stream = r.stream or True
-
-
-class ClientPlaybackState:
- def __init__(self, flows, exit):
- self.flows, self.exit = flows, exit
- self.current = None
- self.testing = False # Disables actual replay for testing.
-
- def count(self):
- return len(self.flows)
-
- def done(self):
- if len(self.flows) == 0 and not self.current:
- return True
- return False
-
- def clear(self, flow):
- """
- A request has returned in some way - if this is the one we're
- servicing, go to the next flow.
- """
- if flow is self.current:
- self.current = None
-
- def tick(self, master):
- if self.flows and not self.current:
- self.current = self.flows.pop(0).copy()
- if not self.testing:
- master.replay_request(self.current)
- else:
- self.current.reply = controller.DummyReply()
- master.request(self.current)
- if self.current.response:
- master.response(self.current)
diff --git a/mitmproxy/options.py b/mitmproxy/options.py
index c4974839..480e0de8 100644
--- a/mitmproxy/options.py
+++ b/mitmproxy/options.py
@@ -30,16 +30,16 @@ class Options(optmanager.OptManager):
anticache=False, # type: bool
anticomp=False, # type: bool
client_replay=None, # type: Optional[str]
- kill=False, # type: bool
+ replay_kill_extra=False, # type: bool
keepserving=True, # type: bool
no_server=False, # type: bool
- nopop=False, # type: bool
+ server_replay_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]
+ server_replay_use_headers=(), # type: Sequence[str]
setheaders=(), # type: Sequence[Tuple[str, str, str]]
server_replay=None, # type: Optional[str]
stickycookie=None, # type: Optional[str]
@@ -47,10 +47,10 @@ class Options(optmanager.OptManager):
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
+ server_replay_ignore_content=False, # type: bool
+ server_replay_ignore_params=(), # type: Sequence[str]
+ server_replay_ignore_payload_params=(), # type: Sequence[str]
+ server_replay_ignore_host=False, # type: bool
# Proxy options
auth_nonanonymous=False, # type: bool
@@ -89,15 +89,15 @@ class Options(optmanager.OptManager):
self.anticomp = anticomp
self.client_replay = client_replay
self.keepserving = keepserving
- self.kill = kill
+ self.replay_kill_extra = replay_kill_extra
self.no_server = no_server
- self.nopop = nopop
+ self.server_replay_nopop = server_replay_nopop
self.refresh_server_playback = refresh_server_playback
self.rfile = rfile
self.scripts = scripts
self.showhost = showhost
self.replacements = replacements
- self.rheaders = rheaders
+ self.server_replay_use_headers = server_replay_use_headers
self.setheaders = setheaders
self.server_replay = server_replay
self.stickycookie = stickycookie
@@ -105,10 +105,10 @@ class Options(optmanager.OptManager):
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
+ self.server_replay_ignore_content = server_replay_ignore_content
+ self.server_replay_ignore_params = server_replay_ignore_params
+ self.server_replay_ignore_payload_params = server_replay_ignore_payload_params
+ self.server_replay_ignore_host = server_replay_ignore_host
# Proxy options
self.auth_nonanonymous = auth_nonanonymous
diff --git a/mitmproxy/protocol/http_replay.py b/mitmproxy/protocol/http_replay.py
index bfde06c5..877eaa22 100644
--- a/mitmproxy/protocol/http_replay.py
+++ b/mitmproxy/protocol/http_replay.py
@@ -33,6 +33,7 @@ class RequestReplayThread(basethread.BaseThread):
def run(self):
r = self.flow.request
first_line_format_backup = r.first_line_format
+ server = None
try:
self.flow.response = None
@@ -103,3 +104,5 @@ class RequestReplayThread(basethread.BaseThread):
self.channel.tell("log", Log(traceback.format_exc(), "error"))
finally:
r.first_line_format = first_line_format_backup
+ if server:
+ server.finish()
diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py
index c92ba4d3..5498c2d9 100644
--- a/mitmproxy/web/app.py
+++ b/mitmproxy/web/app.py
@@ -116,7 +116,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler):
def json(self):
if not self.request.headers.get("Content-Type").startswith("application/json"):
return None
- return json.loads(self.request.body)
+ return json.loads(self.request.body.decode())
@property
def state(self):
diff --git a/setup.py b/setup.py
index ba846ed5..9f75ca15 100644
--- a/setup.py
+++ b/setup.py
@@ -85,7 +85,7 @@ setup(
"tornado>=4.3, <4.5",
"urwid>=1.3.1, <1.4",
"watchdog>=0.8.3, <0.9",
- "brotlipy>=0.5.1, <0.6",
+ "brotlipy>=0.5.1, <0.7",
],
extras_require={
':sys_platform == "win32"': [
diff --git a/test/mitmproxy/builtins/test_clientplayback.py b/test/mitmproxy/builtins/test_clientplayback.py
new file mode 100644
index 00000000..15702340
--- /dev/null
+++ b/test/mitmproxy/builtins/test_clientplayback.py
@@ -0,0 +1,37 @@
+import mock
+
+from mitmproxy.builtins import clientplayback
+from mitmproxy import options
+
+from .. import tutils, mastertest
+
+
+class TestClientPlayback:
+ def test_playback(self):
+ cp = clientplayback.ClientPlayback()
+ cp.configure(options.Options(), [])
+ assert cp.count() == 0
+ f = tutils.tflow(resp=True)
+ cp.load([f])
+ assert cp.count() == 1
+ RP = "mitmproxy.protocol.http_replay.RequestReplayThread"
+ with mock.patch(RP) as rp:
+ assert not cp.current
+ with mastertest.mockctx():
+ cp.tick()
+ rp.assert_called()
+ assert cp.current
+
+ cp.keepserving = False
+ cp.flows = None
+ cp.current = None
+ with mock.patch("mitmproxy.controller.Master.shutdown") as sd:
+ with mastertest.mockctx():
+ cp.tick()
+ sd.assert_called()
+
+ def test_configure(self):
+ cp = clientplayback.ClientPlayback()
+ cp.configure(
+ options.Options(), []
+ )
diff --git a/test/mitmproxy/builtins/test_script.py b/test/mitmproxy/builtins/test_script.py
index 0bac6ca0..09e5bc92 100644
--- a/test/mitmproxy/builtins/test_script.py
+++ b/test/mitmproxy/builtins/test_script.py
@@ -137,6 +137,31 @@ class TestScript(mastertest.MasterTest):
class TestScriptLoader(mastertest.MasterTest):
+ def test_run_once(self):
+ s = state.State()
+ o = options.Options(scripts=[])
+ m = master.FlowMaster(o, None, s)
+ sl = script.ScriptLoader()
+ m.addons.add(o, sl)
+
+ f = tutils.tflow(resp=True)
+ with m.handlecontext():
+ sc = sl.run_once(
+ tutils.test_data.path(
+ "data/addonscripts/recorder.py"
+ ), [f]
+ )
+ evts = [i[1] for i in sc.ns.call_log]
+ assert evts == ['start', 'request', 'responseheaders', 'response', 'done']
+
+ with m.handlecontext():
+ tutils.raises(
+ "file not found",
+ sl.run_once,
+ "nonexistent",
+ [f]
+ )
+
def test_simple(self):
s = state.State()
o = options.Options(scripts=[])
diff --git a/test/mitmproxy/builtins/test_serverplayback.py b/test/mitmproxy/builtins/test_serverplayback.py
index 72070c7a..4db509da 100644
--- a/test/mitmproxy/builtins/test_serverplayback.py
+++ b/test/mitmproxy/builtins/test_serverplayback.py
@@ -22,7 +22,7 @@ class TestServerPlayback:
def test_ignore_host(self):
sp = serverplayback.ServerPlayback()
- sp.configure(options.Options(replay_ignore_host=True), [])
+ sp.configure(options.Options(server_replay_ignore_host=True), [])
r = tutils.tflow(resp=True)
r2 = tutils.tflow(resp=True)
@@ -35,7 +35,7 @@ class TestServerPlayback:
def test_ignore_content(self):
s = serverplayback.ServerPlayback()
- s.configure(options.Options(replay_ignore_content=False), [])
+ s.configure(options.Options(server_replay_ignore_content=False), [])
r = tutils.tflow(resp=True)
r2 = tutils.tflow(resp=True)
@@ -46,7 +46,7 @@ class TestServerPlayback:
r2.request.content = b"bar"
assert not s._hash(r) == s._hash(r2)
- s.configure(options.Options(replay_ignore_content=True), [])
+ s.configure(options.Options(server_replay_ignore_content=True), [])
r = tutils.tflow(resp=True)
r2 = tutils.tflow(resp=True)
r.request.content = b"foo"
@@ -63,8 +63,8 @@ class TestServerPlayback:
s = serverplayback.ServerPlayback()
s.configure(
options.Options(
- replay_ignore_content=True,
- replay_ignore_payload_params=[
+ server_replay_ignore_content=True,
+ server_replay_ignore_payload_params=[
"param1", "param2"
]
),
@@ -87,8 +87,8 @@ class TestServerPlayback:
s = serverplayback.ServerPlayback()
s.configure(
options.Options(
- replay_ignore_content=False,
- replay_ignore_payload_params=[
+ server_replay_ignore_content=False,
+ server_replay_ignore_payload_params=[
"param1", "param2"
]
),
@@ -127,7 +127,7 @@ class TestServerPlayback:
def test_headers(self):
s = serverplayback.ServerPlayback()
- s.configure(options.Options(rheaders=["foo"]), [])
+ s.configure(options.Options(server_replay_use_headers=["foo"]), [])
r = tutils.tflow(resp=True)
r.request.headers["foo"] = "bar"
@@ -167,9 +167,9 @@ class TestServerPlayback:
assert not s.next_flow(r)
- def test_load_with_nopop(self):
+ def test_load_with_server_replay_nopop(self):
s = serverplayback.ServerPlayback()
- s.configure(options.Options(nopop=True), [])
+ s.configure(options.Options(server_replay_nopop=True), [])
r = tutils.tflow(resp=True)
r.request.headers["key"] = "one"
@@ -187,7 +187,7 @@ class TestServerPlayback:
s = serverplayback.ServerPlayback()
s.configure(
options.Options(
- replay_ignore_params=["param1", "param2"]
+ server_replay_ignore_params=["param1", "param2"]
),
[]
)
@@ -208,7 +208,7 @@ class TestServerPlayback:
s = serverplayback.ServerPlayback()
s.configure(
options.Options(
- replay_ignore_payload_params=["param1", "param2"]
+ server_replay_ignore_payload_params=["param1", "param2"]
),
[]
)
@@ -270,7 +270,7 @@ class TestServerPlayback:
def test_server_playback_kill(self):
state = flow.State()
s = serverplayback.ServerPlayback()
- o = options.Options(refresh_server_playback = True, kill=True)
+ o = options.Options(refresh_server_playback = True, replay_kill_extra=True)
m = mastertest.RecordingMaster(o, None, state)
m.addons.add(o, s)
diff --git a/test/mitmproxy/console/test_master.py b/test/mitmproxy/console/test_master.py
index fcb87e1b..8388a6bd 100644
--- a/test/mitmproxy/console/test_master.py
+++ b/test/mitmproxy/console/test_master.py
@@ -107,7 +107,7 @@ def test_format_keyvals():
def test_options():
- assert console.master.Options(kill=True)
+ assert console.master.Options(replay_kill_extra=True)
class TestMaster(mastertest.MasterTest):
diff --git a/test/mitmproxy/mastertest.py b/test/mitmproxy/mastertest.py
index 68d88ea1..a14fe02a 100644
--- a/test/mitmproxy/mastertest.py
+++ b/test/mitmproxy/mastertest.py
@@ -1,8 +1,10 @@
+import contextlib
+
from . import tutils
import netlib.tutils
from mitmproxy.flow import master
-from mitmproxy import flow, proxy, models, controller
+from mitmproxy import flow, proxy, models, controller, options
class TestMaster:
@@ -47,3 +49,12 @@ class RecordingMaster(master.FlowMaster):
def add_log(self, e, level):
self.event_log.append((level, e))
+
+
+@contextlib.contextmanager
+def mockctx():
+ state = flow.State()
+ o = options.Options(refresh_server_playback = True, keepserving=False)
+ m = RecordingMaster(o, proxy.DummyServer(o), state)
+ with m.handlecontext():
+ yield
diff --git a/test/mitmproxy/protocol/test_http1.py b/test/mitmproxy/protocol/test_http1.py
index 7d04c56b..2fc4ac63 100644
--- a/test/mitmproxy/protocol/test_http1.py
+++ b/test/mitmproxy/protocol/test_http1.py
@@ -18,14 +18,15 @@ class TestInvalidRequests(tservers.HTTPProxyTest):
def test_double_connect(self):
p = self.pathoc()
- r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port))
+ with p.connect():
+ r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port))
assert r.status_code == 400
assert b"Invalid HTTP request form" in r.content
def test_relative_request(self):
p = self.pathoc_raw()
- p.connect()
- r = p.request("get:/p/200")
+ with p.connect():
+ r = p.request("get:/p/200")
assert r.status_code == 400
assert b"Invalid HTTP request form" in r.content
@@ -61,5 +62,8 @@ class TestHeadContentLength(tservers.HTTPProxyTest):
def test_head_content_length(self):
p = self.pathoc()
- resp = p.request("""head:'%s/p/200:h"Content-Length"="42"'""" % self.server.urlbase)
+ with p.connect():
+ resp = p.request(
+ """head:'%s/p/200:h"Content-Length"="42"'""" % self.server.urlbase
+ )
assert resp.headers["Content-Length"] == "42"
diff --git a/test/mitmproxy/test_dump.py b/test/mitmproxy/test_dump.py
index 40beeb0d..06f39e9d 100644
--- a/test/mitmproxy/test_dump.py
+++ b/test/mitmproxy/test_dump.py
@@ -45,18 +45,17 @@ class TestDumpMaster(mastertest.MasterTest):
m = dump.DumpMaster(None, o)
f = tutils.tflow(err=True)
m.error(f)
- assert m.error(f)
assert "error" in o.tfile.getvalue()
def test_replay(self):
- o = dump.Options(server_replay=["nonexistent"], kill=True)
+ o = dump.Options(server_replay=["nonexistent"], replay_kill_extra=True)
tutils.raises(exceptions.OptionsError, dump.DumpMaster, None, o)
with tutils.tmpdir() as t:
p = os.path.join(t, "rep")
self.flowfile(p)
- o = dump.Options(server_replay=[p], kill=True)
+ o = dump.Options(server_replay=[p], replay_kill_extra=True)
o.verbosity = 0
o.flow_detail = 0
m = dump.DumpMaster(None, o)
@@ -64,13 +63,13 @@ class TestDumpMaster(mastertest.MasterTest):
self.cycle(m, b"content")
self.cycle(m, b"content")
- o = dump.Options(server_replay=[p], kill=False)
+ o = dump.Options(server_replay=[p], replay_kill_extra=False)
o.verbosity = 0
o.flow_detail = 0
m = dump.DumpMaster(None, o)
self.cycle(m, b"nonexistent")
- o = dump.Options(client_replay=[p], kill=False)
+ o = dump.Options(client_replay=[p], replay_kill_extra=False)
o.verbosity = 0
o.flow_detail = 0
m = dump.DumpMaster(None, o)
diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py
index 91013efc..0fe45afb 100644
--- a/test/mitmproxy/test_flow.py
+++ b/test/mitmproxy/test_flow.py
@@ -37,39 +37,6 @@ def test_app_registry():
assert ar.get(r)
-class TestClientPlaybackState:
-
- def test_tick(self):
- first = tutils.tflow()
- s = flow.State()
- fm = flow.FlowMaster(None, None, s)
- fm.start_client_playback([first, tutils.tflow()], True)
- c = fm.client_playback
- c.testing = True
-
- assert not c.done()
- assert not s.flow_count()
- assert c.count() == 2
- c.tick(fm)
- assert s.flow_count()
- assert c.count() == 1
-
- c.tick(fm)
- assert c.count() == 1
-
- c.clear(c.current)
- c.tick(fm)
- assert c.count() == 0
- c.clear(c.current)
- assert c.done()
-
- fm.state.clear()
- fm.tick(timeout=0)
-
- fm.stop_client_playback()
- assert not fm.client_playback
-
-
class TestHTTPFlow(object):
def test_copy(self):
@@ -477,13 +444,13 @@ class TestFlowMaster:
fm = flow.FlowMaster(None, None, s)
f = tutils.tflow(resp=True)
f.request.content = None
- assert "missing" in fm.replay_request(f)
+ tutils.raises("missing", fm.replay_request, f)
f.intercepted = True
- assert "intercepting" in fm.replay_request(f)
+ tutils.raises("intercepted", fm.replay_request, f)
f.live = True
- assert "live" in fm.replay_request(f)
+ tutils.raises("live", fm.replay_request, f)
def test_duplicate_flow(self):
s = flow.State()
@@ -521,26 +488,6 @@ class TestFlowMaster:
fm.shutdown()
- def test_client_playback(self):
- s = flow.State()
-
- f = tutils.tflow(resp=True)
- pb = [tutils.tflow(resp=True), f]
- fm = flow.FlowMaster(
- options.Options(),
- DummyServer(ProxyConfig(options.Options())),
- s
- )
- assert not fm.start_client_playback(pb, False)
- fm.client_playback.testing = True
-
- assert not fm.state.flow_count()
- fm.tick(0)
- assert fm.state.flow_count()
-
- f.error = Error("error")
- fm.error(f)
-
class TestRequest:
diff --git a/test/mitmproxy/test_fuzzing.py b/test/mitmproxy/test_fuzzing.py
index 27ea36a6..905ba1cd 100644
--- a/test/mitmproxy/test_fuzzing.py
+++ b/test/mitmproxy/test_fuzzing.py
@@ -11,17 +11,20 @@ class TestFuzzy(tservers.HTTPProxyTest):
def test_idna_err(self):
req = r'get:"http://localhost:%s":i10,"\xc6"'
p = self.pathoc()
- assert p.request(req % self.server.port).status_code == 400
+ with p.connect():
+ assert p.request(req % self.server.port).status_code == 400
def test_nullbytes(self):
req = r'get:"http://localhost:%s":i19,"\x00"'
p = self.pathoc()
- assert p.request(req % self.server.port).status_code == 400
+ with p.connect():
+ assert p.request(req % self.server.port).status_code == 400
def test_invalid_ipv6_url(self):
req = 'get:"http://localhost:%s":i13,"["'
p = self.pathoc()
- resp = p.request(req % self.server.port)
+ with p.connect():
+ resp = p.request(req % self.server.port)
assert resp.status_code == 400
# def test_invalid_upstream(self):
diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py
index e0a8da47..c5a5bb71 100644
--- a/test/mitmproxy/test_server.py
+++ b/test/mitmproxy/test_server.py
@@ -60,7 +60,7 @@ class CommonMixin:
# Disconnect error
l.request.path = "/p/305:d0"
rt = self.master.replay_request(l, block=True)
- assert not rt
+ assert rt
if isinstance(self, tservers.HTTPUpstreamProxyTest):
assert l.response.status_code == 502
else:
@@ -72,7 +72,7 @@ class CommonMixin:
# In upstream mode with ssl, the replay will fail as we cannot establish
# SSL with the upstream proxy.
rt = self.master.replay_request(l, block=True)
- assert not rt
+ assert rt
if isinstance(self, tservers.HTTPUpstreamProxyTest):
assert l.response.status_code == 502
else:
@@ -91,11 +91,11 @@ class CommonMixin:
def test_invalid_http(self):
t = tcp.TCPClient(("127.0.0.1", self.proxy.port))
- t.connect()
- t.wfile.write(b"invalid\r\n\r\n")
- t.wfile.flush()
- line = t.rfile.readline()
- assert (b"Bad Request" in line) or (b"Bad Gateway" in line)
+ with t.connect():
+ t.wfile.write(b"invalid\r\n\r\n")
+ t.wfile.flush()
+ line = t.rfile.readline()
+ assert (b"Bad Request" in line) or (b"Bad Gateway" in line)
def test_sni(self):
if not self.ssl:
@@ -208,20 +208,22 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin, AppMixin):
def test_app_err(self):
p = self.pathoc()
- ret = p.request("get:'http://errapp/'")
+ with p.connect():
+ ret = p.request("get:'http://errapp/'")
assert ret.status_code == 500
assert b"ValueError" in ret.content
def test_invalid_connect(self):
t = tcp.TCPClient(("127.0.0.1", self.proxy.port))
- t.connect()
- t.wfile.write(b"CONNECT invalid\n\n")
- t.wfile.flush()
- assert b"Bad Request" in t.rfile.readline()
+ with t.connect():
+ t.wfile.write(b"CONNECT invalid\n\n")
+ t.wfile.flush()
+ assert b"Bad Request" in t.rfile.readline()
def test_upstream_ssl_error(self):
p = self.pathoc()
- ret = p.request("get:'https://localhost:%s/'" % self.server.port)
+ with p.connect():
+ ret = p.request("get:'https://localhost:%s/'" % self.server.port)
assert ret.status_code == 400
def test_connection_close(self):
@@ -232,25 +234,28 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin, AppMixin):
# Lets sanity check that the connection does indeed stay open by
# issuing two requests over the same connection
p = self.pathoc()
- assert p.request("get:'%s'" % response)
- assert p.request("get:'%s'" % response)
+ with p.connect():
+ assert p.request("get:'%s'" % response)
+ assert p.request("get:'%s'" % response)
# Now check that the connection is closed as the client specifies
p = self.pathoc()
- assert p.request("get:'%s':h'Connection'='close'" % response)
- # There's a race here, which means we can get any of a number of errors.
- # Rather than introduce yet another sleep into the test suite, we just
- # relax the Exception specification.
- with raises(Exception):
- p.request("get:'%s'" % response)
+ with p.connect():
+ assert p.request("get:'%s':h'Connection'='close'" % response)
+ # There's a race here, which means we can get any of a number of errors.
+ # Rather than introduce yet another sleep into the test suite, we just
+ # relax the Exception specification.
+ with raises(Exception):
+ p.request("get:'%s'" % response)
def test_reconnect(self):
req = "get:'%s/p/200:b@1:da'" % self.server.urlbase
p = self.pathoc()
- assert p.request(req)
- # Server has disconnected. Mitmproxy should detect this, and reconnect.
- assert p.request(req)
- assert p.request(req)
+ with p.connect():
+ assert p.request(req)
+ # Server has disconnected. Mitmproxy should detect this, and reconnect.
+ assert p.request(req)
+ assert p.request(req)
def test_get_connection_switching(self):
def switched(l):
@@ -260,18 +265,21 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin, AppMixin):
req = "get:'%s/p/200:b@1'"
p = self.pathoc()
- assert p.request(req % self.server.urlbase)
- assert p.request(req % self.server2.urlbase)
+ with p.connect():
+ assert p.request(req % self.server.urlbase)
+ assert p.request(req % self.server2.urlbase)
assert switched(self.proxy.tlog)
def test_blank_leading_line(self):
p = self.pathoc()
- req = "get:'%s/p/201':i0,'\r\n'"
- assert p.request(req % self.server.urlbase).status_code == 201
+ with p.connect():
+ req = "get:'%s/p/201':i0,'\r\n'"
+ assert p.request(req % self.server.urlbase).status_code == 201
def test_invalid_headers(self):
p = self.pathoc()
- resp = p.request("get:'http://foo':h':foo'='bar'")
+ with p.connect():
+ resp = p.request("get:'http://foo':h':foo'='bar'")
assert resp.status_code == 400
def test_stream(self):
@@ -301,15 +309,16 @@ class TestHTTPAuth(tservers.HTTPProxyTest):
self.master.options.auth_singleuser = "test:test"
assert self.pathod("202").status_code == 407
p = self.pathoc()
- ret = p.request("""
- get
- 'http://localhost:%s/p/202'
- h'%s'='%s'
- """ % (
- self.server.port,
- http.authentication.BasicProxyAuth.AUTH_HEADER,
- authentication.assemble_http_basic_auth("basic", "test", "test")
- ))
+ with p.connect():
+ ret = p.request("""
+ get
+ 'http://localhost:%s/p/202'
+ h'%s'='%s'
+ """ % (
+ self.server.port,
+ http.authentication.BasicProxyAuth.AUTH_HEADER,
+ authentication.assemble_http_basic_auth("basic", "test", "test")
+ ))
assert ret.status_code == 202
@@ -318,14 +327,15 @@ class TestHTTPReverseAuth(tservers.ReverseProxyTest):
self.master.options.auth_singleuser = "test:test"
assert self.pathod("202").status_code == 401
p = self.pathoc()
- ret = p.request("""
- get
- '/p/202'
- h'%s'='%s'
- """ % (
- http.authentication.BasicWebsiteAuth.AUTH_HEADER,
- authentication.assemble_http_basic_auth("basic", "test", "test")
- ))
+ with p.connect():
+ ret = p.request("""
+ get
+ '/p/202'
+ h'%s'='%s'
+ """ % (
+ http.authentication.BasicWebsiteAuth.AUTH_HEADER,
+ authentication.assemble_http_basic_auth("basic", "test", "test")
+ ))
assert ret.status_code == 202
@@ -354,7 +364,8 @@ class TestHTTPS(tservers.HTTPProxyTest, CommonMixin, TcpMixin):
def test_error_post_connect(self):
p = self.pathoc()
- assert p.request("get:/:i0,'invalid\r\n\r\n'").status_code == 400
+ with p.connect():
+ assert p.request("get:/:i0,'invalid\r\n\r\n'").status_code == 400
class TestHTTPSCertfile(tservers.HTTPProxyTest, CommonMixin):
@@ -389,7 +400,8 @@ class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxyTest):
def _request(self):
p = self.pathoc(sni="example.mitmproxy.org")
- return p.request("get:/p/242")
+ with p.connect():
+ return p.request("get:/p/242")
def test_verification_w_cadir(self):
self.config.options.update(
@@ -426,7 +438,8 @@ class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxyTest):
def _request(self):
p = self.pathoc(sni="example.mitmproxy.org")
- return p.request("get:/p/242")
+ with p.connect():
+ return p.request("get:/p/242")
@classmethod
def get_options(cls):
@@ -481,13 +494,15 @@ class TestSocks5(tservers.SocksModeTest):
def test_simple(self):
p = self.pathoc()
- p.socks_connect(("localhost", self.server.port))
- f = p.request("get:/p/200")
+ with p.connect():
+ p.socks_connect(("localhost", self.server.port))
+ f = p.request("get:/p/200")
assert f.status_code == 200
def test_with_authentication_only(self):
p = self.pathoc()
- f = p.request("get:/p/200")
+ with p.connect():
+ f = p.request("get:/p/200")
assert f.status_code == 502
assert b"SOCKS5 mode failure" in f.content
@@ -496,21 +511,21 @@ class TestSocks5(tservers.SocksModeTest):
mitmproxy doesn't support UDP or BIND SOCKS CMDs
"""
p = self.pathoc()
-
- socks.ClientGreeting(
- socks.VERSION.SOCKS5,
- [socks.METHOD.NO_AUTHENTICATION_REQUIRED]
- ).to_file(p.wfile)
- socks.Message(
- socks.VERSION.SOCKS5,
- socks.CMD.BIND,
- socks.ATYP.DOMAINNAME,
- ("example.com", 8080)
- ).to_file(p.wfile)
-
- p.wfile.flush()
- p.rfile.read(2) # read server greeting
- f = p.request("get:/p/200") # the request doesn't matter, error response from handshake will be read anyway.
+ with p.connect():
+ socks.ClientGreeting(
+ socks.VERSION.SOCKS5,
+ [socks.METHOD.NO_AUTHENTICATION_REQUIRED]
+ ).to_file(p.wfile)
+ socks.Message(
+ socks.VERSION.SOCKS5,
+ socks.CMD.BIND,
+ socks.ATYP.DOMAINNAME,
+ ("example.com", 8080)
+ ).to_file(p.wfile)
+
+ p.wfile.flush()
+ p.rfile.read(2) # read server greeting
+ f = p.request("get:/p/200") # the request doesn't matter, error response from handshake will be read anyway.
assert f.status_code == 502
assert b"SOCKS5 mode failure" in f.content
@@ -531,21 +546,23 @@ class TestHttps2Http(tservers.ReverseProxyTest):
p = pathoc.Pathoc(
("localhost", self.proxy.port), ssl=True, sni=sni, fp=None
)
- p.connect()
return p
def test_all(self):
p = self.pathoc(ssl=True)
- assert p.request("get:'/p/200'").status_code == 200
+ with p.connect():
+ assert p.request("get:'/p/200'").status_code == 200
def test_sni(self):
p = self.pathoc(ssl=True, sni="example.com")
- assert p.request("get:'/p/200'").status_code == 200
- assert all("Error in handle_sni" not in msg for msg in self.proxy.tlog)
+ with p.connect():
+ assert p.request("get:'/p/200'").status_code == 200
+ assert all("Error in handle_sni" not in msg for msg in self.proxy.tlog)
def test_http(self):
p = self.pathoc(ssl=False)
- assert p.request("get:'/p/200'").status_code == 200
+ with p.connect():
+ assert p.request("get:'/p/200'").status_code == 200
class TestTransparent(tservers.TransparentProxyTest, CommonMixin, TcpMixin):
@@ -703,29 +720,29 @@ class TestRedirectRequest(tservers.HTTPProxyTest):
self.master.redirect_port = self.server2.port
p = self.pathoc()
-
- self.server.clear_log()
- self.server2.clear_log()
- r1 = p.request("get:'/p/200'")
- assert r1.status_code == 200
- assert self.server.last_log()
- assert not self.server2.last_log()
-
- self.server.clear_log()
- self.server2.clear_log()
- r2 = p.request("get:'/p/201'")
- assert r2.status_code == 201
- assert not self.server.last_log()
- assert self.server2.last_log()
-
- self.server.clear_log()
- self.server2.clear_log()
- r3 = p.request("get:'/p/202'")
- assert r3.status_code == 202
- assert self.server.last_log()
- assert not self.server2.last_log()
-
- assert r1.content == r2.content == r3.content
+ with p.connect():
+ self.server.clear_log()
+ self.server2.clear_log()
+ r1 = p.request("get:'/p/200'")
+ assert r1.status_code == 200
+ assert self.server.last_log()
+ assert not self.server2.last_log()
+
+ self.server.clear_log()
+ self.server2.clear_log()
+ r2 = p.request("get:'/p/201'")
+ assert r2.status_code == 201
+ assert not self.server.last_log()
+ assert self.server2.last_log()
+
+ self.server.clear_log()
+ self.server2.clear_log()
+ r3 = p.request("get:'/p/202'")
+ assert r3.status_code == 202
+ assert self.server.last_log()
+ assert not self.server2.last_log()
+
+ assert r1.content == r2.content == r3.content
class MasterStreamRequest(tservers.TestMaster):
@@ -743,22 +760,22 @@ class TestStreamRequest(tservers.HTTPProxyTest):
def test_stream_simple(self):
p = self.pathoc()
-
- # a request with 100k of data but without content-length
- r1 = p.request("get:'%s/p/200:r:b@100k:d102400'" % self.server.urlbase)
- assert r1.status_code == 200
- assert len(r1.content) > 100000
+ with p.connect():
+ # a request with 100k of data but without content-length
+ r1 = p.request("get:'%s/p/200:r:b@100k:d102400'" % self.server.urlbase)
+ assert r1.status_code == 200
+ assert len(r1.content) > 100000
def test_stream_multiple(self):
p = self.pathoc()
+ with p.connect():
+ # simple request with streaming turned on
+ r1 = p.request("get:'%s/p/200'" % self.server.urlbase)
+ assert r1.status_code == 200
- # simple request with streaming turned on
- r1 = p.request("get:'%s/p/200'" % self.server.urlbase)
- assert r1.status_code == 200
-
- # now send back 100k of data, streamed but not chunked
- r1 = p.request("get:'%s/p/201:b@100k'" % self.server.urlbase)
- assert r1.status_code == 201
+ # now send back 100k of data, streamed but not chunked
+ r1 = p.request("get:'%s/p/201:b@100k'" % self.server.urlbase)
+ assert r1.status_code == 201
def test_stream_chunked(self):
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -887,7 +904,8 @@ class TestUpstreamProxy(tservers.HTTPUpstreamProxyTest, CommonMixin, AppMixin):
("~s", "baz", "ORLY")
]
p = self.pathoc()
- req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase)
+ with p.connect():
+ req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase)
assert req.content == b"ORLY"
assert req.status_code == 418
@@ -948,7 +966,8 @@ class TestUpstreamProxySSL(
def test_simple(self):
p = self.pathoc()
- req = p.request("get:'/p/418:b\"content\"'")
+ with p.connect():
+ req = p.request("get:'/p/418:b\"content\"'")
assert req.content == b"content"
assert req.status_code == 418
@@ -1006,48 +1025,49 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
])
p = self.pathoc()
- req = p.request("get:'/p/418:b\"content\"'")
- assert req.content == b"content"
- assert req.status_code == 418
-
- assert self.proxy.tmaster.state.flow_count() == 2 # CONNECT and request
- # CONNECT, failing request,
- assert self.chain[0].tmaster.state.flow_count() == 4
- # reCONNECT, request
- # failing request, request
- assert self.chain[1].tmaster.state.flow_count() == 2
- # (doesn't store (repeated) CONNECTs from chain[0]
- # as it is a regular proxy)
-
- assert not self.chain[1].tmaster.state.flows[0].response # killed
- assert self.chain[1].tmaster.state.flows[1].response
-
- assert self.proxy.tmaster.state.flows[0].request.first_line_format == "authority"
- assert self.proxy.tmaster.state.flows[1].request.first_line_format == "relative"
-
- assert self.chain[0].tmaster.state.flows[
- 0].request.first_line_format == "authority"
- assert self.chain[0].tmaster.state.flows[
- 1].request.first_line_format == "relative"
- assert self.chain[0].tmaster.state.flows[
- 2].request.first_line_format == "authority"
- assert self.chain[0].tmaster.state.flows[
- 3].request.first_line_format == "relative"
-
- assert self.chain[1].tmaster.state.flows[
- 0].request.first_line_format == "relative"
- assert self.chain[1].tmaster.state.flows[
- 1].request.first_line_format == "relative"
-
- req = p.request("get:'/p/418:b\"content2\"'")
-
- assert req.status_code == 502
- assert self.proxy.tmaster.state.flow_count() == 3 # + new request
- # + new request, repeated CONNECT from chain[1]
- assert self.chain[0].tmaster.state.flow_count() == 6
- # (both terminated)
- # nothing happened here
- assert self.chain[1].tmaster.state.flow_count() == 2
+ with p.connect():
+ req = p.request("get:'/p/418:b\"content\"'")
+ assert req.content == b"content"
+ assert req.status_code == 418
+
+ assert self.proxy.tmaster.state.flow_count() == 2 # CONNECT and request
+ # CONNECT, failing request,
+ assert self.chain[0].tmaster.state.flow_count() == 4
+ # reCONNECT, request
+ # failing request, request
+ assert self.chain[1].tmaster.state.flow_count() == 2
+ # (doesn't store (repeated) CONNECTs from chain[0]
+ # as it is a regular proxy)
+
+ assert not self.chain[1].tmaster.state.flows[0].response # killed
+ assert self.chain[1].tmaster.state.flows[1].response
+
+ assert self.proxy.tmaster.state.flows[0].request.first_line_format == "authority"
+ assert self.proxy.tmaster.state.flows[1].request.first_line_format == "relative"
+
+ assert self.chain[0].tmaster.state.flows[
+ 0].request.first_line_format == "authority"
+ assert self.chain[0].tmaster.state.flows[
+ 1].request.first_line_format == "relative"
+ assert self.chain[0].tmaster.state.flows[
+ 2].request.first_line_format == "authority"
+ assert self.chain[0].tmaster.state.flows[
+ 3].request.first_line_format == "relative"
+
+ assert self.chain[1].tmaster.state.flows[
+ 0].request.first_line_format == "relative"
+ assert self.chain[1].tmaster.state.flows[
+ 1].request.first_line_format == "relative"
+
+ req = p.request("get:'/p/418:b\"content2\"'")
+
+ assert req.status_code == 502
+ assert self.proxy.tmaster.state.flow_count() == 3 # + new request
+ # + new request, repeated CONNECT from chain[1]
+ assert self.chain[0].tmaster.state.flow_count() == 6
+ # (both terminated)
+ # nothing happened here
+ assert self.chain[1].tmaster.state.flow_count() == 2
class AddUpstreamCertsToClientChainMixin:
@@ -1066,12 +1086,13 @@ class AddUpstreamCertsToClientChainMixin:
d = f.read()
upstreamCert = SSLCert.from_pem(d)
p = self.pathoc()
- upstream_cert_found_in_client_chain = False
- for receivedCert in p.server_certs:
- if receivedCert.digest('sha256') == upstreamCert.digest('sha256'):
- upstream_cert_found_in_client_chain = True
- break
- assert(upstream_cert_found_in_client_chain == self.master.options.add_upstream_certs_to_client_chain)
+ with p.connect():
+ upstream_cert_found_in_client_chain = False
+ for receivedCert in p.server_certs:
+ if receivedCert.digest('sha256') == upstreamCert.digest('sha256'):
+ upstream_cert_found_in_client_chain = True
+ break
+ assert(upstream_cert_found_in_client_chain == self.master.options.add_upstream_certs_to_client_chain)
class TestHTTPSAddUpstreamCertsToClientChainTrue(
diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py
index 1597f59c..4291f743 100644
--- a/test/mitmproxy/tservers.py
+++ b/test/mitmproxy/tservers.py
@@ -3,6 +3,7 @@ import threading
import tempfile
import flask
import mock
+import sys
from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer
@@ -10,6 +11,7 @@ import pathod.test
import pathod.pathoc
from mitmproxy import flow, controller, options
from mitmproxy import builtins
+import netlib.exceptions
testapp = flask.Flask(__name__)
@@ -104,6 +106,14 @@ class ProxyTestBase(object):
cls.server.shutdown()
cls.server2.shutdown()
+ def teardown(self):
+ try:
+ self.server.wait_for_silence()
+ except netlib.exceptions.Timeout:
+ # FIXME: Track down the Windows sync issues
+ if sys.platform != "win32":
+ raise
+
def setup(self):
self.master.clear_log()
self.master.state.clear()
@@ -125,6 +135,15 @@ class ProxyTestBase(object):
)
+class LazyPathoc(pathod.pathoc.Pathoc):
+ def __init__(self, lazy_connect, *args, **kwargs):
+ self.lazy_connect = lazy_connect
+ pathod.pathoc.Pathoc.__init__(self, *args, **kwargs)
+
+ def connect(self):
+ return pathod.pathoc.Pathoc.connect(self, self.lazy_connect)
+
+
class HTTPProxyTest(ProxyTestBase):
def pathoc_raw(self):
@@ -134,14 +153,14 @@ class HTTPProxyTest(ProxyTestBase):
"""
Returns a connected Pathoc instance.
"""
- p = pathod.pathoc.Pathoc(
- ("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
- )
if self.ssl:
- p.connect(("127.0.0.1", self.server.port))
+ conn = ("127.0.0.1", self.server.port)
else:
- p.connect()
- return p
+ conn = None
+ return LazyPathoc(
+ conn,
+ ("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
+ )
def pathod(self, spec, sni=None):
"""
@@ -152,18 +171,20 @@ class HTTPProxyTest(ProxyTestBase):
q = "get:'/p/%s'" % spec
else:
q = "get:'%s/p/%s'" % (self.server.urlbase, spec)
- return p.request(q)
+ with p.connect():
+ return p.request(q)
def app(self, page):
if self.ssl:
p = pathod.pathoc.Pathoc(
("127.0.0.1", self.proxy.port), True, fp=None
)
- p.connect((options.APP_HOST, options.APP_PORT))
- return p.request("get:'%s'" % page)
+ with 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'" % (options.APP_HOST, page))
+ with p.connect():
+ return p.request("get:'http://%s%s'" % (options.APP_HOST, page))
class TResolver:
@@ -210,7 +231,8 @@ class TransparentProxyTest(ProxyTestBase):
else:
p = self.pathoc()
q = "get:'/p/%s'" % spec
- return p.request(q)
+ with p.connect():
+ return p.request(q)
def pathoc(self, sni=None):
"""
@@ -219,7 +241,6 @@ class TransparentProxyTest(ProxyTestBase):
p = pathod.pathoc.Pathoc(
("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
)
- p.connect()
return p
@@ -247,7 +268,6 @@ class ReverseProxyTest(ProxyTestBase):
p = pathod.pathoc.Pathoc(
("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
)
- p.connect()
return p
def pathod(self, spec, sni=None):
@@ -260,7 +280,8 @@ class ReverseProxyTest(ProxyTestBase):
else:
p = self.pathoc()
q = "get:'/p/%s'" % spec
- return p.request(q)
+ with p.connect():
+ return p.request(q)
class SocksModeTest(HTTPProxyTest):