aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy')
-rw-r--r--libmproxy/console/flowlist.py8
-rw-r--r--libmproxy/console/grideditor.py16
-rw-r--r--libmproxy/console/help.py10
-rw-r--r--libmproxy/console/searchable.py4
-rw-r--r--libmproxy/console/window.py2
-rw-r--r--libmproxy/filt.py21
-rw-r--r--libmproxy/flow.py27
-rw-r--r--libmproxy/protocol/http.py88
-rw-r--r--libmproxy/proxy/config.py18
-rw-r--r--libmproxy/proxy/server.py12
-rw-r--r--libmproxy/script.py86
-rw-r--r--libmproxy/version.py4
-rw-r--r--libmproxy/web/app.py3
13 files changed, 179 insertions, 120 deletions
diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py
index bb23df75..46cd0de1 100644
--- a/libmproxy/console/flowlist.py
+++ b/libmproxy/console/flowlist.py
@@ -50,9 +50,9 @@ class EventListBox(urwid.ListBox):
self.master.clear_events()
key = None
elif key == "G":
- self.set_focus(0)
- elif key == "g":
self.set_focus(len(self.master.eventlist) - 1)
+ elif key == "g":
+ self.set_focus(0)
return urwid.ListBox.keypress(self, size, key)
@@ -338,10 +338,10 @@ class FlowListBox(urwid.ListBox):
self.master.clear_flows()
elif key == "e":
self.master.toggle_eventlog()
- elif key == "G":
+ elif key == "g":
self.master.state.set_focus(0)
signals.flowlist_change.send(self)
- elif key == "g":
+ elif key == "G":
self.master.state.set_focus(self.master.state.flow_count())
signals.flowlist_change.send(self)
elif key == "l":
diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py
index b20e54e4..d32ce5b4 100644
--- a/libmproxy/console/grideditor.py
+++ b/libmproxy/console/grideditor.py
@@ -5,9 +5,11 @@ import re
import os
import urwid
+from netlib import odict
+from netlib.http import user_agents
+
from . import common, signals
from .. import utils, filt, script
-from netlib import http_uastrings, http_cookies, odict
FOOTER = [
@@ -416,9 +418,9 @@ class GridEditor(urwid.WidgetWrap):
res.append(i[0])
self.callback(self.data_out(res), *self.cb_args, **self.cb_kwargs)
signals.pop_view_state.send(self)
- elif key == "G":
- self.walker.set_focus(0)
elif key == "g":
+ self.walker.set_focus(0)
+ elif key == "G":
self.walker.set_focus(len(self.walker.lst) - 1)
elif key in ["h", "left"]:
self.walker.left()
@@ -516,7 +518,7 @@ class HeaderEditor(GridEditor):
return text
def set_user_agent(self, k):
- ua = http_uastrings.get_by_shortcut(k)
+ ua = user_agents.get_by_shortcut(k)
if ua:
self.walker.add_value(
[
@@ -529,7 +531,7 @@ class HeaderEditor(GridEditor):
if key == "U":
signals.status_prompt_onekey.send(
prompt = "Add User-Agent header:",
- keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS],
+ keys = [(i[0], i[1]) for i in user_agents.UASTRINGS],
callback = self.set_user_agent,
)
return True
@@ -592,7 +594,7 @@ class SetHeadersEditor(GridEditor):
return text
def set_user_agent(self, k):
- ua = http_uastrings.get_by_shortcut(k)
+ ua = user_agents.get_by_shortcut(k)
if ua:
self.walker.add_value(
[
@@ -606,7 +608,7 @@ class SetHeadersEditor(GridEditor):
if key == "U":
signals.status_prompt_onekey.send(
prompt = "Add User-Agent header:",
- keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS],
+ keys = [(i[0], i[1]) for i in user_agents.UASTRINGS],
callback = self.set_user_agent,
)
return True
diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py
index 4e81a566..ba87348d 100644
--- a/libmproxy/console/help.py
+++ b/libmproxy/console/help.py
@@ -28,7 +28,7 @@ class HelpView(urwid.ListBox):
keys = [
("j, k", "down, up"),
("h, l", "left, right (in some contexts)"),
- ("g, G", "go to end, beginning"),
+ ("g, G", "go to beginning, end"),
("space", "page down"),
("pg up/down", "page up/down"),
("arrows", "up, down, left, right"),
@@ -42,12 +42,12 @@ class HelpView(urwid.ListBox):
text.append(urwid.Text([("head", "\n\nGlobal keys:\n")]))
keys = [
- ("c", "client replay"),
+ ("c", "client replay of HTTP requests"),
("i", "set interception pattern"),
("o", "options"),
("q", "quit / return to previous page"),
("Q", "quit without confirm prompt"),
- ("S", "server replay"),
+ ("S", "server replay of HTTP responses"),
]
text.extend(
common.format_keyvals(keys, key="key", val="text", indent=4)
@@ -108,8 +108,8 @@ class HelpView(urwid.ListBox):
return None
elif key == "?":
key = None
- elif key == "G":
- self.set_focus(0)
elif key == "g":
+ self.set_focus(0)
+ elif key == "G":
self.set_focus(len(self.body.contents))
return urwid.ListBox.keypress(self, size, key)
diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py
index 627d595d..dea0ac7f 100644
--- a/libmproxy/console/searchable.py
+++ b/libmproxy/console/searchable.py
@@ -33,10 +33,10 @@ class Searchable(urwid.ListBox):
self.find_next(False)
elif key == "N":
self.find_next(True)
- elif key == "G":
+ elif key == "g":
self.set_focus(0)
self.walker._modified()
- elif key == "g":
+ elif key == "G":
self.set_focus(len(self.walker) - 1)
self.walker._modified()
else:
diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py
index 8754ed57..69d5e242 100644
--- a/libmproxy/console/window.py
+++ b/libmproxy/console/window.py
@@ -23,7 +23,7 @@ class Window(urwid.Frame):
if not k:
if args[1] == "mouse drag":
signals.status_message.send(
- message = "Hold down alt or ctrl to select text.",
+ message = "Hold down shift, alt or ctrl to select text.",
expire = 1
)
elif args[1] == "mouse press" and args[2] == 4:
diff --git a/libmproxy/filt.py b/libmproxy/filt.py
index 1983586b..bd17a807 100644
--- a/libmproxy/filt.py
+++ b/libmproxy/filt.py
@@ -241,6 +241,19 @@ class FUrl(_Rex):
def __call__(self, f):
return re.search(self.expr, f.request.url)
+class FSrc(_Rex):
+ code = "src"
+ help = "Match source address"
+
+ def __call__(self, f):
+ return f.client_conn and re.search(self.expr, repr(f.client_conn.address))
+
+class FDst(_Rex):
+ code = "dst"
+ help = "Match destination address"
+
+ def __call__(self, f):
+ return f.server_conn and re.search(self.expr, repr(f.server_conn.address))
class _Int(_Action):
def __init__(self, num):
@@ -313,6 +326,8 @@ filt_rex = [
FRequestContentType,
FResponseContentType,
FContentType,
+ FSrc,
+ FDst,
]
filt_int = [
FCode
@@ -324,7 +339,7 @@ def _make():
# ones.
parts = []
for klass in filt_unary:
- f = pp.Literal("~%s" % klass.code)
+ f = pp.Literal("~%s" % klass.code) + pp.WordEnd()
f.setParseAction(klass.make)
parts.append(f)
@@ -333,12 +348,12 @@ def _make():
pp.QuotedString("\"", escChar='\\') |\
pp.QuotedString("'", escChar='\\')
for klass in filt_rex:
- f = pp.Literal("~%s" % klass.code) + rex.copy()
+ f = pp.Literal("~%s" % klass.code) + pp.WordEnd() + rex.copy()
f.setParseAction(klass.make)
parts.append(f)
for klass in filt_int:
- f = pp.Literal("~%s" % klass.code) + pp.Word(pp.nums)
+ f = pp.Literal("~%s" % klass.code) + pp.WordEnd() + pp.Word(pp.nums)
f.setParseAction(klass.make)
parts.append(f)
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 59312ceb..4b725ae5 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -158,7 +158,7 @@ class StreamLargeBodies(object):
def run(self, flow, is_request):
r = flow.request if is_request else flow.response
code = flow.response.code if flow.response else None
- expected_size = netlib.http.expected_http_body_size(
+ expected_size = netlib.http.http1.HTTP1Protocol.expected_http_body_size(
r.headers, is_request, flow.request.method, code
)
if not (0 <= expected_size <= self.max_size):
@@ -663,9 +663,12 @@ class FlowMaster(controller.Master):
for s in self.scripts[:]:
self.unload_script(s)
- def unload_script(self, script):
- script.unload()
- self.scripts.remove(script)
+ def unload_script(self, script_obj):
+ try:
+ script_obj.unload()
+ except script.ScriptError as e:
+ self.add_event("Script error:\n" + str(e), "error")
+ self.scripts.remove(script_obj)
def load_script(self, command):
"""
@@ -678,16 +681,16 @@ class FlowMaster(controller.Master):
return v.args[0]
self.scripts.append(s)
- def run_single_script_hook(self, script, name, *args, **kwargs):
- if script and not self.pause_scripts:
- ret = script.run(name, *args, **kwargs)
- if not ret[0] and ret[1]:
- e = "Script error:\n" + ret[1][1]
- self.add_event(e, "error")
+ def _run_single_script_hook(self, script_obj, name, *args, **kwargs):
+ if script_obj and not self.pause_scripts:
+ try:
+ script_obj.run(name, *args, **kwargs)
+ except script.ScriptError as e:
+ self.add_event("Script error:\n" + str(e), "error")
def run_script_hook(self, name, *args, **kwargs):
- for script in self.scripts:
- self.run_single_script_hook(script, name, *args, **kwargs)
+ for script_obj in self.scripts:
+ self._run_single_script_hook(script_obj, name, *args, **kwargs)
def get_ignore_filter(self):
return self.server.config.check_ignore.patterns
diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py
index 2bb1f528..f2ac5acc 100644
--- a/libmproxy/protocol/http.py
+++ b/libmproxy/protocol/http.py
@@ -1,14 +1,16 @@
from __future__ import absolute_import
import Cookie
+import copy
+import threading
+import time
import urllib
import urlparse
-import time
-import copy
from email.utils import parsedate_tz, formatdate, mktime_tz
-import threading
-from netlib import http, tcp, http_status, http_cookies
-import netlib.utils
-from netlib import odict
+
+import netlib
+from netlib import http, tcp, odict, utils
+from netlib.http import cookies, http1
+
from .tcp import TCPHandler
from .primitives import KILL, ProtocolHandler, Flow, Error
from ..proxy.connection import ServerConnection
@@ -303,6 +305,10 @@ class HTTPRequest(HTTPMessage):
is_replay=bool
)
+ @property
+ def body(self):
+ return self.content
+
@classmethod
def from_state(cls, state):
f = cls(
@@ -354,11 +360,10 @@ class HTTPRequest(HTTPMessage):
if hasattr(rfile, "reset_timestamps"):
rfile.reset_timestamps()
- req = http.read_request(
- rfile,
+ protocol = http1.HTTP1Protocol(rfile=rfile, wfile=wfile)
+ req = protocol.read_request(
include_body = include_body,
body_size_limit = body_size_limit,
- wfile = wfile
)
if hasattr(rfile, "first_byte_timestamp"):
@@ -375,7 +380,7 @@ class HTTPRequest(HTTPMessage):
req.path,
req.httpversion,
req.headers,
- req.content,
+ req.body,
timestamp_start,
timestamp_end
)
@@ -642,7 +647,7 @@ class HTTPRequest(HTTPMessage):
"""
ret = odict.ODict()
for i in self.headers["cookie"]:
- ret.extend(http_cookies.parse_cookie_header(i))
+ ret.extend(cookies.parse_cookie_header(i))
return ret
def set_cookies(self, odict):
@@ -650,7 +655,7 @@ class HTTPRequest(HTTPMessage):
Takes an netlib.odict.ODict object. Over-writes any existing Cookie
headers.
"""
- v = http_cookies.format_cookie_header(odict)
+ v = cookies.format_cookie_header(odict)
self.headers["Cookie"] = [v]
def replace(self, pattern, repl, *args, **kwargs):
@@ -724,6 +729,12 @@ class HTTPResponse(HTTPMessage):
msg=str
)
+
+ @property
+ def body(self):
+ return self.content
+
+
@classmethod
def from_state(cls, state):
f = cls(None, None, None, None, None)
@@ -760,11 +771,12 @@ class HTTPResponse(HTTPMessage):
if hasattr(rfile, "reset_timestamps"):
rfile.reset_timestamps()
- httpversion, code, msg, headers, content = http.read_response(
- rfile,
+ protocol = http1.HTTP1Protocol(rfile=rfile)
+ resp = protocol.read_response(
request_method,
body_size_limit,
- include_body=include_body)
+ include_body=include_body
+ )
if hasattr(rfile, "first_byte_timestamp"):
# more accurate timestamp_start
@@ -776,11 +788,11 @@ class HTTPResponse(HTTPMessage):
timestamp_end = None
return HTTPResponse(
- httpversion,
- code,
- msg,
- headers,
- content,
+ resp.httpversion,
+ resp.status_code,
+ resp.msg,
+ resp.headers,
+ resp.body,
timestamp_start,
timestamp_end
)
@@ -894,7 +906,7 @@ class HTTPResponse(HTTPMessage):
"""
ret = []
for header in self.headers["set-cookie"]:
- v = http_cookies.parse_set_cookie_header(header)
+ v = http.cookies.parse_set_cookie_header(header)
if v:
name, value, attrs = v
ret.append([name, [value, attrs]])
@@ -910,7 +922,7 @@ class HTTPResponse(HTTPMessage):
values = []
for i in odict.lst:
values.append(
- http_cookies.format_set_cookie_header(
+ http.cookies.format_set_cookie_header(
i[0],
i[1][0],
i[1][1]
@@ -1044,7 +1056,8 @@ class HTTPHandler(ProtocolHandler):
self.c.server_conn.send(request_raw)
# Only get the headers at first...
flow.response = HTTPResponse.from_stream(
- self.c.server_conn.rfile, flow.request.method,
+ self.c.server_conn.rfile,
+ flow.request.method,
body_size_limit=self.c.config.body_size_limit,
include_body=False
)
@@ -1081,10 +1094,13 @@ class HTTPHandler(ProtocolHandler):
if flow.response.stream:
flow.response.content = CONTENT_MISSING
else:
- flow.response.content = http.read_http_body(
- self.c.server_conn.rfile, flow.response.headers,
+ protocol = http1.HTTP1Protocol(rfile=self.c.server_conn.rfile)
+ flow.response.content = protocol.read_http_body(
+ flow.response.headers,
self.c.config.body_size_limit,
- flow.request.method, flow.response.code, False
+ flow.request.method,
+ flow.response.code,
+ False
)
flow.response.timestamp_end = utils.timestamp()
@@ -1231,7 +1247,7 @@ class HTTPHandler(ProtocolHandler):
pass
def send_error(self, code, message, headers):
- response = http_status.RESPONSES.get(code, "Unknown")
+ response = http.status_codes.RESPONSES.get(code, "Unknown")
html_content = """
<html>
<head>
@@ -1285,6 +1301,7 @@ class HTTPHandler(ProtocolHandler):
if not request.host and flow.server_conn:
request.host, request.port = flow.server_conn.address.host, flow.server_conn.address.port
+
# Now we can process the request.
if request.form_in == "authority":
if self.c.client_conn.ssl_established:
@@ -1363,7 +1380,7 @@ class HTTPHandler(ProtocolHandler):
# We provide a mostly unified API to the user, which needs to be
# unfiddled here
# ( See also: https://github.com/mitmproxy/mitmproxy/issues/337 )
- address = netlib.tcp.Address((flow.request.host, flow.request.port))
+ address = tcp.Address((flow.request.host, flow.request.port))
ssl = (flow.request.scheme == "https")
@@ -1417,8 +1434,8 @@ class HTTPHandler(ProtocolHandler):
h = flow.response._assemble_head(preserve_transfer_encoding=True)
self.c.client_conn.send(h)
- chunks = http.read_http_body_chunked(
- self.c.server_conn.rfile,
+ protocol = http1.HTTP1Protocol(rfile=self.c.server_conn.rfile)
+ chunks = protocol.read_http_body_chunked(
flow.response.headers,
self.c.config.body_size_limit,
flow.request.method,
@@ -1440,15 +1457,18 @@ class HTTPHandler(ProtocolHandler):
semantics. Returns True, if so.
"""
close_connection = (
- http.connection_close(
+ http1.HTTP1Protocol.connection_close(
flow.request.httpversion,
- flow.request.headers) or http.connection_close(
+ flow.request.headers
+ ) or http1.HTTP1Protocol.connection_close(
flow.response.httpversion,
- flow.response.headers) or http.expected_http_body_size(
+ flow.response.headers
+ ) or http1.HTTP1Protocol.expected_http_body_size(
flow.response.headers,
False,
flow.request.method,
- flow.response.code) == -1)
+ flow.response.code) == -1
+ )
if close_connection:
if flow.request.form_in == "authority" and flow.response.code == 200:
# Workaround for
diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py
index c5306b4a..ec91a6e0 100644
--- a/libmproxy/proxy/config.py
+++ b/libmproxy/proxy/config.py
@@ -2,7 +2,11 @@ from __future__ import absolute_import
import os
import re
from OpenSSL import SSL
-from netlib import http_auth, certutils, tcp
+
+import netlib
+from netlib import http, certutils, tcp
+from netlib.http import authentication
+
from .. import utils, platform, version
from .primitives import RegularProxyMode, SpoofMode, SSLSpoofMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode
@@ -103,7 +107,7 @@ class ProxyConfig:
self.openssl_method_server = ssl_version_server
else:
self.openssl_method_server = tcp.SSL_VERSIONS[ssl_version_server]
-
+
if ssl_verify_upstream_cert:
self.openssl_verification_mode_server = SSL.VERIFY_PEER
else:
@@ -164,18 +168,18 @@ def process_proxy_options(parser, options):
return parser.error(
"Invalid single-user specification. Please use the format username:password")
username, password = options.auth_singleuser.split(':')
- password_manager = http_auth.PassManSingleUser(username, password)
+ password_manager = authentication.PassManSingleUser(username, password)
elif options.auth_nonanonymous:
- password_manager = http_auth.PassManNonAnon()
+ password_manager = authentication.PassManNonAnon()
elif options.auth_htpasswd:
try:
- password_manager = http_auth.PassManHtpasswd(
+ password_manager = authentication.PassManHtpasswd(
options.auth_htpasswd)
except ValueError as v:
return parser.error(v.message)
- authenticator = http_auth.BasicProxyAuth(password_manager, "mitmproxy")
+ authenticator = authentication.BasicProxyAuth(password_manager, "mitmproxy")
else:
- authenticator = http_auth.NullProxyAuth(None)
+ authenticator = authentication.NullProxyAuth(None)
certs = []
for i in options.certs:
diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py
index 2711bd0e..e77439fb 100644
--- a/libmproxy/proxy/server.py
+++ b/libmproxy/proxy/server.py
@@ -167,12 +167,12 @@ class ConnectionHandler:
self.channel.tell("serverdisconnect", self)
self.server_conn = None
- def set_server_address(self, address):
+ def set_server_address(self, addr):
"""
Sets a new server address with the given priority.
Does not re-establish either connection or SSL handshake.
"""
- address = tcp.Address.wrap(address)
+ address = tcp.Address.wrap(addr)
# Don't reconnect to the same destination.
if self.server_conn and self.server_conn.address == address:
@@ -309,15 +309,15 @@ class ConnectionHandler:
self.client_conn.finish()
def log(self, msg, level, subs=()):
- msg = [
+ full_msg = [
"%s:%s: %s" %
(self.client_conn.address.host,
self.client_conn.address.port,
msg)]
for i in subs:
- msg.append(" -> " + i)
- msg = "\n".join(msg)
- self.channel.tell("log", Log(msg, level))
+ full_msg.append(" -> " + i)
+ full_msg = "\n".join(full_msg)
+ self.channel.tell("log", Log(full_msg, level))
def find_cert(self):
host = self.server_conn.address.host
diff --git a/libmproxy/script.py b/libmproxy/script.py
index 46edb86b..e13f0e2b 100644
--- a/libmproxy/script.py
+++ b/libmproxy/script.py
@@ -3,7 +3,7 @@ import os
import traceback
import threading
import shlex
-from . import controller
+import sys
class ScriptError(Exception):
@@ -55,21 +55,17 @@ class ScriptContext:
class Script:
"""
- The instantiator should do something along this vein:
-
- s = Script(argv, master)
- s.load()
+ Script object representing an inline script.
"""
def __init__(self, command, master):
- self.command = command
- self.argv = self.parse_command(command)
+ self.args = self.parse_command(command)
self.ctx = ScriptContext(master)
self.ns = None
self.load()
@classmethod
- def parse_command(klass, command):
+ def parse_command(cls, command):
if not command or not command.strip():
raise ScriptError("Empty script command.")
if os.name == "nt": # Windows: escape all backslashes in the path.
@@ -89,54 +85,66 @@ class Script:
def load(self):
"""
- Loads a module.
+ Loads an inline script.
+
+ Returns:
+ The return value of self.run("start", ...)
- Raises ScriptError on failure, with argument equal to an error
- message that may be a formatted traceback.
+ Raises:
+ ScriptError on failure
"""
+ if self.ns is not None:
+ self.unload()
ns = {}
+ script_dir = os.path.dirname(os.path.abspath(self.args[0]))
+ sys.path.append(script_dir)
try:
- execfile(self.argv[0], ns, ns)
- except Exception as v:
- raise ScriptError(traceback.format_exc(v))
+ execfile(self.args[0], ns, ns)
+ except Exception as e:
+ # Python 3: use exception chaining, https://www.python.org/dev/peps/pep-3134/
+ raise ScriptError(traceback.format_exc(e))
+ sys.path.pop()
self.ns = ns
- r = self.run("start", self.argv)
- if not r[0] and r[1]:
- raise ScriptError(r[1][1])
+ return self.run("start", self.args)
def unload(self):
- return self.run("done")
+ ret = self.run("done")
+ self.ns = None
+ return ret
def run(self, name, *args, **kwargs):
"""
- Runs a plugin method.
+ Runs an inline script hook.
Returns:
+ The return value of the method.
+ None, if the script does not provide the method.
- (True, retval) on success.
- (False, None) on nonexistent method.
- (False, (exc, traceback string)) if there was an exception.
+ Raises:
+ ScriptError if there was an exception.
"""
f = self.ns.get(name)
if f:
try:
- return (True, f(self.ctx, *args, **kwargs))
- except Exception as v:
- return (False, (v, traceback.format_exc(v)))
+ return f(self.ctx, *args, **kwargs)
+ except Exception as e:
+ raise ScriptError(traceback.format_exc(e))
else:
- return (False, None)
+ return None
class ReplyProxy(object):
- def __init__(self, original_reply):
- self._ignore_calls = 1
- self.lock = threading.Lock()
+ def __init__(self, original_reply, script_thread):
self.original_reply = original_reply
+ self.script_thread = script_thread
+ self._ignore_call = True
+ self.lock = threading.Lock()
def __call__(self, *args, **kwargs):
with self.lock:
- if self._ignore_calls > 0:
- self._ignore_calls -= 1
+ if self._ignore_call:
+ self.script_thread.start()
+ self._ignore_call = False
return
self.original_reply(*args, **kwargs)
@@ -145,16 +153,19 @@ class ReplyProxy(object):
def _handle_concurrent_reply(fn, o, *args, **kwargs):
- # Make first call to o.reply a no op
-
- reply_proxy = ReplyProxy(o.reply)
- o.reply = reply_proxy
+ # 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()
- ScriptThread(target=run).start()
+
+ script_thread = ScriptThread(target=run)
+
+ reply_proxy = ReplyProxy(o.reply, script_thread)
+ o.reply = reply_proxy
class ScriptThread(threading.Thread):
@@ -171,6 +182,7 @@ def concurrent(fn):
"clientdisconnect"):
def _concurrent(ctx, obj):
_handle_concurrent_reply(fn, obj, ctx, obj)
+
return _concurrent
raise NotImplementedError(
- "Concurrent decorator not supported for this method.")
+ "Concurrent decorator not supported for '%s' method." % fn.func_name)
diff --git a/libmproxy/version.py b/libmproxy/version.py
index 7836c849..0af60af5 100644
--- a/libmproxy/version.py
+++ b/libmproxy/version.py
@@ -1,4 +1,6 @@
-IVERSION = (0, 12, 2)
+from __future__ import (absolute_import, print_function, division)
+
+IVERSION = (0, 13, 1)
VERSION = ".".join(str(i) for i in IVERSION)
MINORVERSION = ".".join(str(i) for i in IVERSION[:2])
NAME = "mitmproxy"
diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py
index 29ae9e7a..d6082ee2 100644
--- a/libmproxy/web/app.py
+++ b/libmproxy/web/app.py
@@ -81,7 +81,8 @@ class WebSocketEventBroadcaster(tornado.websocket.WebSocketHandler):
@classmethod
def broadcast(cls, **kwargs):
- message = json.dumps(kwargs)
+ message = json.dumps(kwargs, ensure_ascii=False)
+
for conn in cls.connections:
try:
conn.write_message(message)