aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml33
-rw-r--r--examples/nonblocking.py4
-rw-r--r--examples/redirect_requests.py2
-rw-r--r--examples/tls_passthrough.py2
-rw-r--r--mitmproxy/console/statusbar.py4
-rw-r--r--mitmproxy/contentviews.py11
-rw-r--r--mitmproxy/controller.py59
-rw-r--r--mitmproxy/flow/master.py56
-rw-r--r--mitmproxy/models/flow.py5
-rw-r--r--mitmproxy/script/concurrent.py65
-rw-r--r--pathod/language/http2.py16
-rw-r--r--setup.py2
-rw-r--r--test/mitmproxy/mastertest.py2
-rw-r--r--test/mitmproxy/script/test_concurrent.py26
-rw-r--r--test/mitmproxy/test_contentview.py7
-rw-r--r--test/mitmproxy/test_controller.py10
-rw-r--r--test/mitmproxy/test_flow.py8
-rw-r--r--test/mitmproxy/test_server.py11
-rw-r--r--test/pathod/test_language_http2.py66
-rw-r--r--tox.ini28
20 files changed, 208 insertions, 209 deletions
diff --git a/.travis.yml b/.travis.yml
index dad81c37..b0a4ea10 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,20 +14,21 @@ matrix:
fast_finish: true
include:
- python: 2.7
+ env: TOXENV=py27
- python: 2.7
- env: NO_ALPN=1
+ env: TOXENV=py27 NO_ALPN=1
+ - python: 3.5
+ env: TOXENV=py35
+ - python: 3.5
+ env: TOXENV=py35 NO_ALPN=1
- language: generic
+ env: TOXENV=py27
os: osx
osx_image: xcode7.1
git:
depth: 9999999
- - python: 3.5
- env: SCOPE="netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py test/pathod/test_language_http.py test/pathod/test_language_websocket.py"
- - python: 3.5
- env: SCOPE="netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py test/pathod/test_language_http.py test/pathod/test_language_websocket.py" NO_ALPN=1
- python: 2.7
- env: DOCS=1
- script: 'cd docs && SPHINXOPTS="-W" make -e html'
+ env: TOXENV=docs
allow_failures:
- python: pypy
@@ -39,23 +40,20 @@ install:
brew outdated openssl || brew upgrade openssl
brew install python
fi
- - pip install -U virtualenv codecov
- - ./dev.sh
- - source venv/bin/activate
before_script:
- - "openssl version -a"
- - "python -c \"from OpenSSL import SSL; print(SSL.SSLeay_version(SSL.SSLEAY_VERSION))\""
- - "flake8 --jobs 4 --count mitmproxy netlib pathod examples test"
+ - "pip install tox"
+ - "tox -e lint"
-script:
- - "py.test --timeout 60 --cov netlib --cov mitmproxy --cov pathod test/$SCOPE"
+script: tox
after_success:
- - codecov
- |
if [[ $TRAVIS_OS_NAME == "osx" && $TRAVIS_PULL_REQUEST == "false" && ($TRAVIS_BRANCH == "master" || -n $TRAVIS_TAG) ]]
then
+ pip install -U virtualenv
+ ./dev.sh
+ source venv/bin/activate
pip install -e ./release
python ./release/rtool.py bdist
python ./release/rtool.py upload-snapshot --bdist --wheel
@@ -71,7 +69,6 @@ notifications:
cache:
directories:
- - $HOME/build/mitmproxy/mitmproxy/venv
+ - $HOME/build/mitmproxy/mitmproxy/.tox
- $HOME/.cache/pip
- $HOME/.pyenv
- - $HOME/Library/Caches/pip
diff --git a/examples/nonblocking.py b/examples/nonblocking.py
index 41674b2a..4609f389 100644
--- a/examples/nonblocking.py
+++ b/examples/nonblocking.py
@@ -4,6 +4,6 @@ from mitmproxy.script import concurrent
@concurrent # Remove this and see what happens
def request(context, flow):
- print("handle request: %s%s" % (flow.request.host, flow.request.path))
+ context.log("handle request: %s%s" % (flow.request.host, flow.request.path))
time.sleep(5)
- print("start request: %s%s" % (flow.request.host, flow.request.path))
+ context.log("start request: %s%s" % (flow.request.host, flow.request.path))
diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py
index 3ff8f9e4..d7db3f1c 100644
--- a/examples/redirect_requests.py
+++ b/examples/redirect_requests.py
@@ -16,7 +16,7 @@ def request(context, flow):
"HTTP/1.1", 200, "OK",
Headers(Content_Type="text/html"),
"helloworld")
- flow.reply(resp)
+ flow.reply.send(resp)
# Method 2: Redirect the request to a different server
if flow.request.pretty_host.endswith("example.org"):
diff --git a/examples/tls_passthrough.py b/examples/tls_passthrough.py
index 23afe3ff..0c6d450d 100644
--- a/examples/tls_passthrough.py
+++ b/examples/tls_passthrough.py
@@ -134,5 +134,5 @@ def next_layer(context, next_layer):
# We don't intercept - reply with a pass-through layer and add a "skipped" entry.
context.log("TLS passthrough for %s" % repr(next_layer.server_conn.address), "info")
next_layer_replacement = RawTCPLayer(next_layer.ctx, logging=False)
- next_layer.reply(next_layer_replacement)
+ next_layer.reply.send(next_layer_replacement)
context.tls_strategy.record_skipped(server_address)
diff --git a/mitmproxy/console/statusbar.py b/mitmproxy/console/statusbar.py
index af8089b6..e576b565 100644
--- a/mitmproxy/console/statusbar.py
+++ b/mitmproxy/console/statusbar.py
@@ -4,7 +4,7 @@ import os.path
import urwid
-import netlib.utils
+import netlib.http.url
from mitmproxy.console import common
from mitmproxy.console import pathedit
from mitmproxy.console import signals
@@ -212,7 +212,7 @@ class StatusBar(urwid.WidgetWrap):
if self.master.server.config.mode in ["reverse", "upstream"]:
dst = self.master.server.config.upstream_server
- r.append("[dest:%s]" % netlib.utils.unparse(
+ r.append("[dest:%s]" % netlib.http.url.unparse(
dst.scheme,
dst.address.host,
dst.address.port
diff --git a/mitmproxy/contentviews.py b/mitmproxy/contentviews.py
index 28c57f06..006967d7 100644
--- a/mitmproxy/contentviews.py
+++ b/mitmproxy/contentviews.py
@@ -62,11 +62,18 @@ KEY_MAX = 30
def pretty_json(s):
+ # type: (bytes) -> bytes
try:
p = json.loads(s)
except ValueError:
return None
- return json.dumps(p, sort_keys=True, indent=4)
+ pretty = json.dumps(p, sort_keys=True, indent=4, ensure_ascii=False)
+ if isinstance(pretty, six.text_type):
+ # json.dumps _may_ decide to return unicode, if the JSON object is not ascii.
+ # From limited testing this is always valid utf8 (otherwise json.loads will fail earlier),
+ # so we can just re-encode it here.
+ return pretty.encode("utf8", "strict")
+ return pretty
def format_dict(d):
@@ -153,7 +160,7 @@ class ViewRaw(View):
content_types = []
def __call__(self, data, **metadata):
- return "Raw", format_text(data)
+ return "Raw", format_text(strutils.bytes_to_escaped_str(data))
class ViewHex(View):
diff --git a/mitmproxy/controller.py b/mitmproxy/controller.py
index 1498c3ad..084702a6 100644
--- a/mitmproxy/controller.py
+++ b/mitmproxy/controller.py
@@ -145,27 +145,6 @@ class Channel(object):
self.q.put((mtype, m))
-class DummyReply(object):
- """
- A reply object that does nothing. Useful when we need an object to seem
- like it has a channel, and during testing.
- """
- def __init__(self):
- self.acked = False
- self.taken = False
- self.handled = False
-
- def take(self):
- self.taken = True
-
- def __call__(self, msg=False):
- self.acked = True
-
-
-# Special value to distinguish the case where no reply was sent
-NO_REPLY = object()
-
-
def handler(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
@@ -192,7 +171,7 @@ def handler(f):
ret = f(*args, **kwargs)
if handling and not message.reply.acked and not message.reply.taken:
- message.reply()
+ message.reply.ack()
return ret
# Mark this function as a handler wrapper
wrapper.func_dict["__handler"] = True
@@ -215,19 +194,45 @@ class Reply(object):
# Has a handler taken responsibility for ack-ing?
self.handled = False
+ def ack(self):
+ self.send(self.obj)
+
+ def kill(self):
+ self.send(exceptions.Kill)
+
def take(self):
self.taken = True
- def __call__(self, msg=NO_REPLY):
+ def send(self, msg):
if self.acked:
raise exceptions.ControlException("Message already acked.")
self.acked = True
- if msg is NO_REPLY:
- self.q.put(self.obj)
- else:
- self.q.put(msg)
+ self.q.put(msg)
def __del__(self):
if not self.acked:
# This will be ignored by the interpreter, but emit a warning
raise exceptions.ControlException("Un-acked message")
+
+
+class DummyReply(object):
+ """
+ A reply object that does nothing. Useful when we need an object to seem
+ like it has a channel, and during testing.
+ """
+ def __init__(self):
+ self.acked = False
+ self.taken = False
+ self.handled = False
+
+ def kill(self):
+ self.send(None)
+
+ def ack(self):
+ self.send(None)
+
+ def take(self):
+ self.taken = True
+
+ def send(self, msg):
+ self.acked = True
diff --git a/mitmproxy/flow/master.py b/mitmproxy/flow/master.py
index ec0bf36d..289102a1 100644
--- a/mitmproxy/flow/master.py
+++ b/mitmproxy/flow/master.py
@@ -103,9 +103,10 @@ class FlowMaster(controller.Master):
except script.ScriptException as e:
self.add_event("Script error:\n{}".format(e), "error")
- def run_script_hook(self, name, *args, **kwargs):
+ def run_scripts(self, name, msg):
for script_obj in self.scripts:
- self._run_single_script_hook(script_obj, name, *args, **kwargs)
+ if not msg.reply.acked:
+ self._run_single_script_hook(script_obj, name, msg)
def get_ignore_filter(self):
return self.server.config.check_ignore.patterns
@@ -373,28 +374,28 @@ class FlowMaster(controller.Master):
@controller.handler
def clientconnect(self, root_layer):
- self.run_script_hook("clientconnect", root_layer)
+ self.run_scripts("clientconnect", root_layer)
@controller.handler
def clientdisconnect(self, root_layer):
- self.run_script_hook("clientdisconnect", root_layer)
+ self.run_scripts("clientdisconnect", root_layer)
@controller.handler
def serverconnect(self, server_conn):
- self.run_script_hook("serverconnect", server_conn)
+ self.run_scripts("serverconnect", server_conn)
@controller.handler
def serverdisconnect(self, server_conn):
- self.run_script_hook("serverdisconnect", server_conn)
+ self.run_scripts("serverdisconnect", server_conn)
@controller.handler
def next_layer(self, top_layer):
- self.run_script_hook("next_layer", top_layer)
+ self.run_scripts("next_layer", top_layer)
@controller.handler
def error(self, f):
self.state.update_flow(f)
- self.run_script_hook("error", f)
+ self.run_scripts("error", f)
if self.client_playback:
self.client_playback.clear(f)
return f
@@ -411,15 +412,19 @@ class FlowMaster(controller.Master):
)
if err:
self.add_event("Error in wsgi app. %s" % err, "error")
- f.reply(exceptions.Kill)
+ f.reply.kill()
return
if f not in self.state.flows: # don't add again on replay
self.state.add_flow(f)
self.active_flows.add(f)
- self.replacehooks.run(f)
- self.setheaders.run(f)
- self.process_new_request(f)
- self.run_script_hook("request", f)
+ if not f.reply.acked:
+ self.replacehooks.run(f)
+ if not f.reply.acked:
+ self.setheaders.run(f)
+ if not f.reply.acked:
+ self.process_new_request(f)
+ if not f.reply.acked:
+ self.run_scripts("request", f)
return f
@controller.handler
@@ -428,20 +433,23 @@ class FlowMaster(controller.Master):
if self.stream_large_bodies:
self.stream_large_bodies.run(f, False)
except netlib.exceptions.HttpException:
- f.reply(exceptions.Kill)
+ f.reply.kill()
return
- self.run_script_hook("responseheaders", f)
+ self.run_scripts("responseheaders", f)
return f
@controller.handler
def response(self, f):
self.active_flows.discard(f)
self.state.update_flow(f)
- self.replacehooks.run(f)
- self.setheaders.run(f)
- self.run_script_hook("response", f)
- if self.client_playback:
- self.client_playback.clear(f)
+ if not f.reply.acked:
+ self.replacehooks.run(f)
+ if not f.reply.acked:
+ self.setheaders.run(f)
+ self.run_scripts("response", f)
+ if not f.reply.acked:
+ if self.client_playback:
+ self.client_playback.clear(f)
self.process_new_response(f)
if self.stream:
self.stream.add(f)
@@ -487,11 +495,11 @@ class FlowMaster(controller.Master):
# TODO: This would break mitmproxy currently.
# self.state.add_flow(flow)
self.active_flows.add(flow)
- self.run_script_hook("tcp_open", flow)
+ self.run_scripts("tcp_open", flow)
@controller.handler
def tcp_message(self, flow):
- self.run_script_hook("tcp_message", flow)
+ self.run_scripts("tcp_message", flow)
message = flow.messages[-1]
direction = "->" if message.from_client else "<-"
self.add_event("{client} {direction} tcp {direction} {server}".format(
@@ -507,14 +515,14 @@ class FlowMaster(controller.Master):
repr(flow.server_conn.address),
flow.error
), "info")
- self.run_script_hook("tcp_error", flow)
+ self.run_scripts("tcp_error", flow)
@controller.handler
def tcp_close(self, flow):
self.active_flows.discard(flow)
if self.stream:
self.stream.add(flow)
- self.run_script_hook("tcp_close", flow)
+ self.run_scripts("tcp_close", flow)
def shutdown(self):
super(FlowMaster, self).shutdown()
diff --git a/mitmproxy/models/flow.py b/mitmproxy/models/flow.py
index e2dac221..de86e451 100644
--- a/mitmproxy/models/flow.py
+++ b/mitmproxy/models/flow.py
@@ -4,7 +4,6 @@ import time
import copy
import uuid
-from mitmproxy import exceptions
from mitmproxy import stateobject
from mitmproxy import version
from mitmproxy.models.connections import ClientConnection
@@ -155,7 +154,7 @@ class Flow(stateobject.StateObject):
"""
self.error = Error("Connection killed")
self.intercepted = False
- self.reply(exceptions.Kill)
+ self.reply.kill()
master.error(self)
def intercept(self, master):
@@ -175,5 +174,5 @@ class Flow(stateobject.StateObject):
if not self.intercepted:
return
self.intercepted = False
- self.reply()
+ self.reply.ack()
master.handle_accept_intercept(self)
diff --git a/mitmproxy/script/concurrent.py b/mitmproxy/script/concurrent.py
index 43d0d328..89c835f6 100644
--- a/mitmproxy/script/concurrent.py
+++ b/mitmproxy/script/concurrent.py
@@ -4,62 +4,25 @@ offload computations from mitmproxy's main master thread.
"""
from __future__ import absolute_import, print_function, division
+from mitmproxy import controller
import threading
-class ReplyProxy(object):
-
- def __init__(self, reply_func, script_thread):
- self.reply_func = reply_func
- self.script_thread = script_thread
- self.master_reply = None
-
- def __call__(self, *args):
- if self.master_reply is None:
- self.master_reply = args
- self.script_thread.start()
- return
- self.reply_func(*args)
-
- def done(self):
- self.reply_func(*self.master_reply)
-
- def __getattr__(self, k):
- return getattr(self.reply_func, k)
-
-
-def _handle_concurrent_reply(fn, o, *args, **kwargs):
- # Make first call to o.reply a no op and start the script thread.
- # We must not start the script thread before, as this may lead to a nasty race condition
- # where the script thread replies a different response before the normal reply, which then gets swallowed.
-
- def run():
- fn(*args, **kwargs)
- # If the script did not call .reply(), we have to do it now.
- reply_proxy.done()
-
- script_thread = ScriptThread(target=run)
-
- reply_proxy = ReplyProxy(o.reply, script_thread)
- o.reply = reply_proxy
-
-
class ScriptThread(threading.Thread):
name = "ScriptThread"
def concurrent(fn):
- if fn.__name__ in (
- "request",
- "response",
- "error",
- "clientconnect",
- "serverconnect",
- "clientdisconnect",
- "next_layer"):
- def _concurrent(ctx, obj):
- _handle_concurrent_reply(fn, obj, ctx, obj)
-
- return _concurrent
- raise NotImplementedError(
- "Concurrent decorator not supported for '%s' method." % fn.__name__)
+ if fn.__name__ not in controller.Events:
+ raise NotImplementedError(
+ "Concurrent decorator not supported for '%s' method." % fn.__name__
+ )
+
+ def _concurrent(ctx, obj):
+ def run():
+ fn(ctx, obj)
+ if not obj.reply.acked:
+ obj.reply.ack()
+ obj.reply.take()
+ ScriptThread(target=run).start()
+ return _concurrent
diff --git a/pathod/language/http2.py b/pathod/language/http2.py
index 85d9047f..ea4fcd27 100644
--- a/pathod/language/http2.py
+++ b/pathod/language/http2.py
@@ -60,7 +60,7 @@ class _HTTP2Message(message.Message):
headers = self.toks(_HeaderMixin)
if not self.raw:
- if not get_header("content-length", headers):
+ if not get_header(b"content-length", headers):
if not self.body:
length = 0
else:
@@ -125,7 +125,7 @@ class ShortcutUserAgent(_HeaderMixin, base.OptionsOrValue):
def values(self, settings):
value = self.value.val
if self.option_used:
- value = user_agents.get_by_shortcut(value.lower())[2]
+ value = user_agents.get_by_shortcut(value.lower().decode())[2].encode()
return (
self.key.get_generator(settings),
@@ -190,7 +190,7 @@ class Response(_HTTP2Message):
resp = http.Response(
(2, 0),
self.status_code.string(),
- '',
+ b'',
headers,
body,
)
@@ -262,7 +262,7 @@ class Request(_HTTP2Message):
else:
path = self.path.string()
if self.nested_response:
- path += self.nested_response.parsed.spec()
+ path += self.nested_response.parsed.spec().encode()
headers = Headers([header.values(settings) for header in self.headers])
@@ -271,11 +271,11 @@ class Request(_HTTP2Message):
body = body.string()
req = http.Request(
- '',
+ b'',
self.method.string(),
- '',
- '',
- '',
+ b'',
+ b'',
+ b'',
path,
(2, 0),
headers,
diff --git a/setup.py b/setup.py
index e8829d49..050043b3 100644
--- a/setup.py
+++ b/setup.py
@@ -97,8 +97,8 @@ setup(
"ipaddress>=1.0.15, <1.1",
],
'dev': [
+ "tox>=2.3, <3",
"mock>=2.0, <2.1",
- "flake8>=2.5.4, <3",
"pytest>=2.8.7, <2.10",
"pytest-cov>=2.2.1, <2.3",
"pytest-timeout>=1.0.0, <1.1",
diff --git a/test/mitmproxy/mastertest.py b/test/mitmproxy/mastertest.py
index 9bb8826d..4d04f337 100644
--- a/test/mitmproxy/mastertest.py
+++ b/test/mitmproxy/mastertest.py
@@ -16,7 +16,9 @@ class MasterTest:
master.request(f)
if not f.error:
f.response = models.HTTPResponse.wrap(netlib.tutils.tresp(content=content))
+ f.reply.acked = False
f = master.response(f)
+ f.client_conn.reply.acked = False
master.clientdisconnect(f.client_conn)
return f
diff --git a/test/mitmproxy/script/test_concurrent.py b/test/mitmproxy/script/test_concurrent.py
index c2f169ad..62541f3f 100644
--- a/test/mitmproxy/script/test_concurrent.py
+++ b/test/mitmproxy/script/test_concurrent.py
@@ -1,29 +1,25 @@
-from threading import Event
-
from mitmproxy.script import Script
from test.mitmproxy import tutils
+from mitmproxy import controller
+import time
-class Dummy:
- def __init__(self, reply):
- self.reply = reply
+class Thing:
+ def __init__(self):
+ self.reply = controller.DummyReply()
@tutils.skip_appveyor
def test_concurrent():
with Script(tutils.test_data.path("data/scripts/concurrent_decorator.py"), None) as s:
- def reply():
- reply.acked.set()
- reply.acked = Event()
-
- f1, f2 = Dummy(reply), Dummy(reply)
+ f1, f2 = Thing(), Thing()
s.run("request", f1)
- f1.reply()
s.run("request", f2)
- f2.reply()
- assert f1.reply.acked == reply.acked
- assert not reply.acked.is_set()
- assert reply.acked.wait(10)
+ start = time.time()
+ while time.time() - start < 5:
+ if f1.reply.acked and f2.reply.acked:
+ return
+ raise ValueError("Script never acked")
def test_concurrent_err():
diff --git a/test/mitmproxy/test_contentview.py b/test/mitmproxy/test_contentview.py
index 48d6c307..f5ba45a6 100644
--- a/test/mitmproxy/test_contentview.py
+++ b/test/mitmproxy/test_contentview.py
@@ -1,5 +1,3 @@
-import json
-
from mitmproxy.exceptions import ContentViewException
from netlib.http import Headers
from netlib.odict import ODict
@@ -279,6 +277,7 @@ def test_get_by_shortcut():
def test_pretty_json():
- s = json.dumps({"foo": 1})
- assert cv.pretty_json(s)
+ assert cv.pretty_json('{"foo": 1}')
assert not cv.pretty_json("moo")
+ assert cv.pretty_json(b'{"foo" : "\xe4\xb8\x96\xe7\x95\x8c"}') # utf8 with chinese characters
+ assert not cv.pretty_json(b'{"foo" : "\xFF"}')
diff --git a/test/mitmproxy/test_controller.py b/test/mitmproxy/test_controller.py
index 83ad428e..5a68e15b 100644
--- a/test/mitmproxy/test_controller.py
+++ b/test/mitmproxy/test_controller.py
@@ -66,7 +66,7 @@ class TestChannel(object):
def reply():
m, obj = q.get()
assert m == "test"
- obj.reply(42)
+ obj.reply.send(42)
Thread(target=reply).start()
@@ -86,7 +86,7 @@ class TestDummyReply(object):
def test_simple(self):
reply = controller.DummyReply()
assert not reply.acked
- reply()
+ reply.ack()
assert reply.acked
@@ -94,16 +94,16 @@ class TestReply(object):
def test_simple(self):
reply = controller.Reply(42)
assert not reply.acked
- reply("foo")
+ reply.send("foo")
assert reply.acked
assert reply.q.get() == "foo"
def test_default(self):
reply = controller.Reply(42)
- reply()
+ reply.ack()
assert reply.q.get() == 42
def test_reply_none(self):
reply = controller.Reply(42)
- reply(None)
+ reply.send(None)
assert reply.q.get() is None
diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py
index 1b1f03f9..af8256c4 100644
--- a/test/mitmproxy/test_flow.py
+++ b/test/mitmproxy/test_flow.py
@@ -807,17 +807,22 @@ class TestFlowMaster:
fm.load_script(tutils.test_data.path("data/scripts/all.py"))
f = tutils.tflow(resp=True)
+ f.client_conn.acked = False
fm.clientconnect(f.client_conn)
assert fm.scripts[0].ns["log"][-1] == "clientconnect"
+ f.server_conn.acked = False
fm.serverconnect(f.server_conn)
assert fm.scripts[0].ns["log"][-1] == "serverconnect"
+ f.reply.acked = False
fm.request(f)
assert fm.scripts[0].ns["log"][-1] == "request"
+ f.reply.acked = False
fm.response(f)
assert fm.scripts[0].ns["log"][-1] == "response"
# load second script
fm.load_script(tutils.test_data.path("data/scripts/all.py"))
assert len(fm.scripts) == 2
+ f.server_conn.reply.acked = False
fm.clientdisconnect(f.server_conn)
assert fm.scripts[0].ns["log"][-1] == "clientdisconnect"
assert fm.scripts[1].ns["log"][-1] == "clientdisconnect"
@@ -828,6 +833,7 @@ class TestFlowMaster:
fm.load_script(tutils.test_data.path("data/scripts/all.py"))
f.error = tutils.terr()
+ f.reply.acked = False
fm.error(f)
assert fm.scripts[0].ns["log"][-1] == "error"
@@ -977,10 +983,12 @@ class TestFlowMaster:
f = tutils.tflow(resp=True)
f.response.headers["set-cookie"] = "foo=bar"
fm.request(f)
+ f.reply.acked = False
fm.response(f)
assert fm.stickycookie_state.jar
assert "cookie" not in f.request.headers
f = f.copy()
+ f.reply.acked = False
fm.request(f)
assert f.request.headers["cookie"] == "foo=bar"
diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py
index b58c4f44..432340c0 100644
--- a/test/mitmproxy/test_server.py
+++ b/test/mitmproxy/test_server.py
@@ -14,7 +14,6 @@ from pathod import pathoc, pathod
from mitmproxy import controller
from mitmproxy.proxy.config import HostMatcher
-from mitmproxy.exceptions import Kill
from mitmproxy.models import Error, HTTPResponse, HTTPFlow
from . import tutils, tservers
@@ -744,7 +743,7 @@ class MasterFakeResponse(tservers.TestMaster):
@controller.handler
def request(self, f):
resp = HTTPResponse.wrap(netlib.tutils.tresp())
- f.reply(resp)
+ f.reply.send(resp)
class TestFakeResponse(tservers.HTTPProxyTest):
@@ -771,7 +770,7 @@ class MasterKillRequest(tservers.TestMaster):
@controller.handler
def request(self, f):
- f.reply(Kill)
+ f.reply.kill()
class TestKillRequest(tservers.HTTPProxyTest):
@@ -788,7 +787,7 @@ class MasterKillResponse(tservers.TestMaster):
@controller.handler
def response(self, f):
- f.reply(Kill)
+ f.reply.kill()
class TestKillResponse(tservers.HTTPProxyTest):
@@ -820,7 +819,7 @@ class MasterIncomplete(tservers.TestMaster):
def request(self, f):
resp = HTTPResponse.wrap(netlib.tutils.tresp())
resp.content = None
- f.reply(resp)
+ f.reply.send(resp)
class TestIncompleteResponse(tservers.HTTPProxyTest):
@@ -942,7 +941,7 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
if not (k[0] in exclude):
f.client_conn.finish()
f.error = Error("terminated")
- f.reply(Kill)
+ f.reply.kill()
return _func(f)
setattr(master, attr, handler)
diff --git a/test/pathod/test_language_http2.py b/test/pathod/test_language_http2.py
index de256626..a2bffe63 100644
--- a/test/pathod/test_language_http2.py
+++ b/test/pathod/test_language_http2.py
@@ -1,4 +1,4 @@
-from six.moves import cStringIO as StringIO
+from six import BytesIO
import netlib
from netlib import tcp
@@ -10,11 +10,11 @@ import tutils
def parse_request(s):
- return language.parse_pathoc(s, True).next()
+ return next(language.parse_pathoc(s, True))
def parse_response(s):
- return language.parse_pathod(s, True).next()
+ return next(language.parse_pathod(s, True))
def default_settings():
@@ -25,7 +25,7 @@ def default_settings():
def test_make_error_response():
- d = StringIO()
+ d = BytesIO()
s = http2.make_error_response("foo", "bar")
language.serve(s, d, default_settings())
@@ -46,15 +46,15 @@ class TestRequest:
def test_simple(self):
r = parse_request('GET:"/foo"')
- assert r.method.string() == "GET"
- assert r.path.string() == "/foo"
+ assert r.method.string() == b"GET"
+ assert r.path.string() == b"/foo"
r = parse_request('GET:/foo')
- assert r.path.string() == "/foo"
+ assert r.path.string() == b"/foo"
def test_multiple(self):
r = list(language.parse_pathoc("GET:/ PUT:/"))
- assert r[0].method.string() == "GET"
- assert r[1].method.string() == "PUT"
+ assert r[0].method.string() == b"GET"
+ assert r[1].method.string() == b"PUT"
assert len(r) == 2
l = """
@@ -71,8 +71,8 @@ class TestRequest:
"""
r = list(language.parse_pathoc(l, True))
assert len(r) == 2
- assert r[0].method.string() == "GET"
- assert r[1].method.string() == "PUT"
+ assert r[0].method.string() == b"GET"
+ assert r[1].method.string() == b"PUT"
l = """
get:"http://localhost:9999/p/200"
@@ -80,11 +80,11 @@ class TestRequest:
"""
r = list(language.parse_pathoc(l, True))
assert len(r) == 2
- assert r[0].method.string() == "GET"
- assert r[1].method.string() == "GET"
+ assert r[0].method.string() == b"GET"
+ assert r[1].method.string() == b"GET"
def test_render_simple(self):
- s = StringIO()
+ s = BytesIO()
r = parse_request("GET:'/foo'")
assert language.serve(
r,
@@ -101,32 +101,32 @@ class TestRequest:
r = parse_request('GET:/')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-length", "0")
+ assert r.headers[0].values(default_settings()) == (b"content-length", b"0")
r = parse_request('GET:/:b"foobar"')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-length", "6")
+ assert r.headers[0].values(default_settings()) == (b"content-length", b"6")
r = parse_request('GET:/:b"foobar":h"content-length"="42"')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-length", "42")
+ assert r.headers[0].values(default_settings()) == (b"content-length", b"42")
r = parse_request('GET:/:r:b"foobar":h"content-length"="42"')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-length", "42")
+ assert r.headers[0].values(default_settings()) == (b"content-length", b"42")
def test_content_type(self):
r = parse_request('GET:/:r:c"foobar"')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-type", "foobar")
+ assert r.headers[0].values(default_settings()) == (b"content-type", b"foobar")
def test_user_agent(self):
r = parse_request('GET:/:r:ua')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("user-agent", user_agents.get_by_shortcut('a')[2])
+ assert r.headers[0].values(default_settings()) == (b"user-agent", user_agents.get_by_shortcut('a')[2].encode())
def test_render_with_headers(self):
- s = StringIO()
+ s = BytesIO()
r = parse_request('GET:/foo:h"foo"="bar"')
assert language.serve(
r,
@@ -142,7 +142,7 @@ class TestRequest:
assert r.values(default_settings())
def test_render_with_body(self):
- s = StringIO()
+ s = BytesIO()
r = parse_request("GET:'/foo':bfoobar")
assert language.serve(
r,
@@ -177,29 +177,29 @@ class TestResponse:
r = parse_response('200')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-length", "0")
+ assert r.headers[0].values(default_settings()) == (b"content-length", b"0")
def test_content_type(self):
r = parse_response('200:r:c"foobar"')
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("content-type", "foobar")
+ assert r.headers[0].values(default_settings()) == (b"content-type", b"foobar")
def test_simple(self):
r = parse_response('200:r:h"foo"="bar"')
- assert r.status_code.string() == "200"
+ assert r.status_code.string() == b"200"
assert len(r.headers) == 1
- assert r.headers[0].values(default_settings()) == ("foo", "bar")
+ assert r.headers[0].values(default_settings()) == (b"foo", b"bar")
assert r.body is None
r = parse_response('200:r:h"foo"="bar":bfoobar:h"bla"="fasel"')
- assert r.status_code.string() == "200"
+ assert r.status_code.string() == b"200"
assert len(r.headers) == 2
- assert r.headers[0].values(default_settings()) == ("foo", "bar")
- assert r.headers[1].values(default_settings()) == ("bla", "fasel")
- assert r.body.string() == "foobar"
+ assert r.headers[0].values(default_settings()) == (b"foo", b"bar")
+ assert r.headers[1].values(default_settings()) == (b"bla", b"fasel")
+ assert r.body.string() == b"foobar"
def test_render_simple(self):
- s = StringIO()
+ s = BytesIO()
r = parse_response('200')
assert language.serve(
r,
@@ -208,7 +208,7 @@ class TestResponse:
)
def test_render_with_headers(self):
- s = StringIO()
+ s = BytesIO()
r = parse_response('200:h"foo"="bar"')
assert language.serve(
r,
@@ -217,7 +217,7 @@ class TestResponse:
)
def test_render_with_body(self):
- s = StringIO()
+ s = BytesIO()
r = parse_response('200:bfoobar')
assert language.serve(
r,
diff --git a/tox.ini b/tox.ini
index 9fb563a0..d1b02463 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,15 +1,31 @@
[tox]
-envlist = py27, py35, lint
+envlist = py27, py35, docs, lint
[testenv]
-deps = -rrequirements.txt
+deps =
+ -rrequirements.txt
+ codecov>=2.0.5
+passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT
[testenv:py27]
-commands = py.test -n 8 --timeout 60 ./test
+commands =
+ py.test --cov netlib --cov mitmproxy --cov pathod --color=yes --timeout 60 ./test
+ codecov -e TOXENV
[testenv:py35]
-commands = py.test -n 8 --timeout 60 test/netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py test/pathod/test_language_http.py test_language_websocket.py
+# remove bash & pipe & grep hack after cryptography ships with openssl 1.1.0
+whitelist_externals = bash
+commands =
+ bash -c 'set -o pipefail ; py.test --cov netlib --cov mitmproxy --cov pathod --color=yes --timeout 60 test/netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py test/pathod/test_language_http.py test/pathod/test_language_websocket.py test/pathod/test_language_http2.py 2>&1 | grep -v Cryptography_locking_cb'
+ codecov -e TOXENV
+
+[testenv:docs]
+basepython = python2.7
+whitelist_externals = make
+changedir = docs
+setenv = SPHINXOPTS="-W"
+commands = make -e html
[testenv:lint]
-deps = flake8
-commands = flake8 --count mitmproxy netlib pathod examples test
+deps = flake8>=2.5.4, <3
+commands = flake8 --jobs 8 --count mitmproxy netlib pathod examples test