Merge branch 'master' of github.com:mitmproxy/mitmproxy

This commit is contained in:
Maximilian Hils 2015-04-26 18:41:40 +02:00
commit f96e4957b1
4 changed files with 91 additions and 113 deletions

2
dev
View File

@ -2,7 +2,7 @@
set -e set -e
VENV=../venv.mitmproxy VENV=../venv.mitmproxy
python2 -m virtualenv $VENV python -m virtualenv $VENV
source $VENV/bin/activate source $VENV/bin/activate
pip install --src .. -r requirements.txt pip install --src .. -r requirements.txt

View File

@ -196,18 +196,36 @@ def raw_format_flow(f, focus, extended, padding):
def save_data(path, data, master, state): def save_data(path, data, master, state):
if not path: if not path:
return return
path = os.path.expanduser(path)
try: try:
with file(path, "wb") as f: with file(path, "wb") as f:
f.write(data) f.write(data)
except IOError, v: except IOError, v:
signals.status_message.send(message=v.strerror) signals.status_message.send(message=v.strerror)
def ask_save_overwite(path, data, master, state):
if not path:
return
path = os.path.expanduser(path)
if os.path.exists(path):
def save_overwite(k):
if k == "y":
save_data(path, data, master, state)
signals.status_prompt_onekey.send(
prompt = "'"+path+"' already exists. Overwite?",
keys = (
("yes", "y"),
("no", "n"),
),
callback = save_overwite
)
else:
save_data(path, data, master, state)
def ask_save_path(prompt, data, master, state): def ask_save_path(prompt, data, master, state):
signals.status_prompt_path.send( signals.status_prompt_path.send(
prompt = prompt, prompt = prompt,
callback = save_data, callback = ask_save_overwite,
args = (data, master, state) args = (data, master, state)
) )

View File

