aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/addons/dumper.py3
-rw-r--r--mitmproxy/flow.py5
-rw-r--r--mitmproxy/io_compat.py1
-rw-r--r--mitmproxy/proxy/protocol/http.py193
-rw-r--r--mitmproxy/proxy/protocol/http1.py2
-rw-r--r--mitmproxy/proxy/protocol/http2.py7
-rw-r--r--mitmproxy/tools/console/common.py9
7 files changed, 120 insertions, 100 deletions
diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py
index d690c000..fb92c629 100644
--- a/mitmproxy/addons/dumper.py
+++ b/mitmproxy/addons/dumper.py
@@ -116,7 +116,8 @@ class Dumper:
else:
client = ""
- method = flow.request.method
+ pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else ''
+ method = flow.request.method + pushed
method_color = dict(
GET="green",
DELETE="red"
diff --git a/mitmproxy/flow.py b/mitmproxy/flow.py
index ff7a2b4a..18395be3 100644
--- a/mitmproxy/flow.py
+++ b/mitmproxy/flow.py
@@ -7,7 +7,7 @@ from mitmproxy import stateobject
from mitmproxy import connections
from mitmproxy import version
-from typing import Optional # noqa
+from typing import Optional, Dict # noqa
class Error(stateobject.StateObject):
@@ -83,6 +83,7 @@ class Flow(stateobject.StateObject):
self._backup = None # type: Optional[Flow]
self.reply = None # type: Optional[controller.Reply]
self.marked = False # type: bool
+ self.metadata = dict() # type: Dict[str, str]
_stateobject_attributes = dict(
id=str,
@@ -92,6 +93,7 @@ class Flow(stateobject.StateObject):
type=str,
intercepted=bool,
marked=bool,
+ metadata=dict,
)
def get_state(self):
@@ -120,6 +122,7 @@ class Flow(stateobject.StateObject):
f.live = False
f.client_conn = self.client_conn.copy()
f.server_conn = self.server_conn.copy()
+ f.metadata = self.metadata.copy()
if self.error:
f.error = self.error.copy()
diff --git a/mitmproxy/io_compat.py b/mitmproxy/io_compat.py
index e1ca27b2..b1b5a296 100644
--- a/mitmproxy/io_compat.py
+++ b/mitmproxy/io_compat.py
@@ -69,6 +69,7 @@ def convert_018_019(data):
data["client_conn"]["sni"] = None
data["client_conn"]["cipher_name"] = None
data["client_conn"]["tls_version"] = None
+ data["metadata"] = dict()
return data
diff --git a/mitmproxy/proxy/protocol/http.py b/mitmproxy/proxy/protocol/http.py
index a47fb455..542f6a94 100644
--- a/mitmproxy/proxy/protocol/http.py
+++ b/mitmproxy/proxy/protocol/http.py
@@ -12,14 +12,14 @@ from mitmproxy.net import websockets
class _HttpTransmissionLayer(base.Layer):
- def read_request_headers(self):
+ def read_request_headers(self, flow):
raise NotImplementedError()
def read_request_body(self, request):
raise NotImplementedError()
- def read_request(self):
- request = self.read_request_headers()
+ def read_request(self, f):
+ request = self.read_request_headers(f)
request.data.content = b"".join(
self.read_request_body(request)
)
@@ -125,7 +125,7 @@ class HttpLayer(base.Layer):
def __init__(self, ctx, mode):
super().__init__(ctx)
self.mode = mode
-
+ self.flow = None # type: http.HTTPFlow
self.__initial_server_conn = None
"Contains the original destination in transparent mode, which needs to be restored"
"if an inline script modified the target server for a single http request"
@@ -140,105 +140,112 @@ class HttpLayer(base.Layer):
self.__initial_server_tls = self.server_tls
self.__initial_server_conn = self.server_conn
while True:
- f = http.HTTPFlow(self.client_conn, self.server_conn, live=self)
- try:
- request = self.get_request_from_client(f)
- # Make sure that the incoming request matches our expectations
- self.validate_request(request)
- except exceptions.HttpReadDisconnect:
- # don't throw an error for disconnects that happen before/between requests.
+ self.flow = http.HTTPFlow(self.client_conn, self.server_conn, live=self)
+ if not self._process_flow(self.flow):
return
- except exceptions.HttpException as e:
- # We optimistically guess there might be an HTTP client on the
- # other end
- self.send_error_response(400, repr(e))
- raise exceptions.ProtocolException(
- "HTTP protocol error in client request: {}".format(e)
+
+ def _process_flow(self, f):
+ try:
+ request = self.get_request_from_client(f)
+ # Make sure that the incoming request matches our expectations
+ self.validate_request(request)
+ except exceptions.HttpReadDisconnect:
+ # don't throw an error for disconnects that happen before/between requests.
+ return False
+ except exceptions.HttpException as e:
+ # We optimistically guess there might be an HTTP client on the
+ # other end
+ self.send_error_response(400, repr(e))
+ raise exceptions.ProtocolException(
+ "HTTP protocol error in client request: {}".format(e)
+ )
+
+ self.log("request", "debug", [repr(request)])
+
+ # Handle Proxy Authentication
+ # Proxy Authentication conceptually does not work in transparent mode.
+ # We catch this misconfiguration on startup. Here, we sort out requests
+ # after a successful CONNECT request (which do not need to be validated anymore)
+ if not (self.http_authenticated or self.authenticate(request)):
+ return False
+
+ f.request = request
+
+ try:
+ # Regular Proxy Mode: Handle CONNECT
+ if self.mode == "regular" and request.first_line_format == "authority":
+ self.handle_regular_mode_connect(request)
+ return False
+ except (exceptions.ProtocolException, exceptions.NetlibException) as e:
+ # HTTPS tasting means that ordinary errors like resolution and
+ # connection errors can happen here.
+ self.send_error_response(502, repr(e))
+ f.error = flow.Error(str(e))
+ self.channel.ask("error", f)
+ return False
+
+ # update host header in reverse proxy mode
+ if self.config.options.mode == "reverse":
+ f.request.headers["Host"] = self.config.upstream_server.address.host
+
+ # set upstream auth
+ if self.mode == "upstream" and self.config.upstream_auth is not None:
+ f.request.headers["Proxy-Authorization"] = self.config.upstream_auth
+ self.process_request_hook(f)
+
+ try:
+ if websockets.check_handshake(request.headers) and websockets.check_client_version(request.headers):
+ # We only support RFC6455 with WebSockets version 13
+ # allow inline scripts to manipulate the client handshake
+ self.channel.ask("websocket_handshake", f)
+
+ if not f.response:
+ self.establish_server_connection(
+ f.request.host,
+ f.request.port,
+ f.request.scheme
)
+ self.get_response_from_server(f)
+ else:
+ # response was set by an inline script.
+ # we now need to emulate the responseheaders hook.
+ self.channel.ask("responseheaders", f)
- self.log("request", "debug", [repr(request)])
+ self.log("response", "debug", [repr(f.response)])
+ self.channel.ask("response", f)
+ self.send_response_to_client(f)
- # Handle Proxy Authentication
- # Proxy Authentication conceptually does not work in transparent mode.
- # We catch this misconfiguration on startup. Here, we sort out requests
- # after a successful CONNECT request (which do not need to be validated anymore)
- if not (self.http_authenticated or self.authenticate(request)):
- return
+ if self.check_close_connection(f):
+ return False
+
+ # Handle 101 Switching Protocols
+ if f.response.status_code == 101:
+ self.handle_101_switching_protocols(f)
+ return False # should never be reached
- f.request = request
-
- try:
- # Regular Proxy Mode: Handle CONNECT
- if self.mode == "regular" and request.first_line_format == "authority":
- self.handle_regular_mode_connect(request)
- return
- except (exceptions.ProtocolException, exceptions.NetlibException) as e:
- # HTTPS tasting means that ordinary errors like resolution and
- # connection errors can happen here.
- self.send_error_response(502, repr(e))
+ # Upstream Proxy Mode: Handle CONNECT
+ if f.request.first_line_format == "authority" and f.response.status_code == 200:
+ self.handle_upstream_mode_connect(f.request.copy())
+ return False
+
+ except (exceptions.ProtocolException, exceptions.NetlibException) as e:
+ self.send_error_response(502, repr(e))
+ if not f.response:
f.error = flow.Error(str(e))
self.channel.ask("error", f)
- return
+ return False
+ else:
+ raise exceptions.ProtocolException(
+ "Error in HTTP connection: %s" % repr(e)
+ )
+ finally:
+ if f:
+ f.live = False
- # update host header in reverse proxy mode
- if self.config.options.mode == "reverse":
- f.request.headers["Host"] = self.config.upstream_server.address.host
-
- # set upstream auth
- if self.mode == "upstream" and self.config.upstream_auth is not None:
- f.request.headers["Proxy-Authorization"] = self.config.upstream_auth
- self.process_request_hook(f)
-
- try:
- if websockets.check_handshake(request.headers) and websockets.check_client_version(request.headers):
- # We only support RFC6455 with WebSockets version 13
- # allow inline scripts to manipulate the client handshake
- self.channel.ask("websocket_handshake", f)
-
- if not f.response:
- self.establish_server_connection(
- f.request.host,
- f.request.port,
- f.request.scheme
- )
- self.get_response_from_server(f)
- else:
- # response was set by an inline script.
- # we now need to emulate the responseheaders hook.
- self.channel.ask("responseheaders", f)
-
- self.log("response", "debug", [repr(f.response)])
- self.channel.ask("response", f)
- self.send_response_to_client(f)
-
- if self.check_close_connection(f):
- return
-
- # Handle 101 Switching Protocols
- if f.response.status_code == 101:
- return self.handle_101_switching_protocols(f)
-
- # Upstream Proxy Mode: Handle CONNECT
- if f.request.first_line_format == "authority" and f.response.status_code == 200:
- self.handle_upstream_mode_connect(f.request.copy())
- return
-
- except (exceptions.ProtocolException, exceptions.NetlibException) as e:
- self.send_error_response(502, repr(e))
- if not f.response:
- f.error = flow.Error(str(e))
- self.channel.ask("error", f)
- return
- else:
- raise exceptions.ProtocolException(
- "Error in HTTP connection: %s" % repr(e)
- )
- finally:
- if f:
- f.live = False
+ return True
def get_request_from_client(self, f):
- request = self.read_request()
+ request = self.read_request(f)
f.request = request
self.channel.ask("requestheaders", f)
if request.headers.get("expect", "").lower() == "100-continue":
diff --git a/mitmproxy/proxy/protocol/http1.py b/mitmproxy/proxy/protocol/http1.py
index 713c48a7..b1fd0ecd 100644
--- a/mitmproxy/proxy/protocol/http1.py
+++ b/mitmproxy/proxy/protocol/http1.py
@@ -9,7 +9,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer):
super().__init__(ctx)
self.mode = mode
- def read_request_headers(self):
+ def read_request_headers(self, flow):
return http.HTTPRequest.wrap(
http1.read_request_head(self.client_conn.rfile)
)
diff --git a/mitmproxy/proxy/protocol/http2.py b/mitmproxy/proxy/protocol/http2.py
index d7d3d651..f440d100 100644
--- a/mitmproxy/proxy/protocol/http2.py
+++ b/mitmproxy/proxy/protocol/http2.py
@@ -243,6 +243,7 @@ class Http2Layer(base.Layer):
def _handle_pushed_stream_received(self, event, h2_connection):
# pushed stream ids should be unique and not dependent on race conditions
# only the parent stream id must be looked up first
+
parent_eid = self.server_to_client_stream_ids[event.parent_stream_id]
with self.client_conn.h2.lock:
self.client_conn.h2.push_stream(parent_eid, event.pushed_stream_id, event.headers)
@@ -455,9 +456,13 @@ class Http2SingleStreamLayer(httpbase._HttpTransmissionLayer, basethread.BaseThr
raise exceptions.Http2ZombieException("Connection already dead")
@detect_zombie_stream
- def read_request_headers(self):
+ def read_request_headers(self, flow):
self.request_arrived.wait()
self.raise_zombie()
+
+ if self.pushed:
+ flow.metadata['h2-pushed-stream'] = True
+
first_line_format, method, scheme, host, port, path = http2.parse_headers(self.request_headers)
return http.HTTPRequest(
first_line_format,
diff --git a/mitmproxy/tools/console/common.py b/mitmproxy/tools/console/common.py
index 78860702..08bf0b67 100644
--- a/mitmproxy/tools/console/common.py
+++ b/mitmproxy/tools/console/common.py
@@ -327,7 +327,7 @@ def export_to_clip_or_file(key, scope, flow, writer):
@lru_cache(maxsize=800)
-def raw_format_flow(f):
+def raw_format_flow(f, flow):
f = dict(f)
pile = []
req = []
@@ -346,7 +346,9 @@ def raw_format_flow(f):
if f["req_is_replay"]:
req.append(fcol(SYMBOL_REPLAY, "replay"))
- req.append(fcol(f["req_method"], "method"))
+
+ pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else ''
+ req.append(fcol(f["req_method"] + pushed, "method"))
preamble = sum(i[1] for i in req) + len(req) - 1
@@ -451,10 +453,11 @@ def format_flow(f, focus, extended=False, hostheader=False, max_url_len=False):
resp_clen = contentdesc,
roundtrip = roundtrip,
))
+
t = f.response.headers.get("content-type")
if t:
d["resp_ctype"] = t.split(";")[0]
else:
d["resp_ctype"] = ""
- return raw_format_flow(tuple(sorted(d.items())))
+ return raw_format_flow(tuple(sorted(d.items())), f)