mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-02-03 16:55:33 +00:00
http2: add push indicator
This commit is contained in:
parent
11d266419c
commit
d86cb76e5b
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
f.request = request
|
||||
# Handle 101 Switching Protocols
|
||||
if f.response.status_code == 101:
|
||||
self.handle_101_switching_protocols(f)
|
||||
return False # should never be reached
|
||||
|
||||
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":
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -242,6 +242,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)
|
||||
@ -454,9 +455,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,
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user