@ -23,19 +23,6 @@ class KillSignal(Exception):
pass pass
def get_line(fp):
"""
Get a line, possibly preceded by a blank.
"""
line = fp.readline()
if line == "\r\n" or line == "\n":
# Possible leftover from previous message
line = fp.readline()
if line == "":
raise tcp.NetLibDisconnect()
return line
def send_connect_request(conn, host, port, update_state=True): def send_connect_request(conn, host, port, update_state=True):
upstream_request = HTTPRequest( upstream_request = HTTPRequest(
"authority", "authority",
@ -345,83 +332,29 @@ class HTTPRequest(HTTPMessage):
Raises: Raises:
HttpError: If the input is invalid. HttpError: If the input is invalid.
""" """
httpversion, host, port, scheme, method, path, headers, content, timestamp_start, timestamp_end = ( timestamp_start, timestamp_end = None, None
None, None, None, None, None, None, None, None, None, None)
timestamp_start = utils.timestamp() timestamp_start = utils.timestamp()
if hasattr(rfile, "reset_timestamps"): if hasattr(rfile, "reset_timestamps"):
rfile.reset_timestamps() rfile.reset_timestamps()
request_line = get_line(rfile) req = http.read_request(
rfile,
if hasattr(rfile, "first_byte_timestamp"): include_body = include_body,
# more accurate timestamp_start body_size_limit = body_size_limit,
timestamp_start = rfile.first_byte_timestamp wfile = wfile
)
request_line_parts = http.parse_init(request_line) timestamp_end = utils.timestamp()
if not request_line_parts:
raise http.HttpError(
400,
"Bad HTTP request line: %s" % repr(request_line)
)
method, path, httpversion = request_line_parts
if path == '*' or path.startswith("/"):
form_in = "relative"
if not netlib.utils.isascii(path):
raise http.HttpError(
400,
"Bad HTTP request line: %s" % repr(request_line)
)
elif method.upper() == 'CONNECT':
form_in = "authority"
r = http.parse_init_connect(request_line)
if not r:
raise http.HttpError(
400,
"Bad HTTP request line: %s" % repr(request_line)
)
host, port, _ = r
path = None
else:
form_in = "absolute"
r = http.parse_init_proxy(request_line)
if not r:
raise http.HttpError(
400,
"Bad HTTP request line: %s" % repr(request_line)
)
_, scheme, host, port, path, _ = r
headers = http.read_headers(rfile)
if headers is None:
raise http.HttpError(400, "Invalid headers")
expect_header = headers.get_first("expect")
if expect_header and expect_header.lower() == "100-continue" and httpversion >= (1, 1):
wfile.write(
'HTTP/1.1 100 Continue\r\n'
'\r\n'
)
wfile.flush()
del headers['expect']
if include_body:
content = http.read_http_body(rfile, headers, body_size_limit,
method, None, True)
timestamp_end = utils.timestamp()
return HTTPRequest( return HTTPRequest(
form_in, req.form_in,
method, req.method,
scheme, req.scheme,
host, req.host,
port, req.port,
path, req.path,
httpversion, req.httpversion,
headers, req.headers,
content, req.content,
timestamp_start, timestamp_start,
timestamp_end timestamp_end
) )
@ -937,7 +870,6 @@ class HTTPResponse(HTTPMessage):
self.headers["Set-Cookie"] = values self.headers["Set-Cookie"] = values
class HTTPFlow(Flow): class HTTPFlow(Flow):
""" """
A HTTPFlow is a collection of objects representing a single HTTP A HTTPFlow is a collection of objects representing a single HTTP
@ -1377,7 +1309,8 @@ class HTTPHandler(ProtocolHandler):
) )
if needs_server_change: if needs_server_change:
# force create new connection to the proxy server to reset state # force create new connection to the proxy server to reset
# state
self.live.change_server(self.c.server_conn.address, force=True) self.live.change_server(self.c.server_conn.address, force=True)
if ssl: if ssl:
send_connect_request( send_connect_request(
@ -1387,8 +1320,9 @@ class HTTPHandler(ProtocolHandler):
) )
self.c.establish_ssl(server=True) self.c.establish_ssl(server=True)
else: else:
# If we're not in upstream mode, we just want to update the host and # If we're not in upstream mode, we just want to update the host
# possibly establish TLS. This is a no op if the addresses match. # and possibly establish TLS. This is a no op if the addresses
# match.
self.live.change_server(address, ssl=ssl) self.live.change_server(address, ssl=ssl)
flow.server_conn = self.c.server_conn flow.server_conn = self.c.server_conn
@ -1396,8 +1330,8 @@ class HTTPHandler(ProtocolHandler):
def send_response_to_client(self, flow): def send_response_to_client(self, flow):
if not flow.response.stream: if not flow.response.stream:
# no streaming: # no streaming:
# we already received the full response from the server and can send # we already received the full response from the server and can
# it to the client straight away. # send it to the client straight away.
self.c.client_conn.send(flow.response.assemble()) self.c.client_conn.send(flow.response.assemble())
else: else:
# streaming: # streaming:
@ -1435,8 +1369,10 @@ class HTTPHandler(ProtocolHandler):
flow.response.code) == -1) flow.response.code) == -1)
if close_connection: if close_connection:
if flow.request.form_in == "authority" and flow.response.code == 200: if flow.request.form_in == "authority" and flow.response.code == 200:
# Workaround for https://github.com/mitmproxy/mitmproxy/issues/313: # Workaround for
# Some proxies (e.g. Charles) send a CONNECT response with HTTP/1.0 and no Content-Length header # https://github.com/mitmproxy/mitmproxy/issues/313: Some
# proxies (e.g. Charles) send a CONNECT response with HTTP/1.0
# and no Content-Length header
pass pass
else: else:
return True return True
@ -1458,14 +1394,16 @@ class HTTPHandler(ProtocolHandler):
self.expected_form_out = "relative" self.expected_form_out = "relative"
self.skip_authentication = True self.skip_authentication = True
# In practice, nobody issues a CONNECT request to send unencrypted HTTP requests afterwards. # In practice, nobody issues a CONNECT request to send unencrypted
# If we don't delegate to TCP mode, we should always negotiate a SSL connection. # HTTP requests afterwards. If we don't delegate to TCP mode, we
# should always negotiate a SSL connection.
# #
# FIXME: # FIXME: Turns out the previous statement isn't entirely true.
# Turns out the previous statement isn't entirely true. Chrome on Windows CONNECTs to :80 # Chrome on Windows CONNECTs to :80 if an explicit proxy is
# if an explicit proxy is configured and a websocket connection should be established. # configured and a websocket connection should be established. We
# We don't support websocket at the moment, so it fails anyway, but we should come up with # don't support websocket at the moment, so it fails anyway, but we
# a better solution to this if we start to support WebSockets. # should come up with a better solution to this if we start to
# support WebSockets.
should_establish_ssl = ( should_establish_ssl = (
address.port in self.c.config.ssl_ports address.port in self.c.config.ssl_ports
or or
@ -1473,12 +1411,18 @@ class HTTPHandler(ProtocolHandler):
) )
if should_establish_ssl: if should_establish_ssl:
self.c.log("Received CONNECT request to SSL port. Upgrading to SSL...", "debug") self.c.log(
"Received CONNECT request to SSL port. "
"Upgrading to SSL...", "debug"
)
self.c.establish_ssl(server=True, client=True) self.c.establish_ssl(server=True, client=True)
self.c.log("Upgrade to SSL completed.", "debug") self.c.log("Upgrade to SSL completed.", "debug")
if self.c.config.check_tcp(address): if self.c.config.check_tcp(address):
self.c.log("Generic TCP mode for host: %s:%s" % address(), "info") self.c.log(
"Generic TCP mode for host: %s:%s" % address(),
"info"
)
TCPHandler(self.c).handle_messages() TCPHandler(self.c).handle_messages()
return False return False
@ -1499,7 +1443,8 @@ class RequestReplayThread(threading.Thread):
def __init__(self, config, flow, masterq, should_exit): def __init__(self, config, flow, masterq, should_exit):
""" """
masterqueue can be a queue or None, if no scripthooks should be processed. masterqueue can be a queue or None, if no scripthooks should be
processed.
""" """
self.config, self.flow = config, flow self.config, self.flow = config, flow
if masterq: if masterq:
@ -1525,12 +1470,17 @@ class RequestReplayThread(threading.Thread):
if not self.flow.response: if not self.flow.response:
# In all modes, we directly connect to the server displayed # In all modes, we directly connect to the server displayed
if self.config.mode == "upstream": if self.config.mode == "upstream":
server_address = self.config.mode.get_upstream_server(self.flow.client_conn)[2:] server_address = self.config.mode.get_upstream_server(
self.flow.client_conn
)[2:]
server = ServerConnection(server_address) server = ServerConnection(server_address)
server.connect() server.connect()
if r.scheme == "https": if r.scheme == "https":
send_connect_request(server, r.host, r.port) send_connect_request(server, r.host, r.port)
server.establish_ssl(self.config.clientcerts, sni=self.flow.server_conn.sni) server.establish_ssl(
self.config.clientcerts,
sni=self.flow.server_conn.sni
)
r.form_out = "relative" r.form_out = "relative"
else: else:
r.form_out = "absolute" r.form_out = "absolute"
@ -1539,12 +1489,18 @@ class RequestReplayThread(threading.Thread):
server = ServerConnection(server_address) server = ServerConnection(server_address)
server.connect() server.connect()
if r.scheme == "https": if r.scheme == "https":
server.establish_ssl(self.config.clientcerts, sni=self.flow.server_conn.sni) server.establish_ssl(
self.config.clientcerts,
sni=self.flow.server_conn.sni
)
r.form_out = "relative" r.form_out = "relative"
server.send(r.assemble()) server.send(r.assemble())
self.flow.server_conn = server self.flow.server_conn = server
self.flow.response = HTTPResponse.from_stream(server.rfile, r.method, self.flow.response = HTTPResponse.from_stream(
body_size_limit=self.config.body_size_limit) server.rfile,
r.method,
body_size_limit=self.config.body_size_limit
)
if self.channel: if self.channel:
response_reply = self.channel.ask("response", self.flow) response_reply = self.channel.ask("response", self.flow)
if response_reply is None or response_reply == KILL: if response_reply is None or response_reply == KILL:
@ -1554,7 +1510,8 @@ class RequestReplayThread(threading.Thread):
if self.channel: if self.channel:
self.channel.ask("error", self.flow) self.channel.ask("error", self.flow)
except KillSignal: except KillSignal:
# KillSignal should only be raised if there's a channel in the first place. # KillSignal should only be raised if there's a channel in the
# first place.
self.channel.tell("log", proxy.Log("Connection killed", "info")) self.channel.tell("log", proxy.Log("Connection killed", "info"))
finally: finally:
r.form_out = form_out_backup r.form_out = form_out_backup

View File

@ -205,7 +205,10 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin):
# Now check that the connection is closed as the client specifies # Now check that the connection is closed as the client specifies
p = self.pathoc() p = self.pathoc()
assert p.request("get:'%s':h'Connection'='close'"%response) assert p.request("get:'%s':h'Connection'='close'"%response)
tutils.raises("disconnect", p.request, "get:'%s'"%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.
tutils.raises(Exception, p.request, "get:'%s'"%response)
def test_reconnect(self): def test_reconnect(self):
req = "get:'%s/p/200:b@1:da'"%self.server.urlbase req = "get:'%s/p/200:b@1:da'"%self.server.urlbase