aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml6
-rw-r--r--README.mkd8
-rw-r--r--doc-src/features/passthrough.html7
-rw-r--r--libmproxy/flow.py16
-rw-r--r--libmproxy/protocol/http.py38
-rw-r--r--libmproxy/script.py16
-rw-r--r--setup.py7
-rw-r--r--test/test_flow.py4
8 files changed, 67 insertions, 35 deletions
diff --git a/.travis.yml b/.travis.yml
index ae4a9641..3dcc7e2f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,10 +2,10 @@ language: python
sudo: false
python:
- "2.7"
-# - pypy # pypy 2.5.0 has a regression bug which breaks our test suite.
+ - pypy
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install:
- - "pip install --upgrade --src . -r requirements.txt"
+ - "pip install --src . -r requirements.txt"
# command to run tests, e.g. python setup.py test
script:
- "nosetests --with-cov --cov-report term-missing"
@@ -20,4 +20,4 @@ notifications:
cache:
directories:
- /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages
- - /home/travis/virtualenv/pypy-2.4.0/site-packages
+ - /home/travis/virtualenv/pypy-2.5.0/site-packages
diff --git a/README.mkd b/README.mkd
index bc18cb48..0fdf3715 100644
--- a/README.mkd
+++ b/README.mkd
@@ -1,4 +1,8 @@
-[![Build Status](https://travis-ci.org/mitmproxy/mitmproxy.png?branch=master)](https://travis-ci.org/mitmproxy/mitmproxy) [![Coverage Status](https://coveralls.io/repos/mitmproxy/mitmproxy/badge.png?branch=master)](https://coveralls.io/r/mitmproxy/mitmproxy)
+[![Build Status](https://travis-ci.org/mitmproxy/mitmproxy.svg?branch=master)](https://travis-ci.org/mitmproxy/mitmproxy) [![Coverage Status](https://coveralls.io/repos/mitmproxy/mitmproxy/badge.svg?branch=master)](https://coveralls.io/r/mitmproxy/mitmproxy)
+[![Latest Version](https://pypip.in/version/mitmproxy/badge.svg?style=flat)](https://pypi.python.org/pypi/mitmproxy/)
+[![Supported Python versions](https://pypip.in/py_versions/mitmproxy/badge.svg?style=flat)](https://pypi.python.org/pypi/mitmproxy)
+[![Supported Python implementations](https://pypip.in/implementation/mitmproxy/badge.svg?style=flat)](https://pypi.python.org/pypi/mitmproxy/)
+
__mitmproxy__ is an interactive, SSL-capable man-in-the-middle proxy for HTTP
with a console interface.
@@ -70,7 +74,7 @@ This installs the latest GitHub versions of mitmproxy, netlib and pathod into `m
### Testing
-The test suite requires the `dev` extra requirements listed in [setup.py](https://github.com/mitmproxy/mitmproxy/blob/master/setup.py) and [pathod](http://pathod.net), version matching mitmproxy. Install these with:
+The test suite requires the `dev` extra requirements listed in [setup.py](https://github.com/mitmproxy/mitmproxy/blob/master/setup.py). Install these with:
`pip install "mitmproxy[dev]"`
diff --git a/doc-src/features/passthrough.html b/doc-src/features/passthrough.html
index 7c830639..15e36434 100644
--- a/doc-src/features/passthrough.html
+++ b/doc-src/features/passthrough.html
@@ -59,13 +59,16 @@ $ <span style="color: white">mitmproxy --ignore ^example\.com:443$</span>
Here are some other examples for ignore patterns:
<pre>
-# Exempt traffic from the iOS App Store (usually just works):
+# Exempt traffic from the iOS App Store (the regex is lax, but usually just works):
--ignore apple.com:443
# "Correct" version without false-positives:
--ignore ^(.+\.)?apple\.com:443$
-# Ignore example.com on all ports, but no subdomains:
+# Ignore example.com, but not its subdomains:
--ignore ^example.com:
+
+# Ignore everything but example.com and mitmproxy.org:
+--ignore ^(?!example\.com)(?!mitmproxy\.org)
# Transparent mode:
--ignore 17\.178\.96\.59:443
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index f9e2b94d..49ec5a0b 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -309,10 +309,10 @@ class StickyCookieState:
# FIXME: We now know that Cookie.py screws up some cookies with
# valid RFC 822/1123 datetime specifications for expiry. Sigh.
c = Cookie.SimpleCookie(str(i))
- m = c.values()[0]
- k = self.ckey(m, f)
- if self.domain_match(f.request.host, k[0]):
- self.jar[self.ckey(m, f)] = m
+ for m in c.values():
+ k = self.ckey(m, f)
+ if self.domain_match(f.request.host, k[0]):
+ self.jar[k] = m
def handle_request(self, f):
l = []
@@ -824,12 +824,12 @@ class FlowMaster(controller.Master):
if self.stickycookie_state:
self.stickycookie_state.handle_response(f)
- def replay_request(self, f, block=False):
+ def replay_request(self, f, block=False, run_scripthooks=True):
"""
Returns None if successful, or error message if not.
"""
- if f.live:
- return "Can't replay request which is still live..."
+ if f.live and run_scripthooks:
+ return "Can't replay live request."
if f.intercepted:
return "Can't replay while intercepting..."
if f.request.content == http.CONTENT_MISSING:
@@ -845,7 +845,7 @@ class FlowMaster(controller.Master):
rt = http.RequestReplayThread(
self.server.config,
f,
- self.masterq,
+ self.masterq if run_scripthooks else False,
self.should_exit
)
rt.start() # pragma: no cover
diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py
index bebb4f7b..2f858a7c 100644
--- a/libmproxy/protocol/http.py
+++ b/libmproxy/protocol/http.py
@@ -1416,20 +1416,31 @@ class RequestReplayThread(threading.Thread):
name = "RequestReplayThread"
def __init__(self, config, flow, masterq, should_exit):
- self.config, self.flow, self.channel = config, flow, controller.Channel(masterq, should_exit)
- threading.Thread.__init__(self)
+ """
+ masterqueue can be a queue or None, if no scripthooks should be processed.
+ """
+ self.config, self.flow = config, flow
+ if masterq:
+ self.channel = controller.Channel(masterq, should_exit)
+ else:
+ self.channel = None
+ super(RequestReplayThread, self).__init__()
def run(self):
r = self.flow.request
form_out_backup = r.form_out
try:
self.flow.response = None
- request_reply = self.channel.ask("request", self.flow)
- if request_reply is None or request_reply == KILL:
- raise KillSignal()
- elif isinstance(request_reply, HTTPResponse):
- self.flow.response = request_reply
- else:
+
+ # If we have a channel, run script hooks.
+ if self.channel:
+ request_reply = self.channel.ask("request", self.flow)
+ if request_reply is None or request_reply == KILL:
+ raise KillSignal()
+ elif isinstance(request_reply, HTTPResponse):
+ self.flow.response = request_reply
+
+ if not self.flow.response:
# In all modes, we directly connect to the server displayed
if self.config.mode == "upstream":
server_address = self.config.mode.get_upstream_server(self.flow.client_conn)[2:]
@@ -1453,13 +1464,16 @@ class RequestReplayThread(threading.Thread):
self.flow.server_conn = server
self.flow.response = HTTPResponse.from_stream(server.rfile, r.method,
body_size_limit=self.config.body_size_limit)
- response_reply = self.channel.ask("response", self.flow)
- if response_reply is None or response_reply == KILL:
- raise KillSignal()
+ if self.channel:
+ response_reply = self.channel.ask("response", self.flow)
+ if response_reply is None or response_reply == KILL:
+ raise KillSignal()
except (proxy.ProxyError, http.HttpError, tcp.NetLibError) as v:
self.flow.error = Error(repr(v))
- self.channel.ask("error", self.flow)
+ if self.channel:
+ self.channel.ask("error", self.flow)
except KillSignal:
+ # KillSignal should only be raised if there's a channel in the first place.
self.channel.tell("log", proxy.Log("Connection killed", "info"))
finally:
r.form_out = form_out_backup
diff --git a/libmproxy/script.py b/libmproxy/script.py
index b559615b..be226004 100644
--- a/libmproxy/script.py
+++ b/libmproxy/script.py
@@ -20,6 +20,12 @@ class ScriptContext:
"""
self._master.add_event(message, level)
+ def kill_flow(self, f):
+ """
+ Kills a flow immediately. No further data will be sent to the client or the server.
+ """
+ f.kill(self._master)
+
def duplicate_flow(self, f):
"""
Returns a duplicate of the specified flow. The flow is also
@@ -36,7 +42,7 @@ class ScriptContext:
Replay the request on the current flow. The response will be added
to the flow object.
"""
- self._master.replay_request(f)
+ return self._master.replay_request(f, block=True, run_scripthooks=False)
@property
def app_registry(self):
@@ -139,8 +145,12 @@ def _handle_concurrent_reply(fn, o, *args, **kwargs):
def run():
fn(*args, **kwargs)
- o.reply() # If the script did not call .reply(), we have to do it now.
- threading.Thread(target=run, name="ScriptThread").start()
+ reply_proxy() # If the script did not call .reply(), we have to do it now.
+ ScriptThread(target=run).start()
+
+
+class ScriptThread(threading.Thread):
+ name = "ScriptThread"
def concurrent(fn):
diff --git a/setup.py b/setup.py
index 9a19aab9..9e22a039 100644
--- a/setup.py
+++ b/setup.py
@@ -34,8 +34,7 @@ script_deps = {
for script in scripts:
deps.update(script_deps[script])
if os.name == "nt":
- deps.add("pydivert>=0.0.4") # Transparent proxying on Windows
-
+ deps.add("pydivert>=0.0.7") # Transparent proxying on Windows
setup(
name="mitmproxy",
@@ -56,6 +55,8 @@ setup(
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Security",
"Topic :: Internet",
"Topic :: Internet :: WWW/HTTP",
@@ -64,7 +65,7 @@ setup(
],
packages=find_packages(),
include_package_data=True,
- scripts = scripts,
+ scripts=scripts,
install_requires=list(deps),
extras_require={
'dev': [
diff --git a/test/test_flow.py b/test/test_flow.py
index 6230ad73..6d77f075 100644
--- a/test/test_flow.py
+++ b/test/test_flow.py
@@ -47,7 +47,7 @@ class TestStickyCookieState:
assert s.domain_match("google.com", ".google.com")
def test_handle_response(self):
- c = "SSID=mooo, FOO=bar; Domain=.google.com; Path=/; "\
+ c = "SSID=mooo; domain=.google.com, FOO=bar; Domain=.google.com; Path=/; "\
"Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; "
s, f = self._response(c, "host")
@@ -667,7 +667,7 @@ class TestFlowMaster:
assert "intercepting" in fm.replay_request(f)
f.live = True
- assert "live" in fm.replay_request(f)
+ assert "live" in fm.replay_request(f, run_scripthooks=True)
def test_script_reqerr(self):
s = flow.State()